Skip to content

fix(relay): preserve reasoning_content and thinking block in playground and Claude conversion - #5125

Closed
mujian2026 wants to merge 1 commit into
QuantumNous:mainfrom
mujian2026:fix/deepseek-reasoning-thinking-conversion
Closed

fix(relay): preserve reasoning_content and thinking block in playground and Claude conversion#5125
mujian2026 wants to merge 1 commit into
QuantumNous:mainfrom
mujian2026:fix/deepseek-reasoning-thinking-conversion

Conversation

@mujian2026

@mujian2026 mujian2026 commented May 26, 2026

Copy link
Copy Markdown

Fixes DeepSeek reasoning_content 400 error in multi-turn conversations for both default/classic playgrounds and Claude-to-OpenAI relay path. Closes #4543. Resolves #4543

Summary by CodeRabbit

Release Notes

New Features

  • Added support for reasoning content in chat messages across all API integrations.
  • Messages now properly include, synchronize, and preserve reasoning text throughout the conversation workflow and custom request bodies.
  • Reasoning content is correctly formatted and converted when exchanging messages with different API providers.

Review Change Stack

…nd and Claude conversion

Fixes DeepSeek reasoning_content 400 error in multi-turn conversations for both default/classic playgrounds and Claude-to-OpenAI relay path.
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR adds end-to-end support for reasoning content across the message handling pipeline. The ChatCompletionMessage type is extended with an optional reasoning_content field, the backend conversion logic maps Claude "thinking" parts to OpenAI ReasoningContent, and frontend message formatting in both web implementations now serializes reasoning content into API payloads. The classic UI also updates message hashing and payload syncing to include reasoning.

Changes

Reasoning content end-to-end support

Layer / File(s) Summary
Message type with reasoning support
web/default/src/features/playground/types.ts
The ChatCompletionMessage interface now includes an optional reasoning_content?: string field alongside role and content.
Claude thinking to OpenAI reasoning conversion
service/convert.go
When converting Claude messages, the new "thinking" case in the media-part switch maps mediaMsg.Thinking into openAIMessage.ReasoningContent.
Message formatting with reasoning content
web/classic/src/helpers/utils.jsx, web/default/src/features/playground/lib/message-utils.ts
Both web implementations extend formatMessageForAPI to include reasoning_content in the API payload: classic reads from message.reasoningContent, default reads from message.reasoning?.content.
Classic UI message sync and hash update
web/classic/src/hooks/playground/useSyncMessageAndCustomBody.js
The hook now includes reasoning_content in both message hash computation and custom payload generation, aligning them with the reasoning field already read during sync.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related PRs

  • QuantumNous/new-api#1235: Both PRs add passthrough/conversion for "reasoning"/"thinking" content in the request/response conversion pipeline (main PR maps Claude thinking to reasoning_content/ReasoningContent, while retrieved PR parses an incoming reasoning field and turns it into Claude Thinking).
  • QuantumNous/new-api#3120: Both PRs modify the Claude→OpenAI request conversion logic to handle Claude "thinking" for OpenRouter by mapping/structuring reasoning fields (including newly supported "enabled/adaptive" reasoning behavior), so the main PR's reasoning_content changes are tightly connected to the retrieved PR's thinking-to-OpenRouter conversion.
  • QuantumNous/new-api#4520: Both PRs implement/request reasoning content handling by mapping ReasoningContent/reasoning_content during message conversion/forwarding (main PR adds reasoning_content and maps Claude "thinking"; retrieved PR changes the Go dto.Message reasoning fields and relay usage to preserve empty strings).

Suggested reviewers

  • seefs001
  • Calcium-Ion

Poem

🐰 A whisper of thought now finds its way,
Through layers both backend and front at play,
From Claude's thinking to OpenAI's mind,
Reasoning content, carefully aligned—
With hashes and syncs, the pipeline sings! 🎶

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main changes: preserving reasoning_content and thinking blocks across playground implementations and Claude conversion, which matches the changeset's primary objective.
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

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/classic/src/hooks/playground/useSyncMessageAndCustomBody.js (1)

37-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

reasoning_content is written, but reverse sync still reads the wrong key.

You now serialize snake_case (reasoning_content), but syncCustomBodyToMessage still reads camelCase at Line 125 (msg.reasoningContent). That drops reasoning on custom-body → message round trips.

Proposed fix
-          ...(msg.role === MESSAGE_ROLES.ASSISTANT && {
-            reasoningContent: msg.reasoningContent || '',
+          ...(msg.role === MESSAGE_ROLES.ASSISTANT && {
+            reasoningContent: msg.reasoning_content ?? msg.reasoningContent ?? '',
             isReasoningExpanded: false,
           }),

Also applies to: 78-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/classic/src/hooks/playground/useSyncMessageAndCustomBody.js` around lines
37 - 44, getMessageHash serializes the reasoning field as snake_case
(reasoning_content) but syncCustomBodyToMessage still reads camelCase
(reasoningContent), causing lost reasoning on round trips; update
syncCustomBodyToMessage to accept both keys (e.g. use msg.reasoning_content ??
msg.reasoningContent) so it reads the serialized snake_case while remaining
backward-compatible, and make the other mapping that sets reasoning_content (the
second occurrence where messages are mapped to custom body) consistent or
likewise tolerant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/convert.go`:
- Around line 152-155: The code maps Claude "thinking" into
openAIMessage.ReasoningContent but the append/filter that pushes openAIMessage
into the messages slice (the guard around the append near the current append
location) only checks for Content or ToolCall and therefore drops messages that
only contain ReasoningContent; update that append guard to also accept messages
where openAIMessage.ReasoningContent is non-nil/non-empty (or mediaMsg.Thinking
was set) so reasoning-only messages are preserved when appending the
openAIMessage.

---

Outside diff comments:
In `@web/classic/src/hooks/playground/useSyncMessageAndCustomBody.js`:
- Around line 37-44: getMessageHash serializes the reasoning field as snake_case
(reasoning_content) but syncCustomBodyToMessage still reads camelCase
(reasoningContent), causing lost reasoning on round trips; update
syncCustomBodyToMessage to accept both keys (e.g. use msg.reasoning_content ??
msg.reasoningContent) so it reads the serialized snake_case while remaining
backward-compatible, and make the other mapping that sets reasoning_content (the
second occurrence where messages are mapped to custom body) consistent or
likewise tolerant.
🪄 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: 3da1069c-7f42-4143-8643-832c69dc3ec4

📥 Commits

Reviewing files that changed from the base of the PR and between 74985fa and da356f4.

📒 Files selected for processing (5)
  • service/convert.go
  • web/classic/src/helpers/utils.jsx
  • web/classic/src/hooks/playground/useSyncMessageAndCustomBody.js
  • web/default/src/features/playground/lib/message-utils.ts
  • web/default/src/features/playground/types.ts

Comment thread service/convert.go
Comment on lines +152 to +155
case "thinking":
if mediaMsg.Thinking != nil {
openAIMessage.ReasoningContent = mediaMsg.Thinking
}

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 | 🟠 Major | ⚡ Quick win

Reasoning-only Claude messages can still be dropped.

This mapping is correct, but the append guard at Line 213 only keeps messages with content/tool calls. If a message has only thinking, ReasoningContent is lost.

Proposed fix
-       if len(openAIMessage.ParseContent()) > 0 || len(openAIMessage.ToolCalls) > 0 {
+       if len(openAIMessage.ParseContent()) > 0 || len(openAIMessage.ToolCalls) > 0 || openAIMessage.ReasoningContent != nil {
            openAIMessages = append(openAIMessages, openAIMessage)
        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/convert.go` around lines 152 - 155, The code maps Claude "thinking"
into openAIMessage.ReasoningContent but the append/filter that pushes
openAIMessage into the messages slice (the guard around the append near the
current append location) only checks for Content or ToolCall and therefore drops
messages that only contain ReasoningContent; update that append guard to also
accept messages where openAIMessage.ReasoningContent is non-nil/non-empty (or
mediaMsg.Thinking was set) so reasoning-only messages are preserved when
appending the openAIMessage.

@seefs001

Copy link
Copy Markdown
Collaborator

重复PR,提交PR请遵守PR模版进行事项确认。

@seefs001 seefs001 closed this May 26, 2026
@meesii

meesii commented May 26, 2026

Copy link
Copy Markdown

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

@seefs001

Copy link
Copy Markdown
Collaborator

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

可以看issue关联的PR。
不进行合并是由于逻辑变更在通用转换逻辑里,不是所有的供应商都支持这种交错思考模式,这样的处理方式不是很妥当,当前只支持使用支持这种交叉思考模式的客户端对接原生API端点。

@meesii

meesii commented May 26, 2026

Copy link
Copy Markdown

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

可以看issue关联的PR。 不进行合并是由于逻辑变更在通用转换逻辑里,不是所有的供应商都支持这种交错思考模式,这样的处理方式不是很妥当,当前只支持使用支持这种交叉思考模式的客户端对接原生API端点。

一分钟前刚测试的,和之前一样报错。
image

@mujian2026

Copy link
Copy Markdown
Author

好的,我看到后端 Claude 格式的转换问题已经在 PR #4591#4497 中有了更全面的修复。
不过我注意到目前官方的这些修复主要集中在后端 API 层面,而在项目的自带 Web Playground 控制台(包括 default 和 classic 前端)在多轮对话时,依然会因为前端代码未保留并回传 reasoning_content 而导致 DeepSeek 模型返回 400 报错。
您提到这是一个重复的 PR,请问对于这部分前端特有的 Bug 修复,我是应该在这个 PR 里剔除后端的修改仅保留前端代码,还是关掉当前 PR,重新开启一个专门针对前端 Playground 的新 PR 呢?

@seefs001

Copy link
Copy Markdown
Collaborator

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

可以看issue关联的PR。 不进行合并是由于逻辑变更在通用转换逻辑里,不是所有的供应商都支持这种交错思考模式,这样的处理方式不是很妥当,当前只支持使用支持这种交叉思考模式的客户端对接原生API端点。

一分钟前刚测试的,和之前一样报错。

image

如果你不愿意认真阅读别人的消息,建议去和AI交流。

@seefs001

Copy link
Copy Markdown
Collaborator

好的,我看到后端 Claude 格式的转换问题已经在 PR #4591#4497 中有了更全面的修复。 不过我注意到目前官方的这些修复主要集中在后端 API 层面,而在项目的自带 Web Playground 控制台(包括 default 和 classic 前端)在多轮对话时,依然会因为前端代码未保留并回传 reasoning_content 而导致 DeepSeek 模型返回 400 报错。 您提到这是一个重复的 PR,请问对于这部分前端特有的 Bug 修复,我是应该在这个 PR 里剔除后端的修改仅保留前端代码,还是关掉当前 PR,重新开启一个专门针对前端 Playground 的新 PR 呢?

Playground理论上是没有这个问题的,要触发这个问题需要tool call,现在Playground还没这么健全

@meesii

meesii commented May 26, 2026

Copy link
Copy Markdown

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

可以看issue关联的PR。 不进行合并是由于逻辑变更在通用转换逻辑里,不是所有的供应商都支持这种交错思考模式,这样的处理方式不是很妥当,当前只支持使用支持这种交叉思考模式的客户端对接原生API端点。

一分钟前刚测试的,和之前一样报错。
image

如果你不愿意认真阅读别人的消息,建议去和AI交流。

1、我不是项目的核心贡献者,不可能和你一样了解项目;
2、使用中我遇到问题找到了相同的 issue #4543,并且关注了这个问题;
3、#4543 中关联的pr 两个是关闭状态,一个是open状态,从我的角度出发问题就是没解决;
4、我回复第一条评论之前搜索了相关的pr,只有一个3周前的合并,但是#4543 的问题依旧。看到有人提交pr但是你说重复了,所以提出了疑问

@mujian2026

Copy link
Copy Markdown
Author

重复PR,提交PR请遵守PR模版进行事项确认。

哪个PR修复这个问题了?我现在是 v1.0.0-rc.8 问题依旧。

可以看issue关联的PR。 不进行合并是由于逻辑变更在通用转换逻辑里,不是所有的供应商都支持这种交错思考模式,这样的处理方式不是很妥当,当前只支持使用支持这种交叉思考模式的客户端对接原生API端点。

一分钟前刚测试的,和之前一样报错。
image

如果你不愿意认真阅读别人的消息,建议去和AI交流。

1、我不是项目的核心贡献者,不可能和你一样了解项目; 2、使用中我遇到问题找到了相同的 issue #4543,并且关注了这个问题; 3、#4543 中关联的pr 两个是关闭状态,一个是open状态,从我的角度出发问题就是没解决; 4、我回复第一条评论之前搜索了相关的pr,只有一个3周前的合并,但是#4543 的问题依旧。看到有人提交pr但是你说重复了,所以提出了疑问

open的那个确实 也没有合并 ,所以并没有修复 。
我这个提交确实 是可以用的,你如果想用,可以根据我这个修改把代码改一下,我自用是ok的

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.

[BUG] ClaudeCode 使用 OpenAI 格式接入的 Deepseek V4 上游会遇到 400 reasoning_content 报错

3 participants