Skip to content

compress 工具反复报错:错误信息误导模型陷入重试死循环(模型问题 × Kernel 缺陷) #66

Description

@Tyan66666

compress 工具反复报错:错误信息误导模型陷入重试死循环(模型问题 × Kernel 缺陷)

摘要

compress 工具在 5 次连续调用中反复失败,每次都给出误导性错误,导致模型照错误建议盲目重试而不成功。根因与修复全部位于本仓库(kernel);下游 billion-context-pi 仅消费本修复(pin 升级随其 release 进行)。本 issue 完整记录:现象 → 复现 → 根因(两个缺陷)→ 修复方案 → 兼容性验证。

现象(从头到尾)

触发环境:出问题的会话使用的模型是 DeepSeek V4 PRO(2026-08-13 的最新版本)。模型失误是触发条件(写了 <50 字符的 summary),但反复重试且永远失败是 Kernel 缺陷(见根因)。

一次真实会话中,模型连续发起 5 次 compress 调用,全部失败:

调用 结果
#1 Errors: Summary too short (22 chars, min 50) —— 不知道是哪一段 range 失败
#2 Errors: Total compressible content too small (1248 chars across 15 range(s), min 5000). Combine more messages into your range(s) to meet the threshold. —— 照做合并更多 range
#3 同上 too small 错误,再次合并
#4 同上
#5 同上,最终放弃

核心循环:错误 #2-#5 的内容是"把更多消息合并进 range",但实际上这些 range 已被上一次调用压缩消耗(block 已创建,消息已被 prune 隐藏)。模型照错误建议操作永远无法成功,且错误信息没有指出"你引用的 range 已不存在"。

复现步骤

  1. 模型调用 compress 时写了 < 50 字符的 summary(触发 Summary too short)。
  2. 错误信息不包含是哪一段 range 失败Summary too short (22 chars, min 50) 无 range 标识)→ 模型无法定位,只能重试整批。
  3. 重试时,被上次调用**已经压缩(block 已创建、消息已被 prune 隐藏)**的 range 被 Kernel 的 minCompressRange 预检静默吞掉:
    • resolveBoundariesBoundary not found in visible context (likely consumed by an existing block)
    • rangeIndexSets 构建处的 catch { continue } 吞掉该错误,只留下 Total compressible content too small (1248 chars across 15 range(s)...)
  4. "across 15 range(s)" 的分母还是 input.ranges.length(15 个输入 range),即使其中 13 个已消费 → 模型以为合并更多 range 就能超过 5000 字符阈值,继续无效重试。

定性:触发是模型失误(合理校验拦截);反复重试且永远失败是 Kernel 缺陷。

根因(Kernel 两个缺陷)

定位自 acp-kernel v0.0.21:

缺陷 A:批次错误无 range 归因

applyCompression 逐 range 执行时,错误只 push 原始 message(src/compress.ts:218):

errors.push(error instanceof Error ? error.message : String(error));

Summary too short (22 chars, min 50) 没有 range m00003..m00004: 前缀。批处理场景下模型无法知道哪一条失败。

缺陷 B:预检吞掉"range 已消费"的真实错误,报误导性 too-small

三处解析各自独立、静默吞错:

  1. rangeIndexSets 构建(src/compress.ts:126-139):resolveBoundariesBoundary not found... (likely consumed by an existing block),被 catch { continue } 吞掉。
  2. minCompressRange 预检(src/compress.ts:162-201):只计算剩余可见字符,totalRangeChars < minCompressRange 时抛出 Total compressible content too small (${totalRangeChars} chars across ${input.ranges.length} range(s), min ${minCompressRange}). Combine more messages... —— 分母是输入 range 数,未扣除已消费的 range;且完全丢弃了"这些 range 已压缩"这一真实原因。
  3. 同一 range 在 rangeIndexSets / 预检 / 逐 range 执行中resolveBoundaries 解析三次,前两次的失败都被静默吞掉。

→ 模型收到"合并更多消息"的建议,照做必失败,形成死循环。

修复方案

完整设计文档见 PR #65 中的 DESIGN.md。

Kernel(acp-kernel,核心修复)

  1. 类型化错误boundaries.ts 新增 export class BoundaryNotFoundError extends Error,携带 code: "BOUNDARY_NOT_FOUND"kind: "unknown" | "consumed"endpoint: "start" | "end"resolveAnchorIndex 对 byRef 未命中(unknown)、消息不在可见集(consumed)等情况直接抛类型化错误,替代原来的通用 Boundary not found Error。
  2. 单次分类复用applyCompression 开头对每个 range 解析一次并分类(ok / consumed / unknown / invalid),rangeIndexSetsminCompressRange 预检、逐 range 循环共用分类结果(解析次数 3 → 2)。
  3. 预检三分支(替换缺陷 B):
    • unknown / invalid:无条件按 range 报错,不 fail-fast(保留部分成功语义)。
    • 有 consumed 且剩余可压缩字符 < 阈值 → 报真实原因:
      Requested range(s) already compressed (e.g. m00003..m00004); remaining compressible content X chars < min 5000. Nothing to do — run acp_status to see current compressible ranges.
    • 无 consumed 且 < 阈值 → 保留原 Total compressible content too small (X chars across K range(s), min Y),但分母 K 改为实际计数 range 数(不再把已消费 range 计入)。
  4. consumed range 放行时 warn+skipminCompressRange 预检通过时,已消费 range 记 warning Skipped range (S..E) — already compressed (messages consumed by existing block(s)); nothing to compress.(沿用 PR fix: overlap warn+skip instead of aborting the whole batch (ISSUE-42) #55 的 warn+skip 风格),并继续处理其余 range。
  5. 逐 range 错误前缀(修复缺陷 A):所有 per-range 错误统一加 range S..E: 前缀。
  6. 10 个新测试(T1-T10)覆盖:短 summary 带 range 前缀+部分成功、consumed 重试报 already-compressed、consumed+小内容不误导、纯小内容保留 too-small 且分母修正、consumed+大内容放行带 warning、空 summary 带前缀、非法 ref 不 fail-fast、unknown ref 提示 acp_status、minCompressRange=0 时 warn+skip、resolveBoundaries 类型化抛错。

下游(billion-context-pi)

插件零逻辑改动(compress-tool.ts 是透传)。仅需在下次 release 时升级 acp-kernel 依赖 pin 0.0.21 → 0.0.22

兼容性验证

对下游引用的逐一核查(kernel 修改面:boundaries.ts / compress.ts / index.ts):

检查项 结果
导出 API 零删除、零改名;仅新增 BoundaryNotFoundError(纯增量)
resolveBoundaries 签名 ResolveBoundariesInput → ResolvedRange 完全未变,返回字段逐字段一致
抛错类型 旧:普通 Error;新:BoundaryNotFoundErrorError 子类,instanceof Error 仍成立)
applyCompression 返回结构 新旧完全一致:{ state, result: { blocksCreated, tokensCompressed, errors, warnings } }warnings 字段 v0.0.21 已存在(非新增)
下游唯一消费点 compress-tool.ts 只解构 { blocksCreated, tokensCompressed, errors, warnings } —— 语义不变,仅 errors/warnings 文案变化(这正是修复目的)
下游测试断言 所有变体(clone-fix/delegate-usage/streaming/watchdog/monitor/activity/billion-context)均无旧文案断言
直接调用面 下游全部经由 runtime.core.processTurn / applyCompression;无任何代码直接调用 resolveBoundaries/parseBoundary
构建与测试 acp-kernel:tsc --noEmit 0 错、240/240 测试通过、build 成功;billion-context-pi:typecheck 0 错、255/255 通过、build 成功(内联新 kernel)

唯一理论风险:若有下游按字符串匹配旧错误文案 "Boundary not found in visible context" —— 经全量核查无此用法,且该函数本只被 kernel 内部调用。

相关 PR

  • PR #65(修复实现,待合并后发布 v0.0.22)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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