Skip to content

fix: set empty reasoning_content for DeepSeek when tool_calls present#14941

Closed
tseek wants to merge 1 commit into
NousResearch:mainfrom
tseek:fix/deepseek-reasoning-tool-calls
Closed

fix: set empty reasoning_content for DeepSeek when tool_calls present#14941
tseek wants to merge 1 commit into
NousResearch:mainfrom
tseek:fix/deepseek-reasoning-tool-calls

Conversation

@tseek

@tseek tseek commented Apr 24, 2026

Copy link
Copy Markdown

When the model returns tool_calls alongside reasoning_content, some DeepSeek models (e.g., deepseek-v4-flash) include non-empty reasoning_content which can cause downstream issues.

This PR applies the same empty-string normalization already done for Kimi models, by checking if the provider is DeepSeek (or custom pointing to api.deepseek.com) and setting reasoning_content to empty string when tool_calls are present.

Tested and verified working with deepseek-v4-flash.

When the model returns tool_calls alongside reasoning_content,
some DeepSeek models (e.g., deepseek-v4-flash) include non-empty
reasoning_content which can cause downstream issues. Apply the same
empty-string normalization already done for Kimi models.
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent loop, run_agent.py, prompt builder provider/deepseek DeepSeek API labels Apr 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Fixes #14938 (DeepSeek V4 Flash tool call failures with reasoning_content error).

@CompaCompu

Copy link
Copy Markdown

I hit the same deepseek-v4-pro 400 error today, and monkey-patched it locally.

Your PR sets empty reasoning_content for DeepSeek when tool_calls are present in the message. My session broke differently: an assistant reply with no reasoning and no tool_calls — just a plain text response sitting between two tool-call turns.

DeepSeek still rejected the request because that assistant message lacks reasoning_content entirely.

The fix I ended up with adds an _is_deepseek_endpoint() check in _copy_reasoning_content_for_api() that applies to all assistant messages, not just ones with tool_calls:

- Assistant with reasoning_content → pass through as-is
- Assistant without reasoning_content → set to "" for DeepSeek
- Non-DeepSeek providers → untouched

This also covered two edge cases your PR doesn't touch:

  1. The _handle_max_iterations summary call has its own api_messages building loop (separate from the main one) that also needed the same treatment
  2. A synthetic error fallback assistant message ("I apologize, but I encountered repeated errors") that gets persisted and replayed on session resume

These are edge cases (iteration budget exhaustion + error recovery + resume) but they'd hit the same 400.

Your fix + #14973 (extract from model_extra) + this broader replay guard would be the complete set.

I am not a coder, I am just telling you what my hermes agent told me it had to do to get deepseek-v4-pro working without breaking.

@alejandro-alarcon-t

Copy link
Copy Markdown

Confirming this PR resolves the issue from #14938 / #14933. Tested on Hermes main (commit e5d41f0) with the patch applied locally.

Repro that previously failed

Config (~/.hermes/config.yaml):

model:
  base_url: https://api.deepseek.com
  default: deepseek-v4-flash
  provider: deepseek

Environment:

  • openai SDK 2.31.0
  • Python 3.12.8
  • macOS (darwin 25.4.0)

Error before patch (in ~/.hermes/logs/errors.log):

ERROR root: Non-retryable client error: Error code: 400 - {'error': {'message':
'The `reasoning_content` in the thinking mode must be passed back to the API.',
'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Happened on the first tool-call turn of an interactive hermes session, reproducible every time.

After applying this patch

Same session, same config, multiple tool-calling turns over ~45 min — zero reasoning_content errors in errors.log. All subsequent DeepSeek V4 Flash requests succeeded.

Notes on the fix

  • Mirrors exactly the existing Kimi /coding pattern (commit 2efb0ee) which solved the same class of bug for Moonshot — good consistency.
  • On openai SDK ≥ 2.x, ChatCompletionMessage.model_config['extra'] == 'allow', so reasoning_content is accessible as a direct attribute and _extract_reasoning already captures it. The gap was solely in _copy_reasoning_content_for_api when the assistant turn emits tool_calls without a reasoning trace — this PR closes exactly that gap.
  • PR fix: extract DeepSeek reasoning_content from model_extra #14973 is orthogonal (covers SDK < 1.60 where the field lands in model_extra); both can coexist.

LGTM 👍

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the fix, @tseek — this was a real and impactful bug.

This automated hermes-sweeper review found that the same fix has already landed on main independently, in a form that's a strict superset of this PR's change:

  • Commit 93a2d6b3fix: add DeepSeek reasoning_content echo for tool-call messages — landed the core fix (empty-string normalization in both _copy_reasoning_content_for_api and _build_assistant_message, plus the _handle_max_iterations edge case raised by @CompaCompu).
  • Commit d58b305adrefactor(deepseek-reasoning): consolidate detection into helpers + regression tests — refactored the detection logic into _needs_deepseek_tool_reasoning() / _needs_kimi_tool_reasoning() helpers and added 21 regression tests in tests/run_agent/test_deepseek_reasoning_content_echo.py.
  • The current run_agent.py:7737 and run_agent.py:7752 reflect this implementation in full on main.

Your analysis was correct and your patch was sound. The fix you contributed triggered the right fix to land — closing as implemented.

@teknium1 teknium1 closed this Apr 27, 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 P1 High — major feature broken, no workaround provider/deepseek DeepSeek API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants