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:
liuhao1024
2026-08-26 12:46:33 +08:00
committed by Teknium
parent 205645ee42
commit 6f88fb030a
2 changed files with 75 additions and 13 deletions
@@ -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 ────────────────────────────────────────────────