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:
parent
b8454fefc7
commit
4f219bd74e
|
|
@ -205,7 +205,16 @@ class TradingService:
|
||||||
"investment_debate_state": final_state.get("investment_debate_state"),
|
"investment_debate_state": final_state.get("investment_debate_state"),
|
||||||
"risk_debate_state": final_state.get("risk_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
|
# Load price data
|
||||||
from backend.app.services.price_service import PriceService
|
from backend.app.services.price_service import PriceService
|
||||||
price_data = None
|
price_data = None
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,22 @@ export default function AnalysisResultsPage() {
|
||||||
const [saveError, setSaveError] = useState<string | null>(null);
|
const [saveError, setSaveError] = useState<string | null>(null);
|
||||||
const [savedToCloud, setSavedToCloud] = useState(false);
|
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
|
// Build analysts array with translations
|
||||||
const ANALYSTS = useMemo(() => ANALYST_KEYS.map(analyst => ({
|
const ANALYSTS = useMemo(() => ANALYST_KEYS.map(analyst => ({
|
||||||
key: analyst.key,
|
key: analyst.key,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from tradingagents.dataflows.interface import route_to_vendor
|
from tradingagents.dataflows.interface import route_to_vendor
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,4 +20,14 @@ def get_stock_data(
|
||||||
Returns:
|
Returns:
|
||||||
str: 一個格式化的數據框,包含指定股票代碼在指定日期範圍內的股價數據。
|
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)
|
return route_to_vendor("get_stock_data", symbol, start_date, end_date)
|
||||||
|
|
@ -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
|
4. **Trading Recommendations**: Provide entry/exit positions, risk control parameters
|
||||||
|
|
||||||
【Technical Operations】
|
【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)
|
• Use get_indicators to calculate technical indicators (set look_back_days to 50 or 200 for moving averages)
|
||||||
• Integrate data to provide professional insights
|
• Integrate data to provide professional insights
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ Please provide a professional, precise, and actionable technical analysis report
|
||||||
4. **操作建議**:提供進出場位置、風險控制參數
|
4. **操作建議**:提供進出場位置、風險控制參數
|
||||||
|
|
||||||
【技術操作】
|
【技術操作】
|
||||||
• 使用 get_stock_data 取得歷史價格資料
|
• 使用 get_stock_data 取得歷史價格資料 — 務必取得至少 1 年的資料(start_date = 交易日期減 365 天)
|
||||||
• 使用 get_indicators 計算技術指標(均線請設定 look_back_days 為 50 或 200)
|
• 使用 get_indicators 計算技術指標(均線請設定 look_back_days 為 50 或 200)
|
||||||
• 整合數據後提出專業見解
|
• 整合數據後提出專業見解
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ DEFAULT_CONFIG = {
|
||||||
# 辯論與討論設定
|
# 辯論與討論設定
|
||||||
"max_debate_rounds": 1,
|
"max_debate_rounds": 1,
|
||||||
"max_risk_discuss_rounds": 1,
|
"max_risk_discuss_rounds": 1,
|
||||||
"max_recur_limit": 100,
|
"max_recur_limit": 200,
|
||||||
# 資料供應商設定
|
# 資料供應商設定
|
||||||
# 類別層級設定 (該類別所有工具的預設值)
|
# 類別層級設定 (該類別所有工具的預設值)
|
||||||
# 可用供應商:
|
# 可用供應商:
|
||||||
|
|
|
||||||
|
|
@ -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, # 真實公司全名
|
"company_name": actual_company_name, # 真實公司全名
|
||||||
"trade_date": str(trade_date), # 交易日期
|
"trade_date": str(trade_date), # 交易日期
|
||||||
"investment_debate_state": InvestDebateState(
|
"investment_debate_state": InvestDebateState(
|
||||||
{"history": "", "current_response": "", "count": 0}
|
{
|
||||||
|
"bull_history": "",
|
||||||
|
"bear_history": "",
|
||||||
|
"history": "",
|
||||||
|
"current_response": "",
|
||||||
|
"judge_decision": "",
|
||||||
|
"count": 0,
|
||||||
|
}
|
||||||
), # 投資辯論的初始狀態
|
), # 投資辯論的初始狀態
|
||||||
"risk_debate_state": RiskDebateState(
|
"risk_debate_state": RiskDebateState(
|
||||||
{
|
{
|
||||||
|
"risky_history": "",
|
||||||
|
"safe_history": "",
|
||||||
|
"neutral_history": "",
|
||||||
"history": "",
|
"history": "",
|
||||||
|
"latest_speaker": "",
|
||||||
"current_risky_response": "",
|
"current_risky_response": "",
|
||||||
"current_safe_response": "",
|
"current_safe_response": "",
|
||||||
"current_neutral_response": "",
|
"current_neutral_response": "",
|
||||||
|
"judge_decision": "",
|
||||||
"count": 0,
|
"count": 0,
|
||||||
}
|
}
|
||||||
), # 風險辯論的初始狀態
|
), # 風險辯論的初始狀態
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,11 @@ class TradingAgentsXGraph:
|
||||||
# Extract language from config (default: zh-TW for backward compatibility)
|
# Extract language from config (default: zh-TW for backward compatibility)
|
||||||
self.language = self.config.get("language", "zh-TW")
|
self.language = self.config.get("language", "zh-TW")
|
||||||
|
|
||||||
# 初始化組件
|
# 初始化組件(從 config 傳入辯論回合數)
|
||||||
self.conditional_logic = ConditionalLogic()
|
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.graph_setup = GraphSetup(
|
||||||
self.quick_thinking_llm,
|
self.quick_thinking_llm,
|
||||||
self.deep_thinking_llm,
|
self.deep_thinking_llm,
|
||||||
|
|
@ -161,7 +164,9 @@ class TradingAgentsXGraph:
|
||||||
self.language, # Pass language for agent reports
|
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.reflector = Reflector(self.quick_thinking_llm)
|
||||||
self.signal_processor = SignalProcessor(self.quick_thinking_llm)
|
self.signal_processor = SignalProcessor(self.quick_thinking_llm)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue