diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 9f91cf82f41..afe2cb3deb9 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -58149,6 +58149,38 @@ "model_info": { "supports_reasoning": true } + }, + { + "name": "vertex-gemini-ids", + "pattern": "^gemini-(?:2\\.[5-9]|[3-9](?:[.-]\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?:-(?:preview|exp|latest))?(?:-\\d{2}-\\d{2,4})?$", + "description": "A bare Gemini text-chat id at 2.5 or higher, anchored to the whole name: gemini-[.minor]-(pro|flash)[-lite] with an optional preview/exp/latest tag and an optional MM-YYYY or MM-DD date. Bare Gemini ids in this map already infer vertex_ai (gemini-2.5-pro is filed under vertex_ai-language-models), so an unmapped id such as gemini-4-pro follows the same convention instead of raising 'LLM Provider NOT provided'. Anchored so a namespaced or non-chat id (gemini/..., -image, -tts) stays on its own path.", + "model_info": { + "litellm_provider": "vertex_ai" + } + }, + { + "name": "gemini-family-baseline", + "pattern": "gemini-(?!.*(?:-tts|-image|-live|-audio|-embedding|-computer-use|-robotics))(?:2\\.[5-9]|[3-9](?:[.-]\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?![a-z])", + "description": "Any Gemini text-chat id at 2.5 or higher under any provider namespace (bare, gemini/, vertex_ai/, databricks-gemini-3-1-pro, ...): gemini-[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. The lookahead excludes the tts, image, live, audio, embedding, computer-use and robotics lines, which are not text-chat models and carry different limits and modes. 2.5 is the floor because every Gemini from 2.5 onward is a thinking model with a 1M context and 64K output; 1.5 and 2.0 are not and are fully mapped. Carries the model-family facts every such Gemini shares, so an unmapped gemini-4-pro keeps reasoning params and tool calling instead of having them dropped; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number.", + "model_info": { + "mode": "chat", + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "max_tokens": 65536, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_vision": true, + "supports_pdf_input": true, + "supports_audio_input": true, + "supports_video_input": true, + "supports_tool_choice": true, + "supports_system_messages": true, + "supports_response_schema": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_web_search": true, + "supports_url_context": true + } } ] }, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 9f91cf82f41..afe2cb3deb9 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -58149,6 +58149,38 @@ "model_info": { "supports_reasoning": true } + }, + { + "name": "vertex-gemini-ids", + "pattern": "^gemini-(?:2\\.[5-9]|[3-9](?:[.-]\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?:-(?:preview|exp|latest))?(?:-\\d{2}-\\d{2,4})?$", + "description": "A bare Gemini text-chat id at 2.5 or higher, anchored to the whole name: gemini-[.minor]-(pro|flash)[-lite] with an optional preview/exp/latest tag and an optional MM-YYYY or MM-DD date. Bare Gemini ids in this map already infer vertex_ai (gemini-2.5-pro is filed under vertex_ai-language-models), so an unmapped id such as gemini-4-pro follows the same convention instead of raising 'LLM Provider NOT provided'. Anchored so a namespaced or non-chat id (gemini/..., -image, -tts) stays on its own path.", + "model_info": { + "litellm_provider": "vertex_ai" + } + }, + { + "name": "gemini-family-baseline", + "pattern": "gemini-(?!.*(?:-tts|-image|-live|-audio|-embedding|-computer-use|-robotics))(?:2\\.[5-9]|[3-9](?:[.-]\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?![a-z])", + "description": "Any Gemini text-chat id at 2.5 or higher under any provider namespace (bare, gemini/, vertex_ai/, databricks-gemini-3-1-pro, ...): gemini-[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. The lookahead excludes the tts, image, live, audio, embedding, computer-use and robotics lines, which are not text-chat models and carry different limits and modes. 2.5 is the floor because every Gemini from 2.5 onward is a thinking model with a 1M context and 64K output; 1.5 and 2.0 are not and are fully mapped. Carries the model-family facts every such Gemini shares, so an unmapped gemini-4-pro keeps reasoning params and tool calling instead of having them dropped; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number.", + "model_info": { + "mode": "chat", + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "max_tokens": 65536, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_vision": true, + "supports_pdf_input": true, + "supports_audio_input": true, + "supports_video_input": true, + "supports_tool_choice": true, + "supports_system_messages": true, + "supports_response_schema": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_web_search": true, + "supports_url_context": true + } } ] }, diff --git a/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py b/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py index 057fa228562..e91a2bb4394 100644 --- a/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py +++ b/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py @@ -135,9 +135,7 @@ def test_fill_missing_requires_per_rule_opt_in(restore_generalizations): "supports_vision": True, } - restore_generalizations( - [{"name": "base", "pattern": r"^acme-", "model_info": {"supports_reasoning": True}}] - ) + restore_generalizations([{"name": "base", "pattern": r"^acme-", "model_info": {"supports_reasoning": True}}]) assert match_fill_missing_generalizations("acme-1", "openai") is None restore_generalizations( @@ -451,6 +449,95 @@ def shipped_cost_map(monkeypatch): set_fallback_generalizations(previous_rules) +def test_shipped_bare_gemini_id_routes_to_vertex_ai(shipped_cost_map): + for model in ( + "gemini-3.9-flash", + "gemini-4-pro", + "gemini-4-pro-preview", + "gemini-3.9-flash-lite-preview-09-2026", + ): + assert model not in litellm.model_cost + assert litellm.get_llm_provider(model=model)[1] == "vertex_ai" + + assert litellm.get_llm_provider(model="gemini-2.5-pro")[1] == "vertex_ai" + + +def test_shipped_gemini_routing_rule_is_anchored(shipped_cost_map): + for model in ( + "gemini-4-flash-image", + "gemini-3.9-flash-preview-tts", + "gemini-2.0-flash-new", + "gemini-1.5-flash-new", + "gemini-4-flashy", + "gemini/gemini-4-pro", + ): + assert match_routing_generalization(model) is None, model + + +@pytest.mark.parametrize( + "model,provider", + [ + ("gemini-4-pro", "gemini"), + ("gemini-3.9-flash", "vertex_ai"), + ("databricks-gemini-4-1-pro", "databricks"), + ("gemini-4-pro-preview-customtools", "gemini"), + ], +) +def test_shipped_gemini_baseline_resolves_unmapped_ids_provider_neutral(shipped_cost_map, model, provider): + assert model not in litellm.model_cost + if provider == "gemini": + assert f"gemini/{model}" not in litellm.model_cost + + info = litellm.get_model_info(model, custom_llm_provider=provider) + assert info["litellm_provider"] == provider + assert info["mode"] == "chat" + assert info["max_input_tokens"] == 1048576 + assert info["max_tokens"] == 65536 + assert info["supports_reasoning"] is True + assert info["supports_function_calling"] is True + assert info["supports_vision"] is True + assert not info.get("input_cost_per_token") + assert not info.get("output_cost_per_token") + + +def test_shipped_gemini_baseline_skips_non_chat_and_pre_2_5_ids(shipped_cost_map): + for model in ( + "gemini-4-flash-image", + "gemini-3.9-flash-preview-tts", + "gemini-4-flash-live-preview", + "gemini-4-flash-native-audio", + "gemini-embedding-4", + "gemini-2.5-computer-use-preview-12-2026", + "gemini-2.0-flash-new", + "gemini-1.5-pro-new", + "gemini-4-flashy", + ): + assert match_capability_generalizations(model) is None, model + + +def test_shipped_gemini_baseline_keeps_reasoning_effort_on_unmapped_model(shipped_cost_map): + assert litellm.supports_reasoning(model="gemini-4-pro", custom_llm_provider="gemini") is True + + optional_params = litellm.utils.get_optional_params( + model="gemini-4-pro", + custom_llm_provider="gemini", + reasoning_effort="medium", + drop_params=False, + ) + assert isinstance(optional_params, dict) + assert optional_params["thinkingConfig"]["thinkingBudget"] > 0 + assert optional_params["thinkingConfig"]["includeThoughts"] is True + + +def test_shipped_gemini_baseline_loses_to_exact_entries(shipped_cost_map): + model = "gemini-2.5-flash-lite" + info = litellm.get_model_info(model, custom_llm_provider="gemini") + entry = litellm.model_cost["gemini/gemini-2.5-flash-lite"] + assert info["max_tokens"] == entry["max_tokens"] + assert info["input_cost_per_token"] == entry["input_cost_per_token"] + assert entry["input_cost_per_token"] > 0 + + def test_shipped_bare_claude_id_routes_to_anthropic(shipped_cost_map): _, provider, _, _ = litellm.get_llm_provider(model="claude-haiku-4-6") assert provider == "anthropic"