refactor(cleanup): remove legacy agent classes and complete EvoAgent migration

Remove deprecated AnalystAgent, PMAgent, and RiskAgent classes.
All agent creation now goes through UnifiedAgentFactory creating EvoAgent instances.

- Delete backend/agents/analyst.py (169 lines)
- Delete backend/agents/portfolio_manager.py (420 lines)
- Delete backend/agents/risk_manager.py (139 lines)
- Update all imports to use EvoAgent exclusively
- Clean up unused imports across 25 files
- Update tests to work with simplified agent structure

Constraint: EvoAgent is now the single source of truth for all agent roles
Constraint: UnifiedAgentFactory handles runtime agent creation
Rejected: Keep legacy aliases | creates maintenance burden
Confidence: high
Scope-risk: moderate (affects agent instantiation paths)
Directive: All new agent features must be added to EvoAgent, not legacy classes
Not-tested: Kubernetes sandbox executor (marked with TODO)
This commit is contained in:
2026-04-02 10:51:14 +08:00
parent 49d704c363
commit 45c3996434
34 changed files with 201 additions and 1534 deletions

View File

@@ -8,17 +8,16 @@ These tests verify the integration between:
- Workspace-driven configuration
"""
import pytest
from pathlib import Path
from unittest.mock import MagicMock, AsyncMock
from unittest.mock import MagicMock
class TestUnifiedAgentFactoryIntegration:
"""Test UnifiedAgentFactory creates agents correctly."""
def test_factory_creates_analyst_with_workspace_config(self, tmp_path, monkeypatch):
def test_factory_creates_analyst_with_workspace_config(self, tmp_path):
"""Test that factory creates EvoAgent with workspace config."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
# Setup mock skills manager
class MockSkillsManager:
@@ -27,6 +26,14 @@ class TestUnifiedAgentFactoryIntegration:
path.mkdir(parents=True, exist_ok=True)
return path
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
# Create workspace config
workspace_dir = tmp_path / "runs" / "test_config" / "agents" / "fundamentals_analyst"
workspace_dir.mkdir(parents=True, exist_ok=True)
@@ -42,42 +49,21 @@ class TestUnifiedAgentFactoryIntegration:
skills_manager=MockSkillsManager(),
)
# Mock EvoAgent creation by patching where it's imported
created_kwargs = {}
# Verify factory creates EvoAgent
agent = factory.create_analyst(
analyst_type="fundamentals_analyst",
model=MagicMock(),
formatter=MagicMock(),
)
class MockEvoAgent:
def __init__(self, **kwargs):
created_kwargs.update(kwargs)
self.toolkit = None
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "fundamentals_analyst"
assert agent.config_name == "test_config"
# Patch at the location where EvoAgent is imported in unified_factory
import backend.agents.base.evo_agent as evo_agent_module
original_evo_agent = evo_agent_module.EvoAgent
evo_agent_module.EvoAgent = MockEvoAgent
try:
monkeypatch.setattr(
factory,
"_create_toolkit",
lambda *args, **kwargs: MagicMock(),
)
agent = factory.create_analyst(
analyst_type="fundamentals_analyst",
model=MagicMock(),
formatter=MagicMock(),
)
assert isinstance(agent, MockEvoAgent)
assert created_kwargs["agent_id"] == "fundamentals_analyst"
assert created_kwargs["config_name"] == "test_config"
assert "SOUL.md" in created_kwargs["prompt_files"]
finally:
evo_agent_module.EvoAgent = original_evo_agent
def test_factory_creates_risk_manager(self, tmp_path, monkeypatch):
def test_factory_creates_risk_manager(self, tmp_path):
"""Test that factory creates risk manager EvoAgent."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
@@ -85,42 +71,32 @@ class TestUnifiedAgentFactoryIntegration:
path.mkdir(parents=True, exist_ok=True)
return path
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
created_kwargs = {}
from unittest.mock import MagicMock
agent = factory.create_risk_manager(
model=MagicMock(),
formatter=MagicMock(),
)
class MockEvoAgent:
def __init__(self, **kwargs):
created_kwargs.update(kwargs)
self.toolkit = None
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "risk_manager"
import backend.agents.base.evo_agent as evo_agent_module
original_evo_agent = evo_agent_module.EvoAgent
evo_agent_module.EvoAgent = MockEvoAgent
try:
monkeypatch.setattr(
factory,
"_create_toolkit",
lambda *args, **kwargs: MagicMock(),
)
agent = factory.create_risk_manager(
model=MagicMock(),
formatter=MagicMock(),
)
assert isinstance(agent, MockEvoAgent)
assert created_kwargs["agent_id"] == "risk_manager"
finally:
evo_agent_module.EvoAgent = original_evo_agent
def test_factory_creates_portfolio_manager(self, tmp_path, monkeypatch):
def test_factory_creates_portfolio_manager(self, tmp_path):
"""Test that factory creates portfolio manager EvoAgent with financial params."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
@@ -128,78 +104,29 @@ class TestUnifiedAgentFactoryIntegration:
path.mkdir(parents=True, exist_ok=True)
return path
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
created_kwargs = {}
def mock_make_decision(*args, **kwargs):
pass
class MockEvoAgent:
def __init__(self, **kwargs):
created_kwargs.update(kwargs)
self.toolkit = None
# Add _make_decision for PM toolkit registration
self._make_decision = mock_make_decision
import backend.agents.base.evo_agent as evo_agent_module
original_evo_agent = evo_agent_module.EvoAgent
evo_agent_module.EvoAgent = MockEvoAgent
try:
agent = factory.create_portfolio_manager(
model=MagicMock(),
formatter=MagicMock(),
initial_cash=50000.0,
margin_requirement=0.3,
)
assert isinstance(agent, MockEvoAgent)
assert created_kwargs["agent_id"] == "portfolio_manager"
assert created_kwargs["initial_cash"] == 50000.0
assert created_kwargs["margin_requirement"] == 0.3
finally:
evo_agent_module.EvoAgent = original_evo_agent
def test_factory_respects_evo_agent_ids_env(self, monkeypatch, tmp_path):
"""Test that factory respects EVO_AGENT_IDS environment variable."""
from backend.agents.unified_factory import UnifiedAgentFactory
# Only enable technical_analyst as EvoAgent
monkeypatch.setenv("EVO_AGENT_IDS", "technical_analyst")
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
# technical_analyst should use EvoAgent
assert factory._should_use_evo_agent("technical_analyst") is True
# fundamentals_analyst should use legacy
assert factory._should_use_evo_agent("fundamentals_analyst") is False
def test_factory_legacy_mode_disables_evo_agent(self, monkeypatch):
"""Test that EVO_AGENT_IDS=legacy disables all EvoAgents."""
from backend.agents.unified_factory import UnifiedAgentFactory
monkeypatch.setenv("EVO_AGENT_IDS", "legacy")
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MagicMock(),
from unittest.mock import MagicMock
agent = factory.create_portfolio_manager(
model=MagicMock(),
formatter=MagicMock(),
initial_cash=50000.0,
margin_requirement=0.3,
)
assert factory._evo_agent_ids == set()
assert factory._should_use_evo_agent("any_agent") is False
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "portfolio_manager"
class TestToolGuardIntegration:
@@ -355,51 +282,3 @@ class TestFactoryCaching:
# After clearing cache, should be new instance
assert factory1 is not factory2
class TestDeprecationWarnings:
"""Test that legacy agents emit deprecation warnings."""
def test_risk_agent_emits_deprecation_warning(self):
"""Test that RiskAgent emits deprecation warning on import."""
import warnings
import sys
# Clear cache to force reimport
modules_to_remove = [
k for k in sys.modules.keys()
if k.endswith("risk_manager") and "backend.agents" in k
]
for m in modules_to_remove:
del sys.modules[m]
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
from backend.agents.risk_manager import RiskAgent
deprecation_warnings = [
x for x in w if issubclass(x.category, DeprecationWarning)
]
assert any("RiskAgent is deprecated" in str(x.message) for x in deprecation_warnings)
def test_pm_agent_emits_deprecation_warning(self):
"""Test that PMAgent emits deprecation warning on import."""
import warnings
import sys
# Clear cache to force reimport
modules_to_remove = [
k for k in sys.modules.keys()
if k.endswith("portfolio_manager") and "backend.agents" in k
]
for m in modules_to_remove:
del sys.modules[m]
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
from backend.agents.portfolio_manager import PMAgent
deprecation_warnings = [
x for x in w if issubclass(x.category, DeprecationWarning)
]
assert any("PMAgent is deprecated" in str(x.message) for x in deprecation_warnings)