diff --git a/.env.example b/.env.example deleted file mode 100644 index 1328b838..00000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# LLM Providers (set the one you use) -OPENAI_API_KEY= -GOOGLE_API_KEY= -ANTHROPIC_API_KEY= -XAI_API_KEY= -OPENROUTER_API_KEY= diff --git a/cli/utils.py b/cli/utils.py index 18abc3a7..294a8ba6 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -1,12 +1,28 @@ -import questionary +import json +from pathlib import Path from typing import List, Optional, Tuple, Dict +import questionary from rich.console import Console from cli.models import AnalystType console = Console() +CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.json" +with CONFIG_PATH.open("r", encoding="utf-8") as config_file: + CONFIG = json.load(config_file) + +BASE_URLS = [(display, url) for display, url in CONFIG["BASE_URLS"]] +DEEP_AGENT_OPTIONS = { + provider: [(display, value) for display, value in options] + for provider, options in CONFIG["DEEP_AGENT_OPTIONS"].items() +} +SHALLOW_AGENT_OPTIONS = { + provider: [(display, value) for display, value in options] + for provider, options in CONFIG["SHALLOW_AGENT_OPTIONS"].items() +} + TICKER_INPUT_EXAMPLES = "Examples: SPY, CNC.TO, 7203.T, 0700.HK" ANALYST_ORDER = [ @@ -136,43 +152,6 @@ def select_research_depth() -> int: def select_shallow_thinking_agent(provider) -> str: """Select shallow thinking llm engine using an interactive selection.""" - # Define shallow thinking llm engine options with their corresponding model names - # Ordering: medium → light → heavy (balanced first for quick tasks) - # Within same tier, newer models first - SHALLOW_AGENT_OPTIONS = { - "openai": [ - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), - ("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"), - ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), - ("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"), - ], - "anthropic": [ - ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), - ("Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"), - ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), - ], - "google": [ - ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), - ("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"), - ("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"), - ], - "xai": [ - ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), - ("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"), - ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), - ], - "openrouter": [ - ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), - ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), - ], - "ollama": [ - ("Qwen3:latest (8B, local)", "qwen3:latest"), - ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), - ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), - ], - } - choice = questionary.select( "Select Your [Quick-Thinking LLM Engine]:", choices=[ @@ -201,45 +180,6 @@ def select_shallow_thinking_agent(provider) -> str: def select_deep_thinking_agent(provider) -> str: """Select deep thinking llm engine using an interactive selection.""" - # Define deep thinking llm engine options with their corresponding model names - # Ordering: heavy → medium → light (most capable first for deep tasks) - # Within same tier, newer models first - DEEP_AGENT_OPTIONS = { - "openai": [ - ("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"), - ("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"), - ("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"), - ("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"), - ], - "anthropic": [ - ("Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"), - ("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"), - ("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"), - ("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"), - ], - "google": [ - ("Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"), - ("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"), - ("Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"), - ("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"), - ], - "xai": [ - ("Grok 4 - Flagship model", "grok-4-0709"), - ("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"), - ("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"), - ("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"), - ], - "openrouter": [ - ("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"), - ("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"), - ], - "ollama": [ - ("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"), - ("GPT-OSS:latest (20B, local)", "gpt-oss:latest"), - ("Qwen3:latest (8B, local)", "qwen3:latest"), - ], - } - choice = questionary.select( "Select Your [Deep-Thinking LLM Engine]:", choices=[ @@ -264,16 +204,6 @@ def select_deep_thinking_agent(provider) -> str: def select_llm_provider() -> tuple[str, str]: """Select the OpenAI api url using interactive selection.""" - # Define OpenAI api options with their corresponding endpoints - BASE_URLS = [ - ("OpenAI", "https://api.openai.com/v1"), - ("Google", "https://generativelanguage.googleapis.com/v1"), - ("Anthropic", "https://api.anthropic.com/"), - ("xAI", "https://api.x.ai/v1"), - ("Openrouter", "https://openrouter.ai/api/v1"), - ("Ollama", "http://localhost:11434/v1"), - ] - choice = questionary.select( "Select your LLM Provider:", choices=[ diff --git a/config.json b/config.json new file mode 100644 index 00000000..518223ea --- /dev/null +++ b/config.json @@ -0,0 +1,84 @@ +{ + "BASE_URLS": [ + ["OpenAI", "https://api.openai.com/v1"], + ["Google", "https://generativelanguage.googleapis.com/v1"], + ["Anthropic", "https://api.anthropic.com/"], + ["xAI", "https://api.x.ai/v1"], + ["Openrouter", "https://openrouter.ai/api/v1"], + ["Ollama", "http://localhost:11434/v1"], + ["LMStudio", "http://localhost:1234/v1"] + ], + "SHALLOW_AGENT_OPTIONS": { + "openai": [ + ["GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"], + ["GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"], + ["GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"], + ["GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"] + ], + "anthropic": [ + ["Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"], + ["Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"], + ["Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"] + ], + "google": [ + ["Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"], + ["Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"], + ["Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"], + ["Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"] + ], + "xai": [ + ["Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"], + ["Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"], + ["Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"] + ], + "openrouter": [ + ["NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"], + ["Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"] + ], + "ollama": [ + ["Qwen3:latest (8B, local)", "qwen3:latest"], + ["GPT-OSS:latest (20B, local)", "gpt-oss:latest"], + ["GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"] + ] + }, + "DEEP_AGENT_OPTIONS": { + "openai": [ + ["GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"], + ["GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"], + ["GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"], + ["GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"] + ], + "anthropic": [ + ["Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"], + ["Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"], + ["Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"], + ["Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"] + ], + "google": [ + ["Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"], + ["Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"], + ["Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"], + ["Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"] + ], + "xai": [ + ["Grok 4 - Flagship model", "grok-4-0709"], + ["Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"], + ["Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"], + ["Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"] + ], + "openrouter": [ + ["Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"], + ["NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"] + ], + "ollama": [ + ["GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"], + ["GPT-OSS:latest (20B, local)", "gpt-oss:latest"], + ["Qwen3:latest (8B, local)", "qwen3:latest"] + ] + }, + "DEFAULT_LLM_SETTINGS": { + "llm_provider": "openai", + "deep_think_llm": "gpt-5.2", + "quick_think_llm": "gpt-5-mini" + } +} diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 898e1e1e..c5a9e348 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -1,4 +1,17 @@ +import json import os +from pathlib import Path + +CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.json" +with CONFIG_PATH.open("r", encoding="utf-8") as config_file: + CONFIG = json.load(config_file) + +DEFAULT_LLM_SETTINGS = CONFIG.get("DEFAULT_LLM_SETTINGS", {}) +BASE_URLS = {display.lower(): url for display, url in CONFIG.get("BASE_URLS", [])} +DEFAULT_PROVIDER = DEFAULT_LLM_SETTINGS.get("llm_provider", "openai").lower() +DEFAULT_BACKEND_URL = BASE_URLS.get( + DEFAULT_PROVIDER, BASE_URLS.get("openai", "https://api.openai.com/v1") +) DEFAULT_CONFIG = { "project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), @@ -8,10 +21,10 @@ DEFAULT_CONFIG = { "dataflows/data_cache", ), # LLM settings - "llm_provider": "openai", - "deep_think_llm": "gpt-5.2", - "quick_think_llm": "gpt-5-mini", - "backend_url": "https://api.openai.com/v1", + "llm_provider": DEFAULT_PROVIDER, + "deep_think_llm": DEFAULT_LLM_SETTINGS.get("deep_think_llm", "gpt-5.2"), + "quick_think_llm": DEFAULT_LLM_SETTINGS.get("quick_think_llm", "gpt-5-mini"), + "backend_url": DEFAULT_BACKEND_URL, # Provider-specific thinking configuration "google_thinking_level": None, # "high", "minimal", etc. "openai_reasoning_effort": None, # "medium", "high", "low" diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index fd9b4e33..0a47ec3e 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -1,6 +1,9 @@ +import json import os +from pathlib import Path from typing import Any, Optional +from dotenv import load_dotenv from langchain_openai import ChatOpenAI from .base_client import BaseLLMClient, normalize_content @@ -24,11 +27,23 @@ _PASSTHROUGH_KWARGS = ( "api_key", "callbacks", "http_client", "http_async_client", ) -# Provider base URLs and API key env vars -_PROVIDER_CONFIG = { - "xai": ("https://api.x.ai/v1", "XAI_API_KEY"), - "openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"), - "ollama": ("http://localhost:11434/v1", None), +CONFIG_PATH = Path(__file__).resolve().parents[2] / "config.json" +with CONFIG_PATH.open("r", encoding="utf-8") as config_file: + CONFIG = json.load(config_file) + +load_dotenv() + +_BASE_URLS = { + display.lower(): url for display, url in CONFIG.get("BASE_URLS", []) +} +_PROVIDER_BASE_URLS = { + "xai": _BASE_URLS.get("xai", "https://api.x.ai/v1"), + "openrouter": _BASE_URLS.get("openrouter", "https://openrouter.ai/api/v1"), + "ollama": _BASE_URLS.get("ollama", "http://localhost:11434/v1"), +} +_PROVIDER_API_KEY_ENV = { + "xai": "XAI_API_KEY", + "openrouter": "OPENROUTER_API_KEY", } @@ -56,14 +71,15 @@ class OpenAIClient(BaseLLMClient): llm_kwargs = {"model": self.model} # Provider-specific base URL and auth - if self.provider in _PROVIDER_CONFIG: - base_url, api_key_env = _PROVIDER_CONFIG[self.provider] + if self.provider in _PROVIDER_BASE_URLS: + base_url = _PROVIDER_BASE_URLS[self.provider] llm_kwargs["base_url"] = base_url + api_key_env = _PROVIDER_API_KEY_ENV.get(self.provider) if api_key_env: api_key = os.environ.get(api_key_env) if api_key: llm_kwargs["api_key"] = api_key - else: + elif self.provider == "ollama": llm_kwargs["api_key"] = "ollama" elif self.base_url: llm_kwargs["base_url"] = self.base_url