diff --git a/cli/display.py b/cli/display.py index cba992cc9..1510e9581 100644 --- a/cli/display.py +++ b/cli/display.py @@ -404,8 +404,8 @@ def display_complete_report(final_state): research.append(("Bull Researcher", debate["bull_history"])) if debate.get("bear_history"): research.append(("Bear Researcher", debate["bear_history"])) - if debate.get("judge_decision"): - research.append(("Research Manager", debate["judge_decision"])) + if final_state.get("investment_plan"): + research.append(("Research Manager", final_state["investment_plan"])) if research: console.print(Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta")) for title, content in research: @@ -431,10 +431,10 @@ def display_complete_report(final_state): for title, content in risk_reports: console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2))) - # V. Portfolio Manager Decision - if risk.get("judge_decision"): - console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green")) - console.print(Panel(Markdown(risk["judge_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2))) + # V. Portfolio Manager Decision + if final_state.get("final_trade_decision"): + console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green")) + console.print(Panel(Markdown(final_state["final_trade_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2))) def update_research_team_status(status): diff --git a/cli/run.py b/cli/run.py index 01f6a6acb..60a2ffff2 100644 --- a/cli/run.py +++ b/cli/run.py @@ -253,7 +253,7 @@ def run_analysis(checkpoint: bool | None = None, portfolio=None): debate_state = chunk["investment_debate_state"] bull_hist = debate_state.get("bull_history", "").strip() bear_hist = debate_state.get("bear_history", "").strip() - judge = debate_state.get("judge_decision", "").strip() + judge = (chunk.get("investment_plan") or "").strip() # Only update status when there's actual content if bull_hist or bear_hist: @@ -288,7 +288,7 @@ def run_analysis(checkpoint: bool | None = None, portfolio=None): agg_hist = risk_state.get("aggressive_history", "").strip() con_hist = risk_state.get("conservative_history", "").strip() neu_hist = risk_state.get("neutral_history", "").strip() - judge = risk_state.get("judge_decision", "").strip() + judge = (chunk.get("final_trade_decision") or "").strip() if agg_hist: if message_buffer.agent_status.get("Aggressive Analyst") != "completed": diff --git a/tests/test_cli_display.py b/tests/test_cli_display.py index 8adb45ea1..674e668bd 100644 --- a/tests/test_cli_display.py +++ b/tests/test_cli_display.py @@ -48,9 +48,9 @@ def _state(ticker, final="评级: 买入"): "fundamentals_report": "基本面", "investment_plan": "计划", "trader_investment_plan": "交易计划", "final_trade_decision": final, "final_rating": "REVIEW", "investment_debate_state": {"bull_history": "", "bear_history": "", "history": "", - "current_response": "", "judge_decision": "", "count": 0}, + "current_response": "", "count": 0}, "risk_debate_state": {"aggressive_history": "", "conservative_history": "", - "neutral_history": "", "history": "", "judge_decision": "", + "neutral_history": "", "history": "", "latest_speaker": "", "current_aggressive_response": "", "current_conservative_response": "", "current_neutral_response": "", "count": 0}, @@ -76,6 +76,19 @@ def test_the_state_log_keeps_non_ascii_readable(tmp_path): assert json.loads(written) # still valid JSON +@pytest.mark.unit +def test_the_state_log_names_each_field_as_the_state_does(tmp_path): + """One name per field: the Trader's plan under its state key, and no second + copy of the managers' decisions under the debate states.""" + _bare_graph(tmp_path)._log_state("2026-09-01", _state("NVDA")) + + logged = json.loads(next(tmp_path.rglob("full_states_log*.json")).read_text(encoding="utf-8")) + assert logged["trader_investment_plan"] == "交易计划" + assert logged["investment_plan"] == "计划" + assert "trader_investment_decision" not in logged + assert "judge_decision" not in json.dumps(logged) + + @pytest.mark.unit def test_the_live_display_does_not_scroll_the_terminal(): """A layout taller than the window makes rich redraw by scrolling, which diff --git a/tests/test_memory_log.py b/tests/test_memory_log.py index 7652aafde..bcd955eb7 100644 --- a/tests/test_memory_log.py +++ b/tests/test_memory_log.py @@ -74,7 +74,6 @@ def _make_pm_state(past_context=""): "aggressive_history": "", "conservative_history": "", "neutral_history": "", - "judge_decision": "", "current_aggressive_response": "", "current_conservative_response": "", "current_neutral_response": "", @@ -906,13 +905,13 @@ class TestLegacyRemoval: "fundamentals_report": "", "investment_debate_state": { "bull_history": "", "bear_history": "", "history": "", - "current_response": "", "judge_decision": "", + "current_response": "", }, "investment_plan": "", "trader_investment_plan": "", "risk_debate_state": { "aggressive_history": "", "conservative_history": "", - "neutral_history": "", "history": "", "judge_decision": "", + "neutral_history": "", "history": "", "current_aggressive_response": "", "current_conservative_response": "", "current_neutral_response": "", "count": 1, "latest_speaker": "", }, diff --git a/tests/test_portfolio_context.py b/tests/test_portfolio_context.py index 228489671..4b89637d2 100644 --- a/tests/test_portfolio_context.py +++ b/tests/test_portfolio_context.py @@ -139,11 +139,11 @@ def test_decision_agents_see_the_portfolio(module, factory, monkeypatch): "news_report": "N", "fundamentals_report": "F", "investment_plan": "P", "trader_investment_plan": "T", "past_context": "", "portfolio_context": "PORTFOLIO_BLOCK_MARKER", - "investment_debate_state": {"history": "", "judge_decision": "", "count": 0}, + "investment_debate_state": {"history": "", "count": 0}, "risk_debate_state": {"history": "", "latest_speaker": "", "count": 0, "aggressive_history": "", "conservative_history": "", "neutral_history": "", "current_aggressive_response": "", "current_conservative_response": "", - "current_neutral_response": "", "judge_decision": ""}, + "current_neutral_response": ""}, } node = getattr(mod, factory)(_LLM()) node(state) diff --git a/tests/test_prompt_integrity.py b/tests/test_prompt_integrity.py index e26e72185..db9f38a27 100644 --- a/tests/test_prompt_integrity.py +++ b/tests/test_prompt_integrity.py @@ -74,11 +74,11 @@ def test_a_report_that_was_never_produced_says_so(module, factory): "market_report": "RSI 61, price 178.", "sentiment_report": "", "news_report": "", "fundamentals_report": "", "investment_plan": "P", "trader_investment_plan": "T", "investment_debate_state": {"bull_history": "", "bear_history": "", "history": "", - "current_response": "", "judge_decision": "", "count": 0}, + "current_response": "", "count": 0}, "risk_debate_state": {"history": "", "latest_speaker": "", "count": 0, "aggressive_history": "", "conservative_history": "", "neutral_history": "", "current_aggressive_response": "", "current_conservative_response": "", - "current_neutral_response": "", "judge_decision": ""}, + "current_neutral_response": ""}, } getattr(mod, factory)(_LLM())(state) diff --git a/tests/test_rating_integrity.py b/tests/test_rating_integrity.py index dc3c4373d..8d6523e08 100644 --- a/tests/test_rating_integrity.py +++ b/tests/test_rating_integrity.py @@ -194,11 +194,11 @@ def test_a_decision_prompt_states_the_shape_of_its_answer(module, factory, must_ "news_report": "N", "fundamentals_report": "F", "investment_plan": "P", "trader_investment_plan": "T", "past_context": "", "portfolio_context": "", "investment_debate_state": {"bull_history": "b", "bear_history": "r", "history": "h", - "current_response": "", "judge_decision": "", "count": 2}, + "current_response": "", "count": 2}, "risk_debate_state": {"history": "h", "latest_speaker": "", "count": 3, "aggressive_history": "", "conservative_history": "", "neutral_history": "", "current_aggressive_response": "", "current_conservative_response": "", - "current_neutral_response": "", "judge_decision": ""}, + "current_neutral_response": ""}, } getattr(mod, factory)(_LLM())(state) diff --git a/tests/test_reporting.py b/tests/test_reporting.py index 04a1d5a72..dd93e4edb 100644 --- a/tests/test_reporting.py +++ b/tests/test_reporting.py @@ -13,9 +13,11 @@ def _state(): return { "market_report": "MKT", "news_report": "NEWS", - "investment_debate_state": {"judge_decision": "RM PLAN"}, + "investment_debate_state": {"bull_history": "BULL"}, + "investment_plan": "RM PLAN", "trader_investment_plan": "TRADE", - "risk_debate_state": {"judge_decision": "PM DECISION"}, + "risk_debate_state": {"neutral_history": "NEUTRAL"}, + "final_trade_decision": "PM DECISION", } diff --git a/tests/test_structured_agent_prompts.py b/tests/test_structured_agent_prompts.py index c89fae767..47b8559ad 100644 --- a/tests/test_structured_agent_prompts.py +++ b/tests/test_structured_agent_prompts.py @@ -72,7 +72,7 @@ def test_research_manager_prompt_states_constraint(): "company_of_interest": "NVDA", "investment_debate_state": { "history": "h", "bull_history": "b", "bear_history": "r", - "current_response": "", "judge_decision": "", "count": 1, + "current_response": "", "count": 1, }, }) assert NO_EXTERNAL_TOOLS in _prompt_text(captured["prompt"]) diff --git a/tests/test_structured_agents.py b/tests/test_structured_agents.py index d385e0ea3..e168c12cd 100644 --- a/tests/test_structured_agents.py +++ b/tests/test_structured_agents.py @@ -292,7 +292,6 @@ def _make_rm_state(): "bull_history": "Bull says...", "bear_history": "Bear says...", "current_response": "", - "judge_decision": "", "count": 1, }, } diff --git a/tradingagents/agents/managers/portfolio_manager.py b/tradingagents/agents/managers/portfolio_manager.py index 228758221..0537b5e80 100644 --- a/tradingagents/agents/managers/portfolio_manager.py +++ b/tradingagents/agents/managers/portfolio_manager.py @@ -86,7 +86,6 @@ Write these sections, in this order, starting with the rating on its own line: final_rating = parse_rating(final_trade_decision) new_risk_debate_state = { - "judge_decision": final_trade_decision, "history": risk_debate_state["history"], "aggressive_history": risk_debate_state["aggressive_history"], "conservative_history": risk_debate_state["conservative_history"], diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index f1bb2c4fc..3bf288c60 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -59,7 +59,6 @@ Write these sections, in this order, starting with the recommendation on its own ) new_investment_debate_state = { - "judge_decision": investment_plan, "history": investment_debate_state.get("history", ""), "bear_history": investment_debate_state.get("bear_history", ""), "bull_history": investment_debate_state.get("bull_history", ""), diff --git a/tradingagents/agents/state.py b/tradingagents/agents/state.py index ba83f5100..5eea76703 100644 --- a/tradingagents/agents/state.py +++ b/tradingagents/agents/state.py @@ -14,7 +14,6 @@ class InvestDebateState(TypedDict): ] history: Annotated[str, "Conversation history"] current_response: Annotated[str, "Latest response"] - judge_decision: Annotated[str, "Final judge decision"] count: Annotated[int, "Length of the current conversation"] @@ -40,7 +39,6 @@ class RiskDebateState(TypedDict): current_neutral_response: Annotated[ str, "Latest response by the neutral analyst" ] - judge_decision: Annotated[str, "Judge's decision"] count: Annotated[int, "Length of the current conversation"] diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 775ddc3ae..733cfc00a 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -41,7 +41,6 @@ class Propagator: "bear_history": "", "history": "", "current_response": "", - "judge_decision": "", "count": 0, } ), @@ -55,7 +54,6 @@ class Propagator: "current_aggressive_response": "", "current_conservative_response": "", "current_neutral_response": "", - "judge_decision": "", "count": 0, } ), diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index c3b7387ea..ec8f6b887 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -359,17 +359,13 @@ class TradingAgentsGraph: "current_response": final_state["investment_debate_state"][ "current_response" ], - "judge_decision": final_state["investment_debate_state"][ - "judge_decision" - ], }, - "trader_investment_decision": final_state["trader_investment_plan"], + "trader_investment_plan": final_state["trader_investment_plan"], "risk_debate_state": { "aggressive_history": final_state["risk_debate_state"]["aggressive_history"], "conservative_history": final_state["risk_debate_state"]["conservative_history"], "neutral_history": final_state["risk_debate_state"]["neutral_history"], "history": final_state["risk_debate_state"]["history"], - "judge_decision": final_state["risk_debate_state"]["judge_decision"], }, "investment_plan": final_state["investment_plan"], "final_trade_decision": final_state["final_trade_decision"], diff --git a/tradingagents/reporting.py b/tradingagents/reporting.py index f89d6a04d..62759b875 100644 --- a/tradingagents/reporting.py +++ b/tradingagents/reporting.py @@ -52,10 +52,10 @@ def write_report_tree(final_state: dict, ticker: str, save_path) -> Path: research_dir.mkdir(exist_ok=True) (research_dir / "bear.md").write_text(debate["bear_history"], encoding="utf-8") research_parts.append(("Bear Researcher", debate["bear_history"])) - if debate.get("judge_decision"): + if final_state.get("investment_plan"): research_dir.mkdir(exist_ok=True) - (research_dir / "manager.md").write_text(debate["judge_decision"], encoding="utf-8") - research_parts.append(("Research Manager", debate["judge_decision"])) + (research_dir / "manager.md").write_text(final_state["investment_plan"], encoding="utf-8") + research_parts.append(("Research Manager", final_state["investment_plan"])) if research_parts: content = "\n\n".join(f"### {name}\n{text}" for name, text in research_parts) sections.append(f"## II. Research Team Decision\n\n{content}") @@ -88,12 +88,12 @@ def write_report_tree(final_state: dict, ticker: str, save_path) -> Path: content = "\n\n".join(f"### {name}\n{text}" for name, text in risk_parts) sections.append(f"## IV. Risk Management Team Decision\n\n{content}") - # 5. Portfolio Manager - if risk.get("judge_decision"): - portfolio_dir = save_path / "5_portfolio" - portfolio_dir.mkdir(exist_ok=True) - (portfolio_dir / "decision.md").write_text(risk["judge_decision"], encoding="utf-8") - sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}") + # 5. Portfolio Manager + if final_state.get("final_trade_decision"): + portfolio_dir = save_path / "5_portfolio" + portfolio_dir.mkdir(exist_ok=True) + (portfolio_dir / "decision.md").write_text(final_state["final_trade_decision"], encoding="utf-8") + sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{final_state['final_trade_decision']}") # Write consolidated report header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"