from copy import deepcopy import json import tradingagents.dataflows.config as dataflow_config import tradingagents.default_config as default_config from tradingagents.llm_clients.anthropic_client import AnthropicClient from tradingagents.llm_clients.google_client import GoogleClient from tradingagents.graph.trading_graph import TradingAgentsGraph class DummyClient: def __init__(self, provider, model, base_url=None, **kwargs): self.provider = provider self.model = model self.base_url = base_url self.kwargs = kwargs def get_llm(self): return { "provider": self.provider, "model": self.model, "base_url": self.base_url, "kwargs": self.kwargs, } class DummyStateGraph: def __init__(self, _state_type): self.nodes = {} def add_node(self, name, node): self.nodes[name] = node def add_edge(self, *_args, **_kwargs): return None def add_conditional_edges(self, *_args, **_kwargs): return None def compile(self): return {"nodes": self.nodes} def _patch_graph_setup_wiring(monkeypatch, recorded_llms): monkeypatch.setattr("tradingagents.graph.setup.StateGraph", DummyStateGraph) monkeypatch.setattr("tradingagents.graph.setup.create_msg_delete", lambda: "delete") def make_factory(node_name): def factory(llm, *_args): recorded_llms[node_name] = llm return node_name return factory monkeypatch.setattr( "tradingagents.graph.setup.create_market_analyst", make_factory("Market Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_social_media_analyst", make_factory("Social Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_news_analyst", make_factory("News Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_fundamentals_analyst", make_factory("Fundamentals Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_bull_researcher", make_factory("Bull Researcher"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_bear_researcher", make_factory("Bear Researcher"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_research_manager", make_factory("Research Manager"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_trader", make_factory("Trader"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_aggressive_debator", make_factory("Aggressive Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_neutral_debator", make_factory("Neutral Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_conservative_debator", make_factory("Conservative Analyst"), ) monkeypatch.setattr( "tradingagents.graph.setup.create_portfolio_manager", make_factory("Portfolio Manager"), ) def test_role_specific_llm_config_overrides_actual_graph_wiring(monkeypatch): recorded_llms = {} monkeypatch.setattr( "tradingagents.graph.trading_graph.create_llm_client", lambda provider, model, base_url=None, **kwargs: DummyClient( provider, model, base_url, **kwargs ), ) monkeypatch.setattr( "tradingagents.graph.trading_graph.FinancialSituationMemory", lambda *args, **kwargs: object(), ) _patch_graph_setup_wiring(monkeypatch, recorded_llms) config = { "llm_routing": { "default": {"provider": "openai", "model": "gpt-5-mini"}, "roles": { "portfolio_manager": { "provider": "openai", "model": "gpt-5.2", } }, } } TradingAgentsGraph( selected_analysts=["market"], config=deepcopy(config), ) assert recorded_llms["Market Analyst"]["model"] == "gpt-5-mini" assert recorded_llms["Portfolio Manager"]["model"] == "gpt-5.2" assert "News Analyst" not in recorded_llms def test_role_specific_route_inherits_default_provider_base_url_and_model(monkeypatch): recorded_llms = {} monkeypatch.setattr( "tradingagents.graph.trading_graph.create_llm_client", lambda provider, model, base_url=None, **kwargs: DummyClient( provider, model, base_url, **kwargs ), ) monkeypatch.setattr( "tradingagents.graph.trading_graph.FinancialSituationMemory", lambda *args, **kwargs: object(), ) _patch_graph_setup_wiring(monkeypatch, recorded_llms) TradingAgentsGraph( selected_analysts=["market"], config={ "llm_provider": "openai", "backend_url": "https://legacy.example/v1", "llm_routing": { "default": { "provider": "anthropic", "base_url": "https://anthropic.example/v1", "model": "claude-sonnet-4-6", }, "roles": { "portfolio_manager": { "base_url": "https://anthropic-pm.example/v1", } }, }, }, ) assert recorded_llms["Market Analyst"]["provider"] == "anthropic" assert recorded_llms["Market Analyst"]["base_url"] == "https://anthropic.example/v1" assert recorded_llms["Market Analyst"]["model"] == "claude-sonnet-4-6" assert recorded_llms["Portfolio Manager"]["provider"] == "anthropic" assert recorded_llms["Portfolio Manager"]["base_url"] == "https://anthropic-pm.example/v1" assert recorded_llms["Portfolio Manager"]["model"] == "claude-sonnet-4-6" def test_mixed_provider_route_does_not_inherit_legacy_backend_url(monkeypatch): recorded_llms = {} monkeypatch.setattr( "tradingagents.graph.trading_graph.create_llm_client", lambda provider, model, base_url=None, **kwargs: DummyClient( provider, model, base_url, **kwargs ), ) monkeypatch.setattr( "tradingagents.graph.trading_graph.FinancialSituationMemory", lambda *args, **kwargs: object(), ) _patch_graph_setup_wiring(monkeypatch, recorded_llms) TradingAgentsGraph( selected_analysts=["market"], config={ "llm_provider": "openai", "backend_url": "https://api.openai.com/v1", "llm_routing": { "default": { "provider": "anthropic", "model": "claude-sonnet-4-6", } }, }, ) assert recorded_llms["Market Analyst"]["provider"] == "anthropic" assert recorded_llms["Market Analyst"]["model"] == "claude-sonnet-4-6" assert recorded_llms["Market Analyst"]["base_url"] is None def test_unused_role_routes_do_not_instantiate_clients(monkeypatch): created_clients = [] def fake_create_llm_client(provider, model, base_url=None, **kwargs): created_clients.append((provider, model)) if provider == "bad-provider": raise AssertionError("unused role route should not be instantiated") return DummyClient(provider, model, base_url, **kwargs) monkeypatch.setattr( "tradingagents.graph.trading_graph.create_llm_client", fake_create_llm_client, ) monkeypatch.setattr( "tradingagents.graph.trading_graph.FinancialSituationMemory", lambda *args, **kwargs: object(), ) monkeypatch.setattr( "tradingagents.graph.trading_graph.GraphSetup.setup_graph", lambda self, selected_analysts: {"selected_analysts": selected_analysts}, ) TradingAgentsGraph( selected_analysts=["market"], config={ "llm_routing": { "default": {"provider": "openai", "model": "gpt-5-mini"}, "roles": { "news": { "provider": "bad-provider", "model": "unused-model", } }, } }, ) assert ("bad-provider", "unused-model") not in created_clients def test_provider_normalization_avoids_duplicate_legacy_client_creation(monkeypatch): created_clients = [] def fake_create_llm_client(provider, model, base_url=None, **kwargs): created_clients.append((provider, model)) return DummyClient(provider, model, base_url, **kwargs) monkeypatch.setattr( "tradingagents.graph.trading_graph.create_llm_client", fake_create_llm_client, ) monkeypatch.setattr( "tradingagents.graph.trading_graph.FinancialSituationMemory", lambda *args, **kwargs: object(), ) monkeypatch.setattr( "tradingagents.graph.trading_graph.GraphSetup.setup_graph", lambda self, selected_analysts: {"selected_analysts": selected_analysts}, ) TradingAgentsGraph( selected_analysts=["market"], config={ "llm_provider": "OpenAI", "quick_think_llm": "gpt-5-mini", "llm_routing": { "roles": { "market": {"provider": "openai", "model": "gpt-5-mini"}, }, }, }, ) assert created_clients.count(("openai", "gpt-5-mini")) == 1 def test_anthropic_client_passes_base_url_to_langchain(monkeypatch): captured_kwargs = {} class FakeChatAnthropic: def __init__(self, **kwargs): captured_kwargs.update(kwargs) monkeypatch.setattr( "tradingagents.llm_clients.anthropic_client.NormalizedChatAnthropic", FakeChatAnthropic, ) client = AnthropicClient( model="claude-sonnet-4-6", base_url="https://anthropic.example/v1", ) client.get_llm() assert captured_kwargs["anthropic_api_url"] == "https://anthropic.example/v1" def test_google_client_passes_base_url_to_langchain(monkeypatch): captured_kwargs = {} class FakeChatGoogleGenerativeAI: def __init__(self, **kwargs): captured_kwargs.update(kwargs) monkeypatch.setattr( "tradingagents.llm_clients.google_client.NormalizedChatGoogleGenerativeAI", FakeChatGoogleGenerativeAI, ) client = GoogleClient( model="gemini-2.5-pro", base_url="https://google.example/v1beta", ) client.get_llm() assert captured_kwargs["base_url"] == "https://google.example/v1beta" def test_default_config_copy_does_not_share_mutable_llm_routing_state(): config = default_config.DEFAULT_CONFIG.copy() assert config["llm_routing"] is None config["llm_routing"] = { "default": {}, "roles": {"portfolio_manager": {"model": "gpt-5.2"}}, } assert default_config.DEFAULT_CONFIG["llm_routing"] is None def test_dataflow_config_returns_isolated_nested_routing(monkeypatch): monkeypatch.setattr(dataflow_config, "_config", None) dataflow_config.initialize_config() config = dataflow_config.get_config() config["llm_routing"]["roles"]["portfolio_manager"] = { "provider": "openai", "model": "gpt-5.2", } assert dataflow_config.get_config()["llm_routing"]["roles"] == {} assert default_config.DEFAULT_CONFIG["llm_routing"] is None def test_log_state_writes_json_snapshot(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) graph = TradingAgentsGraph.__new__(TradingAgentsGraph) graph.ticker = "AAPL" graph.log_states_dict = {} final_state = { "company_of_interest": "Apple", "trade_date": "2026-03-24", "market_report": "market", "sentiment_report": "sentiment", "news_report": "news", "fundamentals_report": "fundamentals", "investment_debate_state": { "bull_history": "bull", "bear_history": "bear", "history": "debate history", "current_response": "current", "judge_decision": "judge", }, "trader_investment_plan": "trader plan", "risk_debate_state": { "aggressive_history": "agg", "conservative_history": "cons", "neutral_history": "neutral", "history": "risk history", "judge_decision": "risk judge", }, "investment_plan": "investment plan", "final_trade_decision": "buy", } graph._log_state("2026-03-24", final_state) output_path = ( tmp_path / "eval_results" / "AAPL" / "TradingAgentsStrategy_logs" / "full_states_log_2026-03-24.json" ) assert output_path.exists() assert ( json.loads(output_path.read_text())["2026-03-24"]["company_of_interest"] == "Apple" )