Skip to content

文档处理与解析 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与内存优化
  8. 故障排查指南
  9. 结论
  10. 附录

引言 ​

本模块负责将多种格式的本地文档统一转换为纯文本,并进一步切分为适合嵌入检索的分块。其设计目标是:

  • 支持多格式加载:文本、HTML、PDF、Office(Word/Excel/PPT 等)。
  • 通过扩展名路由到对应解析器,按需加载第三方依赖,降低冷启动成本。
  • 提供稳定可控的文本分块算法,兼顾中文语义完整性与模型 token 上限。
  • 使用结构化错误类型,便于上层进行国际化提示与恢复策略。

该模块属于 Agent 端的 RAG 子系统,位于 agent/src/rag/document 下,由 loader 统一入口、parsers 多实现、chunker 分块逻辑组成,并通过 config 暴露配置项与扩展名路由表。

项目结构 ​

RAG 文档处理相关代码集中在以下路径:

  • agent/src/rag/document/loader.js:按扩展名路由到解析器,统一输出 { text, fileName }。
  • agent/src/rag/document/chunker.js:段落优先 + 字符窗口兜底 + 重叠的分块算法。
  • agent/src/rag/document/parsers/text.js:纯文本类格式(txt/md/json/csv)读取与 BOM 清理。
  • agent/src/rag/document/parsers/pdf.js:基于 pdf-parse 的 PDF 文本提取。
  • agent/src/rag/document/parsers/office.js:docx/xlsx/xls/pptx 等 Office 文档的多库组合解析。
  • agent/src/rag/config.js:RAG 根目录、默认分块参数、解析器扩展名路由表等。
  • agent/src/rag/errors.js:RAG 业务错误 RagError 及翻译辅助函数。
graph TB
subgraph "RAG 文档处理"
L["loader.js<br/>按扩展名路由"]
C["chunker.js<br/>文本分块"]
P_TXT["parsers/text.js<br/>纯文本"]
P_PDF["parsers/pdf.js<br/>PDF"]
P_OFFICE["parsers/office.js<br/>Office"]
CFG["config.js<br/>路由表/默认配置"]
ERR["errors.js<br/>RagError"]
end
L --> CFG
L --> P_TXT
L --> P_PDF
L --> P_OFFICE
L --> ERR
C --> CFG

图表来源

章节来源

核心组件 ​

  • 文档加载器(loader):根据文件扩展名选择解析器,动态 import 对应模块,返回统一结构。
  • 解析器(parsers):针对不同格式实现 parse(filePath) → Promise<string>。
  • 分块器(chunker):对纯文本执行段落优先切分,控制 chunkSize 与 overlap,保留原文偏移。
  • 配置(config):集中管理 RAG 根目录、默认分块参数、解析器扩展名路由表。
  • 错误(errors):定义 RagError 与 translateRagError,用于上层响应层国际化。

章节来源

架构总览 ​

下图展示从文件路径到最终分块的完整流程:loader 根据扩展名路由到具体 parser,parser 返回纯文本;随后 chunker 对文本进行分块,返回包含原文偏移的结构,供后续 BM25 混合检索使用。

sequenceDiagram
participant Caller as "调用方"
participant Loader as "loader.js"
participant Parser as "parsers/*"
participant Chunker as "chunker.js"
Caller->>Loader : loadDocument(filePath)
Loader->>Loader : 根据扩展名查找路由
Loader->>Parser : 动态导入并调用 parse(filePath)
Parser-->>Loader : 返回纯文本
Loader-->>Caller : { text, fileName }
Caller->>Chunker : chunkText(text, options)
Chunker-->>Caller : chunks[]含 startPos/endPos

图表来源

详细组件分析 ​

文档加载器(loader.js) ​

  • 功能职责:
    • 判断是否受支持:isSupportedFile(filePath)。
    • 列出支持的扩展名:supportedExtensions()。
    • 加载并解析文档:loadDocument(filePath) → Promise<{ text, fileName }>。
  • 路由机制:
    • 通过 extname 获取扩展名,查 PARSER_ROUTES 得到解析器名称。
    • 若未匹配,抛出 RagError('unsupportedFormat', { ext })。
  • 按需加载:
    • PARSERS 映射为延迟 import 函数,仅当实际用到对应格式时才加载依赖,减少冷启动开销。
  • 返回值:
    • 统一返回 { text, fileName },fileName 取自 basename(filePath)。
flowchart TD
Start(["开始"]) --> GetExt["获取扩展名"]
GetExt --> CheckRoute{"是否在路由表中?"}
CheckRoute --> |否| ThrowErr["抛出 RagError('unsupportedFormat')"]
CheckRoute --> |是| LazyImport["动态 import 对应解析器"]
LazyImport --> CallParse["调用 parse(filePath)"]
CallParse --> ReturnObj["返回 { text, fileName }"]
ThrowErr --> End(["结束"])
ReturnObj --> End

图表来源

章节来源

解析器(parsers) ​

  • text.js(纯文本):
    • 使用 fs/promises.readFile 读取 UTF-8 文本。
    • 去除 UTF-8 BOM(首字节 0xFEFF),避免影响 embedding 质量。
  • pdf.js(PDF):
    • 通过 createRequire 加载 CJS 包 pdf-parse,避免 ESM 直接 import 导致的调试分支问题。
    • 读取二进制 Buffer,调用 pdf-parse 提取 data.text。
  • office.js(Office):
    • docx:使用 mammoth.extractRawText 快速提取纯文本。
    • xlsx/xls:使用 xlsx 逐 sheet 转 CSV,并以“# SheetName”标题分隔,利于检索理解表格结构。
    • 其他(pptx/odt/ods/rtf 等):使用 officeparser.parseOffice 转为 AST 后导出文本。
classDiagram
class TextParser {
+parse(filePath) string
}
class PdfParser {
+parse(filePath) string
}
class OfficeParser {
+parseDocx(filePath) string
+parseSpreadsheet(filePath) string
+parseGeneric(filePath) string
+parse(filePath) string
}
OfficeParser --> TextParser : "不直接使用"
OfficeParser --> PdfParser : "不直接使用"

图表来源

章节来源

文本分块器(chunker.js) ​

  • 目标:
    • 在保持语义完整性的前提下,将长文本切分为适合嵌入模型的片段。
    • 为 BM25 混合检索预留原文偏移(startPos/endPos)。
  • 关键参数:
    • chunkSize:默认 400,范围限制在 [50, 2000]。
    • overlap:默认 80,最大不超过 chunkSize/2。
    • CHUNK_SIZE_WARN_LIMIT:超过此值会告警,防止被模型静默截断。
  • 断点优先级:
    • 优先在自然断点处收束:\n\n、\n、句号、感叹号、问号、分号及其英文变体。
    • 搜索区间为块后半段,从后往前找最近断点。
  • 边界处理:
    • 跳过块间空白,确保每个 chunk 非空。
    • 重叠回退保证不会死循环:next = end - overlap,若 next ≤ start 则取 end。
  • 复杂度:
    • 时间复杂度 O(n),其中 n 为文本长度;断点查找在最坏情况下为常数级多次扫描。
    • 空间复杂度 O(k),k 为生成的 chunk 数量。
flowchart TD
S["开始"] --> Normalize["规范化换行"]
Normalize --> Clamp["clamp(chunkSize, overlap)"]
Clamp --> Warn{"chunkSize > 安全上限?"}
Warn --> |是| LogWarn["记录告警日志"]
Warn --> |否| Loop["遍历文本"]
LogWarn --> Loop
Loop --> SkipWS["跳过空白"]
SkipWS --> SetEnd["end = min(start+chunkSize, n)"]
SetEnd --> FindBP{"未到文末?"}
FindBP --> |是| BP["在后半段寻找最近断点"]
FindBP --> |否| Slice["切片"]
BP --> Slice
Slice --> Push{"chunk 非空?"}
Push --> |是| Add["加入结果数组"]
Push --> |否| NextStep["计算下一起点"]
Add --> NextStep
NextStep --> More{"pos < n?"}
More --> |是| Loop
More --> |否| Return["返回 chunks[]"]

图表来源

章节来源

配置与扩展名路由(config.js) ​

  • 解析器扩展名路由表 PARSER_ROUTES:
    • .txt/.md/.json/.csv → text
    • .html/.htm → html
    • .pdf → pdf
    • .docx/.pptx/.xlsx/.xls → office
  • 默认分块配置 DEFAULT_CHUNK_CONFIG:
    • chunkSize=400,overlap=80。
  • 安全上限 CHUNK_SIZE_WARN_LIMIT:
    • 500,超过将触发告警。
  • RAG 根目录与环境变量:
    • RAG_ROOT 可由 AI_HELPER_RAG_ROOT 覆盖,便于测试隔离或自定义数据目录。

章节来源

错误处理(errors.js) ​

  • RagError:
    • 携带 code 与 params,便于上层 i18n 渲染。
  • translateRagError:
    • 将任意 Error 转为 { error, code },RagError 走 t(rag.err_${code}, params) 翻译。

章节来源

依赖关系分析 ​

  • loader.js 依赖:
    • path.extname/basename:路径处理。
    • config.PARSER_ROUTES:扩展名到解析器名称的映射。
    • errors.RagError:不支持格式的错误。
  • parsers:
    • text.js:fs/promises.readFile。
    • pdf.js:module.createRequire + pdf-parse(CJS)。
    • office.js:mammoth、xlsx、officeparser(按需 require/import)。
  • chunker.js 依赖:
    • config.CHUNK_SIZE_WARN_LIMIT:安全上限告警。
graph LR
Loader["loader.js"] --> Routes["config.PARSER_ROUTES"]
Loader --> Errors["errors.RagError"]
Loader --> P_Text["parsers/text.js"]
Loader --> P_Pdf["parsers/pdf.js"]
Loader --> P_Office["parsers/office.js"]
Chunker["chunker.js"] --> Config["config.CHUNK_SIZE_WARN_LIMIT"]

图表来源

章节来源

性能与内存优化 ​

  • 按需加载解析器:
    • loader.js 中 PARSERS 使用箭头函数延迟 import,仅在需要时加载对应依赖,降低冷启动与常驻内存占用。
  • 大文件处理:
    • PDF 与 Office 解析均先读取整个文件到内存(Buffer 或字符串),对于超大文件建议在上层做大小限制与分批处理。
    • chunker 对文本进行线性扫描,时间复杂度 O(n),无额外复杂数据结构,内存占用与输出 chunk 数成正比。
  • 分块安全边界:
    • chunkSize 被 clamp 到 [50, 2000],且超过 CHUNK_SIZE_WARN_LIMIT 会告警,避免模型静默截断导致内容丢失。
    • overlap 控制在合理范围,避免重复过多影响检索效率。
  • 文本预处理:
    • text.js 去除 UTF-8 BOM,提升 embedding 质量。
    • chunker 统一换行为 \n,提高断点查找稳定性。

[本节为通用指导,无需特定文件引用]

故障排查指南 ​

  • 不支持的文件格式:
    • 现象:调用 loadDocument 抛出 RagError('unsupportedFormat', { ext })。
    • 排查:检查文件扩展名是否在 PARSER_ROUTES 中;必要时新增路由映射。
  • PDF 解析失败:
    • 现象:pdf-parse 报错或返回空文本。
    • 排查:确认文件可读、非损坏 PDF;注意 pdf-parse 为 CJS,必须通过 createRequire 加载。
  • Office 解析异常:
    • 现象:docx/xlsx/pptx 解析失败或文本为空。
    • 排查:确认依赖已安装;xlsx 需逐 sheet 转 CSV;pptx 等走 officeparser。
  • 分块过大告警:
    • 现象:控制台输出 chunkSize 超过安全上限的警告。
    • 排查:调整 chunkSize 至默认 400 附近,确保不超过 CHUNK_SIZE_WARN_LIMIT。
  • 错误国际化:
    • 现象:错误消息未翻译或显示原始 message。
    • 排查:上层应使用 translateRagError(err, t) 将 RagError 转为可翻译对象。

章节来源

结论 ​

该文档处理与解析模块以 loader 为统一入口,通过扩展名路由到不同解析器,实现多格式文档到纯文本的统一转换;chunker 在此基础上提供稳定可控的分块能力,兼顾中文语义完整性与模型 token 上限。整体架构清晰、依赖按需加载、错误类型规范,便于扩展与维护。针对大文件场景,建议在上层增加大小限制与批处理策略,并结合 chunker 的安全上限告警进行调优。

[本节为总结性内容,无需特定文件引用]

附录 ​

支持的文件格式列表 ​

  • 文本类:.txt、.md、.json、.csv
  • HTML:.html、.htm
  • PDF:.pdf
  • Office:.docx、.pptx、.xlsx、.xls

章节来源

配置选项说明 ​

  • RAG_ROOT:RAG 数据根目录,可通过环境变量 AI_HELPER_RAG_ROOT 覆盖。
  • DEFAULT_CHUNK_CONFIG:默认分块参数(chunkSize=400,overlap=80)。
  • CHUNK_SIZE_WARN_LIMIT:分块安全上限(500),超过将告警。
  • PARSER_ROUTES:解析器扩展名路由表,决定各扩展名对应的解析器名称。

章节来源