diff --git a/.kiro/specs/active/024-generic-agent-interface-contrib/tasks.md b/.kiro/specs/active/024-generic-agent-interface-contrib/tasks.md index 940deccc..daf56c3b 100644 --- a/.kiro/specs/active/024-generic-agent-interface-contrib/tasks.md +++ b/.kiro/specs/active/024-generic-agent-interface-contrib/tasks.md @@ -12,7 +12,7 @@ No standardized input/output contract for agents. Hard to swap, compose, or benc - [x] 1. Define AgentInput schema: ticker, date, context (market data, news, fundamentals) - [x] 2. Define AgentOutput schema: rating (5-tier), confidence, price_targets, thesis, risk_factors - [x] 3. Create BaseAgent abstract class with analyze(input) -> output contract -- [ ] 4. Refactor existing agents (fundamentals, sentiment, news, technical) to implement BaseAgent +- [x] 4. Refactor existing agents (fundamentals, sentiment, news, technical) to implement BaseAgent - [ ] 5. Create AgentRegistry for pluggable agent discovery - [ ] 6. Add agent benchmarking: compare outputs across different LLM backends - [ ] 7. Document interface for third-party agent contributions diff --git a/tradingagents/agents/__init__.py b/tradingagents/agents/__init__.py index c485fb04..0360e6c0 100644 --- a/tradingagents/agents/__init__.py +++ b/tradingagents/agents/__init__.py @@ -8,6 +8,12 @@ from .analysts.fundamentals_analyst import create_fundamentals_analyst from .analysts.market_analyst import create_market_analyst from .analysts.news_analyst import create_news_analyst from .analysts.social_media_analyst import create_social_media_analyst +from .analysts.base_analysts import ( + FundamentalsAgent, + SentimentAgent, + NewsAgent, + TechnicalAgent, +) from .researchers.bear_researcher import create_bear_researcher from .researchers.bull_researcher import create_bull_researcher @@ -23,6 +29,10 @@ from .trader.trader import create_trader __all__ = [ "BaseAgent", + "FundamentalsAgent", + "SentimentAgent", + "NewsAgent", + "TechnicalAgent", "FinancialSituationMemory", "AgentState", "AgentInput", diff --git a/tradingagents/agents/analysts/base_analysts.py b/tradingagents/agents/analysts/base_analysts.py new file mode 100644 index 00000000..df755ff0 --- /dev/null +++ b/tradingagents/agents/analysts/base_analysts.py @@ -0,0 +1,109 @@ +"""BaseAgent implementations for the four analyst types. + +Each class wraps the existing analyst logic behind the standardized +``BaseAgent.analyze(AgentInput) -> AgentOutput`` contract while the +original ``create_*`` factory functions remain unchanged for LangGraph +node compatibility. +""" + +from __future__ import annotations + +from langchain_core.messages import HumanMessage + +from tradingagents.agents.base_agent import BaseAgent +from tradingagents.agents.utils.schemas import AgentInput, AgentOutput + +# Shared prompt that asks the LLM to return a JSON matching AgentOutput. +_STRUCTURED_SUFFIX = ( + "\n\nAfter your analysis, provide a final JSON object with these exact keys:\n" + '- "rating": one of "BUY", "OVERWEIGHT", "HOLD", "UNDERWEIGHT", "SELL"\n' + '- "confidence": float 0.0-1.0\n' + '- "thesis": one-paragraph summary\n' + '- "risk_factors": list of strings\n' + "Return ONLY the JSON object, no other text." +) + + +def _invoke_structured(llm, role_prompt: str, agent_input: AgentInput) -> AgentOutput: + """Ask *llm* to produce an ``AgentOutput`` via structured output.""" + full_prompt = ( + f"{role_prompt}\n\n" + f"Ticker: {agent_input.ticker}\n" + f"Date: {agent_input.date}\n" + ) + if agent_input.context: + for k, v in agent_input.context.items(): + full_prompt += f"\n--- {k} ---\n{v}\n" + + full_prompt += _STRUCTURED_SUFFIX + + structured_llm = llm.with_structured_output(AgentOutput) + return structured_llm.invoke([HumanMessage(content=full_prompt)]) + + +class FundamentalsAgent(BaseAgent): + """Standardized fundamentals analyst.""" + + name: str = "fundamentals_analyst" + + def __init__(self, llm) -> None: + self.llm = llm + + def analyze(self, agent_input: AgentInput) -> AgentOutput: + return _invoke_structured( + self.llm, + "You are a fundamentals analyst. Evaluate the company's financial health " + "using balance sheets, cash flow, income statements, and key ratios.", + agent_input, + ) + + +class SentimentAgent(BaseAgent): + """Standardized sentiment / social-media analyst.""" + + name: str = "sentiment_analyst" + + def __init__(self, llm) -> None: + self.llm = llm + + def analyze(self, agent_input: AgentInput) -> AgentOutput: + return _invoke_structured( + self.llm, + "You are a sentiment analyst. Evaluate public sentiment from social media, " + "news headlines, and community discussions about the company.", + agent_input, + ) + + +class NewsAgent(BaseAgent): + """Standardized news analyst.""" + + name: str = "news_analyst" + + def __init__(self, llm) -> None: + self.llm = llm + + def analyze(self, agent_input: AgentInput) -> AgentOutput: + return _invoke_structured( + self.llm, + "You are a news analyst. Evaluate recent news, macroeconomic events, " + "and geopolitical developments relevant to the company.", + agent_input, + ) + + +class TechnicalAgent(BaseAgent): + """Standardized technical / market analyst.""" + + name: str = "technical_analyst" + + def __init__(self, llm) -> None: + self.llm = llm + + def analyze(self, agent_input: AgentInput) -> AgentOutput: + return _invoke_structured( + self.llm, + "You are a technical analyst. Evaluate price action, volume, moving averages, " + "MACD, RSI, Bollinger Bands, and other technical indicators.", + agent_input, + )