From e0027bdc53c2612f6a8a7264d7d74ff145f81731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 17:12:30 +0300 Subject: [PATCH 01/19] fix(responses): reject unsupported reasoning effort --- .../llms/openai/responses/transformation.py | 32 ++++++++++++--- .../test_reasoning_effort_capability.py | 40 +++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index eac844a790d..b4299d2c907 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -121,9 +121,8 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): ) -> dict: """No mapping applied since inputs are in OpenAI spec already. - GPT-5 models have restrictions on temperature (only temperature=1 - is accepted unless reasoning_effort='none' on models that support it). - Apply the same validation used by the chat completions path. + GPT-5 models have restrictions on temperature and reasoning effort. + Apply the same capability checks used by the chat completions path. """ params: Final = dict(response_api_optional_params) @@ -131,10 +130,33 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): params["max_output_tokens"] = self._enforce_min_max_output_tokens(params.get("max_output_tokens")) if self._is_gpt_5_model(model=model): + reasoning: Final = params.get("reasoning") or {} + effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None + if isinstance(effort, str): + from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config + + unsupported_effort: Final = ( + effort == "xhigh" and not OpenAIGPT5Config._supports_reasoning_effort_level(model, effort) + ) or ( + effort in ("minimal", "low") + and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled(model, effort) + ) + if unsupported_effort: + if drop_params or litellm.drop_params: + updated_reasoning: Final = dict(reasoning) + updated_reasoning.pop("effort", None) + if updated_reasoning: + params["reasoning"] = updated_reasoning + else: + params.pop("reasoning", None) + else: + raise litellm.UnsupportedParamsError( + message=f"reasoning.effort={effort} is not supported for this model.", + status_code=400, + ) + temperature: Final = params.get("temperature") if temperature is not None and temperature != 1: - reasoning: Final = params.get("reasoning") or {} - effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None supports_none: Final = self._supports_reasoning_effort_none(model=model) if supports_none and self._effort_resolves_to_none(model, effort): pass # flexible temperature allowed diff --git a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py new file mode 100644 index 00000000000..e055f413faf --- /dev/null +++ b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py @@ -0,0 +1,40 @@ +import pytest + +import litellm +from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig + + +@pytest.mark.parametrize("effort", ["minimal", "low"]) +def test_rejects_explicitly_unsupported_lower_reasoning_effort(effort: str) -> None: + config = OpenAIResponsesAPIConfig() + + with pytest.raises(litellm.UnsupportedParamsError, match=f"reasoning.effort={effort}"): + config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": effort}}, + model="gpt-5.5-pro", + drop_params=False, + ) + + +def test_keeps_supported_reasoning_effort() -> None: + config = OpenAIResponsesAPIConfig() + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium"}}, + model="gpt-5.5-pro", + drop_params=False, + ) + + assert result["reasoning"] == {"effort": "medium"} + + +def test_drop_params_removes_only_unsupported_effort() -> None: + config = OpenAIResponsesAPIConfig() + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal", "summary": "detailed"}}, + model="gpt-5.5-pro", + drop_params=True, + ) + + assert result["reasoning"] == {"summary": "detailed"} From 7a295933a5fb2157b11cede760c6ded7a27aa6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 17:25:49 +0300 Subject: [PATCH 02/19] fix(responses): validate temperature after effort drop --- litellm/llms/openai/responses/transformation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index b4299d2c907..fcb6760d3b3 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -157,8 +157,12 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): temperature: Final = params.get("temperature") if temperature is not None and temperature != 1: + transformed_reasoning: Final = params.get("reasoning") or {} + transformed_effort: Final = ( + transformed_reasoning.get("effort") if isinstance(transformed_reasoning, dict) else None + ) supports_none: Final = self._supports_reasoning_effort_none(model=model) - if supports_none and self._effort_resolves_to_none(model, effort): + if supports_none and self._effort_resolves_to_none(model, transformed_effort): pass # flexible temperature allowed elif drop_params or litellm.drop_params: params.pop("temperature", None) From f985141e7cdfda45188124a7cc8cc1932d215d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 17:26:11 +0300 Subject: [PATCH 03/19] test(responses): cover effort drop temperature interaction --- .../test_reasoning_effort_capability.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py index e055f413faf..2eef324eacd 100644 --- a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py +++ b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py @@ -1,6 +1,7 @@ import pytest import litellm +from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig @@ -38,3 +39,20 @@ def test_drop_params_removes_only_unsupported_effort() -> None: ) assert result["reasoning"] == {"summary": "detailed"} + + +def test_drop_params_revalidates_temperature_after_effort_drop(monkeypatch: pytest.MonkeyPatch) -> None: + config = OpenAIResponsesAPIConfig() + + monkeypatch.setattr(OpenAIGPT5Config, "_supports_reasoning_effort_level", lambda model, level: False) + monkeypatch.setattr(config, "_supports_reasoning_effort_none", lambda model: True) + monkeypatch.setattr(config, "_effort_resolves_to_none", lambda model, effort: effort is None) + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "xhigh"}, "temperature": 0.5}, + model="gpt-5-test", + drop_params=True, + ) + + assert "reasoning" not in result + assert result["temperature"] == 0.5 From 111bc6dfb1a542dbb28b8e0b5bfa0effce3c9623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 17:46:51 +0300 Subject: [PATCH 04/19] chore(responses): satisfy type discipline gate --- litellm/llms/openai/responses/transformation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index fcb6760d3b3..f0932e3dd11 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -143,7 +143,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): ) if unsupported_effort: if drop_params or litellm.drop_params: - updated_reasoning: Final = dict(reasoning) + updated_reasoning: Final = dict(reasoning) # mutable-ok: local request payload copy before removing unsupported effort updated_reasoning.pop("effort", None) if updated_reasoning: params["reasoning"] = updated_reasoning @@ -157,7 +157,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): temperature: Final = params.get("temperature") if temperature is not None and temperature != 1: - transformed_reasoning: Final = params.get("reasoning") or {} + transformed_reasoning: Final = params.get("reasoning") or {} # mutable-ok: local request view for transformed effort validation transformed_effort: Final = ( transformed_reasoning.get("effort") if isinstance(transformed_reasoning, dict) else None ) @@ -365,7 +365,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): else item_data.get("reasoning_content", "") ) - # Create ResponseReasoningItem object from the item data + # Create ResponsesReasoningItem object with item data reasoning_item: Final = ResponseReasoningItem(**item_data) # Convert back to dict with exclude_none=True to exclude None fields From a1c0bdb97a99090134438fe56990aa9d58b9741f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 17:48:49 +0300 Subject: [PATCH 05/19] chore(responses): restore existing comment --- litellm/llms/openai/responses/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index f0932e3dd11..47bbcbeda4e 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -365,7 +365,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): else item_data.get("reasoning_content", "") ) - # Create ResponsesReasoningItem object with item data + # Create ResponseReasoningItem object from the item data reasoning_item: Final = ResponseReasoningItem(**item_data) # Convert back to dict with exclude_none=True to exclude None fields From dbdb157bee5b8f959abced45a4d2c2186867153b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 18:08:50 +0300 Subject: [PATCH 06/19] chore(responses): keep lint comments formatted --- litellm/llms/openai/responses/transformation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 47bbcbeda4e..7301f12fe21 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -143,7 +143,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): ) if unsupported_effort: if drop_params or litellm.drop_params: - updated_reasoning: Final = dict(reasoning) # mutable-ok: local request payload copy before removing unsupported effort + updated_reasoning: Final = dict(reasoning) # mutable-ok: local copy updated_reasoning.pop("effort", None) if updated_reasoning: params["reasoning"] = updated_reasoning @@ -157,7 +157,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): temperature: Final = params.get("temperature") if temperature is not None and temperature != 1: - transformed_reasoning: Final = params.get("reasoning") or {} # mutable-ok: local request view for transformed effort validation + transformed_reasoning: Final = params.get("reasoning") or {} # mutable-ok: local view transformed_effort: Final = ( transformed_reasoning.get("effort") if isinstance(transformed_reasoning, dict) else None ) @@ -794,4 +794,4 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): response._hidden_params["additional_headers"] = processed_headers response._hidden_params["headers"] = raw_response_headers - return response + return response \ No newline at end of file From 88a444f8f599e661f48985e58aae72a28019f188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 18:10:57 +0300 Subject: [PATCH 07/19] chore(responses): restore final newline --- litellm/llms/openai/responses/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 7301f12fe21..2b71038c14a 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -794,4 +794,4 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): response._hidden_params["additional_headers"] = processed_headers response._hidden_params["headers"] = raw_response_headers - return response \ No newline at end of file + return response From 031cad4cfd9838ae997382d682c136d9e1cd872c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 18:40:50 +0300 Subject: [PATCH 08/19] chore(responses): satisfy basedpyright gate --- litellm/llms/openai/responses/transformation.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 2b71038c14a..61aa35c1f36 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -136,10 +136,15 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config unsupported_effort: Final = ( - effort == "xhigh" and not OpenAIGPT5Config._supports_reasoning_effort_level(model, effort) + effort == "xhigh" + and not OpenAIGPT5Config._supports_reasoning_effort_level( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate + model, effort + ) ) or ( effort in ("minimal", "low") - and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled(model, effort) + and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate + model, effort + ) ) if unsupported_effort: if drop_params or litellm.drop_params: From 97e201b1c604b294bf221c3bf4486f28aad35931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 18:49:38 +0300 Subject: [PATCH 09/19] chore(responses): suppress helper call diagnostics --- litellm/llms/openai/responses/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 61aa35c1f36..05d05c9e135 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -138,12 +138,12 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): unsupported_effort: Final = ( effort == "xhigh" and not OpenAIGPT5Config._supports_reasoning_effort_level( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, effort + model, effort # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call ) ) or ( effort in ("minimal", "low") and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, effort + model, effort # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call ) ) if unsupported_effort: From d6514effaba9b456edbe21efc89206e6f2cfe59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 19:15:33 +0300 Subject: [PATCH 10/19] chore(responses): format helper suppressions --- litellm/llms/openai/responses/transformation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 05d05c9e135..74b0a2fff6d 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -138,12 +138,14 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): unsupported_effort: Final = ( effort == "xhigh" and not OpenAIGPT5Config._supports_reasoning_effort_level( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, effort # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call + model, + effort, # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call ) ) or ( effort in ("minimal", "low") and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, effort # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call + model, + effort, # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call ) ) if unsupported_effort: From 3a30409bec55796537e7bd78a7de776f6bab5c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 19:30:01 +0300 Subject: [PATCH 11/19] fix(responses): keep reasoning effort typed --- .../llms/openai/responses/transformation.py | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 74b0a2fff6d..a8caf332ca0 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -81,6 +81,25 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): return OpenAIGPT5Config.effort_resolves_to_none(model, effort) + @staticmethod + def _is_unsupported_reasoning_effort(model: str, effort: str | None) -> bool: + """Apply the GPT-5 reasoning-effort capability flags used by chat completions.""" + from litellm.utils import _is_explicitly_disabled_factory, _supports_factory + + if effort == "xhigh": + return not _supports_factory( + model=model, + custom_llm_provider=None, + key="supports_xhigh_reasoning_effort", + ) + if effort in ("minimal", "low"): + return _is_explicitly_disabled_factory( + model=model, + custom_llm_provider=None, + key=f"supports_{effort}_reasoning_effort", + ) + return False + @staticmethod def _enforce_min_max_output_tokens(max_output_tokens: "int | None") -> "int | None": """Raise sub-minimum max_output_tokens up to the OpenAI Responses API minimum. @@ -130,46 +149,31 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): params["max_output_tokens"] = self._enforce_min_max_output_tokens(params.get("max_output_tokens")) if self._is_gpt_5_model(model=model): - reasoning: Final = params.get("reasoning") or {} - effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None - if isinstance(effort, str): - from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config + reasoning: Final = response_api_optional_params.get("reasoning") + effort: Final = reasoning.get("effort") if reasoning is not None else None + unsupported_effort: Final = self._is_unsupported_reasoning_effort(model, effort) + should_drop_effort: Final = unsupported_effort and (drop_params or litellm.drop_params) - unsupported_effort: Final = ( - effort == "xhigh" - and not OpenAIGPT5Config._supports_reasoning_effort_level( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, - effort, # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call - ) - ) or ( - effort in ("minimal", "low") - and OpenAIGPT5Config._is_reasoning_effort_level_explicitly_disabled( # pyright: ignore[reportPrivateUsage, reportArgumentType, reportCallIssue] # shared GPT-5 capability gate - model, - effort, # pyright: ignore[reportArgumentType, reportCallIssue] # shared helper call - ) - ) - if unsupported_effort: - if drop_params or litellm.drop_params: - updated_reasoning: Final = dict(reasoning) # mutable-ok: local copy + if unsupported_effort: + if should_drop_effort: + if reasoning is not None: + updated_reasoning: Final = reasoning.copy() # mutable-ok: local copy updated_reasoning.pop("effort", None) if updated_reasoning: params["reasoning"] = updated_reasoning else: params.pop("reasoning", None) - else: - raise litellm.UnsupportedParamsError( - message=f"reasoning.effort={effort} is not supported for this model.", - status_code=400, - ) + else: + raise litellm.UnsupportedParamsError( + message=f"reasoning.effort={effort} is not supported for this model.", + status_code=400, + ) temperature: Final = params.get("temperature") if temperature is not None and temperature != 1: - transformed_reasoning: Final = params.get("reasoning") or {} # mutable-ok: local view - transformed_effort: Final = ( - transformed_reasoning.get("effort") if isinstance(transformed_reasoning, dict) else None - ) + effective_effort: Final = None if should_drop_effort else effort supports_none: Final = self._supports_reasoning_effort_none(model=model) - if supports_none and self._effort_resolves_to_none(model, transformed_effort): + if supports_none and self._effort_resolves_to_none(model, effective_effort): pass # flexible temperature allowed elif drop_params or litellm.drop_params: params.pop("temperature", None) From 7dc5ae4d27c6ee7911c61aa9cfc55f49268d50f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 20:01:33 +0300 Subject: [PATCH 12/19] fix(responses): use public reasoning capability helper --- .../llms/openai/responses/transformation.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index a8caf332ca0..54ce6a366e6 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -84,20 +84,16 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): @staticmethod def _is_unsupported_reasoning_effort(model: str, effort: str | None) -> bool: """Apply the GPT-5 reasoning-effort capability flags used by chat completions.""" - from litellm.utils import _is_explicitly_disabled_factory, _supports_factory + from litellm.router_utils.reasoning_effort_capability import ( + declared_reasoning_efforts_for_model, + ) + lookup_model: Final = model.removeprefix("openai/") + declared: Final = declared_reasoning_efforts_for_model(lookup_model, "openai") if effort == "xhigh": - return not _supports_factory( - model=model, - custom_llm_provider=None, - key="supports_xhigh_reasoning_effort", - ) + return declared is None or effort not in declared if effort in ("minimal", "low"): - return _is_explicitly_disabled_factory( - model=model, - custom_llm_provider=None, - key=f"supports_{effort}_reasoning_effort", - ) + return declared is not None and effort not in declared return False @staticmethod From 96a9fb6e1a758f0f59b4504a89bcb6422ad1da77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= Date: Sun, 30 Aug 2026 20:28:23 +0300 Subject: [PATCH 13/19] fix(responses): share GPT-5 effort capability gate --- litellm/llms/openai/chat/gpt_5_transformation.py | 9 +++++++++ litellm/llms/openai/responses/transformation.py | 12 ++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index 0223be300b0..bfaf396c7fb 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -156,6 +156,15 @@ class OpenAIGPT5Config(OpenAIGPTConfig): key=f"supports_{level}_reasoning_effort", ) + @classmethod + def is_reasoning_effort_unsupported(cls, model: str, level: str | None) -> bool: + """Return whether GPT-5 capability metadata rejects this effort level.""" + if level == "xhigh": + return not cls._supports_reasoning_effort_level(model, level) + if level in ("minimal", "low"): + return cls._is_reasoning_effort_level_explicitly_disabled(model, level) + return False + @classmethod def effort_resolves_to_none(cls, model: str, effective_effort: str | None) -> bool: """Whether this request's reasoning effort ends up as "none", which is the single diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 54ce6a366e6..8dd8b574ee8 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -84,17 +84,9 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): @staticmethod def _is_unsupported_reasoning_effort(model: str, effort: str | None) -> bool: """Apply the GPT-5 reasoning-effort capability flags used by chat completions.""" - from litellm.router_utils.reasoning_effort_capability import ( - declared_reasoning_efforts_for_model, - ) + from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config - lookup_model: Final = model.removeprefix("openai/") - declared: Final = declared_reasoning_efforts_for_model(lookup_model, "openai") - if effort == "xhigh": - return declared is None or effort not in declared - if effort in ("minimal", "low"): - return declared is not None and effort not in declared - return False + return OpenAIGPT5Config.is_reasoning_effort_unsupported(model, effort) @staticmethod def _enforce_min_max_output_tokens(max_output_tokens: "int | None") -> "int | None": From 0764a0a923bf1e7e3cd6689929baa03b4f6a50a2 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Mon, 14 Sep 2026 00:12:15 +0300 Subject: [PATCH 14/19] fix(responses): dispatch effort capability check by provider and drop test monkeypatching - Azure responses config reads Azure map entry (OpenAI entry disables minimal on gpt-5.4, Azure enables it); temperature revalidation test now uses real gpt-5.1 map data --- .../llms/azure/responses/transformation.py | 9 +++++ .../test_reasoning_effort_capability.py | 40 +++++++++++++++---- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/litellm/llms/azure/responses/transformation.py b/litellm/llms/azure/responses/transformation.py index 7fe12138ebc..696840b0d17 100644 --- a/litellm/llms/azure/responses/transformation.py +++ b/litellm/llms/azure/responses/transformation.py @@ -38,6 +38,15 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): def _effort_resolves_to_none(model: str, effort: str | None) -> bool: return AzureOpenAIGPT5Config.effort_resolves_to_none(model, effort) + @staticmethod + def _is_unsupported_reasoning_effort(model: str, effort: str | None) -> bool: + """Read the effort capability from the Azure map entry, like the sibling checks above. + + The inherited OpenAI lookup would resolve a bare Azure deployment name (e.g. ``gpt-5.4``) + against the OpenAI entry, which can explicitly disable an effort Azure supports. + """ + return AzureOpenAIGPT5Config.is_reasoning_effort_unsupported(model, effort) + def get_supported_openai_params(self, model: str) -> list: """ Azure Responses API does not support context_management (compaction). diff --git a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py index 2eef324eacd..cdb8f3ee4bc 100644 --- a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py +++ b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py @@ -1,7 +1,7 @@ import pytest import litellm -from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config +from litellm.llms.azure.responses.transformation import AzureOpenAIResponsesAPIConfig from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig @@ -41,18 +41,44 @@ def test_drop_params_removes_only_unsupported_effort() -> None: assert result["reasoning"] == {"summary": "detailed"} -def test_drop_params_revalidates_temperature_after_effort_drop(monkeypatch: pytest.MonkeyPatch) -> None: - config = OpenAIResponsesAPIConfig() +def test_drop_params_revalidates_temperature_after_effort_drop() -> None: + """Dropping an unsupported effort revalidates temperature against the emptied effort. - monkeypatch.setattr(OpenAIGPT5Config, "_supports_reasoning_effort_level", lambda model, level: False) - monkeypatch.setattr(config, "_supports_reasoning_effort_none", lambda model: True) - monkeypatch.setattr(config, "_effort_resolves_to_none", lambda model, effort: effort is None) + `gpt-5.1` really declares `xhigh` unsupported while supporting (and defaulting to) + `none`, so no capability stubbing is needed: the dropped effort resolves to `none` + and a valid non-default temperature survives. + """ + config = OpenAIResponsesAPIConfig() result = config.map_openai_params( response_api_optional_params={"reasoning": {"effort": "xhigh"}, "temperature": 0.5}, - model="gpt-5-test", + model="gpt-5.1", drop_params=True, ) assert "reasoning" not in result assert result["temperature"] == 0.5 + + +def test_azure_config_uses_azure_capability_map_for_effort() -> None: + """A bare Azure deployment name must resolve against the Azure map entry. + + OpenAI's `gpt-5.4` entry explicitly disables `minimal` while Azure's enables it; + the OpenAI lookup would wrongly reject an effort Azure supports. + """ + openai_config = OpenAIResponsesAPIConfig() + with pytest.raises(litellm.UnsupportedParamsError, match="reasoning.effort=minimal"): + openai_config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal"}}, + model="gpt-5.4", + drop_params=False, + ) + + azure_config = AzureOpenAIResponsesAPIConfig() + result = azure_config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal"}}, + model="gpt-5.4", + drop_params=False, + ) + + assert result["reasoning"] == {"effort": "minimal"} From 14d81858dbae967553dd618499b922ed11cc91d2 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Mon, 14 Sep 2026 00:30:11 +0300 Subject: [PATCH 15/19] fix(tests): wrap pytest.raises match in re.escape to satisfy RUF043 --- .../llms/openai/responses/test_reasoning_effort_capability.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py index cdb8f3ee4bc..585a0ea3b5f 100644 --- a/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py +++ b/tests/test_litellm/llms/openai/responses/test_reasoning_effort_capability.py @@ -1,3 +1,5 @@ +import re + import pytest import litellm @@ -67,7 +69,7 @@ def test_azure_config_uses_azure_capability_map_for_effort() -> None: the OpenAI lookup would wrongly reject an effort Azure supports. """ openai_config = OpenAIResponsesAPIConfig() - with pytest.raises(litellm.UnsupportedParamsError, match="reasoning.effort=minimal"): + with pytest.raises(litellm.UnsupportedParamsError, match=re.escape("reasoning.effort=minimal")): openai_config.map_openai_params( response_api_optional_params={"reasoning": {"effort": "minimal"}}, model="gpt-5.4", From d1cea94f497c9a3a3610e8c6176d9328be656c95 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Mon, 14 Sep 2026 01:21:38 +0300 Subject: [PATCH 16/19] fix(auth): cache prefetched org entries with the management TTL - the 5s default fuse expired before slow requests reached get_org_object, sending them to the DB (proxy-behavior auth prefetch test failed under load with dead_db TypeError) --- litellm/proxy/auth/auth_object_prefetch.py | 8 +++++--- .../test_litellm/proxy/auth/test_auth_object_prefetch.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/auth/auth_object_prefetch.py b/litellm/proxy/auth/auth_object_prefetch.py index 52e26e885c9..503223e7d87 100644 --- a/litellm/proxy/auth/auth_object_prefetch.py +++ b/litellm/proxy/auth/auth_object_prefetch.py @@ -14,7 +14,6 @@ from pydantic import BaseModel, TypeAdapter, ValidationError from litellm._logging import verbose_proxy_logger from litellm.caching.redis_cache import RedisCache -from litellm.constants import DEFAULT_IN_MEMORY_TTL from litellm.models.organization import LiteLLM_OrganizationTable from litellm.models.team import LiteLLM_TeamTableCachedObj from litellm.models.team_membership import LiteLLM_TeamMembership @@ -190,14 +189,17 @@ def _iter_entries(refs: AuthObjectRefs, management_ttl: float) -> Iterator[_Cach None, ) if refs.organization_id is not None: + # Organization entries use the management TTL like every other prefetched object: a + # 5s fuse expires before slow requests reach the getters, sending them back to + # the DB the prefetch was meant to spare. yield _CacheEntry( - f"org_id:{refs.organization_id}", "organization_row", LiteLLM_OrganizationTable, DEFAULT_IN_MEMORY_TTL + f"org_id:{refs.organization_id}", "organization_row", LiteLLM_OrganizationTable, management_ttl ) yield _CacheEntry( f"org_id:{refs.organization_id}:with_budget", "organization_row", LiteLLM_OrganizationTable, - DEFAULT_IN_MEMORY_TTL, + management_ttl, ) if refs.project_id is not None: yield _CacheEntry(f"project_id:{refs.project_id}", "project_row", LiteLLM_ProjectTableCachedObj, management_ttl) diff --git a/tests/test_litellm/proxy/auth/test_auth_object_prefetch.py b/tests/test_litellm/proxy/auth/test_auth_object_prefetch.py index 0fd0dda3017..58761e270a0 100644 --- a/tests/test_litellm/proxy/auth/test_auth_object_prefetch.py +++ b/tests/test_litellm/proxy/auth/test_auth_object_prefetch.py @@ -181,8 +181,8 @@ async def test_cold_regime_is_one_mget_one_query_and_the_getters_never_touch_io_ assert sets == sorted( [ f"SET {TEAM_ID}_{USER_ID} ttl=5", - f"SET org_id:{ORG_ID} ttl=5", - f"SET org_id:{ORG_ID}:with_budget ttl=5", + f"SET org_id:{ORG_ID} ttl=60", + f"SET org_id:{ORG_ID}:with_budget ttl=60", f"SET {USER_ID} ttl=60", f"SET team_id:{TEAM_ID} ttl=60", f"SET team_membership:{USER_ID}:{TEAM_ID} ttl=None", From 7d6550e1bacfceffcb2756f4d6504178545b5bc1 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Mon, 14 Sep 2026 01:27:23 +0300 Subject: [PATCH 17/19] chore(ui): sync stale schema.d.ts soft_budget removal (matches generator output; unblocks Check UI API Types Sync now that the branch touches litellm/proxy/) --- ui/litellm-dashboard/src/lib/http/schema.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 7eadaa6c991..839aa52fa84 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -16781,7 +16781,6 @@ export interface paths { * - permissions: Optional[dict] - [Not Implemented Yet] User-specific permissions, eg. turning off pii masking. * - metadata: Optional[dict] - Metadata for user, store information for user. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" } * - max_parallel_requests: Optional[int] - Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. - * - soft_budget: Optional[float] - Get alerts when user crosses given budget, doesn't block requests. * - model_max_budget: Optional[dict] - Model-specific max budget for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-budgets-to-keys) * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[float] - Model-specific rpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) @@ -16887,7 +16886,6 @@ export interface paths { * - permissions: Optional[dict] - [Not Implemented Yet] User-specific permissions, eg. turning off pii masking. * - metadata: Optional[dict] - Metadata for user, store information for user. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" } * - max_parallel_requests: Optional[int] - Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. - * - soft_budget: Optional[float] - Get alerts when user crosses given budget, doesn't block requests. * - model_max_budget: Optional[dict] - Model-specific max budget for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-budgets-to-keys) * - budget_fallbacks: Optional[Dict[str, List[str]]] - Per-model fallback chain tried in order when that model's own `model_max_budget` is exceeded, e.g. {"gpt-4o": ["gpt-4o-mini"]}. * - model_rpm_limit: Optional[float] - Model-specific rpm limit for user. [Docs](https://docs.litellm.ai/docs/proxy/users#add-model-specific-limits-to-keys) From dce9f6f2760a58ff35b460a224f0817791070c42 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Fri, 25 Sep 2026 15:06:19 +0300 Subject: [PATCH 18/19] \fix(lint): narrow reasoning to dict before copy to satisfy basedpyright budget" --- litellm/llms/openai/responses/transformation.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index 26179aa1140..ec91be473aa 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -256,12 +256,13 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): if unsupported_effort: if should_drop_effort: - updated_reasoning: Final = dict(reasoning) # mutable-ok: local copy - updated_reasoning.pop("effort", None) - if updated_reasoning: - params["reasoning"] = updated_reasoning - else: - params.pop("reasoning", None) + if isinstance(reasoning, dict): + updated_reasoning: Final = reasoning.copy() # mutable-ok: local copy + updated_reasoning.pop("effort", None) + if updated_reasoning: + params["reasoning"] = updated_reasoning + else: + params.pop("reasoning", None) else: raise litellm.UnsupportedParamsError( message=f"reasoning.effort={effort} is not supported for this model.", From b9ffdf819ef727c6b77d9e09142e612782753f20 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Sat, 26 Sep 2026 17:41:04 +0300 Subject: [PATCH 19/19] fix(responses): remove unused suppression --- litellm/llms/openai/responses/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index ec91be473aa..0d523507a2a 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -257,7 +257,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): if unsupported_effort: if should_drop_effort: if isinstance(reasoning, dict): - updated_reasoning: Final = reasoning.copy() # mutable-ok: local copy + updated_reasoning: Final = reasoning.copy() updated_reasoning.pop("effort", None) if updated_reasoning: params["reasoning"] = updated_reasoning