Ch39: 挑战深读②——文档解析天花板
Part 10 · 对应挑战 #2 · challenges.md §2 前置:Ch34 RAGFlow 源码 · 实验语料:parse-pitfalls.md
结论先行:解析天花板 = 格式无限 × 解析一致 impossible。GIGO(Garbage In, Garbage Out)在 RAG 里被放大——噪声进 chunk、进向量、进回答,Rerank 救不了被撕碎的表格。大师基线:Level 2 结构理解(版面+表格)为生产默认;Level 3 页级 VLM 按需;噪声必须在 parse 层最早过滤。
一、日常类比
搬家收到一堆不同规格包装箱:有的标签清晰(结构化 docx),有的只写「杂物」(纯 PDF 文本流),有的外语标签(扫描件),有的箱中套箱(嵌套表格),有的糊了旧报纸(水印印章)。你要统一编目入库——编目质量完全取决于能从每个箱子提取多少真实信息。RAG 的 docreader/docparser 就是那套编目流水线;编错一次,后面检索再强也是搜错内容。
二、解析三层次(Level 1–3)
Level 1: 文本提取 —— 能不能把字读出来
└─ 多数系统都能做;扫描 OCR 错误率 5–15%;倾斜/印章/低分辨率级联放大
Level 2: 结构理解 —— 标题/段落/表格/图注是否还原
└─ RAGFlow DeepDoc、WeKnora IMA 的主战场
└─ 双栏 PDF 文本流乱序、Word 嵌套表、PPT 绝对坐标 —— 本质是视觉推理
Level 3: 语义理解 —— 「这张表说明什么业务风险?」
└─ 目前没有项目可靠做到;需 VLM,成本高
└─ 净利率降 5% 是波动还是风险 —— 要领域知识,非 parse 能独力解决
大师选型:默认停在 L2;仅当 L2 输出仍无法满足 QA(扫描公式、复杂图)时对 单页 调 VLM。Dify/FastGPT/AnythingLLM 多数停在 L1,复杂排版靠 Unstructured/PyPDF2,质量参差。
| 项目 | 解析深度 | 典型代价 |
|---|---|---|
| RAGFlow DeepDoc | L2 极致,GPU 吃紧 | 100 页 PDF 数分钟 |
| WeKnora IMA + docreader | L2,偏中文实体抽取 | Python docreader ~513K 行 |
| Dify / FastGPT / AnythingLLM | L1 为主 | 人工审核或外部 txt2vec |
三、Defense in Depth 管线
解析不是单点函数,而是 纵深防御——每一层拦截一类失败,避免漏到 chunker:
① 格式路由 按 mime/扩展名选 parser(Excel≠PDF≠MHTML)
② 结构恢复 版面顺序、表格 rowspan/colspan、公式 LaTeX
③ 噪声过滤 IMAGE/DISPIMG、页眉页脚、水印 OCR 垃圾
④ chunk 契约 输出格式满足下游 protectedPatterns(MD 表 / HTML 边界)
⑤ 检索仍失败 按需 VLM 页级理解(贵,非默认)
原则:能在 ③ 过滤的绝不要期望 ⑤ Rerank 过滤;能在 ② 还原结构的绝不要在 ④ 用滑动窗口硬切表。实验语料 parse-pitfalls.md 把三条 WeKnora 真 issue 写成 可复现陷阱清单,贡献 docreader 时应先对照该文件写 pytest。
四、三类文档陷阱(challenges.md 浓缩)
PDF 文本流陷阱:双栏 PDF 左栏末行与右栏首行在文本流中相邻,不做版面分析则左右栏交叉,产出不可读字符串。
Word 嵌套表:docx XML 允许表套表,深度 20+;多数解析器 展平 内层表,行列语义可能全错。
扫描级联错误:图像差 → OCR 错字 → 分词错 → BM25 漏召 → 向量语义偏移 → 每一步放大上一步误差。
PPT 的绝对坐标、手写体、印章遮挡等仍在 L2 边界外——贡献前先确认 issue 属于哪一层,避免用 VLM 修 L1 OCR 能解决的问题。
五、WeKnora 实战三联:1785 / 1725 / 1743
| Case | 层级 | 现象 | 修复要点 |
|---|---|---|---|
| #1785 | ③ 噪声 | WPS Excel 嵌入图 → cell 纯文本 DISPIMG("ID_xxx",1) 进回答 |
excel_parser.py 最早阶段正则过滤 IMAGE 变体;data_only=True 对伪函数无效 |
| #1725 | ②→④ 契约 | PaddleOCR-VL 输出 HTML 表;chunker 只保护 \| pipe \| 表 |
html_table_normalizer.go:简单表→MD,复杂表剥 style;否则表超 ChunkSize 拦腰切断 |
| #1743 | ①② 跨语言 | MHTML 内嵌图降采样/链接在 chunk 边界断裂 | Python 状态机保留 MIME+分辨率;Go 侧 ![...]() 整段保护 |
1785 方法论:RAG 噪声过滤必须在 最早解析层,不是下游检索兜底——与 #1248 反馈归因同属「边界识别」问题。
1725 方法论:上游输出格式必须满足下游 保护条件;HTML token 约为 Markdown 同内容的 2–3 倍,style 属性纯浪费 context。
1743 方法论:改 Python docreader 必须 追踪到 Go chunker 假设;跨语言 PR 要 Python 输出 → Go 输入集成测试。
六、平台对照与贡献切口
RAGFlow 最深,适合学 DeepDoc 表格模型与 GPU 部署 trade-off(Ch34)。WeKnora 贡献切口清晰:
- 中文双栏 PDF 阅读顺序、中文表格行列(微调版面模型 + 准确率 benchmark)
- docreader 参数化 pytest:每个 parse-pitfalls 条目一条测试
- docparser 与 chunker 格式契约 文档化(1725/1743 类 bug 的预防)
前沿:VLM 端到端页解析(RAGFlow 探索中)、Nougat PDF→MD(中文弱)。竞赛 PR 优先 L2 可量化改进 + fixture,而非直接上全库 VLM。
七、为什么检索层修不好「表被撕碎」
用户问「Q3 营收是多少?」——若表头在 chunk A、数据在 chunk B,向量可能只召回含数字的 B,LLM 看到 23.5 | 18.7 不知单位。Rerank 对单 chunk 打分,无法重建表头-数据组合;图谱路也无法从不完整字符串抽实体。唯一正解在 ingest:整表保留 + 表头同行(见 Ch40 与 Ch27)。
章末自检
- 解释 GIGO 在 RAG 四段管线(parse→chunk→embed→retrieve)中如何级联
- 默写 Defense in Depth 五步并各举一条 1785/1725/1743 对应
- 说明 Level 2 与 Level 3 的边界:何时才值得 VLM
- 打开 parse-pitfalls.md,复述 Excel 与 HTML 表的两条修复原则