M09. 压缩:对话超长之后如何继续工作#
压缩不是把聊天记录“删掉一半”。它是一种上下文重建操作:选择一个合法切点,把较早历史交给模型总结,再把摘要和保留区重新接回当前会话。
1. 什么时候触发#
触发通常有两类:接近模型上下文上限时自动触发,或用户/宿主显式调用 compact。Pi 需要先估算当前上下文 token,再结合模型窗口、输出预算和安全余量判断是否进入压缩。
估算不必精确到最后一个 token,但必须稳定、保守且可观测。压缩成功后仍要留出下一轮模型输出和工具结果的空间。
2. 切点不能随便选#
如果把一个 assistant tool call 和它的 tool result 拆开,重建后的上下文可能包含没有对应调用的结果,或者让模型误以为某个工具已经完成。切点要尊重 turn 和消息配对关系。
当单个 turn 本身就很大时,Pi 需要允许更细的处理,并用类似 turnPrefix 的信息告诉摘要过程:这个 turn 被截断后,保留区从哪里开始、缺失部分是什么。
3. 摘要必须结构化#
自由发挥的摘要很容易漏掉文件路径、已完成动作和未解决问题。对编码 Agent,更有用的摘要至少要保留:
- 当前任务目标和约束。
- 已经做过的关键决策。
- 文件的创建、修改和删除情况。
- 测试、命令和结果。
- 尚未完成的工作与风险。
结构化模板不是为了让文章好看,而是为了让下一轮模型能够稳定地把摘要当作工作记忆使用。
4. 文件跟踪是编码 Agent 的特殊要求#
仅有一段“我们修改了代码”的文字不够。摘要还需要累积文件操作,让后续模型知道哪些文件已经存在、哪些改动未验证、哪些路径是用户明确指定的。
这也是为什么 compaction 的结果除了 summary,还可能包含 details、usage 和文件相关信息。上下文压缩的对象不是纯聊天,而是一个带工作区状态的任务轨迹。
5. 物理形态:CompactionEntry#
压缩结果会作为 session entry 保存,而不是覆盖原来的 JSONL 行。典型字段包括摘要、保留区起点、压缩前 token 数和可选 usage。
type CompactionEntry = {
type: "compaction";
id: string;
parentId: string | null;
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
};这是概念化的类型轮廓,具体字段以当前源码为准。追加 entry 的好处是可追踪、可重建、可审计,失败时也不会破坏旧历史。
6. 压缩之后如何重建上下文#
重建时,系统沿当前 session path 读取 entry:遇到 compaction entry,就使用摘要和保留区;继续追加的新消息仍然以正常 message entry 进入路径。这样,压缩后的上下文既包含历史摘要,也能继续承载最新工作。
7. 压缩失败怎么办#
摘要调用本身也可能超时、限流或返回无效格式。安全策略是保留旧上下文和旧 leaf,不把失败的半成品当成成功压缩;必要时提示用户减少任务范围、切换模型或手动开始新会话。
自动重试要有上限。压缩失败后无限重试,既不能解决窗口问题,还会制造额外成本。
8. 如何评估压缩质量#
不要只看摘要是否“通顺”。可以做恢复测试:压缩前设置一组事实、文件改动和待办,压缩后让 Agent 回答并执行后续动作,检查事实、路径、未完成项是否都还在。
特别测试边界:工具调用正在进行时压缩、刚发生错误时压缩、存在分支时压缩、摘要调用失败时重启。
9. 压缩的正确性条件#
一次压缩只有在三件事同时成立时才算成功:旧 entry 仍可追踪;新摘要能够重建后续工作所需的事实;下一轮上下文低于安全阈值。摘要“读起来通顺”并不是正确性标准。真正的标准是压缩后 Agent 能否继续识别目标、文件状态、已完成动作和未解决风险。
资料
本文依据 Pi Compaction 官方文档、SessionManager 源码重新创作。