mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(reasoning): suggest reasoning_effort_levels and base_model only where the route reads them
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:
parent
f5f5715ea7
commit
812f0054a4
10 changed files with 60 additions and 30 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue