From 899df60a6638bb5d541cee851d351f70cdcc8344 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: Sat, 22 Aug 2026 17:37:06 +0530 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfix(deepseek):=20fall=20back=20to=20th?= =?UTF-8?q?inking=5Falways=5Fon=20model=20flag=20for=20tool-loop=20400s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeepSeekChatConfig._thinking_mode_active only returned True when a caller explicitly passed thinking={"type": "enabled"}, which is correct for opt-in reasoning models but wrong for deepseek-v4-pro and deepseek-v4-flash, which run thinking mode on by default server-side with no explicit param required. Requests through a router or proxy deployment that never set a reasoning param hit the guard as False, _fill_reasoning_content never ran, and the DeepSeek API rejected every second call of a multi-turn tool loop with a 400 asking for reasoning_content to be passed back. The model cost map already has an established thinking_always_on flag, currently wired up only for Anthropic Claude Fable 5 / Mythos 5 models via AnthropicModelInfo._supports_model_capability, itself built on the same generic _supports_factory used by supports_reasoning. Added is_thinking_always_on to litellm/utils.py as a one-line wrapper mirroring supports_reasoning, and extended _thinking_mode_active to fall back to it when the request carries no explicit thinking param, while an explicit thinking={"type": "disabled"} still short-circuits to False. Added thinking_always_on: true to the deepseek-v4-pro and deepseek-v4-flash entries in the model cost map (both the bare and deepseek/-prefixed keys, which are kept as duplicates) and to the CI-enforced byte-identical backup copy. Added tests covering the always-on fallback, the explicit-disable override, and confirming opt-in models like deepseek-reasoner are unaffected. Verified locally: reverting only the code change while keeping the new cost-map flag reproduces the exact failure this fixes. Closes #37636 --- litellm/llms/deepseek/chat/transformation.py | 20 +++++++++++-------- ...odel_prices_and_context_window_backup.json | 12 +++++++---- litellm/utils.py | 7 +++++++ model_prices_and_context_window.json | 12 +++++++---- .../chat/test_deepseek_chat_transformation.py | 18 +++++++++++++++++ 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index 566c960333a..d3e0c7b0abd 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -11,7 +11,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import ( ) from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import AllMessageValues -from litellm.utils import supports_reasoning +from litellm.utils import is_thinking_always_on, supports_reasoning from ...openai.chat.gpt_transformation import OpenAIGPTConfig @@ -127,16 +127,20 @@ class DeepSeekChatConfig(OpenAIGPTConfig): def _thinking_mode_active(self, model: str, optional_params: dict) -> bool: """ - Returns True only when thinking mode is actually active for this request: + Returns True when thinking mode is active for this request: - model supports reasoning (capability check) - - user explicitly passed thinking={"type": "enabled"} (opt-in check) + - AND either the user explicitly opted in with thinking={"type": "enabled"}, + or thinking is on by default for this model and was not explicitly disabled. """ + if not supports_reasoning(model=model, custom_llm_provider="deepseek"): + return False thinking: Final = optional_params.get("thinking") - return ( - supports_reasoning(model=model, custom_llm_provider="deepseek") - and isinstance(thinking, dict) - and thinking.get("type") == "enabled" - ) + thinking_type = thinking.get("type") if isinstance(thinking, dict) else None + if thinking_type == "enabled": + return True + if thinking_type == "disabled": + return False + return is_thinking_always_on(model=model, custom_llm_provider="deepseek") @staticmethod def _drop_unsupported_tools(optional_params: dict) -> dict: diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 3af7d9e5019..f1b13ef3d66 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -49632,7 +49632,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, @@ -49658,7 +49659,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek/deepseek-v4-flash": { "cache_creation_input_token_cost": 0.0, @@ -49684,7 +49686,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek/deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, @@ -49710,7 +49713,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "tencent/deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, diff --git a/litellm/utils.py b/litellm/utils.py index e5ce7157e77..10be6715a93 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2614,6 +2614,13 @@ def supports_reasoning(model: str, custom_llm_provider: str | None = None) -> bo return _supports_factory(model=model, custom_llm_provider=custom_llm_provider, key="supports_reasoning") +def is_thinking_always_on(model: str, custom_llm_provider: str | None = None) -> bool: + """ + Check if the given model always runs in thinking mode by default and return a boolean value. + """ + return _supports_factory(model=model, custom_llm_provider=custom_llm_provider, key="thinking_always_on") + + def supports_native_structured_output(model: str, custom_llm_provider: str | None = None) -> bool: """ Check if the given model supports native structured outputs and return a boolean value. diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 3af7d9e5019..f1b13ef3d66 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -49632,7 +49632,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, @@ -49658,7 +49659,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek/deepseek-v4-flash": { "cache_creation_input_token_cost": 0.0, @@ -49684,7 +49686,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "deepseek/deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, @@ -49710,7 +49713,8 @@ "supports_response_schema": true, "supports_system_messages": true, "supports_tool_choice": true, - "supports_vision": false + "supports_vision": false, + "thinking_always_on": true }, "tencent/deepseek-v4-pro": { "cache_creation_input_token_cost": 0.0, 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 fa6f23dc7ff..c05ee57b725 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 @@ -108,6 +108,24 @@ def test_thinking_mode_active_bool_thinking_returns_false_without_crashing(): assert config._thinking_mode_active(model="deepseek-reasoner", optional_params={"thinking": True}) is False +def test_thinking_mode_active_true_by_default_for_always_on_model(): + config = DeepSeekChatConfig() + assert config._thinking_mode_active(model="deepseek-v4-pro", optional_params={}) is True + + +def test_thinking_mode_active_false_when_explicitly_disabled_for_always_on_model(): + config = DeepSeekChatConfig() + assert ( + config._thinking_mode_active(model="deepseek-v4-pro", optional_params={"thinking": {"type": "disabled"}}) + is False + ) + + +def test_thinking_mode_active_false_by_default_for_opt_in_model(): + config = DeepSeekChatConfig() + assert config._thinking_mode_active(model="deepseek-reasoner", optional_params={}) is False + + class TestDeepSeekThinkingParams: """Test thinking and reasoning_effort parameter handling for DeepSeek."""