diff --git a/tests/test_model_validation.py b/tests/test_model_validation.py index dc46704c2..4f6ded8fa 100644 --- a/tests/test_model_validation.py +++ b/tests/test_model_validation.py @@ -64,3 +64,37 @@ def test_legacy_ids_stay_valid_without_being_offered(): for model in ids: assert validate_model(provider, model), model assert model not in offered, f"{model} is legacy but still in the picker" + + +@pytest.mark.unit +def test_an_explicit_alias_of_a_listed_model_is_known(): + """gpt-5.6 is served under its own name and as gpt-5.6-sol; naming the + explicit one should not warn that the model is unknown.""" + from tradingagents.llm_clients.validators import validate_model + + assert validate_model("openai", "gpt-5.6-sol") + + +@pytest.mark.unit +@pytest.mark.parametrize("provider", ["openai", "anthropic", "google", "xai"]) +@pytest.mark.parametrize("mode", ["quick", "deep"]) +def test_every_provider_lets_you_name_your_own_model(provider, mode): + """The docs tell users to name any model their provider serves; the picker + has to offer that too, or a new model is unreachable until we ship a list.""" + from tradingagents.llm_clients.model_catalog import get_model_options + + assert "custom" in [value for _, value in get_model_options(provider, mode)] + + +@pytest.mark.unit +@pytest.mark.parametrize("provider, model", [ + ("xai", "grok-4.20-0309-reasoning"), + ("deepseek", "deepseek-v4-flash"), + ("qwen", "qwen3.7-max"), +]) +def test_a_retired_model_id_still_runs_without_a_warning(provider, model): + """A config written against an earlier release keeps working: the provider + still serves these, they are just no longer offered in the picker.""" + from tradingagents.llm_clients.validators import validate_model + + assert validate_model(provider, model) diff --git a/tradingagents/llm_clients/model_catalog.py b/tradingagents/llm_clients/model_catalog.py index 219065ed5..e60cc3476 100644 --- a/tradingagents/llm_clients/model_catalog.py +++ b/tradingagents/llm_clients/model_catalog.py @@ -45,14 +45,12 @@ _GLM_MODELS: dict[str, list[ModelOption]] = { # "Custom model ID". _QWEN_MODELS: dict[str, list[ModelOption]] = { "quick": [ - ("Qwen 3.7 Plus - Latest, balanced speed/cost", "qwen3.7-plus"), - ("Qwen 3.6 Plus - Previous-gen balanced", "qwen3.6-plus"), + ("Qwen 3.8 Flash - Latest fast model, 1M ctx", "qwen3.8-flash"), ("Custom model ID", "custom"), ], "deep": [ - ("Qwen 3.7 Max - Latest flagship, most intelligent, 1M ctx", "qwen3.7-max"), - ("Qwen 3.6 Max - Previous-gen flagship", "qwen3.6-max"), - ("Qwen 3.7 Plus - Balanced alternative", "qwen3.7-plus"), + ("Qwen 3.8 Max - Latest flagship", "qwen3.8-max"), + ("Qwen 3.8 Flash - Fast alternative, 1M ctx", "qwen3.8-flash"), ("Custom model ID", "custom"), ], } @@ -101,23 +99,27 @@ MODEL_OPTIONS: ProviderModeOptions = { ("GPT-5.6 Luna - Fast, cost-efficient frontier", "gpt-5.6-luna"), ("GPT-5.6 Terra - Balances intelligence and cost", "gpt-5.6-terra"), ("GPT-5.4 Mini - Fast, strong coding and tool use", "gpt-5.4-mini"), + ("Custom model ID", "custom"), ], "deep": [ ("GPT-6 Astra - Latest frontier reasoning", "gpt-6-astra"), ("GPT-5.6 - Frontier reasoning (Sol)", "gpt-5.6"), ("GPT-5.6 Terra - Balances intelligence and cost", "gpt-5.6-terra"), ("GPT-5.5 - Previous-gen frontier, 1M context", "gpt-5.5"), + ("Custom model ID", "custom"), ], }, "anthropic": { "quick": [ ("Claude Sonnet 5 - Best speed and intelligence balance", "claude-sonnet-5"), ("Claude Haiku 4.5 - Fastest with near-frontier intelligence", "claude-haiku-4-5"), + ("Custom model ID", "custom"), ], "deep": [ ("Claude Opus 5 - Frontier agentic and enterprise work", "claude-opus-5"), ("Claude Fable 5.1 - Most capable, demanding long-horizon reasoning", "claude-fable-5-1"), ("Claude Sonnet 5 - Near-frontier intelligence at Sonnet cost", "claude-sonnet-5"), + ("Custom model ID", "custom"), ], }, "google": { @@ -125,23 +127,26 @@ MODEL_OPTIONS: ProviderModeOptions = { ("Gemini 3.8 Flash - Most capable Flash", "gemini-3.8-flash"), ("Gemini 3.5 Flash Lite - Fast and cost-efficient", "gemini-3.5-flash-lite"), ("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite"), + ("Custom model ID", "custom"), ], "deep": [ ("Gemini 3.8 Flash - Most capable Flash, 1M context", "gemini-3.8-flash"), ("Gemini 3.1 Pro - Reasoning-first, complex workflows (preview)", "gemini-3.1-pro-preview"), ("Gemini 3.5 Flash - Previous Flash, strong agentic + coding", "gemini-3.5-flash"), + ("Custom model ID", "custom"), ], }, "xai": { "quick": [ - ("Grok 4.3 - Latest flagship, fast with built-in reasoning", "grok-4.3"), - ("Grok 4.20 (Non-Reasoning) - Speed-optimized", "grok-4.20-0309-non-reasoning"), + ("Grok 4.6 - Latest flagship, fastest, 500K ctx", "grok-4.6"), ("Grok Build 0.1 - Coding-specialized, 256K ctx", "grok-build-0.1"), + ("Custom model ID", "custom"), ], "deep": [ - ("Grok 4.3 - Latest flagship, built-in reasoning, 1M ctx", "grok-4.3"), - ("Grok 4.20 (Reasoning) - Previous-gen reasoning", "grok-4.20-0309-reasoning"), - ("Grok 4.20 Multi-Agent - Multi-agent reasoning", "grok-4.20-multi-agent-0309"), + ("Grok 4.6 - Latest flagship, 500K ctx", "grok-4.6"), + ("Grok 4.5 - Previous flagship, coding and agentic", "grok-4.5"), + ("Grok 4.3 - Older generation, 1M ctx", "grok-4.3"), + ("Custom model ID", "custom"), ], }, # DeepSeek: the deepseek-chat / deepseek-reasoner aliases are deprecated @@ -150,12 +155,12 @@ MODEL_OPTIONS: ProviderModeOptions = { # handles the reasoning_content round-trip). "deepseek": { "quick": [ - ("DeepSeek V4 Flash - Latest fast model, thinking + non-thinking", "deepseek-v4-flash"), + ("DeepSeek Flash - V4.1 Flash, fast, 1M ctx", "deepseek-flash"), ("Custom model ID", "custom"), ], "deep": [ - ("DeepSeek V4 Pro - Latest flagship", "deepseek-v4-pro"), - ("DeepSeek V4 Flash - Fast, supports thinking", "deepseek-v4-flash"), + ("DeepSeek V4 Pro - Flagship", "deepseek-v4-pro"), + ("DeepSeek Flash - V4.1 Flash, fast, 1M ctx", "deepseek-flash"), ("Custom model ID", "custom"), ], }, @@ -201,7 +206,17 @@ MODEL_OPTIONS: ProviderModeOptions = { # changing) models — offer "Custom model ID" rather than a list that goes # stale. The endpoint + key are wired by the provider; the user picks the # model their account has access to. - "mistral": _CUSTOM_ONLY, + "mistral": { + "quick": [ + ("Mistral Small 4 - Fast, 262K ctx", "mistral-small-2603"), + ("Custom model ID", "custom"), + ], + "deep": [ + ("Mistral Medium 3.5 - 262K ctx", "mistral-medium-2604"), + ("Mistral Small 4 - Fast, 262K ctx", "mistral-small-2603"), + ("Custom model ID", "custom"), + ], + }, "groq": _CUSTOM_ONLY, "nvidia": _CUSTOM_ONLY, # Bedrock model IDs / cross-region inference profile IDs are user-specified. @@ -214,11 +229,15 @@ def get_model_options(provider: str, mode: str) -> list[ModelOption]: return MODEL_OPTIONS[provider.lower()][mode] -# Still served by the provider but no longer offered in the picker. Known to -# validation so existing configs that name them run without an unknown-model -# warning. +# Served by the provider but not offered in the picker: models retired from the +# menu, and the explicit ID of a model listed under a shorter name. Known to +# validation so a config naming one runs without an unknown-model warning. LEGACY_MODELS: dict[str, list[str]] = { - "openai": ["gpt-5.4"], + "openai": ["gpt-5.4", "gpt-5.6-sol"], + "xai": ["grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", + "grok-4.20-multi-agent-0309"], + "deepseek": ["deepseek-v4-flash"], + "qwen": ["qwen3.7-max", "qwen3.7-plus", "qwen3.6-max", "qwen3.6-plus"], "anthropic": ["claude-fable-5", "claude-opus-4-8", "claude-opus-4-7"], }