From 5b41fc9b11311ce2694891f654cd69ec5352c953 Mon Sep 17 00:00:00 2001 From: gegemeimingzi <200456873+gegemeimingzi@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:35:49 +0800 Subject: [PATCH 1/4] fix(vertex): floor reasoning_effort to low on gemini-3.7-flash gemini-3.7-flash dropped THINKING_LEVEL_MINIMAL support on Vertex; only LOW, MEDIUM, and HIGH are accepted. reasoning_effort values minimal, none, and disable previously mapped to "minimal" via the broad gemini-3.x-flash substring match and returned HTTP 400. They now floor to "low" so they resolve to a level Vertex accepts. Fixes #37314 --- .../vertex_and_google_ai_studio_gemini.py | 34 ++++++++--- tests/llm_translation/test_gemini.py | 58 +++++++++++++++++++ 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index d298670aa7a..e63030b0690 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -272,6 +272,21 @@ 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: """ @@ -856,10 +871,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: @@ -869,20 +882,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, so we use "minimal" for the models that accept it, "low" for others. 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: @@ -953,8 +966,11 @@ 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 diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py index 0a5aebdf91b..d3eb455dafb 100644 --- a/tests/llm_translation/test_gemini.py +++ b/tests/llm_translation/test_gemini.py @@ -1806,6 +1806,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. From d246efcf41d9c748e6554bef5bd88098cbbeb670 Mon Sep 17 00:00:00 2001 From: gegemeimingzi <200456873+gegemeimingzi@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:45:00 +0800 Subject: [PATCH 2/4] style(vertex): shorten comment to fit 120-char limit --- .../llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index e63030b0690..1e98a53da83 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -889,7 +889,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): elif reasoning_effort == "high": return {"thinkingLevel": "high", "includeThoughts": True} elif reasoning_effort == "disable": - # Gemini 3 cannot fully disable thinking, so we use "minimal" for the models that accept it, "low" for others. + # Gemini 3 cannot fully disable thinking; "minimal" where accepted, "low" otherwise. if is_gemini3flash: return {"thinkingLevel": "minimal", "includeThoughts": False} else: From 398ed330f15fa3d745a2790e2e8510e9017b860d Mon Sep 17 00:00:00 2001 From: gegemeimingzi <200456873+gegemeimingzi@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:50:39 +0800 Subject: [PATCH 3/4] style(vertex): apply ruff format to the 3.7-flash thinking fix --- .../gemini/vertex_and_google_ai_studio_gemini.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 1e98a53da83..b3281cb2a38 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -281,11 +281,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): 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 - ) + 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: @@ -967,9 +963,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): # Follow provider defaults unless explicitly opted into legacy behavior. if litellm.enable_gemini_default_thinking_level_low is True: params["thinkingLevel"] = ( - "minimal" - if VertexGeminiConfig._is_gemini3_flash_supports_minimal(model) - else "low" + "minimal" if VertexGeminiConfig._is_gemini3_flash_supports_minimal(model) else "low" ) else: # Thinking disabled From 91b1be133c4fce3f3fda2ac366866c33de0b8365 Mon Sep 17 00:00:00 2001 From: gegemeimingzi <200456873+gegemeimingzi@users.noreply.github.com> Date: Wed, 19 Aug 2026 23:02:48 +0800 Subject: [PATCH 4/4] test(vertex): cover gemini-3.7-flash in thinking-param mapping --- tests/llm_translation/test_gemini.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py index d3eb455dafb..298db3e3cdb 100644 --- a/tests/llm_translation/test_gemini.py +++ b/tests/llm_translation/test_gemini.py @@ -1649,6 +1649,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