OpenClaw 学习系列之十三:SQLite 存储结构与混合检索深度解析
2026/6/7 3:45:08 网站建设 项目流程

SQLite 存储结构与混合检索深度解析

本文档基于源码分析,详细记录记忆系统的 SQLite 表结构和混合检索实现。
与 08. 记忆系统 互补,本文聚焦底层存储和检索算法细节。

一、数据库位置与驱动

数据库路径~/.openclaw/state/memory/{agentId}.sqlite

技术栈

组件技术
SQLite 驱动Node.js 内置node:sqlite(同步 API)
向量检索扩展sqlite-vec(原生编译扩展,通过src/memory/sqlite-vec.ts加载)
全文检索SQLite FTS5(内置虚拟表)
向量距离函数vec_distance_cosine()
关键词排名BM25 算法(FTS5 原生)

配置路径agents.defaults.memorySearch.store

store: { driver: "sqlite" // 默认值 path: "~/.openclaw/state/memory/{agentId}.sqlite" vector: { enabled: true // 是否启用 sqlite-vec 向量索引 extensionPath?: string // 自定义 sqlite-vec 扩展路径 } }

二、表结构详解(共 6 张表)

2.1meta– 元数据表

源码src/memory/memory-schema.ts:10-13

CREATETABLEIFNOTEXISTSmeta(keyTEXTPRIMARYKEY,valueTEXTNOTNULL);
类型说明
keyTEXT PK元数据键名
valueTEXT值(如 embedding 模型名、provider、chunk 配置等)

用途:存储记忆索引的全局配置信息,如当前使用的 embedding 模型、provider 等。


2.2files– 已索引文件追踪表

源码src/memory/memory-schema.ts:16-22

CREATETABLEIFNOTEXISTSfiles(pathTEXTPRIMARYKEY,sourceTEXTNOTNULLDEFAULT'memory',hashTEXTNOTNULL,mtimeINTEGERNOTNULL,sizeINTEGERNOTNULL);
类型说明
pathTEXT PK文件路径
sourceTEXT来源标识(默认'memory'
hashTEXT文件内容哈希
mtimeINTEGER文件修改时间(Unix 时间戳)
sizeINTEGER文件大小(字节)

用途:增量同步的基础。通过hashmtime判断文件是否变更,避免重复索引。


2.3chunks– 核心文本块表

源码src/memory/memory-schema.ts:25-36

CREATETABLEIFNOTEXISTSchunks(idTEXTPRIMARYKEY,pathTEXTNOTNULL,sourceTEXTNOTNULLDEFAULT'memory',start_lineINTEGERNOTNULL,end_lineINTEGERNOTNULL,hashTEXTNOTNULL,modelTEXTNOTNULL,textTEXTNOTNULL,embeddingTEXTNOTNULL,updated_atINTEGERNOTNULL);
类型说明
idTEXT PK块唯一标识
pathTEXT所属文件路径
sourceTEXT来源标识
start_lineINTEGER块在文件中的起始行号
end_lineINTEGER块在文件中的结束行号
hashTEXT块内容哈希
modelTEXT生成 embedding 所用的模型名
textTEXT块的原文内容
embeddingTEXTembedding 向量(JSON 序列化的浮点数组)
updated_atINTEGER更新时间戳

索引

  • idx_chunks_path ON chunks(path)
  • idx_chunks_source ON chunks(source)

分块配置(默认值):

chunking: { tokens: 400 // 每块 400 tokens overlap: 80 // 块间重叠 80 tokens }

2.4embedding_cache– embedding 缓存表

源码src/memory/memory-schema.ts:39-48

CREATETABLEIFNOTEXISTSembedding_cache(providerTEXTNOTNULL,modelTEXTNOTNULL,provider_keyTEXTNOTNULL,hashTEXTNOTNULL,embeddingTEXTNOTNULL,dimsINTEGER,updated_atINTEGERNOTNULL,PRIMARYKEY(provider,model,provider_key,hash));
类型说明
providerTEXTembedding 提供商(openai/gemini/voyage/local)
modelTEXT模型名称
provider_keyTEXT提供商的 API key 标识
hashTEXT文本内容哈希
embeddingTEXT缓存的 embedding 向量(JSON)
dimsINTEGER向量维度
updated_atINTEGER更新时间戳

索引idx_embedding_cache_updated_at ON embedding_cache(updated_at)

用途:避免对相同文本重复调用 embedding API,以(provider, model, provider_key, hash)四元组作为复合主键去重。


2.5chunks_vec– 向量虚拟表(sqlite-vec)

源码src/memory/manager-sync-ops.ts:234-237

CREATEVIRTUALTABLEIFNOTEXISTSchunks_vecUSINGvec0(idTEXTPRIMARYKEY,embeddingFLOAT[dimensions]-- dimensions 随 embedding 模型变化);
类型说明
idTEXT PK关联到chunks.id
embeddingFLOAT[N]浮点向量,维度由模型决定

启用条件agents.*.memorySearch.store.vector.enabled = true

用途:利用sqlite-vec扩展提供高效的余弦相似度检索。查询时使用vec_distance_cosine()函数计算距离。

降级策略:当sqlite-vec扩展不可用时,回退到内存中逐一计算余弦相似度(src/memory/manager-search.ts)。


2.6chunks_fts– 全文检索虚拟表(FTS5)

源码src/memory/memory-schema.ts:59-67

CREATEVIRTUALTABLEIFNOTEXISTSchunks_ftsUSINGfts5(text,id UNINDEXED,path UNINDEXED,source UNINDEXED,model UNINDEXED,start_line UNINDEXED,end_line UNINDEXED);
是否索引说明
text块的原文内容(参与全文索引)
id块 ID(仅存储,不参与检索)
path文件路径
source来源标识
modelembedding 模型
start_line起始行
end_line结束行

启用条件agents.*.memorySearch.query.hybrid.enabled = true

用途:使用 SQLite FTS5 的 BM25 排名算法进行关键词全文检索。只有text列参与倒排索引,其余列通过UNINDEXED标记为仅存储。


表关系图

┌──────────┐ ┌──────────────┐ ┌─────────────┐ │ meta │ │ files │ │embedding_cache│ │ (配置) │ │ (文件追踪) │ │ (缓存) │ └──────────┘ └──────┬───────┘ └─────────────┘ │ path ▼ ┌──────────────┐ │ chunks │ │ (核心文本块) │ └──┬───────┬───┘ │ id │ id ▼ ▼ ┌────────────┐ ┌────────────┐ │ chunks_vec │ │ chunks_fts │ │ (向量索引) │ │ (全文索引) │ └────────────┘ └────────────┘

三、混合检索机制

3.1 三种检索模式

模式触发条件算法源码位置
纯向量检索FTS 未启用vec_distance_cosine余弦相似度src/memory/manager-search.ts:20-94
纯关键词检索无 embedding providerFTS5 BM25 排名src/memory/manager-search.ts:136-191
混合检索两者都启用(默认)加权融合src/memory/manager.ts:259-367

3.2 混合检索流程

源码src/memory/manager.ts:259-367src/memory/hybrid.ts

用户查询 │ ├──────────────────────┐ │ │ ▼ ▼ 向量检索 关键词检索 (余弦相似度) (FTS5 BM25) │ │ │ top K×4 候选 │ top K×4 候选 │ │ └──────────┬───────────┘ │ ▼ 结果融合 (加权合并) │ ▼ 可选:时间衰减 │ ▼ 可选:MMR 多样性重排 │ ▼ 阈值过滤 + Top-K 截断 │ ▼ 返回最终结果

3.3 向量检索

源码src/memory/manager-search.ts:20-94

  1. 将用户查询通过 embedding 模型转为向量
  2. 使用sqlite-vecvec_distance_cosine()计算余弦距离
  3. 按距离升序排列,取 top-K 候选
-- sqlite-vec 向量检索 SQL(简化)SELECTid,vec_distance_cosine(embedding,?)ASdistanceFROMchunks_vecORDERBYdistanceASCLIMIT?

降级:无sqlite-vec时,从chunks表加载所有 embedding 到内存,逐一计算余弦相似度。

3.4 关键词检索

源码src/memory/manager-search.ts:136-191

  1. 对用户查询进行分词(Unicode 感知的正则分词)
  2. 构建 FTS5 查询表达式
  3. 使用 BM25 排名

查询构建src/memory/hybrid.ts:33-55):

原始查询: "如何配置 SQLite 向量检索" ↓ Unicode 分词: /[\p{L}\p{N}_]+/gu 分词结果: ["如何", "配置", "SQLite", "向量", "检索"] ↓ 引号包裹 + AND 连接 FTS5 查询: "如何" AND "配置" AND "SQLite" AND "向量" AND "检索"

3.5 BM25 分数转换

源码src/memory/hybrid.ts:33-55

FTS5 的 BM25 返回负数(越小越相关),需要转换到[0, 1]区间:

relevance = -rank // 取反,变为正数 score = relevance / (1 + relevance) // 映射到 [0, 1)

示例:

  • rank = -5.0relevance = 5.0score = 5/6 = 0.833
  • rank = -0.5relevance = 0.5score = 0.5/1.5 = 0.333
  • rank = -0.1relevance = 0.1score = 0.1/1.1 = 0.091

3.6 结果融合算法

源码src/memory/hybrid.ts:57-155

加权融合公式

finalScore = vectorWeight × vectorScore + textWeight × textScore

默认权重src/agents/memory-search.ts):

  • vectorWeight = 0.7(70% 语义相关性)
  • textWeight = 0.3(30% 词汇匹配度)
  • 自动归一化:vectorWeight / (vectorWeight + textWeight)

融合过程

  1. 构建联合集:按 chunk ID 合并两路结果
  2. 计算混合分数
    • 仅在向量结果中出现:score = vectorWeight × vectorScore
    • 仅在关键词结果中出现:score = textWeight × textScore
    • 两路都命中:score = vectorWeight × vectorScore + textWeight × textScore
  3. 按混合分数降序排列

3.7 可选后处理

时间衰减(Temporal Decay)

源码src/memory/temporal-decay.ts

对时效性敏感的记忆应用指数衰减:

decayedScore = score × exp(-λ × ageInDays) 其中 λ = ln(2) / halfLifeDays
  • 默认半衰期 30 天:30 天前的记忆分数衰减为原来的 50%
  • 日期来源:从记忆文件路径中解析(memory/YYYY-MM-DD.md
  • “常青” 文件(如MEMORY.md或无日期文件)跳过衰减

配置

temporalDecay: { enabled: false // 默认关闭 halfLifeDays: 30 // 半衰期天数 }
MMR 多样性重排(Maximal Marginal Relevance)

源码src/memory/mmr.ts

在保持相关性的同时增加结果多样性:

MMR(d) = λ × relevance(d) - (1-λ) × max_sim(d, selected) 其中 sim() 使用基于 token 的 Jaccard 相似度
  • λ = 0.7:偏向相关性(λ → 1退化为纯相关性排序)
  • 贪心选择:每轮选 MMR 得分最高的文档加入结果集

配置

mmr: { enabled: false // 默认关闭 lambda: 0.7 // 相关性 vs 多样性权衡 }

四、完整配置参考

源码src/agents/memory-search.ts:15-89

agents.defaults.memorySearch: { enabled: true // 是否启用记忆检索 provider: "auto" // embedding 提供商 // "openai"|"gemini"|"voyage"|"local"|"auto" model: "text-embedding-3-small" // embedding 模型(示例) store: { driver: "sqlite" path: "~/.openclaw/state/memory/{agentId}.sqlite" vector: { enabled: true // 启用 sqlite-vec extensionPath?: string // 自定义扩展路径 } } chunking: { tokens: 400 // 每块 token 数 overlap: 80 // 块间重叠 token 数 } query: { maxResults: 6 // 最终返回结果数 minScore: 0.35 // 最低分数阈值 [0, 1] hybrid: { enabled: true // 启用混合检索 vectorWeight: 0.7 // 向量检索权重 textWeight: 0.3 // 关键词检索权重 candidateMultiplier: 4 // 候选倍数(取 maxResults × 4 个候选) mmr: { enabled: false // MMR 多样性重排 lambda: 0.7 // 相关性 vs 多样性 [0, 1] } temporalDecay: { enabled: false // 时间衰减 halfLifeDays: 30 // 半衰期天数 } } } }

五、核心源码文件索引

文件功能
src/memory/memory-schema.ts表创建和 schema 初始化
src/memory/sqlite.tsNode.js 内置 SQLite 模块加载
src/memory/sqlite-vec.tssqlite-vec 扩展加载
src/memory/manager.ts检索编排(第 259-367 行)
src/memory/manager-search.ts向量和关键词检索函数
src/memory/manager-sync-ops.ts数据库初始化和向量表管理
src/memory/hybrid.ts混合结果融合和评分
src/memory/hybrid.test.ts混合检索算法测试
src/memory/mmr.tsMMR 多样性重排
src/memory/temporal-decay.ts时间衰减评分
src/memory/query-expansion.ts会话式查询的关键词提取
src/agents/memory-search.ts配置 schema 和默认值

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询