主题
文档处理与解析
本文引用的源码文件
本文引用的文件
目录
引言
本模块负责将多种格式的本地文档统一转换为纯文本,并进一步切分为适合嵌入检索的分块。其设计目标是:
- 支持多格式加载:文本、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
图表来源
- 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/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L1-L70
核心组件
- 文档加载器(loader):根据文件扩展名选择解析器,动态 import 对应模块,返回统一结构。
- 解析器(parsers):针对不同格式实现 parse(filePath) → Promise<string>。
- 分块器(chunker):对纯文本执行段落优先切分,控制 chunkSize 与 overlap,保留原文偏移。
- 配置(config):集中管理 RAG 根目录、默认分块参数、解析器扩展名路由表。
- 错误(errors):定义 RagError 与 translateRagError,用于上层响应层国际化。
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L37-L69
- agent/src/rag/errors.js#L1-L30
架构总览
下图展示从文件路径到最终分块的完整流程: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
图表来源
- agent/src/rag/document/loader.js#L21-L48
- agent/src/rag/config.js#L56-L69
- agent/src/rag/errors.js#L5-L16
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/config.js#L56-L69
- agent/src/rag/errors.js#L1-L30
解析器(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 : "不直接使用"
图表来源
- agent/src/rag/document/parsers/text.js#L10-L14
- agent/src/rag/document/parsers/pdf.js#L15-L19
- agent/src/rag/document/parsers/office.js#L15-L55
章节来源
- agent/src/rag/document/parsers/text.js#L1-15
- agent/src/rag/document/parsers/pdf.js#L1-21
- agent/src/rag/document/parsers/office.js#L1-56
文本分块器(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) 翻译。
- 将任意 Error 转为 { error, code },RagError 走 t(
章节来源
依赖关系分析
- 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"]
图表来源
- agent/src/rag/document/loader.js#L4-L14
- agent/src/rag/config.js#L56-L69
- agent/src/rag/errors.js#L5-L16
- agent/src/rag/document/chunker.js#L7-L8
章节来源
- agent/src/rag/document/loader.js#L1-L49
- agent/src/rag/document/chunker.js#L1-L76
- agent/src/rag/config.js#L56-L69
- agent/src/rag/errors.js#L1-L30
性能与内存优化
- 按需加载解析器:
- 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 转为可翻译对象。
章节来源
- agent/src/rag/document/loader.js#L38-L47
- agent/src/rag/document/parsers/pdf.js#L15-L19
- agent/src/rag/document/parsers/office.js#L50-L55
- agent/src/rag/document/chunker.js#L36-L41
- agent/src/rag/errors.js#L24-L29
结论
该文档处理与解析模块以 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:解析器扩展名路由表,决定各扩展名对应的解析器名称。
章节来源