Cherry Studio 知识库 V2 迁移深度解析:KnowledgeVectorMigrator 向量存储迁移全流程
2026/9/12 18:18:43 网站建设 项目流程

Cherry Studio 知识库 V2 迁移深度解析:KnowledgeVectorMigrator 向量存储迁移全流程

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

导读

KnowledgeVectorMigrator是 Cherry Studio V2 数据迁移流水线中专用于知识库向量数据的迁移器:它将 v1 时代按知识库(base)独立的embedjs向量数据库,重建为 v2 时代每个知识库一套 7 表结构的index.sqlite索引存储(KnowledgeIndexStore)。本文以迁移器文档与 KnowledgeVectorMigrator.ts 源码为准,完整讲解其数据源、目标存储、六大核心转换、内存与文件安全契约、校验规则与跳过语义,帮助你理解一次"不重新向量化"的向量库迁移是如何在字节级、路径级和内存级三个维度上保证与运行时一致的。读完本文,你将掌握 Cherry Studio 知识库 V2 迁移的设计取舍、目录展开向量重归属机制、以及迁移器如何防御 Windows 文件锁与 V8 堆内存溢出。

迁移器在 V2 迁移流水线中的位置

KnowledgeVectorMigrator注册于 migratorRegistry.ts:

new KnowledgeMigrator(), new KnowledgeVectorMigrator(), new ChatMigrator(),

其类定义声明:

readonly id = 'knowledge_vector' readonly name = 'KnowledgeVector' readonly description = 'Rebuild legacy knowledge vectors into the per-base index.sqlite store' readonly order = 3.5

几个关键位置语义:

  • order = 3.5:它严格排在KnowledgeMigrator(负责把 Redux + Dexie 导出迁移进knowledge_base/knowledge_item表)之后执行,因为向量迁移必须依赖前者产出的已迁移库与条目行。
  • 两阶段模型:与流水线中所有迁移器一致,它实现prepare()execute()两个阶段——prepare()只读、只规划、只统计;execute()才真正重建存储并落库。
  • 共享数据(ctx.sharedData:它通过三个共享键从KnowledgeMigrator接手映射关系:KNOWLEDGE_BASE_ID_REMAP_SHARED_DATA_KEY(legacy base id → 迁移后 base id)、KNOWLEDGE_ITEM_ID_REMAP_SHARED_DATA_KEY(legacy item id → 迁移后 item id)、KNOWLEDGE_DIRECTORY_CHILD_LOADER_REMAP_SHARED_DATA_KEY(迁移后 base id → loader id → 目录展开子条目 id)。

注意:KnowledgeMigrator在 order 1.8 只对 legacy 向量库做count(*)加一次length(vector)探测,而KnowledgeVectorMigrator在 order 3.5 要逐行读取pageContent/uniqueLoaderId/vector并重解 base 重映射——因此"探测通过、真正读取时失败"(如损坏页、瞬时文件锁、重映射断裂)只会发生在 3.5,源码markBaseUnmigrated的注释明确说明了这一可达路径。

数据源与目标存储

数据源

数据来源文件/路径
迁移后的知识库身份与维度SQLiteknowledge_baseknowledge_base
迁移后的知识条目身份SQLiteknowledge_itemknowledge_item
Legacy loader 元数据Reduxknowledge.bases[].items[]ReduxStateReader.getCategory('knowledge')
Legacy 分块向量每库独立的 legacy 向量库ctx.sources.knowledgeVectorSource.openBase(base.id)(流式)

源码中,Redux 状态读取为:

const knowledgeState = ctx.sources.reduxState.getCategory<LegacyKnowledgeStateWithLoaders>('knowledge') const migratedBases = await ctx.db.select().from(knowledgeBaseTable)

路径安全要求:source reader 由MigrationContextctx.paths.knowledgeBaseDir初始化,必须读取迁移解析出的 v1 userData 路径,而非 v2 路径注册表或app.getPath()KnowledgeVectorMigrator自身也应持续使用 reader 抽象,而不是内联拼接向量库路径。这与KnowledgeMigrator文档中的 Path Safety 约束一致(README-KnowledgeMigrator.md)。

目标存储

  • 目标位置为迁移后 base 的运行时路径:{knowledgeBaseDir}/{migratedBaseId}/.cherry/index.sqlite,即每库一套 7 表索引存储。
  • 构建路径是createKnowledgeIndexStoreAtPath——与运行时KnowledgeVectorStoreService打开存储所用的同一个工厂(driver → schema →ensureIndexMetaKnowledgeIndexStore),随后按条目调用KnowledgeIndexStore.rebuildMaterial。因此迁移产物与运行时自己构建的存储逐字节一致:每个迁移条目对应一个material,其 legacy 分块成为该 material 的search_unit

源码中的布局常量与注释明确了运行时路径的唯一事实来源:

// Runtime vector store + material layout — source of truth: // src/main/features/knowledge/pathStorage.ts // (CHERRY_META_DIR / VECTOR_STORE_FILE / MATERIAL_ROOT_DIR). Runtime opens // {knowledgeBaseDir}/{baseId}/.cherry/index.sqlite by the migrated (new) base id... const KNOWLEDGE_META_DIR = '.cherry' const KNOWLEDGE_VECTOR_STORE_FILE = 'index.sqlite' const KNOWLEDGE_MATERIAL_ROOT_DIR = 'raw'

测试文件 KnowledgeVectorMigrator.test.ts 专门用runtimeVectorStorePath(baseId)runtimeMaterialPath(baseId, relativePath)两个镜像函数做读回断言——"如果迁移器写到了运行时不会打开的位置,测试就会失败",这正是该回归测试要防御的 bug。

六大核心转换

1. Loader 身份重映射

  • 无嵌入模型的可恢复失败库在 base 级被跳过:它们保留 SQLite 中的 base/items 行,用户选择新模型后必须重建。
  • uniqueLoaderId不作为持久化字段保留,而是被解析回迁移后的knowledge_item(即material_id)。
  • uniqueIds[]优先级高于 legacy 单值uniqueId(源码buildLoaderTargetMap中先遍历uniqueIds,非空即用之)。
  • 一条 legacy 向量行只有在能映射到已存在的 V2knowledge_item.id时才有效;未映射的 legacy 行被视为无效索引残留而非必须保留的业务数据(源码classifyVectorRow中的unmapped_loader分支明确注释了这一点)。

2. 可索引条目过滤

  • 只有映射到可索引 V2 条目类型的向量才会迁移,可索引类型为fileurlnote(源码常量INDEXABLE_KNOWLEDGE_ITEM_TYPES = new Set<KnowledgeItemType>(['file', 'url', 'note']))。
  • 被 v1 索引的directory的容器级向量,正常路径下会被重归属到按每个嵌入 loader id 合成的 per-file 子条目(一个file子条目对应一个嵌入 loader id,见KnowledgeMigrator.expandLegacyDirectoryItem),从而文件夹无需重新向量化即可保持可搜索。
  • 容器级向量仅作为兜底被跳过并告警——即 legacy 向量源不可读,或某嵌入文件没有可迁移向量时——此时文件夹被保留为迁移失败墓碑(directory_not_migrated)。
  • 该过滤不会knowledge_item删除directory行,只是阻止容器级向量写入 V2 存储。
  • 目录子条目的合成逻辑来自collectDirectoryGroups:每个子条目携带groupId = 容器id,而迁移只在目录展开子条目上设置非空groupId,因此按行分组可捕获 base 的全部子条目。源码特意强调不能从 loader 重映射的值来推导分组,因为两个重叠/重复的 v1 文件夹共享同一文件的 loader id 时,KnowledgeMigrator的 last-write-wins 重映射只保留后一个子条目,按行扫描才能把未分到向量的子条目也正确降级。
  • 独立条目与目录的 loader 冲突collectStandaloneLoaderOwners防止目录重归属"偷走"独立条目的向量——md5(path)会让独立添加的文件与文件夹内的文件碰撞同一个 loader id;解析时独立条目保持向量所有权,并记录directory_child_loader_conflict告警。

3. Material 组装(Route A——保留 v1 切分)

  • 每个迁移条目一个material;其relative_path通过共享的toMaterialRelativePath助手从迁移后的knowledge_item推导,与运行时索引任务完全一致——文件使用其存储的relativePath(有处理产物时用处理产物路径)。
  • 迁移的 url 则改为固定指向为它在raw/下物化的快照文件(由content.text加 OKF frontmatter 印章生成),替换助手原本会返回的 item-id 虚拟路径。
  • 存储填充其余行(current_content_hash、时间戳);没有origin/index_policy/file_ext列,content不携带text_format
  • 条目的 legacy 分块正文按 legacy 读取顺序拼接为单一规范content.text,以文档分隔符(\n\n,源码常量DOCUMENT_SEPARATOR)连接;每个分块成为一个search_unit,其[char_start, char_end)切片精确切回自身正文,并附带 body 的search_text行;unit_index为条目的读取顺序。
function buildMigratedUnits(pageContents: string[]): RebuildMaterialInput['units'] { const units: RebuildMaterialInput['units'] = [] let cursor = 0 pageContents.forEach((pageContent, index) => { if (index > 0) cursor += DOCUMENT_SEPARATOR.length const charStart = cursor const charEnd = cursor + pageContent.length cursor = charEnd units.push({ unitType: 'chunk', unitIndex: index, charStart, charEnd }) }) return units }
  • 这是合成拼接而非重新切分:第一次真正的 reindex 会用运行时切分器重新切分并收敛。迁移按库记录日志。

4. 嵌入复用(零重新向量化)

  • legacyvector载荷从F32_BLOB解码为Float32Array(端到端保持,驻留大小仅为number[]的一半),通过encodeVectorBlob(raw little-endian float32)写入embedding表,以正文的embedding_text_hash为键。字节与运行时编码完全一致,因此无需重新向量化,存储可跨引擎移植。
  • 相同分块正文(库内或跨库)折叠为一行embeddinghashEmbeddingText去重 + INSERT OR IGNORE)。
  • 不支持的向量编码在unsupported_vector_encoding下跳过,与真正缺失载荷分开统计。
  • 向量长度与库记录dimensions不一致的在dimension_mismatch下跳过,而不是破坏整个库的暴力余弦扫描。

5. 身份再生成

  • legacy 分块行 id 不重用;unit_id/content_hash/search_text_id由存储根据 material id、内容与偏移确定性推导。

6. 身份戳

  • ensureIndexMeta写入单一meta身份行(schema 版本 + base id),使运行时打开存储时无需重新引导,并在base_id不匹配时拒绝被换入/外来的index.sqlite
  • 构建契约快照(嵌入模型、维度、切分器配置哈希)有意不存储——模型/维度变更会创建新库,切分器变更会重建派生索引。

内存契约:最多驻留"一个条目的文本 + 一批向量"

prepare() 阶段

prepare()将每个库的 legacy 行流式扫描一次openBase().reader.iterateRows()),只保留:

  • 每个条目的 rowid 列表;
  • 计数(expectedUnitCountexpectedEmbeddingCountsourceRowCount);
  • 按原因聚合的跳过统计(采样封顶,绝不每个被拒行一条消息);
  • 每个 url/note 条目的预留快照路径(通过向量无关的文本投影 point-read 该条目自身行推导)。

PreparedBasePlan接口刻意不持有任何向量或分块文本。它保留的唯一按分块结构是 rowid 列表,且该列表确实随迁移总块数线性增长:每个分块一个 JS number(packed SMI 数组)——100 万分块时约 10.5 B/分块,1000 万时约 8.0 B/分块(node --expose-gc下围绕Map<string, number[]>构建的堆增量实测)。

文档给出的典型语料估算(注意这是估算而非代码强制上限):

  • 迁移器只要求dimensions为正整数,不约束pageContent长度或库的分块数;
  • 普通语料下,一个分块在 legacy 库中占用 ≥5 KB(1024 维 4 KB float32 向量 + ~1 KB 文本);
  • 因此计划开销约为 legacy 文件磁盘大小的 1/500:100 MB 计划意味着 ~50 GB 的 v1 向量库;
  • 耗尽 V8 的 4 GB old-space 需要约 4 亿分块(约 2 TB 源数据);
  • 观测到的最重语料约 7.5 万分块 ≈ 0.7 MB 计划。

低维度、极短分块的语料会改变这一比例。保留该计划是可接受的权衡——它换来的是对 legacy 库按条目重读(而非第二次全量扫描)——其合理性来自 v1 "一次嵌入调用一个分块"的索引方式从未产生过接近该量级的数据,而非存在代码检查的上限。

execute() 阶段

execute()一次只重读一个条目:

  • 其文本整体通过向量无关的列投影读取(loadTextRowsByRowids——内容 schema 每个 material 一行文本,因此拼接文本在 schema 不变的前提下不可再压缩);
  • 其向量以固定≤500 rowid的 point-read 批次读取(loadRowsByRowids),由rebuildMaterialIterable嵌入输入在其写事务内惰性拉取
const VECTOR_STREAM_BATCH_SIZE = 500 // 与 reader 的 IN 子句批次 ROWID_BATCH_SIZE 对齐
function* iterateLegacyEmbeddingBatches(...): Generator<RebuildMaterialEmbeddingInput> { for (let offset = 0; offset < rowids.length; offset += VECTOR_STREAM_BATCH_SIZE) { const batch = rowids.slice(offset, offset + VECTOR_STREAM_BATCH_SIZE) const rows = reader.loadRowsByRowids(batch) if (rows.length !== batch.length) { throw new Error(...) } // fail-closed ... } }
  • url/note 快照文件在该条目的回合内写入,绝不跨库缓冲;
  • 解码后的向量端到端保持Float32Array(驻留为number[]的一半)。

OOM 历史(源码注释完整记录):最初在 prepare→execute 之间保留每个库的所有 material,峰值达到所有库向量之和(28 库语料耗尽 V8 堆);后续的"按库重读"仍一次性加载整个库——单个大库(六位数分块 × 高维度)就能独自耗尽,且因迁移每次启动都从头重跑而循环崩溃。OOM 回归防护测试(KnowledgeVectorMigrator.test.ts)钉死了PreparedBasePlan的精确键集合与每个阶段的读取形态(prepare 流式;execute 经投影读文本、向量按 ≤500 rowid 分批——一个 501 分块的条目必须以 500 + 1 到达)。

此外,流式扫描每 1024 行(STREAM_ROW_YIELD_INTERVAL)主动让出事件循环一次,避免六位数行的库在整个扫描期间冻结迁移 UI。

文件安全契约

原地构建,不做 rename

迁移器将每个重建的存储直接构建在运行时路径{migratedBaseId}/.cherry/index.sqlite——无临时文件、无 rename。文档解释了 rename 曾是迁移在 Windows 上最脆弱的环节:

  • WAL 模式下打开的 SQLite 存储可能使文件在close()之后仍被锁住(wal_checkpoint(TRUNCATE)PERSIST_WAL与数秒等待都不可靠地释放它);
  • 叠加 AV/搜索索引器扫描刚写入的文件(无DELETEshare);
  • MoveFileEx需要源文件的DELETE权限,于是 rename 抛出EBUSY/EPERM,库丢失存储。重试只能等掉瞬态 AV 扫描,等不掉永不释放的句柄。

原地构建完全移除了移动操作——close()后残留的任何锁都无害,因为没有东西移动或重开该文件;运行时只在 bootstrap 之后很久才打开它。

源码中对文件系统瞬态锁做了多层防御:

const REMOVE_RETRY_OPTIONS = { recursive: true, force: true, maxRetries: 5, retryDelay: 100 } as const const TRANSIENT_FS_LOCK_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']) const FS_RETRY_MAX_ATTEMPTS = 8 const FS_RETRY_BASE_DELAY_MS = 100 const FS_RETRY_MAX_DELAY_MS = 1500

retryOnTransientFsLock以指数退避(100ms → 最大 1500ms)保护raw/快照writeFilemkdirfs.rm的内置重试不覆盖写/建目录,且其 errno 集合显著遗漏了EACCES)。

崩溃原子性的取舍

原地构建以 rename 的崩溃原子性(中断的构建会在运行时路径留下部分索引)换取上述鲁棒性。这是安全的,因为:

  • 迁移闸门对任何未完成的运行都从头重跑(verifyAndClearNewTables()清行、KnowledgeMigrator重新铸新 uuid 目录、运行时绝不在迁移中途打开存储);
  • 每库 catch 会在捕获失败时擦除部分产物;
  • 崩溃遗留的目录永远不会被knowledge_base行引用,因此永远不会被挂载(与 rename 路径产出的死磁盘一致);
  • 目标处的index.sqlite{-wal,-shm}家族在(重新)构建前被移除(带可存活 EBUSY 的重试),WAL 通过PRAGMA wal_checkpoint(TRUNCATE)折回主文件,运行时打开的是一个自包含的存储。

全有或全无发布 + 快照 pin

一个库全有或全无地发布,将其 url/note 行固定到快照是发布的最后一步——只有该事务提交后存储才算提升。此前任何抛出(构建、关闭或 pin 事务)都会擦除索引并将库标记为可恢复的failed

  • 零 pin 不得记为成功:一个completed的 url/note 条目没有relativePathderiveConceptId守卫的不变量违例,且没有任何机制修复它——index-documents跳过completed条目,所以 ensure-snapshot 永远不会重新捕获;已完成的迁移永远不会重跑。
  • 标记为failed的库转而走恢复流程:把条目重新加入一个新库(其行不是completed,因此确实会被索引)。
  • 该恢复只有一种情况有损:url 条目会重新抓取实时页面,而不是重读本迁移器写入但从未 pin 的raw/快照——因此已经失效的链接不可恢复。

legacy 源永不移动或删除

v1 的 legacy embedjs 库({knowledgeBaseDir}/{legacyBaseId}绝不被移动或删除。每个迁移库获得新 uuid,重建的 V2 存储位于不同路径({migratedBaseId}/.cherry/index.sqlite),永不与 legacy 扁平路径冲突——legacy 源无需搬迁。用户在失败、放弃甚至成功的迁移后回滚到 v1,知识库依然可用。

重试天然幂等

legacy 源仍在原处,重试直接通过KnowledgeVectorSourceReader重读原始 legacy 库。

当前限制

单库失败非致命

单个库的失败不会中止整个迁移。当一个库的向量存储无法重建——无论prepare()完全无法读取/映射其 legacy 源,还是execute()在重建或发布中途失败——该库被跳过、标记为可恢复的failed/missing_vector_store行(UI 据此显示 re-index 入口),失败以告警呈现,execute()仍返回success: true,其余库继续迁移。

  • 这防止单个库的迁移错误把用户挡在应用之外:失败库在 UI 内恢复,而不是重跑整个迁移。
  • 整个迁移只在结构性/完整性错误(迁移器抛出,或validate()的对账失败)时整体失败,绝不会因某库数据无法迁移而失败。
  • 唯一例外:如果库自身的failed/missing_vector_store标记无法写入应用数据库,迁移器抛出且整个迁移失败。该标记是阻止库进入运行时打开路径的唯一手段,因此迁移记录为completed却没有该标记,会让库永久无法搜索且无路可退;失败反而让下次启动从头重跑。

磁盘空间不回收

成功迁移后,v1 legacy 向量库(以及复制的 legacy 上传文件)以孤儿形式留在磁盘上,磁盘空间不回收。回收被有意留给未来一个独立的清理步骤,并以用户放弃 v1 为前提。

校验

每个成功迁移的库,重建存储的行数必须与 prepare 时的规划一致:

  • material数 == 每个迁移条目一个;
  • search_unit数 == 这些条目保留的分块总数;
  • embedding数 == 库内不同的 embedding 文本哈希数;
  • 每个search_text行必须解析到已存储的embedding零未覆盖单元)——这是重建自愈不变量在迁移期的形态:没有支撑向量的单元会静默缺席向量搜索。

流式批次还有 fail-closed 的漂移防御(iterateLegacyEmbeddingBatches):行数/解码/维度不匹配会抛出(中止事务);文本扫描之后pageContent变化产生的哈希没有单元推导它,存储的 embedding 覆盖检查会将其转为回滚。

被跳过的数据清单

库级跳过

  • 不在迁移后knowledge_base中的库;
  • 标记为failedembeddingModelId = null的库;
  • dimensions无效的库;
  • legacy 库文件缺失、解析为目录、不含vectors表、或扫描中途不可读的库;
  • 迁移后 id 无法映射回 legacy 知识库 id 的库;
  • 重映射后的 legacy id 不在 legacy Redux 状态中的库。

行级跳过

  • uniqueLoaderId无法映射到迁移后knowledge_item.id的向量行;
  • 映射到非索引容器类型(如directory)的向量行——仅限兜底路径(legacy 源不可读,或嵌入文件没有可迁移向量);正常路径会重归属到 per-file 子条目;
  • vector载荷缺失或为空的向量行;
  • 载荷存在但通过不支持的运行时编码暴露的向量行;
  • 向量长度与库记录dimensions不一致的向量行。

跳过的语义

被跳过的根本不产生存储,这与产生空存储不同。因此上述每个库级跳过都会在execute()的 flush 中被标记为可恢复的failed/missing_vector_store,而非completed,仅两个例外:根本没有迁移后knowledge_base行的库(prepare()迭代的是迁移后的行,无行可标记),以及已为failed的库(它携带自己的错误——如missing_embedding_model——不得覆盖)。

标记的原因:没有任何机制把knowledge_base行与文件系统对账。一个没有存储的completed库,首次运行时打开会创建并缓存一个空白的index.sqlite,搜索结果永远为空,没有失败徽标也没有恢复入口。恢复是 UI 内的 restore 流程(把条目重新加入新库并重新向量化),而不是重跑迁移——迁移本身仍然完成。

如果某库下所有 legacy 向量都被跳过,该库仍会被规划,其重建的 V2 存储预期为空(仅 schema +meta行)。这是有意为之:只有能被证明属于迁移后knowledge_item行的向量在 V2 中才保持有效。

目录孤儿降级

源码中还有一层精细的孤儿降级逻辑:目录展开产生的子条目若未获得任何向量(markEmptyDirectoryChildren),会被降级为failed/directory_not_migrated,其虚拟路径源无法 reindex;若一组内所有子条目都为空,容器本身也被降级。整个库被跳过时(markDirectoryGroupsFullyOrphaned),容器与所有子条目一起降级。降级写入(flushDirectoryDegradations)按 500 一批(DEGRADE_UPDATE_CHUNK)执行,规避 SQLite 绑定变量上限;且降级失败只记告警不抛出——丢失降级只留下某库内几个空文档,而丢失库标记会让整个库静默不可搜索,两者的爆炸半径完全不同(KnowledgeVectorMigrator.ts)。

测试与回归保障

KnowledgeVectorMigrator.test.ts(约 3300 行)围绕迁移器的核心不变量构建了多层回归防线:

  • 运行时路径读回断言runtimeVectorStorePath镜像pathStorage.ts{root}/{baseId}/.cherry/index.sqlite布局,runtimeMaterialPath镜像raw/布局——迁移器写到运行时不会打开的位置即失败;
  • OOM 回归守卫:钉死PreparedBasePlan的精确键集合与每阶段读取形态(prepare 流式;execute 按 ≤500 rowid 分批,501 分块条目必须以 500 + 1 到达);
  • 字节级等价验证:读回时用KnowledgeIndexStoreencodeVectorBlobhashEmbeddingText等运行时同一套实现断言存储内容;
  • skip 语义验证unmapped_loaderdimension_mismatch、缺失/空载荷、非索引容器类型、legacy 库缺失/目录/非 embedjs 格式等各分支;
  • directory 展开与重归属验证:legacy sitemap 映射为 url、目录容器级向量重归属到合成子条目、冲突时的独立条目所有权保留。

与 KnowledgeMigrator 的协作与恢复流

KnowledgeVectorMigrator无法脱离其前置迁移器独立理解。两阶段的协作要点:

  1. 目录展开KnowledgeMigrator.expandLegacyDirectoryItem为每个嵌入 loader 源合成一个file子条目,并记录 loader id → 子条目 id 的映射,KnowledgeVectorMigrator据此把容器级向量重归属到子条目(README-KnowledgeMigrator.md)。
  2. 缺失嵌入模型的可恢复失败:当 legacy 库的嵌入模型 id 存在于 Redux 但不存在于 V2user_model(例如ollama::dengcao/Qwen3-Embedding-0.6B:Q8_0),KnowledgeMigrator将其保存为embeddingModelId = nullstatus = failederror = missing_embedding_modelKnowledgeVectorMigrator因嵌入模型契约无法验证而跳过该库的向量。恢复走knowledge:restore-base运行时流程——以源库配置与所选模型创建新库、仅复制根条目、运行正常索引流——原始 failed 库保留供用户确认后再删除。
  3. 维度解析KnowledgeMigrator从 legacy 向量库vectors表的首个非空 blob 长度(length(vector)/4)解析dimensionsKnowledgeVectorMigrator用同一值做dimension_mismatch校验。

总结

KnowledgeVectorMigrator是 Cherry Studio V2 迁移中最能体现"与运行时逐字节一致"设计哲学的实现:复用createKnowledgeIndexStoreAtPath工厂与rebuildMaterial,使迁移产物与运行时自建存储无差别;通过 F32_BLOB →Float32ArrayencodeVectorBlob的端到端浮点通道实现零重新向量化;以"prepare 流式 + execute 分批 point-read"的内存契约消化了 OOM 崩溃历史;以原地构建 + 全有或全无发布 + 快照 pin 处理了 Windows 文件锁与崩溃原子性的矛盾;最终以 500 分批的写入、封顶采样的告警和逐库非致命的失败语义,保证任意规模的语料都能在不阻塞用户的前提下完成迁移。

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询