给一群 AI 智能体共享一份记忆——当每个智能体运行不同模型
多数智能体框架都会给每个智能体分配一个独立的上下文窗口(模型一次能处理的文本范围),并称之为记忆。单个智能体时,这个做法还行;一旦运行多个智能体,它就悄悄成了系统里代价最高的设计选择。我们运营着一组智能体,特意让不同的智能体——另一个负责结构化抽取,还有一对完全走本地路径,不依赖任何外部推理服务。按能力分流还算简单,真正的难点在于:一个智能体学到的东西,只有它自己知道。
这篇文章记录了我们踩过的坑,以及最终采用的方案。
故障模式
症状表现为重复劳动。某个抽取智能体发现,某家供应商的发票把税额栏放在小计上方,这算一条有用的知识。两天后,另一个智能体——不同模型、不同提示词、同一条流水线——遇到同一家供应商,从头又推导了一遍。接着第三个智能体再来一遍。没有任何东西出错:每个智能体的行为都是正确的。但整个系统就是无法积累任何东西,因为知识只存在于当时恰好打开的那个上下文窗口里。你在为已经拥有的知识,反复支付推理费用。
最直观的修复办法是传入更多历史记录。这个方案会失效,原因值得说清楚:上下文窗口是按调用、按模型隔离的。某个模型的 20 万 token 窗口,帮不到另一个运行 3.2 万 token 窗口的模型,而且两者都会在会话结束时消失。你不能用更大的缓冲区来解决持久化问题。
“统一记忆”到底意味着什么
一旦接受记忆必须存在智能体之外,需求就变得具体了:
与模型无关的存储。 如果记忆以某个模型的向量嵌入形式存储,你就把记忆层和某个供应商绑死了。以后换模型,意味着全部重新索引。
一个智能体写入,所有智能体可读。 否则就只是带额外步骤的按智能体记忆。
可溯源。 当记忆出错时——它一定会出错——你需要知道是哪个智能体、哪条上下文写入了这条错误信息。
...会话,以及时间。无法归因的记忆,就是无法修复的记忆。
限定范围。 并非每个智能体都应该看到所有内容。客服智能体没有理由去读财务上下文。
可审计。 凡是涉及受监管的工作,“智能体知道 X” 就必须成为一项你可以……
能提供证据,而不是靠推断。第 3 点往往被人跳过,而恰恰是它最坑人。共享记忆库如果没有溯源信息,每次输出出错都会变成一场无休止的排查。
最终形态:刻意分开的两层
只追加的事件日志是事实来源。 每次写入记忆都是一个事件,附带智能体身份、会话身份、渠道和时间戳。日志从不被修改。如果某个事实后来发现是错的,就追加一条更正,而不是改写历史。正是这一层让第 3 点和第 5 点成为可能,而且它刻意做得朴素无华。
派生索引才是智能体真正查询的东西。 它从日志重建而来,也就是说它是一次性的。换 embedding 模型、改分块方式、或者认定某条路径不该用语义搜索——重建索引就好,日志毫发无损。
关键在于依赖的方向:索引依赖日志,而没有任何东西依赖索引。正因为这样,你可以不迁移数据就更换检索策略——同样是换一个 embedding 模型,有人花一下午,有人花一个季度,差别就在这里。
目前的实现是在配置好的路径上跑一个文档索引后端,按计划周期执行嵌入(embed),另外还有带保留时长的会话导出。具体用哪个后端远不如这个分层重要——我们已经换过一次后端,日志让这次更换悄无声息。
检索是圈定范围的问题,不是搜索的问题
我们本能地想让检索更聪明:更好的 embedding、重排序、混合搜索。但实践证明,收益来自在排序之前先收窄每个智能体可搜索的范围。
一个在问发票格式的智能体,不应该去搜支持工单记录。这不是质量考量,而是正确性考量。跨领域的语义近邻,恰恰就是那种「看着合理其实错误」的上下文,会让模型自信地胡说八道。
先圈范围,再排序。一个小而正确的候选集,胜过一个大而排序精美的候选集,而且成本低得多。