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.1 meta – 元数据表

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

CREATE TABLE IF NOT EXISTS meta (
  key   TEXT PRIMARY KEY,
  value TEXT NOT NULL
);
类型说明
keyTEXT PK元数据键名
valueTEXT值(如 embedding 模型名、provider、chunk 配置等)

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


2.2 files – 已索引文件追踪表

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

CREATE TABLE IF NOT EXISTS files (
  path   TEXT PRIMARY KEY,
  source TEXT NOT NULL DEFAULT 'memory',
  hash   TEXT NOT NULL,
  mtime  INTEGER NOT NULL,
  size   INTEGER NOT NULL
);
类型说明
pathTEXT PK文件路径
sourceTEXT来源标识(默认 'memory'
hashTEXT文件内容哈希
mtimeINTEGER文件修改时间(Unix 时间戳)
sizeINTEGER文件大小(字节)

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


2.3 chunks – 核心文本块表

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

CREATE TABLE IF NOT EXISTS chunks (
  id         TEXT PRIMARY KEY,
  path       TEXT NOT NULL,
  source     TEXT NOT NULL DEFAULT 'memory',
  start_line INTEGER NOT NULL,
  end_line   INTEGER NOT NULL,
  hash       TEXT NOT NULL,
  model      TEXT NOT NULL,
  text       TEXT NOT NULL,
  embedding  TEXT NOT NULL,
  updated_at INTEGER NOT NULL
);
类型说明
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.4 embedding_cache – embedding 缓存表

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

CREATE TABLE IF NOT EXISTS embedding_cache (
  provider     TEXT NOT NULL,
  model        TEXT NOT NULL,
  provider_key TEXT NOT NULL,
  hash         TEXT NOT NULL,
  embedding    TEXT NOT NULL,
  dims         INTEGER,
  updated_at   INTEGER NOT NULL,
  PRIMARY KEY (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.5 chunks_vec – 向量虚拟表(sqlite-vec)

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

CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec USING vec0(
  id        TEXT PRIMARY KEY,
  embedding FLOAT[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.6 chunks_fts – 全文检索虚拟表(FTS5)

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

CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
  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(简化)
SELECT id, vec_distance_cosine(embedding, ?) AS distance
FROM chunks_vec
ORDER BY distance ASC
LIMIT ?

降级:无 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 和默认值
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐