Repository navigation
[PR] fix: salvage bad-shape-A compress args at the execution layer, all lanes (#4889) - #4891
ranxianglei wants to merge 12 commits into
Conversation
…nes (#4889) parseCompressInput now wraps a bare line-form content value ({"content": <unquoted text>}) as a JSON string and re-parses it when the kernel ladder recovers nothing — the same rescue the plugin relay performed on the Anthropic wire only (#2591), now shared by every lane: proxy-mode loop/JSON rewrite paths and any host that executes compress through the proxy. Adoption discipline matches #3780/#1494: fires only after a failed parse, adopted only when strictly more ranges result. The wrap step is extracted from salvageCompressArgs into tryBareContentWrap so both call sites run one implementation instead of two regex copies (salvageCompressArgs behavior unchanged, pinned by the #2580 suite). New subpath export billion-context/salvage (repairCompressArgs): provider plugins sitting between model and host can repair compress arguments before the host's strict JSON.parse with the same ladder — strict JSON in, null = leave as-is out (#1039 boundary preserved).
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.7368-pr.4891.090de4eThis exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr4891.tgz
npm install -g packageInstall instructions are refreshed on each push. |
…graph gate (#4889) check-test-import-graph.mjs keeps a hardcoded ENTRY_FILES list in sync with tsup.config.ts (drift makes new entries look unreachable). The salvage PR added src/salvage.ts to tsup.config.ts, package.json exports and check-unused-exports.mjs but missed this gate's list, so the gate failed on tests/issue4889-bare-content-execution-salvage.test.ts importing the module. Master head is green — PR-specific failure, not inherited.
(ai: vllm-qwen/qwen3.8-27b) CI 修复完成: 1. 是什么问题:CI 门禁 2. 什么原因导致的:该门禁( 3. 影响范围与严重程度:仅本 PR 的 CI 红;运行时行为零影响—— 4. 怎么修复的:在 check-test-import-graph.mjs 的 ENTRY_FILES 补一行 5. 是否有回归问题:无。改动仅 CI 守卫脚本 +1 行,不触碰任何运行时代码;既有门禁语义(白名单、retired-pin 标记判定)逐条不变,本地输出与修复前除 salvage 条目外完全一致。 6. 是否破坏缓存:不涉及。 7. 是否改了设计方向:否。 8. 是否引入了新的配置:无。 已在 #4599(CI 总控)留一行结论。 |
…tent-salvage-all-lanes
…tent-salvage-all-lanes
(ai: qwen/qwen3.8-27b) Merge-guard sync done: merged origin/master into the PR branch — two clean merges (master moved mid-sync: 52cd3b8 → 0cf53c8), zero conflicts; pushed head 697ce5e. State after push: branch up-to-date with base (no longer behind), mergeable=true, mergeable_state=blocked awaiting the fresh CI run (6 success / 15 queued / 0 failures so far); local typecheck + build + targeted salvage tests (issue4889/1502/2580) all green before pushing. |
…tent-salvage-all-lanes
(ai: qwen/qwen3.8-27b) Sync round 3: merged origin/master e4cc685 (PR #4856/#4855 hermes compression-channel header) into the branch — zero conflicts (its only shared file src/plugin.ts is untouched by this PR's own commits) → pushed 0b13709; local verification green (typecheck/build/issue4889 suite + both import-graph gates); PR now mergeable=true against base e4cc685 with CI starting fresh (2 success / 19 queued / 0 failures so far). |
用
|
| 输入 | repairCompressArgs(raw) |
判读 |
|---|---|---|
本单逐字坏样本(5713 字符,{"content": m00186–m00289 … 裸文本 + 42 个真实换行) |
返回 5895 字符严格 JSON,JSON.parse 通过,content 为字符串(line-form 原样保留,没有强行转数组) |
规则命中,与 issue 里的预期一致 |
合法 JSON {"content":"m00001–m00002 摘要\n正文"} |
null |
#1039 透传不变量成立 |
| 同一份合法 JSON 从 30% / 60% / 95% 处截断 | null × 3 |
这条对我们最关键:gateway 侧要靠它把「截断」和「写坏」分开,截断仍走既有的 max-tokens,不会被误「修复」 |
单引号形态 {'content': 'm00186–m00289 …'} |
repaired | #603 档也在这条入口里 |
与 compress 无关的坏 JSON({"command": "echo hi" ) |
null |
不乱修,符合预期 |
(输入侧 JSON.parse 对坏样本是 Unexpected token 'm';输出侧 content 首 120 字符与原文逐字一致,截断位置/prob 都未改。)
gateway 侧集成计划(我们这半边,发版后做)
- 触发条件收窄到
classify === 'invalid'(结构闭合、括号配平、JSON.parse仍失败)且工具名 ==compress;truncated(参数半截/不配平)路径完全不动,继续报max-tokens。 - 调
import('billion-context/salvage').repairCompressArgs(raw):非null→ 用修复后的串继续产出 tool_call(本轮不失败);null→ 保持现在的TOOL_ARGS_INVALID。 - 现装
0.1.208没有./salvage这个 subpath,所以会try/catch静默降级(导出不存在 = 退回今天的行为),不需要你们为兼容做任何事;升到含本 PR 的版本后自动生效。 - 发版后我们会回报实测:整轮丢失是否真的归零、命中/未命中比例、以及 rescue 前后的 usage 对照。
一条可选的 API 小建议(不阻塞、不影响合并)
repairCompressArgs 现在只在「内部救到 ≥1 range」时返回非 null,gateway 侧因此只能记「修好了」而无法知道救回 1 个还是 N 个 区间,日志/告警的信息量有限。如果改动成本低,返回 { json: string, ranges: number }(或加一个重载 repairCompressArgsDetailed)会更好;保持现状我们也能接。
再次确认:③(PREFERRED 翻成对象数组)被拒的理由我们认可——那是 kernel 决策、应单独拍板,不该塞进这个 PR。
(ai: qwen/qwen3.8-27b) 感谢逐字样本的实测——五个 case 的判读(坏样本救回 / 合法 JSON 与截断一律 null / 单引号档命中 / 无关坏 JSON 不乱修)正是本 PR 要保证的行为,gateway 侧「只拦 classify=invalid 且工具名 compress、截断路径完全不动」的集成计划与我们这边的边界一致,可以按此推进。 关于那条 API 小建议:已采纳并合进本 PR(commit f198f79)。
③(PREFERRED 翻对象数组)维持拒绝不变——kernel 决策面,单独拍板。CI 正在新 head 上跑(mergeable=true,base e4cc685),绿了就可以合;合入后你们升版即自动生效,无需任何兼容处理。 |
…tent-salvage-all-lanes
…tent-salvage-all-lanes # Conflicts: # tsup.config.ts
…tent-salvage-all-lanes
…tent-salvage-all-lanes
Closes #4889
What this changes (plain language)
When the model writes compress args in bad shape A (
{"content": <bare text>}with no opening quote), only the Anthropic plugin relay could rescue them before (#2591 wire rewrite). Every other path — proxy mode (loop / JSON rewrite, all four protocols) and third-party provider plugins where bili never touches the SSE bytes — fed the args to strict JSON parsing and failed: proxy mode lost one fold plus a retry; the plugin lane lost the whole turn. This PR moves the rescue into the shared execution-layer parser so every lane recovers, and exports the same repair entry point so provider plugins can fix args before the host'sJSON.parse.Behavior change (old → new)
kind=malformed-json) → fold fails (proxy lane: model retries; plugin lane: whole turn aborted by the host).{"content":"…"}and re-parsed through the kernel; the fold proceeds when ≥1 range survives. Fires only after a failed parse and adopts only when strictly MORE ranges result (compress: literal backslash-n in string content still fails as kind=content-not-array, hiding the real missing-summary reason #3780/compress failed may be caused by params #1494 discipline) — payloads that parse today are never touched, valid JSON stays byte-identical ([bug] 插件透传会剥掉工具调用参数里的 render tag(arguments 流挂了 tag-echo 过滤器) #1039).billion-context/salvage→repairCompressArgs(raw, callId?)returning a strict-JSON replacement string ornull(= leave as-is). This is not user configuration (no config-file/env/CLI surface change); it targets provider plugins/gateways sitting between model and host.Deliberately NOT done
kernel/changes, no version bump (content branch), no new dependencies.Verification
tests/issue4889-bare-content-execution-salvage.test.ts(new, 9 tests incl. the issue's verbatim incident sample + negative gates),tests/issue2580-compress-arg-salvage.test.ts,tests/issue1502-raw-args-salvage.test.ts(25/25 pass); full build + dist gates clean; builtdist/salvage.jssmoke-tested (bad sample → repaired strict JSON; valid input → null).Files
src/compress-tool.ts— rescue step inparseCompressInput(+ shared import)src/plugin-compress-salvage.ts— wrap step extracted totryBareContentWrap(one implementation for both call sites;salvageCompressArgsbehavior unchanged, pinned by the [Bug] compress 工具描述推荐「ONE plain string … no JSON escaping」,模型照做即写出非法 JSON,宿主整轮 MALFORMED_RESPONSE 中止 #2580 suite)src/salvage.ts— new tsup entry +package.json./salvageexport + unused-export gate entry-file list