This commit is contained in:
Hazelhof 2026-09-23 14:51:57 +00:00 • committed by GitHub
commit e1990570cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 66 additions and 0 deletions

View file

@ -51,13 +51,30 @@ class DeepSeekChatConfig(OpenAIGPTConfig):
thinking_value: Final = optional_params.pop("thinking", None)
reasoning_effort: Final = optional_params.pop("reasoning_effort", None)
thinking_enabled = False
# Handle thinking parameter - accept both enabled and disabled, ignore budget_tokens
if isinstance(thinking_value, dict) and thinking_value.get("type") in ("enabled", "disabled"):
optional_params["thinking"] = {"type": thinking_value["type"]}
thinking_enabled = thinking_value["type"] == "enabled"
# Otherwise fall back to reasoning_effort: "none" disables, anything else enables
elif reasoning_effort is not None:
optional_params["thinking"] = {"type": "disabled" if reasoning_effort == "none" else "enabled"}
thinking_enabled = reasoning_effort != "none"
else:
thinking_enabled = True # DeepSeek thinking mode is on by default
# reasoning_effort is a real DeepSeek top-level param (default "high"; valid
# values low/medium/high/xhigh/max). The branch above only used it as an on/off
# toggle and DROPPED the value, so DeepSeek always ran default HIGH effort — which
# can run away, exhausting max_tokens during reasoning and returning empty content
# (finish_reason="length", content:null). Preserve the value into the request body
# when thinking is on so callers can bound effort (e.g. reasoning_effort="low").
# Exclude the "none" sentinel: it is an OpenAI-style thinking-OFF switch, not a real
# effort value, so passing it alongside explicit thinking:enabled would be
# contradictory. See https://api-docs.deepseek.com/guides/thinking_mode
if thinking_enabled and reasoning_effort not in (None, "none"):
optional_params["reasoning_effort"] = reasoning_effort
return optional_params

View file

@ -0,0 +1,49 @@
"""Verify DeepSeek `reasoning_effort` passes through the transformation.
Covers the fix that preserves the effort value (low/medium/high/xhigh/max) into
the request body when thinking is on, instead of mapping it to a binary thinking
toggle and discarding it. See https://api-docs.deepseek.com/guides/thinking_mode
"""
from litellm.llms.deepseek.chat.transformation import DeepSeekChatConfig
def _map(non_default_params, drop_params=True):
cfg = DeepSeekChatConfig()
return cfg.map_openai_params(non_default_params, {}, "deepseek/deepseek-v4-flash", drop_params)
def test_reasoning_effort_low_passes_through():
out = _map({"reasoning_effort": "low"})
assert out["reasoning_effort"] == "low"
assert out["thinking"] == {"type": "enabled"}
def test_reasoning_effort_max_passes_through():
out = _map({"reasoning_effort": "max"})
assert out["reasoning_effort"] == "max"
assert out["thinking"] == {"type": "enabled"}
def test_reasoning_effort_none_disables_thinking():
out = _map({"reasoning_effort": "none"})
assert out["thinking"] == {"type": "disabled"}
assert "reasoning_effort" not in out
def test_explicit_thinking_disabled_wins_and_drops_effort():
out = _map({"thinking": {"type": "disabled"}, "reasoning_effort": "low"})
assert out["thinking"] == {"type": "disabled"}
assert "reasoning_effort" not in out
def test_no_reasoning_effort_leaves_no_key():
out = _map({})
assert out.get("reasoning_effort") is None
def test_explicit_enabled_thinking_with_none_effort_not_contradictory():
# "none" is a thinking-OFF sentinel, not a real effort value: it must not be
# passed alongside explicit thinking:enabled (would contradict the provider).
out = _map({"thinking": {"type": "enabled"}, "reasoning_effort": "none"})
assert out["thinking"] == {"type": "enabled"}
assert "reasoning_effort" not in out