feat(agent): complete EvoAgent integration for all 6 agent roles
Migrate all agent roles from Legacy to EvoAgent architecture: - fundamentals_analyst, technical_analyst, sentiment_analyst, valuation_analyst - risk_manager, portfolio_manager Key changes: - EvoAgent now supports Portfolio Manager compatibility methods (_make_decision, get_decisions, get_portfolio_state, load_portfolio_state, update_portfolio) - Add UnifiedAgentFactory for centralized agent creation - ToolGuard with batch approval API and WebSocket broadcast - Legacy agents marked deprecated (AnalystAgent, RiskAgent, PMAgent) - Remove backend/agents/compat.py migration shim - Add run_id alongside workspace_id for semantic clarity - Complete integration test coverage (13 tests) - All smoke tests passing for 6 agent roles Constraint: Must maintain backward compatibility with existing run configs Constraint: Memory support must work with EvoAgent (no fallback to Legacy) Rejected: Separate PM implementation for EvoAgent | unified approach cleaner Confidence: high Scope-risk: broad Directive: EVO_AGENT_IDS env var still respected but defaults to all roles Not-tested: Kubernetes sandbox mode for skill execution
This commit is contained in:
@@ -5,6 +5,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.core.state_sync import StateSync
|
||||
from backend.services import gateway_cycle_support, gateway_runtime_support
|
||||
|
||||
|
||||
@@ -43,6 +44,12 @@ class _DummyStorage:
|
||||
self.initial_cash = 100000.0
|
||||
self.is_live_session_active = False
|
||||
self.server_state_updates = []
|
||||
self.max_feed_history = 200
|
||||
self.runtime_db = SimpleNamespace(
|
||||
get_recent_feed_events=lambda limit=200: [],
|
||||
get_last_day_feed_events=lambda current_date=None, limit=200: [],
|
||||
)
|
||||
self._persisted_server_state = {}
|
||||
|
||||
def can_apply_initial_cash(self):
|
||||
return True
|
||||
@@ -54,6 +61,9 @@ class _DummyStorage:
|
||||
def update_server_state_from_dashboard(self, state):
|
||||
self.server_state_updates.append(state)
|
||||
|
||||
def read_persisted_server_state(self):
|
||||
return dict(self._persisted_server_state)
|
||||
|
||||
def load_file(self, name):
|
||||
if name == "summary":
|
||||
return {"totalAssetValue": self.initial_cash}
|
||||
@@ -199,3 +209,70 @@ async def test_refresh_market_store_for_watchlist_emits_system_messages(monkeypa
|
||||
|
||||
assert gateway.state_sync.system_messages[0] == "正在同步自选股市场数据: AAPL, MSFT"
|
||||
assert "自选股市场数据已同步:" in gateway.state_sync.system_messages[1]
|
||||
|
||||
|
||||
def test_initial_state_payload_prefers_dashboard_snapshot_for_top_level_views():
|
||||
storage = _DummyStorage()
|
||||
sync = StateSync(storage=storage)
|
||||
sync._state = {
|
||||
"holdings": [],
|
||||
"trades": [],
|
||||
"stats": {},
|
||||
"leaderboard": [],
|
||||
"portfolio": {"total_value": 100000.0},
|
||||
}
|
||||
|
||||
payload = sync.get_initial_state_payload(include_dashboard=True)
|
||||
|
||||
assert payload["holdings"] == []
|
||||
assert payload["trades"] == []
|
||||
assert payload["stats"] == {}
|
||||
assert payload["leaderboard"] == []
|
||||
assert payload["dashboard"]["summary"]["totalAssetValue"] == 100000.0
|
||||
|
||||
|
||||
def test_initial_state_payload_uses_dashboard_snapshot_for_sparse_runtime_state():
|
||||
class SnapshotStorage(_DummyStorage):
|
||||
def build_dashboard_snapshot_from_state(self, state):
|
||||
return {
|
||||
"summary": {"totalAssetValue": 123456.0},
|
||||
"holdings": [{"ticker": "AAPL"}],
|
||||
"stats": {"totalTrades": 3},
|
||||
"trades": [{"ticker": "AAPL"}],
|
||||
"leaderboard": [{"agentId": "technical_analyst"}],
|
||||
}
|
||||
|
||||
sync = StateSync(storage=SnapshotStorage())
|
||||
sync._state = {
|
||||
"holdings": [],
|
||||
"trades": [],
|
||||
"stats": {},
|
||||
"leaderboard": [],
|
||||
}
|
||||
|
||||
payload = sync.get_initial_state_payload(include_dashboard=True)
|
||||
|
||||
assert payload["holdings"][0]["ticker"] == "AAPL"
|
||||
assert payload["trades"][0]["ticker"] == "AAPL"
|
||||
assert payload["stats"]["totalTrades"] == 3
|
||||
assert payload["leaderboard"][0]["agentId"] == "technical_analyst"
|
||||
|
||||
|
||||
def test_initial_state_payload_falls_back_to_persisted_portfolio():
|
||||
storage = _DummyStorage()
|
||||
storage._persisted_server_state = {
|
||||
"portfolio": {
|
||||
"total_value": 123456.0,
|
||||
"pnl_percent": 12.34,
|
||||
"equity": [{"t": 1, "v": 123456.0}],
|
||||
}
|
||||
}
|
||||
sync = StateSync(storage=storage)
|
||||
sync._state = {
|
||||
"portfolio": {},
|
||||
}
|
||||
|
||||
payload = sync.get_initial_state_payload(include_dashboard=True)
|
||||
|
||||
assert payload["portfolio"]["total_value"] == 123456.0
|
||||
assert payload["portfolio"]["pnl_percent"] == 12.34
|
||||
|
||||
Reference in New Issue
Block a user