fix(commandcode): forward DeepSeek reasoning controls through the wire
CommandCode fronts DeepSeek with vendor-prefixed ids (deepseek/deepseek-v4-flash). DeepSeek V4+ defaults to thinking mode when the thinking field is omitted, so /reasoning none changed the Hermes session state but not the actual request -- the turn sat in reflecting.../brainstorming... for minutes (#95232). Strip the vendor prefix for DeepSeek-family ids and delegate to the native DeepSeek profile's build_api_kwargs_extras (extra_body.thinking + reasoning_effort mapping); other CommandCode model families keep the base no-op behavior. The prior no-op tests codified the bug and are rewritten to pin the new contract.
This commit is contained in:
@@ -48,6 +48,32 @@ class CommandCodeAnthropicProfile(CommandCodeProfile):
|
||||
all_models = super().fetch_models(api_key=api_key, base_url=base_url, timeout=timeout)
|
||||
return None if all_models is None else [m for m in all_models if m.startswith("claude-")]
|
||||
|
||||
def build_api_kwargs_extras(
|
||||
self, *, reasoning_config: dict | None = None, model: str | None = None, **context
|
||||
) -> tuple[dict, dict]:
|
||||
"""Apply the native DeepSeek reasoning controls for DeepSeek-family ids.
|
||||
|
||||
CommandCode fronts DeepSeek with vendor-prefixed ids
|
||||
(``deepseek/deepseek-v4-flash``). DeepSeek V4+ defaults to thinking
|
||||
mode when the ``thinking`` field is omitted, so without an explicit
|
||||
wire control a Hermes ``/reasoning none`` changes the session state
|
||||
but not the actual request — the turn sits in
|
||||
``reflecting.../brainstorming...`` for minutes (#95232). Strip the
|
||||
vendor prefix and delegate to the native DeepSeek profile's logic
|
||||
(extra_body.thinking + reasoning_effort mapping); other CommandCode
|
||||
model families keep the base no-op behavior.
|
||||
"""
|
||||
m = (model or "").strip()
|
||||
if not m.lower().startswith("deepseek/") or len(m) <= len("deepseek/"):
|
||||
return {}, {}
|
||||
from plugins.model_providers.deepseek import deepseek as _deepseek_profile
|
||||
|
||||
return _deepseek_profile.build_api_kwargs_extras(
|
||||
reasoning_config=reasoning_config,
|
||||
model=m.split("/", 1)[1],
|
||||
**context,
|
||||
)
|
||||
|
||||
|
||||
commandcode = CommandCodeProfile(
|
||||
name="commandcode", aliases=("commandcode-chat",), api_mode="chat_completions",
|
||||
|
||||
@@ -86,28 +86,64 @@ class TestCommandCodeProfileIdentity:
|
||||
|
||||
|
||||
class TestCommandCodeProfileNoThinkingInterference:
|
||||
"""Chat completions profile is a no-op for thinking config — it delegates
|
||||
to the underlying model's provider (DeepSeek, Qwen, etc.) for wire format.
|
||||
"""Reasoning wire controls for the chat-completions profile.
|
||||
|
||||
DeepSeek-family ids get the native DeepSeek controls (DeepSeek V4+
|
||||
defaults to thinking when the field is omitted, so an explicit wire
|
||||
control is required for ``/reasoning none`` to reach the request,
|
||||
#95232); every other CommandCode model family keeps the base no-op.
|
||||
"""
|
||||
|
||||
def test_passthrough_no_reasoning_config(self, commandcode_profile):
|
||||
def test_deepseek_disabled_reasoning_sends_thinking_disabled(
|
||||
self, commandcode_profile
|
||||
):
|
||||
extra_body, top_level = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config=None, model="deepseek/deepseek-v4-pro"
|
||||
reasoning_config={"enabled": False},
|
||||
model="deepseek/deepseek-v4-flash",
|
||||
)
|
||||
# Chat completions profile doesn't inject thinking params — that's
|
||||
# the DeepSeek provider's job when routed through DeepSeek's own profile.
|
||||
# When routed through CommandCode, the underlying model API handles it.
|
||||
assert isinstance(extra_body, dict)
|
||||
assert isinstance(top_level, dict)
|
||||
# Default ProviderProfile returns ({}, {}).
|
||||
assert extra_body.get("thinking") == {"type": "disabled"}
|
||||
assert top_level == {}
|
||||
|
||||
def test_passthrough_with_reasoning_config(self, commandcode_profile):
|
||||
def test_deepseek_enabled_reasoning_maps_effort(self, commandcode_profile):
|
||||
extra_body, top_level = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": True, "effort": "high"},
|
||||
model="deepseek/deepseek-v4-pro",
|
||||
)
|
||||
assert isinstance(extra_body, dict)
|
||||
assert isinstance(top_level, dict)
|
||||
assert extra_body.get("thinking") == {"type": "enabled"}
|
||||
assert top_level.get("reasoning_effort") == "high"
|
||||
|
||||
def test_deepseek_no_config_defaults_to_enabled(self, commandcode_profile):
|
||||
# Matches DeepSeek's API default, applied explicitly so the field is
|
||||
# never omitted for thinking-capable models.
|
||||
extra_body, _ = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config=None, model="deepseek/deepseek-v4-flash"
|
||||
)
|
||||
assert extra_body.get("thinking") == {"type": "enabled"}
|
||||
|
||||
def test_deepseek_v3_stays_noop(self, commandcode_profile):
|
||||
extra_body, top_level = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": False},
|
||||
model="deepseek/deepseek-v3",
|
||||
)
|
||||
assert extra_body == {}
|
||||
assert top_level == {}
|
||||
|
||||
def test_passthrough_non_deepseek_family(self, commandcode_profile):
|
||||
extra_body, top_level = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config={"enabled": True, "effort": "high"},
|
||||
model="Qwen/Qwen3.7-Max",
|
||||
)
|
||||
assert extra_body == {}
|
||||
assert top_level == {}
|
||||
|
||||
def test_passthrough_no_reasoning_config_non_deepseek(
|
||||
self, commandcode_profile
|
||||
):
|
||||
extra_body, top_level = commandcode_profile.build_api_kwargs_extras(
|
||||
reasoning_config=None, model="gpt-5.5"
|
||||
)
|
||||
assert extra_body == {}
|
||||
assert top_level == {}
|
||||
|
||||
|
||||
# ── Anthropic Messages profile ────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user