Pi 中的压缩机制是如何工作的

编程智能体中的上下文管理

大型语言模型 (LLMs) 在固定的上下文窗口内运行,这限制了它们在单次请求期间可以处理的输入量。在编程智能体会话中,随着系统提示词、工具定义、对话历史和工具输出的累积,输入会不断增长。一旦总输入超过上下文窗口,LLM 将会因大小错误而拒绝请求。

为了防止会话失败,智能体必须要么开始一段新对话——这会丢弃所有累积的上下文和先前的决策——要么实施 compaction(压缩),即为现有历史记录创建一个更小、更压缩的表示,以为新消息腾出空间。

Pi 的压缩实现

Pi 通过总结较旧的对话内容,同时保留一定数量的近期消息,来实现压缩。当上下文限制接近窗口总大小时,该过程会自动触发,或者也可以通过 /compact 命令手动触发。

压缩过程

Pi 的压缩遵循特定的工作流,以确保智能体在不浪费 token 的情况下保留关键信息:

  1. 保留预算 (Retention Budget):Pi 保留一定数量的可配置近期消息(默认为 20,000 tokens,大约 5 到 20 轮对话)保持不变。
  2. 序列化 (Serialization):保留截止点之前的所有消息都会被提取并序列化以进行总结。
  3. 专门的请求 (Specialized Request):Pi 会向“上下文总结助手”发送一个独立的请求,而不是标准的编程助手。这使得 Pi 可以使用不同的、可能更具成本效益的 LLM 模型来进行总结。
  4. 结构化输出 (Structured Output):压缩提示词明确要求生成一个分为三个部分的结构化总结:goal(目标)、progress(进度)和 key decisions(关键决策)。这充当了对话下一阶段的“交接简报”。

集成与可移植性

生成的总结以纯文本形式存储在会话中。这种方法确保了压缩后的上下文对用户是可读的,并且可以在不同的 LLM 模型之间进行移植,从而允许用户在不丢失已总结历史记录的情况下切换模型。

对提示词缓存的影响

提示词缓存 (Prompt caching) 通过允许 LLM 提供商重用对话的前缀,从而降低成本和延迟。然而,缓存需要精确的 token 对 token 的前缀匹配。

由于压缩机制是用一个新的总结来替换一大块较旧的历史记录,它改变了对话的前缀。这实际上破坏了现有的提示词缓存,这意味着在压缩后的第一次请求中,总结之后的所有 token——包括保留的近期对话轮次——都必须重新计算。一旦新状态建立,随后的请求将再次受益于缓存。

其他上下文策略与社区观点

虽然 Pi 使用基于 LLM 的总结,但开发者和用户提出了几种管理上下文溢出的替代策略:

修剪 (Pruning) vs. 总结 (Summarization)

一些用户认为,修剪(即确定性地移除低价值消息)优于总结。

"I find summarized conversations lead to more frustrating future chats because the LLM misses intent and or context."

建议的修剪策略包括移除 "thinking" 痕迹、工具调用输出和代码库探索日志,同时保留用户消息和最终的助手结论。

架构替代方案

  • 嵌套线程 (Nested Threading):一种方法是将旧消息移入一个会自动进行总结的子线程中,从而在提供整洁的主线程的同时,让子线程中保留完整的历史记录。
  • 动态修剪 (Dynamic Pruning):一些实现使用标签来动态折叠和展开工具调用和聊天历史的总结。
  • KV Cache 操作 (KV Cache Manipulation):对于运行本地堆栈的用户,有人建议直接在 GPU 上进行操作,在推理过程中清除或用总结替换旧的工具调用,从而避免进行完整的全新 LLM 请求。
  • 交接 (Handoffs):与其进行压缩,有些人更倾向于“交接”,即指示当前的 LLM 对一段全新的对话会话中所需的一切进行总结。

当前方法的局限性

标准压缩机制的批评者指出,对于本地 LLM 来说,解析 128k tokens 仅为了生成一个简短的总结,其计算成本可能非常高。此外,一些用户报告称,如果工具调用循环很长,智能体可能直到循环结束时才会检查压缩限制,这可能导致内存溢出 (OOM) 错误。

Sources

相关