fix(gemini): simplify model version check (#42465)

This commit is contained in:
Philipp Schmid 2026-09-22 18:53:22 +02:00 • committed by GitHub
parent c835a1a982
commit 7056151b91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 28 deletions

View file

@ -102,7 +102,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig):
"include_server_side_tool_invocations",
"service_tier",
]
if supports_reasoning(model, custom_llm_provider="gemini"):
if supports_reasoning(model, custom_llm_provider="gemini") or self._is_gemini_3_or_newer(model):
supported_params.append("reasoning_effort")
supported_params.append("thinking")
if self.is_model_gemini_audio_model(model):

View file

@ -2,6 +2,7 @@
## httpx client for vertex ai calls
## Initial implementation - covers gemini + image gen calls
import json
import re
import time
from collections.abc import Callable, Mapping, Sequence
from copy import deepcopy
@ -283,20 +284,15 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
@staticmethod
def _is_gemini_3_or_newer(model: str) -> bool:
"""
Check if the model is Gemini 3 Pro or newer.
Gemini 3 models include:
- gemini-3-pro-preview
- gemini-3-flash
- gemini-3-flash-preview (Gemini 3 Flash)
- 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
Check if the model is Gemini 3 or newer.
"""
# Check for Gemini 3 models
if "gemini-3" in model:
return True
return False
model_name = model.split("/")[-1].lower()
if not model_name:
return False
# Pre-Gemini 3 models: gemini-1.x, gemini-2.x, gemini-pro, gemini-flash, gemini-exp
if re.match(r"^gemini-(?:[12](?:\.\d+)?|exp|(?:pro|flash)(?!-(?:lite-)?latest$))(?:-|$)", model_name):
return False
return True
@staticmethod
def _forward_gemini_function_call_id(model: str) -> bool:
@ -347,9 +343,10 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
if self._supports_penalty_parameters(model):
supported_params.extend(["frequency_penalty", "presence_penalty"])
if supports_reasoning(model):
if supports_reasoning(model) or self._is_gemini_3_or_newer(model):
supported_params.append("reasoning_effort")
supported_params.append("thinking")
return supported_params
def map_tool_choice_values(self, model: str, tool_choice: str | dict) -> ToolConfig | None:
@ -871,8 +868,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
@staticmethod
def _supports_minimal_thinking_level(model: str) -> bool:
lowered: Final = model.lower()
is_gemini3flash: Final = "gemini-3" in lowered and "flash" in lowered
return is_gemini3flash and not is_explicitly_disabled_factory(
is_gemini3_or_newer_flash: Final = VertexGeminiConfig._is_gemini_3_or_newer(model) and "flash" in lowered
return is_gemini3_or_newer_flash and not is_explicitly_disabled_factory(
model=model, custom_llm_provider=None, key="supports_minimal_reasoning_effort"
)
@ -890,9 +887,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
Returns:
GeminiThinkingConfig with thinkingLevel and includeThoughts
"""
is_gemini3flash: Final = model and ("flash" in model.lower() and "gemini-3" in model.lower())
supports_minimal: Final = bool(model) and VertexGeminiConfig._supports_minimal_thinking_level(model)
is_gemini31pro: Final = model and ("gemini-3.1-pro-preview" in model.lower())
if reasoning_effort == "minimal":
if supports_minimal:
return {"thinkingLevel": "minimal", "includeThoughts": True}
@ -901,10 +896,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
elif reasoning_effort == "low":
return {"thinkingLevel": "low", "includeThoughts": True}
elif reasoning_effort == "medium":
if is_gemini31pro or is_gemini3flash:
return {"thinkingLevel": "medium", "includeThoughts": True}
else:
return {"thinkingLevel": "high", "includeThoughts": True}
return {"thinkingLevel": "medium", "includeThoughts": True}
elif reasoning_effort == "high":
return {"thinkingLevel": "high", "includeThoughts": True}
elif reasoning_effort in ("disable", "none"):

View file

@ -517,7 +517,7 @@ def test_shipped_gemini_chat_baseline_keeps_reasoning_effort_on_unmapped_model(s
drop_params=False,
)
assert isinstance(optional_params, dict)
assert optional_params["thinkingConfig"]["thinkingBudget"] > 0
assert optional_params["thinkingConfig"]["thinkingLevel"] == "medium"
assert optional_params["thinkingConfig"]["includeThoughts"] is True

View file

@ -2435,10 +2435,16 @@ def test_is_gemini_3_or_newer():
VertexGeminiConfig,
)
# Gemini 3 models
# Gemini 3+ models
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-3-pro-preview") == True
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-3-flash") == True
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-3-pro") == True
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-3.1-pro-preview") == True
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-test-id-bla") == True
assert VertexGeminiConfig._is_gemini_3_or_newer("test-id-bla") == True
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("vertex_ai/gemini-3-pro-preview")
== True
@ -2453,11 +2459,53 @@ def test_is_gemini_3_or_newer():
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-2.0-flash") == False
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-1.5-pro") == False
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-pro") == False
assert VertexGeminiConfig._is_gemini_3_or_newer("gemini-flash") == False
# Edge cases
assert VertexGeminiConfig._is_gemini_3_or_newer("") == False
@pytest.mark.parametrize(
"model",
[
"gemini-3.1-pro-preview",
"gemini-3-flash",
"gemini-test-id-bla",
"test-id-bla",
],
)
def test_gemini_3_reasoning_effort_maps_to_thinking_level(model: str):
"""Test that reasoning_effort maps to thinkingLevel and default temperature=1.0"""
from litellm.llms.gemini.chat.transformation import GoogleAIStudioGeminiConfig
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)
vertex_cfg = VertexGeminiConfig()
studio_cfg = GoogleAIStudioGeminiConfig()
for cfg in (vertex_cfg, studio_cfg):
supported = cfg.get_supported_openai_params(model)
assert "reasoning_effort" in supported
assert "thinking" in supported
for effort in ("low", "medium", "high"):
mapped = vertex_cfg.map_openai_params(
non_default_params={"reasoning_effort": effort},
optional_params={},
model=model,
drop_params=False,
)
assert mapped["thinkingConfig"] == {
"thinkingLevel": effort,
"includeThoughts": True,
}
assert mapped["temperature"] == 1.0
assert "thinkingBudget" not in mapped["thinkingConfig"]
def _tool_call_messages(tool_call_id: str):
return [
{"role": "user", "content": "hi"},
@ -2643,7 +2691,7 @@ def test_reasoning_effort_maps_to_thinking_level_gemini_3():
assert result["thinkingConfig"]["thinkingLevel"] == "low"
assert result["thinkingConfig"]["includeThoughts"] is True
# Test medium -> high + includeThoughts=True (medium not available yet)
# Test medium -> medium + includeThoughts=True
optional_params = {}
non_default_params = {"reasoning_effort": "medium"}
result = v.map_openai_params(
@ -2652,7 +2700,7 @@ def test_reasoning_effort_maps_to_thinking_level_gemini_3():
model=model,
drop_params=False,
)
assert result["thinkingConfig"]["thinkingLevel"] == "high"
assert result["thinkingConfig"]["thinkingLevel"] == "medium"
assert result["thinkingConfig"]["includeThoughts"] is True
# Test high -> high + includeThoughts=True
@ -2853,7 +2901,7 @@ def test_reasoning_effort_dict_format_gemini_3():
model=model,
drop_params=False,
)
assert result["thinkingConfig"]["thinkingLevel"] == "high"
assert result["thinkingConfig"]["thinkingLevel"] == "medium"
assert result["thinkingConfig"]["includeThoughts"] is True
# Test dict format without effort key - no thinkingConfig should be set