mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
feat: multi-agent architecture — core orchestrator, specialised agents, strategy system (#647)
* feat: multi-agent architecture — core orchestrator, specialised agents, strategy system Phase 0-7 core agent infrastructure: - AgentContext/AgentOpinion/StageResult protocols - run_agent_loop() shared runner extracted from AgentExecutor - AgentOrchestrator with 4 modes (quick/standard/full/strategy) - BaseAgent ABC + Technical/Intel/Risk/Decision/Portfolio agents - StrategyRouter (regime detection), StrategyAggregator (weighted consensus) - AgentMemory with prediction tracking and confidence calibration - Backtest summary tools registered as read-only Agent tools - AGENT_ARCH switch (single/multi), config registry + WebUI entries - is_agent_available() auto-detection from LITELLM_MODEL - Config __post_init__ validation for AGENT_ARCH/ORCHESTRATOR_MODE/STRATEGY_ROUTING * fix: address PR #647 review comments - base_agent: propagate tool_calls_log in result.meta for orchestrator aggregation - risk_agent: fix docstring — AGENT_RISK_OVERRIDE is bool, not string - __init__: remove ResearchAgent from lazy imports (research.py not on this branch) - router: cast trend_score to float to handle LLM string responses - config_registry: fix duplicate display_order 65 — bump AGENT_MEMORY_ENABLED to 66 and cascade - test_system_config_service: add missing assertions to validate test - agent.py: clarify user_id must include platform prefix in docstring * feat(multi-agent): strategy mode, data perspective fix, review fixes - Multi-agent orchestrator: add strategy mode with consensus voting - Fix data perspective MA N/A: compute trend before agent branch, read from trend_result instead of LLM output - Fix risk override double-apply: make _apply_risk_override idempotent so normal runs don't over-downgrade signals - Fix timeout error suppression: preserve error_message on agent result regardless of success flag - Fix notification float subscript: wrap dashboard values in str() - Add skills->strategies backward compat on ChatRequest model - Fix zero-value falsy bug in price_position dict construction - Add fill_price_position_if_needed post-processing for single-agent - Add _bias_label helper for computed bias status display - Increase orchestrator timeout default to 600s - Web: skills->strategies rename, i18n updates - Python 3.9 compat: add future annotations to stock_mapping.py * fix: update pipeline routing test for trend_result param, fix risk docstring - test_agent_mode_routes_to_agent: assert 8 positional args (trend_result added) - risk_agent.py: fix docstring to match actual boolean config behavior * fix typeerror
This commit is contained in:
@@ -28,6 +28,7 @@ except ModuleNotFoundError:
|
||||
|
||||
from src.agent.executor import AgentExecutor, AgentResult
|
||||
from src.agent.llm_adapter import LLMResponse, ToolCall
|
||||
from src.agent.runner import parse_dashboard_json, serialize_tool_result
|
||||
from src.agent.tools.registry import ToolRegistry, ToolDefinition, ToolParameter
|
||||
|
||||
|
||||
@@ -317,35 +318,30 @@ class TestAgentExecutor(unittest.TestCase):
|
||||
# ============================================================
|
||||
|
||||
class TestDashboardParsing(unittest.TestCase):
|
||||
"""Test _parse_dashboard with various input formats."""
|
||||
|
||||
def setUp(self):
|
||||
self.executor = AgentExecutor(
|
||||
ToolRegistry(), _make_mock_adapter(), max_steps=1
|
||||
)
|
||||
"""Test parse_dashboard_json with various input formats."""
|
||||
|
||||
def test_parse_markdown_json_block(self):
|
||||
content = f"Here is my analysis:\n```json\n{json.dumps(SAMPLE_DASHBOARD)}\n```\nDone."
|
||||
result = self.executor._parse_dashboard(content)
|
||||
result = parse_dashboard_json(content)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(result["sentiment_score"], 75)
|
||||
|
||||
def test_parse_raw_json(self):
|
||||
content = json.dumps(SAMPLE_DASHBOARD)
|
||||
result = self.executor._parse_dashboard(content)
|
||||
result = parse_dashboard_json(content)
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
def test_parse_json_in_text(self):
|
||||
content = f"Let me present: {json.dumps(SAMPLE_DASHBOARD)} — that's all."
|
||||
result = self.executor._parse_dashboard(content)
|
||||
result = parse_dashboard_json(content)
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
def test_parse_empty_content(self):
|
||||
self.assertIsNone(self.executor._parse_dashboard(""))
|
||||
self.assertIsNone(self.executor._parse_dashboard(None))
|
||||
self.assertIsNone(parse_dashboard_json(""))
|
||||
self.assertIsNone(parse_dashboard_json(None))
|
||||
|
||||
def test_parse_no_json(self):
|
||||
self.assertIsNone(self.executor._parse_dashboard("This is just plain text with no JSON"))
|
||||
self.assertIsNone(parse_dashboard_json("This is just plain text with no JSON"))
|
||||
|
||||
|
||||
# ============================================================
|
||||
@@ -353,29 +349,24 @@ class TestDashboardParsing(unittest.TestCase):
|
||||
# ============================================================
|
||||
|
||||
class TestSerializeToolResult(unittest.TestCase):
|
||||
"""Test _serialize_tool_result for various types."""
|
||||
|
||||
def setUp(self):
|
||||
self.executor = AgentExecutor(
|
||||
ToolRegistry(), _make_mock_adapter(), max_steps=1
|
||||
)
|
||||
"""Test serialize_tool_result for various types."""
|
||||
|
||||
def test_serialize_none(self):
|
||||
result = self.executor._serialize_tool_result(None)
|
||||
result = serialize_tool_result(None)
|
||||
self.assertEqual(json.loads(result), {"result": None})
|
||||
|
||||
def test_serialize_string(self):
|
||||
result = self.executor._serialize_tool_result("hello")
|
||||
result = serialize_tool_result("hello")
|
||||
self.assertEqual(result, "hello")
|
||||
|
||||
def test_serialize_dict(self):
|
||||
d = {"key": "value", "num": 42}
|
||||
result = self.executor._serialize_tool_result(d)
|
||||
result = serialize_tool_result(d)
|
||||
self.assertEqual(json.loads(result), d)
|
||||
|
||||
def test_serialize_list(self):
|
||||
lst = [1, 2, 3]
|
||||
result = self.executor._serialize_tool_result(lst)
|
||||
result = serialize_tool_result(lst)
|
||||
self.assertEqual(json.loads(result), lst)
|
||||
|
||||
def test_serialize_dataclass(self):
|
||||
@@ -384,7 +375,7 @@ class TestSerializeToolResult(unittest.TestCase):
|
||||
name: str = "test"
|
||||
value: int = 42
|
||||
|
||||
result = self.executor._serialize_tool_result(Sample())
|
||||
result = serialize_tool_result(Sample())
|
||||
parsed = json.loads(result)
|
||||
self.assertEqual(parsed["name"], "test")
|
||||
self.assertEqual(parsed["value"], 42)
|
||||
|
||||
Reference in New Issue
Block a user