"""Agent factory functions for creating configured CrewAI Agent instances.""" from crewai import Agent, LLM from crew.config import LLMConfig def create_llm(config: LLMConfig) -> LLM: """Create a shared crewai.LLM instance pointing to the vLLM endpoint. Newer versions of crewai validate ``Agent(llm=...)`` strictly and only accept a :class:`crewai.LLM` (or a plain model-name string). A raw ``langchain_openai.ChatOpenAI`` is rejected. crewai.LLM delegates to litellm under the hood, and litellm needs an explicit provider prefix for self-hosted OpenAI-compatible endpoints — hence the ``hosted_vllm/`` prefix on the model name. ``api_key`` is a required positional for the OpenAI provider flow even when the upstream server (vLLM) does not enforce auth; any non-empty value is accepted. """ model_name = config.model_name if not model_name.startswith("hosted_vllm/"): model_name = f"hosted_vllm/{model_name}" return LLM( model=model_name, base_url=config.base_url, api_key="not-needed", temperature=config.temperature, max_tokens=config.max_tokens, timeout=config.request_timeout, ) def create_market_scanner(llm: LLM, tools: list) -> Agent: """Create the Market Scanner agent. Args: llm: Shared LLM instance tools: [search_news, get_price_change, get_volume] """ return Agent( role="Market Scanner", goal="Detect significant market events, price movements, and volume anomalies for the given ticker", backstory=( "You are an experienced market surveillance specialist who monitors " "news feeds, price action, and trading volumes 24/7. You have a keen " "eye for detecting material events that could impact stock prices." ), llm=llm, tools=tools, max_iter=5, verbose=True, ) def create_fundamental_analyst(llm: LLM, tools: list) -> Agent: """Create the Fundamental Analyst agent. Args: llm: Shared LLM instance tools: [get_financials, get_earnings, get_peers] """ return Agent( role="Fundamental Analyst", goal="Determine the intrinsic value of the company by analyzing financial metrics, earnings trends, and peer comparisons", backstory=( "You are a seasoned equity research analyst with 15 years of experience " "in fundamental valuation. You specialize in dissecting financial statements, " "identifying earnings quality, and comparing companies against their peers." ), llm=llm, tools=tools, max_iter=5, verbose=True, ) def create_technical_analyst(llm: LLM, tools: list) -> Agent: """Create the Technical Analyst agent. Args: llm: Shared LLM instance tools: [get_price_history, calculate_indicators] """ return Agent( role="Technical Analyst", goal="Identify optimal entry and exit points using price patterns and technical indicators", backstory=( "You are a quantitative technical analyst who combines classical chart " "patterns with modern indicator analysis. You focus on RSI, MACD, " "Bollinger Bands, and moving average crossovers to time entries precisely." ), llm=llm, tools=tools, max_iter=5, verbose=True, ) def create_risk_manager(llm: LLM, tools: list) -> Agent: """Create the Risk Manager agent. Args: llm: Shared LLM instance tools: [calculate_position_size, set_stop_loss] """ return Agent( role="Risk Manager", goal="Protect capital through optimal position sizing and stop-loss placement based on volatility", backstory=( "You are a portfolio risk specialist who never lets a single trade " "risk more than the defined threshold. You use ATR-based stop-losses " "and position sizing formulas to ensure consistent risk management." ), llm=llm, tools=tools, max_iter=5, verbose=True, ) def create_chief_strategist(llm: LLM) -> Agent: """Create the Chief Strategist agent (no tools, pure reasoning).""" return Agent( role="Chief Strategist", goal="Synthesize all agent analyses into a single, actionable trading signal with confidence level", backstory=( "You are the head of trading strategy with decades of experience " "integrating fundamental, technical, and risk perspectives into " "decisive trading calls. You weigh conflicting signals and produce " "clear BUY/SELL/HOLD recommendations with calibrated confidence." ), llm=llm, tools=[], max_iter=5, verbose=True, )