From baf67dbd5846be2db6036eee8120baf1f8ee4624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E6=9D=B0?= Date: Tue, 14 Apr 2026 02:51:07 +0800 Subject: [PATCH] Trim the research phase before trusting profiling output The legacy path was already narrowed to market-only compact execution, but the research stage remained the slowest leg and the profiler lacked persistent raw event artifacts for comparison. This change further compresses the compact prompts for Bull Researcher, Bear Researcher, and Research Manager, adds durable raw event dumps to the graph profiler, and keeps profiling evidence out of the runtime contract itself. Constraint: No new dependencies and no runtime-contract pollution for profiling-only data Rejected: Add synthetic timing fields back into the subprocess protocol | those timings are not real graph-stage boundaries and would mislead diagnosis Rejected: Skip raw event dump persistence and rely on console output | makes multi-run comparison and regression tracking fragile Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep profiling as an external diagnostic surface; if stage timing ever enters contracts again, it must come from real graph boundaries Tested: python -m pytest web_dashboard/backend/tests/test_executors.py web_dashboard/backend/tests/test_services_migration.py web_dashboard/backend/tests/test_api_smoke.py -q Tested: python -m compileall tradingagents/agents/researchers/bull_researcher.py tradingagents/agents/researchers/bear_researcher.py tradingagents/agents/managers/research_manager.py orchestrator/profile_stage_chain.py Tested: real provider profiling via orchestrator/profile_stage_chain.py with market-only compact settings; dump persisted to orchestrator/profile_runs/600519.SS_2026-04-10_20260413T184742Z.json Not-tested: browser/manual consumption of the persisted profiling dump --- .gitignore | 1 + orchestrator/profile_stage_chain.py | 10 +++++++++ .../agents/managers/research_manager.py | 11 +++++++--- .../agents/researchers/bear_researcher.py | 21 +++++++++++-------- .../agents/researchers/bull_researcher.py | 21 +++++++++++-------- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 005cbd99..6f52df29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Git worktrees .worktrees/ +orchestrator/profile_runs/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/orchestrator/profile_stage_chain.py b/orchestrator/profile_stage_chain.py index 68fad753..5022fc51 100644 --- a/orchestrator/profile_stage_chain.py +++ b/orchestrator/profile_stage_chain.py @@ -5,6 +5,8 @@ import json import signal import time from collections import defaultdict +from datetime import datetime, timezone +from pathlib import Path from tradingagents.graph.propagation import Propagator from tradingagents.graph.trading_graph import TradingAgentsGraph @@ -34,6 +36,7 @@ def build_parser() -> argparse.ArgumentParser: parser.add_argument("--analysis-prompt-style", default="compact") parser.add_argument("--selected-analysts", default="market") parser.add_argument("--overall-timeout", type=int, default=120) + parser.add_argument("--dump-dir", default="orchestrator/profile_runs") return parser @@ -65,6 +68,10 @@ def main() -> None: phase_totals = defaultdict(float) started_at = time.monotonic() last_at = started_at + dump_dir = Path(args.dump_dir) + dump_dir.mkdir(parents=True, exist_ok=True) + run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + dump_path = dump_dir / f"{args.ticker.replace('/', '_')}_{args.date}_{run_id}.json" def alarm_handler(signum, frame): raise _ProfileTimeout(f"profiling timeout after {args.overall_timeout}s") @@ -97,6 +104,7 @@ def main() -> None: "analysis_prompt_style": args.analysis_prompt_style, "node_timings": node_timings, "phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()}, + "dump_path": str(dump_path), } except Exception as exc: payload = { @@ -108,10 +116,12 @@ def main() -> None: "error": str(exc), "node_timings": node_timings, "phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()}, + "dump_path": str(dump_path), } finally: signal.alarm(0) + dump_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2)) print(json.dumps(payload, ensure_ascii=False, indent=2)) diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index bd610fd7..304d9e24 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -17,7 +17,10 @@ def create_research_manager(llm, memory): investment_debate_state = state["investment_debate_state"] curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" - past_memories = memory.get_memories(curr_situation, n_matches=2) + past_memories = memory.get_memories( + curr_situation, + n_matches=1 if use_compact_analysis_prompt() else 2, + ) past_memory_str = "" for i, rec in enumerate(past_memories, 1): @@ -32,12 +35,14 @@ Return a concise response with: 3. Simple execution plan Past lessons: -{truncate_prompt_text(past_memory_str, 400)} +{truncate_prompt_text(past_memory_str, 180)} {instrument_context} Debate history: -{truncate_prompt_text(history, 1200)}""" +{truncate_prompt_text(history, 700)} + +Keep the full answer under 180 words.""" else: prompt = f"""As the portfolio manager and debate facilitator, your role is to critically evaluate this round of debate and make a definitive decision: align with the bear analyst, the bull analyst, or choose Hold only if it is strongly justified based on the arguments presented. diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index 815a50cf..ec418734 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -18,7 +18,10 @@ def create_bear_researcher(llm, memory): fundamentals_report = state["fundamentals_report"] curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" - past_memories = memory.get_memories(curr_situation, n_matches=2) + past_memories = memory.get_memories( + curr_situation, + n_matches=1 if use_compact_analysis_prompt() else 2, + ) past_memory_str = "" for i, rec in enumerate(past_memories, 1): @@ -27,15 +30,15 @@ def create_bear_researcher(llm, memory): if use_compact_analysis_prompt(): prompt = f"""You are a Bear Analyst. Make the strongest concise short case against the stock. -Use only the highest-signal evidence from the reports below. Address the latest bull point directly. Keep the answer under 220 words and end with a clear stance. +Use only the highest-signal evidence from the reports below. Address the latest bull point directly. Keep the answer under 140 words and end with a clear stance. -Market report: {truncate_prompt_text(market_research_report, 800)} -Sentiment report: {truncate_prompt_text(sentiment_report, 500)} -News report: {truncate_prompt_text(news_report, 500)} -Fundamentals report: {truncate_prompt_text(fundamentals_report, 700)} -Debate history: {truncate_prompt_text(history, 600)} -Last bull argument: {truncate_prompt_text(current_response, 400)} -Past lessons: {truncate_prompt_text(past_memory_str, 400)} +Market: {truncate_prompt_text(market_research_report, 420)} +Sentiment: {truncate_prompt_text(sentiment_report, 220)} +News: {truncate_prompt_text(news_report, 220)} +Fundamentals: {truncate_prompt_text(fundamentals_report, 320)} +Debate history: {truncate_prompt_text(history, 260)} +Last bull argument: {truncate_prompt_text(current_response, 180)} +Past lessons: {truncate_prompt_text(past_memory_str, 180)} """ else: prompt = f"""You are a Bear Analyst making the case against investing in the stock. Your goal is to present a well-reasoned argument emphasizing risks, challenges, and negative indicators. Leverage the provided research and data to highlight potential downsides and counter bullish arguments effectively. diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index e93434d5..c4d1f125 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -18,7 +18,10 @@ def create_bull_researcher(llm, memory): fundamentals_report = state["fundamentals_report"] curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" - past_memories = memory.get_memories(curr_situation, n_matches=2) + past_memories = memory.get_memories( + curr_situation, + n_matches=1 if use_compact_analysis_prompt() else 2, + ) past_memory_str = "" for i, rec in enumerate(past_memories, 1): @@ -27,15 +30,15 @@ def create_bull_researcher(llm, memory): if use_compact_analysis_prompt(): prompt = f"""You are a Bull Analyst. Make the strongest concise long case for the stock. -Use only the highest-signal evidence from the reports below. Address the latest bear point directly. Keep the answer under 220 words and end with a clear stance. +Use only the highest-signal evidence from the reports below. Address the latest bear point directly. Keep the answer under 140 words and end with a clear stance. -Market report: {truncate_prompt_text(market_research_report, 800)} -Sentiment report: {truncate_prompt_text(sentiment_report, 500)} -News report: {truncate_prompt_text(news_report, 500)} -Fundamentals report: {truncate_prompt_text(fundamentals_report, 700)} -Debate history: {truncate_prompt_text(history, 600)} -Last bear argument: {truncate_prompt_text(current_response, 400)} -Past lessons: {truncate_prompt_text(past_memory_str, 400)} +Market: {truncate_prompt_text(market_research_report, 420)} +Sentiment: {truncate_prompt_text(sentiment_report, 220)} +News: {truncate_prompt_text(news_report, 220)} +Fundamentals: {truncate_prompt_text(fundamentals_report, 320)} +Debate history: {truncate_prompt_text(history, 260)} +Last bear argument: {truncate_prompt_text(current_response, 180)} +Past lessons: {truncate_prompt_text(past_memory_str, 180)} """ else: prompt = f"""You are a Bull Analyst advocating for investing in the stock. Your task is to build a strong, evidence-based case emphasizing growth potential, competitive advantages, and positive market indicators. Leverage the provided research and data to address concerns and counter bearish arguments effectively.