fix(reasoning): suggest reasoning_effort_levels and base_model only where the route reads them
Some checks are pending
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run

The chat gate suggested model_info.reasoning_effort_levels on routes that never pass declared levels
(github_copilot, chatgpt and other OpenAIConfig delegates) and model_info.base_model on OpenAI and
Azure AI chat, whose gate looks up the deployment name rather than base_model. /v1/responses/compact
suggested levels it never reads either.

Declared levels are now None when a caller passes none, so the advice appears only when a caller fed
them, and the base_model advice is limited to the Azure chat gate, the one that looks up base_model.

The openai row of the chat warning test now expects no base_model advice, the Responses warning test
goes through get_optional_params_responses_api the way /v1/responses and compact call it, and the Azure
direct-config test passes declared levels the way get_optional_params does. Long test lines are wrapped.
This commit is contained in:
mateo-berri 2026-10-28 16:44:47 -07:00
parent f5f5715ea7
commit 812f0054a4
10 changed files with 60 additions and 30 deletions

View file

@ -24,6 +24,7 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
GPT5_SERIES_ROUTE = "gpt5_series/"
OPT_IN_REASONING_EFFORTS: ClassVar[frozenset[str]] = frozenset(("xhigh", "none"))
LOOKS_UP_BASE_MODEL: ClassVar[bool] = True
@classmethod
def _model_map_lookup_name(cls, model: str) -> str:
@ -88,7 +89,7 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
drop_params: bool,
api_version: str = "",
*,
declared_reasoning_efforts: Collection[str] = (),
declared_reasoning_efforts: Collection[str] | None = None,
) -> dict:
return OpenAIGPT5Config.map_openai_params(
self,

View file

@ -106,7 +106,7 @@ class AzureAIStudioConfig(OpenAIConfig):
model: str,
drop_params: bool,
*,
declared_reasoning_efforts: Collection[str] = (),
declared_reasoning_efforts: Collection[str] | None = None,
) -> dict[str, object]: # mutable-ok: OpenAIConfig.map_openai_params signature
if not azureAIGPT5Config.is_model_gpt_5_model(model):
return super().map_openai_params(

View file

@ -114,6 +114,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
OPT_IN_REASONING_EFFORTS: ClassVar[frozenset[str]] = frozenset(("xhigh",))
ROUTE_SPECIFIC_REASONING_EFFORTS: ClassVar[frozenset[str]] = frozenset(("max",))
LOOKS_UP_BASE_MODEL: ClassVar[bool] = False
@classmethod
def is_model_gpt_5_model(cls, model: str) -> bool:
@ -328,7 +329,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
model: str,
drop_params: bool,
*,
declared_reasoning_efforts: Collection[str] = (),
declared_reasoning_efforts: Collection[str] | None = None,
) -> dict:
if self.is_model_gpt_5_search_model(model):
if "max_tokens" in non_default_params:
@ -359,14 +360,15 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
if (
requested_effort is not None
and requested_effort not in declared_reasoning_efforts
and requested_effort not in (declared_reasoning_efforts or ())
and self.refuses_reasoning_effort_level(model, requested_effort)
):
if litellm.drop_params or drop_params:
verbose_logger.warning(
refused_reasoning_effort_warning(
suggest_declared_levels=True,
suggest_base_model=not has_catalog_entry(self._model_map_lookup_name(model), None),
suggest_declared_levels=declared_reasoning_efforts is not None,
suggest_base_model=self.LOOKS_UP_BASE_MODEL
and not has_catalog_entry(self._model_map_lookup_name(model), None),
),
"reasoning_effort",
requested_effort,

View file

@ -382,7 +382,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
) -> dict:
return dict(
self.map_openai_params_for_deployment(
response_api_optional_params, model, drop_params, declared_reasoning_efforts=()
response_api_optional_params, model, drop_params, declared_reasoning_efforts=None
)
)
@ -395,7 +395,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
model: str,
drop_params: bool,
*,
declared_reasoning_efforts: Collection[str],
declared_reasoning_efforts: Collection[str] | None,
) -> Mapping[str, object]:
"""No mapping applied since inputs are in OpenAI spec already.
@ -435,7 +435,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
if (
isinstance(reasoning, dict)
and isinstance(requested_effort, str)
and requested_effort not in declared_reasoning_efforts
and requested_effort not in (declared_reasoning_efforts or ())
and self._effort_level_is_disabled(lookup_name, requested_effort)
):
if not (drop_params or litellm.drop_params):
@ -445,7 +445,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
)
verbose_logger.warning(
refused_reasoning_effort_warning(
suggest_declared_levels=self.honors_declared_reasoning_efforts(), suggest_base_model=False
suggest_declared_levels=declared_reasoning_efforts is not None, suggest_base_model=False
),
"reasoning.effort",
requested_effort,

View file

@ -127,12 +127,12 @@ def _provider_mapped_params(
params: ResponsesAPIOptionalRequestParams,
model: str,
drop_params: bool,
declared_reasoning_efforts: Collection[str],
declared_reasoning_efforts: Collection[str] | None,
) -> Mapping[str, object]:
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
if (
declared_reasoning_efforts
declared_reasoning_efforts is not None
and isinstance(config, OpenAIResponsesAPIConfig)
and config.honors_declared_reasoning_efforts()
):
@ -283,7 +283,7 @@ class ResponsesAPIRequestUtils:
response_api_optional_params: ResponsesAPIOptionalRequestParams,
allowed_openai_params: list[str] | None = None,
drop_params: bool | None = None,
declared_reasoning_efforts: Collection[str] = (),
declared_reasoning_efforts: Collection[str] | None = None,
) -> dict:
"""
Get optional parameters for the responses API.

View file

@ -291,6 +291,7 @@ def test_azure_dropped_effort_warning_suggests_base_model_only_for_a_name_the_ma
model=model,
drop_params=True,
api_version="2025-04-01-preview",
declared_reasoning_efforts=(),
)
assert "reasoning_effort" not in params

View file

@ -14,7 +14,10 @@ import pytest
import litellm
from litellm.llms.bedrock.common_utils import bedrock_supports_openai_responses
from litellm.llms.bedrock.responses.transformation import BedrockOpenAIResponsesConfig, _without_disabled_reasoning_effort
from litellm.llms.bedrock.responses.transformation import (
BedrockOpenAIResponsesConfig,
_without_disabled_reasoning_effort,
)
from litellm.responses.file_search.emulated_handler import should_use_emulated_file_search
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders

View file

@ -591,7 +591,9 @@ class TestBedrockMantleRefusedEffort:
assert "reasoning" not in params
drop_warnings: Final = [
record.getMessage() for record in caplog.records if f"Dropping reasoning.effort={refused!r}" in record.getMessage()
record.getMessage()
for record in caplog.records
if f"Dropping reasoning.effort={refused!r}" in record.getMessage()
]
assert len(drop_warnings) == 1
assert "model_info" not in drop_warnings[0]

View file

@ -16,6 +16,7 @@ from litellm.llms.azure.responses.transformation import AzureOpenAIResponsesAPIC
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.responses.litellm_completion_transformation.transformation import LiteLLMCompletionResponsesConfig
from litellm.responses.utils import ResponsesAPIRequestUtils
from litellm.types.llms.openai import (
ImageGenerationPartialImageEvent,
IncompleteDetails,
@ -2388,8 +2389,17 @@ class TestResponsesSurfaceSharesTheEffortRule:
)
assert mapped["reasoning"] == {"summary": "auto"}
def test_dropping_a_refused_effort_logs_one_warning_naming_the_level_and_the_fix(
self, local_model_cost_map: None, caplog: pytest.LogCaptureFixture
@pytest.mark.parametrize(
("declared_reasoning_efforts", "suggests_declared_levels"),
[((), True), (None, False)],
ids=["responses route reads model_info levels", "compact route reads none"],
)
def test_dropping_a_refused_effort_logs_one_warning_naming_only_the_fix_its_route_honors(
self,
local_model_cost_map: None,
caplog: pytest.LogCaptureFixture,
declared_reasoning_efforts: tuple[str, ...] | None,
suggests_declared_levels: bool,
) -> None:
refused: Final = next(
level
@ -2397,19 +2407,19 @@ class TestResponsesSurfaceSharesTheEffortRule:
if litellm.model_cost["gpt-6.1-sol"].get(f"supports_{level}_reasoning_effort") is False
)
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
mapped: Final = OpenAIResponsesAPIConfig().map_openai_params(
response_api_optional_params={"reasoning": {"effort": refused}},
mapped: Final = ResponsesAPIRequestUtils.get_optional_params_responses_api(
model="gpt-6.1-sol",
responses_api_provider_config=OpenAIResponsesAPIConfig(),
response_api_optional_params={"reasoning": {"effort": refused}},
drop_params=True,
declared_reasoning_efforts=declared_reasoning_efforts,
)
assert "reasoning" not in mapped
drop_warnings: Final = [record.getMessage() for record in caplog.records if "Dropping" in record.getMessage()]
assert len(drop_warnings) == 1
assert all(
fragment in drop_warnings[0]
for fragment in (repr(refused), "gpt-6.1-sol", "model_info.reasoning_effort_levels")
)
assert repr(refused) in drop_warnings[0] and "gpt-6.1-sol" in drop_warnings[0]
assert ("model_info.reasoning_effort_levels" in drop_warnings[0]) is suggests_declared_levels
assert "model_info.base_model" not in drop_warnings[0]
def test_dropping_reasoning_for_a_model_without_supports_reasoning_logs_one_warning(

View file

@ -1593,11 +1593,17 @@ def test_gpt_6_astra_takes_the_reasoning_series_request_shape():
@pytest.mark.parametrize(
("custom_llm_provider", "level"),
[("azure", "none"), ("azure", "xhigh"), ("openai", "xhigh")],
("custom_llm_provider", "level", "advice"),
[
("azure", "none", frozenset(("model_info.reasoning_effort_levels", "model_info.base_model"))),
("azure", "xhigh", frozenset(("model_info.reasoning_effort_levels", "model_info.base_model"))),
("openai", "xhigh", frozenset(("model_info.reasoning_effort_levels",))),
("azure_ai", "xhigh", frozenset(("model_info.reasoning_effort_levels",))),
("github_copilot", "xhigh", frozenset()),
],
)
def test_a_reasoning_effort_the_level_gate_drops_logs_a_warning_naming_the_fix(
caplog: pytest.LogCaptureFixture, custom_llm_provider: str, level: str
def test_a_reasoning_effort_the_level_gate_drops_logs_a_warning_naming_only_the_fixes_its_route_honors(
caplog: pytest.LogCaptureFixture, custom_llm_provider: str, level: str, advice: frozenset[str]
) -> None:
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
params: Final = litellm.get_optional_params(
@ -1614,9 +1620,14 @@ def test_a_reasoning_effort_the_level_gate_drops_logs_a_warning_naming_the_fix(
if record.levelno == logging.WARNING and "reasoning_effort" in record.getMessage()
]
assert len(drop_warnings) == 1
assert all(
fragment in drop_warnings[0]
for fragment in (level, "gpt-5.6-terra-ptu", "model_info.base_model", "model_info.reasoning_effort_levels")
assert repr(level) in drop_warnings[0] and "gpt-5.6-terra-ptu" in drop_warnings[0]
assert (
frozenset(
fragment
for fragment in ("model_info.reasoning_effort_levels", "model_info.base_model")
if fragment in drop_warnings[0]
)
== advice
)