主题
文档处理管道
本文引用的源码文件
本文引用的文件
目录
引言
本技术文档聚焦 RAG 文档处理管道,围绕多格式文档解析、动态路由加载、文本分块策略、预处理流程以及从上传到索引构建的完整链路展开。该管道位于 Agent 端的 RAG 子系统,负责将多种来源(本地文件、前端上传、URL)的文档统一解析为纯文本,再经分块、向量化后写入向量存储,同时保留原文备份以支持混合检索与重建。
项目结构
RAG 文档处理相关代码集中在 agent/src/rag 下,其中文档解析与分块位于 document 子目录,知识库生命周期与导入编排位于 manager.js,配置与错误类型分别位于 config.js 与 errors.js。
graph TB subgraph "RAG 文档处理" L["document/loader.js<br/>按扩展名路由解析器"] C["document/chunker.js<br/>段落优先+重叠分块"] P_TXT["document/parsers/text.js<br/>文本/BOM清理"] P_PDF["document/parsers/pdf.js<br/>PDF 文本提取"] P_OFFICE["document/parsers/office.js<br/>Office/PPT/表格解析"] CFG["rag/config.js<br/>扩展名路由表/默认参数"] ERR["rag/errors.js<br/>RagError 与翻译"] MGR["rag/manager.js<br/>导入/分块/向量化/存储编排"] end MGR --> L L --> P_TXT L --> P_PDF L --> P_OFFICE MGR --> C L --> CFG MGR --> ERR
图示来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- 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
- agent/src/rag/config.js#L56-L69
- agent/src/rag/errors.js#L1-L30
- agent/src/rag/manager.js#L749-L897
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L1-L70
- agent/src/rag/manager.js#L749-L897
核心组件
- 文档加载器(loader.js):根据文件扩展名动态选择并懒加载对应解析器,返回统一文本与文件名。
- 文本分块器(chunker.js):段落优先断点 + 字符窗口兜底 + 重叠回退,输出带偏移区间的分块。
- 解析器族(parsers/*):
- text.js:UTF-8 读取并去除 BOM。
- pdf.js:通过 pdf-parse 提取 PDF 文本。
- office.js:docx/xlsx/xls/pptx 等 Office 系列的多后端解析。
- 配置(config.js):集中维护扩展名路由表、默认分块参数、模型与批大小等常量。
- 错误(errors.js):定义 RagError 与错误翻译工具,便于上层响应层国际化。
- 管理器(manager.js):封装知识库生命周期、导入入口(文本/文件/URL)、分块、向量化、存储与备份、重建与恢复。
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- 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
- agent/src/rag/config.js#L1-L70
- agent/src/rag/errors.js#L1-L30
- agent/src/rag/manager.js#L749-L897
架构总览
下图展示从“文档进入”到“索引构建”的端到端流程,包括解析、分块、向量化、存储与备份。
sequenceDiagram
participant Client as "调用方"
participant Manager as "RagManager"
participant Loader as "loadDocument"
participant Parser as "具体解析器"
participant Chunker as "chunkText"
participant Embed as "EmbeddingProvider"
participant Store as "VectraStore"
Client->>Manager : ingestFile / ingestUrl / ingestText
Manager->>Loader : loadDocument(filePath)
Loader->>Parser : parse(filePath)
Parser-->>Loader : text
Loader-->>Manager : {text, fileName}
Manager->>Chunker : chunkText(text, options)
Chunker-->>Manager : chunks[{text,startPos,endPos}]
Manager->>Embed : embedDocuments(chunks)
Embed-->>Manager : embeddings[]
Manager->>Store : upsertChunks(documentId, chunks, embeddings, metadata)
Manager->>Manager : _saveDocumentBackup(...)
Manager-->>Client : {documentId, name, chunkCount}
图示来源
- agent/src/rag/manager.js#L749-L897
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/document/chunker.js#L31-L74
详细组件分析
文档加载器与解析器路由
- 扩展名路由表由 config.js 的 PARSER_ROUTES 维护,loader.js 据此判断是否受支持并动态选择解析器名称。
- 解析器采用按需 import,仅实际使用的格式才加载对应依赖,降低冷启动成本。
- 不支持的扩展名会抛出 RagError('unsupportedFormat'),便于上层统一处理。
flowchart TD
A["收到 filePath"] --> B["ext = extname(filePath).toLowerCase()"]
B --> C{"ext 在 PARSER_ROUTES ?"}
C -- 否 --> E["抛出 RagError('unsupportedFormat')"]
C -- 是 --> D["mod = await PARSERS[name]()"]
D --> F["text = await mod.parse(filePath)"]
F --> G["返回 {text, fileName}"]
图示来源
章节来源
文本分块策略
- 自然断点优先级:优先在段落或句子边界切分,避免截断语义。
- 字符窗口兜底:当找不到断点时,按 chunkSize 切分。
- 重叠回退:下一块起点回退 overlap 字符,防止上下文断裂;同时保证 next > start 避免死循环。
- 安全上限:超过 CHUNK_SIZE_WARN_LIMIT 会告警,避免被模型静默截断。
flowchart TD
S["开始"] --> N["规范化换行"]
N --> W["计算 chunkSize/overlap钳制范围"]
W --> L{"pos < n ?"}
L -- 否 --> END["结束"]
L -- 是 --> SKIP["跳过空白起始"]
SKIP --> END_POS["end = min(start+chunkSize, n)"]
END_POS --> BRK{"未到文末?"}
BRK -- 是 --> FIND["在后半段寻找最近断点"]
FIND --> ADJ["若找到则 end=breakAt"]
BRK -- 否 --> NEXT_STEP["继续"]
ADJ --> NEXT_STEP["取 slice(start,end).trim()"]
NEXT_STEP --> PUSH{"是否为空?"}
PUSH -- 否 --> ADD["加入 chunks"]
PUSH -- 是 --> NOOP["跳过"]
ADD --> CHECKEND{"end >= n ?"}
NOOP --> CHECKEND
CHECKEND -- 是 --> END
CHECKEND -- 否 --> OVERLAP["next = max(end-overlap, start+1)"]
OVERLAP --> UPDATE["pos = next; 回到 L"]
图示来源
章节来源
解析器实现要点
- 文本类(text.js):以 UTF-8 读取,去除首字节 BOM,避免影响 embedding 质量。
- PDF(pdf.js):通过 pdf-parse 提取文本;因 CJS 特性使用 createRequire 加载。
- Office(office.js):
- docx → mammoth 提取纯文本。
- xlsx/xls → xlsx 逐 sheet 转 CSV,保留行列结构。
- pptx 及其他 → officeparser 通用解析。
章节来源
- 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
文档预处理与元数据
- 编码处理:文本解析器显式处理 UTF-8 BOM;URL 链路通过 decodeBytes 按 contentType 解码(兼容 GBK 站点)。
- 特殊字符清理:分块前对换行进行规范化,避免 \r\n 混用导致断点异常。
- 表格数据提取:Excel 转为 CSV 文本,保留行列结构,利于检索理解。
- 元数据保留:
- 用户 metadata 先展开,系统键(documentName/source/createdAt)最后写入,确保不被覆盖。
- 原文备份包含 id/name/text/source/metadata/chunkCount/createdAt,供 BM25 与重建使用。
章节来源
- agent/src/rag/document/parsers/text.js#L10-L14
- agent/src/rag/document/chunker.js#L34-L36
- agent/src/rag/document/parsers/office.js#L24-L33
- agent/src/rag/manager.js#L868-L879
端到端导入流程(文件/URL/文本)
- 文件导入:
- 本地路径:走沙箱校验(allowedPaths + 硬阻止清单),通过后交由 loader 解析。
- base64 上传:体积预检后落临时文件,解析后删除。
- URL 导入:
- 安全抓取(重定向校验、私网拦截、响应体上限)。
- HTML → htmlToText;PDF → 临时文件 + loader;其他 text/* → 解码为文本。
- 统一入库:
- 分块 → 批量向量化(含维度校验)→ 事务写入 VectraStore → 原文备份 → 更新注册表计数。
sequenceDiagram
participant API as "调用方"
participant M as "RagManager"
participant FS as "文件系统/网络"
participant L as "loadDocument"
participant C as "chunkText"
participant E as "EmbeddingProvider"
participant V as "VectraStore"
API->>M : ingestFile(path|contentBase64)
M->>FS : 沙箱检查/写临时文件
M->>L : parse(filePath)
L-->>M : text
M->>C : chunkText(text)
C-->>M : chunks
M->>E : embedDocuments(chunks)
E-->>M : embeddings
M->>V : upsertChunks(...)
M->>M : _saveDocumentBackup(...)
M-->>API : {documentId, name, chunkCount}
图示来源
- agent/src/rag/manager.js#L749-L897
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/document/chunker.js#L31-L74
章节来源
依赖关系分析
- loader.js 依赖 config.js 的 PARSER_ROUTES,决定解析器名称;运行时按需 import 具体解析器模块。
- chunker.js 依赖 config.js 的 CHUNK_SIZE_WARN_LIMIT 做安全告警。
- manager.js 组合 loader、chunker、embedding provider、VectraStore,并负责进度上报与错误包装。
- errors.js 提供 RagError 与 translateRagError,用于上层响应层国际化。
graph LR CFG["config.js"] --> LDR["loader.js"] CFG --> CHK["chunker.js"] LDR --> TXT["parsers/text.js"] LDR --> PDF["parsers/pdf.js"] LDR --> OFF["parsers/office.js"] MGR["manager.js"] --> LDR MGR --> CHK MGR --> ERR["errors.js"]
图示来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L1-L70
- agent/src/rag/manager.js#L749-L897
- agent/src/rag/errors.js#L1-L30
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L1-L70
- agent/src/rag/manager.js#L749-L897
- agent/src/rag/errors.js#L1-L30
性能与容量控制
- 分块大小与重叠:默认 chunkSize=400、overlap=80,兼顾中文 token 上限与安全边界;超过 500 字符触发告警。
- 批大小:EMBED_BATCH_SIZE=10,适配部分平台单次批量上限。
- 导入体积限制:
- 文本导入上限 10MB。
- 文件上传上限 50MB(base64 长度近似换算)。
- 内存保护:超大内容在解析/分块/向量化前快速失败,避免全量驻留内存。
- 进度监控:导入各阶段(parsing/chunking/embedding/storing/done/error)均上报进度快照,供前端轮询。
章节来源
- agent/src/rag/document/chunker.js#L31-L41
- agent/src/rag/config.js#L37-L54
- agent/src/rag/manager.js#L42-L45
- agent/src/rag/manager.js#L723-L789
- agent/src/rag/manager.js#L849-L897
故障排查指南
- 不支持的文件格式:
- 现象:抛出 RagError('unsupportedFormat')。
- 排查:确认扩展名是否在 PARSER_ROUTES 中;必要时添加新路由。
- 内容为空:
- 现象:emptyContent / emptyDocument / urlNoContent。
- 排查:检查解析结果是否为空;URL 场景注意反爬壳页或无文本层 PDF。
- 维度不匹配:
- 现象:embeddingDimensionMismatch。
- 排查:核对 embeddingConfig.dimensions 与实际模型输出维度是否一致。
- 重建中断恢复:
- 现象:进程重启后出现 .rebuild_* 目录或 rebuild_manifest.json。
- 行为:自动收敛(完成换入/补提交/回滚旧索引/清理残留)。
- 权限与密钥:
- collections.json 落盘权限 0o600;apiKey 落盘前混淆,内存明文。
章节来源
- agent/src/rag/document/loader.js#L38-L47
- agent/src/rag/manager.js#L723-L897
- agent/src/rag/manager.js#L434-L640
- agent/src/rag/errors.js#L1-L30
结论
该 RAG 文档处理管道通过“扩展名路由 + 按需解析器 + 段落优先分块 + 批量向量化 + 原子重建”的组合,实现了多格式文档的统一接入与高可靠索引构建。其设计强调可观测性(进度上报)、安全性(沙箱/尺寸限制/权限)与可恢复性(重建清单与恢复逻辑),适合在生产环境中稳定运行。
附录:端到端示例与扩展机制
端到端示例(上传到索引构建)
以下示例描述从“前端上传文件”到“索引构建完成”的调用序列,包含错误处理与进度监控的关键节点。
sequenceDiagram
participant UI as "前端/调用方"
participant M as "RagManager"
participant L as "loadDocument"
participant C as "chunkText"
participant E as "EmbeddingProvider"
participant V as "VectraStore"
UI->>M : ingestFile({fileName, contentBase64})
M->>M : 体积预检≈ base64*3/4
M->>M : 写临时文件
M->>L : loadDocument(tmpPath)
L-->>M : {text, fileName}
M->>C : chunkText(text, collection.chunkConfig)
C-->>M : chunks
M->>E : embedDocuments(chunks)
E-->>M : embeddings
M->>V : upsertChunks(documentId, chunks, embeddings, metadata)
M->>M : _saveDocumentBackup(...)
M-->>UI : {documentId, name, chunkCount}
Note over M : 全程上报 phase=parsing/chunking/embedding/storing/done/error
图示来源
- agent/src/rag/manager.js#L749-L897
- agent/src/rag/document/loader.js#L38-L48
- agent/src/rag/document/chunker.js#L31-L74
扩展新文档格式的步骤
- 新增解析器:
- 在 parsers/ 下新增解析模块,实现统一的 parse(filePath) 接口。
- 注册路由:
- 在 config.js 的 PARSER_ROUTES 中添加扩展名到解析器名称的映射。
- 可选优化:
- 如需懒加载,可在 loader.js 的 PARSERS 映射中增加对应的动态 import。
- 验证:
- 使用 isSupportedFile 与 supportedExtensions 校验扩展名。
- 通过 ingestFile 传入测试文件,观察 parsing → chunking → embedding → storing → done 的进度。
章节来源