统一 compress 参数解析(salvage + diagnostics)— 4 个实现收敛到内核
背景
billion-context-omp#121(弱模型 compress 参数失败,实测 ~50% 失败率)与后续 #74(b41cf23 删除 auto-compress 317 行,含其 salvage 逻辑)暴露了一个结构性问题: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(stream.ts:216-219) |
| 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 |
src/rebuild.ts:63-107 extractRanges |
JSON.parse catch → continue 静默丢弃(fork-recovery 路径) |
#70(f71a9bc)删除的 auto-compress salvage(剥 ```json 围栏 + 截断 JSON 正则抽取)说明这类修复曾经存在过,随 #74 架构调整被整体丢弃——能力跟着通道被删,而不是先提升到拥有它的层。
#121 实测的三类失败形状(本仓库代码全部复现):
| 畸形形状 |
proxy |
omp |
pi |
kernel rebuild |
| JSON 截断(提前收尾,括号不全) |
静默丢弃 |
静默丢弃 |
错误串 |
静默丢弃 |
| ```json 围栏包裹 |
静默丢弃 |
静默丢弃 |
错误串 |
静默丢弃 |
| 尾逗号 |
静默丢弃 |
静默丢弃 |
错误串 |
静默丢弃 |
新发现: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。同一协议四个实现再次漂移。
内核 / adapter 划分(依据 DESIGN.md)
DESIGN.md:18-41:CORE = 纯 TS、零宿主依赖、无状态、零 I/O;ADAPTER = 薄宿主层。职责表(DESIGN.md:199-214):决策 = core,文本渲染/日志 = adapter。
进内核(本 PR)
parseCompressArgs(input: unknown, opts?: { callId?: string }) → { ranges: CompressRangeSpec[]; diagnostics } — 统一 lenient 解析器:
- 严格 JSON(对象,或整体被 stringify 的 JSON 串)
- 剥 ```json 围栏
- 尾逗号
- 字符串字面量内裸换行 → 转义(JSON spec 要求转义,修复无歧义,且仅在修复后可解析时生效)
- 截断数组 salvage:
content 数组提前收尾时,按字符串状态/括号深度 walk 原始文本,提取所有完整的顶层条目逐一解析 + 校验——能救多少救多少,不整份丢弃
- JSON-string content(vLLM #176 形状)
- 名字变体:
startId / startRef / messageId(end 侧同理)
- diagnostics 是数据不是日志:
{ ok, kind, rawPrefix(前 800 字符), length, keys, invalidItems }。内核不打印日志(零 I/O 边界),adapter 决定怎么用(落盘 / 回喂模型 / UI 提示)
rebuild.ts 换用 — 顺带修 vLLM fork-recovery 缺口
index.ts 导出
留 adapter(各自实现,消费 kernel diagnostics 数据)
- raw args 协议提取(openai SSE / responses / anthropic tool_use / omp stream / wire / pi host-parsed)
- 失败留证(proxy loggerLog / omp debug.event / pi ui.notify)
- 失败回喂模型(proxy SSE note / pi throw + tool result / omp tool result text)
- nudge 注入点 + 提示文本、循环/轮次控制
- omp host 侧 live tool-args 解析在 opencode 宿主内,三个 adapter 仓库都够不着,需宿主侧修
重试策略协调:不另起状态机
#73(mid-band cadence + rejection escalation)已提议 kernel 侧 compressRejects: number(on CompressionState)+ rejectionFeedback() helper(escalate@3 / suppress@4 / 成功重置)。parse 失败重试 nudge 与 kernel reject 升级共用同一 failure-streak 机制:#73 的 streak 统计所有 compress 失败(reject + parse),adapter 按失败来源选择反馈文本。本 PR 的 parser 只产出数据(diagnostics + salvage ranges),retry 策略随 #73 落地,避免双归属。
下游 adapter bump(本 PR 落地后,各开 PR)
| 仓库 |
改动 |
收益 |
| billion-context(proxy) |
parseCompressInput/safeJsonParse → kernel parseCompressArgs;diagnostics → loggerLog;salvage ranges 照常进 applyRanges |
~50% 失败率全在该链;salvage 后大量输入不再是失败 |
| billion-context-omp |
compressToolArgs → kernel parser;diagnostics → debug.event |
消灭静默丢弃;wire-fold 路径同步受益 |
| billion-context-pi |
normalizeRanges → kernel parser;保留 throw 契约(pi isError 依赖 throw);#164 retry nudge 接 #73 streak |
行为对齐 + 单一数据源 |
Acceptance
统一 compress 参数解析(salvage + diagnostics)— 4 个实现收敛到内核
背景
billion-context-omp#121(弱模型 compress 参数失败,实测 ~50% 失败率)与后续 #74(
b41cf23删除 auto-compress 317 行,含其 salvage 逻辑)暴露了一个结构性问题:compress 工具参数解析在整条 stack 里从来没有共享层。现存 4 个各自为政的实现,容错程度各不相同,同一份畸形输入全栈行为不一致:billion-context/src/util.ts:26-32safeJsonParse+src/compress-tool.ts:54-83parseCompressInputcatch → {}静默吞;只打一行parsed 0 valid ranges. top keys:(空 keys),模型收到Compression FAILED: no valid ranges parsed(stream.ts:216-219)billion-context-omp/src/messages.ts:105-126compressToolArgs(host 流 +src/wire-fold.ts:318-321共用)JSON.parsecatch →return null静默丢弃,无任何日志(已用真实代码逐形状复现)billion-context-pi/src/compress-tool.ts:75-94normalizeRangessrc/rebuild.ts:63-107extractRangesJSON.parsecatch →continue静默丢弃(fork-recovery 路径)#70(
f71a9bc)删除的 auto-compress salvage(剥 ```json 围栏 + 截断 JSON 正则抽取)说明这类修复曾经存在过,随 #74 架构调整被整体丢弃——能力跟着通道被删,而不是先提升到拥有它的层。#121 实测的三类失败形状(本仓库代码全部复现):
新发现: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。同一协议四个实现再次漂移。内核 / adapter 划分(依据 DESIGN.md)
DESIGN.md:18-41:CORE = 纯 TS、零宿主依赖、无状态、零 I/O;ADAPTER = 薄宿主层。职责表(DESIGN.md:199-214):决策 = core,文本渲染/日志 = adapter。
进内核(本 PR)
parseCompressArgs(input: unknown, opts?: { callId?: string }) → { ranges: CompressRangeSpec[]; diagnostics }— 统一 lenient 解析器:content数组提前收尾时,按字符串状态/括号深度 walk 原始文本,提取所有完整的顶层条目逐一解析 + 校验——能救多少救多少,不整份丢弃startId/startRef/messageId(end 侧同理){ ok, kind, rawPrefix(前 800 字符), length, keys, invalidItems }。内核不打印日志(零 I/O 边界),adapter 决定怎么用(落盘 / 回喂模型 / UI 提示)rebuild.ts换用 — 顺带修 vLLM fork-recovery 缺口index.ts导出留 adapter(各自实现,消费 kernel diagnostics 数据)
重试策略协调:不另起状态机
#73(mid-band cadence + rejection escalation)已提议 kernel 侧
compressRejects: number(on CompressionState)+rejectionFeedback()helper(escalate@3 / suppress@4 / 成功重置)。parse 失败重试 nudge 与 kernel reject 升级共用同一 failure-streak 机制:#73 的 streak 统计所有 compress 失败(reject + parse),adapter 按失败来源选择反馈文本。本 PR 的 parser 只产出数据(diagnostics+ salvage ranges),retry 策略随 #73 落地,避免双归属。下游 adapter bump(本 PR 落地后,各开 PR)
parseCompressInput/safeJsonParse→ kernelparseCompressArgs;diagnostics → loggerLog;salvage ranges 照常进applyRangescompressToolArgs→ kernel parser;diagnostics → debug.eventnormalizeRanges→ kernel parser;保留 throw 契约(pi isError 依赖 throw);#164 retry nudge 接 #73 streakAcceptance
parseCompressArgs通过畸形语料:截断 / 围栏 / 尾逗号 / 裸换行 / JSON-string content / vLLM content 串 / 名字变体 / 截断 salvage / garbage / 空输入 / 缺 content / 无效项计数 / callId 盖章rebuildCompressionState在 vLLM JSON-string content 下不再静默丢弃(fork-recovery 回归测试)npm test/npm run typecheck/npm run build全绿