mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(bedrock): forward raw reasoning_effort to DeepSeek V3, drop it only for R1
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
This commit is contained in:
parent
738896d776
commit
dd707da81c
2 changed files with 103 additions and 38 deletions
|
|
@ -422,13 +422,13 @@ class AmazonConverseConfig(BaseConfig):
|
|||
"""
|
||||
Handle the reasoning_effort parameter based on the model type.
|
||||
|
||||
- GPT-OSS models: passed through unchanged via additionalModelRequestFields.
|
||||
- GPT-OSS and DeepSeek V3 models: passed through unchanged via additionalModelRequestFields.
|
||||
- OpenAI GPT-5.x models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
|
||||
- Nova 2 models: transformed to reasoningConfig.
|
||||
- Anthropic models: mapped to ``thinking`` (and ``output_config.effort`` on
|
||||
adaptive Claude 4.6 / 4.7).
|
||||
"""
|
||||
if "gpt-oss" in model:
|
||||
if "gpt-oss" in model or "deepseek" in model:
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
elif "openai.gpt-5" in model:
|
||||
reasoning: Final[BedrockConverseGptReasoningEffortBlock] = {"effort": reasoning_effort}
|
||||
|
|
@ -509,14 +509,19 @@ class AmazonConverseConfig(BaseConfig):
|
|||
)
|
||||
thinking["budget_tokens"] = BEDROCK_MIN_THINKING_BUDGET_TOKENS
|
||||
|
||||
def _model_accepts_anthropic_thinking_param(self, model: str, base_model: str) -> bool:
|
||||
"""Whether the model accepts the Anthropic-shaped ``thinking`` / ``reasoning_effort`` request field.
|
||||
def _is_deepseek_model(self, model: str, base_model: str) -> bool:
|
||||
return "deepseek" in model or "deepseek" in base_model
|
||||
|
||||
The Converse mapping serializes ``thinking`` into ``additionalModelRequestFields`` in Anthropic's
|
||||
shape. Only Anthropic Claude reasoning models accept that field; DeepSeek models reason natively
|
||||
and reject it (a 400 when it leaks through), even though they advertise ``supports_reasoning``.
|
||||
def _is_deepseek_r1_model(self, model: str, base_model: str) -> bool:
|
||||
return "deepseek.r1" in model or "deepseek.r1" in base_model
|
||||
|
||||
def _model_accepts_anthropic_thinking_param(self, model: str, base_model: str) -> bool:
|
||||
"""Whether the model accepts the Anthropic-shaped ``thinking`` request field.
|
||||
|
||||
Only Claude reasoning models accept it. DeepSeek advertises ``supports_reasoning`` but reasons
|
||||
natively: R1 returns a 400 when the field is sent and V3 silently ignores it.
|
||||
"""
|
||||
if "deepseek" in model or "deepseek" in base_model:
|
||||
if self._is_deepseek_model(model=model, base_model=base_model):
|
||||
return False
|
||||
return (
|
||||
"claude-3-7" in model
|
||||
|
|
@ -526,17 +531,13 @@ 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.
|
||||
def _model_rejects_reasoning_effort_param(self, model: str, base_model: str) -> bool:
|
||||
"""Whether the model returns a 400 for every ``reasoning_effort`` shape 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.
|
||||
DeepSeek R1 always reasons and rejects any reasoning request field. DeepSeek V3 accepts a raw
|
||||
``reasoning_effort`` like gpt-oss does, and every other model maps it to a shape it accepts.
|
||||
"""
|
||||
return "deepseek" in model or "deepseek" in base_model
|
||||
return self._is_deepseek_r1_model(model=model, base_model=base_model)
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> list[str]:
|
||||
from litellm.utils import supports_function_calling
|
||||
|
|
@ -595,6 +596,9 @@ class AmazonConverseConfig(BaseConfig):
|
|||
|
||||
if "gpt-oss" in model or "openai.gpt-5" in model or "openai.gpt-5" in base_model:
|
||||
supported_params.append("reasoning_effort")
|
||||
elif self._is_deepseek_model(model=model, base_model=base_model):
|
||||
if not self._is_deepseek_r1_model(model=model, base_model=base_model):
|
||||
supported_params.append("reasoning_effort")
|
||||
elif self._is_nova_2_model(model):
|
||||
# Nova 2 models support reasoning_effort (transformed to reasoningConfig)
|
||||
# These models use a different reasoning structure than Anthropic's thinking parameter
|
||||
|
|
@ -891,8 +895,10 @@ class AmazonConverseConfig(BaseConfig):
|
|||
drop_params: bool,
|
||||
) -> dict:
|
||||
is_thinking_enabled: Final = self.is_thinking_enabled(non_default_params)
|
||||
drop_reasoning_request_param: Final = self._model_reasons_natively_and_rejects_request_param(
|
||||
model=model, base_model=BedrockModelInfo.get_base_model(model)
|
||||
base_model: Final = BedrockModelInfo.get_base_model(model)
|
||||
drop_thinking_param: Final = self._is_deepseek_model(model=model, base_model=base_model)
|
||||
drop_reasoning_effort_param: Final = self._model_rejects_reasoning_effort_param(
|
||||
model=model, base_model=base_model
|
||||
)
|
||||
|
||||
for param, value in non_default_params.items():
|
||||
|
|
@ -942,9 +948,9 @@ class AmazonConverseConfig(BaseConfig):
|
|||
optional_params["_parallel_tool_use_config"] = {
|
||||
"tool_choice": {"type": "auto", "disable_parallel_tool_use": not value}
|
||||
}
|
||||
if param == "thinking" and drop_reasoning_request_param:
|
||||
if param == "thinking" and drop_thinking_param:
|
||||
verbose_logger.debug(
|
||||
"Dropping unsupported `thinking` param for Bedrock model=%s; it reasons natively and rejects it.",
|
||||
"Dropping unsupported `thinking` param for Bedrock model=%s; it reasons natively.",
|
||||
model,
|
||||
)
|
||||
elif param == "thinking" and "openai.gpt-5" not in model:
|
||||
|
|
@ -973,9 +979,9 @@ class AmazonConverseConfig(BaseConfig):
|
|||
AnthropicModelInfo.translate_legacy_thinking_for_adaptive_model(
|
||||
model=model, optional_params=optional_params, custom_llm_provider="bedrock"
|
||||
)
|
||||
elif param == "reasoning_effort" and isinstance(value, str) and drop_reasoning_request_param:
|
||||
elif param == "reasoning_effort" and isinstance(value, str) and drop_reasoning_effort_param:
|
||||
verbose_logger.debug(
|
||||
"Dropping unsupported `reasoning_effort` param for Bedrock model=%s; it reasons natively and rejects it.",
|
||||
"Dropping unsupported `reasoning_effort` param for Bedrock model=%s; it always reasons and rejects it.",
|
||||
model,
|
||||
)
|
||||
elif param == "reasoning_effort" and isinstance(value, str):
|
||||
|
|
|
|||
|
|
@ -873,31 +873,55 @@ def test_get_supported_openai_params():
|
|||
],
|
||||
)
|
||||
def test_bedrock_deepseek_does_not_advertise_thinking(model):
|
||||
"""DeepSeek reasons natively on Bedrock and rejects the Anthropic-shaped
|
||||
`thinking`/`reasoning_effort` field, so it must not be advertised as supported
|
||||
(otherwise it leaks into additionalModelRequestFields and Bedrock 400s)."""
|
||||
"""DeepSeek reasons natively on Bedrock and does not take the Anthropic-shaped `thinking`
|
||||
field (R1 400s on it, V3 ignores it), so it must not be advertised as supported."""
|
||||
config = AmazonConverseConfig()
|
||||
supported_params = config.get_supported_openai_params(model=model)
|
||||
assert "thinking" not in supported_params
|
||||
assert "reasoning_effort" not in supported_params
|
||||
assert "output_config" not in supported_params
|
||||
|
||||
|
||||
def test_bedrock_deepseek_r1_thinking_raises_without_drop_params():
|
||||
"""Passing `thinking` to Bedrock DeepSeek R1 must fail client-side with a clear
|
||||
UnsupportedParamsError instead of leaking through and hitting a Bedrock 400."""
|
||||
@pytest.mark.parametrize("model", ["bedrock/us.deepseek.r1-v1:0", "bedrock/converse/us.deepseek.r1-v1:0"])
|
||||
def test_bedrock_deepseek_r1_does_not_advertise_reasoning_effort(model):
|
||||
"""DeepSeek R1 always reasons and returns a 400 for any reasoning_effort shape."""
|
||||
config = AmazonConverseConfig()
|
||||
assert "reasoning_effort" not in config.get_supported_openai_params(model=model)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["bedrock/deepseek.v3-v1:0", "bedrock/deepseek.v3.2", "bedrock/us.deepseek.v3.2"])
|
||||
def test_bedrock_deepseek_v3_advertises_reasoning_effort(model):
|
||||
"""DeepSeek V3 on Bedrock accepts a raw reasoning_effort in additionalModelRequestFields."""
|
||||
config = AmazonConverseConfig()
|
||||
assert "reasoning_effort" in config.get_supported_openai_params(model=model)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["us.deepseek.r1-v1:0", "deepseek.v3.2"])
|
||||
def test_bedrock_deepseek_thinking_raises_without_drop_params(model):
|
||||
"""Passing `thinking` to Bedrock DeepSeek must fail client-side with a clear
|
||||
UnsupportedParamsError instead of leaking through to Bedrock."""
|
||||
with pytest.raises(litellm.UnsupportedParamsError):
|
||||
litellm.utils.get_optional_params(
|
||||
model="us.deepseek.r1-v1:0",
|
||||
model=model,
|
||||
custom_llm_provider="bedrock",
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
)
|
||||
|
||||
|
||||
def test_bedrock_deepseek_r1_thinking_dropped_does_not_leak_into_request():
|
||||
def test_bedrock_deepseek_r1_reasoning_effort_raises_without_drop_params():
|
||||
with pytest.raises(litellm.UnsupportedParamsError):
|
||||
litellm.utils.get_optional_params(
|
||||
model="us.deepseek.r1-v1:0",
|
||||
custom_llm_provider="bedrock",
|
||||
reasoning_effort="high",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["us.deepseek.r1-v1:0", "deepseek.v3.2"])
|
||||
def test_bedrock_deepseek_thinking_dropped_does_not_leak_into_request(model):
|
||||
"""With drop_params, `thinking` is dropped rather than forwarded into
|
||||
additionalModelRequestFields for Bedrock DeepSeek R1."""
|
||||
additionalModelRequestFields for Bedrock DeepSeek."""
|
||||
optional_params = litellm.utils.get_optional_params(
|
||||
model="us.deepseek.r1-v1:0",
|
||||
model=model,
|
||||
custom_llm_provider="bedrock",
|
||||
thinking={"type": "enabled", "budget_tokens": 1024},
|
||||
drop_params=True,
|
||||
|
|
@ -906,20 +930,20 @@ def test_bedrock_deepseek_r1_thinking_dropped_does_not_leak_into_request():
|
|||
|
||||
config = AmazonConverseConfig()
|
||||
request = config._transform_request(
|
||||
model="bedrock/converse/us.deepseek.r1-v1:0",
|
||||
model=f"bedrock/converse/{model}",
|
||||
messages=[{"role": "user", "content": "Say hi in one word."}],
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert "thinking" not in request.get("additionalModelRequestFields", {})
|
||||
assert "thinking" not in (request.get("additionalModelRequestFields") or {})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("param", ["thinking", "reasoning_effort"])
|
||||
def test_bedrock_deepseek_r1_reasoning_params_not_forwarded_by_map(param):
|
||||
"""Even when map_openai_params is called directly (bypassing the supported-params
|
||||
gate), DeepSeek R1 must not forward the Anthropic-shaped thinking/reasoning_effort
|
||||
into additionalModelRequestFields, since Bedrock rejects it with a 400."""
|
||||
gate), DeepSeek R1 must not forward thinking/reasoning_effort into
|
||||
additionalModelRequestFields, since Bedrock rejects both with a 400."""
|
||||
config = AmazonConverseConfig()
|
||||
model = "bedrock/converse/us.deepseek.r1-v1:0"
|
||||
value = {"type": "enabled", "budget_tokens": 1024} if param == "thinking" else "high"
|
||||
|
|
@ -943,6 +967,41 @@ def test_bedrock_deepseek_r1_reasoning_params_not_forwarded_by_map(param):
|
|||
assert request.get("additionalModelRequestFields") is None
|
||||
|
||||
|
||||
def test_bedrock_deepseek_v3_reasoning_effort_forwarded_raw():
|
||||
"""DeepSeek V3 takes reasoning_effort verbatim in additionalModelRequestFields, never
|
||||
converted into the Anthropic `thinking` block that Claude models get."""
|
||||
config = AmazonConverseConfig()
|
||||
model = "bedrock/deepseek.v3.2"
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "high", "max_tokens": 100},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
|
||||
request = config._transform_request(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "Say hi in one word."}],
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert request["additionalModelRequestFields"] == {"reasoning_effort": "high"}
|
||||
|
||||
|
||||
def test_bedrock_deepseek_v3_thinking_dropped_by_map():
|
||||
config = AmazonConverseConfig()
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 1024}, "max_tokens": 100},
|
||||
optional_params={},
|
||||
model="bedrock/deepseek.v3.2",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert "reasoning_effort" not in optional_params
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model, param, value, kept_key",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue