From ee779d4259b19bb10f526eede7e51fc599307b65 Mon Sep 17 00:00:00 2001 From: NasonZ Date: Fri, 11 Sep 2026 12:56:55 +0100 Subject: [PATCH] fix(deepseek): forward graded reasoning_effort instead of discarding it map_openai_params collapsed every non-"none" reasoning_effort into a bare thinking: {"type": "enabled"} and dropped the level, so low, high and max were all indistinguishable from the default and callers could not ask for reduced reasoning at all DeepSeek accepts reasoning_effort directly in thinking mode and grades it server-side, so the value is forwarded rather than remapped here Fixes #27439 --- litellm/llms/deepseek/chat/transformation.py | 8 +++- .../chat/test_deepseek_chat_transformation.py | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index ea19a7c7ddf..ea0693512de 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -39,7 +39,8 @@ class DeepSeekChatConfig(OpenAIGPTConfig): Handles `thinking` and `reasoning_effort` parameters for DeepSeek reasoner models. DeepSeek supports `{"type": "enabled"}` and `{"type": "disabled"}` - no budget_tokens like Anthropic. `reasoning_effort="none"` is the OpenAI-style way to ask for thinking - off, so it maps to `{"type": "disabled"}`; any other effort keeps thinking on. + off, so it maps to `{"type": "disabled"}`; any other effort keeps thinking on and + is forwarded, since DeepSeek grades effort itself server-side. Reference: https://api-docs.deepseek.com/guides/thinking_mode """ @@ -57,7 +58,10 @@ class DeepSeekChatConfig(OpenAIGPTConfig): # Otherwise fall back to reasoning_effort: "none" disables, anything else enables elif reasoning_effort is not None: - optional_params["thinking"] = {"type": "disabled" if reasoning_effort == "none" else "enabled"} + thinking_enabled: Final = reasoning_effort != "none" + optional_params["thinking"] = {"type": "enabled" if thinking_enabled else "disabled"} + if thinking_enabled: + optional_params["reasoning_effort"] = reasoning_effort return optional_params diff --git a/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py b/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py index 3f93264d0d0..702385a277e 100644 --- a/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py +++ b/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -443,6 +443,7 @@ class TestDeepSeekThinkingParams: ) assert result["thinking"] == {"type": "enabled"} + assert result["reasoning_effort"] == "medium" def test_map_reasoning_effort_low(self): """Test that reasoning_effort='low' maps to thinking enabled.""" @@ -457,6 +458,7 @@ class TestDeepSeekThinkingParams: ) assert result["thinking"] == {"type": "enabled"} + assert result["reasoning_effort"] == "low" def test_map_reasoning_effort_high(self): """Test that reasoning_effort='high' maps to thinking enabled.""" @@ -471,6 +473,43 @@ class TestDeepSeekThinkingParams: ) assert result["thinking"] == {"type": "enabled"} + assert result["reasoning_effort"] == "high" + + def test_map_graded_effort_is_forwarded_not_collapsed(self): + """Every effort value reaches DeepSeek, which grades it server-side.""" + for effort in ("minimal", "low", "medium", "default", "high", "xhigh", "max", "ultra"): + result = self.config.map_openai_params( + non_default_params={"reasoning_effort": effort}, + optional_params={}, + model=self.model, + drop_params=False, + ) + assert result["thinking"] == {"type": "enabled"} + assert result["reasoning_effort"] == effort + + def test_explicit_thinking_is_honoured_exactly_as_given(self): + """An explicit toggle decides thinking, and no effort is merged into it.""" + result = self.config.map_openai_params( + non_default_params={"thinking": {"type": "enabled"}, "reasoning_effort": "low"}, + optional_params={}, + model=self.model, + drop_params=False, + ) + assert result["thinking"] == {"type": "enabled"} + assert "reasoning_effort" not in result + + def test_disabled_thinking_carries_no_effort(self): + """Nothing is thinking, so an effort value would be meaningless.""" + for params in ({"reasoning_effort": "none"}, + {"thinking": {"type": "disabled"}}): + result = self.config.map_openai_params( + non_default_params=params, + optional_params={}, + model=self.model, + drop_params=False, + ) + assert result["thinking"] == {"type": "disabled"} + assert "reasoning_effort" not in result def test_map_reasoning_effort_none_does_not_enable_thinking(self): """Test that reasoning_effort='none' does not enable thinking."""