# -*- 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_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