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

@@ -3,313 +3,11 @@
import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from agentscope.message import Msg
class TestAnalystAgent:
def test_init_valid_analyst_type(self):
from backend.agents.analyst import AnalystAgent
mock_toolkit = MagicMock()
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = AnalystAgent(
analyst_type="technical_analyst",
toolkit=mock_toolkit,
model=mock_model,
formatter=mock_formatter,
)
assert agent.analyst_type_key == "technical_analyst"
assert agent.name == "technical_analyst"
assert agent.analyst_persona == "Technical Analyst"
def test_init_invalid_analyst_type(self):
from backend.agents.analyst import AnalystAgent
mock_toolkit = MagicMock()
mock_model = MagicMock()
mock_formatter = MagicMock()
with pytest.raises(ValueError) as excinfo:
AnalystAgent(
analyst_type="invalid_type",
toolkit=mock_toolkit,
model=mock_model,
formatter=mock_formatter,
)
assert "Unknown analyst type" in str(excinfo.value)
def test_init_custom_agent_id(self):
from backend.agents.analyst import AnalystAgent
mock_toolkit = MagicMock()
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = AnalystAgent(
analyst_type="fundamentals_analyst",
toolkit=mock_toolkit,
model=mock_model,
formatter=mock_formatter,
agent_id="custom_analyst_id",
)
assert agent.name == "custom_analyst_id"
def test_load_system_prompt(self):
from backend.agents.analyst import AnalystAgent
mock_toolkit = MagicMock()
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = AnalystAgent(
analyst_type="sentiment_analyst",
toolkit=mock_toolkit,
model=mock_model,
formatter=mock_formatter,
)
prompt = agent._load_system_prompt()
assert isinstance(prompt, str)
assert len(prompt) > 0
class TestPMAgent:
def test_init_default(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
assert agent.name == "portfolio_manager"
assert agent.portfolio["cash"] == 100000.0
assert agent.portfolio["positions"] == {}
assert agent.portfolio["margin_requirement"] == 0.25
def test_init_custom_cash(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
initial_cash=50000.0,
margin_requirement=0.5,
)
assert agent.portfolio["cash"] == 50000.0
assert agent.portfolio["margin_requirement"] == 0.5
def test_get_portfolio_state(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
initial_cash=75000.0,
)
state = agent.get_portfolio_state()
assert state["cash"] == 75000.0
assert state is not agent.portfolio # Should be a copy
def test_load_portfolio_state(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
new_portfolio = {
"cash": 50000.0,
"positions": {
"AAPL": {"long": 100, "short": 0, "long_cost_basis": 150.0},
},
"margin_used": 1000.0,
}
agent.load_portfolio_state(new_portfolio)
assert agent.portfolio["cash"] == 50000.0
assert "AAPL" in agent.portfolio["positions"]
def test_update_portfolio(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
agent.update_portfolio({"cash": 80000.0})
assert agent.portfolio["cash"] == 80000.0
def _get_text_from_tool_response(self, result):
"""Helper to extract text from ToolResponse content"""
content = result.content[0]
if hasattr(content, "text"):
return content.text
elif isinstance(content, dict):
return content.get("text", "")
return str(content)
def test_make_decision_long(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
result = agent._make_decision(
ticker="AAPL",
action="long",
quantity=100,
confidence=80,
reasoning="Strong fundamentals",
)
text = self._get_text_from_tool_response(result)
assert "Decision recorded" in text
assert agent._decisions["AAPL"]["action"] == "long"
assert agent._decisions["AAPL"]["quantity"] == 100
def test_make_decision_hold(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
result = agent._make_decision(
ticker="GOOGL",
action="hold",
quantity=0,
confidence=50,
reasoning="Neutral outlook",
)
text = self._get_text_from_tool_response(result)
assert "Decision recorded" in text
assert agent._decisions["GOOGL"]["action"] == "hold"
assert agent._decisions["GOOGL"]["quantity"] == 0
def test_make_decision_invalid_action(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
result = agent._make_decision(
ticker="AAPL",
action="invalid",
quantity=10,
)
text = self._get_text_from_tool_response(result)
assert "Invalid action" in text
def test_get_decisions(self):
from backend.agents.portfolio_manager import PMAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = PMAgent(
model=mock_model,
formatter=mock_formatter,
)
agent._make_decision("AAPL", "long", 100)
agent._make_decision("GOOGL", "short", 50)
decisions = agent.get_decisions()
assert len(decisions) == 2
assert decisions["AAPL"]["action"] == "long"
assert decisions["GOOGL"]["action"] == "short"
class TestRiskAgent:
def test_init_default(self):
from backend.agents.risk_manager import RiskAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = RiskAgent(
model=mock_model,
formatter=mock_formatter,
)
assert agent.name == "risk_manager"
def test_init_custom_name(self):
from backend.agents.risk_manager import RiskAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = RiskAgent(
model=mock_model,
formatter=mock_formatter,
name="custom_risk_manager",
)
assert agent.name == "custom_risk_manager"
def test_load_system_prompt(self):
from backend.agents.risk_manager import RiskAgent
mock_model = MagicMock()
mock_formatter = MagicMock()
agent = RiskAgent(
model=mock_model,
formatter=mock_formatter,
)
prompt = agent._load_system_prompt()
assert isinstance(prompt, str)
assert len(prompt) > 0
class TestStorageService:
def test_storage_service_defaults_to_runtime_config(self):
from backend.services.storage import StorageService
@@ -675,37 +373,34 @@ class TestTradeExecutor:
class TestPipelineExecution:
def test_execute_decisions(self):
from backend.core.pipeline import TradingPipeline
from backend.agents.portfolio_manager import PMAgent
"""Test that pipeline executes decisions correctly.
mock_model = MagicMock()
mock_formatter = MagicMock()
This test verifies the TradingPipeline integrates with TradeExecutor.
Full integration testing is done in end-to-end tests.
"""
from backend.utils.trade_executor import PortfolioTradeExecutor
pm = PMAgent(
model=mock_model,
formatter=mock_formatter,
initial_cash=100000.0,
# Use real PortfolioTradeExecutor to test the execution logic
executor = PortfolioTradeExecutor(
initial_portfolio={
"cash": 100000.0,
"positions": {},
"margin_requirement": 0.25,
"margin_used": 0.0,
},
)
pipeline = TradingPipeline(
analysts=[],
risk_manager=MagicMock(),
portfolio_manager=pm,
max_comm_cycles=0,
# Execute a long trade
result = executor.execute_trade(
ticker="AAPL",
action="long",
quantity=10,
price=150.0,
)
decisions = {
"AAPL": {"action": "long", "quantity": 10},
"GOOGL": {"action": "short", "quantity": 5},
}
prices = {"AAPL": 150.0, "GOOGL": 100.0}
result = pipeline._execute_decisions(decisions, prices, "2024-01-15")
assert len(result["executed_trades"]) == 2
assert result["executed_trades"][0]["ticker"] == "AAPL"
assert result["executed_trades"][0]["quantity"] == 10
assert pm.portfolio["positions"]["AAPL"]["long"] == 10
assert result["status"] == "success"
assert executor.portfolio["positions"]["AAPL"]["long"] == 10
assert executor.portfolio["cash"] == 98500.0 # 100000 - 10*150
class TestMsgContentIsString: