mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(gemini): read the minimal thinking floor from the cost map and cover the /v1/messages bridge
This commit is contained in:
parent
e7454e52e6
commit
a1ad95dbbd
7 changed files with 71 additions and 20 deletions
|
|
@ -4,9 +4,9 @@ from typing import Final
|
|||
|
||||
import litellm
|
||||
from litellm.utils import (
|
||||
_is_explicitly_disabled_factory,
|
||||
_supports_factory,
|
||||
declared_value_factory,
|
||||
is_explicitly_disabled_factory,
|
||||
)
|
||||
|
||||
from .gpt_transformation import OpenAIGPTConfig
|
||||
|
|
@ -192,7 +192,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
|
||||
Use this for opt-out checks where unknown models should be allowed through.
|
||||
"""
|
||||
return _is_explicitly_disabled_factory(
|
||||
return is_explicitly_disabled_factory(
|
||||
model=cls._model_map_lookup_name(model),
|
||||
custom_llm_provider=None,
|
||||
key=f"supports_{level}_reasoning_effort",
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ from litellm.utils import (
|
|||
CustomStreamWrapper,
|
||||
ModelResponse,
|
||||
is_base64_encoded,
|
||||
is_explicitly_disabled_factory,
|
||||
supports_reasoning,
|
||||
)
|
||||
|
||||
|
|
@ -110,7 +111,6 @@ else:
|
|||
|
||||
|
||||
SUPPORTED_REASONING_EFFORTS: Final = ("minimal", "low", "medium", "high", "none", "disable")
|
||||
GEMINI_FLASH_MODELS_WITHOUT_MINIMAL_THINKING: Final = ("gemini-3.7-flash", "gemini-3.8-flash")
|
||||
|
||||
|
||||
def _unsupported_reasoning_effort(reasoning_effort: str) -> UnsupportedParamsError:
|
||||
|
|
@ -865,7 +865,9 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
|
|||
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 any(m in lowered for m in GEMINI_FLASH_MODELS_WITHOUT_MINIMAL_THINKING)
|
||||
return is_gemini3flash and not is_explicitly_disabled_factory(
|
||||
model=model, custom_llm_provider=None, key="supports_minimal_reasoning_effort"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _map_reasoning_effort_to_thinking_level(
|
||||
|
|
|
|||
|
|
@ -25159,6 +25159,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -25216,6 +25217,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27081,6 +27083,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27140,6 +27143,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27555,6 +27559,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27612,6 +27617,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
|
|||
|
|
@ -2675,7 +2675,7 @@ def declared_value_factory(model: str, custom_llm_provider: str | None, key: str
|
|||
"""Return a string value the model map declares for *key*, or ``None`` when it says nothing.
|
||||
|
||||
The string-valued sibling of :func:`_supports_factory` and
|
||||
:func:`_is_explicitly_disabled_factory`, public where those two are not because it is read
|
||||
:func:`is_explicitly_disabled_factory`, public like the latter because both are read
|
||||
from the provider configs rather than from this module, sharing their
|
||||
``get_llm_provider`` -> ``_get_model_info_helper`` chain and their unprefixed-twin
|
||||
fallback (#20885), so a provider-prefixed entry that omits the key still answers
|
||||
|
|
@ -2711,7 +2711,7 @@ def declared_value_factory(model: str, custom_llm_provider: str | None, key: str
|
|||
return None
|
||||
|
||||
|
||||
def _is_explicitly_disabled_factory(model: str, custom_llm_provider: str | None, key: str) -> bool:
|
||||
def is_explicitly_disabled_factory(model: str, custom_llm_provider: str | None, key: str) -> bool:
|
||||
"""Return True only when the model map explicitly sets *key* to ``False``.
|
||||
|
||||
This is the opt-out mirror of :func:`_supports_factory`. Where
|
||||
|
|
@ -2830,7 +2830,7 @@ def is_vision_explicitly_disabled(model: str, custom_llm_provider: str | None =
|
|||
The opt-out mirror of :func:`supports_vision`: a missing declaration reads as not
|
||||
disabled, so unknown or newly added models stay eligible for image routing.
|
||||
"""
|
||||
return _is_explicitly_disabled_factory(model, custom_llm_provider, "supports_vision")
|
||||
return is_explicitly_disabled_factory(model, custom_llm_provider, "supports_vision")
|
||||
|
||||
|
||||
def supports_vision(model: str, custom_llm_provider: str | None = None) -> bool:
|
||||
|
|
|
|||
|
|
@ -25159,6 +25159,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -25216,6 +25217,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27081,6 +27083,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27140,6 +27143,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27555,6 +27559,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
@ -27612,6 +27617,7 @@
|
|||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_minimal_reasoning_effort": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map
|
|||
from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config
|
||||
from litellm.llms.openai.openai import OpenAIConfig
|
||||
from litellm.utils import (
|
||||
_is_explicitly_disabled_factory,
|
||||
is_explicitly_disabled_factory,
|
||||
peek_reasoning_summary_aliases,
|
||||
strip_reasoning_summary_aliases_from_optional_params,
|
||||
)
|
||||
|
|
@ -524,19 +524,19 @@ def test_gpt5_minimal_explicitly_disabled_check(gpt5_config: OpenAIGPT5Config):
|
|||
|
||||
|
||||
def test_is_explicitly_disabled_factory_minimal():
|
||||
"""_is_explicitly_disabled_factory returns True only for explicit False entries.
|
||||
"""is_explicitly_disabled_factory returns True only for explicit False entries.
|
||||
|
||||
Verifies the shared helper used by _is_reasoning_effort_level_explicitly_disabled
|
||||
directly — so future changes to the helper are caught without going through the
|
||||
method wrapper.
|
||||
"""
|
||||
key = "supports_minimal_reasoning_effort"
|
||||
assert _is_explicitly_disabled_factory("gpt-5.4-mini", None, key)
|
||||
assert _is_explicitly_disabled_factory("gpt-5.4-nano", None, key)
|
||||
assert _is_explicitly_disabled_factory("openai/gpt-5.4-mini", None, key)
|
||||
assert _is_explicitly_disabled_factory("gpt-5.4", None, key)
|
||||
assert _is_explicitly_disabled_factory("gpt-5.4-pro", None, key)
|
||||
assert not _is_explicitly_disabled_factory("gpt-5.4-turbo-preview", None, key)
|
||||
assert is_explicitly_disabled_factory("gpt-5.4-mini", None, key)
|
||||
assert is_explicitly_disabled_factory("gpt-5.4-nano", None, key)
|
||||
assert is_explicitly_disabled_factory("openai/gpt-5.4-mini", None, key)
|
||||
assert is_explicitly_disabled_factory("gpt-5.4", None, key)
|
||||
assert is_explicitly_disabled_factory("gpt-5.4-pro", None, key)
|
||||
assert not is_explicitly_disabled_factory("gpt-5.4-turbo-preview", None, key)
|
||||
|
||||
|
||||
def test_gpt5_unknown_model_passes_through_minimal(config: OpenAIConfig):
|
||||
|
|
|
|||
|
|
@ -5,11 +5,14 @@ from copy import deepcopy
|
|||
from typing import Final, List, cast
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
import litellm
|
||||
from litellm import ModelResponse, completion
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages import handler as anthropic_messages_handler
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
|
||||
from litellm.llms.gemini.chat.transformation import GoogleAIStudioGeminiConfig
|
||||
from litellm.llms.vertex_ai.common_utils import VertexAIError
|
||||
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import (
|
||||
|
|
@ -2683,7 +2686,7 @@ def test_reasoning_effort_maps_to_thinking_level_gemini_3():
|
|||
[
|
||||
"gemini-3.7-flash",
|
||||
"vertex_ai/gemini-3.8-flash",
|
||||
"gemini-3.8-flash-preview",
|
||||
"gemini/gemini-3.8-flash",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -2691,7 +2694,7 @@ def test_reasoning_effort_maps_to_thinking_level_gemini_3():
|
|||
[("minimal", True), ("none", False), ("disable", False)],
|
||||
)
|
||||
def test_gemini_37_38_flash_floor_minimal_thinking_level(
|
||||
model, reasoning_effort, include_thoughts
|
||||
local_model_cost_map, model, reasoning_effort, include_thoughts
|
||||
):
|
||||
result = VertexGeminiConfig._map_reasoning_effort_to_thinking_level(
|
||||
reasoning_effort, model
|
||||
|
|
@ -2717,7 +2720,7 @@ def test_gemini_37_38_flash_floor_minimal_thinking_level(
|
|||
],
|
||||
)
|
||||
def test_gemini_flash_minimal_thinking_support(
|
||||
model, reasoning_effort, expected_level, include_thoughts
|
||||
local_model_cost_map, model, reasoning_effort, expected_level, include_thoughts
|
||||
):
|
||||
result = VertexGeminiConfig._map_reasoning_effort_to_thinking_level(
|
||||
reasoning_effort, model
|
||||
|
|
@ -2727,7 +2730,7 @@ def test_gemini_flash_minimal_thinking_support(
|
|||
assert result["includeThoughts"] is include_thoughts
|
||||
|
||||
|
||||
def test_gemini_38_flash_feature_flag_uses_low_thinking_level(monkeypatch):
|
||||
def test_gemini_38_flash_feature_flag_uses_low_thinking_level(local_model_cost_map, monkeypatch):
|
||||
monkeypatch.setattr(litellm, "enable_gemini_default_thinking_level_low", True)
|
||||
thinking_param = {"type": "enabled", "budget_tokens": 1024}
|
||||
|
||||
|
|
@ -2742,7 +2745,7 @@ def test_gemini_38_flash_feature_flag_uses_low_thinking_level(monkeypatch):
|
|||
assert result_36["thinkingLevel"] == "minimal"
|
||||
|
||||
|
||||
def test_gemini_38_flash_public_reasoning_effort_none_uses_low():
|
||||
def test_gemini_38_flash_public_reasoning_effort_none_uses_low(local_model_cost_map):
|
||||
result = VertexGeminiConfig().map_openai_params(
|
||||
non_default_params={"reasoning_effort": "none"},
|
||||
optional_params={},
|
||||
|
|
@ -2756,6 +2759,40 @@ def test_gemini_38_flash_public_reasoning_effort_none_uses_low():
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gemini_38_flash_messages_bridge_thinking_disabled_sends_low_thinking_level(local_model_cost_map):
|
||||
captured: dict[str, dict] = {}
|
||||
|
||||
def upstream(request: httpx.Request) -> httpx.Response:
|
||||
captured["body"] = json.loads(request.content)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"candidates": [{"content": {"parts": [{"text": "hi"}], "role": "model"}, "finishReason": "STOP"}],
|
||||
"usageMetadata": {"promptTokenCount": 1, "candidatesTokenCount": 1, "totalTokenCount": 2},
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
|
||||
client = AsyncHTTPHandler()
|
||||
client.client = httpx.AsyncClient(transport=httpx.MockTransport(upstream))
|
||||
|
||||
await anthropic_messages_handler.anthropic_messages(
|
||||
max_tokens=16,
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
model="gemini/gemini-3.8-flash",
|
||||
custom_llm_provider="gemini",
|
||||
thinking={"type": "disabled"},
|
||||
api_key="fake-gemini-key",
|
||||
client=client,
|
||||
)
|
||||
|
||||
assert captured["body"]["generationConfig"]["thinkingConfig"] == {
|
||||
"thinkingLevel": "low",
|
||||
"includeThoughts": False,
|
||||
}
|
||||
|
||||
|
||||
def test_reasoning_effort_dict_format_gemini_3():
|
||||
"""
|
||||
Test that reasoning_effort works when passed as dict format from OpenAI Agents SDK.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue