Skip to content

fix: 修复claudecode使用deepseek-v4系列, 上游响应tool_use前没有thinking内容,导致后续对话报错#4597

Open
chi-cat wants to merge 1 commit intoQuantumNous:mainfrom
chi-cat:fix_deepseek_thinking
Open

fix: 修复claudecode使用deepseek-v4系列, 上游响应tool_use前没有thinking内容,导致后续对话报错#4597
chi-cat wants to merge 1 commit intoQuantumNous:mainfrom
chi-cat:fix_deepseek_thinking

Conversation

@chi-cat
Copy link
Copy Markdown

@chi-cat chi-cat commented May 2, 2026

⚠️ 提交说明 / PR Notice

修复claudecode使用接口deepseek-v4系列(api.deepseek.com/anthropic容易复现), 上游响应tool_use前没有thinking内容,导致后续对话报错(The content[].thinking in the thinking mode must be passed back to the API.),

  1. 目前已知deepseek-v4系列默认开启thinking模式quick_start,
  2. 又已知在两个 user 消息之间,如果模型未进行工具调用,则中间 assistant 的 reasoning_content 无需参与上下文拼接,在后续轮次中将其传入 API 会被忽略。在两个 user 消息之间,如果模型进行了工具调用,则中间 assistant 的 reasoning_content 需参与上下文拼接,在后续所有 user 交互轮次中必须回传给 API。 thinking_mode

📝 变更描述 / Description

修复为检测到开启deepseek-v4系列时,如果未禁用thinking时,如果assistant消息前没有thiking内容则自动补充空thiking内容。

错误消息体

[
   // {"memo": "更多历史消息"},
    {
      "content": [
        {
          "content": "warning: LF will be replaced by CRLF in docs/superpowers/specs/2026-05-01-client-naming-design.md.\nThe file will have its original line endings in your working directory\n[master 95ccf8a9] docs(spec): add client naming \u0026 address tracking design\n 1 file changed, 49 insertions(+)\n create mode 100644 docs/superpowers/specs/2026-05-01-client-naming-design.md",
          "is_error": false,
          "tool_use_id": "toolu_5862fee7544643348ce991ec",
          "type": "tool_result"
        }
      ],
      "role": "user"
    },
    {
      "content": [
        {
          "id": "toolu_9c16f9c821ff435eac6d59b7",
          "input": {
            "status": "completed",
            "taskId": "3"
          },
          "name": "TaskUpdate",
          "type": "tool_use"
        }
      ],
      "role": "assistant"
    },
    {
      "content": [
        {
          "content": "Updated task #3 status",
          "tool_use_id": "toolu_9c16f9c821ff435eac6d59b7",
          "type": "tool_result"
        }
      ],
      "role": "user"
    }
  ],
./test\ copy.sh 
{"error":{"type":"invalid_request_error","message":"The `content[].thinking` in the thinking mode must be passed back to the API. (request id: 202605021803088601583108268d9d6ZUp0IBtu)"},"type":"error"}

修复消息体

[
   // {"memo": "更多历史消息"},
    {
      "content": [
        {
          "content": "warning: LF will be replaced by CRLF in docs/superpowers/specs/2026-05-01-client-naming-design.md.\nThe file will have its original line endings in your working directory\n[master 95ccf8a9] docs(spec): add client naming \u0026 address tracking design\n 1 file changed, 49 insertions(+)\n create mode 100644 docs/superpowers/specs/2026-05-01-client-naming-design.md",
          "is_error": false,
          "tool_use_id": "toolu_5862fee7544643348ce991ec",
          "type": "tool_result"
        }
      ],
      "role": "user"
    },
    {
      "content": [
       // 自动补充一条空的thinking消息
        {
          "signature": "",
          "thinking": "",
          "type": "thinking"
        },
        {
          "id": "toolu_9c16f9c821ff435eac6d59b7",
          "input": {
            "status": "completed",
            "taskId": "3"
          },
          "name": "TaskUpdate",
          "type": "tool_use"
        }
      ],
      "role": "assistant"
    },
    {
      "content": [
        {
          "content": "Updated task #3 status",
          "tool_use_id": "toolu_9c16f9c821ff435eac6d59b7",
          "type": "tool_result"
        }
      ],
      "role": "user"
    }
  ],
./test\ copy.sh 
event: message_start
data: {"message":{"model":"deepseek-v4-pro","id":"msg_545486d9-1e4b-4eff-87a4-37476c53b2da","role":"assistant","type":"message","content":[],"usage":{"input_tokens":72770,"output_tokens":0}},"type":"message_start"}


event: ping
data: {"type":"ping"}


event: content_block_start
data: {"type":"content_block_start","content_block":{"type":"thinking","signature":"","thinking":""},"index":0}


event: content_block_delta
data: {"delta":{"type":"thinking_delta","thinking":"Spec"},"type":"content_block_delta","index":0}

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

#4515

Summary by CodeRabbit

  • Bug Fixes
    • Fixed the ordering of message components in Claude requests for DeepSeek V4 models, ensuring proper message structure and enhanced compatibility with advanced features.
    • Improved request processing for the Ali channel to automatically apply DeepSeek-specific message formatting adjustments, resulting in better request handling consistency and reduced formatting errors across integrated channels.

…The `content[].thinking` in the thinking mode must be passed back to the API.), 修复为检测到开启deepseek-v4系列时,自动补充空thiking内容。目前已知deepseek-v4系列默认开启thinking模式(https://api-docs.deepseek.com/zh-cn/quick_start/pricing), 又已知在两个 user 消息之间,如果模型未进行工具调用,则中间 assistant 的 reasoning_content 无需参与上下文拼接,在后续轮次中将其传入 API 会被忽略。在两个 user 消息之间,如果模型进行了工具调用,则中间 assistant 的 reasoning_content 需参与上下文拼接,在后续所有 user 交互轮次中必须回传给 API。 (https://api-docs.deepseek.com/zh-cn/guides/thinking_mode)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Walkthrough

A new EnsureThinkingBeforeToolUse function in the DeepSeek channel ensures that thinking blocks are prepended before tool_use blocks in DeepSeek V4 Claude requests when thinking is enabled. This function is integrated into both the DeepSeek and Ali adapters' Claude request conversion paths.

Changes

DeepSeek V4 Thinking Block Ordering

Layer / File(s) Summary
Core Logic Implementation
relay/channel/deepseek/thinking.go
New module adds EnsureThinkingBeforeToolUse and helpers isDeepSeekV4 and ensureThinkingInContentArray. For each assistant message in a DeepSeek V4 request with thinking enabled, if content contains tool_use blocks but lacks a leading thinking block, an empty thinking block is prepended at index 0.
DeepSeek Adapter Wiring
relay/channel/deepseek/adaptor.go
ConvertClaudeRequest now calls EnsureThinkingBeforeToolUse(claudeRequest) to enforce thinking block ordering before returning the converted request.
Ali Adapter Wiring
relay/channel/ali/adaptor.go
ConvertClaudeRequest now calls deepseek.EnsureThinkingBeforeToolUse(req) for the Ali Anthropic messages path. Import added for relay/channel/deepseek package.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • seefs001

Poem

🐰 A thinking block hops into place,
Before the tools join the race,
DeepSeek V4 now in order they stand,
With ali and deepseek channel hand-in-hand! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title is in Chinese and addresses fixing a specific issue with DeepSeek V4 models where thinking content is missing before tool_use, causing subsequent conversation errors.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/channel/deepseek/thinking.go`:
- Around line 40-43: The prefix check in isDeepSeekV4 is too strict
(HasPrefix("deepseek-v4-")) and mismatches supportsAliAnthropicMessages which
uses Contains("deepseek-v4"); update isDeepSeekV4 to use
strings.HasPrefix(modelName, "deepseek-v4") (or otherwise match the same
"deepseek-v4" substring logic used by supportsAliAnthropicMessages) so models
named "deepseek-v4" and variants like "deepseek-v4-pro" route consistently and
EnsureThinkingBeforeToolUse no longer silently no-ops for the bare alias; adjust
the implementation in isDeepSeekV4 accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: afb17dfd-9f2a-41f2-b8c7-77aaa45868d4

📥 Commits

Reviewing files that changed from the base of the PR and between dac55f0 and 11a979c.

📒 Files selected for processing (3)
  • relay/channel/ali/adaptor.go
  • relay/channel/deepseek/adaptor.go
  • relay/channel/deepseek/thinking.go

Comment on lines +40 to +43
// isDeepSeekV4 checks if the model name belongs to DeepSeek V4 series.
func isDeepSeekV4(modelName string) bool {
return strings.HasPrefix(modelName, "deepseek-v4-")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

isDeepSeekV4 and Ali's supportsAliAnthropicMessages use inconsistent matching strategies.

isDeepSeekV4 requires HasPrefix("deepseek-v4-") (trailing dash), but supportsAliAnthropicMessages uses Contains("deepseek-v4") (no dash). For all current model names (deepseek-v4-flash, deepseek-v4-pro) this is harmless — both checks pass. However, a model configured with the bare alias "deepseek-v4" would be routed through Ali's Anthropic messages path but EnsureThinkingBeforeToolUse would silently no-op, leaving the thinking-before-tool-use gap unfixed.

Consider aligning the two checks:

🛠 Proposed fix
 func isDeepSeekV4(modelName string) bool {
-	return strings.HasPrefix(modelName, "deepseek-v4-")
+	return strings.HasPrefix(modelName, "deepseek-v4-") || modelName == "deepseek-v4"
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// isDeepSeekV4 checks if the model name belongs to DeepSeek V4 series.
func isDeepSeekV4(modelName string) bool {
return strings.HasPrefix(modelName, "deepseek-v4-")
}
// isDeepSeekV4 checks if the model name belongs to DeepSeek V4 series.
func isDeepSeekV4(modelName string) bool {
return strings.HasPrefix(modelName, "deepseek-v4-") || modelName == "deepseek-v4"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/deepseek/thinking.go` around lines 40 - 43, The prefix check in
isDeepSeekV4 is too strict (HasPrefix("deepseek-v4-")) and mismatches
supportsAliAnthropicMessages which uses Contains("deepseek-v4"); update
isDeepSeekV4 to use strings.HasPrefix(modelName, "deepseek-v4") (or otherwise
match the same "deepseek-v4" substring logic used by
supportsAliAnthropicMessages) so models named "deepseek-v4" and variants like
"deepseek-v4-pro" route consistently and EnsureThinkingBeforeToolUse no longer
silently no-ops for the bare alias; adjust the implementation in isDeepSeekV4
accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant