主题
向量存储与索引
本文引用的源码文件
本文引用的文件
目录
引言
本文聚焦 Agent 端 RAG 子系统的向量存储与索引实现,围绕 Vectra 存储适配器展开,解释向量数据的持久化方式、索引构建流程、查询优化路径、元数据存储结构、主键设计、数据一致性保证、增量更新机制、数据迁移策略和存储空间管理。文档同时给出架构图、数据表结构与性能调优建议,并讨论大规模向量数据的存储方案、备份恢复和数据清理策略。
项目结构
RAG 的向量存储相关代码集中在 agent/src/rag 下:
- store/vectra.js:VectraStore 适配器,封装 vectra@0.15.0 的 LocalIndex。
- store/index.js:向量存储统一入口 createVectorStore。
- manager.js:知识库生命周期、导入、重建、检索等编排逻辑。
- config.js:RAG_ROOT、COLLECTIONS_REGISTRY、默认 embedding/chunk/search 配置等。
- docs/RAG-Knowledge-Retrieval-Design.md:RAG 设计文档,包含 Vectra 存储布局、容量与性能说明。
graph TB subgraph "Agent RAG" M["manager.js<br/>知识库编排"] S["store/index.js<br/>createVectorStore"] V["store/vectra.js<br/>VectraStore"] C["config.js<br/>RAG_ROOT/注册表/默认配置"] D["docs/RAG-Knowledge-Retrieval-Design.md<br/>设计文档"] end M --> V M --> C S --> V D -.参考.-> M D -.参考.-> V
图表来源
- agent/src/rag/manager.js#L1-L29
- agent/src/rag/store/index.js#L1-L14
- agent/src/rag/store/vectra.js#L1-L21
- agent/src/rag/config.js#L1-L18
- docs/RAG-Knowledge-Retrieval-Design.md#L416-L450
章节来源
- agent/src/rag/manager.js#L1-L29
- agent/src/rag/store/index.js#L1-L14
- agent/src/rag/config.js#L1-L18
- docs/RAG-Knowledge-Retrieval-Design.md#L416-L450
核心组件
- VectraStore:对 vectra@0.15.0 LocalIndex 的适配层,负责索引创建、分块 upsert、向量检索、按文档列出/删除、全量枚举、重建。
- RagManager:知识库与文档的生命周期管理,串联解析、分块、向量化、写入 Vectra、原文备份、注册表计数更新;提供重建、恢复、统计、检索等能力。
- VectorStore 工厂:createVectorStore 动态加载 VectraStore,为未来扩展其他后端(如 Qdrant)预留接口。
- RAG 配置:定义 RAG_ROOT、collections.json 注册表路径、默认 embedding/chunk/search 参数、解析器路由等。
章节来源
- agent/src/rag/store/vectra.js#L13-L123
- agent/src/rag/manager.js#L172-L184
- agent/src/rag/store/index.js#L1-L14
- agent/src/rag/config.js#L11-L18
架构总览
RAG 的向量存储采用“本地文件型向量库 + 文件系统备份”的组合:
- 每个知识库一个目录(kb_xxx),内含 Vectra 索引文件与 documents/ 原始文档备份。
- collections.json 作为知识库注册表,保存知识库元数据、embeddingConfig、chunkConfig、documentCount、chunkCount 等。
- RagManager 负责索引重建时的原子换入与崩溃恢复,确保旧索引可用、新索引可切换。
graph TB Client["调用方工具/路由"] Manager["RagManager<br/>manager.js"] Store["VectraStore<br/>store/vectra.js"] Index["LocalIndex(vectra)<br/>index.json / index.protobuf"] Registry["collections.json<br/>知识库注册表"] Docs["documents/<br/>原始文档备份"] Client --> Manager Manager --> Store Store --> Index Manager --> Registry Manager --> Docs
图表来源
- agent/src/rag/manager.js#L1-L29
- agent/src/rag/store/vectra.js#L13-L21
- agent/src/rag/config.js#L11-L18
- docs/RAG-Knowledge-Retrieval-Design.md#L416-L450
详细组件分析
VectraStore 适配器
VectraStore 是向量存储的核心适配器,封装 vectra@0.15.0 的 LocalIndex API,解决以下约束:
- createIndex 对已存在索引会抛错,需先 isIndexCreated。
- 无批量 upsert API,使用 beginUpdate/endUpdate 事务包裹逐条 upsertItem,避免每条一次落盘。
- 无按元数据删除 API,通过 listItemsByMetadata + deleteItems 两步完成。
- queryItems 为位置参数签名 (vector, query, topK, filter?, isBm25?),query 仅 BM25 路径使用。
- metadata 中记录 documentId/startPos/endPos,为 BM25 混合检索预留;原始文档备份见 documents/。
关键方法职责:
- ensureCreated:幂等创建索引。
- upsertChunks:事务批量写入一个文档的全部分块,系统保留键覆盖用户传入的同名键,防止篡改导致孤儿分块。
- search:统一返回 {id, content, metadata, score},score 为归一化余弦相似度 [-1,1]。
- listDocumentChunks/removeDocument:按 documentId 列出或删除所有分块。
- listAllItems/recreate:用于统计与重建场景。
classDiagram
class VectraStore {
+constructor(collectionId, rootDir)
+ensureCreated() Promise~void~
+upsertChunks(documentId, chunks, embeddings, metadata) Promise~void~
+search(queryVector, queryText, options) Promise~Array~
+listDocumentChunks(documentId) Promise~Array~
+removeDocument(documentId) Promise~number~
+listAllItems() Promise~Array~
+recreate() Promise~void~
}
图表来源
章节来源
RagManager 与向量写入流程
RagManager 将“解析 → 分块 → 向量化 → 写入 Vectra → 原文备份 → 更新注册表计数”串成完整链路,并提供进度上报、并发控制、错误处理。
sequenceDiagram
participant Caller as "调用方"
participant Manager as "RagManager"
participant Provider as "EmbeddingProvider"
participant Store as "VectraStore"
participant FS as "文件系统"
Caller->>Manager : ingestText/ingestFile/ingestUrl
Manager->>Manager : chunkText(text, chunkConfig)
Manager->>Provider : embedDocuments(chunks)
Provider-->>Manager : embeddings[]
Manager->>Store : upsertChunks(documentId, chunks, embeddings, meta)
Store->>FS : beginUpdate/endUpdate 事务写入
Manager->>FS : _saveDocumentBackup(doc.json)
Manager->>Manager : _updateRegistry(计数+1)
Manager-->>Caller : {documentId, name, chunkCount}
图表来源
章节来源
向量检索流程
检索由 RagManager.search 委托给 Searcher,底层调用 VectraStore.search,最终走 LocalIndex.queryItems。
sequenceDiagram
participant Caller as "调用方"
participant Manager as "RagManager"
participant Searcher as "Searcher"
participant Store as "VectraStore"
participant Index as "LocalIndex"
Caller->>Manager : search(collectionId, query, options)
Manager->>Manager : normalizeSearchOptions(options)
Manager->>Searcher : new Searcher(store, provider)
Searcher->>Store : search(queryVector, queryText, options)
Store->>Index : queryItems(vector, query, topK, filter, isBm25)
Index-->>Store : results[]
Store-->>Searcher : [{id,content,metadata,score}]
Searcher-->>Manager : 结果集
Manager-->>Caller : 搜索结果
图表来源
章节来源
索引重建与原子换入
当 embedding 或 chunk 配置发生向量空间级变更时,RagManager 触发后台重建:
- 在临时目录 .rebuild_{collectionId} 内用新配置重放 documents/ 备份,生成新索引与新备份。
- 写 rebuild_manifest.json 清单,随后原子 rename(finalDir, oldIndexKeep),再 rename(tmpRoot/collectionId, finalDir)。
- 提交注册表后删除清单并清理临时目录;失败时保留现场供恢复。
flowchart TD Start(["开始重建"]) --> ReadBackups["读取 documents/ 备份"] ReadBackups --> BuildTmp["在临时目录构建新索引与备份"] BuildTmp --> WriteManifest["写入 rebuild_manifest.json"] WriteManifest --> SwapOld["rename(finalDir -> index_backup)"] SwapOld --> SwapNew["rename(tmpRoot/collectionId -> finalDir)"] SwapNew --> CommitReg["提交注册表新配置/计数"] CommitReg --> Cleanup["删除清单/清理临时目录"] Cleanup --> End(["完成"]) SwapOld --> |失败| Rollback["回滚旧索引"] Rollback --> End
图表来源
章节来源
依赖关系分析
- RagManager 依赖:
- VectraStore:写入/查询向量索引。
- EmbeddingProvider:根据 collection.embeddingConfig 生成向量。
- FileSystem:读写 collections.json、documents/、重建临时目录。
- Security/url-guard:路径沙箱与 URL 导入安全。
- VectraStore 依赖:
- vectra.LocalIndex:本地索引 API。
- path/homedir/os:路径拼接与根目录。
graph LR Manager["RagManager"] --> VectraStore["VectraStore"] Manager --> Embedding["EmbeddingProvider"] Manager --> FS["文件系统"] Manager --> Security["Security/URL Guard"] VectraStore --> LocalIndex["vectra.LocalIndex"]
图表来源
章节来源
性能与容量特性
- Vectra 是轻量级本地文件向量库,非独立数据库服务。
- 检索为暴力余弦相似度扫描(非 ANN),速度随数据量线性变慢,但检索质量不随数据量下降。
- v0.15.0 优化:min-heap top-K 查询 O(N log K)、不再深拷贝向量、并行 metadata 读取。
- 超过 10 万分块建议迁移外接方案(如 Qdrant HNSW)。
容量与性能参考(来自设计文档):
- 小型:约 1,000 分块,磁盘 ~3MB,内存 ~5MB,查询 <1ms。
- 中型:约 10,000 分块,磁盘 ~30MB,内存 ~50MB,查询 1-5ms。
- 大型:约 100,000 分块,磁盘 ~300MB,内存 ~500MB,查询 5-50ms。
- 超大:约 1,000,000 分块,磁盘 ~3GB,内存 ~5GB,查询 50-500ms。
章节来源
增量更新、迁移与一致性
增量更新机制
- 导入文本/文件/URL 均走 _ingestText:分块 → 向量化 → 事务写入 Vectra → 原文备份 → 更新注册表计数。
- 同一知识库的导入/重建互斥(_activeOps),避免并发写坏索引。
- 注册表更新通过 _registryLock 串行化,防并发写坏。
章节来源
- agent/src/rag/manager.js#L703-L715
- agent/src/rag/manager.js#L849-L897
- agent/src/rag/manager.js#L209-L229
数据迁移策略
- 当 embedding 或 chunk 配置变化触发重建时,RagManager 在临时目录重建索引,完成后原子换入,最后提交注册表。
- 重建期间新配置不写入注册表,完成后一次性生效;失败时旧索引与旧配置保持可用。
章节来源
数据一致性保证
- 事务写入:beginUpdate/endUpdate 包裹 upsertChunks,失败 cancelUpdate 丢弃半成品。
- 原子换入:rename(oldIndexKeep) → rename(newIndex) → 提交注册表 → 清理临时目录。
- 崩溃恢复:rebuild_manifest.json 与 index_backup 组合判定半完成状态,支持回滚/补提交/清理。
章节来源
- agent/src/rag/store/vectra.js#L41-L65
- agent/src/rag/manager.js#L509-L549
- agent/src/rag/manager.js#L561-L640
元数据模型与主键设计
知识库注册表(collections.json)
- 字段包括:id、name、description、embeddingConfig、chunkConfig、createdAt、documentCount、chunkCount、enabled(可选)。
- apiKey 在落盘前混淆,内存中保持明文;权限设置为 0o600。
章节来源
向量条目元数据(Vectra metadata)
- 系统保留键:documentId、chunkIndex、totalChunks、text、startPos、endPos。
- 业务键:documentName、source、createdAt 以及用户自定义 metadata(用户键在前,系统键在后,系统键不可被覆盖)。
章节来源
主键设计
- 知识库 ID:kb_ + 时间戳 + 随机字节,正则校验 kb_[a-z0-9]+。
- 文档 ID:doc_ + 时间戳 + 随机字节,正则校验 doc_[a-z0-9]+。
- 分块 ID:
${documentId}_chunk_${i},唯一且有序。
章节来源
- agent/src/rag/manager.js#L30-L32
- agent/src/rag/manager.js#L275-L289
- agent/src/rag/manager.js#L863-L864
- agent/src/rag/store/vectra.js#L45-L57
数据表结构(概念映射)
- 知识库集合(collections.json):
- id(主键)、name、description、embeddingConfig、chunkConfig、createdAt、documentCount、chunkCount、enabled。
- 向量条目(Vectra 内部条目):
- id(主键)、vector、metadata(documentId、chunkIndex、totalChunks、text、startPos、endPos、documentName、source、createdAt、...)。
- 文档备份(documents/{documentId}.json):
- id、name、text、source、metadata、chunkCount、createdAt。
erDiagram
COLLECTION {
string id PK
string name
string description
object embeddingConfig
object chunkConfig
timestamp createdAt
integer documentCount
integer chunkCount
boolean enabled
}
VECTOR_ITEM {
string id PK
float[] vector
object metadata
}
DOCUMENT_BACKUP {
string id PK
string name
text text
string source
object metadata
integer chunkCount
timestamp createdAt
}
COLLECTION ||--o{ VECTOR_ITEM : "包含"
COLLECTION ||--o{ DOCUMENT_BACKUP : "包含"
图表来源
- agent/src/rag/manager.js#L271-L300
- agent/src/rag/store/vectra.js#L47-L57
- agent/src/rag/manager.js#L928-L932
备份恢复与清理策略
重建清单与中断恢复
- rebuild_manifest.json:记录目标 embeddingConfig、chunkConfig、documentCount、chunkCount、startedAt。
- 进程重启后 recoverInterruptedRebuilds 扫描 .rebuild_* 目录,按形态收敛:
- finalDir 缺失 + tmpRoot/<id> 含清单:完成换入并补提交。
- finalDir 缺失 + tmpRoot/index_backup:回滚旧索引。
- finalDir 存在 + 清单存在:补提交并清理。
- finalDir 正常 + 残留 tmpRoot:安全清理。
章节来源
- agent/src/rag/manager.js#L498-L507
- agent/src/rag/manager.js#L561-L640
- agent/src/rag/manager.js#L646-L670
文档删除与计数修正
- 删除文档:从 Vectra 删除该文档的所有分块,删除 documents/{documentId}.json,更新注册表计数。
- getStats:实时聚合索引条目,修正注册表计数漂移。
章节来源
大规模存储方案与调优指南
存储布局
- RAG_ROOT:默认 ~/.ai-helper-agent/rag,可通过环境变量 AI_HELPER_RAG_ROOT 覆盖。
- 每个知识库目录:kb_xxx,包含 Vectra 索引文件(index.json 或 index.protobuf)与 documents/ 原始文档备份。
- models/:transformers.js 模型缓存。
章节来源
容量与性能调优
- 小/中型库(≤10 万分块):Vectra 本地索引足够,查询延迟低。
- 大型库(>10 万分块):建议迁移至外接方案(如 Qdrant HNSW),以降低查询延迟。
- 检索参数:topK 上限 SEARCH_TOPK_MAX=50,threshold 钳制到 [-1,1],防止候选集放大与阈值异常。
- 分块参数:默认 chunkSize=400、overlap=80,中文 512 token 上限内的安全边界。
- Embedding 批大小:EMBED_BATCH_SIZE=10,兼容平台单次批量上限。
章节来源
- agent/src/rag/manager.js#L38-L45
- agent/src/rag/manager.js#L156-L170
- agent/src/rag/config.js#L37-L54
- docs/RAG-Knowledge-Retrieval-Design.md#L432-L450
备份恢复与清理
- 原文备份:documents/{documentId}.json,BM25 docReader 依赖。
- 重建临时目录:.rebuild_{collectionId},含 index_backup 与 rebuild_manifest.json。
- 清理策略:重建成功后删除清单与临时目录;失败时保留现场供恢复。
章节来源
- agent/src/rag/manager.js#L876-L879
- agent/src/rag/manager.js#L509-L549
- agent/src/rag/manager.js#L561-L640
故障排查
- 导入失败:检查内容大小限制(INGEST_MAX_TEXT_BYTES/INGEST_MAX_FILE_BYTES)、空内容、URL 无正文、路径沙箱拒绝。
- 维度不匹配:embeddingDimensionMismatch,检查远端模型 dimensions 配置与实际输出维度。
- 重建失败:查看 rebuild_manifest.json 与 index_backup 是否存在,确认是否处于半完成状态;recoverInterruptedRebuilds 可自动收敛。
- 检索为空:检查 threshold 是否过大、topK 是否过小、知识库是否启用(enabled=false 不参与 LLM 自主检索)。
章节来源
- agent/src/rag/manager.js#L723-L739
- agent/src/rag/manager.js#L806-L843
- agent/src/rag/manager.js#L908-L926
- agent/src/rag/manager.js#L561-L640
结论
VectraStore 适配器以 vectra@0.15.0 为基础,结合 RagManager 的事务写入、原子换入与崩溃恢复机制,实现了可靠的向量数据持久化、索引构建与查询优化。通过 collections.json 注册表与 documents/ 原文备份,系统在数据一致性、可恢复性与可观测性方面具备完善保障。对于小规模数据,Vectra 本地索引表现良好;对于大规模数据,建议迁移至外接向量数据库以获得更好的检索性能。