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 <noreply@anthropic.com>
This commit is contained in:
MarkLo127 2026-03-12 17:17:23 +08:00
parent b8454fefc7
commit 4f219bd74e
7 changed files with 62 additions and 9 deletions

View File

@ -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

View File

@ -54,6 +54,22 @@ export default function AnalysisResultsPage() {
const [saveError, setSaveError] = useState<string | null>(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,

View File

@ -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)

View File

@ -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
整合數據後提出專業見解

View File

@ -16,7 +16,7 @@ DEFAULT_CONFIG = {
# 辯論與討論設定
"max_debate_rounds": 1,
"max_risk_discuss_rounds": 1,
"max_recur_limit": 100,
"max_recur_limit": 200,
# 資料供應商設定
# 類別層級設定 (該類別所有工具的預設值)
# 可用供應商:

View File

@ -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,
}
), # 風險辯論的初始狀態

View File

@ -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)