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:
Jay 2026-05-08 12:13:07 -04:00
parent 829a422038
commit cc84e2c76b
2 changed files with 143 additions and 42 deletions

View file

@ -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]

View file

@ -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