From ab1f739188cf4096ae7882a6b7848e702480ed02 Mon Sep 17 00:00:00 2001 From: mateo Date: Thu, 23 Jul 2026 03:29:45 +0000 Subject: [PATCH] fix(bedrock): only drop reasoning params for DeepSeek, not all non-allowlisted models The DeepSeek leak fix gated the drop of thinking/reasoning_effort on a positive allowlist (_model_accepts_anthropic_thinking_param). Its negation dropped the params for any model the allowlist and ARN introspection missed, so Claude behind application-inference-profile ARNs, gpt-oss-safeguard (absent from the cost map so supports_reasoning is False), and Nova 2 custom-import ARNs all silently lost reasoning in prod. Gate the drop on a narrow denylist instead: only DeepSeek reasons natively and 400s on the request field, so drop only there and leave every other model (including opaque ARNs and gpt-oss/Nova 2 which route reasoning_effort through their own shapes) untouched. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../bedrock/chat/converse_transformation.py | 18 ++++++-- .../chat/test_converse_transformation.py | 46 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index d7ee15cedd7..617ce423d51 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -510,6 +510,18 @@ class AmazonConverseConfig(BaseConfig): or supports_reasoning(model=base_model, custom_llm_provider=self.custom_llm_provider) ) + def _model_reasons_natively_and_rejects_request_param(self, model: str, base_model: str) -> bool: + """Whether the model reasons natively and rejects any reasoning request field on Converse. + + The Converse mapping serializes ``thinking`` into ``additionalModelRequestFields`` in Anthropic's + shape and ``reasoning_effort`` into a provider-specific shape. DeepSeek reasons on its own and + returns a 400 when either field is sent, even though it advertises ``supports_reasoning``, so both + must be dropped for it. Every other model either accepts one of those shapes (Claude ``thinking``, + gpt-oss / Nova 2 ``reasoning_effort``) or is an opaque ARN we can't introspect, so we leave those + untouched rather than silently degrading reasoning. + """ + return "deepseek" in model or "deepseek" in base_model + def get_supported_openai_params(self, model: str) -> List[str]: from litellm.utils import supports_function_calling @@ -857,7 +869,7 @@ class AmazonConverseConfig(BaseConfig): drop_params: bool, ) -> dict: is_thinking_enabled = self.is_thinking_enabled(non_default_params) - accepts_thinking_param = self._model_accepts_anthropic_thinking_param( + drop_reasoning_request_param = self._model_reasons_natively_and_rejects_request_param( model=model, base_model=BedrockModelInfo.get_base_model(model) ) @@ -909,7 +921,7 @@ class AmazonConverseConfig(BaseConfig): optional_params["_parallel_tool_use_config"] = { "tool_choice": {"disable_parallel_tool_use": disable_parallel} } - if param == "thinking" and not accepts_thinking_param: + if param == "thinking" and drop_reasoning_request_param: verbose_logger.debug( "Dropping unsupported `thinking` param for Bedrock model=%s; it reasons natively and rejects it.", model, @@ -937,7 +949,7 @@ class AmazonConverseConfig(BaseConfig): litellm.verbose_logger.warning(DROP_UNSUPPORTED_ADAPTIVE_THINKING_WARNING, model) else: optional_params["thinking"] = value - elif param == "reasoning_effort" and isinstance(value, str) and not accepts_thinking_param: + elif param == "reasoning_effort" and isinstance(value, str) and drop_reasoning_request_param: verbose_logger.debug( "Dropping unsupported `reasoning_effort` param for Bedrock model=%s; it reasons natively and rejects it.", model, diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index 93455f00143..87a172f2409 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -633,6 +633,52 @@ def test_bedrock_deepseek_r1_reasoning_params_not_forwarded_by_map(param): assert request.get("additionalModelRequestFields") is None +@pytest.mark.parametrize( + "model, param, value, kept_key", + [ + ( + "bedrock/us.anthropic.claude-opus-4-20250514-v1:0", + "thinking", + {"type": "enabled", "budget_tokens": 1024}, + "thinking", + ), + ( + "bedrock/arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123", + "thinking", + {"type": "enabled", "budget_tokens": 1024}, + "thinking", + ), + ( + "bedrock/openai.gpt-oss-safeguard-20b-1:0", + "reasoning_effort", + "high", + "reasoning_effort", + ), + ( + "bedrock/us.amazon.nova-2-lite-v1:0", + "reasoning_effort", + "high", + "reasoningConfig", + ), + ], +) +def test_bedrock_non_deepseek_reasoning_params_preserved(model, param, value, kept_key): + """The DeepSeek leak fix must only drop reasoning request params for DeepSeek. + + Claude behind an application-inference-profile ARN, gpt-oss-safeguard (absent from the + cost map so `supports_reasoning` is False), and Nova 2 all reason via a request param and + must keep it. Regression guard against gating the drop on a positive allowlist, which + silently degraded reasoning for anything the allowlist/ARN introspection missed.""" + config = AmazonConverseConfig() + optional_params = config.map_openai_params( + non_default_params={param: value, "max_tokens": 100}, + optional_params={}, + model=model, + drop_params=False, + ) + assert kept_key in optional_params + + def test_get_supported_openai_params_bedrock_converse(): """ Test that all documented bedrock converse models have the same set of supported openai params when using