我与大语言模型打交道越久,就越发现自己陷入一场永恒的战争:上下文够不够、太多、Token 浪费、压缩拉锯战。看看那些智能体记忆系统,我们能发现一些强大的武器来应对这个问题。
在我研究的 19 个系统中,有一个发现反复出现:更大的窗口反而加剧了预算问题,而不是解决它。一个 200K Token 的窗口并不会对全部 200K Token 一视同仁。性能在窗口填满之前就已经下降了。这种下降是非均匀的:处在长上下文中间的内容,受到的关注度可靠地低于边缘的内容。而且,智能体的实际任务只占用窗口的固定一部分,无论窗口有多大,这意味着其他所有内容都在争夺相同的注意力预算。
那些处理好这个问题的系统,都收敛到了六种机制上。这些机制都不稀奇。有几个甚至简单得令人尴尬。但跳过它们的系统,都付出了代价。
六种机制
压缩传递
这是最显而易见的机制。你把一段长对话或一大段记忆片段,进行总结,然后用总结替换掉原文。MemoryOS 在片段级别上这样做:它的片段总结器会在对话片段超过阈值时触发,将其压缩成紧凑的表示形式,防止它挤占工作上下文。Karpathy 模式的知识库(purpose.md、overview.md)在知识层面做了类似的版本:知识库是智能体学到的关于某个主题的所有内容的压缩形式,跨会话维护。
其代价是信息丢失。压缩本质上是一种有损操作。总结只捕捉了总结器在压缩时判定为相关的内容。如果智能体后来需要某个当时未被判定为相关的细节,它就没了。这不是避开压缩的理由,但这是一个不把它当作唯一机制的理由。
还有第二个容易被忽视的成本。压缩在运行时也不是免费的。MemoryOS 在一次交互中可能要支付 20 次甚至更多的大语言模型调用,来维护其片段总结。对于交互频率高的系统来说,这是一个实实在在的运营成本。
结果预览截断
每次检索不返回完整的记忆内容,而是返回一个简短的预览,让智能体决定是否要获取完整记录。supermemory 提供了片段长度控制,让调用者可以调整每次返回多少文本。mem9 更进一步:它用三个环境变量(MEM9_SOURCE_TURN_MIN_SCORE、MEM9_SOURCE_TURN_PER_MEMORY_LIMIT、MEM9_SOURCE_TURN_TOTAL_LIMIT)来装饰源轮次,让操作者精确控制出现多少源轮次,以及最低相关度分数是多少。
其代价是多一次工具调用。如果智能体需要完整内容,它必须显式请求。对于大多数检索模式来说,这是一个合理的权衡:智能体在支付读取整条记录的 Token 成本之前,就能获得足够的信号来判断记录是否相关。
两步检索
这是预览截断的一个具体且重要的变体。搜索返回标识符和简短预览。一个单独的 GetByID 调用在需要时获取完整记录。mem9 的 MemoryRepo 接口就是围绕这种模式构建的:搜索和获取是独立的操作,拥有不同的 Token 成本。
数字清楚地说明了这一点。如果一次返回 10 条匹配,每条 1,500 Token,那就是 15,000 Token 注入到上下文中,不管智能体用不用它们。两步检索返回 10 个标识符和简短预览,总共大约 450 Token,然后只获取智能体实际需要的记录。在一个会话的 20 次召回步骤中,这个差异累积起来可以节省大约 200,000 Token。
这是你能采用的最便宜的纪律。它不需要对记忆存储进行任何架构更改,不需要额外的大语言模型调用,也没有信息丢失。它只是一个检索接口的决定。
分解再召回
不将完整的用户查询发送给检索层,而是先将其分解为子查询。SimpleMem 的意图感知检索规划器在命中记忆存储之前,将传入的查询分解成原子化的检索意图。GitNexus 用其查询工具分解做了类似的事情:复杂的查询被分割成有针对性的子查询,每个子查询检索记忆图的一个聚焦片段。
好处是精确性。分解后的查询检索到的不相关材料更少,这意味着上下文中的噪声更少。代价是延迟:分解在检索开始之前增加了一个规划步骤。对于交互式智能体来说,这很重要。对于批量或后台智能体来说,通常没关系。
分层存储作为预算过滤器
如果你已经构建了一个分层记忆架构(上周那篇文章的主题),你会得到一个副产品:预算过滤。supermemory 的三层模型意味着,热门的、频繁访问的材料位于一个返回紧凑、高信号结果的层级中。冷门材料位于一个默认不被查询的层级中。Hindsight 的观察层也是这样工作的:原始观察不会直接注入到上下文中;它们在被提升到更高层级后,才会成为检索候选。
代价是召回完整性。尚未被提升的材料可能与当前查询相关,但不会在标准检索过程中出现。这和压缩的代价一样,但失败模式不同:你不是因为总结而丢失信息,而是因为降级而丢失。
自引导工具响应
这是讨论最少的机制,也是比较有趣的一个。不是让智能体在工具调用后自行决定做什么,而是工具响应本身包含一个关于下一步做什么的提示。GitNexus 在工具响应中附加了一个 ---
**Next:** 块,建议后续操作。mem9 用结构化的元数据装饰源轮次,引导智能体的下一步检索。
其效果是,智能体在工具调用之间花费更少的 Token 进行规划。工具响应携带了足够的结构,让下一步变得显而易见。代价是提示工程的工作量:编写良好的自引导响应需要预先知道智能体下一步可能需要什么,这并不总是可行的。
Tolaria 的极限案例
Tolaria 值得单独看看,因为它代表了预算纪律走向极端的逻辑终点。ADR-0009 记录了从系统中彻底移除 Embedding 的决定。Tolaria 只使用子字符串搜索。没有向量索引,没有语义检索,没有 Embedding 调用。
理由很直接:最便宜的 Token 就是你从一开始就没有检索到的那个。基于 Embedding 的检索返回语义相似的结果,这意味着它会返回智能体没有明确请求的结果。其中一些结果有用。很多没用。但所有结果都要消耗 Token。
Tolaria 的立场是,对于其用例来说,不相关但相似的结果的成本,在一个会话中累积起来,超过了语义召回带来的好处。这个权衡是否适用于你的系统,取决于你的系统是用来做什么的。对于查询精确且结构化的系统(代码导航、通过标识符查找文档),Tolaria 的立场是站得住脚的。对于查询模糊且探索性的系统,移除 Embedding 会以难以恢复的方式破坏召回。
Tolaria 案例的价值不在于你应该复制它。而在于它让语义检索的成本变得可见,而大多数系统都没有做到这一点。
反对纯压缩系统的理由
在 19 个系统中,有几个依赖压缩作为它们主要或唯一的预算机制。这些失败模式值得指出。
首先,总结会丢失那些在压缩时未被判定为相关、但后来变得相关的细节。这不是假设:这是任何有损压缩方案应用于未来相关性未知的信息时的标准失败模式。
其次,压缩是一个热路径成本。MemoryOS 每次交互支付 20 多次大语言模型调用,这在压缩密集型系统中并不罕见。在规模下,这个成本不可忽视。
第三点,也是最微妙的一点:没有逃生舱的压缩就是缓慢的遗忘。如果减少上下文大小的唯一方法是总结,而总结又是有损的,那么系统就在不断丢弃信息,且无法恢复。两步检索、分层存储和结果预览截断都保留了原始记录。压缩做不到。
这些都不意味着压缩是错误的。它意味着仅靠压缩是不够的。
近期加权和持久队列
有两个机制不属于上面六类,但值得一提。
graymatter 使用 RRF 融合,并将近期性权重设为一半。严格来说,这不是一个预算机制,但它起到了类似的作用:通过降低旧材料在检索排名中的权重,它减少了过时、低信号记录挤占近期、高信号记录的可能性。其效果是通过排名权重进行软分层,而非显式的层级提升。
llm-wiki 的 540 行摄取队列状态机采取了不同的方法。该队列序列化摄取操作,并在任何内容进入记忆存储之前,应用一个四信号相关度排序器。预算控制在写入时而非读取时进行。未通过相关度阈值的内容不会被存储,这意味着它不会被检索,也不会消耗上下文。这是间接的预算控制,但它是持久的:节省的效果会在每一次未来的会话中累积。
设计良好的系统的共同点
纵观这 19 个系统,那些处理好上下文预算的系统共享一些特性。
它们将检索视为两步操作,而不是一步注入。它们先返回预览,再返回完整记录。它们保留原始记录,而不是用总结替换它们。它们通过显式参数而非硬编码默认值,让操作者控制检索量。而且,它们在写入时和读取时都考虑预算。
那些处理得不好的系统,往往依赖于单一机制(通常是压缩),并将上下文窗口视为一个需要填满的缓冲区,而不是一个需要管理的资源。
研究的最终结论很简单。更大的窗口需要更多的纪律,而不是更少。不是因为填满它们在原则上是错的,而是因为用错误的内容填满它们,比让房间空着代价更大。
在我的下一篇文章中,我计划从“记忆即注入”转向“记忆即工具”,涵盖这 19 个系统如何处理自动推入上下文的内容与智能体必须显式请求的内容之间的界限。*
一如既往,如果你觉得这篇文章有趣、有用,或者只是想帮忙传播知识:
请分享





