Pi 编码助手的上下文压缩机制详解
如果你曾经在编码代理中进行过长时间的编码会话,比如圆周率Claude Code 或 Codex,你将触发一次压缩。在这篇文章中,我们将解释压缩的工作原理以及何时 Pi 需要压缩。
一次关于LLM的对话
大型语言模型(LLM)的局限性上下文窗口.上下文窗口是模型在产生响应时能够“看到”的内容。该变压器架构LLM使用的输入限制了它们能处理的输入量。
编码代理会话的输入包含所有之前的消息和工具调用,随着你的工作,这个输入会不断增加。一旦超过上下文窗口,LLM就会拒绝该请求。
在与像Pi这样的编码代理进行交互式操作时,代理会向LLM发送请求并接收响应。每个请求都包含一个系统提示符、加载的文件,如AGENTS.md、工具定义以及会话历史。
编码代理的第一个LLM请求包含这个初始上下文,以及第一个用户消息。
request 1:
[system][tools][user]
这开始了一个回合。LLM可能先返回一个包含工具调用的助手消息。代理程序执行这些请求,并向LLM发送包含完整对话的新请求,现在还包括工具结果。
我们又收到一个助手消息。当助手完成输出时,回合结束。
after request 1:
[system][tools][user][assistant: tool call][tool result][assistant]
<-------------------> ^ <--------->
returned by LLM | returned by LLM
|
produced by the agent
我们继续工作,又发了一条消息。
request 2:
[system][tools][user][assistant: tool call][tool result][assistant][user]
^
new user message
每回合扩展对话内容。最终,历史超过上下文限制。下一个请求返回错误,如Request exceeds the maximum size.
[system][tools][user][assistant][....][tool result][user]
^
exceeds context window
处理上下文溢出
当我们无法继续现有的对话时,我们有两个选择。
- 我们可以开始一场没有积累上下文的新空洞对话。这会丢弃历史,包括之前的决定和未解决的工作。这仍然可能是个好主意,因为随着上下文规模的增加,LLM输出的性能会下降.
- 我们可以创建一个更小的对话上下文表示,因为我们希望保持对话的持续。这就是压缩的作用。
压缩
理论上,实现压缩有很多种方式。例如,我们可以写一个确定性函数,保留对话中的一部分内容,丢弃其余部分。但在实际操作中,压缩的实现会使用LLM请求来总结对话历史。
压缩用压缩表示替代部分历史,留出空间用于额外的消息和工具调用。
[system][tools][compaction result][user]
^
new message
Pi 的实现
让我们更仔细地看看圆周率具体是如何实现的实现压缩.
当对话过长时,Pi 会使用压缩来总结旧内容,同时保留最近的工作。当上下文限制接近上下文窗口的总大小时,会触发压缩。也可以手动触发,使用/compact指挥部。
Pi 在回合结束后检查自动压缩。在此之前,每个请求扩展现有提示符,并可重复使用其缓存前缀。如果遇到上下文溢出错误,Pi 也可能在回合中途压缩。
在压缩时,Pi 保留了部分近期消息不变。
before compaction:
[system + tools][older turns][recent retained messages]
保留消息数量因Pi使用而变化可配置令牌预算.Pi目前默认的2万个代币,大约需要5到20回合。在这个截止点之前的所有消息都会被提取并序列化,并会进行总结。
Pi 的压缩提示词
对编码代理来说,一个好的总结理想结果就像从一个班次切换到下一个班次的交接简报。Pi的压缩提示强调了现有上下文中有很多内容已经不再相关。
我们应该只保留下一个LLM请求中仍然重要的上下文。
因此,Pi发送的压缩请求与普通对话不同。
- 独立压缩请求中使用的系统提示不同。我们不是告诉LLM“你是专家编码助手”,而是告诉LLM“你是上下文摘要助理。”
- 压缩请求中的用户消息也不同。它请求“这是本次对话分支的结构化总结,方便稍后回来时提供背景。”提示指定了目标、进度和关键决策的章节。
- 这是一个独立请求,不使用任何现有的对话记录,这意味着它可以使用不同的大型语言模型,而不会产生不必要的成本。
压缩结果作为压缩条目附加到 Pi 会话中,会话可以继续。压缩请求结束后,上下文被压缩。
after compaction:
[system][tools][summary][recent turns][new user message]
现在对话语境中有更多留言空间。
Pi 在会话中以纯文本形式存储压缩摘要。这保持了压缩后的上下文可读性,便携式,因为我们可以在Pi中切换模型并继续使用摘要。
压缩与提示缓存
提示缓存LLM提供者利用它来降低同一对话中的重复请求成本。在活跃编码会话中,我们为模型已生成的上下文支付更少的费用。这种缓存需要精确的前缀匹配,因此压缩会话会破坏提示缓存。
cached before compaction:
[system][tools][older history][recent retained turns]
<-------------------- cached prefix -------------------->
first request after compaction:
[system][tools][summary][recent retained turns][new user message]
<-- reusable -->^
|
first changed token
|
+-- everything after this point must be recomputed
保留的回合包含相同的标记,但它们现在遵循不同的前缀。因此,它们之前的缓存状态无法重复使用。
压缩后的新请求将再次受益于快速缓存。
实验
由于 Pi 是可伸缩且可塑的,你可以用自己的压缩替换它的压缩。要测试不同的压缩机制,请让 Pi 创建一个带有自定义压缩提示的扩展。