最火的AI Agent框架OpenClaw,记忆系统居然只用了一个SQLite
最近在翻OpenClaw的源码,翻到记忆系统那块的时候愣住了。

这玩意的整套RAG——向量索引、全文检索、嵌入缓存——全塞在一个SQLite文件里。没有Pinecone,没有Weaviate,没有任何外部向量数据库。
我第一反应是:这也太糙了吧?
看完实现之后改主意了。不是糙,是想明白了才这么干的。
先说背景:为什么AI需要"记忆"
你跟ChatGPT聊过的都知道,AI是没有长期记忆的。你昨天告诉它你喜欢用TypeScript,今天它又问你一遍。
OpenClaw要解决的就是这个问题。它让AI Agent能记住你之前聊过什么、做过什么决定、偏好是什么,下次对话的时候直接调出来。
这就需要一套检索系统——把你的历史信息存起来,需要的时候快速找到最相关的那几条。业内叫RAG(Retrieval-Augmented Generation),检索增强生成。
大部分团队做RAG的标配是:向量数据库 + 嵌入模型 + 一堆胶水代码。OpenClaw的做法是把这些全打包进SQLite。
一个SQLite凭什么能干这事?
因为SQLite不只是"那个轻量级数据库"。
加上 sqlite-vec 扩展,它能做向量KNN搜索。加上 FTS5 模块,它能做全文检索。两个加在一起,就是一个麻雀虽小五脏俱全的RAG引擎。
OpenClaw在SQLite里建了6张表,分工非常清楚:
- meta表存配置元数据,比如用的哪个嵌入模型、向量维度多少
-
- 文本被切成小块存在chunks表,每块带着embedding向量
-
- 向量索引在chunks_vec虚拟表里,用sqlite-vec做KNN查询
-
- 全文索引在chunks_fts虚拟表里,用FTS5做关键词匹配
-
- files表追踪哪些文件索引过了,通过对比内容哈希和mtime判断要不要重新处理
-
- embedding_cache表缓存嵌入结果,同样的文本不重复调API
没有多余的东西。我看过不少RAG实现,表结构动辄十几张,各种冗余字段。OpenClaw这6张表的设计,看得出是想清楚了才动手的。
- embedding_cache表缓存嵌入结果,同样的文本不重复调API
最精妙的部分:混合检索
单纯用向量搜索有个问题——你搜"JWT认证"这种精确术语的时候,语义检索经常不如关键词好使。反过来,你问"之前讨论的那个安全方案"这种模糊描述,关键词又搞不定。
OpenClaw的解法是两路同时跑,然后五步合并:
取并集 → 加权求和(向量0.7 + 关键词0.3) → 时间衰减 → 排序 → MMR去重
时间衰减这个设计我特别喜欢——30天前的内容得分自动减半,但核心决策文档标记为"常青"永不衰减。你一个月前的会议记录确实不该和昨天的笔记抢位置。
MMR(Maximal Marginal Relevance)是防止返回一堆高度重复的结果。前两条已经选了JWT相关内容,第三条即使JWT分数也高,也会让位给一条关于OAuth的不同视角。
整条管线的权重、衰减速率、MMR参数全部可配。想要纯向量搜索?textWeight设0。想关时间衰减?一个开关。这种"默认好用、细节可调"的设计风格贯穿整个OpenClaw。
嵌入模型的降级策略
这块也值得说一下。OpenClaw支持6种嵌入提供商——OpenAI、Gemini、Voyage、Mistral、本地Ollama、还有完全离线的GGUF模型。
设成auto模式时,它会按优先级自动往下找:
本地有GGUF模型文件?直接用,零网络依赖。没有?看谁有API key就用谁,OpenAI、Gemini、Voyage、Mistral挨个试。全都没有?没关系,退化成纯全文检索模式,不做语义理解但照样能搜。
不是报错崩溃,是一级一级往下降。这比我见过的大多数RAG框架都优雅——很多项目embedding配不好直接就挂了。
有个细节:Ollama故意被排除在auto之外。因为auto模式不该假设你本地开着一个Ollama服务,要用得手动指定。这种克制我觉得是对的。
为什么不用向量数据库?
这是我看完源码之后想得最多的问题。
Pinecone、Milvus、Qdrant这些专业向量数据库,处理几千万条数据没问题。但OpenClaw面对的场景是什么?个人AI助手,每个用户几百篇笔记,顶多几千个文本块。
用向量数据库就意味着:要单独部署一个服务、要维护连接池、要处理服务挂了的情况、每个月还要付费。
SQLite呢?数据就在一个文件里,备份就是复制文件,迁移就是拷走,ACID事务保证一致性。不需要运维,不需要网络,不需要付钱。
OpenClaw的源码注释里说得很直白:牺牲分布式扩展性,换取零服务器依赖和极低运维复杂度。
对于个人工具这个场景,这就是正确的取舍。不是每个钉子都需要用向量数据库这把锤子。
几个可以直接拿走的实践
翻源码的时候记了几个有意思的工程细节:
本地嵌入模型的向量要做L2归一化。 OpenClaw对本地GGUF模型、Gemini、Ollama返回的向量做了归一化处理,归一化后余弦相似度等价于内积,sqlite-vec的L2距离搜索直接就是余弦排序。OpenAI、Voyage这些远程API本身就返回归一化过的向量,所以不需要再处理。这个区分很细,但说明作者对每家API的行为都摸清楚了。
嵌入缓存按四元组做主键: (provider, model, provider_key, content_hash)。换了模型不会命中错误缓存,换了API endpoint也不会。见过不少项目在这里翻车——切模型后旧缓存污染新索引。
中文BM25要过滤单字。 “的”、“了”、"是"这种单个汉字基本没区分度,直接跳过。关键词上限12个就够了。OpenClaw的QMD后端专门写了一个normalizeHanBm25Query函数处理这个,在外部检索引擎做中文搜索时自动生效。
文件去重用realpath。 符号链接可能导致同一个文件被索引两遍,fs.realpath()解析真实路径后用Set去重。小细节,但说明作者踩过这个坑。
说到底
OpenClaw这套记忆系统给我最大的启发不是技术层面的——SQLite + FTS5 + sqlite-vec这个组合并不新鲜。
真正让我佩服的是克制。
在一个大家都在往项目里塞Redis、Kafka、向量数据库的时代,OpenClaw选了最简单的技术栈,然后在检索策略上下足了功夫。五步混合管线、时间衰减、MMR去重——这些才是真正决定搜索质量的东西,跟你用什么数据库关系不大。
做AI产品的同行可以想想:你的RAG系统,真的需要那么重的基础设施吗?
更多推荐


所有评论(0)