From c00014468545d76b9a7504b0718dc52280fd4acb Mon Sep 17 00:00:00 2001 From: BiGy2018 <360055070@qq.com> Date: Tue, 22 Sep 2026 00:18:10 +0800 Subject: [PATCH 1/3] fix(deepseek): preserve native reasoning effort for V4 models --- litellm/llms/deepseek/chat/transformation.py | 8 ++++ .../chat/test_deepseek_chat_transformation.py | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index ea19a7c7ddf..ff71348cffe 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -59,6 +59,14 @@ class DeepSeekChatConfig(OpenAIGPTConfig): elif reasoning_effort is not None: optional_params["thinking"] = {"type": "disabled" if reasoning_effort == "none" else "enabled"} + if ( + model.removeprefix("deepseek/").startswith(("deepseek-v4-", "deepseek-flash")) + and optional_params.get("thinking") == {"type": "enabled"} + and isinstance(reasoning_effort, str) + and reasoning_effort != "none" + ): + return {**optional_params, "reasoning_effort": reasoning_effort} + return optional_params def _fill_reasoning_content(self, messages: list[AllMessageValues]) -> list[AllMessageValues]: diff --git a/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py b/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py index 3f93264d0d0..93a5a69075a 100644 --- a/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py +++ b/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -1,7 +1,47 @@ +from typing import Final + +import pytest + import litellm from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig +@pytest.mark.parametrize( + "model", ("deepseek-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp", "deepseek/deepseek-v4-flash") +) +@pytest.mark.parametrize("effort", ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")) +@pytest.mark.parametrize("thinking", (None, "enabled")) +def test_v4_keeps_requested_effort_with_enabled_thinking(model: str, effort: str, thinking: str | None) -> None: + params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} + result: Final = DeepSeekChatConfig().map_openai_params(params, {}, model, False) + assert result == {"thinking": {"type": "enabled"}, "reasoning_effort": effort} + + +@pytest.mark.parametrize( + "thinking,effort,expected", + ( + (None, None, {}), + (None, "none", {"thinking": {"type": "disabled"}}), + ("disabled", "low", {"thinking": {"type": "disabled"}}), + ("disabled", "max", {"thinking": {"type": "disabled"}}), + ("enabled", "none", {"thinking": {"type": "enabled"}}), + ("enabled", None, {"thinking": {"type": "enabled"}}), + ), +) +def test_v4_retains_explicit_thinking_precedence( + thinking: str | None, effort: str | None, expected: dict[str, dict[str, str]] +) -> None: + params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} + result: Final = DeepSeekChatConfig().map_openai_params(params, {}, "deepseek-flash", False) + assert result == expected + + +@pytest.mark.parametrize("model", ("deepseek-chat", "deepseek-reasoner", "deepseek/deepseek-reasoner")) +def test_legacy_models_retain_toggle_only_mapping(model: str) -> None: + result: Final = DeepSeekChatConfig().map_openai_params({"reasoning_effort": "low"}, {}, model, False) + assert result == {"thinking": {"type": "enabled"}} + + def _function_tool(name: str) -> dict: return { "type": "function", From e890f0c6577f1c3c73d03af20ef367476ee921e7 Mon Sep 17 00:00:00 2001 From: BiGy2018 <360055070@qq.com> Date: Tue, 22 Sep 2026 00:31:06 +0800 Subject: [PATCH 2/3] fix(deepseek): retain mutable request mapping contract --- litellm/llms/deepseek/chat/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/deepseek/chat/transformation.py b/litellm/llms/deepseek/chat/transformation.py index ff71348cffe..e9edac58ec0 100644 --- a/litellm/llms/deepseek/chat/transformation.py +++ b/litellm/llms/deepseek/chat/transformation.py @@ -61,11 +61,11 @@ class DeepSeekChatConfig(OpenAIGPTConfig): if ( model.removeprefix("deepseek/").startswith(("deepseek-v4-", "deepseek-flash")) - and optional_params.get("thinking") == {"type": "enabled"} + and not (isinstance(thinking_value, dict) and thinking_value.get("type") == "disabled") and isinstance(reasoning_effort, str) and reasoning_effort != "none" ): - return {**optional_params, "reasoning_effort": reasoning_effort} + return {**optional_params, "reasoning_effort": reasoning_effort} # mutable-ok: adapters mutate this dict return optional_params From b3e0e9f8250731d91c905fc4961883449153b205 Mon Sep 17 00:00:00 2001 From: BiGy2018 <360055070@qq.com> Date: Tue, 22 Sep 2026 09:45:32 +0800 Subject: [PATCH 3/3] test(deepseek): include native effort regression in coverage --- .../chat/test_deepseek_chat_transformation.py | 41 +++++++++++++++++++ .../chat/test_deepseek_chat_transformation.py | 40 ------------------ 2 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py 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 new file mode 100644 index 00000000000..38d7d351c9e --- /dev/null +++ b/tests/test_litellm/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -0,0 +1,41 @@ +from typing import Final + +import pytest + +from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig + + +@pytest.mark.parametrize( + "model", ("deepseek-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp", "deepseek/deepseek-v4-flash") +) +@pytest.mark.parametrize("effort", ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")) +@pytest.mark.parametrize("thinking", (None, "enabled")) +def test_v4_keeps_requested_effort_with_enabled_thinking(model: str, effort: str, thinking: str | None) -> None: + params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} + result: Final = DeepSeekChatConfig().map_openai_params(params, {}, model, False) + assert result == {"thinking": {"type": "enabled"}, "reasoning_effort": effort} + + +@pytest.mark.parametrize( + "thinking,effort,expected", + ( + (None, None, {}), + (None, "none", {"thinking": {"type": "disabled"}}), + ("disabled", "low", {"thinking": {"type": "disabled"}}), + ("disabled", "max", {"thinking": {"type": "disabled"}}), + ("enabled", "none", {"thinking": {"type": "enabled"}}), + ("enabled", None, {"thinking": {"type": "enabled"}}), + ), +) +def test_v4_retains_explicit_thinking_precedence( + thinking: str | None, effort: str | None, expected: dict[str, dict[str, str]] +) -> None: + params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} + result: Final = DeepSeekChatConfig().map_openai_params(params, {}, "deepseek-flash", False) + assert result == expected + + +@pytest.mark.parametrize("model", ("deepseek-chat", "deepseek-reasoner", "deepseek/deepseek-reasoner")) +def test_legacy_models_retain_toggle_only_mapping(model: str) -> None: + result: Final = DeepSeekChatConfig().map_openai_params({"reasoning_effort": "low"}, {}, model, False) + assert result == {"thinking": {"type": "enabled"}} diff --git a/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py b/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py index 93a5a69075a..3f93264d0d0 100644 --- a/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py +++ b/tests/unit/llms/deepseek/chat/test_deepseek_chat_transformation.py @@ -1,47 +1,7 @@ -from typing import Final - -import pytest - import litellm from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig -@pytest.mark.parametrize( - "model", ("deepseek-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp", "deepseek/deepseek-v4-flash") -) -@pytest.mark.parametrize("effort", ("minimal", "low", "medium", "high", "xhigh", "max", "ultra")) -@pytest.mark.parametrize("thinking", (None, "enabled")) -def test_v4_keeps_requested_effort_with_enabled_thinking(model: str, effort: str, thinking: str | None) -> None: - params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} - result: Final = DeepSeekChatConfig().map_openai_params(params, {}, model, False) - assert result == {"thinking": {"type": "enabled"}, "reasoning_effort": effort} - - -@pytest.mark.parametrize( - "thinking,effort,expected", - ( - (None, None, {}), - (None, "none", {"thinking": {"type": "disabled"}}), - ("disabled", "low", {"thinking": {"type": "disabled"}}), - ("disabled", "max", {"thinking": {"type": "disabled"}}), - ("enabled", "none", {"thinking": {"type": "enabled"}}), - ("enabled", None, {"thinking": {"type": "enabled"}}), - ), -) -def test_v4_retains_explicit_thinking_precedence( - thinking: str | None, effort: str | None, expected: dict[str, dict[str, str]] -) -> None: - params: Final = {"reasoning_effort": effort, **({"thinking": {"type": thinking}} if thinking else {})} - result: Final = DeepSeekChatConfig().map_openai_params(params, {}, "deepseek-flash", False) - assert result == expected - - -@pytest.mark.parametrize("model", ("deepseek-chat", "deepseek-reasoner", "deepseek/deepseek-reasoner")) -def test_legacy_models_retain_toggle_only_mapping(model: str) -> None: - result: Final = DeepSeekChatConfig().map_openai_params({"reasoning_effort": "low"}, {}, model, False) - assert result == {"thinking": {"type": "enabled"}} - - def _function_tool(name: str) -> dict: return { "type": "function",