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