聊聊 opencode 上下文压缩: 如何做到单会话 1 亿 token 不爆不丢,acp 模型主动压缩才是唯一正道

潜水 10 年:) 可以先把话撂在这里,acp 这个插件出现其他上下文压缩方式就可以谢幕了,甚至尝试继续扩大模型上下文的行为也变得无意义。 先说说上下文压缩插件 acp 是啥,这是一个 opencode 中的插件。为什么需要上下文压缩?用过 AI coding 的都知道,模型上下文都是有限的,哪怕你...
聊聊 opencode 上下文压缩: 如何做到单会话 1 亿 token 不爆不丢,acp 模型主动压缩才是唯一正道
聊聊 opencode 上下文压缩: 如何做到单会话 1 亿 token 不爆不丢,acp 模型主动压缩才是唯一正道

潜水 10 年:)

可以先把话撂在这里,acp 这个插件出现其他上下文压缩方式就可以谢幕了,甚至尝试继续扩大模型上下文的行为也变得无意义。

先说说上下文压缩插件 acp 是啥,这是一个 opencode 中的插件。为什么需要上下文压缩?用过 AI coding 的都知道,模型上下文都是有限的,哪怕你有 100w 上下文也无济于事,面对旷日持久的大项目也捉襟见肘(尤其是可怜的 glm5.1 啊啊啊)。

所以 gpt 和你聊天实际上是假聊天,你和他每次说的消息,包括他的输出,都作为上下文,每次都全部发给 gpt 处理,久而久之你们就攒满几十万甚至 100w 上下文,就要删去一些。

作为程序员,显而易见想到的方式就是把上下文提炼总结,替换掉原会话冗余的上下文。现在大部分上下文压缩都是这么做的,只不过有一些做的很粗糙(大部分),有的很精细(比如 claude 多级级联),有一些还甚至想出做向量存储相关性搜索等等。

不过最终面临的问题还是压缩效果不好的问题。

想法很简单,实现很简单:模型自己决定要不要压缩,怎么压缩

说起来一肚子气,本来这个插件是要提给原作者dcp的,我修了几十个 bug ,增加了类似 jvm 的垃圾回收算法,结果他看都不看全给我关了!!!

废话不多说先看看效果:最近上下文几乎都没有超过 50%!

模型 GLM-5.1 ( 204K context window ),ACP 阈值 55%。

指标 修复前 (5/14 - 5/19) 修复后 (5/19 - 5/20) 采样数 2,749 362 峰值 context 74.2% 45.3% 平均 context 35.9% 25.3% context <40% 的占比 64.5% 87.0% context >55% 的次数 130 次 (4.7%) 0 次 (0%)

主:修复前之前还有,再之前会话超过几百条就上下文丢干净了。

这是最近几天这个会话的 token

指标 数值 总 Input Tokens 35,232,595(约 3520 万) 总 Output Tokens 616,525(约 62 万) 总 Tokens (输入+输出) 约 35,849,120(约 3585 万) 消息总数 3,762 条 会话时长 约 6 天( 5/14 - 5/20 ) 模型 GLM-5.1 ( 204,800 token 上下文窗口) ACP 压缩阈值 55%( 112,640 tokens )

原理:把上下文压缩当做一个 skill

是的,就这么简单。你不需要像 claude 一样搞多级压缩(都是人工定死的,哪有模型灵活?)也不需要搞外部 api 专门压缩(外部 api 才不了解你需要哪些信息)。也不需要专门训练模型,只需要交给模型一个 skill ,他自己决定什么时候调用即可。

重点说明一些基本特性:

  1. 每条消息模型都可以压缩,或者解压缩(这很重要)
  2. 模型可以把连续 N 条消息压缩成 1 条消息。
  3. 模型可以删除消息。
  4. 模型可以修改消息。

系统做什么?

  • 在模型启动的时候告诉模型可以压缩
  • 在上下文 45%的时候提示模型应该压缩
  • 在上下文 55%的时候提示模型必须压缩

仅此而已。

最后说说我改了啥?和原生 dcp 有啥区别

原始 DCP 有个致命问题:上下文状态全靠 msgId 列表追踪,但这些 ID 不持久化,一重启就丢,35 个压缩块全部作废,559K 字符的摘要变成废纸,3175 条原始消息全部涌回上下文,GLM-5.1 直接返回 model_context_window_exceeded

我从头重写了核心架构,主要改了这些:

1. 独立 Block 架构 — 不再有巨型摘要

DCP 原始设计有个脑残的地方:每次压缩会把旧 block 的摘要内联展开到新 block 中。打个比方,就像你每次整理抽屉,都把之前所有整理记录的完整版塞进新记录里。经过 23 次压缩后,最新 block 的摘要膨胀到 90K 字符 — 整个会话历史的递归摘要。这个巨型摘要无法再压缩(它本身就是摘要),占据了上下文的大部分空间,会话卡在 70% context 无法继续。

ACP 改成独立 Block 架构:每个 block 独立存在,摘要只覆盖自己的范围。多个 active block 在上下文中同时存在。不再自动嵌套,(bN) 引用保留为文本标签。模型需要显式用 bN 作为 boundary 才会消费旧 block 。

2. 压缩块状态机(类似 JVM 分代 GC )

每个压缩块有 young → old 代的概念。新压缩的块是 young generation ,经历多次压缩周期后晋升为 old generation 。Old generation 的摘要会被 GC 自动截断(保留头部 + 尾部引用标记),防止老摘要无限膨胀吃掉上下文。

JVM GC ACP 触发条件 Young Gen 新创建的压缩块 每次压缩产生 Minor GC 合并最近的 young 块 55% 阈值触发 Old Gen 存活超过 N 次压缩周期的块 survivedCount ≥ 5 自动晋升 Major GC 截断 old-gen 块摘要 超过阈值自动执行 Age-based deactivation 超龄块自动停用 age > 15

实测效果:126 个 active blocks ( 63K tokens 死重)→ GC 自动清理到 10 个。

3. 34 个 Bug 修复

fork 以来修了 34 个 bug( 4 CRITICAL ,15 HIGH )。每个都是真实踩到的坑,不是坐着想出来的:

最离谱的几个:

  • 状态不持久化:重启后所有压缩块丢失,几千条原始消息涌回上下文,API 直接返回 model_context_window_exceeded,会话当场暴毙。这是 DCP 最大的坑,我反复踩了 N 次。

  • prune summary 静默丢失:遇到一种边界情况,原始消息被删了但摘要没注入进去 — 也就是数据直接丢了,不是压缩质量不好,是真的没了。

  • 每轮 20-50 秒延迟:DCP 的 Logger 在 debug=false 时仍然执行 new Error() + Error.prepareStackTrace 来获取调用栈,每次 50-100ms 。syncToolCache 对每个 tool call 调一次 logger ,500 个 tool × 100ms = 50 秒。用户以为模型在思考,实际上是 DCP 在那做无用的堆栈追踪。( PS:大概原生 dcp 上下文没这么大过,所以不会复现这个 bug 吧哈哈)

  • 阈值计算错误:DCP 用 inputBudget(= context limit - output limit = 73K )代替 context limit( 204K )算百分比,导致 36% 就触发 CRITICAL WARNING ,模型疯狂压缩一个根本没满的上下文。

  • 压缩完模型罢工:compress 工具返回后,模型说"压缩完成,接下来你想做什么?"然后停下来。正在执行的多步任务被直接中断。

  • 前缀缓存被打破:DCP 把动态数据注入到对话中间的锚定消息里,GLM-5.1 的 cache 是前缀匹配,锚定消息内容每轮变化 → 后面所有内容都变成 cache miss ,命中率从 99% 持续下降到 82%。

  • npm 静默覆盖:opencode 自动安装 npm 原版 DCP ,原版缺少我的 bug fix ,加载会话时清空所有压缩块,1866 条消息未压缩直接发送。(好吧,这个不是 bug ,现在我改名了,再也不会有这个困扰了)

完整的 bug 列表太多了就不贴了,感兴趣的看 GitHub 。

增加了 300 个测试

凸(艹皿艹 ),原来 dcp bug 那么多,总计 15 个测试只有 5 个能跑,让 glm5.1 帮我写了 300 个基准测试,

竞品对比

ACP DCP 原版 Morph opencode 内置 压缩方式 模型自己决定 模型自己决定 外部专用压缩 API 被动全量摘要 额外 LLM 调用 无 idle 分析可选(费 token ) 需要 API Key 1 次摘要调用 触发时机 45% 建议,55% 必须 可配置 70% 95%(基本已经来不及了)

PS:实际上 glm5.1 本身就是压缩大师了:)。

安装

opencode plugin opencode-acp@latest --global

或者

{
  "plugin": ["opencode-acp@latest"]
}

配置文件 acp.jsonc

{
  "maxContextLimit": "55%",  // 触发压缩的阈值
  "gc": {
    "enabled": true,
    "interval": 300  // 每 5 分钟 GC 检查一次
  }
}

链接


吐槽归吐槽,DCP 原作者的设计思路我是认可的(好吧,这句话是模型给我加的,原作者思路是对的,只不过并没有发挥到登峰造极) — 让模型自己决定压缩,而不是搞一堆规则和外部 API 。只是在工程实现上踩了太多坑,我花了几周把这些坑都填上了。

如果你也在用 opencode 做大项目,试试看。 几周 session 不用重开。

遇到 bug 就提 github 吧,我自己高频用,应该还会发现很多 bug 。你发现了在 issue 说,直接提 pr 即可。

来源: V2EX - 技术查看原文