Repository navigation
compress 参数解析统一:前因后果与全栈收敛(tracking) #129
Description
compress 参数解析统一:前因后果与全栈收敛(tracking)
TL;DR:弱/本地模型 ~50% compress 参数不可解析。根因是 compress 参数解析在整条 stack 从来没有共享层——4 个实现各自为政,容错程度不一,同一份畸形输入全栈行为不一致。原
/compact拦截器的 salvage 逻辑在 #74 删拦截器时蒸发。本 issue 记录前因后果 + 解决方案 + 现状 + 关联,供后来追查。
一、前因(起因)
1. 用户侧症状 — billion-context-omp#121
- 弱/本地模型(vLLM/qwen3.8 等)是严格 JSON 参数生成的失败重灾区,实测 ~50% compress 参数不可解析。
- 用户实测证据:billion-context proxy + vllm/qwen3.8 本地模型,2026-08-22 日志与截图(
[acp-compress-input] parsed 0 valid ranges. top keys: (空))。 - 失败形状(本仓库代码全部复现):
| 畸形形状 | proxy | omp | pi | kernel rebuild |
|---|---|---|---|---|
| JSON 截断(提前收尾,括号不全) | 静默丢弃 | 静默丢弃 | 错误串 | 静默丢弃 |
| ```json 围栏包裹 | 静默丢弃 | 静默丢弃 | 错误串 | 静默丢弃 |
| 尾逗号 | 静默丢弃 | 静默丢弃 | 错误串 | 静默丢弃 |
| 字符串内裸换行 | 静默丢弃 | 静默丢弃 | 错误串 | 静默丢弃 |
| gateway stringify(vLLM) | 部分 | 静默丢弃 | 错误串 | 静默丢全部块 |
2. salvage 逻辑的蒸发 — #68/#70/#72/#73 → #74
2026-08-17,/compact 的容错解析系列在合并后 34 分钟内被整体删除:
| PR | commit | 内容 |
|---|---|---|
| #68 | 2992494 |
/compact 永不回退原生压缩——失败重试一次,再失败取消 |
| #70 | f71a9bc |
parseSummary() 容错解析:① 剥 ```json 围栏;② JSON.parse 失败 → 纯 prose ≥50 字符直接作为 summary;③ 截断 JSON → 正则 /^\{\s*"summary"\s*:\s*"([\s\S]*)$/ 抽出残缺字符串值(≥50 字符) |
| #72 | f573002 |
移除 summary 输出上限 |
| #73 | — | 移除 summary 超时 |
| #74 | b41cf23 |
以「/compact 是宿主功能,ACP 经由 compress 工具拥有压缩」为由删除整个 /compact 拦截器 + src/auto-compress.ts(317 行),上述容错逻辑随之消失 |
#74 的架构决策本身没问题(等、单一职责),但容错解析的价值不依赖 /compact 通道——它应该随 compress 工具路径存活,而没有。能力跟着通道被删,而不是先提升到拥有它的层。
3. 结构性问题 — acp-kernel#108
compress 工具参数解析在整条 stack 里从来没有共享层。现存 4 个各自为政的实现,容错程度各不相同:
| 实现 | 位置 | 对畸形输入 |
|---|---|---|
| proxy | billion-context/src/util.ts:26-32 safeJsonParse + src/compress-tool.ts:54-83 parseCompressInput |
catch → {} 静默吞;只打一行 parsed 0 valid ranges. top keys:(空 keys),模型收到 Compression FAILED: no valid ranges parsed |
| omp | billion-context-omp/src/messages.ts:105-126 compressToolArgs(host 流 + src/wire-fold.ts:318-321 共用) |
JSON.parse catch → return null 静默丢弃,无任何日志 |
| pi | billion-context-pi/src/compress-tool.ts:75-94 normalizeRanges |
返回可行动错误串并 throw(唯一给模型自我纠正机会的,但只给机会不修数据) |
| kernel rebuild | acp-kernel/src/rebuild.ts:63-107 extractRanges |
JSON.parse catch → continue 静默丢弃(fork-recovery 路径) |
4. 新发现:kernel 侧 vLLM fork-recovery 缺口(超出 #121 描述)
src/rebuild.ts 的 extractRanges 只接受 Array.isArray(content),不支持 JSON-string content。vLLM 宿主会把整个 tool 参数 stringify(billion-context#176),此场景下 fork-recovery(session 恢复/重建)静默丢失全部压缩块——而 proxy 的 parseCompressInput 接受 JSON-string content。同一协议四个实现再次漂移。
二、后果(影响)
- 弱模型 compress 调用 ~50% 失败,模型收到
Compression FAILED: no valid ranges parsed或静默无响应 → 上下文无法压缩 → 长会话 token 膨胀。 - 同一协议四个实现漂移,维护成本高,修复一处其他三处不复利。
- vLLM fork-recovery 静默丢块(数据丢失风险)。
三、解决方案
内核 — acp-kernel PR #111
新增 parseCompressArgs(input: unknown, opts?: { callId?: string }) → { ranges: CompressRangeSpec[]; diagnostics }:
- 严格 JSON(对象,或整体被 stringify 的 JSON 串)/ 一层 double-stringification
- 剥 ```json 围栏 / 尾逗号 / 字符串字面量内裸换行 → 转义
- 截断数组 salvage:按字符串状态/括号深度 walk 原始文本,提取所有完整的顶层条目逐一解析 + 校验——能救多少救多少,不整份丢弃,partial 条目绝不发明
- JSON-string content(vLLM fix: reclaim dead message refs, reuse exhausted capacity, classify boundary failures (billion-context#386) #176 形状)
- 名字变体:
startId/startRef/messageId(end 侧同理) - 顶层单 range fallback + 顶层
topic/summaryMaxCharsfallback(per-entry 优先) - diagnostics 是数据不是日志:
{ ok, kind, rawPrefix(≤800), length, keys, invalidItems },kind ∈ ok | empty-input | not-object | missing-content | content-not-array | malformed-json | truncated | no-valid-ranges。内核不打印日志(零 I/O 边界),adapter 决定怎么用(落盘 / 回喂模型 / UI 提示) rebuild.ts换用——顺带修 vLLM fork-recovery 缺口
三 adapter 收敛(消费 kernel diagnostics 数据)
| 仓库 | 改动 | 收益 |
|---|---|---|
| billion-context(proxy) | parseCompressInput/safeJsonParse → kernel parseCompressArgs;diagnostics → loggerLog;删有害的 safeJsonParse 预解析(截断串→{} 丢 salvage,改传 raw string) |
~50% 失败率全在该链;salvage 后大量输入不再是失败 |
| billion-context-omp | compressToolArgs → kernel parser;diagnostics → debug.event;live tool 路径不动(typebox 类型化 + issue #47 错误契约) |
消灭静默丢弃;wire-fold 路径同步受益 |
| billion-context-pi | normalizeRanges → kernel parser;保留 throw 契约(pi isError 依赖 throw);describeDiagnostics 用原始 content 类型区分 vLLM 串化形状 |
行为对齐 + 单一数据源 |
留 adapter(不进内核):raw args 协议提取(openai SSE / responses / anthropic tool_use / omp stream / wire / pi host-parsed)、失败留证落点、失败回喂模型、nudge 注入点 + 提示文本、循环/轮次控制。omp host 侧 live tool-args 解析在 opencode 宿主内,三个 adapter 仓库都够不着,需宿主侧修。
重试策略协调:不另起状态机。#73(mid-band cadence + rejection escalation)已提议 kernel 侧 compressRejects: number + rejectionFeedback() helper。parse 失败重试 nudge 与 kernel reject 升级共用同一 failure-streak 机制:streak 统计所有 compress 失败(reject + parse),adapter 按失败来源选择反馈文本。本 PR 的 parser 只产出数据(diagnostics + salvage ranges),retry 策略随 #73 落地,避免双归属。
四、时间线
| 日期 | 事件 |
|---|---|
| 2026-08-17 | #68/#70/#72/#73 合并(/compact 容错解析)→ 34 分钟后 #74 (b41cf23) 删除整个拦截器,salvage 蒸发 |
| 2026-08-22 | billion-context-omp#121 报告(弱模型 ~50% 失败);acp-kernel#108 建(结构性问题);acp-kernel PR #111 开(内核 parseCompressArgs) |
| 2026-08-22 | 三 adapter 本地适配完成,1607 测试全绿 |
五、现状(2026-08-22)
- ✅ 内核 PR feat: add parseCompressArgs — lenient compress-arg parsing with diagnostics #111 OPEN(2 commits:
2d58a4blenient parsing + diagnostics、70d46f2top-level single range + topic/summaryMaxChars fallbacks),BEHIND master 需 rebase - ⏸️ 三 adapter 本地改好未提交(等内核发 v0.0.33 后 bump 版本再提交——现在提交会因
package.jsonpin 旧 0.0.32 导致 fresh install 拉到无parseCompressArgs的内核而 import 报错) - ✅ 测试 1607 全绿(内核 429 + proxy 512 + omp 257 + pi 409)
- 📄 影响面报告:
/home/dog/tmp/impact-report.md
六、合并顺序
- 合并内核 PR #111 → 发
v0.0.33 - 三 adapter 各自
package.jsonbump 到0.0.33+ 建分支 + 提交 + 推 + 开 PR - 统一 review
七、关联
- 根因 issue:billion-context-omp#121
- 内核跟踪 issue:acp-kernel#108
- 内核 PR:acp-kernel#111
- 被删的 salvage 实现:
git show f71a9bc:src/auto-compress.ts的parseSummary() - 删除提交:
b41cf23(feat: wire/ submodule — protocol codecs (anthropic/openai/responses) + content-hash message identity, extracted from billion-context proxy #74) - 相关:billion-context#176(vLLM stringify)、billion-context-pi#164(retry nudge)、acp-kernel#73(rejection streak)、billion-context-omp#47(弱模型重试循环)
八、Acceptance
-
parseCompressArgs通过畸形语料:截断 / 围栏 / 尾逗号 / 裸换行 / JSON-string content / vLLM content 串 / 名字变体 / 截断 salvage / garbage / 空输入 / 缺 content / 无效项计数 / callId 盖章 -
rebuildCompressionState在 vLLM JSON-string content 下不再静默丢弃(fork-recovery 回归测试) - 所有失败路径 diagnostics 字段完整正确(rawPrefix / length / keys / invalidItems)
-
npm test/npm run typecheck/npm run build全绿 - 三个 adapter 仓库 bump 新版并过各自测试(后续 PR)
Activity