mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-16 23:41:43 +00:00
Merge pull request #41320 from BerriAI/litellm_gemini_fallback_generalizations
feat(model_info): add provider-neutral Gemini 2.5+ chat baseline fallback generalization
This commit is contained in:
commit
daa98cd6ff
4 changed files with 135 additions and 41 deletions
|
|
@ -58151,6 +58151,23 @@
|
|||
"model_info": {
|
||||
"supports_reasoning": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gemini-chat-baseline",
|
||||
"pattern": "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": "Any Gemini text-chat id at 2.5 or higher under any namespace, including bare ids, gemini/, vertex_ai/, openrouter/google/, deepinfra/google/, vercel_ai_gateway/google/, oci/google., and databricks-gemini-<major>-<minor>: gemini-<major>[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. The capability flags were verified against each of those providers' own catalogs and docs. The lookahead excludes the tts, image, live, audio, embedding, computer-use, robotics, transcribe and translate lines, which are different modes with different capabilities. Provider-specific deviations, such as Perplexity's Agent API serving these as mode responses, are carried by their exact map entries, which always win over this rule. Carries no token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number. Source check 2026-09-15: all 45 first-party 2.5+ text-chat entries in this map carry every field below, and the OpenRouter (openrouter.ai/api/v1/models), Vercel AI Gateway (ai-gateway.vercel.sh/v1/models), DeepInfra (api.deepinfra.com/models/list), OCI and Databricks model docs list reasoning, tools and image input for the same models.",
|
||||
"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_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_web_search": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -58151,6 +58151,23 @@
|
|||
"model_info": {
|
||||
"supports_reasoning": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gemini-chat-baseline",
|
||||
"pattern": "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": "Any Gemini text-chat id at 2.5 or higher under any namespace, including bare ids, gemini/, vertex_ai/, openrouter/google/, deepinfra/google/, vercel_ai_gateway/google/, oci/google., and databricks-gemini-<major>-<minor>: gemini-<major>[.minor]-(pro|flash)[-lite] with any trailing preview, date or variant tag. The capability flags were verified against each of those providers' own catalogs and docs. The lookahead excludes the tts, image, live, audio, embedding, computer-use, robotics, transcribe and translate lines, which are different modes with different capabilities. Provider-specific deviations, such as Perplexity's Agent API serving these as mode responses, are carried by their exact map entries, which always win over this rule. Carries no token limits or pricing, so those stay on the standard unmapped behavior rather than a guessed number. Source check 2026-09-15: all 45 first-party 2.5+ text-chat entries in this map carry every field below, and the OpenRouter (openrouter.ai/api/v1/models), Vercel AI Gateway (ai-gateway.vercel.sh/v1/models), DeepInfra (api.deepinfra.com/models/list), OCI and Databricks model docs list reasoning, tools and image input for the same models.",
|
||||
"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_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_web_search": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,94 @@ def shipped_cost_map(monkeypatch):
|
|||
set_fallback_generalizations(previous_rules)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider",
|
||||
[
|
||||
("gemini-4-pro", "gemini"),
|
||||
("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"),
|
||||
("google/gemini-4-pro", "openrouter"),
|
||||
("google/gemini-4-pro", "deepinfra"),
|
||||
("google/gemini-4-pro", "vercel_ai_gateway"),
|
||||
("google.gemini-4-pro", "oci"),
|
||||
("databricks-gemini-4-1-pro", "databricks"),
|
||||
],
|
||||
)
|
||||
def test_shipped_gemini_chat_baseline_resolves_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
|
||||
elif provider in {"openrouter", "deepinfra", "vercel_ai_gateway", "oci", "databricks"}:
|
||||
assert f"{provider}/{model}" not in litellm.model_cost
|
||||
|
||||
info = litellm.get_model_info(model, custom_llm_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_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_chat_baseline_loses_to_perplexity_exact_entries(shipped_cost_map):
|
||||
info = litellm.get_model_info("google/gemini-2.5-pro", custom_llm_provider="perplexity")
|
||||
entry = litellm.model_cost["perplexity/google/gemini-2.5-pro"]
|
||||
assert info["mode"] == "responses"
|
||||
assert entry["supports_reasoning"] is False
|
||||
|
||||
|
||||
def test_shipped_gemini_chat_baseline_skips_non_chat_and_pre_2_5_ids(shipped_cost_map):
|
||||
for model in (
|
||||
"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",
|
||||
"databricks-gemini-3-1-flash-image",
|
||||
"openrouter/google/gemini-2.0-flash-001",
|
||||
):
|
||||
assert match_capability_generalizations(model) is None, model
|
||||
|
||||
|
||||
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(
|
||||
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_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"]
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue