跳转到内容

向量记忆系统

向量记忆系统

学习目标

  • 理解 Cherry Studio 如何通过 SQLite + LibSQL 实现本地持久化记忆存储
  • 掌握主进程 MemoryService 与渲染进程的委托模式(IPC 通信)
  • 了解嵌入向量生成、语义相似度搜索与用户隔离的具体实现
  • 能够基于现有架构设计自己的记忆管理方案

前置知识

本章专注于 Cherry Studio 的具体工程实现,以下内容已在通用知识教程中详细讲解,不再重复:

项目实践

整体架构:双进程委托模式

Cherry Studio 基于 Electron 构建,记忆系统采用经典的主进程承载 + 渲染进程委托架构。渲染进程不直接操作数据库,而是通过 IPC(进程间通信)将记忆操作委托给主进程。

为什么选择委托模式?

  • SQLite 数据库文件位于主进程可访问的路径(DATA_PATH/Memory/memories.db),渲染进程无权直接读写
  • 向量嵌入生成需要调用外部 API(OpenAI 兼容接口),密钥应保存在主进程
  • IPC 通道天然提供了一层隔离,渲染进程的代码注入不会影响数据库操作

核心模块与文件布局

文件路径职责大小
src/main/services/memory/MemoryService.ts主进程记忆服务,协调数据库操作与向量嵌入26KB+
src/main/services/memory/queries.tsMemoryQueries 数据库操作层,封装所有 SQL 语句核心依赖
src/renderer/src/services/MemoryService.ts渲染进程代理,通过 IPC 转发请求8KB+
src/renderer/src/store/memory.tsRedux memory slice,存储记忆配置状态管理
src/main/knowledge/embedjs/embeddings/Embeddings.ts嵌入向量生成模块向量计算

数据库初始化与路径迁移

Cherry Studio 使用 @libsql/client(v0.14.0)作为 SQLite 驱动。LibSQL 是 SQLite 的一个分支,在保持兼容性的同时扩展了远程同步能力。

关键细节:

  • 数据库路径:DATA_PATH/Memory/memories.db,而非旧版的 userData/memories.db
  • migrateMemoryDb 方法负责检测旧路径数据库并迁移到新位置,保证升级平滑过渡
  • 建表逻辑在 MemoryQueries 中完成,使用 CREATE TABLE IF NOT EXISTS 确保幂等

嵌入向量配置

Cherry Studio 的记忆系统不绑定特定的嵌入模型提供商,而是通过可配置的方式支持多种后端。

统一维度约束:

  • UNIFIED_DIMENSION = 1536 — 系统内部统一使用的嵌入向量维度
  • 所有存储到数据库的向量必须为 1536 维,不同模型的输出在此维度上对齐
  • 如果配置的模型输出维度不为 1536,需要进行截断或填充处理

记忆类型定义

系统定义了三种核心类型:

类型用途关键字段
MemoryItem单条记忆记录id, content, embedding, userId, agentId, tags, timestamp
MemoryHistoryItem记忆操作审计日志id, action, memoryId, userId, timestamp
MemoryConfig用户记忆系统配置userId, agentId, enabled, embeddingModel, similarityThreshold

记忆操作流程

相似度阈值: SIMILARITY_THRESHOLD = 0.85

  • 语义搜索时,只有余弦相似度 >= 0.85 的结果才会被返回
  • 这个阈值是一个经验值,在保证召回精度的同时避免返回过多弱相关结果

用户与 Agent 隔离

Cherry Studio 支持多用户和多 Agent 场景,记忆系统通过 userIdagentId 实现数据隔离。

隔离层级:

  1. userId 级别:不同用户的记忆完全隔离,互不可见
  2. agentId 级别:同一用户下不同 Agent(如”翻译助手”、“代码审查”)拥有各自独立的记忆池
  3. 数据库查询层面强制附加过滤条件,不存在绕过隔离的路径

渲染进程操作流程

渲染进程的 MemoryService 是一个薄代理层,负责将 UI 层的调用转换为 IPC 消息。

配置读取流程:

  • 渲染进程通过 selectMemoryConfig 从 Redux store 获取当前记忆配置
  • 配置包含模型名称、提供商、API 密钥等敏感信息
  • 这些配置会随 IPC 请求传递给主进程,由主进程实际调用嵌入 API

知识库参数转换

getKnowledgeBaseParams 方法负责将 MemoryConfig 转换为知识库模块可用的参数格式,打通记忆系统与知识库之间的数据流。这使得记忆中的内容可以被知识库检索模块复用,实现记忆与知识的联动。

问题与规避

1. 数据库路径迁移风险

旧版本数据库位于 userData/memories.db,新版改为 DATA_PATH/Memory/memories.db。迁移过程中的风险:

  • 文件冲突:如果新路径下已存在同名文件,迁移可能覆盖数据
  • 权限问题:新路径的目录权限可能与旧路径不同

规避策略migrateMemoryDb 应在启动早期执行,且仅在旧路径存在、新路径不存在时触发迁移。

2. 嵌入向量维度不一致

系统规定 UNIFIED_DIMENSION = 1536,但不同嵌入模型的输出维度可能不同:

  • OpenAI text-embedding-3-small:默认 1536 维(可通过 dimensions 参数调整)
  • 其他提供商的模型可能输出 768、1024 等不同维度

规避策略

  • 配置层提供 dimensions 字段,确保与模型实际输出匹配
  • 存储前进行维度校验,不一致时截断或零填充至 1536 维
  • MemoryConfig 中记录使用的模型与维度,便于追溯

3. 相似度阈值调优

SIMILARITY_THRESHOLD = 0.85 是固定阈值,但不同场景可能需要不同的精度:

  • 高精确场景(如代码补全记忆):阈值应更高(0.90+)
  • 高召回场景(如知识推荐):阈值可适当降低(0.75+)

规避策略MemoryConfig 中的 similarityThreshold 字段允许用户自定义阈值,而非硬编码。

4. 大批量写入性能

嵌入向量生成通常是网络 I/O 操作,批量写入时如果逐条处理会导致性能瓶颈。

规避策略

  • MemoryConfig 提供 batchSize 字段控制批量大小
  • Embeddings 模块支持批量生成嵌入向量,减少 API 调用次数
  • 主进程可采用异步队列缓冲写入请求,避免阻塞 UI

5. IPC 通信序列化开销

渲染进程与主进程之间的 IPC 通信需要序列化/反序列化,传输大体积嵌入向量(1536 维 float 数组)时可能成为瓶颈。

规避策略

  • 嵌入向量生成在主进程完成,渲染进程只需传递文本内容,不传输向量
  • 搜索结果返回时只传递记忆内容和相似度分数,不返回完整向量
  • 对于批量操作,合并为单次 IPC 调用而非多次独立调用

设计取舍

为什么选择 SQLite 而非专用向量数据库?

Cherry Studio 定位为本地优先的桌面应用,选择 SQLite 的核心理由:

  • 零运维:无需额外部署向量数据库服务,安装即用
  • 离线可用:记忆数据完全存储在本地,不依赖网络
  • 轻量级@libsql/client 的打包体积小,不影响应用分发体积
  • 足够的性能:对于桌面应用的记忆数据量(通常 < 10 万条),SQLite 的向量搜索性能完全够用

取舍代价

  • 不支持分布式记忆共享
  • 向量搜索算法需要自行实现(余弦相似度计算),无法使用 HNSW 等高级索引
  • 大规模数据量下搜索性能不如专用向量数据库

委托模式 vs 渲染进程直连

选择委托模式的核心理由:

  • 安全性:API 密钥和嵌入配置由主进程保管,不会泄露到渲染进程
  • 一致性:所有记忆操作集中到主进程,避免多渲染进程并发写入导致的数据竞争
  • 可维护性:数据库操作逻辑集中在一处,升级数据库 schema 时只需修改主进程代码

嵌入模型可配置 vs 内置固定模型

Cherry Studio 选择了可配置方案,通过 Redux store 管理 MemoryConfig,允许用户自行选择嵌入模型提供商。这是对灵活性的取舍,代价是需要在维度对齐和配置管理上投入更多工程精力。

参考来源