最近在翻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张表的设计,看得出是想清楚了才动手的。

最精妙的部分:混合检索

单纯用向量搜索有个问题——你搜"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系统,真的需要那么重的基础设施吗?

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐