压缩与分支摘要
LLMs 的上下文窗口有限。当对话过长时,pi 使用压缩来总结旧内容并保留近期工作。本页涵盖自动压缩和分支摘要。
源文件(pi):
packages/coding-agent/src/core/compaction/compaction.ts- 自动压缩逻辑packages/coding-agent/src/core/compaction/branch-summarization.ts- 分支摘要packages/coding-agent/src/core/compaction/utils.ts- 共享工具(文件追踪、序列化)packages/coding-agent/src/core/session-manager.ts- 条目类型(CompactionEntry、BranchSummaryEntry)packages/coding-agent/src/core/extensions/types.ts- 扩展事件类型
要在项目中查看 TypeScript 定义,请检查 node_modules/@earendil-works/pi-coding-agent/dist/。
概述
pi 有两种摘要机制:
| 机制 | 触发条件 | 目的 |
|---|---|---|
| 压缩 | 上下文超过阈值,或 /compact | 总结旧消息以释放上下文 |
| 分支摘要 | /tree 导航 | 切换分支时保留上下文 |
两者使用相同的结构化摘要格式,并累积追踪文件操作。
压缩
何时触发
自动压缩在以下情况触发:
contextTokens > contextWindow - reserveTokens默认情况下,reserveTokens 为 16384 个 token(可在 ~/.pi/agent/settings.json 或 <project-dir>/.pi/settings.json 中配置)。这会为 LLM 的回复留出空间。
你也可以通过 /compact [instructions] 手动触发,可选指令会聚焦摘要。
工作原理
- 找到切分点:从最新消息向后遍历,累积 token 估计值,直到达到
keepRecentTokens(默认 20k,可在~/.pi/agent/settings.json或<project-dir>/.pi/settings.json中配置) - 提取消息:收集从上一个保留边界(或会话开始)到切分点的消息
- 生成摘要:调用 LLM 以结构化格式总结,在必要时传递上一个摘要作为迭代上下文
- 追加条目:保存带有摘要和
firstKeptEntryId的CompactionEntry - 重新加载:会话重新加载,使用摘要及从
firstKeptEntryId开始的后续消息
压缩前:
entry: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
└────────┬───────┘ └──────────────┬──────────────┘
messagesToSummarize kept messages
↑
firstKeptEntryId (entry 4)
压缩后(追加新条目):
entry: 0 1 2 3 4 5 6 7 8 9 10
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
└──────────┬──────┘ └──────────────────────┬───────────────────┘
not sent to LLM sent to LLM
↑
starts from firstKeptEntryId
LLM 看到的:
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑ └─────────────────┬────────────────┘
prompt from cmp messages from firstKeptEntryId在重复压缩时,被总结的范围从上一次压缩的保留边界(firstKeptEntryId)开始,而不是从压缩条目本身开始;如果无法在路径中找到该保留条目,则回退到上一次压缩之后的条目。这通过在下一次总结传递中包含它们来保留经过早期压缩存活的会话。pi 还会在写入新的 CompactionEntry 之前从重建的会话上下文中重新计算 tokensBefore,因此 token 计数反映实际被替换的压缩前上下文。
拆分轮次
"轮次"以用户消息开始,包括所有助手回复和工具调用,直到下一个用户消息。通常,压缩在轮次边界处切分。
当单个轮次超过 keepRecentTokens 时,切分点会落在轮次中间的助手消息上。这是"拆分轮次":
拆分轮次(一个超大轮次超过预算):
entry: 0 1 2 3 4 5 6 7 8
┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
│ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
└─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑
turnStartIndex = 1 firstKeptEntryId = 7
│ │
└──── turnPrefixMessages (1-6) ───────┘
└── kept (7-8)
isSplitTurn = true