Skip to content

fix(agent): send thinking control to DeepSeek direct API to prevent 400 on tool-call replay#17225

Closed
luyao618 wants to merge 1 commit into
NousResearch:mainfrom
luyao618:fix/deepseek-direct-api-thinking-control
Closed

fix(agent): send thinking control to DeepSeek direct API to prevent 400 on tool-call replay#17225
luyao618 wants to merge 1 commit into
NousResearch:mainfrom
luyao618:fix/deepseek-direct-api-thinking-control

Conversation

@luyao618

Copy link
Copy Markdown
Contributor

What does this PR do?

DeepSeek's direct API (api.deepseek.com) defaults to thinking=enabled when no explicit thinking parameter is sent. When the user sets reasoning_effort: none in config, the setting was silently ignored because _supports_reasoning_extra_body() returns False for direct DeepSeek endpoints — so extra_body.reasoning (OpenRouter format) was never sent. The model defaulted to thinking=enabled, generated reasoning_content in its response, and the next API call failed with HTTP 400: "reasoning_content must be passed back".

This PR adds DeepSeek direct API thinking control to ChatCompletionsTransport.build_kwargs(), following the existing Kimi pattern: emit extra_body.thinking with type: "enabled" or type: "disabled" based on the user's reasoning_config. The is_deepseek_direct flag is threaded from run_agent.py, detected via provider name (deepseek) or base URL matching (api.deepseek.com).

The OpenRouter DeepSeek path is unchanged — it uses extra_body.reasoning which is a separate parameter handled by the existing supports_reasoning gate.

Related Issue

Fixes #17212

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/transports/chat_completions.py: Added DeepSeek direct API extra_body.thinking block (after existing Kimi block), with enabled/disabled control based on reasoning_config
  • run_agent.py: Added _is_deepseek_direct detection (provider == "deepseek" or base_url matches api.deepseek.com) and threaded it to transport params
  • tests/agent/transports/test_chat_completions.py: Added 5 test cases covering default enabled, disabled via config, effort=none, OpenRouter unchanged, Kimi unchanged

How to Test

  1. Configure Hermes with DeepSeek direct API (api.deepseek.com) and reasoning_effort: none
  2. Send a message that triggers tool calls (e.g. "run date")
  3. Verify the second API call succeeds (no 400 error)
  4. Run the targeted tests: pytest tests/agent/transports/test_chat_completions.py -v
  5. Run full suite: pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS (Darwin 25.4.0, Apple Silicon), Python 3.11

Documentation & Housekeeping

  • Updated relevant documentation — or N/A
  • Updated cli-config.yaml.example — or N/A
  • Updated contributing / agents docs — or N/A
  • Considered cross-platform impact — or N/A
  • Updated tool descriptions/schemas — or N/A

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists provider/deepseek DeepSeek API comp/agent Core agent loop, run_agent.py, prompt builder labels Apr 29, 2026
@luyao618 luyao618 force-pushed the fix/deepseek-direct-api-thinking-control branch from b9675a4 to 3a2d7d6 Compare April 30, 2026 01:50
…00 on tool-call replay

DeepSeek's direct API (api.deepseek.com) defaults to thinking=enabled when
no explicit thinking parameter is sent. This causes the model to generate
reasoning_content in responses, which must be replayed on subsequent API
calls. When the user sets reasoning_effort: none, the config was silently
ignored because _supports_reasoning_extra_body() returns False for direct
DeepSeek — so extra_body.reasoning was never sent. The model defaulted to
thinking=enabled, generated reasoning_content, and the next API call failed
with 400 'reasoning_content must be passed back'.

Add DeepSeek direct API thinking control to ChatCompletionsTransport,
following the existing Kimi pattern: emit extra_body.thinking with type
'enabled' or 'disabled' based on reasoning_config. Thread is_deepseek_direct
from run_agent.py (detected via provider name or base_url matching
api.deepseek.com).

OpenRouter DeepSeek path is unchanged — it uses extra_body.reasoning which
is a separate parameter handled by the existing supports_reasoning gate.

Fixes NousResearch#17212
@luyao618

Copy link
Copy Markdown
Contributor Author

Closing: this PR has merge conflicts and the codebase has moved on. Will re-submit if the fix is still needed.

@luyao618 luyao618 closed this May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists provider/deepseek DeepSeek API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeepSeek direct API 400 "reasoning_content must be passed back" on multi-turn tool calls

2 participants