mirror of
https://github.com/farcasclaudiu/TradingAgents.git
synced 2026-06-29 09:01:26 +03:00
feat: structured-output Trader and Research Manager (#434, finishes the trio)
Extends the canonical structured-output pattern from the Portfolio Manager to the other two decision-making agents. Each of the three agents now returns a typed Pydantic instance via llm.with_structured_output() in a single primary call, and a render helper turns the result into the same markdown shape downstream agents and saved reports already consume. - ResearchPlan: 5-tier recommendation, conversational rationale, concrete strategic actions for the trader. - TraderProposal: 3-tier action (transaction direction is naturally Buy / Hold / Sell — position sizing happens later at the Portfolio Manager), reasoning, and optional entry_price / stop_loss / position_sizing. Rendered output preserves the trailing "FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**" line for backward compatibility with the analyst stop-signal text. - PortfolioDecision: 5-tier rating, executive summary, investment thesis, optional price_target / time_horizon (unchanged). The shared try-structured-then-fallback pattern is extracted into tradingagents/agents/utils/structured.py (bind_structured + invoke_structured_or_freetext) so all three agents go through the same code path and log the same warning when a provider lacks structured output and the agent falls back to free-text generation. Net effect for users: every saved markdown report (research/manager.md, trading/trader.md, portfolio/decision.md) now has consistent section headers across runs and providers, easier to scan. Net effect for the runtime: the rating extraction round-trip is gone — the rating comes from the structured response itself, not a second LLM call. SignalProcessor was already simplified to a heuristic adapter in the previous commit. 11 new tests in tests/test_structured_agents.py cover the Trader and Research Manager render functions, structured-output happy paths, and free-text fallback. Full suite: 88 tests pass in ~2s without API keys.
This commit is contained in:
@@ -5,35 +5,24 @@ Uses LangChain's ``with_structured_output`` so the LLM produces a typed
|
||||
back to markdown for storage in ``final_trade_decision`` so memory log,
|
||||
CLI display, and saved reports continue to consume the same shape they do
|
||||
today. When a provider does not expose structured output, the agent falls
|
||||
back to a free-text invocation and the existing heuristic rating parser.
|
||||
back gracefully to free-text generation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from tradingagents.agents.schemas import PortfolioDecision, render_pm_decision
|
||||
from tradingagents.agents.utils.agent_utils import (
|
||||
build_instrument_context,
|
||||
get_language_instruction,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from tradingagents.agents.utils.structured import (
|
||||
bind_structured,
|
||||
invoke_structured_or_freetext,
|
||||
)
|
||||
|
||||
|
||||
def create_portfolio_manager(llm):
|
||||
# Wrap once at agent construction; if the provider does not support
|
||||
# structured output we keep ``structured_llm`` as None and use the
|
||||
# free-text fallback for every call.
|
||||
try:
|
||||
structured_llm = llm.with_structured_output(PortfolioDecision)
|
||||
except (NotImplementedError, AttributeError) as exc:
|
||||
logger.warning(
|
||||
"Portfolio Manager: provider does not support with_structured_output (%s); "
|
||||
"falling back to free-text generation",
|
||||
exc,
|
||||
)
|
||||
structured_llm = None
|
||||
structured_llm = bind_structured(llm, PortfolioDecision, "Portfolio Manager")
|
||||
|
||||
def portfolio_manager_node(state) -> dict:
|
||||
instrument_context = build_instrument_context(state["company_of_interest"])
|
||||
@@ -74,7 +63,13 @@ def create_portfolio_manager(llm):
|
||||
|
||||
Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()}"""
|
||||
|
||||
final_trade_decision = _invoke_pm(structured_llm, llm, prompt)
|
||||
final_trade_decision = invoke_structured_or_freetext(
|
||||
structured_llm,
|
||||
llm,
|
||||
prompt,
|
||||
render_pm_decision,
|
||||
"Portfolio Manager",
|
||||
)
|
||||
|
||||
new_risk_debate_state = {
|
||||
"judge_decision": final_trade_decision,
|
||||
@@ -95,26 +90,3 @@ Be decisive and ground every conclusion in specific evidence from the analysts.{
|
||||
}
|
||||
|
||||
return portfolio_manager_node
|
||||
|
||||
|
||||
def _invoke_pm(structured_llm, plain_llm, prompt: str) -> str:
|
||||
"""Run the PM call and return the markdown-rendered decision.
|
||||
|
||||
Tries the structured-output path first; if it fails for any reason
|
||||
(provider does not support it, model returns malformed JSON, network
|
||||
glitch on the structured endpoint), falls back to the plain free-text
|
||||
invocation so the pipeline still produces a result.
|
||||
"""
|
||||
if structured_llm is not None:
|
||||
try:
|
||||
decision = structured_llm.invoke(prompt)
|
||||
return render_pm_decision(decision)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Portfolio Manager: structured-output invocation failed (%s); "
|
||||
"retrying once as free text",
|
||||
exc,
|
||||
)
|
||||
|
||||
response = plain_llm.invoke(prompt)
|
||||
return response.content
|
||||
|
||||
Reference in New Issue
Block a user