Restore reasoning_text when replaying history to DeepSeek - #2449
Open
JunkaiWang-TheoPhy wants to merge 1 commit into
Open
JunkaiWang-TheoPhy wants to merge 1 commit into
JunkaiWang-TheoPhy wants to merge 1 commit into
Conversation
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 路径
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #2433 (split out of #2434 for reviewability).
Problem
DeepSeek's thinking mode requires
reasoning_texton every replayed reasoning item:The official Codex backend requires the opposite — reasoning items with
content: []— which is whyresponses_reasoning_sanitize.gomigrates DeepSeek's visible reasoning intosummaryand emptiescontenton 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:reasoningwithcontent:[{reasoning_text}]+ assistant messagereasoningwithsummaryonly,content: []reasoning_text must be passed backreasoningitem at all, but an assistant message or tool pairObserved 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
contentand a non-emptysummary, the summary text is written back asreasoning_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.gogains only the call site.Verification
go test -count=1 . -run TestProviderGatewayRestoreReasoning— 3 new cases: summary restored, existingcontentuntouched, non-DeepSeek gateways byte-identical.Not covered
/responses/compactand websocket paths.Note
Independent of #2447 and #2448 — separate file, no shared functions.