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:
2026-04-02 00:55:08 +08:00
parent 0fa413380c
commit 16b54d5ccc
73 changed files with 9454 additions and 904 deletions

View File

@@ -178,3 +178,84 @@ def test_news_service_range_explain(monkeypatch):
assert response.status_code == 200
assert response.json()["result"]["news_count"] == 1
def test_news_service_contract_stability():
"""Verify news service API maintains contract stability."""
app = create_app()
routes = {route.path: route for route in app.routes if hasattr(route, "methods")}
# Health endpoint
assert "/health" in routes
# News/explain endpoints
assert "/api/enriched-news" in routes
assert "/api/news-for-date" in routes
assert "/api/news-timeline" in routes
assert "/api/categories" in routes
assert "/api/similar-days" in routes
assert "/api/stories/{ticker}" in routes
assert "/api/range-explain" in routes
# Verify all are GET endpoints (read-only service)
for path in ["/api/enriched-news", "/api/news-for-date", "/api/news-timeline",
"/api/categories", "/api/similar-days", "/api/stories/{ticker}",
"/api/range-explain"]:
assert "GET" in routes[path].methods
def test_news_service_enriched_news_contract(monkeypatch):
"""Test enriched news endpoint maintains response contract."""
app = create_app()
app.dependency_overrides.clear()
from backend.apps import news_service as news_service_module
app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore()
monkeypatch.setattr(
"backend.domains.news.enrich_news_for_symbol",
lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1, "news": [{"id": "1", "title": "Test"}]},
)
with TestClient(app) as client:
response = client.get(
"/api/enriched-news",
params={"ticker": "AAPL", "end_date": "2026-03-23"},
)
assert response.status_code == 200
payload = response.json()
assert "news" in payload
def test_news_service_stories_contract(monkeypatch):
"""Test stories endpoint maintains response contract."""
app = create_app()
from backend.apps import news_service as news_service_module
app.dependency_overrides[news_service_module.get_market_store] = lambda: _FakeStore()
monkeypatch.setattr(
"backend.domains.news.enrich_news_for_symbol",
lambda *args, **kwargs: {"symbol": "AAPL", "analyzed": 1},
)
monkeypatch.setattr(
"backend.domains.news.get_or_create_stock_story",
lambda store, symbol, as_of_date: {
"symbol": symbol,
"as_of_date": as_of_date,
"story": "story body",
"source": "local",
"headline": "Test Headline",
},
)
with TestClient(app) as client:
response = client.get(
"/api/stories/AAPL",
params={"as_of_date": "2026-03-23"},
)
assert response.status_code == 200
payload = response.json()
assert "symbol" in payload
assert "as_of_date" in payload
assert "story" in payload