跳到主要内容

05 · 上下文压缩:长会话不爆 token

本章讲:当一次会话越来越长、快超过模型上下文窗口时,opencode 怎么自动"瘦身"还不丢关键信息。

1. 它要解决的小问题

每轮都把全部历史喂给模型,token 只增不减,迟早撑爆上下文窗口(也越来越贵)。需要一个机制:在快溢出时,把早期历史压成一段摘要,只保留最近的细节,让对话能继续。

2. 思路 / 直觉:总结 + 裁剪

opencode 的压缩(compaction)分两件事:

  • 总结(summarize):让模型读早期历史,写一段"到目前为止发生了什么"的摘要,作为一条特殊的 compaction part 插入。
  • 裁剪(prune):把摘要之前的旧消息从"喂给模型的历史"里剔掉(原始记录仍在 SQLite,只是不再发给模型),但保护最近若干轮和某些关键工具输出。
完整历史(很长): [旧...旧][中][最近2轮]
│ 检测到接近溢出

① 总结旧+中段 ──► [摘要]
② 裁剪 ──► 喂给模型的 = [摘要] + [最近2轮]

▼ 之后的轮次基于瘦身后的历史继续

怎么读:左边是膨胀的完整历史;压缩后只把"摘要 + 最近几轮"发给模型,token 立刻降下来。

3. 触发:溢出检测

压缩的触发点在两处,都靠"用量 vs 模型上下文上限"的判断(session/overflow.tsisOverflow):

  • 流处理中:每个 step-finish 后,processor 检查本步用量是否溢出,是则置 ctx.needsCompaction = true,提前停掉当前流(processor.ts:475-480)。
  • 循环顶部:runLoop 在下一轮看到上一步溢出,就 compaction.create({ auto: true }) 排一个压缩任务(prompt.ts:1161-1168)。

压缩也可被 ContextOverflowError 触发:provider 直接报上下文超限时,halt(processor.ts:605)把它转成"需要压缩"(除非用户把 compaction.auto 关了)。

4. 总结怎么生成

总结复用同一套 LLM 调用,但用专门的 prompt。压缩 prompt 由 buildPrompt 构造——它从 core 包导入(compaction.ts:23import { buildPrompt } from "@opencode-ai/core/session/compaction"),在压缩流程里被调用(compaction.ts:348)。压缩产物是一条带 summary 标记的 assistant 消息;completedCompactions(compaction.ts:62)负责在历史里找出"已完成的压缩点"——即"有 compaction part 的 user 消息" + "对应的、已 finish 且无 error 的 summary assistant 消息"配对。

5. 裁剪的预算规则

裁剪不是"砍到摘要为止"那么粗暴,它有一套预算常量(compaction.ts:28-34):

常量含义
PRUNE_MINIMUM20_000低于这个 token 量不值得裁
PRUNE_PROTECT40_000这一段最近历史受保护、不裁
DEFAULT_TAIL_TURNS2至少保留最近 2 轮完整对话
MIN/MAX_PRESERVE_RECENT_TOKENS2_000 / 8_000保留的"最近内容"token 区间
TOOL_OUTPUT_MAX_CHARS2_000裁剪时工具输出压到这个字符上限
PRUNE_PROTECTED_TOOLS["skill"]skill 工具的输出不被裁(它含长效指令)

直觉:越近的越完整保留,越远的越敢压,而 skill 这类"加载后要长期生效的指令"被单独护住,免得压缩把 agent 的能力说明丢了。

6. 关键细节 / 坑

  • 可手动可自动。 用户可主动触发压缩;compaction.auto 配置项可关掉自动压缩(processor.ts:606),关掉后溢出会直接报错而非静默压缩。
  • 压缩本身也是一次 LLM 调用,所以也走 processor;processor.ts:314-316330-332 明确禁止"在生成 summary 时再调工具"(会抛错)。
  • prompt 构造在 core 包:buildPrompt(从 @opencode-ai/core/session/compaction 导入,compaction.ts:23),压缩逻辑跨 packages/opencodepackages/core 两层。
  • 裁剪只影响"发给模型的投影",原始消息/part 仍持久化在 SQLite——这呼应 CONTEXT.md 里"Session History 是投影"的术语设计。

7. 横向对比(同 shelf 兄弟)

上下文管理是所有编码 agent 的共同关切,各家取舍不同:

  • 总结式压缩(opencode 本章):溢出时让模型自己写摘要 + 按预算裁旧历史。优点是无限续航;代价是摘要有损,早期细节会丢。
  • 靠工具结果截断(见 02 章 truncate):opencode 同时用"单条工具输出截断 + 写临时文件"防止单次输出撑爆——这是"入口处削峰",和 compaction 的"全局瘦身"互补。
  • 与纯"滑动窗口丢最旧"的朴素做法相比,opencode 保留摘要 + 保护最近轮 + 保护 skill 输出,更不容易丢关键状态。

8. 代码地图

主题文件路径符号名
压缩服务packages/opencode/src/session/compaction.tsService(process, create, prune, isOverflow)
已完成压缩点识别packages/opencode/src/session/compaction.tscompletedCompactions, summaryText
预算常量packages/opencode/src/session/compaction.tsPRUNE_PROTECT, DEFAULT_TAIL_TURNS, PRUNE_PROTECTED_TOOLS
溢出检测packages/opencode/src/session/overflow.tsisOverflow, usable
流中触发压缩packages/opencode/src/session/processor.tshandleEvent(step-finish), halt
循环中排压缩任务packages/opencode/src/session/prompt.tsrunLoop(compaction.create)
压缩 prompt 构造packages/opencode/src/session/compaction.tsbuildPrompt(来自 core)