mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(gemini): treat rolling -latest aliases as Gemini 3+
`_is_gemini_3_or_newer` matched the substring "gemini-3", so Google's
rolling aliases (`gemini-flash-latest`, `gemini-flash-lite-latest` and
`gemini-pro-latest`) were not recognised even though they resolve to
Gemini 3.x. Every Gemini 3 behaviour keyed off that check was therefore
silently disabled for them, including the documented
`skip_thought_signature_validator` fallback.
The visible symptom is a multi-turn tool call failing with
`400 Function call is missing a thought_signature in functionCall parts`
whenever the assistant turn's tool call does not carry LiteLLM's
`__thought__`-encoded id, for example history replayed by a client that
uses its own tool-call ids.
Matching is by exact alias name rather than an `endswith("-latest")`
check, because versioned names such as
`gemini-2.5-flash-native-audio-latest` also end in `-latest` and are not
Gemini 3.
This commit is contained in:
parent
973329e986
commit
16b157f939
2 changed files with 92 additions and 1 deletions
|
|
@ -106,6 +106,19 @@ else:
|
|||
StreamingChoices = Any
|
||||
|
||||
|
||||
# Google's rolling aliases, which always resolve to the newest release of each
|
||||
# tier and today serve Gemini 3.x. They carry no version number, so they must be
|
||||
# listed explicitly for Gemini 3 feature detection to see them.
|
||||
# https://ai.google.dev/gemini-api/docs/models
|
||||
GEMINI_ROLLING_LATEST_ALIASES = frozenset(
|
||||
{
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class VertexAIBaseConfig:
|
||||
def get_mapped_special_auth_params(self) -> dict:
|
||||
"""
|
||||
|
|
@ -266,11 +279,21 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
|
|||
- gemini-3.1-pro-preview, gemini-3.1-flash, gemini-3.1-flash-lite-preview
|
||||
- gemini-3.5-flash
|
||||
- Any future Gemini 3.x models
|
||||
- The rolling `-latest` aliases, which Google keeps pointed at the
|
||||
newest release of each tier and which currently serve Gemini 3.x.
|
||||
|
||||
The `-latest` aliases are matched by exact name rather than by an
|
||||
`endswith("-latest")` suffix check, because versioned names such as
|
||||
`gemini-2.5-flash-native-audio-latest` also end in `-latest` and are
|
||||
not Gemini 3.
|
||||
"""
|
||||
# Check for Gemini 3 models
|
||||
if "gemini-3" in model:
|
||||
return True
|
||||
return False
|
||||
# Rolling aliases carry no version in the name, so the substring check
|
||||
# above cannot see them. Strip any provider prefix (`gemini/`,
|
||||
# `vertex_ai/`) before comparing.
|
||||
return model.split("/")[-1] in GEMINI_ROLLING_LATEST_ALIASES
|
||||
|
||||
@staticmethod
|
||||
def _forward_gemini_function_call_id(model: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -2275,6 +2275,74 @@ def test_is_gemini_3_or_newer():
|
|||
# Edge cases
|
||||
assert VertexGeminiConfig._is_gemini_3_or_newer("") == False
|
||||
|
||||
# Rolling `-latest` aliases resolve to Gemini 3.x and carry no version in
|
||||
# the name, so they must be detected explicitly.
|
||||
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-flash-latest") == True
|
||||
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-flash-lite-latest") == True
|
||||
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-pro-latest") == True
|
||||
assert (
|
||||
VertexGeminiConfig._is_gemini_3_or_newer("gemini/gemini-flash-latest") == True
|
||||
)
|
||||
assert (
|
||||
VertexGeminiConfig._is_gemini_3_or_newer("vertex_ai/gemini-pro-latest") == True
|
||||
)
|
||||
|
||||
# A versioned name that merely ends in `-latest` is not Gemini 3.
|
||||
assert (
|
||||
VertexGeminiConfig._is_gemini_3_or_newer(
|
||||
"gemini-2.5-flash-native-audio-latest"
|
||||
)
|
||||
== False
|
||||
)
|
||||
|
||||
|
||||
def test_thought_signature_fallback_for_rolling_latest_alias():
|
||||
"""
|
||||
A tool call whose thought signature did not survive the round-trip must
|
||||
still get the dummy signature Google documents, otherwise Gemini rejects
|
||||
the follow-up turn with:
|
||||
|
||||
400 Function call is missing a thought_signature in functionCall parts.
|
||||
|
||||
Regression test: `gemini-flash-latest` serves Gemini 3.x but was not
|
||||
detected as such, so the fallback was skipped and every multi-turn tool
|
||||
call that lost its signature failed.
|
||||
"""
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
convert_to_gemini_tool_call_invoke,
|
||||
)
|
||||
|
||||
message = {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_abc123", # no `__thought__` suffix -> signature lost
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_website",
|
||||
"arguments": '{"query": "Iceland"}',
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
for model in [
|
||||
"gemini-3-flash",
|
||||
"gemini-flash-latest",
|
||||
"gemini-flash-lite-latest",
|
||||
"gemini-pro-latest",
|
||||
"gemini/gemini-flash-latest",
|
||||
]:
|
||||
parts = convert_to_gemini_tool_call_invoke(message, model=model)
|
||||
assert any(
|
||||
"thoughtSignature" in part for part in parts
|
||||
), f"expected a thought signature for {model}"
|
||||
|
||||
# Pre-Gemini-3 models must not gain a signature they never needed.
|
||||
parts = convert_to_gemini_tool_call_invoke(message, model="gemini-2.5-flash")
|
||||
assert not any("thoughtSignature" in part for part in parts)
|
||||
|
||||
|
||||
def _tool_call_messages(tool_call_id: str):
|
||||
return [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue