Skip to content

fix(deepseek): subclass ProviderProfile so reasoning_effort + thinking reach the API#25301

Closed
Unveiling9559 wants to merge 1 commit into
NousResearch:mainfrom
Unveiling9559:deepseek-profile-reasoning-forwarding
Closed

fix(deepseek): subclass ProviderProfile so reasoning_effort + thinking reach the API#25301
Unveiling9559 wants to merge 1 commit into
NousResearch:mainfrom
Unveiling9559:deepseek-profile-reasoning-forwarding

Conversation

@Unveiling9559

Copy link
Copy Markdown

Summary

  • DeepSeek plugin ships a bare ProviderProfile, so build_api_kwargs_extras returns ({}, {}) and agent.reasoning_effort never reaches the API.
  • Subclass with a real implementation that emits both reasoning_effort (top-level) and extra_body.thinking per DeepSeek's documented thinking-mode contract.
  • Bonus: update stale aliases/fallback_models (deepseek-chat/deepseek-reasonerdeepseek-v4-pro/deepseek-v4-flash).

Why

The transport's profile path (agent/transports/chat_completions.py, the if _profile: return _build_kwargs_from_profile(...) short-circuit) means registered providers bypass the legacy is_kimi/is_tokenhub-style flag handling. Since DeepSeek is registered as a bare profile, it inherits the no-op base build_api_kwargs_extras — so agent.reasoning_effort: xhigh config is silently dropped on the floor for provider: deepseek. DeepSeek's API still answers (server-side default of effort=high, thinking=enabled), so the bug looks like "reasoning seems to work" but the config value is inert.

How

Match the OpenRouter / Kimi-coding / Qwen-OAuth pattern: subclass ProviderProfile and override build_api_kwargs_extras to return (extra_body_additions, top_level_kwargs).

Per the live API:

  • reasoning_effort enum = {low, medium, high, max, xhigh} — pass through verbatim (server maps low|mediumhigh, xhighmax per the docs footnote).
  • thinking.type enum = {adaptive, enabled, disabled}.
  • thinking.type=disabled + reasoning_effort set → 400 conflict, so omit reasoning_effort when thinking is disabled.
  • Hermes' minimal effort is not in DeepSeek's enum → remap to low.

Test plan

  • Live API: reasoning_config={"enabled": True, "effort": "xhigh"} → emitted body has reasoning_effort=xhigh + extra_body={"thinking":{"type":"enabled"}} → 121 reasoning tokens (vs ~50 default).
  • Live API: 3-sub-turn tool-call flow (get_dateget_weather → final answer), all 200 OK.
  • Disabled path: {"enabled": False} → only extra_body.thinking={"type":"disabled"}, no reasoning_effort (avoids 400).
  • None path: emits nothing, server defaults apply.
  • Unit-level case sweep: low/medium/high/max/xhigh/minimal/None/disabled all produce expected output.

…g reach the API

The DeepSeek plugin shipped a bare ProviderProfile with no overrides for
build_api_kwargs_extras or build_extra_body. Once register_provider() is
called, ChatCompletionsTransport.build_kwargs takes the profile path
(chat_completions.py: `if _profile: return ...` short-circuit) and never
reaches the legacy is_kimi-style flag handling that lives below it. So
with the profile returning ({}, {}) from build_api_kwargs_extras, neither
top-level reasoning_effort nor extra_body.thinking is emitted.

Net effect: `agent.reasoning_effort` is dead air for any user with
`provider: deepseek`. DeepSeek's API still applies its server-side
defaults (effort=high, thinking=enabled) so requests succeed, but the
config value is inert.

Subclass ProviderProfile with a real build_api_kwargs_extras that emits
both fields the DeepSeek native API documents at
https://api-docs.deepseek.com/guides/thinking_mode:
  - top-level reasoning_effort (passed verbatim; DeepSeek's server maps
    low/medium -> high and xhigh -> max per the docs footnote)
  - extra_body.thinking = {"type": "enabled"|"disabled"}

The hermes "minimal" effort is remapped to "low" because DeepSeek's API
rejects "minimal" with HTTP 400 ("unknown variant `minimal`, expected
one of `high`, `low`, `medium`, `max`, `xhigh`"). When reasoning_config
is None, both fields are omitted so users without `agent.reasoning_effort`
set get DeepSeek's API defaults unchanged. When thinking is disabled,
reasoning_effort is omitted to avoid the documented 400 conflict
("thinking options type cannot be disabled when reasoning_effort is
set").

Stale `aliases=("deepseek-chat",)` and `fallback_models=("deepseek-chat",
"deepseek-reasoner")` are also updated — the real models DeepSeek
currently serves are deepseek-v4-pro / deepseek-v4-flash. Verified via
GET https://api.deepseek.com/v1/models on 2026-05-13.

Live API verification (2026-05-13):
  reasoning_effort=xhigh + extra_body.thinking={"type":"enabled"}
  -> 121 reasoning tokens (vs ~50 at server defaults)
3-sub-turn tool-call flow (get_date -> get_weather -> answer):
  all 200 OK, reasoning_content round-tripped via existing
  _copy_reasoning_content_for_api logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins provider/deepseek DeepSeek API duplicate This issue or pull request already exists labels May 14, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #22218 — same fix: subclass ProviderProfile as DeepSeekProfile with build_api_kwargs_extras() emitting reasoning_effort + extra_body.thinking. Multiple competing PRs exist: #16448, #21052, #24130, #24225.

@teknium1

Copy link
Copy Markdown
Contributor

Automated hermes-sweeper review: this DeepSeek profile-path fix is already implemented on current main by the canonical maintainer PR #26648.

Evidence:

  • plugins/model-providers/deepseek/__init__.py:47 now defines DeepSeekProfile(ProviderProfile) instead of registering a bare ProviderProfile.
  • plugins/model-providers/deepseek/__init__.py:50 overrides build_api_kwargs_extras(...); plugins/model-providers/deepseek/__init__.py:67 emits extra_body.thinking, and plugins/model-providers/deepseek/__init__.py:77-80 emits top-level reasoning_effort for enabled thinking.
  • agent/transports/chat_completions.py:526-557 calls the profile hook and merges both the top-level kwargs and profile extra body into the outgoing request.
  • tests/plugins/model_providers/test_deepseek_profile.py:155-169 pins the full transport kwargs shape for deepseek-v4-pro.
  • Merged commit cd9470f41638bd515db096cd934c463205790110 (fix(deepseek): wire thinking-mode via DeepSeekProfile, not legacy fallback) is from PR fix(deepseek): wire thinking-mode via DeepSeekProfile (closes #15700, #17212, #17825) #26648, whose maintainer comment explicitly says it supersedes fix(deepseek): subclass ProviderProfile so reasoning_effort + thinking reach the API #25301.

Thanks for the clear diagnosis here; the same root fix has landed on main.

@teknium1 teknium1 closed this Jun 12, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have provider/deepseek DeepSeek API sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants