Skip to content

文档处理管道 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与容量控制
  8. 故障排查指南
  9. 结论
  10. 附录:端到端示例与扩展机制

引言 ​

本技术文档聚焦 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

图示来源

章节来源

核心组件 ​

  • 文档加载器(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)、分块、向量化、存储与备份、重建与恢复。

章节来源

架构总览 ​

下图展示从“文档进入”到“索引构建”的端到端流程,包括解析、分块、向量化、存储与备份。

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}

图示来源

详细组件分析 ​

文档加载器与解析器路由 ​

  • 扩展名路由表由 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 通用解析。

章节来源

文档预处理与元数据 ​

  • 编码处理:文本解析器显式处理 UTF-8 BOM;URL 链路通过 decodeBytes 按 contentType 解码(兼容 GBK 站点)。
  • 特殊字符清理:分块前对换行进行规范化,避免 \r\n 混用导致断点异常。
  • 表格数据提取:Excel 转为 CSV 文本,保留行列结构,利于检索理解。
  • 元数据保留:
    • 用户 metadata 先展开,系统键(documentName/source/createdAt)最后写入,确保不被覆盖。
    • 原文备份包含 id/name/text/source/metadata/chunkCount/createdAt,供 BM25 与重建使用。

章节来源

端到端导入流程(文件/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}

图示来源

章节来源

依赖关系分析 ​

  • 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"]

图示来源

章节来源

性能与容量控制 ​

  • 分块大小与重叠:默认 chunkSize=400、overlap=80,兼顾中文 token 上限与安全边界;超过 500 字符触发告警。
  • 批大小:EMBED_BATCH_SIZE=10,适配部分平台单次批量上限。
  • 导入体积限制:
    • 文本导入上限 10MB。
    • 文件上传上限 50MB(base64 长度近似换算)。
  • 内存保护:超大内容在解析/分块/向量化前快速失败,避免全量驻留内存。
  • 进度监控:导入各阶段(parsing/chunking/embedding/storing/done/error)均上报进度快照,供前端轮询。

章节来源

故障排查指南 ​

  • 不支持的文件格式:
    • 现象:抛出 RagError('unsupportedFormat')。
    • 排查:确认扩展名是否在 PARSER_ROUTES 中;必要时添加新路由。
  • 内容为空:
    • 现象:emptyContent / emptyDocument / urlNoContent。
    • 排查:检查解析结果是否为空;URL 场景注意反爬壳页或无文本层 PDF。
  • 维度不匹配:
    • 现象:embeddingDimensionMismatch。
    • 排查:核对 embeddingConfig.dimensions 与实际模型输出维度是否一致。
  • 重建中断恢复:
    • 现象:进程重启后出现 .rebuild_* 目录或 rebuild_manifest.json。
    • 行为:自动收敛(完成换入/补提交/回滚旧索引/清理残留)。
  • 权限与密钥:
    • collections.json 落盘权限 0o600;apiKey 落盘前混淆,内存明文。

章节来源

结论 ​

该 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

图示来源

扩展新文档格式的步骤 ​

  • 新增解析器:
    • 在 parsers/ 下新增解析模块,实现统一的 parse(filePath) 接口。
  • 注册路由:
    • 在 config.js 的 PARSER_ROUTES 中添加扩展名到解析器名称的映射。
  • 可选优化:
    • 如需懒加载,可在 loader.js 的 PARSERS 映射中增加对应的动态 import。
  • 验证:
    • 使用 isSupportedFile 与 supportedExtensions 校验扩展名。
    • 通过 ingestFile 传入测试文件,观察 parsing → chunking → embedding → storing → done 的进度。

章节来源