feat(deepseek): add native support for thinking and reasoning_effort params (#17712)

* feat(deepseek): add native support for thinking and reasoning_effort params

Add proper parameter mapping for DeepSeek thinking mode, allowing users
to use the unified LiteLLM interface instead of extra_body workarounds.

Supported formats:
- thinking={"type": "enabled"}
- thinking={"type": "enabled", "budget_tokens": X} (budget_tokens ignored)
- reasoning_effort="low|medium|high" (maps to thinking enabled)

DeepSeek only supports {"type": "enabled"} without budget_tokens,
so any budget_tokens are stripped and all reasoning_effort values
(except "none") map to enabled.

Reference: https://api-docs.deepseek.com/guides/thinking_mode

* docs(deepseek): add thinking and reasoning_effort parameter documentation
This commit is contained in:
Cesar Garcia 2025-12-11 20:28:43 -03:00 • committed by GitHub
parent df9a644e37
commit a037414985
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 264 additions and 1 deletions

View file

@ -58,9 +58,56 @@ We support ALL Deepseek models, just set `deepseek/` as a prefix when sending co
## Reasoning Models
| Model Name | Function Call |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| deepseek-reasoner | `completion(model="deepseek/deepseek-reasoner", messages)` |
| deepseek-reasoner | `completion(model="deepseek/deepseek-reasoner", messages)` |
### Thinking / Reasoning Mode
Enable thinking mode for DeepSeek reasoner models using `thinking` or `reasoning_effort` parameters:
<Tabs>
<TabItem value="thinking" label="thinking param">
```python
from litellm import completion
import os
os.environ['DEEPSEEK_API_KEY'] = ""
resp = completion(
model="deepseek/deepseek-reasoner",
messages=[{"role": "user", "content": "What is 2+2?"}],
thinking={"type": "enabled"},
)
print(resp.choices[0].message.reasoning_content) # Model's reasoning
print(resp.choices[0].message.content) # Final answer
```
</TabItem>
<TabItem value="reasoning_effort" label="reasoning_effort param">
```python
from litellm import completion
import os
os.environ['DEEPSEEK_API_KEY'] = ""
resp = completion(
model="deepseek/deepseek-reasoner",
messages=[{"role": "user", "content": "What is 2+2?"}],
reasoning_effort="medium", # low, medium, high all map to thinking enabled
)
print(resp.choices[0].message.reasoning_content) # Model's reasoning
print(resp.choices[0].message.content) # Final answer
```
</TabItem>
</Tabs>
:::note
DeepSeek only supports `{"type": "enabled"}` - unlike Anthropic, it doesn't support `budget_tokens`. Any `reasoning_effort` value other than `"none"` enables thinking mode.
:::
### Basic Usage
<Tabs>
<TabItem value="sdk" label="SDK">

View file

@ -14,6 +14,54 @@ from ...openai.chat.gpt_transformation import OpenAIGPTConfig
class DeepSeekChatConfig(OpenAIGPTConfig):
def get_supported_openai_params(self, model: str) -> list:
"""
DeepSeek reasoner models support thinking parameter.
"""
params = super().get_supported_openai_params(model)
params.extend(["thinking", "reasoning_effort"])
return params
def map_openai_params(
self,
non_default_params: dict,
optional_params: dict,
model: str,
drop_params: bool,
) -> dict:
"""
Map OpenAI params to DeepSeek params.
Handles `thinking` and `reasoning_effort` parameters for DeepSeek reasoner models.
DeepSeek only supports `{"type": "enabled"}` - no budget_tokens like Anthropic.
Reference: https://api-docs.deepseek.com/guides/thinking_mode
"""
# Let parent handle standard params first
optional_params = super().map_openai_params(
non_default_params, optional_params, model, drop_params
)
# Pop thinking/reasoning_effort from optional_params first (parent may have added them)
# Then re-add only if valid for DeepSeek
thinking_value = optional_params.pop("thinking", None)
reasoning_effort = optional_params.pop("reasoning_effort", None)
# Handle thinking parameter - only accept {"type": "enabled"}
if thinking_value is not None:
if (
isinstance(thinking_value, dict)
and thinking_value.get("type") == "enabled"
):
# DeepSeek only accepts {"type": "enabled"}, ignore budget_tokens
optional_params["thinking"] = {"type": "enabled"}
# Handle reasoning_effort - map to thinking enabled
elif reasoning_effort is not None and reasoning_effort != "none":
optional_params["thinking"] = {"type": "enabled"}
return optional_params
@overload
def _transform_messages(
self, messages: List[AllMessageValues], model: str, is_async: Literal[True]

View file

View file

@ -0,0 +1,168 @@
"""
Unit tests for DeepSeek chat transformation.
Tests the thinking and reasoning_effort parameter handling for DeepSeek models.
"""
import pytest
from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig
class TestDeepSeekThinkingParams:
"""Test thinking and reasoning_effort parameter handling for DeepSeek."""
def setup_method(self):
self.config = DeepSeekChatConfig()
self.model = "deepseek-reasoner"
def test_get_supported_openai_params_includes_thinking(self):
"""Test that thinking and reasoning_effort are in supported params."""
params = self.config.get_supported_openai_params(self.model)
assert "thinking" in params
assert "reasoning_effort" in params
def test_map_thinking_enabled(self):
"""Test that thinking={"type": "enabled"} is passed through correctly."""
non_default_params = {"thinking": {"type": "enabled"}}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert result["thinking"] == {"type": "enabled"}
def test_map_thinking_with_budget_tokens_strips_budget(self):
"""Test that budget_tokens is stripped from thinking param (DeepSeek doesn't support it)."""
non_default_params = {"thinking": {"type": "enabled", "budget_tokens": 2048}}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
# Should strip budget_tokens, only pass type
assert result["thinking"] == {"type": "enabled"}
assert "budget_tokens" not in result.get("thinking", {})
def test_map_reasoning_effort_medium(self):
"""Test that reasoning_effort='medium' maps to thinking enabled."""
non_default_params = {"reasoning_effort": "medium"}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert result["thinking"] == {"type": "enabled"}
def test_map_reasoning_effort_low(self):
"""Test that reasoning_effort='low' maps to thinking enabled."""
non_default_params = {"reasoning_effort": "low"}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert result["thinking"] == {"type": "enabled"}
def test_map_reasoning_effort_high(self):
"""Test that reasoning_effort='high' maps to thinking enabled."""
non_default_params = {"reasoning_effort": "high"}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert result["thinking"] == {"type": "enabled"}
def test_map_reasoning_effort_none_does_not_enable_thinking(self):
"""Test that reasoning_effort='none' does not enable thinking."""
non_default_params = {"reasoning_effort": "none"}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert "thinking" not in result
def test_map_reasoning_effort_null_does_not_enable_thinking(self):
"""Test that reasoning_effort=None does not enable thinking."""
non_default_params = {"reasoning_effort": None}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert "thinking" not in result
def test_thinking_takes_precedence_over_reasoning_effort(self):
"""Test that thinking param takes precedence when both are provided."""
non_default_params = {
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
# thinking should be set, reasoning_effort should not override
assert result["thinking"] == {"type": "enabled"}
def test_invalid_thinking_type_ignored(self):
"""Test that invalid thinking type values are ignored."""
non_default_params = {"thinking": {"type": "invalid"}}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert "thinking" not in result
def test_thinking_none_value_ignored(self):
"""Test that thinking=None is ignored."""
non_default_params = {"thinking": None}
optional_params = {}
result = self.config.map_openai_params(
non_default_params=non_default_params,
optional_params=optional_params,
model=self.model,
drop_params=False,
)
assert "thinking" not in result