mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
fix: gate reasoning_effort to V4 models, handle combined params
Address Greptile review feedback: - Add _is_v4_model() to only forward reasoning_effort to V4 Pro/Flash (older models like deepseek-reasoner don't support the param) - Forward reasoning_effort even when thinking dict is also provided (fixes silent drop in the elif branch) - Move normalization (low→high, xhigh→max) before branching - Expand tests: V4 vs non-V4 coverage, combined params, model detection Signed-off-by: Jay <moonandstar99@yahoo.com>
This commit is contained in:
parent
829a422038
commit
cc84e2c76b
2 changed files with 143 additions and 42 deletions
|
|
@ -47,29 +47,41 @@ class DeepSeekChatConfig(OpenAIGPTConfig):
|
|||
thinking_value = optional_params.pop("thinking", None)
|
||||
reasoning_effort = optional_params.pop("reasoning_effort", None)
|
||||
|
||||
# Normalize reasoning_effort values per DeepSeek V4 compatibility
|
||||
# mappings: low/medium→high, xhigh→max
|
||||
if reasoning_effort is not None and reasoning_effort != "none":
|
||||
if reasoning_effort in ("low", "medium"):
|
||||
reasoning_effort = "high"
|
||||
elif reasoning_effort == "xhigh":
|
||||
reasoning_effort = "max"
|
||||
|
||||
# Handle thinking parameter - only accept {"type": "enabled"}
|
||||
if thinking_value is not None:
|
||||
if (
|
||||
isinstance(thinking_value, dict)
|
||||
and thinking_value.get("type") == "enabled"
|
||||
):
|
||||
# DeepSeek only accepts {"type": "enabled"}, ignore budget_tokens
|
||||
optional_params["thinking"] = {"type": "enabled"}
|
||||
# Forward reasoning_effort alongside thinking for V4 models
|
||||
if reasoning_effort is not None and reasoning_effort != "none":
|
||||
if self._is_v4_model(model):
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
|
||||
# Handle reasoning_effort - enable thinking and pass effort level
|
||||
# DeepSeek V4 Pro/Flash support reasoning_effort as a native param
|
||||
# with values "high" and "max". Older models ignore it gracefully.
|
||||
# Handle reasoning_effort alone (without explicit thinking dict)
|
||||
elif reasoning_effort is not None and reasoning_effort != "none":
|
||||
optional_params["thinking"] = {"type": "enabled"}
|
||||
# Normalize per DeepSeek V4 compatibility mappings
|
||||
if reasoning_effort in ("low", "medium"):
|
||||
reasoning_effort = "high"
|
||||
elif reasoning_effort == "xhigh":
|
||||
reasoning_effort = "max"
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
# Only V4 models support reasoning_effort as a native parameter
|
||||
if self._is_v4_model(model):
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
|
||||
return optional_params
|
||||
|
||||
@staticmethod
|
||||
def _is_v4_model(model: str) -> bool:
|
||||
"""Check if the model is a DeepSeek V4 variant that supports
|
||||
reasoning_effort as a native parameter."""
|
||||
return "v4" in model.lower()
|
||||
|
||||
@overload
|
||||
def _transform_messages(
|
||||
self, messages: List[AllMessageValues], model: str, is_async: Literal[True]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class TestDeepSeekThinkingParams:
|
|||
def setup_method(self):
|
||||
self.config = DeepSeekChatConfig()
|
||||
self.model = "deepseek-reasoner"
|
||||
self.v4_model = "deepseek-v4-pro"
|
||||
|
||||
def test_get_supported_openai_params_includes_thinking(self):
|
||||
"""Test that thinking and reasoning_effort are in supported params."""
|
||||
|
|
@ -51,8 +52,10 @@ class TestDeepSeekThinkingParams:
|
|||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert "budget_tokens" not in result.get("thinking", {})
|
||||
|
||||
def test_map_reasoning_effort_medium(self):
|
||||
"""Test that reasoning_effort='medium' enables thinking and normalizes to 'high'."""
|
||||
# --- reasoning_effort on non-V4 models (backward compat) ---
|
||||
|
||||
def test_map_reasoning_effort_medium_non_v4(self):
|
||||
"""Non-V4: reasoning_effort='medium' enables thinking but does NOT forward effort."""
|
||||
non_default_params = {"reasoning_effort": "medium"}
|
||||
optional_params = {}
|
||||
|
||||
|
|
@ -64,10 +67,10 @@ class TestDeepSeekThinkingParams:
|
|||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "high"
|
||||
assert "reasoning_effort" not in result
|
||||
|
||||
def test_map_reasoning_effort_low(self):
|
||||
"""Test that reasoning_effort='low' enables thinking and normalizes to 'high'."""
|
||||
def test_map_reasoning_effort_low_non_v4(self):
|
||||
"""Non-V4: reasoning_effort='low' enables thinking but does NOT forward effort."""
|
||||
non_default_params = {"reasoning_effort": "low"}
|
||||
optional_params = {}
|
||||
|
||||
|
|
@ -79,10 +82,10 @@ class TestDeepSeekThinkingParams:
|
|||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "high"
|
||||
assert "reasoning_effort" not in result
|
||||
|
||||
def test_map_reasoning_effort_high(self):
|
||||
"""Test that reasoning_effort='high' is passed through to DeepSeek V4."""
|
||||
def test_map_reasoning_effort_high_non_v4(self):
|
||||
"""Non-V4: reasoning_effort='high' enables thinking but does NOT forward effort."""
|
||||
non_default_params = {"reasoning_effort": "high"}
|
||||
optional_params = {}
|
||||
|
||||
|
|
@ -93,29 +96,116 @@ class TestDeepSeekThinkingParams:
|
|||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert "reasoning_effort" not in result
|
||||
|
||||
# --- reasoning_effort on V4 models (native support) ---
|
||||
|
||||
def test_map_reasoning_effort_high_v4(self):
|
||||
"""V4: reasoning_effort='high' enables thinking AND forwards effort."""
|
||||
non_default_params = {"reasoning_effort": "high"}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "high"
|
||||
|
||||
def test_map_reasoning_effort_max(self):
|
||||
"""Test that reasoning_effort='max' is passed through to DeepSeek V4."""
|
||||
def test_map_reasoning_effort_max_v4(self):
|
||||
"""V4: reasoning_effort='max' is forwarded."""
|
||||
non_default_params = {"reasoning_effort": "max"}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.model,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "max"
|
||||
|
||||
def test_map_reasoning_effort_xhigh_normalizes_to_max(self):
|
||||
"""Test that reasoning_effort='xhigh' normalizes to 'max' per DeepSeek V4 compat."""
|
||||
def test_map_reasoning_effort_xhigh_normalizes_to_max_v4(self):
|
||||
"""V4: reasoning_effort='xhigh' normalizes to 'max'."""
|
||||
non_default_params = {"reasoning_effort": "xhigh"}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "max"
|
||||
|
||||
def test_map_reasoning_effort_low_normalizes_to_high_v4(self):
|
||||
"""V4: reasoning_effort='low' normalizes to 'high'."""
|
||||
non_default_params = {"reasoning_effort": "low"}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "high"
|
||||
|
||||
def test_map_reasoning_effort_medium_normalizes_to_high_v4(self):
|
||||
"""V4: reasoning_effort='medium' normalizes to 'high'."""
|
||||
non_default_params = {"reasoning_effort": "medium"}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "high"
|
||||
|
||||
# --- Combined thinking + reasoning_effort ---
|
||||
|
||||
def test_thinking_and_reasoning_effort_both_forwarded_v4(self):
|
||||
"""V4: When both thinking and reasoning_effort are provided,
|
||||
both are forwarded — effort is NOT silently dropped."""
|
||||
non_default_params = {
|
||||
"thinking": {"type": "enabled"},
|
||||
"reasoning_effort": "max",
|
||||
}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.v4_model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "max"
|
||||
|
||||
def test_thinking_takes_precedence_non_v4(self):
|
||||
"""Non-V4: When both are provided, thinking is set but
|
||||
reasoning_effort is NOT forwarded (model doesn't support it)."""
|
||||
non_default_params = {
|
||||
"thinking": {"type": "enabled"},
|
||||
"reasoning_effort": "high",
|
||||
}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
|
|
@ -124,7 +214,9 @@ class TestDeepSeekThinkingParams:
|
|||
)
|
||||
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
assert result["reasoning_effort"] == "max"
|
||||
assert "reasoning_effort" not in result
|
||||
|
||||
# --- Edge cases ---
|
||||
|
||||
def test_map_reasoning_effort_none_does_not_enable_thinking(self):
|
||||
"""Test that reasoning_effort='none' does not enable thinking."""
|
||||
|
|
@ -139,6 +231,7 @@ class TestDeepSeekThinkingParams:
|
|||
)
|
||||
|
||||
assert "thinking" not in result
|
||||
assert "reasoning_effort" not in result
|
||||
|
||||
def test_map_reasoning_effort_null_does_not_enable_thinking(self):
|
||||
"""Test that reasoning_effort=None does not enable thinking."""
|
||||
|
|
@ -154,24 +247,6 @@ class TestDeepSeekThinkingParams:
|
|||
|
||||
assert "thinking" not in result
|
||||
|
||||
def test_thinking_takes_precedence_over_reasoning_effort(self):
|
||||
"""Test that thinking param takes precedence when both are provided."""
|
||||
non_default_params = {
|
||||
"thinking": {"type": "enabled"},
|
||||
"reasoning_effort": "high",
|
||||
}
|
||||
optional_params = {}
|
||||
|
||||
result = self.config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=self.model,
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
# thinking should be set, reasoning_effort should not override
|
||||
assert result["thinking"] == {"type": "enabled"}
|
||||
|
||||
def test_invalid_thinking_type_ignored(self):
|
||||
"""Test that invalid thinking type values are ignored."""
|
||||
non_default_params = {"thinking": {"type": "invalid"}}
|
||||
|
|
@ -199,3 +274,17 @@ class TestDeepSeekThinkingParams:
|
|||
)
|
||||
|
||||
assert "thinking" not in result
|
||||
|
||||
# --- V4 model detection ---
|
||||
|
||||
def test_is_v4_model_positive(self):
|
||||
"""Test V4 model detection for various V4 model names."""
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek-v4-pro") is True
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek-v4-flash") is True
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek/deepseek-v4-pro") is True
|
||||
|
||||
def test_is_v4_model_negative(self):
|
||||
"""Test V4 model detection rejects non-V4 models."""
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek-reasoner") is False
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek-chat") is False
|
||||
assert DeepSeekChatConfig._is_v4_model("deepseek-coder") is False
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue