From 6aa6d40c5dd1d2b81543d89fea02ef180eb7d373 Mon Sep 17 00:00:00 2001 From: Milan Date: Thu, 11 Jun 2026 23:04:35 +0300 Subject: [PATCH] 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 --- litellm/llms/deepseek/chat/transformation.py | 34 +++++++------------ .../chat/test_deepseek_chat_transformation.py | 10 +++--- .../test_deepseek_completion.py | 34 +++++++++++++------ 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index d8e11c99323..c8ece202841 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -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, diff --git a/tests/litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py b/tests/litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py index 01a2c522d8d..ba3dfed2b60 100644 --- a/tests/litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py +++ b/tests/litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -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( diff --git a/tests/llm_translation/test_deepseek_completion.py b/tests/llm_translation/test_deepseek_completion.py index 2ede5d3f3f8..e574b7e1f63 100644 --- a/tests/llm_translation/test_deepseek_completion.py +++ b/tests/llm_translation/test_deepseek_completion.py @@ -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,