Skip to content

Restore reasoning_text when replaying history to DeepSeek - #2449

Open
JunkaiWang-TheoPhy wants to merge 1 commit into
jlcodes99:mainfrom
JunkaiWang-TheoPhy:fix/deepseek-reasoning-text-replay
Open

JunkaiWang-TheoPhy wants to merge 1 commit into
jlcodes99:mainfrom
JunkaiWang-TheoPhy:fix/deepseek-reasoning-text-replay

Conversation

@JunkaiWang-TheoPhy

@JunkaiWang-TheoPhy JunkaiWang-TheoPhy commented Sep 15, 2026

Copy link
Copy Markdown

Part of #2433 (split out of #2434 for reviewability).

Problem

DeepSeek's thinking mode requires reasoning_text on every replayed reasoning item:

400 {"error":{"message":"The `reasoning_text` in the thinking mode must be passed back to the API.","type":"invalid_request_error"}}

The official Codex backend requires the opposite — reasoning items with content: [] — which is why responses_reasoning_sanitize.go migrates DeepSeek's visible reasoning into summary and empties content on the response path. That sanitisation is deliberate (it keeps a thread usable after switching to an official account), but it makes the two upstreams demand contradictory shapes for the same field: DeepSeek's own transcripts are stored summary-only and are rejected the moment they are replayed to DeepSeek.

Measured directly against https://api.deepseek.com/responses:

input result
reasoning with content:[{reasoning_text}] + assistant message 200
reasoning with summary only, content: [] 400 reasoning_text must be passed back
no reasoning item at all, but an assistant message or tool pair 400 (same)

Observed in production on this machine three times in one day (09:00, 14:35, 22:16) across unrelated threads; each failure leaves the thread unable to continue until it is reloaded.

Change

A reverse normalisation on the request path, scoped to the DeepSeek gateway: when a reasoning item has an empty content and a non-empty summary, the summary text is written back as reasoning_text. Nothing is invented — the text came from that model's own thinking — and official-bound requests keep the summary-only shape. The response-path sanitiser is untouched.

Logic lives in a new provider_gateway_reasoning.go; provider_gateway.go gains only the call site.

Verification

  • go test -count=1 . -run TestProviderGatewayRestoreReasoning — 3 new cases: summary restored, existing content untouched, non-DeepSeek gateways byte-identical.
  • Live: all four shapes (summary-only + assistant, no reasoning + tool pair, summary-only + tool pair, reasoning_text + assistant) return 200 after deploy, and a real two-turn session (turn 1 executes a tool, turn 2 replays that history) completes normally.

Not covered

/responses/compact and websocket paths.

Note

Independent of #2447 and #2448 — separate file, no shared functions.

DeepSeek 的思考模式要求历史回放里的推理项必须带 reasoning_text:

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

而官方 Codex 后端只接受 content 为空数组的推理项(见 responses_reasoning_sanitize.go)。网关在
响应出口为了让同一份历史能切回官方账号,会把上游返回的 content 迁移进 summary 并清空 content。
两个上游对同一字段的要求正好相反,于是 DeepSeek 自己产生的历史再发回 DeepSeek 时必然失败。

实测(直连 https://api.deepseek.com):

  reasoning 带 content:[{reasoning_text}] + 助手消息 -> 200
  reasoning 只有 summary、content 为空            -> 400 reasoning_text must be passed back
  无 reasoning 但有助手消息/工具配对               -> 400(同上)

本补丁在请求出口做反向补全:content 为空而 summary 有文本时,把 summary 文本写回
reasoning_text。不新增信息——那段文字本来就来自该模型自己的思考;发往官方账号的请求保持
summary-only 形状。逻辑独立成 provider_gateway_reasoning.go,provider_gateway.go 只增加一个
调用点,不触碰既有的响应侧清洗。

Constraint: 两个上游要求相反的字段形状,只能在各自方向分别归一化
Rejected: 直接去掉推理项 | 实测无 reasoning 且存在助手/工具内容时同样 400
Rejected: 改动响应出口的清洗 | 会让历史无法切回官方账号,是 1.3.53 有意为之的兼容
Confidence: high
Scope-risk: narrow
Directive: 推理项归一化必须区分方向——出口给官方用 summary,出口给第三方用 reasoning_text
Tested: go test -count=1 . -run TestProviderGatewayRestoreReasoning;3 个新用例(summary 回填、已有 content 不动、非 DeepSeek 不改写)
Not-tested: /responses/compact 与 websocket 路径
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-15T14:38:38.119603Z aa06bda PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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