Skip to content

fix(strict-echo): clear blank reasoning content on strict-echo Responses wire (#2169) - #2174

Merged
ranxianglei merged 2 commits into
masterfrom
2026-10-05_strict-echo-blank-reasoning
Oct 5, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-10-05_strict-echo-blank-reasoning

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #2169

Problem

Long Codex CLI sessions against DeepSeek's Responses wire 400'd on EVERY request (The 'reasoning_text' in the thinking mode must be passed back) once the client's resent history carried reasoning items whose content was whitespace-only reasoning_text. DeepSeek's validation rejects ANY reasoning content it did not issue — whitespace included (issue control experiments: replacing the blank text with real text still 400s; content-less / content:[] / summary-only forms all 200).

Provenance audit: bili never fabricates that shape. Verified against source, tag v0.1.184 and the published npm dist: the #1479 injection pushes a content-free item, the kernel round-trip re-emits raw reasoning items verbatim (coreToResponses, patchOriginalItem passthrough), and the #1029 marker strip is role-gated so it cannot touch reasoning items. The items ride INBOUND from the client's history and were forwarded verbatim — the missing piece was an outbound gate for them.

Second facet, same failure domain: both 400 learners matched only reasoning_content (chat-wire spelling); DeepSeek's Responses error spells it reasoning_text, so behind non-deepseek origins/models the learned flag never armed and every fresh session re-paid the 400.

Changes

Behavior changes (disclosed)

Surface Old New Why
strict-echo Responses upstreams reasoning items with whitespace-only content forwarded verbatim → guaranteed 400, session stuck content key removed at the outbound gate → accepted (issue control experiments) fixes the stuck-session 400 class
non-strict upstreams untouched untouched (change sits inside the enabled gate) wire fidelity
400 learning armed only on reasoning_content wording also arms on reasoning_text wording DeepSeek-compatible gateways behind relays learn instead of re-paying 400s every session

No config-surface change. No kernel change. No persistence change. No new env vars.

Tests

  • New unit suite in tests/reasoning-echo.test.ts (8 tests): predicate semantics, identity/no-mutation guarantees, conservative shape handling, disabled passthrough, combined orphan-run + blank-item pipeline, normalizeStrictEchoBody integration.
  • New full-pipeline test in tests/loop-strict-echo-responses.test.ts: drives DeepSeek's exact error wording through the proxy — req1 400 arms the learner (body verified byte-exact while the gate is off), req2 sanitized at the outbound gate with the [Bug] #762's strict-echo repair is chat-completions-only: Responses-wire requests still 400 with reasoning_content_missing after a compress fold #1479 blank injection still landing and the sentinel firing exactly once total.
  • Local targeted runs: those two files 53/53 pass; adjacent learner suites (loop-strict-echo, strict-echo-model-criterion, reasoning-echo-400) 4/4 pass; typecheck clean; build clean. Full regression left to CI.

…t 400s on the Responses wire (#2169)

DeepSeek thinking-mode validation rejects any reasoning item carrying content it did not issue — whitespace-only reasoning_text included ('reasoning_text must be passed back'). Blank items of that shape ride the client's resent history into bili requests and were forwarded verbatim, 400-ing every request of a long session. bili never fabricates them: the kernel round-trip re-emits raw reasoning items verbatim, patchOriginalItem passes them through, the #1479 injection is content-free, and the #1029 marker strip is role-gated — all verified against source, tag v0.1.184 and the published npm dist.

- normalizeStrictEchoResponsesInput clears 'content' from reasoning items whose parts are exclusively whitespace-only reasoning_text (lossless; DeepSeek accepts the content-less form per #2169 control experiments). Same gate as the existing injection; unknown/mixed shapes untouched.

- Both 400 learners (main path, compress loop) match the reasoning_text spelling in addition to reasoning_content, so DeepSeek-compatible gateways behind non-deepseek origins/models arm the flag instead of re-paying 400s.

Tests: unit suite for the sanitize predicate (identity/no-mutation/conservative-shape cases) + full pipeline test driving DeepSeek's exact error wording end-to-end. Closes #2169
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-05_strict-echo-blank-reasoning (5cb8f54)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.2228-pr.2174.336552b

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2174.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review 进行中(不重做,核对已完成的工作):

  • 代码已逐行核对:src/strict-echo.ts 出站门(predicate + 浅拷贝删 content、无空白时返回原数组保持 identity)、主路径 src/server.ts:4928 与 loop 重请求 src/loop/core.ts:449(709/755/1062/1102 四处 re-request body 全覆盖)均确认;learner regex 两处(server.ts forward / loop UpstreamHttpError)同步加宽为 /reasoning_(?:content|text)/i,无第三处遗漏。
  • 测试有效性:新增 pipeline 测试用 DeepSeek 原文错误体驱动 req1→400 武装 learner→req2 出站净化,且 req1 断言钉住了旧行为(gate 关时原样转发),有牙。
  • 本地验证(rebased head):PR 分支 rebase 到当前 master(cd6cbb9)干净;typecheck 干净、build 干净、定向测试 5 个文件 57/57 通过(含相邻 learner 套件)。全量回归交给 CI。
  • 回归面:disabled/无 reasoning items 时返回同一数组引用,零日志;唯一行为差 = strict-echo gate 武装后空白 reasoning content 出站被删键(session state 不动)。master 漂移 6 commits 全部是 fix: bound web admin session loading (paged list, summary index, lazy detail) #1939 web-sessions 区域,与 strict-echo 路径不相交,GitHub mergeable=true。

CI 剩 2 个 windows 测试矩阵 job 在跑,完成后给结构化七问结论。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 ## Review 结论(结构化七问)

① 是什么问题:DeepSeek Responses wire 上,客户端重发历史里带着 content 为纯空白 reasoning_text 的 reasoning item 时,每个请求都 400(The 'reasoning_text' in the thinking mode must be passed back),长会话直接卡死。附带同域第二面:400 learner 只匹配 chat-wire 拼写 reasoning_content,而 DeepSeek Responses 错误体拼的是 reasoning_text——非 deepseek origin/model 后面(中继/网关部署)学到的 flag 永远不武装,每个新会话重新付一次 400。

② 什么原因导致的:两层。表层:bili 把客户端 inbound 的 reasoning item 原样转发,缺出站闸门。根层:DeepSeek 校验拒绝任何它没发过的 reasoning content(空白也算——issue 对照实验证明:换真文本仍 400,清空/content:[]/仅 summary 全 200);而 bili 自身从不制造这个形状(#1479 注入是 content-free、kernel round-trip verbatim、#1029 marker strip role-gated,已在 master/v0.1.184/npm dist 三处核实)——所以缺的就是 strict-echo 条件下的出站净化。第二面是 learner regex 拼写覆盖不全,纯代码缺陷。

③ 怎么修复的:在源头决策点修,不在传输中遮症状——出站重建处(主路径 src/server.ts:4928 + loop 全部 4 处 re-request body src/loop/core.ts:709/755/1062/1102 经 withStrictEchoRepair)、既有 strict-echo gate 之下,对 parts 全部是空白 reasoning_text 的 reasoning item 删掉 content 键(无损——空白无信息量)。谓词保守:missing/non-array/空数组/外来 part 类型/非字符串 text/任何真实文本 → item 字节级不动;无事可做时返回同一数组引用(保住调用方 patched !== body.input 语义)。learner regex 两处同步加宽 /reasoning_(?:content|text)/i(严格超集)。

④ 是否有回归问题:无。逐项审过的既有行为:

⑤ 是否破坏缓存:否。不改任何缓存键/重试游标/去重键/persistence 格式;归一化只作用于每请求出站重建体。前缀缓存稳定性(head system 消息字节稳定)不受影响——且该场景下旧行为是每个请求都 400,本就不存在可破坏的工作缓存。issue 里第二个观察(fold-drift 请求无注入也 400)由同一机制覆盖:闸门对每次出站请求生效,与 #1479 注入是否触发无关。

⑥ 是否改了设计方向:否。沿用既有架构(KDD #7 双压缩模式纪律下的 proxy-mode 出站净化),无新子系统、无 kernel 变更、无 config surface 变更。

⑦ 是否引入了新的配置:无。无新 env var、无新 config key、无默认值变化。

证据链

  • 代码逐行核对:调用点全覆盖(主路径 + loop 4 处)、identity 保持、insert/clear 不相交、无第三处 learner 站点(grep 确认其余 reasoning_content 命中均为 adapter 流式字段/tag-echo-filter/plugin.ts 字段表,与本改动无关)。
  • 测试有效性:pipeline 测试用 DeepSeek 原文错误体驱动完整序列——req1(gate 关)断言空白 content 原样转发(钉死旧行为)→ 400 武装 learner;req2 出站净化("content" in rs === false、summary 保留)+ [Bug] #762's strict-echo repair is chat-completions-only: Responses-wire requests still 400 with reasoning_content_missing after a compress fold #1479 注入仍落位 + sentinel 全程恰好触发一次。有牙,bug 回归时抓得住。
  • 本地验证(rebased head):PR 分支 rebase 到当前 master(cd6cbb9)干净;typecheck 干净、build 干净;定向测试 5 文件(reasoning-echo / loop-strict-echo-responses / loop-strict-echo / strict-echo-model-criterion / reasoning-echo-400)57/57 通过。全量回归按纪律交给 CI。
  • CI:head 17e2615 上 21/21 全绿(ubuntu+windows × node 22/24 全量矩阵、e2e、registry e2e、image billing、OC WS、theorem、kernel-guard/version-guard/docs-sync)。
  • master 漂移:base 之后 6 commits 全部属 fix: bound web admin session loading (paged list, summary index, lazy detail) #1939 web-sessions 区域,与 strict-echo 路径不相交,GitHub mergeable=true。

判定:可合。 披露的行为变更(见 PR 表格)与代码事实一致,无隐藏漂移。

…sion fixture

The issue author provided a redacted copy of the exact outbound body DeepSeek rejected (89 items; the 5 blank whitespace-content reasoning echoes preserved verbatim, all other text replaced by length placeholders, item order/types/call_ids unchanged). Drives the real shape through normalizeStrictEchoResponsesInput: the 5 blanks lose their content key, every other item stays the same reference (byte-exact), no #1479 injection needed for this body. Closes #2169
@ranxianglei
ranxianglei merged commit 8bca8bf into master Oct 5, 2026
21 checks passed
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.47-master.8bca8bf (rolling master tag).

npm install -g billion-context@0.0.47-master.8bca8bf  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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] Responses 线路 strict-echo (#1479) 注入的空白 reasoning 被严格后端拒绝(DeepSeek: reasoning_text must be passed back)

1 participant