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
This commit is contained in:
parent
8a4f0ad540
commit
baf67dbd58
|
|
@ -1,5 +1,6 @@
|
||||||
# Git worktrees
|
# Git worktrees
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
orchestrator/profile_runs/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import json
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from tradingagents.graph.propagation import Propagator
|
from tradingagents.graph.propagation import Propagator
|
||||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
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("--analysis-prompt-style", default="compact")
|
||||||
parser.add_argument("--selected-analysts", default="market")
|
parser.add_argument("--selected-analysts", default="market")
|
||||||
parser.add_argument("--overall-timeout", type=int, default=120)
|
parser.add_argument("--overall-timeout", type=int, default=120)
|
||||||
|
parser.add_argument("--dump-dir", default="orchestrator/profile_runs")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -65,6 +68,10 @@ def main() -> None:
|
||||||
phase_totals = defaultdict(float)
|
phase_totals = defaultdict(float)
|
||||||
started_at = time.monotonic()
|
started_at = time.monotonic()
|
||||||
last_at = started_at
|
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):
|
def alarm_handler(signum, frame):
|
||||||
raise _ProfileTimeout(f"profiling timeout after {args.overall_timeout}s")
|
raise _ProfileTimeout(f"profiling timeout after {args.overall_timeout}s")
|
||||||
|
|
@ -97,6 +104,7 @@ def main() -> None:
|
||||||
"analysis_prompt_style": args.analysis_prompt_style,
|
"analysis_prompt_style": args.analysis_prompt_style,
|
||||||
"node_timings": node_timings,
|
"node_timings": node_timings,
|
||||||
"phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()},
|
"phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()},
|
||||||
|
"dump_path": str(dump_path),
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
payload = {
|
payload = {
|
||||||
|
|
@ -108,10 +116,12 @@ def main() -> None:
|
||||||
"error": str(exc),
|
"error": str(exc),
|
||||||
"node_timings": node_timings,
|
"node_timings": node_timings,
|
||||||
"phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()},
|
"phase_totals_seconds": {key: round(value, 3) for key, value in phase_totals.items()},
|
||||||
|
"dump_path": str(dump_path),
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
signal.alarm(0)
|
signal.alarm(0)
|
||||||
|
|
||||||
|
dump_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ def create_research_manager(llm, memory):
|
||||||
investment_debate_state = state["investment_debate_state"]
|
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}"
|
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 = ""
|
past_memory_str = ""
|
||||||
for i, rec in enumerate(past_memories, 1):
|
for i, rec in enumerate(past_memories, 1):
|
||||||
|
|
@ -32,12 +35,14 @@ Return a concise response with:
|
||||||
3. Simple execution plan
|
3. Simple execution plan
|
||||||
|
|
||||||
Past lessons:
|
Past lessons:
|
||||||
{truncate_prompt_text(past_memory_str, 400)}
|
{truncate_prompt_text(past_memory_str, 180)}
|
||||||
|
|
||||||
{instrument_context}
|
{instrument_context}
|
||||||
|
|
||||||
Debate history:
|
Debate history:
|
||||||
{truncate_prompt_text(history, 1200)}"""
|
{truncate_prompt_text(history, 700)}
|
||||||
|
|
||||||
|
Keep the full answer under 180 words."""
|
||||||
else:
|
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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ def create_bear_researcher(llm, memory):
|
||||||
fundamentals_report = state["fundamentals_report"]
|
fundamentals_report = state["fundamentals_report"]
|
||||||
|
|
||||||
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{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 = ""
|
past_memory_str = ""
|
||||||
for i, rec in enumerate(past_memories, 1):
|
for i, rec in enumerate(past_memories, 1):
|
||||||
|
|
@ -27,15 +30,15 @@ def create_bear_researcher(llm, memory):
|
||||||
if use_compact_analysis_prompt():
|
if use_compact_analysis_prompt():
|
||||||
prompt = f"""You are a Bear Analyst. Make the strongest concise short case against the stock.
|
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)}
|
Market: {truncate_prompt_text(market_research_report, 420)}
|
||||||
Sentiment report: {truncate_prompt_text(sentiment_report, 500)}
|
Sentiment: {truncate_prompt_text(sentiment_report, 220)}
|
||||||
News report: {truncate_prompt_text(news_report, 500)}
|
News: {truncate_prompt_text(news_report, 220)}
|
||||||
Fundamentals report: {truncate_prompt_text(fundamentals_report, 700)}
|
Fundamentals: {truncate_prompt_text(fundamentals_report, 320)}
|
||||||
Debate history: {truncate_prompt_text(history, 600)}
|
Debate history: {truncate_prompt_text(history, 260)}
|
||||||
Last bull argument: {truncate_prompt_text(current_response, 400)}
|
Last bull argument: {truncate_prompt_text(current_response, 180)}
|
||||||
Past lessons: {truncate_prompt_text(past_memory_str, 400)}
|
Past lessons: {truncate_prompt_text(past_memory_str, 180)}
|
||||||
"""
|
"""
|
||||||
else:
|
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ def create_bull_researcher(llm, memory):
|
||||||
fundamentals_report = state["fundamentals_report"]
|
fundamentals_report = state["fundamentals_report"]
|
||||||
|
|
||||||
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{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 = ""
|
past_memory_str = ""
|
||||||
for i, rec in enumerate(past_memories, 1):
|
for i, rec in enumerate(past_memories, 1):
|
||||||
|
|
@ -27,15 +30,15 @@ def create_bull_researcher(llm, memory):
|
||||||
if use_compact_analysis_prompt():
|
if use_compact_analysis_prompt():
|
||||||
prompt = f"""You are a Bull Analyst. Make the strongest concise long case for the stock.
|
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)}
|
Market: {truncate_prompt_text(market_research_report, 420)}
|
||||||
Sentiment report: {truncate_prompt_text(sentiment_report, 500)}
|
Sentiment: {truncate_prompt_text(sentiment_report, 220)}
|
||||||
News report: {truncate_prompt_text(news_report, 500)}
|
News: {truncate_prompt_text(news_report, 220)}
|
||||||
Fundamentals report: {truncate_prompt_text(fundamentals_report, 700)}
|
Fundamentals: {truncate_prompt_text(fundamentals_report, 320)}
|
||||||
Debate history: {truncate_prompt_text(history, 600)}
|
Debate history: {truncate_prompt_text(history, 260)}
|
||||||
Last bear argument: {truncate_prompt_text(current_response, 400)}
|
Last bear argument: {truncate_prompt_text(current_response, 180)}
|
||||||
Past lessons: {truncate_prompt_text(past_memory_str, 400)}
|
Past lessons: {truncate_prompt_text(past_memory_str, 180)}
|
||||||
"""
|
"""
|
||||||
else:
|
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.
|
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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue