From bf9e8ea9ef7eff06cbc58e143e5ba1de376b81c9 Mon Sep 17 00:00:00 2001 From: shivam Date: Fri, 11 Sep 2026 23:19:17 +0000 Subject: [PATCH 1/4] fix(bedrock_mantle): gate reasoning.summary on the OpenAI Responses path Mantle's /openai/v1/responses rejects reasoning.summary values other than "auto" with 400 unsupported_parameter. Drop it with a warning under drop_params, otherwise raise UnsupportedParamsError naming the remedy. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../responses/transformation.py | 42 ++++++++++- ...bedrock_mantle_responses_transformation.py | 75 +++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py index bbbda4d14b6..98a0d4dbc90 100644 --- a/litellm/llms/bedrock_mantle/responses/transformation.py +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -56,6 +56,7 @@ _BEDROCK_MANTLE_SUPPORTED_RESPONSE_TOOL_TYPES: Final = frozenset( ) _BEDROCK_MANTLE_SUPPORTED_SERVICE_TIERS: Final = frozenset({"auto", "default"}) +_BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES: Final = frozenset({"auto"}) _CODEX_ADDITIONAL_TOOLS_INPUT_ITEM_TYPE: Final = "additional_tools" @@ -187,6 +188,35 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI ) return {key: value for key, value in params.items() if key != "service_tier"} + def _handle_unsupported_reasoning_summary(self, params: dict, model: str, drop_params: bool) -> dict: + reasoning: Final = params.get("reasoning") + if not self.use_openai_path or not isinstance(reasoning, dict): + return params + summary: Final = reasoning.get("summary") + if summary is None or summary in _BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES: + return params + if not drop_params: + raise litellm.utils.UnsupportedParamsError( + status_code=400, + message=( + f"bedrock_mantle does not support reasoning.summary={summary!r} for {model!r}; the Bedrock Mantle " + "OpenAI Responses path only accepts 'auto'. Set `drop_params: true` (litellm_settings or this " + 'deployment\'s litellm_params) to have LiteLLM drop it, or set `model_reasoning_summary = "auto"` ' + "in the client (Codex CLI: ~/.codex/config.toml)." + ), + ) + verbose_logger.warning( + "Bedrock Mantle Responses API: dropping unsupported reasoning.summary %r (supported: %s).", + summary, + sorted(_BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES), + ) + stripped: Final = {key: value for key, value in reasoning.items() if key != "summary"} + return ( + {**params, "reasoning": stripped} + if stripped + else {key: value for key, value in params.items() if key != "reasoning"} + ) + def transform_responses_api_request( self, model: str, @@ -343,12 +373,16 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI model: str, drop_params: bool, ) -> dict: - params: Final = self._handle_unsupported_service_tier( - super().map_openai_params( - response_api_optional_params=response_api_optional_params, - model=model, + params: Final = self._handle_unsupported_reasoning_summary( + self._handle_unsupported_service_tier( + super().map_openai_params( + response_api_optional_params=response_api_optional_params, + model=model, + drop_params=drop_params, + ), drop_params=drop_params, ), + model=model, drop_params=drop_params, ) diff --git a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py index 9457d5faaff..45f427defcd 100644 --- a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py +++ b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py @@ -539,6 +539,81 @@ class TestBedrockMantleServiceTier: assert "priority" in str(mock_warning.call_args) +class TestBedrockMantleReasoningSummary: + @pytest.mark.parametrize("summary", ["concise", "detailed"]) + def test_unsupported_reasoning_summary_dropped_when_drop_params_true(self, summary): + cfg = BedrockMantleResponsesAPIConfig() + params = cfg.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium", "summary": summary}}, + model="openai.gpt-5.6-sol", + drop_params=True, + ) + assert params["reasoning"] == {"effort": "medium"} + + def test_reasoning_summary_only_field_drops_reasoning(self): + cfg = BedrockMantleResponsesAPIConfig() + params = cfg.map_openai_params( + response_api_optional_params={"reasoning": {"summary": "detailed"}}, + model="openai.gpt-5.6-sol", + drop_params=True, + ) + assert "reasoning" not in params + + @pytest.mark.parametrize("summary", ["concise", "detailed"]) + def test_unsupported_reasoning_summary_raises_when_drop_params_false(self, summary): + cfg = BedrockMantleResponsesAPIConfig() + with pytest.raises(litellm.UnsupportedParamsError) as excinfo: + cfg.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium", "summary": summary}}, + model="openai.gpt-5.6-sol", + drop_params=False, + ) + assert summary in str(excinfo.value) + assert "reasoning.summary" in str(excinfo.value) + assert "drop_params" in str(excinfo.value) + + @pytest.mark.parametrize("drop_params", [True, False]) + def test_supported_reasoning_summary_kept(self, drop_params): + cfg = BedrockMantleResponsesAPIConfig() + params = cfg.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium", "summary": "auto"}}, + model="openai.gpt-5.6-sol", + drop_params=drop_params, + ) + assert params["reasoning"] == {"effort": "medium", "summary": "auto"} + + def test_reasoning_summary_kept_on_standard_path(self): + cfg = BedrockMantleResponsesAPIConfig(use_openai_path=False) + params = cfg.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium", "summary": "detailed"}}, + model="openai.gpt-oss-120b", + drop_params=False, + ) + assert params["reasoning"] == {"effort": "medium", "summary": "detailed"} + + def test_absent_reasoning_untouched(self): + cfg = BedrockMantleResponsesAPIConfig() + params = cfg.map_openai_params( + response_api_optional_params={"stream": True}, + model="openai.gpt-5.6-sol", + drop_params=False, + ) + assert params == {"stream": True} + + def test_drop_logged_at_warning_level(self): + from unittest.mock import patch + + cfg = BedrockMantleResponsesAPIConfig() + with patch("litellm.llms.bedrock_mantle.responses.transformation.verbose_logger.warning") as mock_warning: + cfg.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium", "summary": "detailed"}}, + model="openai.gpt-5.6-sol", + drop_params=True, + ) + assert mock_warning.call_count == 1 + assert "detailed" in str(mock_warning.call_args) + + class TestBedrockMantleCodexRequestEndToEnd: def test_codex_priority_tier_request_becomes_mantle_acceptable(self): cfg = BedrockMantleResponsesAPIConfig() From 37a1b859d7327b1d895e0640d0862e04f07c727c Mon Sep 17 00:00:00 2001 From: shivam Date: Fri, 11 Sep 2026 23:34:14 +0000 Subject: [PATCH 2/4] fix(bedrock_mantle): harden reasoning summary validation Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../responses/transformation.py | 18 +++++++++++----- ...bedrock_mantle_responses_transformation.py | 21 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py index 98a0d4dbc90..c43b03b2ba9 100644 --- a/litellm/llms/bedrock_mantle/responses/transformation.py +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -188,12 +188,16 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI ) return {key: value for key, value in params.items() if key != "service_tier"} - def _handle_unsupported_reasoning_summary(self, params: dict, model: str, drop_params: bool) -> dict: + def _handle_unsupported_reasoning_summary( + self, params: dict[str, object], model: str, drop_params: bool + ) -> dict[str, object]: reasoning: Final = params.get("reasoning") if not self.use_openai_path or not isinstance(reasoning, dict): return params summary: Final = reasoning.get("summary") - if summary is None or summary in _BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES: + if summary is None or ( + isinstance(summary, str) and summary in _BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES + ): return params if not drop_params: raise litellm.utils.UnsupportedParamsError( @@ -210,11 +214,15 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI summary, sorted(_BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES), ) - stripped: Final = {key: value for key, value in reasoning.items() if key != "summary"} + stripped: Final = { # mutable-ok: dynamic reasoning fields must remain a mutable dict + key: value for key, value in reasoning.items() if key != "summary" + } return ( - {**params, "reasoning": stripped} + {**params, "reasoning": stripped} # mutable-ok: downstream parameter filtering needs a mutable dict if stripped - else {key: value for key, value in params.items() if key != "reasoning"} + else { # mutable-ok: downstream parameter filtering needs a mutable dict + key: value for key, value in params.items() if key != "reasoning" + } ) def transform_responses_api_request( diff --git a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py index 45f427defcd..40566261c84 100644 --- a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py +++ b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_responses_transformation.py @@ -572,6 +572,16 @@ class TestBedrockMantleReasoningSummary: assert "reasoning.summary" in str(excinfo.value) assert "drop_params" in str(excinfo.value) + def test_unhashable_reasoning_summary_raises_unsupported_params_error(self): + cfg = BedrockMantleResponsesAPIConfig() + with pytest.raises(litellm.UnsupportedParamsError) as excinfo: + cfg.map_openai_params( + response_api_optional_params={"reasoning": {"summary": ["detailed"]}}, + model="openai.gpt-5.6-sol", + drop_params=False, + ) + assert "reasoning.summary" in str(excinfo.value) + @pytest.mark.parametrize("drop_params", [True, False]) def test_supported_reasoning_summary_kept(self, drop_params): cfg = BedrockMantleResponsesAPIConfig() @@ -600,18 +610,17 @@ class TestBedrockMantleReasoningSummary: ) assert params == {"stream": True} - def test_drop_logged_at_warning_level(self): - from unittest.mock import patch - + def test_drop_logged_at_warning_level(self, caplog): cfg = BedrockMantleResponsesAPIConfig() - with patch("litellm.llms.bedrock_mantle.responses.transformation.verbose_logger.warning") as mock_warning: + with caplog.at_level(logging.WARNING, logger="LiteLLM"): cfg.map_openai_params( response_api_optional_params={"reasoning": {"effort": "medium", "summary": "detailed"}}, model="openai.gpt-5.6-sol", drop_params=True, ) - assert mock_warning.call_count == 1 - assert "detailed" in str(mock_warning.call_args) + warnings = [record for record in caplog.records if "dropping unsupported reasoning.summary" in record.getMessage()] + assert len(warnings) == 1 + assert "detailed" in warnings[0].getMessage() class TestBedrockMantleCodexRequestEndToEnd: From eb2ce87cb391350692c9f96b8e09a6032cd40ebd Mon Sep 17 00:00:00 2001 From: shivam Date: Fri, 11 Sep 2026 23:45:43 +0000 Subject: [PATCH 3/4] refactor(bedrock_mantle): drop unneeded mutable-ok suppressions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/bedrock_mantle/responses/transformation.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py index c43b03b2ba9..4710577aea0 100644 --- a/litellm/llms/bedrock_mantle/responses/transformation.py +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -214,15 +214,11 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI summary, sorted(_BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES), ) - stripped: Final = { # mutable-ok: dynamic reasoning fields must remain a mutable dict - key: value for key, value in reasoning.items() if key != "summary" - } + stripped: Final = {key: value for key, value in reasoning.items() if key != "summary"} return ( - {**params, "reasoning": stripped} # mutable-ok: downstream parameter filtering needs a mutable dict + {**params, "reasoning": stripped} if stripped - else { # mutable-ok: downstream parameter filtering needs a mutable dict - key: value for key, value in params.items() if key != "reasoning" - } + else {key: value for key, value in params.items() if key != "reasoning"} ) def transform_responses_api_request( From f5c298a816930af46b023561bed27ba041b3c48b Mon Sep 17 00:00:00 2001 From: shivam Date: Fri, 11 Sep 2026 23:47:00 +0000 Subject: [PATCH 4/4] fix(bedrock_mantle): satisfy LIT002 gate for reasoning summary handler Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/bedrock_mantle/responses/transformation.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py index 4710577aea0..53a3e634adf 100644 --- a/litellm/llms/bedrock_mantle/responses/transformation.py +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -214,11 +214,15 @@ class BedrockMantleResponsesAPIConfig(BedrockMantleAuthMixin, OpenAIResponsesAPI summary, sorted(_BEDROCK_MANTLE_OPENAI_PATH_SUPPORTED_REASONING_SUMMARIES), ) - stripped: Final = {key: value for key, value in reasoning.items() if key != "summary"} + stripped: Final = { # mutable-ok: map_openai_params contract returns a plain dict + key: value for key, value in reasoning.items() if key != "summary" + } return ( - {**params, "reasoning": stripped} + {**params, "reasoning": stripped} # mutable-ok: map_openai_params contract returns a plain dict if stripped - else {key: value for key, value in params.items() if key != "reasoning"} + else { # mutable-ok: map_openai_params contract returns a plain dict + key: value for key, value in params.items() if key != "reasoning" + } ) def transform_responses_api_request(