mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
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:
parent
df9a644e37
commit
a037414985
5 changed files with 264 additions and 1 deletions
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
0
tests/litellm/llms/deepseek/__init__.py
Normal file
0
tests/litellm/llms/deepseek/__init__.py
Normal file
0
tests/litellm/llms/deepseek/chat/__init__.py
Normal file
0
tests/litellm/llms/deepseek/chat/__init__.py
Normal 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
|
||||
Loading…
Add table
Reference in a new issue