
1. RAG 管线里最容易被低估的“脏活累活”做过 RAG 项目的人都有一个共同体会模型选型、向量库调参、检索策略优化这些环节大家讨论得热火朝天但真正让整个管线跑不顺的往往是文档解析这一步。你拿到一份 80 页的 PDF 招标文件里面有跨页表格、有页眉页脚、有扫描件混排、有章节层级嵌套如果解析出来的文本是一锅粥后面 embedding 再强、rerank 再精细检索命中率也上不去。RAG 的核心链路是“文档进、答案出”而文档解析就是这条链路的入口。入口如果堵了后面全是白费功夫。IBM 开源的Docling就是冲着这个痛点来的——它试图用一个统一的工具把 PDF、DOCX、PPTX、HTML、图片等多种格式的文档解析成结构化的、带页码和章节信息的统一表示直接喂给下游的 RAG 管线。这篇文章不讲空泛的概念我会从 RAG 管线的实际痛点出发拆解 Docling 的设计思路、核心能力、实操流程以及我在实际项目中踩过的坑。适合正在做 RAG 项目、被文档解析折磨过的工程师也适合刚接触 RAG 想了解“入口环节怎么做”的新手。2. 为什么文档解析是 RAG 管线的真正瓶颈2.1 检索命中率上不去的根因往往不在检索层很多人做 RAG 优化第一反应是换 embedding 模型、调 chunk size、加 rerank。这些当然有用但如果你用的是一个解析质量很差的管线这些优化都是在补救上游的损失。举个实际例子。一份合同 PDF里面有这样的内容第三条 付款方式 3.1 甲方应在合同签订后 30 日内支付合同总额的 30% 作为预付款。 3.2 尾款应在项目验收合格后 60 日内结清。如果解析工具把页眉“XX 项目合同”和页脚“第 5 页 共 20 页”混进了正文又把 3.1 和 3.2 的编号丢了chunk 出来就是一段没有层级、没有编号的散文本。用户问“预付款比例是多少”检索层可能匹配到“30%”这个数字但无法确认它属于“预付款”还是“尾款”因为上下文结构已经丢了。这就是结构信息丢失的代价。RAG 不只是“找到相关文本”还要“理解文本在文档中的位置和角色”。页码、章节号、段落层级、表格结构这些都是帮助 LLM 准确定位答案的关键信号。2.2 多格式文档的统一处理是个工程噩梦真实项目里知识库的来源从来不是单一格式。你可能同时要处理产品手册PDF带大量图表和跨页表格内部制度DOCX有复杂的多级标题培训材料PPTX每页信息密度低但页数多网页存档HTML嵌套 div 结构混乱扫描件图片型 PDF需要 OCR如果每种格式用一套工具、一套后处理逻辑维护成本会迅速失控。更麻烦的是不同工具输出的文本结构不一致下游 chunk 策略没法统一检索质量波动很大。Docling 的思路是不管你喂什么格式输出都是同一套结构化文档对象模型。页码、章节、段落、表格、图片都有统一的表示。这样下游的 chunk、embedding、检索逻辑只需要针对一种数据结构来写工程复杂度大幅降低。2.3 表格和版面分析是分水岭普通文本提取工具比如很多基于 pdfminer 的方案能抽出文字但面对表格就歇了。而 RAG 场景里表格往往是信息密度最高的部分——财务数据、参数对比、责任划分全在表格里。一个合格的文档解析工具必须能做到识别表格边界把单元格内容按行列结构还原处理跨页表格把断开的表格拼接起来区分正文和页眉页脚、脚注、边栏保留阅读顺序而不是按 PDF 内部的绘制顺序输出这些能力决定了 RAG 能不能回答“表格里第三行第二列是什么”这类问题。Docling 在版面分析和表格结构还原上下了不少功夫这也是它区别于简单文本提取工具的核心。3. Docling 的核心设计思路拆解3.1 统一文档对象模型一次解析多处复用Docling 最核心的设计是它的文档对象模型DoclingDocument。不管你输入的是 PDF、DOCX 还是 HTML解析后都变成一个树状结构节点类型包括章节标题带层级正文段落表格带行列结构图片带位置和说明列表有序/无序页码和页面边界信息这个模型的好处是下游任务可以基于同一套结构做不同的事情。比如做 chunk 时可以按章节切分保证每个 chunk 不跨章节做检索时可以把章节标题作为上下文拼进 chunk做引用时可以直接定位到页码和章节号做表格问答时可以把表格转成结构化文本或 Markdown我试过在同一个知识库项目里用 Docling 的输出同时支持了“段落检索”和“表格检索”两条链路不需要为表格单独写一套解析逻辑。这个统一性带来的维护便利在实际项目中非常值钱。3.2 版面分析 OCR 的组合拳Docling 的解析流程大致分几步页面级版面分析识别每一页上的文本块、表格、图片、页眉页脚区域阅读顺序推断确定这些块应该按什么顺序阅读多栏排版时尤其重要表格结构识别把表格区域还原成行列结构文本提取或 OCR对文本型 PDF 直接提取对扫描件走 OCR结构组装把页面级结果拼成完整的文档树这个流程里版面分析是关键。很多工具失败就失败在把多栏排版的文档按单栏顺序输出导致句子错乱。Docling 用了基于深度学习的版面分析模型对复杂版面的处理明显好于传统基于规则的方法。注意OCR 环节对扫描件的解析质量影响很大。如果扫描件本身清晰度差、有倾斜或噪点OCR 错误会直接传导到下游。实际项目中我建议对扫描件先做预处理去噪、纠偏、提高对比度再喂给解析工具。3.3 为什么选择开源而不是调 API市面上有不少文档解析的云服务按页收费效果也不错。但 Docling 选择开源解决的是几个实际问题数据隐私很多企业的合同、招标文件、内部制度不能上传到外部服务成本可控大批量文档解析时按页收费的成本会迅速累积可定制开源意味着你可以针对自己的文档特点调整解析策略可离线内网环境、边缘设备上也能跑这不是说云服务不好而是说在 RAG 项目里文档解析往往是高频、大批量的操作本地化、可定制的方案在长期来看更划算。Docling 支持本地运行也支持容器化部署对工程团队比较友好。4. 从安装到跑通Docling 实操全流程4.1 环境准备与安装Docling 是 Python 包安装比较直接。我建议用虚拟环境避免依赖冲突。python -m venv docling-env source docling-env/bin/activate # Windows 用 docling-env\Scripts\activate pip install docling如果你需要 OCR 支持还需要安装额外的依赖。Docling 默认可能不带 OCR 模型需要根据文档类型决定是否启用。# 安装带 OCR 支持的版本具体包名以官方文档为准 pip install docling[ocr]提示OCR 模型体积较大首次运行时会下载。如果在内网环境需要提前把模型文件准备好或者配置本地模型路径。安装完成后可以用一个简单脚本验证from docling.document_converter import DocumentConverter converter DocumentConverter() result converter.convert(test.pdf) print(result.document.export_to_markdown())如果能看到结构化的 Markdown 输出说明基础环境没问题。4.2 解析一份复杂 PDF 的完整过程我拿一份实际的招标文件来演示。这份文件有 60 多页包含多级标题、跨页表格、页眉页脚和扫描件混排。第一步初始化转换器。Docling 允许你配置解析选项比如是否启用 OCR、是否做表格结构识别。from docling.document_converter import DocumentConverter, PdfFormatOption from docling.datamodel.pipeline_options import PdfPipelineOptions pipeline_options PdfPipelineOptions() pipeline_options.do_ocr True pipeline_options.do_table_structure True converter DocumentConverter( format_options{ pdf: PdfFormatOption(pipeline_optionspipeline_options) } )第二步执行转换。result converter.convert(tender_document.pdf) doc result.document第三步检查解析结果。我通常会先看几个关键指标总页数是否和原文件一致章节标题是否被正确识别表格是否被还原成结构化数据页眉页脚是否被排除在正文之外# 导出为 Markdown 查看整体结构 markdown_output doc.export_to_markdown() with open(output.md, w, encodingutf-8) as f: f.write(markdown_output) # 查看文档中的表格数量 tables doc.tables print(f识别到 {len(tables)} 个表格) # 查看章节结构 for item in doc.iterate_items(): if item.label section_header: print(f标题: {item.text})4.3 把解析结果接入 RAG 管线解析只是第一步关键是怎么把结构化输出变成检索友好的 chunk。我的做法是按章节切分利用 Docling 输出的章节层级保证每个 chunk 不跨章节拼接上下文把章节标题作为前缀拼进 chunk增强检索时的语义匹配表格单独处理表格转成 Markdown 或结构化 JSON单独建索引保留页码信息每个 chunk 带上页码和章节号方便引用溯源def build_chunks(doc, max_chunk_size500): chunks [] current_section for item in doc.iterate_items(): if item.label section_header: current_section item.text elif item.label text: chunk_text f[{current_section}] {item.text} chunks.append({ text: chunk_text, section: current_section, page: item.prov[0].page_no if item.prov else None }) return chunks这个逻辑可以根据实际需求调整。比如对于长段落可以按句子边界进一步切分对于表格可以单独走一条索引链路。实操心得chunk 大小不要一刀切。正文段落可以按 300-500 字切表格建议整表保留或按行切分标题类内容可以单独存一份用于检索增强。我在项目里试过统一 500 字切分结果表格被切得七零八落检索效果很差。后来改成“正文按段落、表格按整表、标题单独存”命中率明显提升。5. 实际项目中的常见问题与排查技巧5.1 解析结果里混入了页眉页脚这是最常见的问题。很多 PDF 的页眉页脚在版面分析阶段没有被正确识别导致每页都混入“XX 公司内部文件”“第 X 页 共 Y 页”这类文本。排查思路检查 Docling 的版面分析是否把页眉页脚区域标记出来了如果误判率高可以在后处理阶段用规则过滤统计每页重复出现的文本行大概率是页眉页脚对于固定模板的文档可以配置区域排除# 简单的后处理过滤统计重复行 from collections import Counter lines markdown_output.split(\n) line_counts Counter(lines) # 出现在超过 50% 页面上的短行可能是页眉页脚 threshold total_pages * 0.5 suspicious [line for line, count in line_counts.items() if count threshold and len(line) 50]5.2 表格结构还原错误跨页表格、合并单元格、无边框表格是表格还原的三大难题。我的经验是跨页表格检查 Docling 是否把两页的表格识别为同一个表。如果没有需要在后处理阶段按表头匹配拼接合并单元格Docling 通常会输出合并信息但下游使用时要注意展开无边框表格这类表格对版面分析模型挑战最大可能需要针对性地微调或换用其他策略问题类型表现排查方法解决思路跨页表格断裂同一表格被拆成两个检查表格标题和表头是否重复按表头匹配拼接合并单元格丢失单元格内容错位对比原 PDF 和输出使用带合并信息的输出格式无边框表格漏检表格被当正文检查版面分析结果调整模型或后处理规则表格内容顺序错乱行列颠倒逐单元格对比检查阅读顺序推断逻辑5.3 OCR 质量不稳定扫描件解析质量波动大主要受扫描质量影响。我踩过的坑包括倾斜扫描导致 OCR 识别率骤降低对比度文档如传真件识别困难中英文混排时语言模型切换不及时应对策略预处理阶段做纠偏和二值化对关键文档人工抽检 OCR 结果考虑对 OCR 结果做后处理纠错比如用语言模型做拼写纠正注意不要盲目相信 OCR 的 100% 准确率。在合同、招标这类关键文档场景建议对解析结果做抽样人工校验尤其是数字和金额部分。5.4 解析速度与资源占用Docling 的深度学习模型在 CPU 上跑会比较慢。一份 60 页的 PDFCPU 上可能需要几分钟。如果文档量大建议使用 GPU 加速如果环境允许批量解析时做并行处理对已经解析过的文档做缓存避免重复计算from concurrent.futures import ProcessPoolExecutor def parse_document(file_path): converter DocumentConverter() result converter.convert(file_path) return result.document.export_to_markdown() with ProcessPoolExecutor(max_workers4) as executor: results list(executor.map(parse_document, pdf_files))6. 文档解析之外RAG 管线还有哪些坑6.1 Chunk 策略与解析结构的配合Docling 给了你结构但怎么用结构是另一回事。我见过不少项目解析做得很好但 chunk 策略还是按固定字数切把章节结构浪费了。正确的做法是让 chunk 策略感知结构标题不单独成 chunk而是作为上下文的锚点段落按语义边界切分不硬切表格整表保留或按行切分但带上表头列表项可以合并成一个 chunk保持完整性6.2 检索层的结构感知有了结构化 chunk检索层也可以做结构感知的优化。比如用户问“第三章讲了什么”可以优先检索章节标题用户问“表格里的数据”可以优先检索表格类 chunk用户问“付款条件”可以优先检索合同条款类章节这些优化都依赖于解析阶段保留的结构信息。如果解析阶段就把结构丢了检索层再聪明也补不回来。6.3 引用溯源与页码定位RAG 系统如果要做到“答案可溯源”页码和章节号是刚需。Docling 的输出里保留了页码信息这让引用溯源变得可行。我在项目里的做法是每个 chunk 都带上page_no和section_path检索命中后直接把页码和章节号返回给用户。用户点击引用可以跳转到原文对应位置。这个体验对合同、制度类知识库尤其重要。7. 一些个人体会和后续扩展方向Docling 不是银弹它解决的是“统一解析”这个问题但 RAG 管线的优化是系统工程。我在实际项目里的体会是解析质量决定了 RAG 的上限检索策略决定了能不能接近这个上限。如果你正在选型文档解析工具我的建议是先用你的真实文档做一轮测试重点看三个指标表格还原准确率、页眉页脚过滤效果、阅读顺序是否正确。这三个指标过关了再考虑接入 RAG 管线。后续如果想进一步优化可以考虑这几个方向把 Docling 的输出和 GraphRAG 结合用章节结构构建知识图谱对表格类内容单独建索引走结构化查询链路用解析出的章节标题做 query 扩展提升检索召回最后分享一个小技巧Docling 的输出是 Markdown 格式你可以直接把它喂给支持 Markdown 的 LLM 做摘要或问答省去很多格式转换的麻烦。我在几个小项目里就是这么干的效果比纯文本拼接好不少。