12 KiB
Knowledge Vector Migrator Notes (V2)
1. 文档目的
这份文档用于说明 V2 知识库向量迁移器的职责边界和核心规则。
它关注的是:
- V1
embedjs向量库的数据来源 - V2 目标向量存储的落点
- 向量迁移过程中的关键字段转换
- 文件安全、校验与跳过规则
这份文档只描述当前已经落地的迁移器行为,不展开到未来在线向量数据重建或最终 retrieval API 设计。
对应实现:
src/main/data/migration/v2/migrators/KnowledgeVectorMigrator.tssrc/main/data/migration/v2/migrators/README-KnowledgeVectorMigrator.md
2. 迁移器的职责
KnowledgeVectorMigrator 的职责不是迁移知识库业务主数据,而是:
- 读取 V1 每个 knowledge base 对应的 legacy
embedjs向量库 - 将旧的 chunk 向量数据转换为新的 better-sqlite3-backed
vectorstores布局 - 保证新向量数据能稳定关联回已经迁移完成的 V2
knowledge_base/knowledge_item
换句话说:
KnowledgeMigrator负责业务主数据KnowledgeVectorMigrator负责向量数据迁移
两者共同完成知识库的完整迁移,但 source of truth 仍然是 V2 业务表,不是向量库。
3. 数据来源
迁移器依赖四类输入:
3.1 已迁移的 knowledge base
来源:
- SQLite
knowledge_base表
作用:
- 提供 base 身份
- 提供 embedding
dimensions - 决定哪些 base 需要尝试迁移向量库
3.2 已迁移的 knowledge item
来源:
- SQLite
knowledge_item表
作用:
- 作为新的业务 item 身份来源
- 为 legacy loader identity 映射提供目标
itemId
3.3 Legacy loader metadata
来源:
- Redux
knowledge.bases[].items[]
作用:
- 从 V1
uniqueId/uniqueIds[]反查到已经迁移后的knowledge_item.id - 建立旧向量记录与新业务 item 的映射关系
3.4 Legacy vector database
来源:
${getDataPath()}/KnowledgeBase/<baseId>
作用:
- 读取 V1
embedjs的vectors表 - 提供原始 chunk 文本、source、vector
4. 目标存储
迁移目标不是继续保留旧 embedjs 格式,而是生成新的 vectorstores 兼容存储。
当前实现的目标结构是:
- 目标文件:迁移后 base 的 runtime 路径
{knowledgeBaseDir}/{migratedBaseId}/.cherry/index.sqlite(不再沿用原 legacy DB 路径) - 目标 schema:以
schema.ts的多表 index store 为准(meta/content/material/search_unit/search_text/embedding+ 外部内容 FTS5search_text_fts),由openBetterSqlite3IndexDriver→createKnowledgeIndexSchema建立。下方“1. 主表字段/2. 普通索引”是旧 embedjs/langchain 单表布局,已被上述多表 schema 取代。
迁移器会为目标存储补齐必要 schema:
- 主表字段
idexternal_idcollectiondocumentmetadataembeddings
- 普通索引
external_idcollection
- FTS 表和触发器
5. 核心转换规则
5.1 Loader identity 映射
V1 的向量记录使用 uniqueLoaderId 关联 loader。
V2 迁移时,不保留这个旧字段作为最终业务标识,而是把它映射成新的 knowledge_item.id,并写入:
external_id
映射规则:
- 优先使用 legacy item 的
uniqueIds[] - 如果不存在,再回退到 legacy item 的
uniqueId - 只有已经成功迁移到 V2
knowledge_item的 item 才能参与映射
这一步的核心目标是:让新向量记录稳定关联回 V2 的业务 item,而不是继续依赖旧 loader identity。
这里有一个重要约束:
- 只有能够映射到 V2
knowledge_item.id的 legacy 向量记录,才属于有效可迁移数据 - 无法映射到
knowledge_item.id的 legacy 向量,即使仍存在于旧embedjsDB 中,也视为无效残留数据 - 因此迁移器的目标不是“尽量保留旧向量文件中的所有内容”,而是“只保留能被当前 V2 业务表证明合法归属的向量数据”
目录(directory)的特殊处理:V1 把目录下每个文件都登记在该目录 item 的 loader id 上,没有 per-file item。迁移时不再把这些容器级向量直接丢弃,而是为每个嵌入文件合成一个 file 子项(一个 loader id 对应一个子项,见 KnowledgeMigrator.expandLegacyDirectoryItem),把目录向量重新归属(re-attribute)到这些子项上,目录因此保持可检索且无需重新 embedding。只有在 fallback 情况下——legacy 向量源不可读,或某个嵌入文件没有可迁移向量——才会跳过容器级向量,并把目录保留为 directory_not_migrated 失败墓碑。
5.2 Chunk 内容映射
旧向量记录中的内容字段会转换为:
pageContent->documentknowledge_item.id->metadata.itemId和external_idknowledge_item.type->metadata.itemTypesource->metadata.source- chunk 顺序 ->
metadata.chunkIndex - chunk 文本 token 估算 ->
metadata.tokenCount
当前实现不会保留所有旧 metadata,只保留迁移和检索必需的最小信息。
迁移后的 metadata 必须满足 runtime KnowledgeChunkMetadataSchema:
itemId、itemType、source、chunkIndex、tokenCount 都是必填字段。
无法补出合法 source 的 legacy row 会被跳过,而不是写入不完整 metadata。
5.3 Embedding 复用
迁移器不会重新做 embedding。
它会直接复用 V1 已存在的向量:
- 从 legacy
vector字段读取原始 little-endian float32 BLOB 字节 - 反序列化为
number[] - 再写入新表的
embeddings
这意味着:
- 迁移成本更低
- 不依赖在线模型调用
- 迁移阶段不会触发重新切块或重新嵌入
5.4 Chunk identity 重建
旧 chunk row 的 id 不会直接复用。
每一条迁移后的向量记录都会生成新的 UUID v4 id。
因此迁移的稳定关联语义不是依赖旧 chunk id,而是依赖:
baseIdexternal_id=knowledge_item.id- chunk 文本与 source 对应的向量记录
6. 文件安全约束
当前迁移器采用“临时文件重建 + 目标路径原子替换 + v1 源原地不动”的策略。
规则如下:
- 先在目标路径的同级写一个临时文件
{targetDbPath}.vectorstore.tmp
- v1 legacy
embedjsDB({knowledgeBaseDir}/{legacyBaseId})在整个迁移过程中不被移动也不被删除- 因为迁移后 base 使用全新 uuid,V2 store 落在
{migratedBaseId}/.cherry/index.sqlite,与 legacy flat path 不同名、不冲突,源文件无需腾挪
- 因为迁移后 base 使用全新 uuid,V2 store 落在
- 临时文件写完整并校验成功后,先删除目标路径上可能已存在的空 store(runtime 可能预先创建过),再把临时文件原子 rename 到目标路径
- 这一步的 unlink 在
EBUSY时会重试(recursive+maxRetries+retryDelay),以兼容 Windows 上的瞬时文件锁
- 这一步的 unlink 在
- 如果当前 base 在替换前失败,已写入的临时文件会被清理;v1 legacy DB 始终原样保留
这意味着:
- 迁移过程任何阶段都不会破坏 v1 原始 legacy DB —— 迁移失败、放弃或成功后回退 v1,知识库都可正常使用
- retry 天然幂等:legacy 源一直在原路径,retry 直接通过
KnowledgeVectorSourceReader重新读取原始 legacy DB
IMPORTANT: 当前已接受的局限
以下行为是当前实现明确接受的限制,不应误读为“未来理想方案”:
- base 级执行失败属于迁移失败,不属于可跳过数据
- 如果某个 base 在重建临时库、写入目标表或替换正式文件时失败,
execute()会直接返回success: false - 这类失败不会被计入
skippedCount,也不应只记 warning 后继续成功
- 如果某个 base 在重建临时库、写入目标表或替换正式文件时失败,
- 迁移成功后 v1 legacy 向量库会作为孤儿文件留在磁盘上
- 因为迁移不再移动或删除 v1 源(连同已复制的 v1 上传文件),知识库磁盘占用大致翻倍
- 这是“保证 v1 可回退”的预期代价;如需在用户确认放弃 v1 后回收磁盘,需要单独的 cleanup 策略、实现和测试
- 用户迁移前的完整备份仍然必要
- 迁移器只保证单个 knowledge base 的 v1 向量 DB 原地完好,不等于完整 V1 备份
- 完整迁移失败后的全局恢复 source of truth 仍然是迁移前备份
7. 校验规则
当前实现会做至少以下校验:
- 每个 base 的目标行数必须与 prepared row 数一致
- 每条迁移后的记录都必须有非空
external_id - 每条迁移后的记录都必须有
metadata.itemId,并与external_id保持一致
如果不满足这些条件,应视为当前 base 迁移失败。
8. 跳过规则
以下情况会被跳过,而不是强行写入:
knowledge_base中不存在对应 base- legacy DB 文件不存在
- legacy DB 路径实际是目录
- legacy DB 不包含
vectors表 uniqueLoaderId无法映射回已迁移的knowledge_item.id- 向量记录缺少
vector或vector为空
这些跳过通常会记录 warning,而不是让整个迁移流程全部中断。
补充说明:
- 如果某个 base 的 legacy 向量记录最终全部被跳过,则该 base 在 V2 中会被重建为空的 vector store
- 这不是“回滚保留旧 DB”的场景,而是预期的数据清洗结果
- 原因是这些被跳过的记录无法稳定关联到当前 V2
knowledge_item,因此不再被视为有效业务向量数据
9. 当前边界与限制
当前迁移器只负责“向量数据重建”,不负责:
- 重新切块
- 重新 embedding
- 重新生成业务 item
- 校正旧知识库的业务配置
- 设计最终 retrieval service 的 API
因此它的定位应该是:
- 一次性的迁移工具
- 不等同于运行时知识库索引服务
10. 对后续实现的影响
基于当前迁移器行为,后续 V2 运行时设计需要遵守以下前提:
- V2 业务真相仍然来自
knowledge_base/knowledge_item - 新向量记录必须能通过
external_id稳定关联到knowledge_item.id - 运行时不应继续依赖 V1
embedjs的uniqueLoaderId - 如果未来需要重建索引,应按 V2 业务表重新生成,而不是继续依赖旧迁移逻辑
11. 与其他文档的关系
knowledge-backend-decisions.md- 定义当前
KnowledgeRuntimeService、data services、queue 和 runtime/vector 边界
- 定义当前
knowledge-schema.md- 定义 V2 业务 schema
- 本文档
- 专门说明向量迁移器如何把旧向量数据接到新的 V2 业务模型上
三者的关系可以简化为:
- schema 定义业务结构
- backend decisions 文档定义当前运行时边界
- vector migrator 文档定义旧向量数据如何迁移进新体系
12. 与当前 Runtime 的衔接
当前 runtime 向量侧实现位于:
src/main/services/knowledge/runtime/KnowledgeRuntimeService.tssrc/main/services/knowledge/vectorstore/KnowledgeVectorStoreService.tssrc/main/features/knowledge/vectorstore/indexStore/BetterSqlite3VectorIndex.ts
这意味着迁移后的向量数据并不是孤立的一次性产物,而是会被当前 runtime 直接按 knowledge base 打开和查询。
当前已确认的衔接点是:
- runtime 通过
KnowledgeVectorStoreService按base.id获取 store - 实际 store provider 是
BetterSqlite3VectorIndex - runtime 检索和写入都基于 better-sqlite3 vector store
因此,迁移器与 runtime 的共同前提是:
- V2 业务真相来自
knowledge_base/knowledge_item - 运行时向量文件与迁移后的向量文件都属于同一类 better-sqlite3-backed vector store 体系
- 运行时关联业务 item 仍应以
knowledge_item.id为稳定标识,而不是继续依赖 V1 loader identity