openclaw - ✅(Solved) Fix DeepSeek V4: ensureDeepSeekV4ToolCallReasoningContent 未覆盖无 tool_calls 的 assistant 消息 [1 pull requests, 6 comments, 3 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
openclaw/openclaw#73417Fetched 2026-04-29 06:20:12
View on GitHub
Comments
6
Participants
3
Timeline
16
Reactions
0
Timeline (top)
commented ×6mentioned ×4subscribed ×4closed ×1

Fix Action

Fixed

PR fix notes

PR #73429: fix(plugin-sdk): backfill reasoning_content on all DeepSeek V4 assistant messages

Description (problem / solution / changelog)

Summary

  • Problem: #73417 — DeepSeek V4 returns 400 (The reasoning_content in the thinking mode must be passed back to the API) when conversation history contains plain assistant text replies without reasoning_content.
  • Root cause: ensureDeepSeekV4ToolCallReasoningContent only backfilled reasoning_content on assistant messages with tool_calls. DeepSeek V4's API requires it on all assistant messages when thinking mode is enabled — including plain text replies.
  • Fix: Remove the !Array.isArray(record.tool_calls) guard so reasoning_content: "" is backfilled on every assistant message missing it. Rename the helper to ensureDeepSeekV4AssistantReasoningContent to reflect the broader scope.
  • Verified locally by the issue reporter who patched the bundled JS and confirmed the fix resolves the 400.

Change Type

  • Bug fix

Scope

  • API / contracts

Linked Issue

  • Closes #73417
  • Related #71372 (original tool-call-only fix)

Root Cause

// Before: skips plain assistant messages
if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) continue;

// After: applies to all assistant messages
if (record.role !== "assistant") continue;

Per DeepSeek official docs: when thinking mode is enabled, reasoning_content must be passed back on all assistant messages in subsequent requests, not just tool-call turns.

Tests

6 new tests covering:

  1. Backfill on assistant messages with tool_calls (existing behavior preserved)
  2. Backfill on plain assistant messages without tool_calls (the fix)
  3. Preserves existing reasoning_content values
  4. Mixed conversation with both tool-call and plain assistant messages
  5. Strips reasoning_content when thinking is disabled
  6. Returns undefined when baseStreamFn is undefined

All tests exercise the exported createDeepSeekV4OpenAICompatibleThinkingWrapper wrapper.

Changed files

  • src/plugin-sdk/provider-stream-shared.test.ts (modified, +82/-0)
  • src/plugin-sdk/provider-stream-shared.ts (modified, +3/-3)

Code Example

The `reasoning_content` in the thinking mode must be passed back to the API.

---

if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) continue;

---

function ensureDeepSeekV4ToolCallReasoningContent(payload) {
    if (!Array.isArray(payload.messages)) return;
    for (const message of payload.messages) {
        if (!message || typeof message !== "object") continue;
        const record = message;
        if (record.role !== "assistant") continue;
        if (!("reasoning_content" in record)) record.reasoning_content = "";
    }
}
RAW_BUFFERClick to expand / collapse

复现步骤

  1. 在会话中开启 DeepSeek V4 thinking mode(如 /think high
  2. 使用 deepseek-v4-flashdeepseek-v4-pro 模型
  3. 正常对话(会产生普通 assistant 回复,不带 tool_calls)
  4. 继续对话,API 返回 400 错误:
The `reasoning_content` in the thinking mode must be passed back to the API.

原因分析

ensureDeepSeekV4ToolCallReasoningContent 函数的当前实现(provider-stream-shared-C_b_e9Jg.js):

if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) continue;

该函数只给有 tool_calls 的 assistant 消息添加 reasoning_content 占位符。

但根据 DeepSeek 官方文档

"进行了工具调用的轮次,在后续所有请求中,必须完整回传 reasoning_content 给 API。若您的代码中未正确回传 reasoning_content,API 会返回 400 报错。"

当历史消息被重放时,所有 assistant 消息(不仅仅是带 tool_calls 的)都需要携带 reasoning_content。当前实现跳过了普通 assistant 文本回复,导致 API 拒绝请求。

修复方案

移除 || !Array.isArray(record.tool_calls) 条件:

function ensureDeepSeekV4ToolCallReasoningContent(payload) {
    if (!Array.isArray(payload.messages)) return;
    for (const message of payload.messages) {
        if (!message || typeof message !== "object") continue;
        const record = message;
        if (record.role !== "assistant") continue;
        if (!("reasoning_content" in record)) record.reasoning_content = "";
    }
}

修复验证

本地修改 dist/provider-stream-shared-C_b_e9Jg.js 后重启 Gateway,测试通过。

相关

备注

  • 只能适配官方的deepseek api,因为有专门的认证,其他地方的api则不能用deepseek v4

extent analysis

TL;DR

Remove the condition || !Array.isArray(record.tool_calls) from the ensureDeepSeekV4ToolCallReasoningContent function to ensure all assistant messages carry reasoning_content.

Guidance

  • Identify the ensureDeepSeekV4ToolCallReasoningContent function in provider-stream-shared-C_b_e9Jg.js and modify it as suggested.
  • Verify that the function now adds reasoning_content to all assistant messages, not just those with tool_calls.
  • Test the modified function by replaying historical messages and checking for API errors.
  • Consult the DeepSeek official documentation for further guidance on implementing thinking mode correctly.

Example

function ensureDeepSeekV4ToolCallReasoningContent(payload) {
    if (!Array.isArray(payload.messages)) return;
    for (const message of payload.messages) {
        if (!message || typeof message !== "object") continue;
        const record = message;
        if (record.role !== "assistant") continue;
        if (!("reasoning_content" in record)) record.reasoning_content = "";
    }
}

Notes

This fix assumes that the issue is solely due to the missing reasoning_content in assistant messages without tool_calls. If other issues arise, further investigation may be necessary.

Recommendation

Apply the workaround by modifying the ensureDeepSeekV4ToolCallReasoningContent function, as this directly addresses the identified issue and is supported by the DeepSeek official documentation.

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

openclaw - ✅(Solved) Fix DeepSeek V4: ensureDeepSeekV4ToolCallReasoningContent 未覆盖无 tool_calls 的 assistant 消息 [1 pull requests, 6 comments, 3 participants]