支持 20+ 文档格式、
Docling Serve + fallback loaders、异步任务、结果持久化与预览。我想解决的不是“文件能不能传上来”,而是“文档能不能被稳定、标准化地接入后续 RAG 链路”。
上一篇我先整体介绍了 RAG Pipeline Hub 这套工程化 RAG 工作台。这一篇开始,正式进入模块拆解。
我选择先写“文档加载”,原因很简单:很多人以为 RAG 的核心是 Embedding 和检索,但真正决定下游质量上限的,往往是文档加载。
如果入口阶段就把文档解析坏了、结构丢了、表格没了、图片上下文没了,后面无论你分块多精细、向量模型多高级、检索链路多复杂,最后都只能在低质量输入上做优化。
所以这一篇我想聊的不是“怎么上传文件”,而是:
在一个面向真实业务流程的 RAG 项目里,文档加载模块到底应该解决什么问题,以及我在这个项目里是怎么设计它的。
项目地址:
- GitHub:
https://github.com/qingni/rag-pipeline-hub
为什么文档加载经常被低估
很多 RAG 项目的起手式都很类似:
- 上传 PDF
- 提取纯文本
- 直接切块
- 送去向量化
这套流程对“验证一个最小可行链路”没问题,但一旦进入真实场景,文档加载马上就会变成一个高频踩坑点。
因为你面对的从来不是一种文档,而是各种杂合输入:
- 结构规整的 PDF
- 扫描件 PDF
- Word 文档
- Excel / 多 Sheet 表格
- PPT / 旧版 Office 文档
- HTML 页面
- JSON / XML / CSV
- TXT / Markdown
不同格式的问题完全不同:
- 有的文本好提,但表格结构容易丢
- 有的文档页面信息很重要,但纯文本抽取会打平
- 有的文件本身是扫描件,没有 OCR 根本拿不到可用内容
- 有的内容里图片是关键上下文,但传统加载只会直接忽略
- 有的文档很大,如果同步解析,接口很容易阻塞
所以文档加载的问题,从来不只是“读文件”,而是:
- 如何稳定支持多格式
- 如何在解析质量和系统稳定性之间做平衡
- 如何把不同来源的结果统一成后续模块可消费的结构
- 如何让用户看到解析结果,而不是只相信一个黑盒过程
这也是我把文档加载作为整个系列第一个模块来展开的原因。
文档加载模块到底在负责什么
在 RAG Pipeline Hub 里,我给文档加载模块的定义不是“上传模块”,而是:
把多种异构文件,转换成统一标准化文档结果的入口模块。
这个定义里有两个关键词很重要。
1. 异构文件
输入不是单一格式,而是各种不同结构、不同复杂度、不同解析方式的文档。
2. 统一标准化结果
输出不能是“每种加载器返回各自一套格式”,而必须变成统一结构,供后续分块、向量化、检索复用。
所以这个模块真正要交付的,不是“上传成功”,而是下面这些内容:
- 可复用的标准化文本结果
- 页级信息
- 表格、图片、公式等结构化元素
- 文档元数据
- 处理统计信息
- 可预览、可持久化、可追踪的解析结果
只有这样,后面的分块模块才能基于稳定输入做策略选择,向量化模块也才能基于统一结果做批处理和推荐。
真实项目里,文档加载的难点到底是什么
如果从工程实现的角度看,我觉得这个模块至少有 4 个核心难点。
1. 解析质量和稳定性是冲突的
高质量解析器通常更强,但也更重、更复杂,部署和调用成本更高。
轻量解析器通常更稳、更快,但在复杂文档、扫描件、表格、图片等场景下效果会明显下降。
这意味着你很难只靠一个加载器解决所有问题。
2. 文档格式多,最佳解析策略并不统一
PDF、DOCX、XLSX、HTML、JSON、TXT 的最佳解析方式本来就不同。
如果一套策略打天下,通常只能得到“平均能用,但不够好”的结果。
3. 大文件和复杂文档不能总走同步链路
解析一个复杂 PDF 或 Office 文档,时间可能远超普通 API 的舒适区间。
如果把这些工作都塞进同步请求里,系统吞吐、用户体验和失败率都会很难看。
4. 解析结果如果不落盘,就很难复用和调试
很多项目处理完文档后只把文本暂存在内存里,然后直接进入分块。
这种方式在 demo 阶段很省事,但一旦要排查问题、对比解析效果、给用户做预览,马上就会不够用。
所以对我来说,文档加载模块必须同时解决:
- 质量
- 稳定性
- 可扩展性
- 可观察性
我在这个项目里的设计目标
围绕上面这些问题,我给文档加载模块定了几个明确目标:
- 支持尽可能多的业务常见文档格式
- 复杂文档优先保证解析质量
- 主解析器失败时必须可降级
- 大文件和复杂解析支持异步处理
- 所有结果尽量统一成标准结构
- 解析结果可持久化、可预览、可复用
如果用一句话总结,就是:
我想把文档加载做成一个“稳定的标准化入口”,而不是一组彼此割裂的文件解析脚本。
当前这套加载链路是怎么设计的
从架构上看,这个模块大致分成 5 层:
- 前端层:上传、进度显示、文档列表、预览、结果展示
- API 层:暴露文档上传、加载、结果查询等接口
- Service 层:负责流程控制、解析策略选择、任务管理
- Loader 层:负责不同格式的具体解析实现
- 数据层:负责原始文档、数据库记录和 JSON 结果存储
可以把它理解成这样一条链:
用户上传文档
-> 根据格式与策略选择解析方式
-> 调用主解析器或专用加载器
-> 生成标准化结果
-> 保存到 JSON 结果文件和数据库
-> 前端展示预览,供下游分块模块继续消费
这里最关键的不是“层次多”,而是职责清晰。
- 前端负责交互,不承担解析逻辑
- API 负责暴露接口,不承载复杂判断
- Service 负责编排,不直接硬编码某一种解析方式
- Loader 负责具体能力实现,便于后续扩展和替换
- 数据层负责结果保存,为复用和调试提供基础
这种拆法最大的好处是,后面你无论是想接更多格式、替换主解析器、优化某类文档的加载效果,还是把加载结果接给其他模块,都不会把整个系统搅成一锅粥。
为什么我选择 Docling Serve + fallback loaders
这是这个模块最关键的设计点。
如果只说一句话,那就是:
高质量解析能力和系统可用性,我都要。
为什么需要 Docling Serve
对于复杂文档,尤其是:
- 结构复杂的 PDF
- 需要 OCR 的扫描件
- 包含表格的文档
- Office 文档
- HTML 和图片类内容
一个高质量主解析器带来的提升非常明显。
在这个项目里,我选择用 Docling Serve 作为主解析器,原因主要有几个:
- 独立 HTTP 服务模式,避免把重型解析能力直接塞进 Backend 进程
- 支持同步和异步调用
- 支持 OCR
- 对复杂格式的统一处理能力更强
- 更适合作为复杂文档的默认入口
简单说,它更适合承担“主战场”的工作。
为什么还必须要有 fallback
但如果工程上只依赖一个主解析器,也会有很现实的问题:
- 服务可能不可用
- 某些格式解析可能失败
- 某些简单文档没必要走重链路
- 某些环境可能不方便部署完整解析服务
所以我没有把 Docling Serve 当成唯一方案,而是把它放进一套“主解析器 + 专用加载器降级链”里。
当前项目里,不同格式会走不同主策略:
PDF / DOCX / DOC / PPT / 复杂格式优先走Docling ServeXLSX / XLS / PPTX会根据格式策略走更合适的原生解析器或补充链路HTML / JSON / CSV / TXT / Markdown / XML直接走专用加载器- 主解析器失败时,再按 fallback 链自动降级
也就是说,这不是一个“单解析器架构”,而是一个解析策略架构。
这是我觉得很多文档处理系统最容易被忽视的一点:
真正需要设计的,不只是某个 loader,而是在不同文档场景下,谁优先、谁兜底、谁适合直接处理。
异步处理为什么非常重要
文档加载还有一个很容易被忽视的问题:不是所有解析任务都适合同步完成。
尤其是下面这些场景:
- 大文件 PDF
- 扫描件 OCR
- 复杂 Office 文档
- 带大量结构化元素的文件
如果这些任务都在接口里同步执行,常见后果就是:
- 请求时间过长
- FastAPI 工作线程被占住
- 用户只能一直等
- 错误恢复和进度反馈都很差
所以在这个项目里,文档加载模块支持异步任务模式。
以 Docling Serve 为例,它会先提交异步转换任务,返回 task_id,后续由 LoadingService 负责轮询状态、获取结果并完成保存。
这背后的好处很直接:
- 避免在 API 请求里长时间阻塞
- 更容易展示进度和任务状态
- 更适合处理大文件和慢任务
- 方便做超时控制和失败恢复
很多时候,异步能力本身并不会直接提升“解析质量”,但它会显著提升系统在真实使用中的可用性。
为什么我坚持把结果标准化并持久化
这个模块还有一个我很在意的设计点:解析结果不是一次性中间变量,而是可复用的数据资产。
如果加载完马上进分块,当然也能跑通,但你会失去很多非常关键的能力:
- 不能方便地做解析结果预览
- 不能快速回看某次加载到底抽取了什么
- 不能对比不同加载器效果
- 不能让分块模块直接复用已有结果
- 不能积累历史记录和统计信息
所以这个项目会把加载结果保存成 JSON 文件,同时在数据库中保留文档信息和处理结果记录。
这意味着文档加载的输出不只是文本,而是一套更完整的标准结果,里面通常包括:
full_textpagestablesimagesmetadataprocessing stats
这么做的价值很大:
- 前端可以直接预览解析结果
- 分块模块可以直接基于结果继续处理
- 出问题时可以回溯具体哪一步解析出了问题
- 后续如果要做评估、对比、推荐,也有基础数据可用
这也是为什么我一直强调:
文档加载模块不是“上传成功”,而是“生成标准化结果成功”。
文档预览为什么不是锦上添花,而是必要能力
如果你做过 RAG 调优,就会发现一个很常见的痛点:
明明最后问答效果不好,但你不知道问题到底出在:
- 文档没解析对
- 表格丢了
- 页级结构丢了
- 图片上下文没保住
- 某些字段被错误清洗了
所以我认为文档加载模块一定要支持预览。
在这个项目里,前端会提供:
- 文档上传
- 文档列表
- 进度展示
- 文档预览
- 结果预览
也就是说,加载模块不是一个后台黑盒任务,而是一个用户能看见结果、能确认质量、能继续决策下一步的工作台。
这件事对后续模块特别重要。
因为只有用户看见了解析效果,才知道接下来该选什么分块策略、是否要重试、是否要更换加载器,或者是否要调整文档来源。
这一层和“普通上传功能”到底有什么区别
如果只看表面,很多人会觉得这个模块无非就是“上传文件 + 解析一下”。
但如果按照工程视角看,它和普通上传功能的差别其实很大。
普通上传关注的是:
- 文件有没有成功传上来
- 文件放在哪里
- 文件元信息有没有记录
而这里的文档加载关注的是:
- 该用什么解析策略
- 是否需要高质量主解析器
- 主解析器失败后怎么降级
- 结果能不能统一结构
- 能不能异步处理
- 能不能预览
- 能不能持久化
- 能不能给下游模块稳定复用
所以它更像一个“文档标准化处理中心”,而不是一个上传控件。
这个模块当前最值得关注的几个点
如果只挑几个最能代表这一层价值的能力,我会选这些:
1. 多格式接入
支持常见业务文档格式,为真实场景打基础。
2. Docling Serve + fallback
主解析器负责质量,fallback 负责可用性。
3. 异步任务与进度追踪
让复杂文档解析更适合真实系统,而不是只适合本地 demo。
4. 结果标准化
把不同来源、不同格式的解析结果统一成后续模块可消费的结构。
5. 结果持久化与预览
让加载模块从“黑盒处理”变成“可观察、可回溯、可复用”的工作台。
我对这个模块的一个核心判断
如果只让我总结一句话,我会说:
RAG 的第一步不是上传文件,也不是调用 Embedding,而是把文档稳定、完整、标准化地变成后续链路可以信任的输入。
这件事做不好,后面所有优化都很容易变成“在脏数据上做精细调参”。
这也是我为什么愿意在文档加载这一层投入这么多工程设计的原因。
它看起来不像检索、Reranker、Prompt 那么“AI 味”浓,但它决定了整条链路的地基是否稳。
下一篇写什么
文档加载解决的是“输入从哪里来,以及怎么标准化”。
接下来的关键问题就是:
这些标准化结果,应该怎么切,才能更适合后续检索?
所以下一篇我会继续写:
《Chunk 不只是 chunk_size:我在 RAG 里做了 6 种分块策略和智能推荐》
项目地址:
- GitHub:
https://github.com/qingni/rag-pipeline-hub
如果这篇文章对你有帮助,欢迎:
- 点个
star - 提个
issue - 留言说说你最常踩坑的文档格式
如果你也做过文档解析、OCR、表格提取或多格式加载,欢迎交流你踩过的坑。我很确定,这一层远比很多人想象中更值得认真做。