Skip to content

compress 参数解析统一:前因后果与全栈收敛(tracking) #129

Description

@ranxianglei

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.tsextractRanges 只接受 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 #176 形状)
  • 名字变体:startId / startRef / messageId(end 侧同理)
  • 顶层单 range fallback + 顶层 topic/summaryMaxChars fallback(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.eventlive 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:2d58a4b lenient parsing + diagnostics、70d46f2 top-level single range + topic/summaryMaxChars fallbacks),BEHIND master 需 rebase
  • ⏸️ 三 adapter 本地改好未提交(等内核发 v0.0.33 后 bump 版本再提交——现在提交会因 package.json pin 旧 0.0.32 导致 fresh install 拉到无 parseCompressArgs 的内核而 import 报错)
  • 测试 1607 全绿(内核 429 + proxy 512 + omp 257 + pi 409)
  • 📄 影响面报告:/home/dog/tmp/impact-report.md

六、合并顺序

  1. 合并内核 PR #111 → 发 v0.0.33
  2. 三 adapter 各自 package.json bump 到 0.0.33 + 建分支 + 提交 + 推 + 开 PR
  3. 统一 review

七、关联


八、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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions