🤖 AI记忆系统设计指南
为什么记忆系统是必要的?
当作者第一次使用 MemGPT(现 Letta)时,感觉就像"跨越了卢比孔河"——记忆将简单的问答机器人转变为看似有生命的实体。
但是,随着模型上下文窗口增大,有人怀疑记忆系统变得不必要。然而研究证明:
- 上下文中间信息遗漏: 研究显示当相关信息在文档集合中间时,性能下降30%
- 模型退化: Chroma研究显示所有前沿模型随着上下文窗口增长而退化
- 信息过载负担: 大量信息增加了搜索和确定相关性的负担
记忆系统的四个阶段
所有记忆系统可分为四个通用阶段:存储(Store) → 检索(Retrieve) → 注入(Inject) → 发出(Emit)
1. 存储 (Store)
存储方法主要分为两派:
- 图数据库: Zep 声称"state of the art"的大海捞针性能
- 平面文件: Letta研究发现"Files are all you need",Claude Code使用markdown文件+frontmatter存储元数据
关键挑战:正确性
AI记忆系统主要犯三种错误:
- 时间错误: LLMs不擅长时间推理,"下周四"这样的日期会很快过期
- 优先级错配: AI会保存当下对话中无关紧要的细节
- 事实错误: 人类会改变想法、记错事情,记忆必然包含事实错误
关键挑战:隐私
你希望AI agent发展记忆并了解你的一切吗?作者认为AI的未来是本地和开源的。
2. 检索 (Retrieve)
检索的第一个关键决策是如何启动记忆搜索:
- 工具调用: 大多数实现暴露 search_memory 工具
- 自动触发: 在agent循环外自动操作agent上下文
向量相似度的陷阱
基本的向量相似度是最有效的,但容易误排或匹配表面相似但实际不相关的内容,导致响应像"太棒了!想谈谈我们之前讨论的完全不相关的话题吗?"
解决方案: 检索后增加过滤步骤,但会增加延迟。
Claude Code的例外
Claude Code不使用向量相似度,而是保持一些关于哪些记忆可用的元数据,委托给后台Sonnet调用。作者猜测是因为他们没有公共的embeddings API。
3. 注入 (Inject)
将回忆的记忆注入标准OpenAI上下文有点像"把方钉塞进圆孔"。标准LLM API没有自然的地方来放置"以下是相关对话的额外信息"。
选项包括:
- 更新system message: 概念上最干净,但频繁更新会使prompt缓存失效,成本高昂
- 工具调用: 作为tool result插入
- Assistant消息: 插入assistant消息
4. 发出 (Emit)
最终决定如何发出响应——直接用户消息、工具调用还是assistant消息。
实践案例:Elroy
作者构建了自己的开源系统 Elroy (elroy.bot),已交互3年。它帮助作者头脑风暴、讨论职业起伏,作为一种交互式日记。
存储策略
经过实验,作者选择markdown文件而非数据库。记忆文件直接放在Obsidian Vault中,与其他笔记自然融合。
不关注实体分类,而是关注agent应该对记忆做什么。使用"议程项目(Agenda Item)"代表作者的一些长期目标,包括子任务和提醒触发器。这使记忆可操作,而不仅仅是泛泛地提供信息。
检索策略
Elroy检索少量记忆,与上下文中已有的进行去重。最初注入原始文本,但发现上下文膨胀。后来添加了反思步骤,让AI暂停思考回忆如何与对话相关联。
最新方案:原始文本,但加上简单的LLM-backed过滤步骤过滤向量相似度搜索结果。误报比漏报更糟糕——让agent突然谈论完全不相关的话题会很奇怪。
核心要点
- 长上下文 ≠ 记忆: 研究证实在中间信息性能下降30%
- 四大阶段: Store, Retrieve, Inject, Emit
- 存储选择: 文件(Claude Code) vs 图数据库
- 检索: 向量相似度 + LLM过滤
- 注入: system message vs tool calls(需权衡缓存)
- 正确性挑战: 时间错误、优先级、事实错误