Skip to content

chore: add a rdagent server with UI #553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 66 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
cf3547a
change_log_object
SunsetWolf Feb 5, 2025
120e5c1
lint code
SunsetWolf Feb 5, 2025
82f2c10
delete comments
SunsetWolf Feb 5, 2025
1f924fd
change_log_object
SunsetWolf Feb 6, 2025
69c709a
change_log_object
SunsetWolf Feb 6, 2025
54c2028
fix import test error
SunsetWolf Feb 6, 2025
5fd1a6d
update code
SunsetWolf Feb 16, 2025
6040976
update code
SunsetWolf Feb 19, 2025
330563a
fix bugs
SunsetWolf Feb 24, 2025
3c19613
skip mypy error
SunsetWolf Feb 27, 2025
f8a1e0a
skip mypy error
SunsetWolf Feb 27, 2025
cd3d69b
skip mypy error
SunsetWolf Feb 27, 2025
a2015cf
Merge branch 'main' of github.com:microsoft/RD-Agent into change_log_…
SunsetWolf Feb 27, 2025
2f1c1a2
Start the flask server before running the demo.
SunsetWolf Feb 28, 2025
dad1844
achieve front and back interaction
SunsetWolf Mar 6, 2025
4b92bb3
Merge branch 'main' of github.com:microsoft/RD-Agent into change_log_…
SunsetWolf Mar 7, 2025
d874e94
fix github-advanced-security comments
SunsetWolf Mar 7, 2025
1b46f92
fix github-advanced-security comments
SunsetWolf Mar 7, 2025
261584d
tmp ignore
WinstonLiyt May 22, 2025
4c2b93a
Merge branch 'main' into change_log_object
WinstonLiyt May 22, 2025
fd36e03
fix CI
XianBW May 22, 2025
4d0ab30
move some logic
XianBW May 22, 2025
5ed4570
change format
XianBW May 22, 2025
567d0e3
adjust logic
XianBW May 22, 2025
f51ed8d
log2json changes
XianBW May 22, 2025
f5eba24
tmp
XianBW May 22, 2025
dc8628b
fix
XianBW May 22, 2025
2d82403
fix bug
XianBW May 22, 2025
a4fa043
refine log2json between 5 scenarios
XianBW May 22, 2025
5b946af
fix
XianBW May 22, 2025
3214f44
refine codes
XianBW May 22, 2025
6298062
fix logic
XianBW May 23, 2025
beccfdf
use localhost
XianBW May 23, 2025
f9ada01
add loop & all_duration param for old scenario startup
XianBW May 23, 2025
8ee1735
merge control logic
XianBW May 23, 2025
4098b3c
add README for server ui api
XianBW May 23, 2025
65bcf90
update README
XianBW May 23, 2025
7a10c18
reuse code in logger
XianBW May 26, 2025
a63aa64
add loop_n and all_duration param
XianBW May 27, 2025
010e1ac
fix upload
XianBW May 27, 2025
a0aeaf0
ui server now use port in setting
XianBW May 28, 2025
5d7de98
fix port setting
XianBW May 28, 2025
5725ce0
fix port setting
XianBW May 28, 2025
fef8f2f
Merge branch 'main' into change_log_object
XianBW May 28, 2025
af5e2b4
Merge branch 'main' into change_log_object
XianBW Jun 3, 2025
cf63471
fix mypy check
XianBW Jun 3, 2025
5c67a24
refine logger and log storage
XianBW Jun 4, 2025
7f4ad96
fix ruff error
XianBW Jun 4, 2025
6144aab
fix CI
XianBW Jun 4, 2025
07e2f44
refine logger, loop, storage
XianBW Jun 5, 2025
15f97d3
Merge branch 'main' into change_log_object
XianBW Jun 5, 2025
c161186
bind one FileStorage with one logger
XianBW Jun 5, 2025
596004a
not truncate log storage
XianBW Jun 5, 2025
bbb6d24
refine LoopBase.load(), use `checkout` instead of `output_path` and `…
XianBW Jun 5, 2025
b02b9c1
clear session folder when loading loop to run
XianBW Jun 5, 2025
811f985
move component info init step to ExpGen Class
XianBW Jun 6, 2025
ce87698
Update rdagent/utils/workflow.py
you-n-g Jun 6, 2025
9bd725c
move truncate_session function to LoopBase class
XianBW Jun 6, 2025
fa9c588
Merge branch 'main' into change_log_object
XianBW Jun 6, 2025
42ce456
add checkout param for other scenarios
XianBW Jun 6, 2025
1eea64d
fix bug
XianBW Jun 6, 2025
cd3e55e
move WebStorage to UI
XianBW Jun 6, 2025
17e1136
change web_storage name
XianBW Jun 6, 2025
3d6a13b
add randomname to requirements
XianBW Jun 6, 2025
39551df
add typer
XianBW Jun 6, 2025
1b09488
fix requirements
XianBW Jun 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,7 @@ mlruns/
/*.sh
.aider*
rdagent/app/benchmark/factor/example.json

# UI Server resources
videos/
static/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ isort:
# First deal with the core folder, and then gradually increase the scope of detection,
# and eventually realize the detection of the complete project.
mypy:
$(PIPRUN) python -m mypy rdagent/core # --exclude rdagent/scripts,git_ignore_folder
$(PIPRUN) python -m mypy rdagent/core

# Check lint with ruff.
# First deal with the core folder, and then gradually increase the scope of detection,
Expand Down
8 changes: 8 additions & 0 deletions rdagent/app/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def ui(port=19899, log_dir="", debug=False):
subprocess.run(cmds)


def server_ui(port=19899):
"""
start web app to show the log traces in real time
"""
subprocess.run(["python", "rdagent/log/server/app.py", f"--port={port}"])


def app():
fire.Fire(
{
Expand All @@ -58,5 +65,6 @@ def app():
"health_check": health_check,
"collect_info": collect_info,
"kaggle": kaggle,
"server_ui": server_ui,
}
)
6 changes: 3 additions & 3 deletions rdagent/app/data_mining/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ModelRDLoop(RDLoop):
skip_loop_error = (ModelEmptyError,)


def main(path=None, step_n=None):
def main(path=None, step_n=None, loop_n=None, all_duration=None, checkout=True):
"""
Auto R&D Evolving loop for models in a medical scenario.

Expand All @@ -23,8 +23,8 @@ def main(path=None, step_n=None):
if path is None:
model_loop = ModelRDLoop(MED_PROP_SETTING)
else:
model_loop = ModelRDLoop.load(path)
model_loop.run(step_n=step_n)
model_loop = ModelRDLoop.load(path, checkout=checkout)
model_loop.run(step_n=step_n, loop_n=loop_n, all_duration=all_duration)


if __name__ == "__main__":
Expand Down
45 changes: 25 additions & 20 deletions rdagent/app/data_science/loop.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pathlib import Path

import fire

from rdagent.app.data_science.conf import DS_RD_SETTING
Expand All @@ -7,12 +9,11 @@


def main(
path=None,
output_path=None,
step_n=None,
loop_n=None,
path: str | None = None,
checkout: bool | str | Path = True,
step_n: int | None = None,
loop_n: int | None = None,
competition="bms-molecular-translation",
do_truncate=True,
timeout=None,
replace_timer=True,
exp_gen_cls: str | None = None,
Expand All @@ -22,29 +23,33 @@ def main(
Parameters
----------
path :
path like `$LOG_PATH/__session__/1/0_propose`. It indicates that we restore the state that after finish the step 0 in loop 1
output_path :
path like `$LOG_PATH`. It indicates that where we want to save our session and log information.
A path like `$LOG_PATH/__session__/1/0_propose`. This indicates that we restore the state after finishing step 0 in loop 1.
checkout :
Used only when a path is provided.
Can be True, False, or a path.
Default is True.
- If True, the new loop will use the existing folder and clear logs for sessions after the one corresponding to the given path.
- If False, the new loop will use the existing folder but keep the logs for sessions after the one corresponding to the given path.
- If a path (or a str like Path) is provided, the new loop will be saved to that path, leaving the original path unchanged.
step_n :
How many steps to run; if None, it will run forever until error or KeyboardInterrupt
Number of steps to run; if None, the process will run indefinitely until an error or KeyboardInterrupt occurs.
loop_n :
How many loops to run; if None, it will run forever until error or KeyboardInterrupt
- if current loop is incomplete, it will be counted as the first loop for completion.
- if both step_n and loop_n are provided, the process will stop as soon as either condition is met.
Number of loops to run; if None, the process will run indefinitely until an error or KeyboardInterrupt occurs.
- If the current loop is incomplete, it will be counted as the first loop for completion.
- If both step_n and loop_n are provided, the process will stop as soon as either condition is met.
competition :
do_truncate :
If set to True, the logger will truncate the future log messages by calling `logger.storage.truncate`.
Competition name.
replace_timer :
If session is loaded, should we replace the timer with session.timer
If a session is loaded, determines whether to replace the timer with session.timer.
exp_gen_cls :
When we have different stages, we can replace the exp_gen with the new proposal
When there are different stages, the exp_gen can be replaced with the new proposal.


Auto R&D Evolving loop for models in a Kaggle scenario.
You can continue running session by
You can continue running a session by using the command:
.. code-block:: bash
dotenv run -- python rdagent/app/data_science/loop.py [--competition titanic] $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional parameter
rdagent kaggle --competition playground-series-s4e8 # You are encouraged to use this one.
dotenv run -- python rdagent/app/data_science/loop.py [--competition titanic] $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is an optional parameter
rdagent kaggle --competition playground-series-s4e8 # This command is recommended.
"""
if competition is not None:
DS_RD_SETTING.competition = competition
Expand All @@ -55,7 +60,7 @@ def main(
if path is None:
kaggle_loop = DataScienceRDLoop(DS_RD_SETTING)
else:
kaggle_loop = DataScienceRDLoop.load(path, output_path, do_truncate, replace_timer)
kaggle_loop: DataScienceRDLoop = DataScienceRDLoop.load(path, checkout=checkout, replace_timer=replace_timer)

# replace exp_gen if we have new class
if exp_gen_cls is not None:
Expand Down
6 changes: 3 additions & 3 deletions rdagent/app/qlib_rd_loop/factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def running(self, prev_out: dict[str, Any]):
return exp


def main(path=None, step_n=None):
def main(path=None, step_n=None, loop_n=None, all_duration=None, checkout=True):
"""
Auto R&D Evolving loop for fintech factors.

Expand All @@ -39,8 +39,8 @@ def main(path=None, step_n=None):
if path is None:
model_loop = FactorRDLoop(FACTOR_PROP_SETTING)
else:
model_loop = FactorRDLoop.load(path)
model_loop.run(step_n=step_n)
model_loop = FactorRDLoop.load(path, checkout=checkout)
model_loop.run(step_n=step_n, loop_n=loop_n, all_duration=all_duration)


if __name__ == "__main__":
Expand Down
6 changes: 3 additions & 3 deletions rdagent/app/qlib_rd_loop/factor_from_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def coding(self, prev_out: dict[str, Any]):
return exp


def main(report_folder=None, path=None, step_n=None):
def main(report_folder=None, path=None, step_n=None, loop_n=None, all_duration=None, checkout=True):
"""
Auto R&D Evolving loop for fintech factors (the factors are extracted from finance reports).

Expand All @@ -158,11 +158,11 @@ def main(report_folder=None, path=None, step_n=None):
if path is None and report_folder is None:
model_loop = FactorReportLoop()
elif path is not None:
model_loop = FactorReportLoop.load(path)
model_loop = FactorReportLoop.load(path, checkout=checkout)
else:
model_loop = FactorReportLoop(report_folder=report_folder)

model_loop.run(step_n=step_n)
model_loop.run(step_n=step_n, loop_n=loop_n, all_duration=all_duration)


if __name__ == "__main__":
Expand Down
6 changes: 3 additions & 3 deletions rdagent/app/qlib_rd_loop/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ModelRDLoop(RDLoop):
skip_loop_error = (ModelEmptyError,)


def main(path=None, step_n=None):
def main(path=None, step_n=None, loop_n=None, all_duration=None, checkout=True):
"""
Auto R&D Evolving loop for fintech models

Expand All @@ -27,8 +27,8 @@ def main(path=None, step_n=None):
if path is None:
model_loop = ModelRDLoop(MODEL_PROP_SETTING)
else:
model_loop = ModelRDLoop.load(path)
model_loop.run(step_n=step_n)
model_loop = ModelRDLoop.load(path, checkout=checkout)
model_loop.run(step_n=step_n, loop_n=loop_n, all_duration=all_duration)


if __name__ == "__main__":
Expand Down
6 changes: 3 additions & 3 deletions rdagent/app/qlib_rd_loop/quant.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def feedback(self, prev_out: dict[str, Any]):
self.trace.hist.append((prev_out["running"], feedback))


def main(path=None, step_n=None):
def main(path=None, step_n=None, loop_n=None, all_duration=None, checkout=True):
"""
Auto R&D Evolving loop for fintech factors.
You can continue running session by
Expand All @@ -129,8 +129,8 @@ def main(path=None, step_n=None):
if path is None:
quant_loop = QuantRDLoop(QUANT_PROP_SETTING)
else:
quant_loop = QuantRDLoop.load(path)
quant_loop.run(step_n=step_n)
quant_loop = QuantRDLoop.load(path, checkout=checkout)
quant_loop.run(step_n=step_n, loop_n=loop_n, all_duration=all_duration)


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions rdagent/app/utils/ape.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pickle
from pathlib import Path

from rdagent.core.conf import RD_AGENT_SETTINGS
from rdagent.log.conf import LOG_SETTINGS


def get_llm_qa(file_path):
Expand All @@ -21,7 +21,7 @@ def get_llm_qa(file_path):

# Example usage
# use
file_path = Path(RD_AGENT_SETTINGS.log_trace_path) / "debug_llm.pkl"
file_path = Path(LOG_SETTINGS.trace_path) / "debug_llm.pkl"
llm_qa = get_llm_qa(file_path)
print(len(llm_qa))

Expand Down
6 changes: 0 additions & 6 deletions rdagent/core/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

# TODO: use pydantic for other modules in Qlib
from pathlib import Path
from typing import cast

Expand Down Expand Up @@ -45,11 +44,6 @@ def base_iter(settings_cls: type[ExtendedBaseSettings]) -> list[type[ExtendedBas


class RDAgentSettings(ExtendedBaseSettings):
# TODO: (xiao) I think LLMSetting may be a better name.
# TODO: (xiao) I think most of the config should be in oai.config
# Log configs
# TODO: (xiao) think it can be a separate config.
log_trace_path: str | None = None

# azure document intelligence configs
azure_document_intelligence_key: str = ""
Expand Down
16 changes: 12 additions & 4 deletions rdagent/log/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Literal, Optional, Union
from typing import Literal, Optional


@dataclass
Expand Down Expand Up @@ -43,10 +43,8 @@ class Storage:
def log(
self,
obj: object,
name: str = "",
save_type: Literal["json", "text", "pkl"] = "text",
tag: str = "",
timestamp: datetime | None = None,
**kwargs: dict,
) -> str | Path:
"""

Expand All @@ -72,6 +70,16 @@ def iter_msg(self) -> Generator[Message, None, None]:
"""
...

@abstractmethod
def truncate(self, time: datetime) -> None:
"""
Remove all log entries after the specified time.
"""
...

def __str__(self) -> str:
return self.__class__.__name__


class View:
"""
Expand Down
24 changes: 24 additions & 0 deletions rdagent/log/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from pydantic_settings import SettingsConfigDict

from rdagent.core.conf import ExtendedBaseSettings


class LogSettings(ExtendedBaseSettings):
model_config = SettingsConfigDict(env_prefix="LOG_", protected_namespaces=())

trace_path: str = str(Path.cwd() / "log" / datetime.now(timezone.utc).strftime("%Y-%m-%d_%H-%M-%S-%f"))

ui_server_port: int | None = None

storages: dict[str, list[int | str]] = {}

def model_post_init(self, _context: Any, /) -> None:
if self.ui_server_port is not None:
self.storages["rdagent.log.ui.storage.WebStorage"] = [self.ui_server_port, self.trace_path]


LOG_SETTINGS = LogSettings()
Loading