From 4f219bd74ef1d1c40f309bc5a2ec8c395111fd48 Mon Sep 17 00:00:00 2001 From: MarkLo127 Date: Thu, 12 Mar 2026 17:17:23 +0800 Subject: [PATCH] Fix: Resolve GraphRecursionError by passing config params to agents and increasing recursion_limit to 200 - Pass max_debate_rounds and max_risk_discuss_rounds from config to ConditionalLogic - Pass max_recur_limit from config to Propagator - Increase default recursion_limit from 100 to 200 in default_config.py - Increase Propagator default max_recur_limit from 100 to 200 Also includes earlier fixes: - Add 365-day minimum date range validation to get_stock_data tool - Update market analyst prompt to specify 1-year data requirement - Initialize all debate state fields (bull_history, bear_history, judge_decision, etc.) - Add report completeness logging in trading_service.py - Add debug logging in frontend results page Co-Authored-By: Claude Haiku 4.5 --- backend/app/services/trading_service.py | 11 ++++++++++- frontend/app/analysis/results/page.tsx | 16 ++++++++++++++++ tradingagents/agents/utils/core_stock_tools.py | 11 +++++++++++ tradingagents/agents/utils/prompts.py | 4 ++-- tradingagents/default_config.py | 2 +- tradingagents/graph/propagation.py | 16 ++++++++++++++-- tradingagents/graph/trading_graph.py | 11 ++++++++--- 7 files changed, 62 insertions(+), 9 deletions(-) diff --git a/backend/app/services/trading_service.py b/backend/app/services/trading_service.py index 0e8910a4..2f135009 100644 --- a/backend/app/services/trading_service.py +++ b/backend/app/services/trading_service.py @@ -205,7 +205,16 @@ class TradingService: "investment_debate_state": final_state.get("investment_debate_state"), "risk_debate_state": final_state.get("risk_debate_state"), } - + + # Log report completeness for debugging + for key, value in reports.items(): + if isinstance(value, dict): + sub_filled = [k for k, v in value.items() if v and k not in ("count", "latest_speaker")] + logger.info(f"📊 Report '{key}': filled fields = {sub_filled}") + else: + status_icon = "✅" if value else "❌" + logger.info(f"📊 Report '{key}': {status_icon} {'populated' if value else 'EMPTY'}") + # Load price data from backend.app.services.price_service import PriceService price_data = None diff --git a/frontend/app/analysis/results/page.tsx b/frontend/app/analysis/results/page.tsx index 403f2606..407bd226 100644 --- a/frontend/app/analysis/results/page.tsx +++ b/frontend/app/analysis/results/page.tsx @@ -54,6 +54,22 @@ export default function AnalysisResultsPage() { const [saveError, setSaveError] = useState(null); const [savedToCloud, setSavedToCloud] = useState(false); + // Debug: log received analysis data structure + useEffect(() => { + if (analysisResult) { + console.log("[Results] analysisResult.reports keys:", Object.keys(analysisResult.reports || {})); + const ids = analysisResult.reports?.investment_debate_state; + const rds = analysisResult.reports?.risk_debate_state; + if (ids) console.log("[Results] investment_debate_state keys:", Object.keys(ids)); + if (rds) console.log("[Results] risk_debate_state keys:", Object.keys(rds)); + // Log which reports are populated + ANALYST_KEYS.forEach(a => { + const val = getNestedValue(analysisResult.reports, a.reportKey); + console.log(`[Results] ${a.key} (${a.reportKey}):`, val ? "populated" : "EMPTY/MISSING"); + }); + } + }, [analysisResult]); + // Build analysts array with translations const ANALYSTS = useMemo(() => ANALYST_KEYS.map(analyst => ({ key: analyst.key, diff --git a/tradingagents/agents/utils/core_stock_tools.py b/tradingagents/agents/utils/core_stock_tools.py index cb775603..a5d6cd66 100644 --- a/tradingagents/agents/utils/core_stock_tools.py +++ b/tradingagents/agents/utils/core_stock_tools.py @@ -1,5 +1,6 @@ from langchain_core.tools import tool from typing import Annotated +from datetime import datetime, timedelta from tradingagents.dataflows.interface import route_to_vendor @@ -19,4 +20,14 @@ def get_stock_data( Returns: str: 一個格式化的數據框,包含指定股票代碼在指定日期範圍內的股價數據。 """ + # 強制至少 365 天的資料範圍,確保分析品質一致 + try: + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + end_dt = datetime.strptime(end_date, "%Y-%m-%d") + if (end_dt - start_dt).days < 365: + start_dt = end_dt - timedelta(days=365) + start_date = start_dt.strftime("%Y-%m-%d") + print(f"[get_stock_data] 日期範圍不足 1 年,已自動調整 start_date 為 {start_date}") + except ValueError: + pass # 日期格式錯誤時使用原始值 return route_to_vendor("get_stock_data", symbol, start_date, end_date) \ No newline at end of file diff --git a/tradingagents/agents/utils/prompts.py b/tradingagents/agents/utils/prompts.py index e60944ef..0d077504 100644 --- a/tradingagents/agents/utils/prompts.py +++ b/tradingagents/agents/utils/prompts.py @@ -87,7 +87,7 @@ You are a senior technical analyst responsible for providing precise market tech 4. **Trading Recommendations**: Provide entry/exit positions, risk control parameters 【Technical Operations】 -• Use get_stock_data to obtain historical price data +• Use get_stock_data to obtain historical price data — always request at least 1 year of data (start_date = trade_date minus 365 days) • Use get_indicators to calculate technical indicators (set look_back_days to 50 or 200 for moving averages) • Integrate data to provide professional insights @@ -118,7 +118,7 @@ Please provide a professional, precise, and actionable technical analysis report 4. **操作建議**:提供進出場位置、風險控制參數 【技術操作】 -• 使用 get_stock_data 取得歷史價格資料 +• 使用 get_stock_data 取得歷史價格資料 — 務必取得至少 1 年的資料(start_date = 交易日期減 365 天) • 使用 get_indicators 計算技術指標(均線請設定 look_back_days 為 50 或 200) • 整合數據後提出專業見解 diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 5ea07140..0c4f33f2 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -16,7 +16,7 @@ DEFAULT_CONFIG = { # 辯論與討論設定 "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, - "max_recur_limit": 100, + "max_recur_limit": 200, # 資料供應商設定 # 類別層級設定 (該類別所有工具的預設值) # 可用供應商: diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 46a991e7..f96202da 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -18,7 +18,7 @@ class Propagator: 這個類別負責建立圖執行的初始狀態,並提供圖呼叫所需的參數。 """ - def __init__(self, max_recur_limit=100): + def __init__(self, max_recur_limit=200): """ 使用設定參數進行初始化。 @@ -68,14 +68,26 @@ class Propagator: "company_name": actual_company_name, # 真實公司全名 "trade_date": str(trade_date), # 交易日期 "investment_debate_state": InvestDebateState( - {"history": "", "current_response": "", "count": 0} + { + "bull_history": "", + "bear_history": "", + "history": "", + "current_response": "", + "judge_decision": "", + "count": 0, + } ), # 投資辯論的初始狀態 "risk_debate_state": RiskDebateState( { + "risky_history": "", + "safe_history": "", + "neutral_history": "", "history": "", + "latest_speaker": "", "current_risky_response": "", "current_safe_response": "", "current_neutral_response": "", + "judge_decision": "", "count": 0, } ), # 風險辯論的初始狀態 diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 866c15de..55b86354 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -146,8 +146,11 @@ class TradingAgentsXGraph: # Extract language from config (default: zh-TW for backward compatibility) self.language = self.config.get("language", "zh-TW") - # 初始化組件 - self.conditional_logic = ConditionalLogic() + # 初始化組件(從 config 傳入辯論回合數) + self.conditional_logic = ConditionalLogic( + max_debate_rounds=self.config.get("max_debate_rounds", 1), + max_risk_discuss_rounds=self.config.get("max_risk_discuss_rounds", 1), + ) self.graph_setup = GraphSetup( self.quick_thinking_llm, self.deep_thinking_llm, @@ -161,7 +164,9 @@ class TradingAgentsXGraph: self.language, # Pass language for agent reports ) - self.propagator = Propagator() + self.propagator = Propagator( + max_recur_limit=self.config.get("max_recur_limit", 200), + ) self.reflector = Reflector(self.quick_thinking_llm) self.signal_processor = SignalProcessor(self.quick_thinking_llm)