This commit is contained in:
gegemeimingzi 2026-08-27 20:07:29 -05:00 committed by GitHub
commit 3380ca959d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 86 additions and 9 deletions

View file

@ -274,6 +274,17 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
return True
return False
@staticmethod
def _is_gemini3_flash_supports_minimal(model: str) -> bool:
"""
Whether a gemini-3.x-flash model accepts THINKING_LEVEL_MINIMAL.
gemini-3.7-flash dropped MINIMAL support: Vertex only accepts LOW, MEDIUM, and
HIGH for it, while earlier 3.x-flash models (3.0, 3.1, 3.5, 3.6) still accept it.
"""
lowered: Final = model.lower()
return "flash" in lowered and "gemini-3" in lowered and "gemini-3.7" not in lowered
@staticmethod
def _forward_gemini_function_call_id(model: str) -> bool:
"""
@ -858,10 +869,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
Returns:
GeminiThinkingConfig with thinkingLevel and includeThoughts
"""
# Check if this is gemini-3-flash which supports MINIMAL thinking level
# Covers gemini-3-flash, gemini-3-flash-preview, gemini-3.1-flash, gemini-3.1-flash-lite-preview,
# gemini-3.5-flash, and any future 3.x-flash variants.
is_gemini3flash: Final = model and ("flash" in model.lower() and "gemini-3" in model.lower())
is_gemini3flash: Final = bool(model) and VertexGeminiConfig._is_gemini3_flash_supports_minimal(model)
is_gemini3_flash_family: Final = bool(model) and "flash" in model.lower() and "gemini-3" in model.lower()
is_gemini31pro: Final = model and ("gemini-3.1-pro-preview" in model.lower())
if reasoning_effort == "minimal":
if is_gemini3flash:
@ -871,20 +880,20 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
elif reasoning_effort == "low":
return {"thinkingLevel": "low", "includeThoughts": True}
elif reasoning_effort == "medium":
if is_gemini31pro or is_gemini3flash:
if is_gemini31pro or is_gemini3_flash_family:
return {"thinkingLevel": "medium", "includeThoughts": True}
else:
return {"thinkingLevel": "high", "includeThoughts": True}
elif reasoning_effort == "high":
return {"thinkingLevel": "high", "includeThoughts": True}
elif reasoning_effort == "disable":
# Gemini 3 cannot fully disable thinking, so we use "minimal" for gemini-3-flash-preview, "low" for others
# Gemini 3 cannot fully disable thinking; "minimal" where accepted, "low" otherwise.
if is_gemini3flash:
return {"thinkingLevel": "minimal", "includeThoughts": False}
else:
return {"thinkingLevel": "low", "includeThoughts": False}
elif reasoning_effort == "none":
# For gemini-3-flash-preview, use "minimal" instead of "low"
# Same as disable: "minimal" where accepted, "low" otherwise.
if is_gemini3flash:
return {"thinkingLevel": "minimal", "includeThoughts": False}
else:
@ -955,8 +964,9 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
params["includeThoughts"] = True
# Follow provider defaults unless explicitly opted into legacy behavior.
if litellm.enable_gemini_default_thinking_level_low is True:
is_gemini3flash: Final = "gemini-3" in model.lower() and "flash" in model.lower()
params["thinkingLevel"] = "minimal" if is_gemini3flash else "low"
params["thinkingLevel"] = (
"minimal" if VertexGeminiConfig._is_gemini3_flash_supports_minimal(model) else "low"
)
else:
# Thinking disabled
params["includeThoughts"] = False

View file

@ -1635,6 +1635,15 @@ def test_anthropic_thinking_param_to_gemini_3_force_low_feature_flag():
)
assert result_pro["thinkingLevel"] == "low"
assert result_pro["includeThoughts"] is True
# gemini-3.7-flash dropped MINIMAL support, so the forced-low mapping must
# not send "minimal" to it (issue #37314).
result_37 = VertexGeminiConfig._map_thinking_param(
thinking_param=thinking_param,
model="gemini-3.7-flash",
)
assert result_37["thinkingLevel"] == "low"
assert result_37["includeThoughts"] is True
finally:
litellm.enable_gemini_default_thinking_level_low = original_force_low_flag
@ -1792,6 +1801,64 @@ def test_gemini_31_flash_lite_reasoning_effort_minimal():
), "gemini-3.1-flash-lite-preview should use thinkingLevel, not thinkingBudget"
def test_gemini_37_flash_reasoning_effort_no_minimal():
"""
gemini-3.7-flash dropped THINKING_LEVEL_MINIMAL support on Vertex; only LOW, MEDIUM,
and HIGH are accepted. reasoning_effort values that previously mapped to "minimal"
(minimal, none, disable) must resolve to "low" instead so they return 200.
Regression test for: https://github.com/BerriAI/litellm/issues/37314
"""
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)
# Values that used to map to "minimal" must now floor at "low" on 3.7.
for reasoning_effort in ("minimal", "none", "disable"):
result = VertexGeminiConfig._map_reasoning_effort_to_thinking_level(
reasoning_effort=reasoning_effort,
model="gemini-3.7-flash",
)
assert (
result["thinkingLevel"] == "low"
), f"gemini-3.7-flash {reasoning_effort} should map to 'low', got '{result['thinkingLevel']}'"
assert result["includeThoughts"] is (reasoning_effort != "none" and reasoning_effort != "disable")
# Explicit low/medium/high are unaffected.
for reasoning_effort, expected_level in (
("low", "low"),
("medium", "medium"),
("high", "high"),
):
result = VertexGeminiConfig._map_reasoning_effort_to_thinking_level(
reasoning_effort=reasoning_effort,
model="gemini-3.7-flash",
)
assert (
result["thinkingLevel"] == expected_level
), f"gemini-3.7-flash {reasoning_effort} should map to '{expected_level}', got '{result['thinkingLevel']}'"
def test_gemini_36_flash_reasoning_effort_minimal_still_supported():
"""
gemini-3.6-flash still accepts THINKING_LEVEL_MINIMAL, so the fix for 3.7 must not
regress the earlier 3.x-flash models.
"""
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
VertexGeminiConfig,
)
for model in ("gemini-3.6-flash", "gemini-3.5-flash"):
result = VertexGeminiConfig._map_reasoning_effort_to_thinking_level(
reasoning_effort="minimal",
model=model,
)
assert (
result["thinkingLevel"] == "minimal"
), f"{model} should keep thinkingLevel='minimal', got '{result['thinkingLevel']}'"
assert result["includeThoughts"] is True
def test_gemini_image_size_limit_exceeded(monkeypatch):
"""
Test that large images exceeding MAX_IMAGE_URL_DOWNLOAD_SIZE_MB are rejected.