refactor(model_info): scope Gemini baseline to first-party chat ids

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-09-15 23:16:32 +00:00
parent 082f02bd73
commit 726430c0e7
4 changed files with 62 additions and 63 deletions

View file

@ -58151,17 +58151,20 @@
}
},
{
"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 tool calling and multimodal input; 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 token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number.",
"name": "gemini-chat-baseline",
"pattern": "^(?:gemini|vertex_ai)/gemini-(?!.*(?:-tts|-image|-live|-audio|-embedding|-computer-use|-robotics|-transcribe|-translate))(?:2\\.[5-9]|[3-9](?:\\.\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?![a-z])",
"description": "A first-party Gemini text-chat id at 2.5 or higher, as gemini/<id> or vertex_ai/<id> (get_model_info tries the provider-prefixed name first, so a bare id under those providers matches too): gemini-<major>[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. Scoped to Google's own namespaces because resellers wrap the same models differently (one exposes them as mode responses without reasoning), so nothing below is certain there. The lookahead excludes the tts, image, live, audio, embedding, computer-use, robotics, transcribe and translate lines, which are different modes with different capabilities. 2.5 is the floor: every first-party text-chat Gemini from 2.5 onward in this map carries all of these fields, 1.5 and 2.0 do not and are fully mapped. Carries no token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number.",
"model_info": {
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_system_messages": true,
"supports_vision": true,
"supports_response_schema": true,
"supports_reasoning": true
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_web_search": true
}
}
]

View file

@ -58151,17 +58151,20 @@
}
},
{
"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 tool calling and multimodal input; 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 token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number.",
"name": "gemini-chat-baseline",
"pattern": "^(?:gemini|vertex_ai)/gemini-(?!.*(?:-tts|-image|-live|-audio|-embedding|-computer-use|-robotics|-transcribe|-translate))(?:2\\.[5-9]|[3-9](?:\\.\\d{1,2})?)-(?:pro|flash)(?:-lite)?(?![a-z])",
"description": "A first-party Gemini text-chat id at 2.5 or higher, as gemini/<id> or vertex_ai/<id> (get_model_info tries the provider-prefixed name first, so a bare id under those providers matches too): gemini-<major>[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. Scoped to Google's own namespaces because resellers wrap the same models differently (one exposes them as mode responses without reasoning), so nothing below is certain there. The lookahead excludes the tts, image, live, audio, embedding, computer-use, robotics, transcribe and translate lines, which are different modes with different capabilities. 2.5 is the floor: every first-party text-chat Gemini from 2.5 onward in this map carries all of these fields, 1.5 and 2.0 do not and are fully mapped. Carries no token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number.",
"model_info": {
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_system_messages": true,
"supports_vision": true,
"supports_response_schema": true,
"supports_reasoning": true
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_web_search": true
}
}
]

View file

@ -453,45 +453,64 @@ def shipped_cost_map(monkeypatch):
"model,provider",
[
("gemini-4-pro", "gemini"),
("gemini-3.9-flash", "vertex_ai"),
("databricks-gemini-4-1-pro", "databricks"),
("gemini/gemini-4-pro", None),
("gemini-3.9-flash-lite-preview-09-2026", "vertex_ai"),
("vertex_ai/gemini-4-pro", None),
("gemini-4-pro-preview-customtools", "gemini"),
],
)
def test_shipped_gemini_baseline_resolves_unmapped_ids_provider_neutral(shipped_cost_map, model, provider):
def test_shipped_gemini_chat_baseline_resolves_first_party_unmapped_ids(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["litellm_provider"] == (provider or model.split("/")[0])
assert info["mode"] == "chat"
assert not info.get("max_input_tokens")
assert info["supports_reasoning"] is True
assert info["supports_function_calling"] is True
assert info["supports_tool_choice"] is True
assert info["supports_response_schema"] is True
assert info["supports_system_messages"] is True
assert info["supports_vision"] is True
assert info["supports_response_schema"] is True
assert info["supports_pdf_input"] is True
assert info["supports_prompt_caching"] is True
assert info["supports_web_search"] 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):
def test_shipped_gemini_chat_baseline_skips_reseller_namespaces(shipped_cost_map):
for model, provider in (
("google/gemini-4-pro", "perplexity"),
("google/gemini-4-pro", "openrouter"),
("databricks-gemini-4-1-pro", "databricks"),
):
with pytest.raises(Exception, match="isn't mapped yet"):
litellm.get_model_info(model, custom_llm_provider=provider)
assert match_capability_generalizations("google/gemini-4-pro") is None
def test_shipped_gemini_chat_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",
"gemini/gemini-4-flash-image",
"gemini/gemini-3.9-flash-preview-tts",
"gemini/gemini-4-flash-live-preview",
"gemini/gemini-4-flash-native-audio",
"gemini/gemini-embedding-4",
"gemini/gemini-2.5-computer-use-preview-12-2026",
"gemini/gemini-2.0-flash-new",
"gemini/gemini-1.5-pro-new",
"gemini/gemini-4-flashy",
"gemini/gemini-4-flash-transcribe",
"gemini/gemini-4-flash-live-translate-preview",
):
assert match_capability_generalizations(model) is None, model
def test_shipped_gemini_baseline_keeps_reasoning_effort_on_unmapped_model(shipped_cost_map):
def test_shipped_gemini_chat_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(
@ -505,7 +524,7 @@ def test_shipped_gemini_baseline_keeps_reasoning_effort_on_unmapped_model(shippe
assert optional_params["thinkingConfig"]["includeThoughts"] is True
def test_shipped_gemini_baseline_loses_to_exact_entries(shipped_cost_map):
def test_shipped_gemini_chat_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"]

View file

@ -1,7 +1,5 @@
import pytest
from litellm.litellm_core_utils.get_supported_openai_params import (
get_supported_openai_params,
)
@ -33,9 +31,7 @@ def test_base_model_label_alone_lacks_bedrock_tools():
"""The label by itself does not advertise tools; this is what made the union
necessary. Guards against the discrepancy disappearing (and the regression test
above silently passing for the wrong reason)."""
params = get_supported_openai_params(
model=BEDROCK_LABEL, custom_llm_provider="bedrock"
)
params = get_supported_openai_params(model=BEDROCK_LABEL, custom_llm_provider="bedrock")
assert params is not None
assert "tools" not in params
@ -46,14 +42,8 @@ def test_base_model_is_additive_not_replacement():
Bedrock: real id supports ``tools`` but not the label's reasoning hint; the union
must contain the real model's ``tools`` regardless of the label being a subset."""
real_only = set(
get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)
)
label_only = set(
get_supported_openai_params(model=BEDROCK_LABEL, custom_llm_provider="bedrock")
)
real_only = set(get_supported_openai_params(model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"))
label_only = set(get_supported_openai_params(model=BEDROCK_LABEL, custom_llm_provider="bedrock"))
combined = set(
get_supported_openai_params(
model=BEDROCK_REAL_MODEL,
@ -70,19 +60,15 @@ def test_base_model_is_additive_not_replacement():
def test_base_model_adds_capabilities_the_real_model_lacks():
"""Regression for #27717 (the behavior the union must preserve).
``gemini-3.1-pro`` isn't in the cost map so it advertises no reasoning support,
``gemini-exp-9999`` isn't in the cost map so it advertises no reasoning support,
but the registered ``gemini-3.1-pro-preview`` base_model does. The hint must add
``reasoning_effort``/``thinking`` without the call erroring."""
real_only = set(
get_supported_openai_params(
model="gemini-3.1-pro", custom_llm_provider="gemini"
)
)
real_only = set(get_supported_openai_params(model="gemini-exp-9999", custom_llm_provider="gemini"))
assert "reasoning_effort" not in real_only
combined = set(
get_supported_openai_params(
model="gemini-3.1-pro",
model="gemini-exp-9999",
custom_llm_provider="gemini",
base_model="gemini-3.1-pro-preview",
)
@ -93,21 +79,15 @@ def test_base_model_adds_capabilities_the_real_model_lacks():
def test_no_base_model_is_unchanged():
"""Omitting ``base_model`` must resolve purely from ``model``."""
with_none = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock", base_model=None
)
plain = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)
with_none = get_supported_openai_params(model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock", base_model=None)
plain = get_supported_openai_params(model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock")
assert with_none == plain
def test_base_model_equal_to_model_is_unchanged():
"""A ``base_model`` identical to ``model`` must not double-resolve or reorder."""
plain = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)
plain = get_supported_openai_params(model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock")
same = get_supported_openai_params(
model=BEDROCK_REAL_MODEL,
custom_llm_provider="bedrock",
@ -152,14 +132,10 @@ def test_bedrock_converse_alias_resolves_like_bedrock():
params saw no Bedrock capabilities for a Converse model invoked via the alias."""
anthropic_model = "bedrock/converse/us.anthropic.claude-sonnet-4-6"
via_alias = get_supported_openai_params(
model=anthropic_model, custom_llm_provider="bedrock_converse"
)
via_alias = get_supported_openai_params(model=anthropic_model, custom_llm_provider="bedrock_converse")
assert via_alias is not None
assert via_alias == get_supported_openai_params(
model=anthropic_model, custom_llm_provider="bedrock"
)
assert via_alias == get_supported_openai_params(model=anthropic_model, custom_llm_provider="bedrock")
assert "web_search_options" not in via_alias
assert "tools" in via_alias
@ -167,9 +143,7 @@ def test_bedrock_converse_alias_resolves_like_bedrock():
def test_bedrock_converse_alias_keeps_nova_web_search_options():
"""Nova on the ``bedrock_converse`` alias still advertises web_search_options, proving the
alias routes through the model-aware config rather than a blanket Bedrock default."""
nova_params = get_supported_openai_params(
model="amazon.nova-pro-v1:0", custom_llm_provider="bedrock_converse"
)
nova_params = get_supported_openai_params(model="amazon.nova-pro-v1:0", custom_llm_provider="bedrock_converse")
assert nova_params is not None
assert "web_search_options" in nova_params