feat(model_info): add Gemini fallback generalization rules (vertex routing + 2.5+ family baseline)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-09-15 22:49:35 +00:00
parent eec954bf9a
commit 024a887521
3 changed files with 154 additions and 3 deletions

View file

@ -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-<major>[.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-<major>[.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
}
}
]
},

View file

@ -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-<major>[.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-<major>[.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
}
}
]
},

View file

@ -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"