mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(bedrock): honor unsupported reasoning effort levels for OpenAI GPT models (#44183)
Bedrock GPT-5.6 rejects reasoning effort minimal with 400 unsupported_value. The model map already sets supports_minimal_reasoning_effort=false for these models, but neither the Converse path nor the native Responses path read it, so the value was forwarded. Drop it under drop_params and raise UnsupportedParamsError otherwise, matching how the OpenAI GPT-5 path treats explicitly disabled effort levels Co-authored-by: Aasif-Multani <20943280+Aasif-Multani@users.noreply.github.com>
This commit is contained in:
parent
6b6222578e
commit
54260bec88
5 changed files with 117 additions and 1 deletions
|
|
@ -104,6 +104,7 @@ from ..common_utils import (
|
|||
BedrockModelInfo,
|
||||
bedrock_converse_supports_parallel_tool_use_config,
|
||||
bedrock_model_accepts_cache_points,
|
||||
bedrock_reasoning_effort_disabled,
|
||||
get_anthropic_beta_from_headers,
|
||||
get_bedrock_tool_name,
|
||||
is_bedrock_application_inference_profile_arn,
|
||||
|
|
@ -1135,6 +1136,25 @@ class AmazonConverseConfig(BaseConfig):
|
|||
"Dropping unsupported `reasoning_effort` param for Bedrock model=%s; it always reasons and rejects it.",
|
||||
model,
|
||||
)
|
||||
elif (
|
||||
param == "reasoning_effort"
|
||||
and isinstance(value, str)
|
||||
and self._is_openai_gpt_reasoning_model(model)
|
||||
and bedrock_reasoning_effort_disabled(model=model, effort=value)
|
||||
):
|
||||
if not (litellm.drop_params or drop_params):
|
||||
raise litellm.utils.UnsupportedParamsError(
|
||||
message=(
|
||||
f"{model} does not support reasoning_effort={value}. "
|
||||
"To drop unsupported params, set `litellm.drop_params = True`."
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
verbose_logger.debug(
|
||||
"Dropping unsupported `reasoning_effort=%s` for Bedrock model=%s.",
|
||||
value,
|
||||
model,
|
||||
)
|
||||
elif param == "reasoning_effort" and isinstance(value, str):
|
||||
self._handle_reasoning_effort_parameter(
|
||||
model=model, reasoning_effort=value, optional_params=optional_params
|
||||
|
|
|
|||
|
|
@ -1017,6 +1017,14 @@ def _mantle_api_base_from_env() -> str | None:
|
|||
return next((base[: -len(suffix)] for suffix in _MANTLE_OPENAI_BASE_SUFFIXES if base.endswith(suffix)), base)
|
||||
|
||||
|
||||
def bedrock_reasoning_effort_disabled(model: str, effort: str) -> bool:
|
||||
from litellm.utils import is_explicitly_disabled_factory
|
||||
|
||||
return is_explicitly_disabled_factory(
|
||||
model=model, custom_llm_provider="bedrock_converse", key=f"supports_{effort}_reasoning_effort"
|
||||
)
|
||||
|
||||
|
||||
def bedrock_supports_openai_responses(model: str | None, model_cost: Mapping[str, object]) -> bool:
|
||||
"""Whether a Bedrock model is served by bedrock-runtime's OpenAI Responses surface.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
|||
from litellm.llms.bedrock.common_utils import (
|
||||
BEDROCK_CHAT_COMPLETIONS_ROUTE_PREFIX,
|
||||
BedrockError,
|
||||
bedrock_reasoning_effort_disabled,
|
||||
bedrock_supports_openai_responses,
|
||||
)
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
|
|
@ -154,6 +155,29 @@ def inline_remote_image_urls(
|
|||
return items # pyright: ignore[reportReturnType] # items keep the caller's input union
|
||||
|
||||
|
||||
def _without_disabled_reasoning_effort(
|
||||
params: Mapping[str, object], model: str, drop_params: bool
|
||||
) -> dict[str, object]: # mutable-ok: becomes the map_openai_params return value
|
||||
reasoning: Final = params.get("reasoning")
|
||||
effort: Final = reasoning.get("effort") if isinstance(reasoning, Mapping) else None
|
||||
if not isinstance(reasoning, Mapping) or not isinstance(effort, str):
|
||||
return dict(params)
|
||||
if not bedrock_reasoning_effort_disabled(model=model, effort=effort):
|
||||
return dict(params)
|
||||
if not (drop_params or litellm.drop_params):
|
||||
raise litellm.UnsupportedParamsError(
|
||||
message=(
|
||||
f"{model} does not support reasoning.effort={effort}. "
|
||||
"To drop unsupported params, set `litellm.drop_params = True`."
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
verbose_logger.debug("Dropping unsupported `reasoning.effort=%s` for Bedrock model=%s.", effort, model)
|
||||
rest: Final = {key: value for key, value in reasoning.items() if key != "effort"}
|
||||
without_reasoning: Final = {key: value for key, value in params.items() if key != "reasoning"}
|
||||
return {**without_reasoning, "reasoning": rest} if rest else without_reasoning
|
||||
|
||||
|
||||
class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig):
|
||||
"""Responses API config for the OpenAI models on the bedrock-runtime endpoint."""
|
||||
|
||||
|
|
@ -270,7 +294,8 @@ class BedrockOpenAIResponsesConfig(BaseAWSLLM, OpenAIResponsesAPIConfig):
|
|||
"Bedrock Runtime Responses API: dropping unsupported parameter(s) %s that the endpoint rejects.",
|
||||
unsupported,
|
||||
)
|
||||
params: Final = {key: value for key, value in mapped.items() if key not in unsupported}
|
||||
supported: Final[dict[str, object]] = {key: value for key, value in mapped.items() if key not in unsupported}
|
||||
params: Final = _without_disabled_reasoning_effort(supported, model, drop_params)
|
||||
tools: Final = params.get("tools")
|
||||
if not isinstance(tools, list):
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -520,6 +520,39 @@ def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(mode
|
|||
assert "thinking" not in additional_request_params
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"us.openai.gpt-5.6-luna",
|
||||
"bedrock/converse/global.openai.gpt-5.6-terra",
|
||||
"us.openai.gpt-6-astra",
|
||||
],
|
||||
)
|
||||
def test_openai_gpt5_converse_rejects_effort_level_disabled_in_model_map(model, local_model_cost_map):
|
||||
config = AmazonConverseConfig()
|
||||
assert litellm.utils.is_explicitly_disabled_factory(
|
||||
model=model, custom_llm_provider="bedrock_converse", key="supports_minimal_reasoning_effort"
|
||||
)
|
||||
|
||||
with pytest.raises(litellm.utils.UnsupportedParamsError, match="minimal"):
|
||||
config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "minimal"},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "minimal"},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=True,
|
||||
)
|
||||
_, additional_request_params, _, _ = config._prepare_request_params(optional_params, model)
|
||||
assert "reasoning" not in additional_request_params
|
||||
assert "thinking" not in additional_request_params
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -328,6 +328,36 @@ class TestBackgroundDrop:
|
|||
assert not [r for r in caplog.records if "dropping unsupported parameter" in r.getMessage()]
|
||||
|
||||
|
||||
class TestDisabledReasoningEffort:
|
||||
@pytest.mark.parametrize("model", ["us.openai.gpt-5.6-luna", MODEL])
|
||||
def test_effort_level_disabled_in_model_map_is_rejected(self, model, local_model_cost_map):
|
||||
with pytest.raises(litellm.UnsupportedParamsError, match="minimal"):
|
||||
_cfg().map_openai_params(
|
||||
response_api_optional_params={"reasoning": {"effort": "minimal"}}, model=model, drop_params=False
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("model", ["us.openai.gpt-5.6-luna", MODEL])
|
||||
def test_effort_level_disabled_in_model_map_is_dropped_with_drop_params(self, model, local_model_cost_map):
|
||||
params = _cfg().map_openai_params(
|
||||
response_api_optional_params={"reasoning": {"effort": "minimal", "summary": "auto"}, "max_output_tokens": 64},
|
||||
model=model,
|
||||
drop_params=True,
|
||||
)
|
||||
assert params == {"reasoning": {"summary": "auto"}, "max_output_tokens": 64}
|
||||
|
||||
def test_effort_only_reasoning_is_removed_when_dropped(self, local_model_cost_map):
|
||||
params = _cfg().map_openai_params(
|
||||
response_api_optional_params={"reasoning": {"effort": "minimal"}}, model=MODEL, drop_params=True
|
||||
)
|
||||
assert params == {}
|
||||
|
||||
def test_supported_effort_level_is_forwarded(self, local_model_cost_map):
|
||||
params = _cfg().map_openai_params(
|
||||
response_api_optional_params={"reasoning": {"effort": "low"}}, model=MODEL, drop_params=False
|
||||
)
|
||||
assert params == {"reasoning": {"effort": "low"}}
|
||||
|
||||
|
||||
def _never_fetch(url: str) -> str:
|
||||
raise AssertionError(f"unexpected sync fetch of {url}")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue