chore: import upstream snapshot with attribution
CI / ci (3.11) (push) Has been cancelled
CI / ci (3.10) (push) Has been cancelled
CI / dependabot (push) Has been cancelled
Release / release_and_publish (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:15 +08:00
commit e64161ec32
892 changed files with 116950 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
# 🐳 Run Docker & Qlib
---
## 📄 Description
This guide explains how to run the Qlib Docker test file located at `test/utils/test_env.py` in the RD-Agent repository.
---
## 🚀 Running Instructions
### 1. Install the required Python libraries
- Ensure that the `docker` Python library is installed:
```sh
pip install docker
```
### 2. Run the test script
- Execute the test script to verify the Docker environment setup:
```sh
python test/utils/test_env.py
```
### Troubleshooting
- **PermissionError: [Errno 13] Permission denied.**
> This error occurs when the current user does not have the necessary permissions to access the Docker socket. To resolve this issue, follow these steps:
1. **Add the current user to the `docker` group**
Docker requires root or `docker` group user permissions to access the Docker socket. Add the current user to the `docker` group:
```sh
sudo usermod -aG docker $USER
```
2. **Refresh group changes**
To apply the group changes, log out and log back in, or use the following command:
```sh
newgrp docker
```
3. **Verify Docker access**
Run the following command to ensure that Docker can be accessed:
```sh
docker run hello-world
```
4. **Rerun the test script**
After completing these steps, rerun the test script:
```sh
python test/utils/test_env.py
```
---
## 🛠️ Detailed Qlib Docker Function Framework
Here, we provide an overview of the specific functions within the Qlib Docker framework, their purposes, and examples of how to call them.
### QTDockerEnv Class in `env.py`
The `QTDockerEnv` class is responsible for setting up and running Docker environments for Qlib experiments.
#### Methods:
1. **prepare()**
- **Purpose**: Prepares the Docker environment for running experiments. This includes building the Docker image if necessary.
- **Example**:
```python
qtde = QTDockerEnv()
qtde.prepare()
```
2. **run(local_path: str, entry: str) -> str**
- **Purpose**: Runs a specified entry point (e.g., a configuration file) in the prepared Docker environment.
- **Parameters**:
- `local_path`: Path to the local directory to mount into the Docker container.
- `entry`: Command or entry point to run inside the Docker container.
- **Returns**: The stdout output from the Docker container.
- **Example**:
```python
result = qtde.run(local_path="/path/to/env_tpl", entry="qrun conf.yaml")
```
---
### 📊 Expected Output
Upon successful execution, the test script will produce analysis results of benchmark returns and various risk metrics. The expected output should be similar to:
```
'The following are analysis results of benchmark return (1 day).'
risk
mean 0.000477
std 0.012295
annualized_return 0.113561
information_ratio 0.598699
max_drawdown -0.370479
'The following are analysis results of the excess return without cost (1 day).'
risk
mean 0.000530
std 0.005718
annualized_return 0.126029
information_ratio 1.428574
max_drawdown -0.072310
'The following are analysis results of the excess return with cost (1 day).'
risk
mean 0.000339
std 0.005717
annualized_return 0.080654
information_ratio 0.914486
max_drawdown -0.086083
'The following are analysis results of indicators (1 day).'
value
ffr 1.0
pa 0.0
pos 0.0
```
By following these steps and using the provided functions, you should be able to run the Qlib Docker tests and obtain the expected analysis results.
+54
View File
@@ -0,0 +1,54 @@
import unittest
class CoSTEERTest(unittest.TestCase):
def setUp(self):
self.test_competition = "aerial-cactus-identification"
def tearDown(self):
pass
def to_str(self, obj):
return "".join(str(obj).split())
def test_data_loader(self):
from rdagent.components.coder.data_science.raw_data_loader.test import (
develop_one_competition,
)
# if all tasks in exp are failed, will raise CoderError
exp = develop_one_competition(self.test_competition)
def test_feature(self):
from rdagent.components.coder.data_science.feature.test import (
develop_one_competition,
)
exp = develop_one_competition(self.test_competition)
def test_model(self):
from rdagent.components.coder.data_science.model.test import (
develop_one_competition,
)
exp = develop_one_competition(self.test_competition)
def test_ensemble(self):
from rdagent.components.coder.data_science.ensemble.test import (
develop_one_competition,
)
exp = develop_one_competition(self.test_competition)
def test_workflow(self):
from rdagent.components.coder.data_science.workflow.test import (
develop_one_competition,
)
exp = develop_one_competition(self.test_competition)
if __name__ == "__main__":
unittest.main()
# pytest test/utils/coder/test_CoSTEER.py
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
import unittest
from rdagent.oai.llm_utils import APIBackend
from rdagent.utils.agent.ret import PythonAgentOut
from rdagent.utils.agent.tpl import T
class TestAgentInfra(unittest.TestCase):
def test_agent_infra(self):
# NOTE: It is not serious. It is just for testing
sys_prompt = T("components.proposal.prompts:hypothesis_gen.system_prompt").r(
targets="targets",
scenario=T("scenarios.qlib.experiment.prompts:qlib_model_background").r(),
hypothesis_output_format=PythonAgentOut.get_spec(),
hypothesis_specification=PythonAgentOut.get_spec(),
)
user_prompt = T("components.proposal.prompts:hypothesis_gen.user_prompt").r(
hypothesis_and_feedback="No Feedback",
RAG="No RAG",
targets="targets",
)
resp = APIBackend().build_messages_and_create_chat_completion(user_prompt=user_prompt, system_prompt=sys_prompt)
code = PythonAgentOut.extract_output(resp)
print(code)
def test_include(self):
parent = T("components.coder.data_science.raw_data_loader.prompts:spec.user.data_loader").r(latest_spec=None)
child = T("scenarios.data_science.share:component_spec.DataLoadSpec").r()
assert child in parent
print(parent)
if __name__ == "__main__":
unittest.main()
+45
View File
@@ -0,0 +1,45 @@
import os
import unittest
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.components.coder.data_science.conf import DSCoderCoSTEERSettings
from rdagent.scenarios.data_science.dev.runner import DSRunnerCoSTEERSettings
from rdagent.utils.env import EnvConf, QlibDockerConf
class ConfUtils(unittest.TestCase):
def test_conf(self):
os.environ["MEM_LIMIT"] = "200g"
os.environ["RUNNING_TIMEOUT_PERIOD"] = "None"
assert QlibDockerConf().mem_limit == "200g" # base class will affect subclasses
os.environ["QLIB_DOCKER_MEM_LIMIT"] = "300g"
assert QlibDockerConf().mem_limit == "300g" # more accurate subclass will override the base class
assert QlibDockerConf().running_timeout_period is None
os.environ["DEFAULT_ENTRY"] = "which python"
os.environ["ENABLE_CACHE"] = "False"
assert EnvConf().enable_cache is False
assert QlibDockerConf().enable_cache is False
os.environ["ENABLE_CACHE"] = "True"
assert EnvConf().enable_cache is True
assert QlibDockerConf().enable_cache is True
def test_ds_costeer_conf(self):
os.environ["DS_CODER_COSTEER_MAX_SECONDS_MULTIPLIER"] = "1000"
coder_conf = DSCoderCoSTEERSettings()
runner_conf = DSRunnerCoSTEERSettings()
print(coder_conf.max_seconds_multiplier)
print(runner_conf.max_seconds_multiplier)
assert coder_conf.max_seconds_multiplier == 1000
# NOTE: coder's config should not affect runner's config
assert runner_conf.max_seconds_multiplier == 1
os.environ["DS_RUNNER_COSTEER_MAX_SECONDS"] = "2000"
assert DSRunnerCoSTEERSettings().max_seconds == 2000
if __name__ == "__main__":
unittest.main()
+160
View File
@@ -0,0 +1,160 @@
import os
import sys
import time
import unittest
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parent.parent))
import shutil
from rdagent.utils.env import (
CondaConf,
LocalConf,
LocalEnv,
QlibDockerConf,
QTDockerEnv,
cleanup_container,
)
DIRNAME = Path(__file__).absolute().resolve().parent
class QlibLocalEnv(LocalEnv):
def prepare(self) -> None:
if not (Path("~/.qlib/qlib_data/cn_data").expanduser().resolve().exists()):
self.check_output(
entry="python -m qlib.run.get_data qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn",
)
else:
print("Data already exists. Download skipped.")
class EnvUtils(unittest.TestCase):
def setUp(self):
self.test_workspace = DIRNAME / "test_workspace"
self.test_workspace.mkdir(exist_ok=True)
def tearDown(self):
if self.test_workspace.exists():
shutil.rmtree(self.test_workspace)
# NOTE: Since I don't know the exact environment in which it will be used, here's just an example.
# NOTE: Because you need to download the data during the prepare process. So you need to have pyqlib in your environment.
def test_local(self):
local_conf = LocalConf(
bin_path="/home/v-linlanglv/miniconda3/envs/RD-Agent-310/bin",
default_entry="qrun conf.yaml",
)
qle = QlibLocalEnv(conf=local_conf)
qle.prepare()
conf_path = str(DIRNAME / "env_tpl" / "conf.yaml")
qle.check_output(entry="qrun " + conf_path)
mlrun_p = DIRNAME / "env_tpl" / "mlruns"
self.assertTrue(mlrun_p.exists(), f"Expected output file {mlrun_p} not found")
def test_local_simple(self):
code_path = DIRNAME / "tmp_code"
code_path.mkdir(exist_ok=True)
# Get user home dynamically
home_bin = str(Path.home() / "miniconda3/bin/")
local_conf = LocalConf(bin_path=home_bin, default_entry="which python")
local_conf.extra_volumes = {str(code_path): "./code"}
print(local_conf)
le = LocalEnv(conf=local_conf)
le.prepare()
result = le.run(local_path=str(code_path))
print(result.stdout, result.exit_code, result.running_time)
def test_conda_simple(self):
conda_conf = CondaConf(default_entry="which python", conda_env_name="MLE")
le = LocalEnv(conf=conda_conf)
le.prepare()
code_path = DIRNAME / "tmp_code"
code_path.mkdir(exist_ok=True)
result = le.run(local_path=str(code_path))
print(result.stdout, result.exit_code, result.running_time)
def test_conda_error(self):
conda_conf = CondaConf(conda_env_name="MLE")
le = LocalEnv(conf=conda_conf)
le.prepare()
file_name = f"{time.time()}.py"
with open(self.test_workspace / file_name, "w") as f:
f.write('import json \njson.loads(b\'{"name": "\xa1"}\')')
result = le.run(local_path=str(self.test_workspace), entry=f"python {file_name}")
assert result.exit_code == 1
assert "bytes can only contain ASCII literal characters" in result.stdout
def test_docker(self):
"""We will mount `env_tpl` into the docker image.
And run the docker image with `qrun conf.yaml`
"""
qtde = QTDockerEnv()
qtde.prepare() # you can prepare for multiple times. It is expected to handle it correctly
# qtde.run("nvidia-smi") # NOTE: you can check your GPU with this command
# the stdout are returned as result
result = qtde.check_output(local_path=str(DIRNAME / "env_tpl"), entry="qrun conf.yaml")
mlrun_p = DIRNAME / "env_tpl" / "mlruns"
self.assertTrue(mlrun_p.exists(), f"Expected output file {mlrun_p} not found")
# read experiment
result = qtde.check_output(local_path=str(DIRNAME / "env_tpl"), entry="python read_exp_res.py")
print(result)
def test_run(self):
"""Test the run method of QTDockerEnv with both valid and invalid commands."""
qtde = QTDockerEnv()
qtde.prepare()
# Test with a valid command
result = qtde.run(entry='echo "Hello, World!"', local_path=str(self.test_workspace))
print(result.exit_code)
assert result.exit_code == 0, f"Expected return code 0, but got {result.exit_code}"
assert "Hello, World!" in result.stdout, "Expected output not found in result"
# Test with an invalid command
result = qtde.run(entry="invalid_command", local_path=str(self.test_workspace))
print(result.exit_code)
assert result.exit_code != 0, "Expected non-zero return code for invalid command"
dc = QlibDockerConf()
dc.running_timeout_period = 1
qtde = QTDockerEnv(dc)
result = qtde.run(entry="sleep 2", local_path=str(self.test_workspace))
print(result.exit_code)
assert result.exit_code == 124, "Expected return code 124 for timeout"
def test_docker_mem(self):
cmd = 'python -c \'print("start"); import numpy as np; size_mb = 500; size = size_mb * 1024 * 1024 // 8; array = np.random.randn(size).astype(np.float64); print("success")\''
qtde = QTDockerEnv(QlibDockerConf(mem_limit="10m"))
qtde.prepare()
result = qtde.check_output(local_path=str(DIRNAME / "env_tpl"), entry=cmd)
self.assertTrue(not result.strip().endswith("success"))
qtde = QTDockerEnv(QlibDockerConf(mem_limit="1g"))
qtde.prepare()
result = qtde.check_output(local_path=str(DIRNAME / "env_tpl"), entry=cmd)
self.assertTrue(result.strip().endswith("success"))
# The above command equals to the follow commands with dockr cli.sh
# docker run --memory=10m -it --rm local_qlib:latest python -c 'import numpy as np; print(123); size_mb = 1; size = size_mb * 1024 * 1024 // 8; array = np.random.randn(size).astype(np.float64); array[0], array[-1] = 1.0, 1.0; print(321)'
# docker run --memory=10g -it --rm local_qlib:latest python -c 'import numpy as np; print(123); size_mb = 1; size = size_mb * 1024 * 1024 // 8; array = np.random.randn(size).astype(np.float64); array[0], array[-1] = 1.0, 1.0; print(321)'
def test_cleanup_container_import(self):
"""Test that cleanup_container function can be imported and has correct interface."""
# Test that the function exists and can be called
self.assertTrue(callable(cleanup_container))
# Test with None (should not raise an exception)
cleanup_container(None, "test context")
# The function should accept positional and keyword arguments
cleanup_container(None)
cleanup_container(None, context="test")
if __name__ == "__main__":
unittest.main()
+61
View File
@@ -0,0 +1,61 @@
import importlib
import os
import unittest
from pathlib import Path
import pytest
@pytest.mark.offline
class TestRDAgentImports(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.rdagent_directory = Path(__file__).resolve().parent.parent.parent
cls.modules = list(cls.import_all_modules_from_directory(cls.rdagent_directory))
@staticmethod
def import_all_modules_from_directory(directory):
for file in directory.joinpath("rdagent").rglob("*.py"):
fstr = str(file)
if "example" in fstr:
continue
if "meta_tpl" in fstr:
continue
if "autorl_bench/workspace/" in fstr:
continue
if "template" in fstr or "tpl" in fstr:
continue
if "model_coder" in fstr:
continue
if "llm_st" in fstr:
continue
if (
"rdagent/log/ui/" in fstr
or fstr.endswith("rdagent/app/cli.py")
or fstr.endswith("rdagent/app/CI/run.py")
or fstr.endswith("rdagent/app/utils/ape.py")
or fstr.endswith("rdagent/log/ui/utils.py")
):
# the entrance points
continue
# llamafactory==0.9.3 pins numpy to an older version, causing other
# installations to fail. The `extract_parameters` tests are therefore
# temporarily disabled and can be re-enabled once the numpy constraint is relaxed.
if "extract_parameters" in fstr:
continue
yield fstr[fstr.index("rdagent") : -3].replace("/", ".")
def test_import_modules(self):
print(self.modules)
for module_name in self.modules:
with self.subTest(module=module_name):
try:
print(module_name)
importlib.import_module(module_name)
except Exception as e:
self.fail(f"Failed to import {module_name}: {e}")
if __name__ == "__main__":
unittest.main()
+32
View File
@@ -0,0 +1,32 @@
import unittest
from pathlib import Path
from rich import print
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
from rdagent.scenarios.kaggle.experiment.workspace import KGFBWorkspace
from rdagent.scenarios.kaggle.kaggle_crawler import download_data
class TestTpl(unittest.TestCase):
def test_competition_template(self):
"""
export KG_COMPETITION=<competition_name> before running this test
"""
competition = KAGGLE_IMPLEMENT_SETTING.competition
print(f"[bold orange]{competition}[/bold orange]")
download_data(competition, settings=KAGGLE_IMPLEMENT_SETTING)
ws = KGFBWorkspace(
template_folder_path=Path(__file__).parent.parent.parent
/ KAGGLE_IMPLEMENT_SETTING.template_path
/ f"{competition}",
)
print(ws.workspace_path)
ws.execute()
success = (ws.workspace_path / "submission.csv").exists()
self.assertTrue(success, "submission.csv is not generated")
# ws.clear()
if __name__ == "__main__":
unittest.main()
+74
View File
@@ -0,0 +1,74 @@
import unittest
import pytest
from rdagent.core.utils import SingletonBaseClass
class A(SingletonBaseClass):
def __init__(self, **kwargs):
print(self, "__init__", kwargs) # make sure the __init__ is called only once.
self.kwargs = kwargs
def __str__(self) -> str:
return f"{self.__class__.__name__}.{getattr(self, 'kwargs', None)}"
def __repr__(self) -> str:
return self.__str__()
@pytest.mark.offline
class MiscTest(unittest.TestCase):
def test_singleton(self):
print("a1=================")
a1 = A()
print("a2=================")
a2 = A()
print("a3=================")
a3 = A(x=3)
print("a4=================")
a4 = A(x=2)
print("a5=================")
a5 = A(b=3)
print("a6=================")
a6 = A(x=3)
# Check that a1 and a2 are the same instance
self.assertIs(a1, a2)
# Check that a3 and a6 are the same instance
self.assertIs(a3, a6)
# Check that a1 and a3 are different instances
self.assertIsNot(a1, a3)
# Check that a3 and a4 are different instances
self.assertIsNot(a3, a4)
# Check that a4 and a5 are different instances
self.assertIsNot(a4, a5)
# Check that a5 and a6 are different instances
self.assertIsNot(a5, a6)
print(id(a1), id(a2), id(a3), id(a4), id(a5), id(a6))
print("...................... Start testing pickle ......................")
# Test pickle
import pickle
with self.assertRaises(pickle.PicklingError):
with open("a3.pkl", "wb") as f:
pickle.dump(a3, f)
# NOTE: If the pickle feature is not disabled,
# loading a3.pkl will return a1, and a1 will be updated with a3's attributes.
# print(a1.kwargs)
# with open("a3.pkl", "rb") as f:
# a3_pkl = pickle.load(f)
# print(id(a3), id(a3_pkl)) # not the same object
# print(a1.kwargs) # a1 will be changed.
if __name__ == "__main__":
unittest.main()
+76
View File
@@ -0,0 +1,76 @@
import os
import tempfile
import unittest
from pathlib import Path
from rdagent.core.experiment import FBWorkspace
class TestFBWorkspace(unittest.TestCase):
"""
Unit-tests for `FBWorkspace`.
"""
def setUp(self) -> None: # noqa: D401
"""
Create an isolated temporary directory for each test case.
"""
self._tmp_dir = tempfile.TemporaryDirectory()
self.tmp_path = Path(self._tmp_dir.name)
def tearDown(self) -> None:
"""
Clean up the temporary directory created in :py:meth:`setUp`.
"""
self._tmp_dir.cleanup()
def test_checkpoint_roundtrip(self) -> None:
"""
Verify that ``create_ws_ckp`` captures the current workspace state and
``recover_ws_ckp`` faithfully restores it.
"""
# create a symbolic link inside workspace and ensure checkpoint preserves the link
external_file = self.tmp_path / "external.txt"
external_file.write_text("external data")
ws = FBWorkspace()
ws.workspace_path = self.tmp_path / "ws"
ws.prepare()
(ws.workspace_path / "sym.txt").symlink_to(external_file)
ws.inject_files(**{"foo.py": "print('hi')", "bar.py": "x = 1"})
# Snapshot current workspace
original_files = {
p.relative_to(ws.workspace_path): (os.readlink(p) if p.is_symlink() else p.read_text())
for p in ws.workspace_path.rglob("*")
if p.is_file() or p.is_symlink()
}
ws.create_ws_ckp()
self.assertIsNotNone(ws.ws_ckp, "Checkpoint data should have been generated")
# Mutate workspace
(ws.workspace_path / "foo.py").write_text("print('changed')")
(ws.workspace_path / "new.py").write_text("pass")
(ws.workspace_path / "sym.txt").unlink()
# Restore and verify equality with snapshot
ws.recover_ws_ckp()
# Ensure symbolic link still exists after recovery.
self.assertTrue((ws.workspace_path / "sym.txt").is_symlink())
recovered_files = {
p.relative_to(ws.workspace_path): (os.readlink(p) if p.is_symlink() else p.read_text())
for p in ws.workspace_path.rglob("*")
if p.is_file() or p.is_symlink()
}
self.assertEqual(recovered_files, original_files)
# Verify large files (>100 KB) are excluded when a size-limit is configured.
from rdagent.core.conf import RD_AGENT_SETTINGS as _SETTINGS
_SETTINGS.workspace_ckp_size_limit = 100 * 1024 # set limit temporarily for this test
large_file = ws.workspace_path / "large.bin"
large_file.write_bytes(b"0" * (110 * 1024)) # 110 KB dummy content
ws.create_ws_ckp()
ws.recover_ws_ckp()
self.assertFalse((ws.workspace_path / "large.bin").exists())