本页目录
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。每题标了 category、answer_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 解剖与版面解析 从「看得见字 ≠ 有文本层」开始,手写版面感知解析器,产出带类型和坐标的元素流——这是五层架构的地基。