refactor(deepseek): drop hardcoded V4 model list, guard on supports_reasoning

Replace the DEFAULT_THINKING_MODEL_PREFIXES name list with the existing
supports_reasoning registry flag (same approach as the Moonshot
reasoning_content fix), so new DeepSeek models only need a cost map entry,
not a code change.

Safety verified against the live DeepSeek API:
- reasoning_content is ignored in non-thinking requests (incl. explicit
  thinking={"type": "disabled"}), so injecting for any reasoning-capable
  model is harmless
- deepseek-v3.2 (the opt-in model the stricter guard protected) is no
  longer served: the API only accepts deepseek-v4-pro / deepseek-v4-flash

thinking={"type": "disabled"} still skips injection entirely.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Milan 2026-06-11 23:04:35 +03:00
parent 2ef49eed60
commit 6aa6d40c5d
No known key found for this signature in database
3 changed files with 42 additions and 36 deletions

View file

@ -136,33 +136,23 @@ class DeepSeekChatConfig(OpenAIGPTConfig):
messages=messages, model=model, is_async=False
)
# Model families where DeepSeek enables thinking mode BY DEFAULT (no
# `thinking` param required). Reference:
# https://api-docs.deepseek.com/guides/thinking_mode
DEFAULT_THINKING_MODEL_PREFIXES = ("deepseek-v4",)
def _is_default_thinking_model(self, model: str) -> bool:
return any(
prefix in model for prefix in self.DEFAULT_THINKING_MODEL_PREFIXES
)
def _thinking_mode_active(self, model: str, optional_params: dict) -> bool:
"""
Returns True when thinking mode is active for this request:
- user explicitly passed thinking={"type": "enabled"} on a model that
supports reasoning, OR
- the model runs in thinking mode by default (DeepSeek V4 family) and
the user did not explicitly disable it.
Returns True when thinking mode may be active for this request.
Models like deepseek-v3.2 (supports_reasoning but opt-in thinking)
remain untouched unless thinking is explicitly enabled.
DeepSeek V4 models enable thinking BY DEFAULT (no `thinking` param
required - https://api-docs.deepseek.com/guides/thinking_mode), so any
reasoning-capable model counts unless the user explicitly disabled
thinking. Same approach as the Moonshot reasoning fix
(litellm/llms/moonshot/chat/transformation.py).
Injecting `reasoning_content` when thinking is NOT active is harmless:
the DeepSeek API ignores the field in non-thinking requests (verified
against the live API, including thinking={"type": "disabled"}).
"""
thinking_type = (optional_params.get("thinking") or {}).get("type")
if thinking_type == "disabled":
if (optional_params.get("thinking") or {}).get("type") == "disabled":
return False
if thinking_type == "enabled":
return supports_reasoning(model=model, custom_llm_provider="deepseek")
return self._is_default_thinking_model(model)
return supports_reasoning(model=model, custom_llm_provider="deepseek")
def transform_request(
self,

View file

@ -220,10 +220,12 @@ class TestDeepSeekV4DefaultThinkingMode:
model=model, optional_params={"thinking": {"type": "enabled"}}
)
def test_opt_in_models_unaffected_by_default(self):
"""deepseek-v3.2 supports reasoning but thinking is opt-in: no thinking
param -> guard must stay off (no spurious injection)."""
assert not self.config._thinking_mode_active(
def test_reasoning_capable_models_active_by_default(self):
"""Any reasoning-capable DeepSeek model counts as potentially
thinking-mode (V4 enables thinking by default). Injection is harmless
when thinking is not actually active: the live API ignores
reasoning_content in non-thinking requests."""
assert self.config._thinking_mode_active(
model="deepseek-v3.2", optional_params={}
)
assert self.config._thinking_mode_active(

View file

@ -233,13 +233,14 @@ def test_deepseek_fill_reasoning_content_multiturn():
def test_deepseek_fill_reasoning_content_guard_in_transform_request():
"""
_fill_reasoning_content must only run when BOTH conditions are true:
1. supports_reasoning() is True for the model
2. thinking mode is explicitly enabled in optional_params ({"type": "enabled"})
_fill_reasoning_content runs for any reasoning-capable DeepSeek model
unless thinking is explicitly disabled.
This prevents spurious injection on models like deepseek-v3.2 that support
thinking as opt-in but not always-on. Addresses oss-pr-review-agent feedback
on PR #28057.
DeepSeek V4 enables thinking mode BY DEFAULT (no `thinking` param
required), so the guard cannot rely on an explicit opt-in (issue #26395).
Injecting reasoning_content when thinking is not actually active is
harmless: the DeepSeek API ignores the field in non-thinking requests
(verified against the live API, including thinking={"type": "disabled"}).
"""
from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig
@ -263,7 +264,8 @@ def test_deepseek_fill_reasoning_content_guard_in_transform_request():
"reasoning_content should be injected when thinking is enabled"
)
# Case 2: reasoning model + thinking NOT in optional_params -> no injection
# Case 2: reasoning model + thinking NOT in optional_params -> injection
# (thinking is on by default for DeepSeek V4 / deepseek-reasoner)
result = config.transform_request(
model="deepseek-reasoner",
messages=messages,
@ -271,11 +273,23 @@ def test_deepseek_fill_reasoning_content_guard_in_transform_request():
litellm_params={},
headers={},
)
assert "reasoning_content" not in result["messages"][1], (
"reasoning_content should not be injected when thinking is not enabled"
assert result["messages"][1].get("reasoning_content") == " ", (
"reasoning_content should be injected by default for reasoning models"
)
# Case 3: non-reasoning model + thinking enabled -> no injection
# Case 3: reasoning model + thinking explicitly disabled -> no injection
result = config.transform_request(
model="deepseek-reasoner",
messages=messages,
optional_params={"thinking": {"type": "disabled"}},
litellm_params={},
headers={},
)
assert "reasoning_content" not in result["messages"][1], (
"reasoning_content should not be injected when thinking is disabled"
)
# Case 4: non-reasoning model -> no injection
result = config.transform_request(
model="deepseek-chat",
messages=messages,