openclaw - 💡(How to fix) Fix Bug: DeepSeek V4 reasoning_content silently dropped, causing 400 on multi-turn conversations [1 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
openclaw/openclaw#72915Fetched 2026-04-28 06:30:21
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
1
Timeline (top)
commented ×1

Error Message

When using DeepSeek V4 models (deepseek-v4-pro, deepseek-v4-flash) with multi-turn conversations, the assistant's reasoning_content from previous turns is silently dropped when constructing follow-up API requests. This causes DeepSeek's API to return a 400 error: 4. API Error: The subsequent request to DeepSeek is missing reasoning_content in the assistant message history, which DeepSeek requires. Result: 400 error.

Root Cause

The issue is in convertMessagesToInputItems (in pi-embedded-C7LhuXsi.js):

  1. Receiving: DeepSeek returns reasoning_content in streaming deltas. OpenClaw correctly identifies it and stores it as a thinking block with thinkingSignature: "reasoning_content" (a plain string - the field name).

    const reasoningField = [
      "reasoning_content",
      "reasoning",
      "reasoning_text"
    ].find((field) => {
      const value = choice.delta[field];
      return typeof value === "string" && value.length > 0;
    });
  2. Replaying: When building subsequent API requests, convertMessagesToInputItems encounters thinking blocks and calls parseThinkingSignature():

    if (block.type === "thinking") {
      const reasoningItem = parseThinkingSignature(block.thinkingSignature);
      if (reasoningItem) items.push(reasoningItem);
      continue;
    }
  3. Failure: parseThinkingSignature expects a JSON-parseable string with an rs_ prefixed ID (OpenAI Responses API format):

    function parseThinkingSignature(value) {
      if (typeof value !== "string" || value.trim().length === 0) return null;
      try {
        const signature = toReasoningSignature(JSON.parse(value));
        return signature ? parseReasoningItem(signature) : null;
      } catch {
        return null;  // <-- "reasoning_content" is not JSON, so this fires
      }
    }

    Since thinkingSignature is the plain string "reasoning_content", JSON.parse("reasoning_content") throws, the catch returns null, and the entire thinking block is silently discarded.

  4. API Error: The subsequent request to DeepSeek is missing reasoning_content in the assistant message history, which DeepSeek requires. Result: 400 error.

Fix Action

Workaround

  1. Use deepseek-chat (non-thinking mode) instead of V4 models, if still available
  2. Use a proxy pool (e.g. qclaw/pool-deepseek-v4-pro) which may handle the conversion at the proxy layer
  3. This is a cross-ecosystem bug also affecting OpenCode, Qwen Code, Roo Code, and other tools using @mariozechner/pi-ai

Code Example

const reasoningField = [
     "reasoning_content",
     "reasoning",
     "reasoning_text"
   ].find((field) => {
     const value = choice.delta[field];
     return typeof value === "string" && value.length > 0;
   });

---

if (block.type === "thinking") {
     const reasoningItem = parseThinkingSignature(block.thinkingSignature);
     if (reasoningItem) items.push(reasoningItem);
     continue;
   }

---

function parseThinkingSignature(value) {
     if (typeof value !== "string" || value.trim().length === 0) return null;
     try {
       const signature = toReasoningSignature(JSON.parse(value));
       return signature ? parseReasoningItem(signature) : null;
     } catch {
       return null;  // <-- "reasoning_content" is not JSON, so this fires
     }
   }

---

{
  "role": "assistant",
  "content": "actual response",
  "reasoning_content": "thinking process..."
}

---

if (block.type === "thinking") {
  pushAssistantText(currentTextPhase);
  
  // Existing: OpenAI Responses API format (rs_ prefix JSON)
  const reasoningItem = parseThinkingSignature(block.thinkingSignature);
  if (reasoningItem) {
    items.push(reasoningItem);
    continue;
  }
  
  // NEW: DeepSeek native format (plain string field name like "reasoning_content")
  if (["reasoning_content", "reasoning", "reasoning_text"].includes(block.thinkingSignature)) {
    items.push({
      type: "message",
      role: "assistant",
      content: block.thinking,
      reasoning_content: block.thinking
    });
    continue;
  }
}
RAW_BUFFERClick to expand / collapse

Bug Description

When using DeepSeek V4 models (deepseek-v4-pro, deepseek-v4-flash) with multi-turn conversations, the assistant's reasoning_content from previous turns is silently dropped when constructing follow-up API requests. This causes DeepSeek's API to return a 400 error:

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

This affects all AI tools built on @mariozechner/pi-ai / OpenClaw (OpenCode, Qwen Code, Roo Code, etc.), making DeepSeek V4 models unusable for multi-turn conversations.

Root Cause

The issue is in convertMessagesToInputItems (in pi-embedded-C7LhuXsi.js):

  1. Receiving: DeepSeek returns reasoning_content in streaming deltas. OpenClaw correctly identifies it and stores it as a thinking block with thinkingSignature: "reasoning_content" (a plain string - the field name).

    const reasoningField = [
      "reasoning_content",
      "reasoning",
      "reasoning_text"
    ].find((field) => {
      const value = choice.delta[field];
      return typeof value === "string" && value.length > 0;
    });
  2. Replaying: When building subsequent API requests, convertMessagesToInputItems encounters thinking blocks and calls parseThinkingSignature():

    if (block.type === "thinking") {
      const reasoningItem = parseThinkingSignature(block.thinkingSignature);
      if (reasoningItem) items.push(reasoningItem);
      continue;
    }
  3. Failure: parseThinkingSignature expects a JSON-parseable string with an rs_ prefixed ID (OpenAI Responses API format):

    function parseThinkingSignature(value) {
      if (typeof value !== "string" || value.trim().length === 0) return null;
      try {
        const signature = toReasoningSignature(JSON.parse(value));
        return signature ? parseReasoningItem(signature) : null;
      } catch {
        return null;  // <-- "reasoning_content" is not JSON, so this fires
      }
    }

    Since thinkingSignature is the plain string "reasoning_content", JSON.parse("reasoning_content") throws, the catch returns null, and the entire thinking block is silently discarded.

  4. API Error: The subsequent request to DeepSeek is missing reasoning_content in the assistant message history, which DeepSeek requires. Result: 400 error.

Expected Behavior

When thinkingSignature is a plain string like "reasoning_content" (DeepSeek's native field name, as opposed to an OpenAI Responses API JSON signature), the system should serialize the thinking block back as a reasoning_content field on the assistant message, per the DeepSeek API specification:

{
  "role": "assistant",
  "content": "actual response",
  "reasoning_content": "thinking process..."
}

Environment

  • OpenClaw version: 2026.4.5 (3e72c03)
  • Provider: DeepSeek (deepseek-native endpoint class)
  • Models affected: deepseek-v4-pro, deepseek-v4-flash (all V4 models with default thinking: enabled)
  • API adapter: openai-completions

Workaround

  1. Use deepseek-chat (non-thinking mode) instead of V4 models, if still available
  2. Use a proxy pool (e.g. qclaw/pool-deepseek-v4-pro) which may handle the conversion at the proxy layer
  3. This is a cross-ecosystem bug also affecting OpenCode, Qwen Code, Roo Code, and other tools using @mariozechner/pi-ai

Suggested Fix

In convertMessagesToInputItems, add handling for DeepSeek's native reasoning_content signature format:

if (block.type === "thinking") {
  pushAssistantText(currentTextPhase);
  
  // Existing: OpenAI Responses API format (rs_ prefix JSON)
  const reasoningItem = parseThinkingSignature(block.thinkingSignature);
  if (reasoningItem) {
    items.push(reasoningItem);
    continue;
  }
  
  // NEW: DeepSeek native format (plain string field name like "reasoning_content")
  if (["reasoning_content", "reasoning", "reasoning_text"].includes(block.thinkingSignature)) {
    items.push({
      type: "message",
      role: "assistant",
      content: block.thinking,
      reasoning_content: block.thinking
    });
    continue;
  }
}

extent analysis

TL;DR

The most likely fix is to modify the convertMessagesToInputItems function to handle DeepSeek's native reasoning_content signature format.

Guidance

  • Identify the convertMessagesToInputItems function in pi-embedded-C7LhuXsi.js and add a conditional check for DeepSeek's native reasoning_content signature format.
  • Update the parseThinkingSignature function to handle plain string signatures like "reasoning_content".
  • Verify that the modified function correctly serializes thinking blocks as reasoning_content fields on assistant messages.
  • Test the fix with DeepSeek V4 models and multi-turn conversations to ensure the 400 error is resolved.

Example

if (block.type === "thinking") {
  // ...
  if (["reasoning_content", "reasoning", "reasoning_text"].includes(block.thinkingSignature)) {
    items.push({
      type: "message",
      role: "assistant",
      content: block.thinking,
      reasoning_content: block.thinking
    });
    continue;
  }
}

Notes

This fix assumes that the thinkingSignature field is a plain string like "reasoning_content" for DeepSeek V4 models. If other models or formats are used, additional handling may be required.

Recommendation

Apply the suggested fix to the convertMessagesToInputItems function to handle DeepSeek's native reasoning_content signature format, as it directly addresses the root cause of the issue.

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 - 💡(How to fix) Fix Bug: DeepSeek V4 reasoning_content silently dropped, causing 400 on multi-turn conversations [1 comments, 2 participants]