hermes - ✅(Solved) Fix reasoning_content dropped in multi-turn tool calls with DeepSeek v4 (causes HTTP 400) [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#17400Fetched 2026-04-30 06:47:52
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
0
Timeline (top)
labeled ×4commented ×2cross-referenced ×1

Error Message

2026-04-29 18:03:42,992 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}
2026-04-29 18:14:10,896 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Full request dumps available at ~/.hermes/sessions/request_dump_*_deepseek*.json

Root Cause

In multi-turn tool-calling scenarios, Hermes sometimes fails to include reasoning_content in assistant messages when constructing subsequent requests.

Evidence from request dumps:

  1. Direct hit (Session 20260429_175349_88d049, Apr 29 18:14):

    • Message [40]: assistant + tool_calls + reasoning_content ✓
    • Message [42]: assistant + tool_calls + reasoning_content ✓
    • Message [44]: assistant + tool_calls WITHOUT reasoning_content ✗ ← causes 400
  2. Null reasoning_content (Session 20260428_160911_013162, Apr 28 16:09):

    • Message [133]: assistant with reasoning_content: null

The pattern: all other models (qwen3.6-plus, kimi-k2.6, etc.) work fine because they don't enforce reasoning_content echo-back. Only DeepSeek enforces this requirement in thinking mode.

Fix Action

Fixed

PR fix notes

PR #17489: fix(agent): pad reasoning_content on DeepSeek/Kimi tool-call turns

Description (problem / solution / changelog)

What does this PR do?

The defensive branch in _build_assistant_message that was supposed to pin reasoning_content="" on DeepSeek thinking-mode tool-call turns never actually fired. It was gated on msg.get("tool_calls") — but tool_calls doesn't get added to msg until ~60 lines later in the same method, so the check was always falsy. Whenever DeepSeek returned reasoning_content=None on a tool-call turn (which happens intermittently in long sessions) and streaming captured no reasoning text, the message got persisted bare. The next replay 400'd:

Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.

The same defect is reachable on Kimi/Moonshot thinking — same enforcement, no separate ticket but the shape's identical.

The fix replaces the dead elif with a single post-tool_calls pad that fires when:

  • assistant_message.tool_calls is truthy (reading the SDK source of truth, not a not-yet-populated dict key, which is what caused the original bug); and
  • reasoning_content isn't already set by an earlier branch (so we never clobber legitimate reasoning); and
  • the active provider enforces echo-back.

Pulled the predicate into a new _needs_thinking_reasoning_pad() helper so _copy_reasoning_content_for_api shares it instead of re-inlining kimi or deepseek.

Related Issue

Fixes #17400

Type of Change

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

Changes Made

  • run_agent.py: removed the dead pad branch in _build_assistant_message; added the unified post-tool_calls pad covering DeepSeek and Kimi/Moonshot; extracted _needs_thinking_reasoning_pad() and reused it in _copy_reasoning_content_for_api.
  • tests/run_agent/test_deepseek_reasoning_content_echo.py: new TestBuildAssistantMessagePadsStrictProviders class — one parametrized test covering five provider/attribute combos (deepseek with reasoning_content=None, deepseek without the attribute at all, kimi-coding, custom-provider via api.moonshot.ai, plus an openrouter case to confirm we don't pad needlessly), and three standalone tests for "real reasoning preserved", "text-only turn untouched", "streamed reasoning wins over the empty pad". Folded the new helper's defaults into the existing _make_agent so the file doesn't end up with two parallel agent factories.

How to Test

  1. pytest tests/run_agent/test_deepseek_reasoning_content_echo.py -q — 31 pass.
  2. To confirm the new tests actually exercise the fix and not just pass trivially: git stash the change to run_agent.py, re-run the file, and 4 of the 8 new cases fail (the four that hit the previously-dead pad). Pop the stash; they pass again.
  3. Wider sweep: pytest tests/run_agent/ -q — 1164 passed, 7 unrelated skips.

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 (no unrelated commits)
  • I've run the relevant test slice (tests/run_agent/) and all tests pass — 1164 passed, 7 unrelated skips. Skipped the full tests/ suite as the change is contained.
  • I've added tests for my changes
  • I've tested on my platform: macOS 15 (Darwin 25.4, arm64)

Documentation & Housekeeping

  • Documentation updates — N/A (internal builder logic, no public surface change)
  • cli-config.yaml.example — N/A
  • CONTRIBUTING.md / AGENTS.md — N/A
  • Cross-platform impact considered — pure Python message-builder logic, no platform-specific paths
  • Tool descriptions/schemas — N/A

Changed files

  • run_agent.py (modified, +25/-10)
  • tests/run_agent/test_deepseek_reasoning_content_echo.py (modified, +119/-0)

Code Example

Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.'}}

---

model:
  default: deepseek-v4-pro
  provider: go-custom
  base_url: https://opencode.ai/zen/go/v1

---

compression:
  enabled: true
  threshold: 0.5
  target_ratio: 0.2
  protect_last_n: 20

---

2026-04-29 18:03:42,992 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}
2026-04-29 18:14:10,896 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}
RAW_BUFFERClick to expand / collapse

Bug Description

When using DeepSeek v4 series models (deepseek-v4-pro) with thinking mode enabled, multi-turn conversations with tool calls intermittently fail with HTTP 400: The reasoning_content in the thinking mode must be passed back to the API.

Root Cause

In multi-turn tool-calling scenarios, Hermes sometimes fails to include reasoning_content in assistant messages when constructing subsequent requests.

Evidence from request dumps:

  1. Direct hit (Session 20260429_175349_88d049, Apr 29 18:14):

    • Message [40]: assistant + tool_calls + reasoning_content ✓
    • Message [42]: assistant + tool_calls + reasoning_content ✓
    • Message [44]: assistant + tool_calls WITHOUT reasoning_content ✗ ← causes 400
  2. Null reasoning_content (Session 20260428_160911_013162, Apr 28 16:09):

    • Message [133]: assistant with reasoning_content: null

The pattern: all other models (qwen3.6-plus, kimi-k2.6, etc.) work fine because they don't enforce reasoning_content echo-back. Only DeepSeek enforces this requirement in thinking mode.

Steps to Reproduce

  1. Configure Hermes with deepseek-v4-pro via an OpenAI-compatible endpoint (e.g., OpenCode Go API at opencode.ai/zen/go/v1)
  2. Enable thinking mode (the model defaults to xhigh)
  3. Have a multi-turn conversation with multiple tool calls (5+ rounds of tool usage)
  4. Eventually the 400 error appears when Hermes sends a request missing reasoning_content in an assistant message

Expected Behavior

All assistant messages (especially those with tool_calls) that the API returned with reasoning_content should have that field preserved when echoed back in subsequent requests.

Actual Behavior

Some assistant messages lose their reasoning_content field. The API then rejects the request with:

Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.'}}

Configuration

model:
  default: deepseek-v4-pro
  provider: go-custom
  base_url: https://opencode.ai/zen/go/v1
compression:
  enabled: true
  threshold: 0.5
  target_ratio: 0.2
  protect_last_n: 20

Related

Logs

2026-04-29 18:03:42,992 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}
2026-04-29 18:14:10,896 ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message': 'Error from provider (DeepSeek): The reasoning_content in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Full request dumps available at ~/.hermes/sessions/request_dump_*_deepseek*.json

extent analysis

TL;DR

Ensure that reasoning_content is properly echoed back in subsequent requests for DeepSeek v4 series models with thinking mode enabled.

Guidance

  • Verify that the reasoning_content field is included in all assistant messages, especially those with tool_calls, by checking the request dumps.
  • Review the Hermes configuration and code to identify why reasoning_content is sometimes missing from assistant messages.
  • Consider adding a check to ensure that reasoning_content is not null or empty before sending the request.
  • Check the DeepSeek API documentation to confirm the requirements for echoing back reasoning_content in thinking mode.

Example

No code snippet is provided as the issue is more related to the configuration and the logic of echoing back reasoning_content.

Notes

The issue seems to be specific to DeepSeek v4 series models with thinking mode enabled, and the problem is intermittent, making it harder to reproduce and debug.

Recommendation

Apply a workaround to ensure that reasoning_content is always echoed back in subsequent requests for DeepSeek v4 series models with thinking mode enabled, as this is a requirement for the DeepSeek API.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix reasoning_content dropped in multi-turn tool calls with DeepSeek v4 (causes HTTP 400) [1 pull requests, 2 comments, 2 participants]