Agent Engineering 课程阅读
首页/课程六 · 多模态文档智能/全景与基线:文本 RAG 的天花板

在 GitHub 查看原文

本页目录

Lesson 00 — 全景与基线:文本 RAG 的天花板

本课目标:先量化「只吃纯文本的 RAG」在真实企业文档上有多瞎——扫描件全盲、表格丢结构、图表不可见——再立一份全程对照的基线,让后面每节课的收益有数字可指

学完你能回答面试官那句:「你们知识库连个扫描件都处理不了,算什么生产系统?」——多模态不是赶时髦,是这些真实失败逼出来的。


0. 这门课要解决什么(先看一个数字)

把一份再普通不过的企业文档——「云启科技制度与经营简报」(6 页 PDF,含扫描件/表格/图表/图文混排)——丢进 kb-qa 现状管线,跑 17 道golden 题:

内容类型 题数 现状能答对 通过率
纯文本 6 6 100%
表格 3 0 0% 🚫
扫描件 4 0 0% 🚫
图表 4 0 0% 🚫

🎯 核心认知:文本 RAG 的天花板就在这里——纯文本题 100% 通过,三类杀手题 0% 通过。差距不是「检索不够好」,而是这些内容压根没进语料:扫描页抽出 0 个字、表格数字被拍平成无结构的串、图表的数值根本不在文本层。这不是调参能修的,是管线的盲区。这门课九节课(L01–L09)就是逐个点亮这些盲区。

现状管线(只吃文本)看这份 PDF 的视角:
   ┌─────────────────────────────────────────────────────┐
   │  PDF  ──get_text()──▶  文本层  ──▶  切块/检索/生成   │
   │   │                   │                              │
   │   │  P1 公司简介      165字  ✅                       │
   │   │  P2 考勤制度      194字  ✅                       │
   │   │  P3 扫描件          0字  🚫 ← 整页是图,抽不到    │
   │   │  P4 薪酬表        170字  🚫 ← 数字在但结构丢了    │
   │   │  P5 营收图表       33字  🚫 ← 数值只在图片里      │
   │   │  P6 培训发展      169字  ✅                       │
   └─────────────────────────────────────────────────────┘
              三类杀手页 = 三种系统性的信息丢失

1. 企业知识库的真实文档构成

学员在 rag-lessons / ops-lessons 里处理的都是「干净的 .md / .txt」——那是教学用的理想数据。真实企业的知识库里,纯文本只占一小部分

文档类型 举例 占比(经验) text-only 管线的下场
扫描件 纸质合同扫描、盖章协议、历史档案 30-50% 文字渲染成图片,抽出 0 字,完全盲
带表格的文档 制度手册、薪酬表、报销标准、价目表 20-30% 表格被拍平成串行文字,行列对应丢失
含图表的文档 经营简报、季报、市场分析、KPI 看板 10-20% 图表是图片对象,数值不在文本层,完全不可见
图文混排 培训手册、产品白皮书、宣传材料 10-20% 文字能抽、图抽不到,图文关联断裂
纯文本 邮件导出、会议纪要、FAQ 10-20% ✅ 正常

💡 「占比」是基于多个企业知识库项目的经验估计,不是精确统计——不同行业差异很大(律所扫描件占大头、互联网公司纯文本多)。重点是没有任何一家企业的知识库是纯文本的

逐类分析现状管线的失败模式

① 扫描页 → 抽出空文本。 扫描件的本质:纸上的字被扫描仪拍成了位图,整页就是一张大图。PDF 里这页没有文本层(text layer),get_text() 返回空串。kb-qa 的 loader 看到 0 个字,直接跳过——这页的知识等于不存在。

② 表格 → 拍平成串行文字。 表格在 PDF 里是「一组带坐标的文字片段」——表头、单元格各是一个 text span。get_text() 按阅读顺序(或坐标顺序)把它们串成一坨:职级\n薪酬\n绩效\n基本工资\n岗位津贴\nP3\n12000\n3000...数字还在,但「P3 的基本工资是 12000」这个行列对应关系没了。LLM 拿到一串数字,分不清谁属于哪一列。

③ 图表 → 图片直接丢弃,完全不可见。 matplotlib 画的柱状图,在 PDF 里是一张图片对象。文字层只有标题和图注,柱子顶上的数值(1800/2400/2900)根本不在文本里。问「Q3 营收多少」,语料里搜不到任何数字——图表白做了。

三类失败,本质都是「信息形态不匹配」:
   扫描件:信息在【图像像素】里   → text-only 管线只看【文本层】   → 瞎
   表格:  信息在【二维结构】里   → text-only 管线只看【一维串】   → 乱
   图表:  信息在【图形编码】里   → text-only 管线只看【文字】     → 空

🎯 一句话:text-only RAG 不是「检索得不够准」,是一半的信息根本没进库。多模态文档智能要做的,就是把这三类信息「翻译」成检索和生成能消费的形态。


2. 多模态文档智能:五层全景图

这门课围绕一个五层架构展开,每节课填一块。先把全景立起来,后面每课开头都会回指这张图:

   ┌─────────────────────────────────────────────────────────────┐
   │              多模态文档智能系统(kb-qa v3)                   │
   ├─────────────────────────────────────────────────────────────┤
   │                                                             │
   │  ⑤ 溯源层    引用带 文档名+页码+区域(bbox),可回到原图核对   │ ← L06
   │      ▲                                                      │
   │  ④ 生成层    按 element_type 路由:文本直答/表格结构/图看图  │ ← L04,L05
   │      ▲                                                      │
   │  ③ 索引层    图片描述入向量库,metadata 带类型,命中后路由   │ ← L05
   │      ▲                                                      │
   │  ② 理解层    图表→VLM 结构化描述;扫描页→OCR 文本            │ ← L03,L04
   │      ▲                                                      │
   │  ① 解析层    PDF→带类型的 Element 流(text/table/image)     │ ← L01,L02
   │      │  每个 Element 带 type + page + bbox(全程携带)       │
   │      ▼                                                      │
   │   原始文档(扫描件/表格/图表/纯文本)                        │
   │                                                             │
   └─────────────────────────────────────────────────────────────┘
解决什么 对应课 试金石验证
① 解析层 把 PDF 拆成带类型和坐标的元素 L01 版面解析、L02 表格 表格题/扫描题 before/after
② 理解层 把图片/扫描变成机器可读 L03 OCR、L04 图表理解 扫描题/图表题 before/after
③ 索引层 让图片元素能被文字搜到 L05 多模态检索 图表题端到端
④ 生成层 按类型路由消费 L04/L05(贯穿) 引用带类型
⑤ 溯源层 引用回到原文档页码+区域 L06 引用溯源 引用可回溯

💡 为什么是五层而不是一个端到端模型? 因为每层的「翻译」成本和精度差异巨大:解析层(PyMuPDF)几乎免费,理解层(OCR/VLM)有真实成本。分层让我们能按元素类型分别路由——文本走免费的老路、表格走结构化、扫描走 OCR、图表走 VLM——把钱花在刀刃上。这就是本课要立的两条主线之一:成本-精度主线


3. 方案对比:三种处理多模态文档的路线

处理多模态文档,业界有三条路线。这不是「哪个最好」的单选题,是成本-精度-复杂度的三角权衡

路线 怎么做 精度 成本 复杂度 适合
全 VLM 逐页看 每页截图丢给 glm-4v-plus,让它直接读 🟢 高(版面理解强) 🔴 极高(按页/按图计费) 🟢 低(一个模型搞定) 文档量小、预算足、要最高精度
传统解析管线 PyMuPDF 抽文本 + pdfplumber 抽表格,不碰 VLM 🔴 低(扫描/图表盲) 🟢 极低(本地 CPU 免费) 🟠 中(要拼多个工具) 纯数字原生文档、无扫描无图表
按元素分类路由(本课程) 解析出元素类型,文本走老路、表格走结构化、扫描走 OCR、图表走 VLM 🟢 高 🟡 中(只对需要的元素花 VLM 钱) 🔴 高(要写分类+路由) 企业级混合文档(最现实)
三条路线的成本画像(处理 100 页混合文档的 VLM 调用次数):

   全 VLM 逐页看:   100 次调用(每页都看)    💰💰💰
   传统解析管线:      0 次调用(不看图)       💰
   分类路由(本课): ~15 次调用(只看图表/低置信扫描)  💰💰

🎯 本课程选「分类路由」的理由:企业文档是混合的——同一份简报里,纯文本页(白嫖本地解析)、表格页(结构化抽取免费)、扫描页(大部分本地 OCR 够用、少数升级 VLM)、图表页(必须 VLM)。一刀切全 VLM 是烧钱,一刀切全本地是赌运气,按类型路由才是工程答案。 这和 ops-L12 的「成本-质量方法论」一脉相承:不是越贵越好,是「每个决策点问一句,这笔钱值不值」。

为什么不直接上 MinerU / unstructured / PaddleOCR?

这些都是优秀的重型管线,但本课程只作方案对比的参照,不作为实现依赖

工具 定位 为什么不用作主依赖
MinerU 版面模型驱动的端到端解析 安装重(含 torch + 模型权重)、Windows 坑多、对教学不透明(黑盒)
unstructured 通用文档解析框架 依赖链长、底层还是调 pdfplumber/pypdf,多一层抽象不如直接用
PaddleOCR 全能 OCR 引擎 比 RapidOCR 重得多(含完整 Paddle 栈),pip 装不顺

💡 本课程的取舍:用轻量、pip 友好、CPU 可跑的工具(PyMuPDF + pdfplumber + RapidOCR)手写分类路由管线,把每个决策点讲透。理解了原理,迁移到 MinerU/PaddleOCR 只是换底层引擎,架构不变。先懂为什么,再选用什么。


4. 试金石:毒文档集 + 全程对照基线

这门课的方法论是「用一份固定的毒文档,逐课量 before/after」——不空谈「多模态很重要」,用数字说话。

4.1 毒文档长什么样

随课交付 data/multimodal_docs/company_briefing.pdf(6 页「云启科技制度与经营简报」),刻意构造四类杀手页:

类型 杀手点 现状管线抽到
P1 纯文本(公司简介) 对照基准 165 字 ✅
P2 纯文本(考勤制度) 对照基准 194 字 ✅
P3 扫描页(保密协议) 文字渲染成图片,无文本层 0 字 🚫
P4 复杂表格(薪酬等级表) 合并表头单元格,行列对应 170 字(乱序串)🚫
P5 图表页(季度营收柱状图) 数值只在 matplotlib 图里 33 字(无数值)🚫
P6 图文混排(培训发展) 正文+晋升阶梯图 169 字(图丢)🚫

17 道 golden 题golden_questions.json):纯文本 6 + 表格 3 + 扫描 4 + 图表 4。每题标了 categoryanswer_tokens(判定用)、source_page(溯源用,L06 会用上)。

💡 毒文档有生成脚本generate_poison_pdf.py),删掉 PDF 重跑能生成一致的成品——保证全程对照的可复现性。扫描页的造法:先在临时文档排好文字 → 渲染成 150dpi 位图 → 整页嵌入图片(无文本层)。这是模拟扫描件最忠实的方式。

4.2 基线结果(L00 跑出来、存档、后面每课对照)

分类       题数   通过   通过率
text        6      6     1.00   ✅ 纯文本没问题
table       3      0     0.00   🚫 数字在但结构丢失(串行化)
scan        4      0     0.00   🚫 整页是图,抽不到字
chart       4      0     0.00   🚫 数值只在图片里
─────────────────────────────────
总体              6/17   0.35

存档在 baseline_multimodal.json。后面每课落地一个机制,就重跑对应题型,看通过率怎么爬: - L02(表格结构化)→ table 从 0% 爬 - L03(OCR)→ scan 从 0% 爬 - L04-L05(图表理解+检索)→ chart 从 0% 爬 - L08 出最终收益表,对照这份基线。

🎯 这份基线是整门课的锚点。 面试时你说「我做了多模态升级」,面试官问「收益多少」——你能掏出这张表:每类题从 0% 爬到多少,有数字。比说「效果很好」强一百倍。

4.3 关于基线评分的诚实说明

本课的基线评分是关键词命中(答案的 token 是否出现在该页文本层里),不是真 RAG 的检索+生成评分

评分方式 本课(mock) 真 RAG(L08)
检索 不评(只看 token 在不在语料) ragas context_recall/precision
生成 不评(无 LLM) ragas faithfulness/answer_relevancy
用途 量化「信息有没有进库」的天花板 量化端到端答案质量

⚠️ 诚实标注:mock 评分的意义是暴露天花板——扫描/图表题的答案 token 根本不在文本层,连「进库」都做不到,更别提检索和生成。这比跑真 RAG 更直接地证明了问题所在:不是检索烂,是信息没进库。真 RAG 评分在 L08 用 ragas 跑(需 API key),本课的 mock 基线全程离线可复现。


5. 本课代码会做什么

code.py(教学,可独立跑,零 API)

  • ① 用 PyMuPDF 逐页抽文本层,打印每页字符量 + 图片数 + 失败模式(扫描页≈0、表格乱序、图表无数值)
  • ② 把全文本拼成语料,对 17 道 golden 题做关键词命中判定(按页判定,避免跨页误伤)
  • ③ 逐题打印「哪些挂了、为什么挂」,存档 baseline_multimodal.json

落地到 kb-qa

  • 本课不动 kb-qa 代码——只新增 data/multimodal_docs/(毒文档 + 生成脚本 + golden 题 + 基线档案)。L00 的角色是立靶子,从 L01 起才改 kb-qa。

6. 跑起来

教学代码(独立可跑,零外部依赖)

# 0) 先生成毒文档(如果 data/multimodal_docs/company_briefing.pdf 不存在)
cd data/multimodal_docs
python generate_poison_pdf.py          # 产出 company_briefing.pdf(6 页)

# 1) 跑基线
cd doc-intelligence-lessons/00_baseline
python code.py

预期输出:

P3   扫描页(保密协议)        0      1    🚫 完全无文本(扫描)
P4   表格页(薪酬表)        170      0    🚫 数字在但结构丢失(串行化)
P5   图表页(营收)           33      1    🚫 图表数值不在文本(不可见)
...
分类       题数   通过   通过率
text        6      6     1.00   ✅
table       3      0     0.00   🚫
scan        4      0     0.00   🚫
chart       4      0     0.00   🚫
总体              6/17   0.35
[OK] 基线已存档:data/multimodal_docs/baseline_multimodal.json

验收检查

  • [ ] 毒文档删掉重跑 generate_poison_pdf.py,产物一致(可复现)
  • [ ] P3 扫描页抽到 0 字、P5 图表页文本不含 1800/2900
  • [ ] 基线显示 text 100%、table/scan/chart 全 0%
  • [ ] baseline_multimodal.json 已生成,后面 L08 要对照

🎯 面试话术

「我先量化了文本 RAG 的天花板:拿一份混合企业文档跑基线,扫描件全盲、表格丢结构、图表不可见——三类杀手题通过率 0%,只有纯文本题 100%。多模态不是赶时髦,是这些真实失败逼出来的。后面我按『解析→理解→索引→生成→溯源』五层逐个修,每修一层重跑对应题型,L08 出收益表对照这份基线。我的选型不是全 VLM 烧钱,是按元素类型分类路由——文本走免费老路、表格走结构化、扫描走本地 OCR、图表才花 VLM 的钱。」


落地清单

文件 改动 如何验证
data/multimodal_docs/generate_poison_pdf.py 新增:毒文档生成脚本(PyMuPDF + matplotlib) python generate_poison_pdf.py 产出 6 页 PDF
data/multimodal_docs/company_briefing.pdf 新增:毒文档成品(随脚本生成) 删掉重跑,产物一致
data/multimodal_docs/golden_questions.json 新增:17 道 golden 题(text/table/scan/chart) python -c "import json; print(len(json.load(open('golden_questions.json'))))" → 17
data/multimodal_docs/baseline_multimodal.json 新增:基线档案(跑 code.py 生成) overall_pass_rate ≈ 0.35,table/scan/chart = 0
doc-intelligence-lessons/00_baseline/code.py 新增:基线演示脚本(零 API) python code.py 打印失败模式 + 存档

📌 两条主线位置:本课立了成本-精度主线的坐标系(全 VLM 贵而强 vs 本地免费而脆,分类路由是工程答案)和溯源主线的伏笔(golden 题已带 source_page,L06 的页码引用靠它)。后面每课结尾都会回指这两条线。

下一课 Lesson 01 — PDF 解剖与版面解析 从「看得见字 ≠ 有文本层」开始,手写版面感知解析器,产出带类型和坐标的元素流——这是五层架构的地基。