跳到主要内容

第 4 章 · 压缩、巧妙之处、边界

本章讲上下文满了怎么办(compaction),然后提炼整套设计可借鉴的精华、它的边界,以及和兄弟项目的取舍对比。

4.1 Compaction:上下文版的「换页」

它要解决的小问题: 对话越来越长,迟早塞不进上下文窗口。直接截断会丢信息;Letta 的做法是把旧消息摘要成一条「summary 消息」,用少量 token 保住要点。

什么时候触发

两条触发路径:

  1. 被动(最常见)。 调模型时真的抛了 ContextWindowExceededError,在 _step 里捕获,压缩后重试最多若干次(agents/letta_agent_v3.py:1218-1247)。
  2. 阈值。 get_compaction_trigger_threshold(services/summarizer/thresholds.py:27)按模型给阈值:GPT-5 家族在上下文窗口的 90% 就主动压缩(SUMMARIZATION_TRIGGER_MULTIPLIER = 0.9,constants.py:83),其他模型到 100% 才压。注释解释:观察到 GPT-5 在输入接近 272k 时会撞 max_output_tokens,所以提前压(thresholds.py:34-39)。
# 真实源码节选,thresholds.py:41
return int(llm_config.context_window * SUMMARIZATION_TRIGGER_MULTIPLIER)

怎么压

入口 compact_messages(services/summarizer/compact.py:135),返回一个 CompactResult(摘要消息 + 被压掉的消息 + 摘要文本 + 新 token 估计,compact.py:32-39)。它支持多种模式(compact.py:189-219):

模式思路
self_compact_all让 agent 自己(用自己的模型)总结全部消息
self_compact_sliding_window滑动窗口式自我总结(上面失败时的兜底)
summarize_all / sliding window用一个独立的 summarizer 模型总结

巧妙处:压缩路由到便宜模型

压缩用哪个模型很讲究(build_summarizer_llm_config,compact.py:42):

  • auto 模式的 agent,压缩特意路由到 Haiku 4.5(便宜、适合总结),Haiku 不可用再退到 zai/glm-5(compact.py:65-77)。
  • 没指定就按 provider 取轻量默认模型(compact.py:79-84)。

直觉:总结是个「低智力、高频次」的活,没必要烧主力大模型的钱——这是很实际的成本工程。

4.2 巧妙之处(可借鉴的技术)

  • 把上下文管理「工具化」。 最核心的一招:不靠系统在背后偷偷截断,而是把 archival_memory_searchmemory_replace 等做成工具,让模型自己决定何时存、何时取、何时整理(functions/function_sets/base.py)。上下文管理从「被动截断」变成「主动操作」。
  • 把外部存储的「目录」喂回提示。 <memory_metadata> 告诉模型「recall 还有 N 条、archival 有这些标签」(prompt_generator.py:69-88),模型才知道值得去搜——否则它不知道外面有什么。
  • 编辑工具借鉴代码编辑的「精确匹配 + 唯一性」。 memory_replace 要求 old_string 唯一、拒绝行号前缀(base.py:343-373),把 LLM 易错的模糊定位逼成精确定位。
  • 系统提示按需重建。 只在记忆真变了才重编译 message[0],动态元数据不计入变化判断(letta_agent_v2.py:764-766,853-860)——省掉大量无谓重建。
  • 压缩路由到便宜模型 + 按模型设触发阈值(compact.py:65-77thresholds.py:27)。
  • 工具返回动态截断,防单个工具结果吃掉上下文(letta_agent_v3.py:143-153)。

4.3 边界与局限(诚实)

  • 重度依赖持久化层。 三层记忆里 recall/archival 都在数据库;archival 还要嵌入向量(pgvector / Turbopuffer / Pinecone)。这不是一个纯内存库,要跑起来得有数据库 + 嵌入服务,部署比「拼 prompt 的 agent 库」重。
  • 多代实现并存。 仓库里同时有 LettaAgentV2/V3、多种 AgentType、多套系统提示(memgpt/memgpt_v2/letta_v1/react/voice…)。这是活跃演进的产物,读代码时要认清「当前主力是 v3 / letta_v1_agent」(agent_loop.py:20-43),别被旧路径带偏。
  • 摘要必然有损。 compaction 把旧消息换成摘要,细节会丢;靠 recall 搜索找回原文,但前提是模型想起来要搜。
  • 行号编辑的脆弱性。 行号视图给 Anthropic 模型用,但需要工具层主动拦截「模型把行号写进编辑参数」的错误(base.py:345-352)——说明这条路径本身是易错的,靠防御性校验兜着。
  • 本库未深读的部分(见 frontmatter uncertainties): 各 provider client 的具体适配、归档 hybrid 搜索的完整 SQL、sleeptime 多 agent 的内部 step 逻辑。

4.4 横向对比(同 shelf 兄弟)

Letta 在「memory」这个关切上的取舍,和其他做记忆/agent 的项目可对照:

维度Letta (MemGPT)典型「prompt-stuffing」agent典型纯向量 RAG 记忆
常驻记忆可被 agent 自编辑的记忆块,渲染进系统提示把全部历史塞进上下文,满了截断一般没有「常驻、可编辑」概念
长期记忆archival(向量 + 标签)+ recall(全量消息)无,或简单滚动窗口向量库检索
谁管理上下文模型自己(用工具)框架被动截断检索器(retriever),模型被动接收
上下文超限compaction 摘要 + 可路由便宜模型截断/报错不适用(每次重新检索)

一句话定位:Letta 把 RAG 的「外部记忆」和 agent 的「工具调用」缝合成一个统一系统,并让模型成为自己上下文的管理者——这正是 MemGPT 论文「LLM as OS」隐喻的工程落地。

4.5 总代码地图(导航索引)

主题文件符号
压缩入口letta/services/summarizer/compact.pycompact_messagesCompactResult
压缩模型选择letta/services/summarizer/compact.pybuild_summarizer_llm_config
触发阈值letta/services/summarizer/thresholds.pyget_compaction_trigger_thresholdis_gpt5_model_family
上下文超限重试压缩letta/agents/letta_agent_v3.py_step(ContextWindowExceededError 分支)
工具返回截断letta/agents/letta_agent_v3.py_compute_tool_return_truncation_chars
关键常量letta/constants.pyDEFAULT_MAX_STEPSSUMMARIZATION_TRIGGER_MULTIPLIERCORE_MEMORY_BLOCK_CHAR_LIMITMAX_EMBEDDING_DIM
上下文窗口概览 schemaletta/schemas/memory.pyContextWindowOverview

回到 index.md · 上一章 03-system-prompt-assembly.md