我不再与AI记忆问题对抗,而是开始对其建模
摘要
作者详细讲述了从平面向量存储转向图数据库(FalkorDB)以构建AI记忆的过程,在其LocalClaw项目中实现了多跳推理、时间查询和溯源追踪。
我看到的大多数AI记忆实现都是一个向量存储器加上一个检索功能的拼凑。你把一些文本嵌入,扔进Chroma或Qdrant,然后就收工大吉。这招在失效前是管用的,而且它的失效速度比人们预期的要快得多。我想聊聊我为LocalClaw实际构建的东西,为什么最后选择了FalkorDB,以及一路走来我学到了什么。不是理论,是真实发生的事。
# 扁平存储阶段
我从一个JSONL事实存储开始。追加事实,通过嵌入相似度检索,注入到上下文中。简单得不行。用了几个星期后,它变成了一团乱麻。我有14条关于同一主题的近乎重复的事实。不同对话中的措辞略有差异,全都分开存储,全都注入进去。去重是多层的——哈希匹配、子串检查、嵌入相似度——但还是不够。每一层都能抓到一些东西,却漏掉另一些。
更大的问题是事实之间没有关系。"Peter在DevMesh工作"和"DevMesh正在构建一个外联平台"是扁平列表里两个孤立的嵌入。你能检索到每一个,但无法从其中一个遍历到另一个。你没法让系统找出所有与DevMesh相关的东西。你无法追踪一个事实是如何随时间演变的。要么有这条事实,要么没有。
我也没有时间智能。当某件事发生变化时,旧事实和新事实并存,没有任何信号表明哪个是当前的。系统不知道它上个月知道什么,也不知道它现在知道什么。在对扁平存储迭代了四次之后,我承认我一直在修补错误的东西。
# 为什么选FalkorDB
我需要一个图。我认真考虑过的选项有Neo4j、Memgraph和FalkorDB。
Neo4j社区版就是个笑话。它故意被阉割,为了把你推向企业版。我不打算为它付费。Memgraph很扎实,但没有原生向量搜索。我必须在它旁边再跑一个独立的向量数据库,而这正是我想摆脱的那种复杂性。
FalkorDB跑在Docker里,使用Redis有线协议,内置原生的HNSW向量搜索,在我目前的规模下内存占用大约85MB。它的许可证是MIT附属的。这就是全部理由。一个存储器。图遍历、向量相似度、混合关键词搜索,全都有了。没有独立的Qdrant容器,没有两个数据库之间的同步问题。只有一个东西能做所有事。
# 图实际带来了什么
模式围绕事实、实体以及它们之间的关系构建。每一条事实通过ABOUT边连接到它所引用的实体。所以"Peter在DGX Spark上运行LocalClaw"会创建一个事实节点,连接到代表Peter、LocalClaw和DGX Spark的实体节点。现在我可以遍历了。给我所有与DGX Spark相关的事实。给我所有与提到LocalClaw的事实相连的实体。这是扁平存储做不到的多跳推理。
当一条事实发生变化时,我不会覆盖它。新事实会生成一条SUPERSEDES边指向旧事实。两者都保留时间戳。我可以查询系统在任何时间点知道什么。"我上个月对这个人角色的了解是什么?"现在是一个真正的查询。每一条事实都通过EXTRACTED\_FROM边追溯到它来自的对话轮次。溯源内置于模式之中,而不是事后才考虑。
向量索引在FalkorDB内部运行:
```sql
CREATE VECTOR INDEX FOR (f:Fact) ON (f.embedding) OPTIONS {dimension: 4096, similarityFunction: 'cosine'}
```
4096维向量来自qwen3-embedding:8b,HNSW索引。O(log n)搜索。没有外部数据库。
# 真正让我意外的地方
小型本地模型在盲目工作时,实体抽取是不可靠的。phi4-mini会把DGX Spark归类为软件。会为"open-source model"和"open-source models"分别创建节点。它没有上下文可供参考,所以它就猜,而且猜得前后不一致。
解决办法是让图来教模型。在从新事实中抽取实体之前,我先从图中查询已有的带类型实体,然后注入到NER提示中:
```
已知实体:
- "DGX Spark", "Mac Mini", "A5000" → 硬件
- "FalkorDB", "Ollama", "LocalClaw" → 软件
- "DevMesh" → 组织
```
现在当phi4-mini在一条新事实中看到DGX Spark时,它有了参考上下文。它会一致地分类,因为它不是从零开始的。每一条正确标注类型的实体都会让未来的抽取变得更好。图会随着时间的推移变得更聪明,而无需任何额外训练。这不是我计划好的,它是从架构中自然涌现出来的。
# 记忆注入
每条消息在被专业系统看到之前,都会触发记忆检索。四层按顺序运行。
**稳定事实**——任何重要度等级为4或5的内容,工作、家庭、重大项目——无论查询是否相关,始终注入。这些是身份级的事实,应该始终存在。
**上下文事实**来自对当前消息的向量搜索。按多信号得分排序取前5条,并与稳定事实去重。
**多跳关联事实**来自从向量搜索结果出发的图遍历。如果关于LocalClaw的事实得分很高,我就遍历实体连接,拉入关于FalkorDB、DGX Spark设置、Ollama的相关事实。这些是纯向量搜索无法浮现的,因为查询没有直接提及它们。
评分公式是:相似度50%,时效性20%,重要性30%。纯向量相似度会找出语义上最接近的东西,而不管它是否重要。昨天的天气评论在纯相似度下,可能比上周的健康状况得分更高。重要性权重解决了这个问题。
# 我学到的
最大的教训是,模型永远不应该做"是什么"的决定。代码决定哪些事实发生了变化、哪些是重复的、紧急程度分数是多少、时间戳意味着什么。模型决定这些事实意味着什么以及该如何处理。当你让模型去做算术、日期比较或基于哈希的去重时,你将会遇到你无法解释的失败。
第二点是,没有示例的重要性等级是没用的。我设了一个1-5的重要性标度,而phi4:14b把所有东西都默认设为2。模型没有参照系。一旦我加入了带有情感权重的具体示例——"妻子被诊断出疾病X" = 5, "问天气" = 1——它就正确校准了。抽象指令不管用,示例才管用。
第三点是,去重是一个流水线,而不是一次检查。哈希捕捉精确匹配。子串捕捉包含关系。嵌入捕捉改述。LLM合并捕捉语义重叠。没有单一方法能捕捉一切。你需要所有这些方法。
# 运行环境
整个记忆系统运行在一台Mac Mini上。FalkorDB在Docker里,qwen3-embedding:8b用于向量,phi4-mini用于实体抽取,phi4:14b用于事实抽取。没有云服务,没有API成本,没有数据离开机器。在当前规模下,图占85MB内存。就这些。
我不是说这是构建Agent记忆的唯一方法。我是说,带有检索功能的扁平事实存储不是记忆。它们是检索。这个差别比大多数实现所暗示的要重要得多。
LocalClaw是开源的。记忆系统是更大架构的一部分。很高兴回答关于它的任何问题。
相似文章
构建完全本地的AI记忆层:成功、失败与设计权衡
一篇技术博客文章,讨论了作者构建完全本地AI记忆层的经验,包括成功之处、失败之处以及涉及的设计权衡。
我厌倦了向每个AI工具重复解释我的项目,所以我为它们构建了一个本地记忆层
作者构建了mem-port,一个本地MCP服务器,通过嵌入式SurrealDB为AI编程助手(ChatGPT、Claude Code、Cursor、Windsurf)提供共享的长期记忆,解决了AI工具之间的上下文漂移问题。
如果AI记忆像人脑而非向量数据库那样工作会怎样?
介绍FERNme,这是一个面向AI智能体的开源记忆层,使用模糊Hebbian图模拟联想记忆,支持零LLM写入、持久化、遗忘及用户所有权等功能。
我为AI编码代理构建了一个开源持久记忆层
一个为AI编码代理设计的开源持久记忆层,使用Postgres和pgvector存储和检索项目决策与上下文,旨在减少上下文窗口大小并提高代理的一致性。
上周我构建了一个AI Agent,这周我添加了记忆功能!
一位开发者分享了他们使用 Anthropic SDK 和 TypeScript 构建具有记忆功能的 AI 智能体的经验,解释了工作记忆、情景记忆、语义记忆和程序记忆之间的区别,以及为生产环境扩展记忆所面临的挑战。