Skip to content

第 6 章 · 上下文压缩 Compaction 与 Context Files

本章目标:理解 pi 为什么需要上下文压缩、自动/手动 compaction 的触发与工作原理,掌握 Context Files(AGENTS.md)的加载层级与 system prompt 定制方法。

6.1 为什么需要压缩

LLM 的上下文窗口是有限的,而编码会话天然"吃"上下文:每读一个文件、每跑一条命令,工具结果都会进入对话历史。一个持续重构半小时的会话,readbash 的输出轻松占掉几十万 token。这带来两个问题:

  • 硬限制:超过模型窗口后请求直接失败;
  • 软成本:即使没超限,冗长的历史也会推高每次请求的费用和延迟。

pi 的解法是压缩(compaction):把较早的对话总结成一份结构化摘要,只保留最近的消息原文。它还有一套配套机制——Context Files(如 AGENTS.md),让你用极小的固定成本注入项目级指令。

6.2 自动与手动触发

自动压缩的触发条件是一条简单的不等式(来自官方文档 compaction.md):

text
contextTokens > contextWindow - reserveTokens

即"当前上下文 token 数超过 模型窗口 − 预留空间"时立即压缩。reserveTokens 默认 16384,给模型的回复留出空间;你也可以在设置里调整(见下文)。此外,当一轮因超限而失败时,pi 会先压缩再重试(overflow recovery)。

手动触发则是在交互模式里输入 /compact,并可以附加指令来聚焦摘要重点:

bash
# 交互模式下:
/compact                          # 按默认结构总结全部旧内容
/compact 重点保留数据库迁移相关的决策和未完成的测试清单

关闭自动压缩

在 settings.json 中把 compaction.enabled 设为 false 可以关闭自动压缩,但 /compact 手动命令仍然可用。长会话中关掉它很容易撞上窗口上限,一般不建议。

6.3 压缩是怎么工作的

理解原理能帮你预判"哪些信息会被丢掉"。官方流程分五步:

  1. 找切点:从最新消息向前累计 token,直到达到 keepRecentTokens(默认 20000),这个位置就是切点;
  2. 提取消息:切点之前(到上一次压缩边界为止)的消息全部进入待总结集合;
  3. 生成摘要:调用 LLM 按固定结构化格式总结,若存在上一份摘要则作为迭代上下文传入;
  4. 追加条目:把摘要和 firstKeptEntryId 写入会话的 CompactionEntry
  5. 重建上下文:下次请求时,LLM 看到的 = system prompt + 摘要 + 切点之后的原文消息。
text
压缩前后 LLM 视角的变化:

之前: [system] [msg1] [msg2] ... [msg8]        ← 全部原文
之后: [system] [summary] [msg7] [msg8]         ← 摘要替代了 msg1~6

两个值得注意的细节:

  • 切点永远不会落在工具结果上——工具结果必须与它的工具调用待在一起,否则会出现"有答案没问题"的断裂上下文;
  • 序列化时会截断工具结果:单条工具结果最多保留 2000 字符,超出部分替换为截断标记,防止总结请求本身撑爆预算。

6.4 信息损失的权衡与调参

压缩本质是有损压缩。结构化摘要格式保证了关键信息不丢:

markdown
## Goal          ← 用户要完成什么
## Constraints & Preferences
## Progress      ← Done / In Progress / Blocked
## Key Decisions ← 决策及理由
## Next Steps
## Critical Context
<read-files>...</read-files>
<modified-files>...</modified-files>

注意末尾的 <read-files> / <modified-files>:pi 会跨多次压缩累积追踪文件操作,所以即使读过某文件的记录被压缩掉了,"这个文件被动过"的事实仍会传递下去。

参数调优示例——小窗口模型或长任务可以保留更多近况:

jsonc
// ~/.pi/agent/settings.json 或 <project>/.pi/settings.json
{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,   // 给回复预留的 token
    "keepRecentTokens": 40000 // 多留一些近期原文,减少信息损失
  }
}

权衡关系很直接:keepRecentTokens 越大,摘要需要覆盖的内容越少、损失越小,但每次请求携带的历史也越贵。另一个相关机制是 /tree 分支导航时的分支总结(branch summarization):切换分支时 pi 会询问是否为被离开的分支生成摘要注入新分支,同样使用上述格式。

6.5 Context Files:AGENTS.md 的加载层级

如果说 compaction 是"动态上下文的减法",Context Files 就是"静态上下文的加法"。pi 启动时按以下顺序加载并拼接 AGENTS.md(或 CLAUDE.md):

text
~/.pi/agent/AGENTS.md     ← 全局个人偏好
+ 父目录链上的 AGENTS.md   ← 从 cwd 一路向上走
+ 当前目录的 AGENTS.md     ← 项目约定
markdown
<!-- ~/.pi/agent/AGENTS.md:全局,适合放个人习惯 -->
- 回复使用中文
- 提交信息遵循 Conventional Commits

<!-- ./AGENTS.md:项目根目录,适合放项目约定 -->
- 包管理器用 pnpm,不要用 npm/yarn
- 测试命令:pnpm test;lint 命令:pnpm lint
- src/legacy 下是即将废弃的代码,不要在其中新增功能

两个进阶用法:

  • 目录级覆盖:某目录存在 AGENTS.override.md 时,pi 加载它而跳过该目录的 AGENTS.md/CLAUDE.md(其他目录照常拼接)——适合 monorepo 中个别子包有特殊规则的场景;
  • 禁用开关--no-context-files-nc)可完全关闭加载,做对比实验时有用。

6.6 System Prompt 定制与 monorepo 实践

默认 system prompt 也可以整体替换或追加:

text
.pi/SYSTEM.md            ← 项目级,整体替换默认系统提示词
~/.pi/agent/SYSTEM.md    ← 全局版
APPEND_SYSTEM.md         ← 同目录放置则只追加、不替换

大型 monorepo 的推荐组织方式是"全局薄、项目厚、子包精准":

text
my-monorepo/
├── AGENTS.md                  # 通用约定:语言、提交规范、顶层命令
├── apps/
│   └── web/
│       └── AGENTS.md          # 前端专属:组件规范、样式方案
├── packages/
│   └── core/
│       ├── AGENTS.md          # 核心库专属:公共 API 变更需评审
│       └── AGENTS.override.md # 若此包规则完全独立,覆盖而非叠加
└── .pi/
    ├── settings.json          # 项目级 pi 设置
    └── SYSTEM.md              # 可选:项目级系统提示词

配合第 6.4 节的压缩调参,长任务的工作流就完整了:Context Files 保证"常驻知识"永远在,compaction 保证"历史过程"不撑爆窗口。

本章小结

  • 自动压缩条件:contextTokens > contextWindow - reserveTokens(默认预留 16384);手动用 /compact [instructions]
  • 压缩保留最近 keepRecentTokens(默认 20000)token 的原文,更早内容总结为固定结构化摘要,文件读写记录跨压缩累积;
  • 工具结果不会被单独切断,序列化时截断到 2000 字符/条;
  • Context Files 按 全局 → 父目录链 → 当前目录 顺序拼接 AGENTS.mdAGENTS.override.md 可覆盖所在目录;
  • .pi/SYSTEM.md 替换系统提示词,APPEND_SYSTEM.md 只追加。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. pi 自动压缩(auto-compaction)的触发条件是什么?

2. 关于压缩的切点(cut point),下列说法正确的是?

3. 某目录同时存在 AGENTS.md 和 AGENTS.override.md,pi 会怎么做?

4. 想把"最近保留原文"的量从默认值提高到 40000 token,应该怎么配置?

🛠️ 动手实践

  1. 在一个测试项目中把 keepRecentTokens 调成 5000 并进行一次超长对话(让 pi 反复读大文件),观察压缩触发的时机与摘要内容。
  2. 为你的某个真实项目编写 AGENTS.md(至少包含构建/测试/lint 三条命令和两条代码约定),重启 pi 后验证 agent 是否遵守。
  3. /compact 聚焦保留 API 设计决策 做一次带指令的手动压缩,对比有无指令时摘要的差异。

完成以上练习后,进入下一章:Settings 设置与项目信任