上下文压缩
Agent 不能无限追加历史。模型上下文窗口有限,长会话最终会遇到:
- prompt too long。
- 工具结果过大。
- 图片或附件过多。
- 旧信息干扰当前任务。
Claude Code 对这个问题做了很多层处理,包括 auto compact、microcompact、session memory compact 和 reactive compact。
在 query.ts 里,模型调用前大致会经历:
text
getMessagesAfterCompactBoundary()
-> applyToolResultBudget()
-> snip compact
-> microcompact()
-> context collapse
-> autocompact()1
2
3
4
5
6
2
3
4
5
6
所以 compact 不是一个单点函数,而是一组上下文治理策略。
compact 的本质
压缩不是简单截断,而是把历史转换成一段保留任务连续性的摘要。
压缩前:
text
几十轮用户消息
几十轮 assistant 消息
大量 tool_result
多个文件内容1
2
3
4
2
3
4
压缩后:
text
compact summary
最近若干轮消息
当前任务相关文件
必要的 system/context 附件1
2
3
4
2
3
4
Claude Code 的 CompactionResult 也不是单条 summary。它会包含 summary messages、保留消息、attachments、hook results 等内容,最后由 buildPostCompactMessages() 组合成压缩后的上下文。
为什么不能直接丢旧消息
旧消息里可能包含:
- 用户目标。
- 已经做过的决策。
- 修改过的文件。
- 当前计划。
- 工具调用结果。
- 用户明确偏好。
直接丢弃会让 Agent 忘记任务。
自研 Agent 的第一版 compact
可以先实现一个简单策略:
text
当估算 token 超过阈值:
取旧消息
调用模型生成 summary
保留 summary
保留最近 N 轮
保留当前打开文件列表1
2
3
4
5
6
2
3
4
5
6
伪代码:
ts
async function compactIfNeeded(messages: Message[]) {
if (estimateTokens(messages) < MAX_CONTEXT_TOKENS * 0.8) {
return messages
}
const old = messages.slice(0, -10)
const recent = messages.slice(-10)
const summary = await summarize(old)
return [
{ type: "system", content: `Conversation summary:\n${summary}` },
...recent,
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
compact 的风险
compact 会引入信息损失。常见问题:
- 摘要漏掉关键文件。
- 摘要改写用户意图。
- 摘要丢失工具调用细节。
- 摘要让后续审计变难。
所以 compact 后仍应保留原始 session log,只压缩发送给模型的上下文。
小结
上下文压缩是长会话 Agent 必备能力,但第一版不要做复杂。先实现“阈值触发摘要 + 最近消息保留 + 原始日志不丢”。