主题
RAG 数据处理管道
本文引用的源码文件
本文引用的文件
- agent/src/rag/config.js
- agent/src/rag/manager.js
- agent/src/rag/searcher.js
- agent/src/rag/detect.js
- agent/src/rag/document/loader.js
- agent/src/rag/document/chunker.js
- agent/src/rag/embedding/index.js
- agent/src/rag/embedding/local.js
- agent/src/rag/embedding/openai-compat.js
- agent/src/rag/store/vectra.js
- agent/src/rag/errors.js
- agent/src/rag/document/parsers/text.js
- agent/src/rag/document/parsers/pdf.js
- agent/src/rag/document/parsers/office.js
目录
引言
本文件面向 RAG(检索增强生成)数据处理管道的实现,覆盖从多格式文档加载、文本分块、向量嵌入生成到索引构建与查询检索的完整链路。重点说明:
- 多格式解析路由与纯文本统一输出
- 中文优化的分块策略与重叠设计
- 本地 BGE 模型与 OpenAI 兼容远端两种嵌入后端
- Vectra 本地向量存储的事务写入与检索接口
- 混合检索引擎:语义向量 + 关键词命中增强
- 大规模数据重建、增量更新、崩溃恢复与存储成本优化
项目结构
RAG 子系统位于 agent/src/rag,按职责划分为配置、管理器、文档处理、嵌入、存储与检索等模块:
- 配置与路径:config.js
- 知识库生命周期与导入/重建:manager.js
- 文档加载与解析:document/loader.js 及 parsers/*
- 文本分块:document/chunker.js
- 嵌入抽象与后端:embedding/index.js, local.js, openai-compat.js
- 向量存储适配器:store/vectra.js
- 检索器与混合检索:searcher.js
- 运行时检测:detect.js
- 错误定义:errors.js
graph TB subgraph "RAG 子系统" CFG["配置<br/>config.js"] MGR["知识库管理<br/>manager.js"] DOC["文档加载<br/>loader.js"] P_TXT["文本解析<br/>parsers/text.js"] P_PDF["PDF 解析<br/>parsers/pdf.js"] P_OFF["Office 解析<br/>parsers/office.js"] CHUNK["分块<br/>chunker.js"] EMB_IDX["嵌入工厂<br/>embedding/index.js"] EMB_LOCAL["本地嵌入<br/>embedding/local.js"] EMB_OPENAI["OpenAI 兼容嵌入<br/>embedding/openai-compat.js"] STORE["向量存储<br/>store/vectra.js"] SEARCH["检索器<br/>searcher.js"] DETECT["依赖检测<br/>detect.js"] ERR["错误定义<br/>errors.js"] end MGR --> CFG MGR --> DOC DOC --> P_TXT DOC --> P_PDF DOC --> P_OFF MGR --> CHUNK MGR --> EMB_IDX EMB_IDX --> EMB_LOCAL EMB_IDX --> EMB_OPENAI MGR --> STORE MGR --> SEARCH SEARCH --> STORE MGR --> DETECT MGR --> ERR
图表来源
- agent/src/rag/config.js#L1-L70
- agent/src/rag/manager.js#L1-L120
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/embedding/index.js#L1-L68
- agent/src/rag/embedding/local.js#L1-L106
- agent/src/rag/embedding/openai-compat.js#L1-L64
- agent/src/rag/store/vectra.js#L1-L124
- agent/src/rag/searcher.js#L1-L162
- agent/src/rag/detect.js#L1-L193
- agent/src/rag/errors.js#L1-L30
章节来源
核心组件
- 配置中心:集中定义 RAG 根目录、集合注册表、默认嵌入模型(BGE 中文)、分块参数、检索阈值、批大小与扩展名路由。
- 知识库管理器:负责集合增删改查、文档导入(文本/文件/URL)、后台重建索引、原子换入与崩溃恢复、进度上报与并发控制。
- 文档处理器:按扩展名动态加载解析器,统一输出纯文本;支持 txt/md/json/csv/html/pdf/docx/xlsx/pptx 等。
- 分块器:段落优先断点 + 字符窗口兜底 + 重叠回退,适配中文 token 近似 1:1 特性并避免静默截断。
- 嵌入提供者:统一 embedQuery(带前缀)与 embedDocuments(无前缀),支持本地 transformers.js 与 OpenAI 兼容远端。
- 向量存储:基于 vectra 的 LocalIndex,封装事务批量 upsert、按元数据删除、BM25 旁路参数与结果归一化。
- 检索器:语义向量检索 + 关键词命中增强(英文词边界/CJK 最长片段)+ 高频词剔除 + 旁路探测。
- 依赖检测:子进程探测 vectra 与 transformers 可用性,Node 版本前置校验,结果缓存。
- 错误体系:结构化 RagError 与翻译辅助,便于响应层国际化。
章节来源
- agent/src/rag/config.js#L1-L70
- agent/src/rag/manager.js#L1-L120
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/embedding/index.js#L1-L68
- agent/src/rag/store/vectra.js#L1-L124
- agent/src/rag/searcher.js#L1-L162
- agent/src/rag/detect.js#L1-L193
- agent/src/rag/errors.js#L1-L30
架构总览
RAG 管道分为“写入侧”和“读取侧”。写入侧完成文档解析、分块、向量化与索引构建;读取侧执行语义检索与关键词增强,返回候选片段供上层 LLM 使用。
sequenceDiagram
participant Client as "调用方"
participant Manager as "知识库管理器"
participant Loader as "文档加载器"
participant Parser as "解析器(text/pdf/office)"
participant Chunker as "分块器"
participant Embed as "嵌入提供者"
participant Store as "向量存储(Vectra)"
participant Searcher as "检索器"
Note over Client,Manager : 写入流程
Client->>Manager : 导入文本/文件/URL
Manager->>Loader : loadDocument(路径或临时文件)
Loader->>Parser : parse(filePath)
Parser-->>Loader : 纯文本
Loader-->>Manager : {text, fileName}
Manager->>Chunker : chunkText(text, 配置)
Chunker-->>Manager : chunks[{text,start,end}]
Manager->>Embed : embedDocuments(chunks)
Embed-->>Manager : embeddings[][]
Manager->>Store : upsertChunks(docId, chunks, embeddings, metadata)
Store-->>Manager : 成功
Note over Client,Searcher : 读取流程
Client->>Searcher : search(query, options)
Searcher->>Embed : embedQuery(query)
Embed-->>Searcher : queryVec
Searcher->>Store : search(queryVec, query, topK*2)
Store-->>Searcher : 候选集
Searcher->>Searcher : 关键词增强/阈值过滤/旁路探测
Searcher-->>Client : 最终结果
图表来源
- agent/src/rag/manager.js#L723-L789
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/document/chunker.js#L31-L75
- agent/src/rag/embedding/index.js#L45-L57
- agent/src/rag/store/vectra.js#L39-L65
- agent/src/rag/searcher.js#L46-L77
详细组件分析
文档加载与多格式解析
- 扩展名路由:通过 config.js 中的 PARSER_ROUTES 将 .txt/.md/.json/.csv 映射为 text,.html/.htm 映射为 html,.pdf 映射为 pdf,.docx/.pptx/.xlsx/.xls 映射为 office。
- 动态加载解析器:loader.js 按需 import 对应解析器,减少冷启动开销。
- 解析器实现:
- text.js:直接读取 UTF-8 并去除 BOM,保证后续 embedding 质量。
- pdf.js:通过 CJS 包 pdf-parse 提取文本,注意 require 包装以避免调试分支问题。
- office.js:docx 走 mammoth 纯文本;表格 xlsx/xls 逐 sheet 转 CSV 保留行列结构;其他 Office 格式走 officeparser。
flowchart TD
Start(["入口: loadDocument"]) --> Ext["解析扩展名"]
Ext --> Route{"是否受支持?"}
Route --> |否| Err["抛出 unsupportedFormat"]
Route --> |是| Load["按需加载解析器"]
Load --> Parse["调用 parse(filePath)"]
Parse --> Text["返回纯文本与文件名"]
Text --> End(["结束"])
图表来源
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/config.js#L56-L69
- agent/src/rag/document/parsers/text.js#L10-L14
- agent/src/rag/document/parsers/pdf.js#L15-L20
- agent/src/rag/document/parsers/office.js#L50-L55
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/config.js#L56-L69
- agent/src/rag/document/parsers/text.js#L1-L15
- agent/src/rag/document/parsers/pdf.js#L1-L21
- agent/src/rag/document/parsers/office.js#L1-L56
文本分块策略
- 目标:在 bge-small-zh-v1.5 最大输入 512 token 的安全边界内切分,避免静默截断。
- 策略:
- 自然断点优先:段落/句号/问号/分号等序列,尽量保持语义完整。
- 字符窗口兜底:当找不到断点时按 chunkSize 切分。
- 重叠回退:下一块起点回退 overlap 字符,防止上下文断裂。
- 安全上限告警:超过 500 字符时打印警告。
flowchart TD
S(["开始: chunkText"]) --> N["规范化换行"]
N --> Params["计算 chunkSize/overlap"]
Params --> Loop{"pos < n ?"}
Loop --> |否| Ret["返回 chunks"]
Loop --> |是| Skip["跳过空白"]
Skip --> EndCalc["end = min(start+chunkSize, n)"]
EndCalc --> Break{"未到文末且找到断点?"}
Break --> |是| Adjust["end=断点后偏移"]
Break --> |否| Keep["保持 end"]
Adjust --> Slice["切片并 trim"]
Keep --> Slice
Slice --> Push{"非空?"}
Push --> |是| Add["加入 chunks"]
Push --> |否| NextPos["计算 next"]
Add --> NextPos
NextPos --> Update["pos=next"]
Update --> Loop
图表来源
章节来源
向量嵌入生成与模型选择
- 统一接口:EmbeddingProvider 提供 embedQuery(自动附加 queryPrefix)与 embedDocuments(无前缀),确保非对称检索一致性。
- 本地模式:基于 @huggingface/transformers 的 feature-extraction pipeline,默认模型 Xenova/bge-small-zh-v1.5,q8 量化约 95MB,缓存至 ~/.ai-helper-agent/rag/models,下载端点默认 hf-mirror。
- 远端模式:OpenAI 兼容 /embeddings 接口,支持 dimensions 透传与 400 错误降级重试,返回向量按 index 排序。
- Token 计数:本地后端暴露 countTokens,用于上游发现超窗风险。
classDiagram
class EmbeddingProvider {
+string mode
+string modelName
+number dimensions
+string queryPrefix
+embedQuery(query) number[]
+embedDocuments(texts) number[][]
}
class LocalEmbedding {
+embed(texts) number[][]
+countTokens(texts) object
}
class OpenAICompatEmbedding {
+embed(texts) number[][]
}
EmbeddingProvider --> LocalEmbedding : "本地模式"
EmbeddingProvider --> OpenAICompatEmbedding : "远端模式"
图表来源
- agent/src/rag/embedding/index.js#L10-L57
- agent/src/rag/embedding/local.js#L18-L68
- agent/src/rag/embedding/openai-compat.js#L9-L62
章节来源
- agent/src/rag/embedding/index.js#L1-L68
- agent/src/rag/embedding/local.js#L1-L106
- agent/src/rag/embedding/openai-compat.js#L1-L64
向量存储与索引构建
- VectraStore 封装 LocalIndex:
- ensureCreated:幂等创建索引。
- upsertChunks:beginUpdate/endUpdate 事务包裹逐条 upsertItem,避免频繁落盘。
- search:queryItems 位置参数签名,统一返回结构,score 为余弦相似度 [-1,1]。
- listDocumentChunks/removeDocument/listAllItems/recreate:按文档维度操作与重建。
- 重建索引:manager._rebuildIndex 使用临时目录全量重放文档备份 → 原子 rename 换入 → 提交注册表 → 清理临时文件;失败时保留现场供恢复。
flowchart TD
A["开始: _rebuildIndex"] --> B["读取 documents/ 备份"]
B --> C["创建临时目录与新索引"]
C --> D["遍历备份: 分块→嵌入→upsertChunks"]
D --> E["写新备份与清单 manifest"]
E --> F{"原子换入成功?"}
F --> |是| G["提交注册表(新配置/计数)"]
F --> |否| H["回滚旧索引(尽力而为)"]
G --> I["删除清单/清理临时目录"]
H --> J["抛错(保留现场)"]
I --> K["结束"]
J --> K
图表来源
章节来源
混合检索引擎与查询优化
- 语义通道:对 query 调用 embedQuery(自动加前缀),得到 queryVec,调用 store.search(topK*2) 获取候选集。
- 关键词通道:
- 提取拉丁与 CJK 术语,分别按词边界匹配与最长片段匹配。
- 统计文档频率 df,剔除高频词(>半数 chunk)与低频豁免(df<=2)。
- 对命中 chunk 进行抬分:max(向量分, 保底) + 单元加分,封顶 SCORE_MAX。
- 未命中者分数不变,合并后统一阈值过滤。
- 旁路探测:若过滤后为空,放宽阈值再检索少量候选,区分“无数据”与“被过滤”。
flowchart TD
Q(["查询: search(query)"]) --> V["embedQuery(query)"]
V --> C["store.search(queryVec, topK*2)"]
C --> K["_boostByKeywords(query, candidates)"]
K --> T["阈值过滤(score>=threshold)"]
T --> Empty{"结果为空?"}
Empty --> |否| R["返回 topK 结果"]
Empty --> |是| F["放宽阈值检索 topK=3"]
F --> Filt["过滤 score>=threshold-0.2"]
Filt --> R
图表来源
章节来源
知识库生命周期与导入流程
- 创建/删除/启用切换:维护 collections.json 注册表,读写串行化,密钥落盘加密。
- 导入文本/文件/URL:
- 文本:体积预检 → 分块 → 嵌入 → 写入 Vectra → 原文备份。
- 文件:沙箱校验 path → 解析 → 同上;上传 base64 先估算尺寸,临时文件解析后清理。
- URL:遵循 ragUrlIngest 策略,防内网扫描。
- 进度上报:_reportProgress 提供 phase/current/total/done/error,前端轮询展示。
- 并发控制:同库导入/重建互斥,_activeOps 占位,避免索引写坏。
sequenceDiagram
participant UI as "前端/工具"
participant M as "RagManager"
participant FS as "文件系统"
participant L as "Loader/Parsers"
participant C as "Chunker"
participant E as "Embedding"
participant S as "VectraStore"
UI->>M : ingestText/ingestFile
M->>M : 体积/沙箱校验
M->>FS : 读取/写入临时文件
M->>L : loadDocument
L-->>M : {text, fileName}
M->>C : chunkText
C-->>M : chunks
M->>E : embedDocuments
E-->>M : embeddings
M->>S : upsertChunks
S-->>M : 成功
M-->>UI : 进度/结果
图表来源
- agent/src/rag/manager.js#L723-L789
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/document/chunker.js#L31-L75
- agent/src/rag/embedding/index.js#L55-L57
- agent/src/rag/store/vectra.js#L39-L65
章节来源
依赖关系分析
- 外部依赖:vectra(向量索引)、@huggingface/transformers(本地嵌入)、pdf-parse/mammoth/xlsx/officeparser(文档解析)。
- 运行时检测:detect.js 通过子进程 probe-child.mjs 探测 vectra 与 transformers 可用性,Node 版本需 >=22。
- 耦合关系:
- manager 强依赖 config、embedding、store、loader、chunker、searcher、security、crypto-lite。
- searcher 依赖 keyword-match(复用长期记忆模块)与 store.embedding。
- embedding/index 解耦 local/openai-compat 后端。
graph LR Detect["detect.js"] --> Transformers["@huggingface/transformers"] Detect --> Vectra["vectra"] Manager["manager.js"] --> Config["config.js"] Manager --> EmbedIdx["embedding/index.js"] Manager --> Store["store/vectra.js"] Manager --> Loader["document/loader.js"] Manager --> Chunker["document/chunker.js"] Manager --> Searcher["searcher.js"] EmbedIdx --> Local["embedding/local.js"] EmbedIdx --> OpenAI["embedding/openai-compat.js"] Loader --> Parsers["parsers/*"]
图表来源
- agent/src/rag/detect.js#L48-L137
- agent/src/rag/manager.js#L10-L28
- agent/src/rag/embedding/index.js#L7-L8
- agent/src/rag/document/loader.js#L9-L14
章节来源
性能与成本优化
- 分块与嵌入批大小:
- 默认 chunkSize=400、overlap=80,适配中文 1 字≈1 token 与模型 512 上限。
- EMBED_BATCH_SIZE=10,兼容部分平台单次批量上限。
- 内存与体积限制:
- INGEST_MAX_TEXT_BYTES=10MB,INGEST_MAX_FILE_BYTES=50MB,避免超大内容驻留内存导致 OOM。
- 搜索 topK 上界 SEARCH_TOPK_MAX=50,防止候选集放大。
- 本地模型缓存:
- transformers.js 模型缓存在 ~/.ai-helper-agent/rag/models,q8 量化降低内存占用。
- pipeline 单例与加载锁避免重复加载。
- 重建与原子换入:
- 临时目录构建新索引,rename 原子换入,失败回滚旧索引,保障线上可用。
- 关键词通道保护:
- KEYWORD_SCAN_LIMIT=20000,大库跳过关键词扫描,避免检索卡顿。
- 存储布局:
- collections.json 注册表 0600 权限,密钥落盘混淆;documents/ 备份原始文档便于重建。
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 依赖不可用:
- detectRagAvailable 返回 false 时查看 errorSummary,常见原因为 Node 版本过低或 vectra/transformers 缺失。
- 子进程探测超时:PROBE_TIMEOUT_MS=60s,原生库加载卡死会触发 SIGKILL。
- 维度不匹配:
- Searcher 在 embedQuery 后校验维度,若不匹配抛出 embeddingDimensionMismatch,需重建索引。
- 导入失败:
- 体积超限:contentTooLarge/fileTooLarge;路径不允许:pathNotAllowed;缺少文件名:missingFileName。
- 解析失败:unsupportedFormat;URL 导入受 ragUrlIngest 策略限制。
- 重建中断恢复:
- recoverInterruptedRebuilds 扫描 .rebuild_* 目录,依据 REBUILD_MANIFEST 补提交注册表或回滚旧索引。
- 错误翻译:
- 业务错误统一为 RagError,响应层 translateRagError 转为可翻译消息。
章节来源
- agent/src/rag/detect.js#L144-L193
- agent/src/rag/searcher.js#L50-L55
- agent/src/rag/manager.js#L723-L789
- agent/src/rag/manager.js#L552-L640
- agent/src/rag/errors.js#L1-L30
结论
该 RAG 数据处理管道以“配置集中、职责清晰、容错完备”为目标,实现了从多格式文档到向量索引的全链路能力。通过非对称检索的前缀机制、中文友好的分块策略、本地与远端双嵌入后端、Vectra 事务写入与混合检索增强,系统在准确性与性能之间取得平衡。重建与崩溃恢复机制保障了大规模数据场景下的稳定性,体积与并发限制则有效控制了资源消耗。建议在部署时关注 Node 版本与依赖安装状态,并根据业务语料调整分块与阈值参数以获得更优召回效果。