8 min read教程

PiAgent 09|上下文压缩:对话超长后如何继续工作

从触发判断、合法切点到摘要回接,理解 compaction 如何重建上下文而不是简单删除历史

PiAgent 09|上下文压缩:对话超长后如何继续工作#

从触发判断、合法切点到摘要回接,理解 compaction 如何重建上下文而不是简单删除历史

压缩不是把聊天记录删掉一半。它是一种上下文重建操作:选择一个合法切点,把较早历史交给摘要过程,再将结构化摘要和保留区重新接回当前会话。压缩质量决定 Agent 在长任务中还能否记住目标、已经改过的文件和未完成的风险。

1. 为什么必须压缩#

模型上下文窗口是有限的,而 Agent 的历史会同时增长:用户消息、assistant 输出、工具调用、工具结果、重试和扩展消息都可能进入 transcript。每次把完整历史原样发送,会遇到:

  • 上下文超限,请求直接失败。
  • 历史占用过大,留给当前任务的输出空间不足。
  • 旧工具结果和重复说明挤掉当前约束。
  • 成本与延迟随轮次线性甚至超线性增长。

压缩的目标不是最短,而是让下一轮模型拥有足以继续工作的“工作记忆”。

2. 什么时候触发#

触发通常有两类:

  1. 自动压缩:接近模型上下文上限时,由 Session 根据窗口、输出预算和安全余量判断。
  2. 显式压缩:用户或宿主调用 session.compact(),或者扩展触发手动整理。

一个保守的预算模型是:

text
可用窗口 = 模型窗口 - 预留输出 - 预留工具结果 - 安全余量
触发条件 = 当前上下文估算值 > 可用窗口

估算不必精确到最后一个 token,但必须稳定、可观测且倾向保守。压缩成功后仍要留出下一轮模型输出和工具结果的空间。

3. 切点不能破坏消息配对#

最危险的压缩方式是按字符串长度随意切历史。如果把 assistant tool call 与 tool result 拆开,重建后的上下文可能包含没有对应调用的结果,或者让模型误以为某个工具已经完成。

切点应尊重:

  • user / assistant / tool result 的语义配对。
  • turn 的边界。
  • 当前分支的父子关系。
  • 正在执行的工具和未收束的运行状态。
text
保留区开始
  ├─ 完整的 user message
  ├─ 完整的 assistant tool call
  └─ 对应的 tool result

当单个 turn 本身就很大时,不能假装它完整保留。摘要过程需要知道这个 turn 被截断了哪些部分,以及保留区从哪里开始。

4. 摘要应保存什么#

对编码 Agent,合格摘要至少包含:

  1. 当前目标、约束和验收标准。
  2. 已经做出的关键决策及原因。
  3. 创建、修改、删除过的文件和重要符号。
  4. 执行过的命令、测试结果和失败原因。
  5. 当前模型、工具或环境限制。
  6. 尚未完成的工作、待确认事项和潜在风险。

摘要不是面向人的会议纪要,而是给下一轮模型使用的状态压缩。像“已经处理了一些文件”这种句子没有恢复价值;文件路径、函数名和未解决问题才是可执行记忆。

5. 压缩后的上下文长什么样#

text
system prompt
  + compaction summary
      - 任务目标
      - 已完成修改
      - 测试结果
      - 未完成事项
      - 风险与约束
  + 保留区中的最近消息
  + 当前用户输入

摘要通常带有 tokensBefore、切点 ID 和可选的 usage/details。Session Manager 还要把压缩摘要作为会话历史中的一种 entry 保存,保证恢复和分支时知道这次重建发生过。

6. session_before_compact 是可控扩展点#

官方扩展 API 提供压缩前事件,扩展可以取消压缩或提供自定义摘要:

ts
pi.on("session_before_compact", async (event, ctx) => {
  if (shouldNotCompactYet(event)) {
    return { cancel: true };
  }

  return {
    compaction: {
      summary: await buildDomainSummary(event),
      firstKeptEntryId: chooseSafeCut(event),
      tokensBefore: event.tokensBefore,
    },
  };
});

自定义摘要适合补充业务状态,例如订单 ID、审批状态或外部任务编号,但不能用它绕过安全规则,也不能把未验证的外部数据当成事实写进记忆。

7. 失败、重试和可观测性#

压缩本身也可能失败:摘要请求超时、切点非法、模型窗口估算不一致或保存摘要失败。应该区分:

  • 摘要生成失败:保留原历史或进入有限重试。
  • 保存失败:不要宣称压缩成功,避免运行状态和持久化状态分叉。
  • 空间不足:报告无法继续,而不是无限递归压缩。

建议记录 compaction_start/end、触发原因、压缩前后 token 估算、切点 ID、重试次数和最终状态。没有这些数据,就很难解释长任务为什么突然忘记了文件或变慢。

常见误区

  • 只按字符数截断,不考虑 tool call/result 配对。
  • 把摘要写成泛泛的“目前进展”,没有路径和决策。
  • 压缩成功后不保存切点,恢复时无法重建同一上下文。
  • 把所有旧日志都放进摘要,压缩后仍然占满窗口。
  • 压缩失败时继续运行,导致模型看到不完整或不一致的状态。

小结

Compaction 的本质是一次有约束的上下文重建:先判断预算,再选择合法切点,摘要关键状态,保留最近有效历史,最后把结果写回会话。它连接了上下文工程、消息模型和会话树,不能被当成一个独立的“删文本工具”。

源码定位

  • Session 压缩入口:packages/coding-agent/src/agent-session.ts
  • 压缩事件与扩展 API:packages/coding-agent/src/extensions/types.ts
  • 会话摘要 entry:packages/coding-agent/src/core/session-manager.ts
  • Agent 上下文构建:packages/agent/src/agent.ts

相关文章