From eb7453d60399911237e853ffbf30571375616299 Mon Sep 17 00:00:00 2001 From: Peter Wong Date: Fri, 3 Apr 2026 01:35:27 +0100 Subject: [PATCH] test --- .gitignore | 2 ++ cli/main.py | 4 +++ .../agents/analysts/quant_analyst.py | 32 +++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7cef8fc7..a024de4b 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,5 @@ __marimo__/ # Cache **/data_cache/ CLAUDE.md +reports/* +results/* diff --git a/cli/main.py b/cli/main.py index 5a1ad1b0..6183ecbf 100644 --- a/cli/main.py +++ b/cli/main.py @@ -667,6 +667,10 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path): analysts_dir.mkdir(exist_ok=True) (analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"]) analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"])) + if final_state.get("quant_report"): + analysts_dir.mkdir(exist_ok=True) + (analysts_dir / "quant.md").write_text(final_state["quant_report"]) + analyst_parts.append(("Quant Analyst", final_state["quant_report"])) if analyst_parts: content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts) sections.append(f"## I. Analyst Team Reports\n\n{content}") diff --git a/tradingagents/agents/analysts/quant_analyst.py b/tradingagents/agents/analysts/quant_analyst.py index 919feee6..bd1be8fe 100644 --- a/tradingagents/agents/analysts/quant_analyst.py +++ b/tradingagents/agents/analysts/quant_analyst.py @@ -1,13 +1,23 @@ +import re + +from langchain_core.messages import ToolMessage from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from tradingagents.agents.utils.quant_tools import get_quant_analysis from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction +def _looks_like_raw_tool_call(text: str) -> bool: + """Return True if the LLM output a tool call as plain text instead of via the API.""" + stripped = re.sub(r"^```+\w*\s*", "", text.strip()) + return bool(re.search(r'"name"\s*:', stripped) and re.search(r'"arguments"\s*:', stripped)) + + def create_quant_analyst(llm): def quant_analyst_node(state): current_date = state["trade_date"] - instrument_context = build_instrument_context(state["company_of_interest"]) + ticker = state["company_of_interest"] + instrument_context = build_instrument_context(ticker) tools = [get_quant_analysis] @@ -49,7 +59,25 @@ def create_quant_analyst(llm): report = "" if len(result.tool_calls) == 0: - report = result.content + content = result.content if isinstance(result.content, str) else "" + if _looks_like_raw_tool_call(content): + # The model output the tool call as plain text instead of using the + # function-calling API (common with weaker/local models). Call the + # tool directly so the quant data is always populated. + try: + raw_data = get_quant_analysis.invoke( + {"ticker": ticker, "analysis_date": current_date} + ) + except Exception as e: + raw_data = f"Quant analysis unavailable: {e}" + # Feed the raw data back to the LLM so it can write the narrative report. + tool_msg = ToolMessage(content=raw_data, tool_call_id="fallback") + followup = chain.invoke(state["messages"] + [result, tool_msg]) + fc = followup.content if isinstance(followup.content, str) else "" + report = fc if fc and not _looks_like_raw_tool_call(fc) else raw_data + result = followup + else: + report = content return { "messages": [result],