Files
evotraders/backend/tests/test_evo_agent_selection.py

438 lines
14 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for selective EvoAgent construction."""
from pathlib import Path
def test_main_resolve_evo_agent_ids_filters_unsupported_roles(monkeypatch):
from backend.core import pipeline_runner as runner_module
monkeypatch.setenv(
"EVO_AGENT_IDS",
"fundamentals_analyst,portfolio_manager,unknown,technical_analyst",
)
resolved = runner_module._resolve_evo_agent_ids()
assert resolved == {"fundamentals_analyst", "portfolio_manager", "technical_analyst"}
def test_pipeline_runner_resolve_evo_agent_ids_keeps_supported_roles(monkeypatch):
from backend.core import pipeline_runner as runner_module
monkeypatch.setenv("EVO_AGENT_IDS", "risk_manager,valuation_analyst")
resolved = runner_module._resolve_evo_agent_ids()
assert resolved == {"risk_manager", "valuation_analyst"}
def test_main_create_analyst_agent_can_build_evo_agent(monkeypatch, tmp_path):
from backend.core import pipeline_runner as runner_module
created = {}
class DummySkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
(path / "agent.yaml").write_text(
"prompt_files:\n - SOUL.md\n",
encoding="utf-8",
)
return path
class DummyEvoAgent:
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
monkeypatch.setenv("EVO_AGENT_IDS", "fundamentals_analyst")
monkeypatch.setattr(runner_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(runner_module, "create_agent_toolkit", lambda *args, **kwargs: "toolkit")
agent = runner_module._create_analyst_agent(
analyst_type="fundamentals_analyst",
run_id="demo",
model="model",
formatter="formatter",
skills_manager=DummySkillsManager(),
active_skill_map={"fundamentals_analyst": [Path("/tmp/skill")]},
long_term_memory=None,
)
assert isinstance(agent, DummyEvoAgent)
assert created["agent_id"] == "fundamentals_analyst"
assert created["config_name"] == "demo"
assert created["prompt_files"] == ["SOUL.md"]
assert agent.toolkit == "toolkit"
assert agent.workspace_id == "demo"
def test_main_create_risk_manager_can_build_evo_agent(monkeypatch, tmp_path):
from backend.core import pipeline_runner as runner_module
created = {}
class DummySkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
(path / "agent.yaml").write_text(
"prompt_files:\n - SOUL.md\n",
encoding="utf-8",
)
return path
class DummyEvoAgent:
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
monkeypatch.setenv("EVO_AGENT_IDS", "risk_manager")
monkeypatch.setattr(runner_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(runner_module, "create_agent_toolkit", lambda *args, **kwargs: "risk-toolkit")
agent = runner_module._create_risk_manager_agent(
run_id="demo",
model="model",
formatter="formatter",
skills_manager=DummySkillsManager(),
active_skill_map={"risk_manager": [Path("/tmp/skill")]},
long_term_memory=None,
)
assert isinstance(agent, DummyEvoAgent)
assert created["agent_id"] == "risk_manager"
assert created["config_name"] == "demo"
assert created["prompt_files"] == ["SOUL.md"]
assert agent.toolkit == "risk-toolkit"
assert agent.workspace_id == "demo"
def test_main_create_portfolio_manager_can_build_evo_agent(monkeypatch, tmp_path):
from backend.core import pipeline_runner as runner_module
created = {}
class DummySkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
(path / "agent.yaml").write_text(
"prompt_files:\n - SOUL.md\n",
encoding="utf-8",
)
return path
class DummyEvoAgent:
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
monkeypatch.setenv("EVO_AGENT_IDS", "portfolio_manager")
monkeypatch.setattr(runner_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(
runner_module,
"create_agent_toolkit",
lambda *args, **kwargs: "pm-toolkit",
)
agent = runner_module._create_portfolio_manager_agent(
run_id="demo",
model="model",
formatter="formatter",
initial_cash=12345.0,
margin_requirement=0.4,
skills_manager=DummySkillsManager(),
active_skill_map={"portfolio_manager": [Path("/tmp/skill")]},
long_term_memory=None,
)
assert isinstance(agent, DummyEvoAgent)
assert created["agent_id"] == "portfolio_manager"
assert created["config_name"] == "demo"
assert created["prompt_files"] == ["SOUL.md"]
assert created["initial_cash"] == 12345.0
assert created["margin_requirement"] == 0.4
assert agent.toolkit == "pm-toolkit"
assert agent.workspace_id == "demo"
def test_evo_agent_reload_runtime_assets_refreshes_prompt_files(monkeypatch, tmp_path):
from backend.agents.base.evo_agent import EvoAgent
workspace_dir = tmp_path / "runs" / "demo" / "agents" / "fundamentals_analyst"
workspace_dir.mkdir(parents=True, exist_ok=True)
(workspace_dir / "SOUL.md").write_text("soul-v1", encoding="utf-8")
(workspace_dir / "MEMORY.md").write_text("memory-v1", encoding="utf-8")
(workspace_dir / "agent.yaml").write_text(
"prompt_files:\n"
" - SOUL.md\n",
encoding="utf-8",
)
class DummyToolkit:
def __init__(self, *args, **kwargs):
self.registered = []
def register_agent_skill(self, path):
self.registered.append(path)
monkeypatch.setattr(
"backend.agents.base.evo_agent.Toolkit",
DummyToolkit,
)
class DummyModel:
pass
class DummyFormatter:
pass
agent = EvoAgent(
agent_id="fundamentals_analyst",
config_name="demo",
workspace_dir=workspace_dir,
model=DummyModel(),
formatter=DummyFormatter(),
prompt_files=["SOUL.md"],
skills_manager=type(
"SkillsManagerStub",
(),
{
"get_agent_active_root": staticmethod(lambda config_name, agent_id: workspace_dir / "skills" / "active"),
"list_active_skill_metadata": staticmethod(lambda config_name, agent_id: []),
},
)(),
)
assert "soul-v1" in agent._sys_prompt
assert "memory-v1" not in agent._sys_prompt
(workspace_dir / "agent.yaml").write_text(
"prompt_files:\n"
" - SOUL.md\n"
" - MEMORY.md\n",
encoding="utf-8",
)
agent.reload_runtime_assets(active_skill_dirs=[])
assert "memory-v1" in agent._sys_prompt
assert agent.workspace_id == "demo"
assert agent.config == {"config_name": "demo"}
def test_pipeline_resolve_evo_agent_ids_filters_unsupported_roles(monkeypatch):
"""Test that pipeline._resolve_evo_agent_ids filters unsupported roles."""
from backend.core import pipeline as pipeline_module
monkeypatch.setenv(
"EVO_AGENT_IDS",
"fundamentals_analyst,portfolio_manager,unknown,technical_analyst",
)
resolved = pipeline_module._resolve_evo_agent_ids()
assert resolved == {"fundamentals_analyst", "portfolio_manager", "technical_analyst"}
def test_pipeline_create_runtime_analyst_uses_evo_agent_when_enabled(monkeypatch, tmp_path):
"""Test that _create_runtime_analyst creates EvoAgent when in EVO_AGENT_IDS."""
from backend.core import pipeline as pipeline_module
created = {}
class DummyEvoAgent:
name = "test_analyst"
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
class DummyAnalystAgent:
name = "test_analyst"
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
monkeypatch.setenv("EVO_AGENT_IDS", "fundamentals_analyst")
monkeypatch.setattr(pipeline_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(pipeline_module, "AnalystAgent", DummyAnalystAgent)
monkeypatch.setattr(
pipeline_module,
"create_agent_toolkit",
lambda *args, **kwargs: "toolkit",
)
monkeypatch.setattr(
pipeline_module,
"get_agent_model",
lambda x: "model",
)
monkeypatch.setattr(
pipeline_module,
"get_agent_formatter",
lambda x: "formatter",
)
# Create a mock pipeline instance
class MockPM:
def __init__(self):
self.config = {"config_name": "demo"}
pipeline = pipeline_module.TradingPipeline(
analysts=[],
risk_manager=None,
portfolio_manager=MockPM(),
)
# Mock workspace_manager methods
monkeypatch.setattr(
pipeline_module.WorkspaceManager,
"ensure_agent_assets",
lambda *args, **kwargs: None,
)
result = pipeline._create_runtime_analyst("test_analyst", "fundamentals_analyst")
assert "Created runtime analyst" in result
assert created.get("agent_id") == "test_analyst"
assert created.get("config_name") == "demo"
def test_pipeline_create_runtime_analyst_uses_legacy_when_not_in_evo_ids(monkeypatch, tmp_path):
"""Test that _create_runtime_analyst creates legacy AnalystAgent when not in EVO_AGENT_IDS."""
from backend.core import pipeline as pipeline_module
created = {}
class DummyEvoAgent:
name = "test_analyst"
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
class DummyAnalystAgent:
name = "test_analyst"
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
# EVO_AGENT_IDS does not include fundamentals_analyst
monkeypatch.setenv("EVO_AGENT_IDS", "technical_analyst")
monkeypatch.setattr(pipeline_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(pipeline_module, "AnalystAgent", DummyAnalystAgent)
monkeypatch.setattr(
pipeline_module,
"create_agent_toolkit",
lambda *args, **kwargs: "toolkit",
)
monkeypatch.setattr(
pipeline_module,
"get_agent_model",
lambda x: "model",
)
monkeypatch.setattr(
pipeline_module,
"get_agent_formatter",
lambda x: "formatter",
)
# Create a mock pipeline instance
class MockPM:
def __init__(self):
self.config = {"config_name": "demo"}
pipeline = pipeline_module.TradingPipeline(
analysts=[],
risk_manager=None,
portfolio_manager=MockPM(),
)
# Mock workspace_manager methods
monkeypatch.setattr(
pipeline_module.WorkspaceManager,
"ensure_agent_assets",
lambda *args, **kwargs: None,
)
result = pipeline._create_runtime_analyst("test_analyst", "fundamentals_analyst")
assert "Created runtime analyst" in result
# Should use legacy AnalystAgent
assert created.get("analyst_type") == "fundamentals_analyst"
def test_main_resolve_evo_agent_ids_returns_all_by_default(monkeypatch):
"""Test that _resolve_evo_agent_ids returns all supported roles by default."""
from backend.core import pipeline_runner as runner_module
from backend.config.constants import ANALYST_TYPES
# Unset EVO_AGENT_IDS to test default behavior
monkeypatch.delenv("EVO_AGENT_IDS", raising=False)
resolved = runner_module._resolve_evo_agent_ids()
expected = set(ANALYST_TYPES) | {"risk_manager", "portfolio_manager"}
assert resolved == expected
def test_evo_agent_supports_long_term_memory(monkeypatch, tmp_path):
"""Test that EvoAgent can be created with long_term_memory."""
from backend import main as main_module
created = {}
class DummySkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
(path / "agent.yaml").write_text(
"prompt_files:\n - SOUL.md\n",
encoding="utf-8",
)
return path
class DummyEvoAgent:
def __init__(self, **kwargs):
created.update(kwargs)
self.toolkit = None
# Default: all roles use EvoAgent
monkeypatch.delenv("EVO_AGENT_IDS", raising=False)
monkeypatch.setattr(main_module, "EvoAgent", DummyEvoAgent)
monkeypatch.setattr(main_module, "create_agent_toolkit", lambda *args, **kwargs: "toolkit")
# Create with long_term_memory - should still use EvoAgent
dummy_memory = {"type": "reme"}
agent = main_module._create_analyst_agent(
analyst_type="fundamentals_analyst",
config_name="demo",
model="model",
formatter="formatter",
skills_manager=DummySkillsManager(),
active_skill_map={"fundamentals_analyst": []},
long_term_memory=dummy_memory,
)
assert isinstance(agent, DummyEvoAgent)
assert created["agent_id"] == "fundamentals_analyst"
assert created["long_term_memory"] is dummy_memory
def test_evo_agent_legacy_mode(monkeypatch):
"""Test that EVO_AGENT_IDS=legacy disables EvoAgent."""
from backend import main as main_module
monkeypatch.setenv("EVO_AGENT_IDS", "legacy")
resolved = main_module._resolve_evo_agent_ids()
assert resolved == set()