AI 工程基础体系 · 第 85/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。
RAG 文档解析:PDF、Office、HTML、OCR、版面与结构恢复
RAG(Retrieval-Augmented Generation,检索增强生成)不是“把文件转成字符串,再交给大模型”。它至少包含三条因果链:
- 原始文件是否被正确解析,决定知识是否进入索引;
- 文档是否被正确切分和表示,决定检索能否找回正确证据;
- 证据是否保留来源、权限和结构,决定生成结果能否被验证。
RAG 论文将生成过程表示为条件生成:
其中, 是用户问题, 是从知识库中检索到的文档片段, 是模型输出, 是文档集合。实际系统不会枚举整个集合,而是先通过检索器选出有限候选片段,再让生成模型基于这些片段回答。
因此,文档解析不是索引前的附属步骤,而是 的输入质量控制。解析时丢失的表格列关系、标题层级、脚注归属或访问权限,通常无法由后续向量模型自动恢复。
一、先定义“文档解析”到底要恢复什么
文档解析是把 PDF、Office、HTML、扫描图像等文件,转换为机器可检索、可引用、可审计的结构化表示。这个过程不等于纯文本抽取。
一个适合 RAG 的中间表示通常至少包含:
{
"document_id": "hr-policy-2024",
"source_uri": "s3://corp-docs/hr/policy.pdf",
"page": 3,
"block_id": "p3-b7",
"block_type": "table",
"text": "等级 | 月度补贴\nP1 | 500\nP2 | 800",
"heading_path": ["员工手册", "福利政策", "交通补贴"],
"bbox": [72, 310, 520, 490],
"language": "zh",
"parser": "pymupdf",
"parser_version": "1.24.x",
"acl": ["hr", "manager"],
"content_hash": "..."
}
这里有几个容易混淆的概念:
- 字符内容:页面或节点中实际出现的文字。
- 版面(layout):文字、图片、表格、标题等元素在页面中的空间位置和相对关系。
- 结构(structure):章节层级、段落边界、列表嵌套、表格行列、脚注引用等语义组织。
- 结构恢复(structure recovery):当源文件没有直接提供这些结构,或结构在转换中丢失时,根据文本、样式、坐标和上下文重新推断。
- OCR(Optical Character Recognition,光学字符识别):从图像像素识别出字符及其位置,不是简单的图片转文字。
- 版面分析:识别页面中的标题、段落、表格、图片、页眉、页脚、公式等区域,并判断其阅读顺序。
例如,PDF 中“标题位于页面上方、字号较大”是版面证据;“该标题下有若干正文段落,直到下一个同级标题”是结构推断。前者不一定能推出后者,尤其是在双栏论文、报告目录和复杂表格中。
二、RAG 文档解析的完整数据流
生产系统通常把解析拆成可重试的阶段,而不是一个不可观测的函数:
flowchart LR
A[文件接入] --> B[类型识别与安全检查]
B --> C[原生内容抽取]
C --> D{内容质量足够?}
D -- 是 --> E[版面与结构恢复]
D -- 否 --> F[渲染页面/提取图像]
F --> G[OCR与版面分析]
G --> E
E --> H[统一文档表示]
H --> I[清洗与结构化切分]
I --> J[权限与元数据绑定]
J --> K[嵌入和索引]
K --> L[检索、重排、引用生成]
每个阶段都应保存输入哈希、输出版本、耗时、警告和错误。原因是文档解析存在部分成功状态:
- PDF 文本层可以读取,但其中一页是扫描图;
- DOCX 正文可以读取,但页眉、文本框或批注没有被读取;
- HTML 主体可读取,但正文被 JavaScript 动态加载;
- OCR 识别出字符,但表格列顺序错误。
如果只保存最终文本,后续无法判断“没有检索到知识”究竟是检索问题,还是解析阶段已经丢失知识。
三、PDF:页面描述格式,不是语义文档格式
PDF 的核心目标是精确呈现页面,而不是表达“这是一个标题”或“这是表格的第三列”。一个 PDF 页面通常由文字绘制指令、字体、图片、路径和坐标组成。即使视觉上是一个段落,文件内部也可能只保存为许多带坐标的独立文字片段。
3.1 PDF 的三种常见内容状态
原生文本 PDF
文字有可提取的字符编码,通常可以获得字符、字体、字号和坐标。法律合同、数字化报告和由文字处理软件导出的 PDF 常属于此类。
扫描 PDF
页面本质是图片,没有可用文字层。普通文本提取可能返回空字符串,或者只返回极少量隐藏文字。
混合 PDF
部分页面有文字层,部分页面是扫描图;也可能正文可复制,但印章、手写批注或表格截图只能通过 OCR 获取。
因此,不能用“文件扩展名是 PDF”决定解析方法。应先检测每页的文字数量、图像数量、字符覆盖率和异常编码。
3.2 用 PyMuPDF 进行原生文本和坐标抽取
下面示例读取每页的词级坐标。它适用于已经包含文字层的 PDF,不会自动解决扫描页 OCR,也不保证表格结构正确。
python -m pip install pymupdf
from pathlib import Path
import fitz # PyMuPDF
pdf_path = Path("contract.pdf")
with fitz.open(pdf_path) as doc:
for page_no, page in enumerate(doc, start=1):
text = page.get_text("text")
words = page.get_text("words")
print(f"--- page {page_no} ---")
print("字符数:", len(text.strip()))
print("词块数:", len(words))
# words 中常见形式为:
# (x0, y0, x1, y1, word, block_no, line_no, word_no)
for item in words[:5]:
x0, y0, x1, y1, word = item[:5]
print({
"text": word,
"bbox": [x0, y0, x1, y1]
})
关键中间结果不是只有 text,还包括:
- 每个词的边界框;
- 所属文本块;
- 行号和词序;
- 页面尺寸;
- 字体信息(若解析器能够提供)。
如果抽取结果类似:
政策范围 本政策适用于...
补贴标准 P1 500 P2 800
而视觉页面是两列表格,则纯文本已经丢失了列关系。此时继续切分、嵌入和检索,只会把错误结构扩散到整个 RAG 流程。
3.3 PDF 的阅读顺序不能直接由文件顺序决定
对单栏文档,可以按页面内的 坐标、再按 坐标排序。但双栏页面若直接按全局坐标排序,可能得到:
左栏第一段 右栏第一段 左栏第二段 右栏第二段
正确顺序通常应是:
左栏第一段 左栏第二段 右栏第一段 右栏第二段
因此,阅读顺序恢复通常包括:
- 按文字块坐标聚类出栏;
- 对每一栏内部按纵向位置排序;
- 识别跨栏标题、页眉和页脚;
- 对表格区域单独处理,不能与正文排序混合;
- 检测页眉页脚的重复模式并标记,而不是盲目删除。
页眉页脚是否删除取决于语义。页码通常是噪声,但“机密等级:内部”可能是权限或合规信息,不能因为重复就删除。
3.4 PDF 表格的难点
表格不是“带很多空格的文本”。至少要恢复:
- 行与列;
- 单元格边界;
- 跨行和跨列;
- 表头与数据的对应关系;
- 单元格内换行;
- 表格标题、脚注和正文引用的关系。
如果只得到:
项目 金额 税率
服务费 1000 6%
还可以通过规则恢复;但遇到跨列表头:
2024 2025
部门 预算 实际 预算 实际
线性文本可能无法判断每个数字对应哪一年。适合检索的表示应显式保留列路径,例如:
部门=研发 | 年份=2024 | 指标=预算 | 值=100
部门=研发 | 年份=2024 | 指标=实际 | 值=92
这类规范化适合问答和过滤,但不应替代原始表格快照。原始单元格坐标仍需保留,以便引用和人工核验。
四、Office:格式内部有结构,但结构不等于页面布局
4.1 DOCX 是 ZIP 加 XML,不是简单文本文件
DOCX 通常是一个 ZIP 容器,内部包含正文 XML、样式、关系、图片、页眉页脚、脚注等。正文段落和表格可以通过库直接访问,但文本框、绘图对象、批注、域代码和复杂分页信息可能需要额外处理。
使用 python-docx 读取普通段落和表格:
python -m pip install python-docx
from docx import Document
doc = Document("manual.docx")
blocks = []
for p in doc.paragraphs:
text = p.text.strip()
if text:
blocks.append({
"type": "paragraph",
"style": p.style.name,
"text": text
})
for table_index, table in enumerate(doc.tables):
rows = []
for row in table.rows:
rows.append([cell.text.strip() for cell in row.cells])
blocks.append({
"type": "table",
"table_index": table_index,
"rows": rows
})
for block in blocks:
print(block)
这段代码能正确处理普通正文和文档主体中的表格,但有明确边界:
- 它不等价于“按页面阅读顺序读取所有可见内容”;
- 文本框中的文字可能不在
doc.paragraphs中; - 页眉页脚需要通过 section 单独读取;
- 文档中的图片需要另行提取和 OCR;
- 表格可能包含嵌套表格、合并单元格或重复单元格引用;
- Word 的分页结果依赖渲染引擎,不能仅由 XML 稳定推断。
4.2 XLSX 与 PPTX 的结构差异
Excel 的核心不是段落,而是二维或多维单元格结构。解析时应保留:
- 工作表名称;
- 单元格坐标;
- 公式与计算结果;
- 合并单元格;
- 隐藏行列;
- 单元格格式;
- 表头范围和数据范围。
将整个工作表拼接成一段文本会破坏行列语义。更适合的单位可能是“表格区域”“按行记录”或“按业务主键聚合的记录”。
PowerPoint 的信息分布在幻灯片、文本框、形状、图表、演讲者备注和图片中。视觉阅读顺序通常需要按形状坐标恢复。一个标题和三个并列文本框,不应仅按 XML 出现顺序拼接。
4.3 Office 渲染与语义解析的取舍
Office 文件可以直接解析出段落和表格,也可以先转换为 PDF 再进行版面分析。两条路径各有损失:
- 直接解析更容易保留逻辑结构、样式和表格对象;
- 渲染后更接近用户看到的分页、位置和视觉层次;
- 转 PDF 可能改变字体、分页、公式、动画或隐藏元素;
- 只读 XML 可能忽略文本框、浮动对象和最终布局。
实际系统常同时保存“逻辑解析结果”和“渲染版面结果”,并通过块 ID 建立关联,而不是强迫一种表示覆盖所有用途。
五、HTML:DOM 结构丰富,但正文和视觉结构可能分离
HTML 至少包含三种结构:
- DOM 结构:标签嵌套关系;
- 样式结构:CSS 决定显示、隐藏、字号和布局;
- 运行时结构:JavaScript 执行后动态生成的内容。
静态抓取只能得到初始 HTML。若正文由 JavaScript 请求接口后插入,解析器可能只看到导航栏和一个空容器。
5.1 静态 HTML 的安全解析
python -m pip install beautifulsoup4 lxml
from bs4 import BeautifulSoup
html = """
<html>
<body>
<nav>首页 | 登录</nav>
<main>
<h1>报销制度</h1>
<h2>交通费用</h2>
<p>市内交通每人每月上限为 500 元。</p>
<table>
<tr><th>等级</th><th>上限</th></tr>
<tr><td>P1</td><td>500</td></tr>
</table>
</main>
</body>
</html>
"""
soup = BeautifulSoup(html, "lxml")
for node in soup.select("script, style, nav, footer"):
node.decompose()
main = soup.select_one("main") or soup
print(main.get_text("\n", strip=True))
这里删除 nav、script 和 footer 是示例策略,不是普遍规范。某些页面的页脚包含法律条款、版本号或生效日期,删除会造成事实缺失。清洗规则应按站点或内容类型测试,并记录规则版本。
5.2 HTML 的结构恢复
HTML 通常比 PDF 更容易获得标题层级,但仍有三个问题:
- 页面使用
<div>模拟标题,缺少语义标签; - 目录、推荐内容和正文混在同一 DOM 中;
- CSS 网格或多栏布局改变了视觉顺序。
因此,正文抽取应结合:
- 标签语义;
- DOM 深度和父子关系;
- 文本密度;
- 链接密度;
- 重复模板识别;
- CSS 可见性;
- 页面渲染后的视觉位置。
对动态页面,浏览器自动化是常见实现,但它会引入更高成本和安全风险:脚本执行可能访问内部网络、触发下载或暴露凭据。生产环境应使用隔离容器、网络白名单、资源限制和超时,而不是在主应用进程中直接执行任意页面脚本。
六、OCR:从像素识别文字,错误会改变检索事实
OCR 的输入是图像,输出通常包括:
- 识别文本;
- 字符或词的置信度;
- 边界框;
- 行和段落分组;
- 版面类别;
- 有时还包括表格和公式区域。
“识别率高”不能只看字符准确率。RAG 更关注语义关键位置是否正确。例如把“不得超过 1000 元”识别为“不得超过 10000 元”,只错一个字符,却改变了政策含义。
6.1 一个可运行的 Tesseract 示例
前置条件:
- 安装 Tesseract OCR 引擎;
- 安装中文语言包,例如
chi_sim; - Python 安装
pytesseract、Pillow。
python -m pip install pillow pytesseract
from PIL import Image
import pytesseract
image = Image.open("scan-page.png")
data = pytesseract.image_to_data(
image,
lang="chi_sim+eng",
output_type=pytesseract.Output.DICT
)
items = []
for i, text in enumerate(data["text"]):
text = text.strip()
if not text:
continue
items.append({
"text": text,
"confidence": float(data["conf"][i]),
"bbox": [
data["left"][i],
data["top"][i],
data["width"][i],
data["height"][i]
]
})
for item in items[:10]:
print(item)
预期输出类似:
{'text': '报销制度', 'confidence': 96.2, 'bbox': [80, 42, 210, 38]}
此示例只完成文字识别和词框输出,不会自动恢复表格或标题层级。常见预处理包括去噪、倾斜校正、分辨率调整、二值化和版面裁剪,但预处理过度会损伤小字体、印章和低对比度文字。
6.2 OCR 的质量控制
可以为每页建立质量信号:
其中:
- 是 OCR 平均置信度;
- 是文字密度或有效字符比例;
- 是与词典、语言模型或原生文本的一致性;
- 是异常指标,例如大量不可识别字符、重复字符或数字格式错误;
- 是按业务设定的权重。
这不是通用质量标准,而是路由信号。低 的页面可以:
- 重新渲染更高分辨率;
- 切分为表格、正文和图片区域后分别识别;
- 使用另一 OCR 引擎交叉比较;
- 标记为“需要人工复核”;
- 禁止直接作为高风险答案的唯一证据。
OCR 置信度也不是事实正确性的概率。引擎可能对错误的“1”和“7”都给出高置信度,因此还要检查金额、日期、百分比、合同编号等领域格式。
七、版面恢复:先恢复区域,再恢复阅读顺序
版面恢复的目标是回答“页面上哪些内容属于什么区域,以及这些区域如何阅读”。它通常包括:
- 页面分割:正文、标题、表格、图片、页眉、页脚;
- 区域分类:判断每个区域的类型;
- 区域排序:恢复阅读顺序;
- 关联分析:把图注、脚注、标题和主体关联起来;
- 坐标保存:支持引用、裁剪和人工核验。
7.1 为什么坐标是重要的一等数据
只保存文本会导致以下问题:
- 无法在原文中高亮证据;
- 无法定位 OCR 错误;
- 无法判断两段文字是否来自不同栏;
- 无法重新构造表格;
- 无法将检索结果展示为“第几页、第几行”。
因此,每个块最好保存归一化坐标:
其中 是页面坐标, 是页面宽高。归一化后,跨分辨率渲染仍能定位相同区域。但不同 PDF 库的坐标原点和单位可能不同,必须在适配层统一,并通过已知页面测试验证。
7.2 标题层级不能只看字号
标题推断可以综合:
- 字号和字体粗细;
- 与前后块的垂直间距;
- 编号模式,如“1”“1.1”“第一章”;
- 是否处于目录或正文;
- 同级标题的样式一致性;
- 下方是否跟随正文;
- 标题文本长度和标点模式。
例如,“第一章 总则”通常是一级标题;“1.1 适用范围”可能是二级标题;但“重要提示”也可能只是加粗的正文。单一规则会误判。
恢复出的 heading_path 对检索非常重要。相同句子放在“退款政策”和“隐私政策”下,含义可能不同。切片时应把路径加入元数据或上下文:
文档:员工手册
章节:福利政策 > 交通补贴
内容:市内交通每人每月上限为 500 元。
八、结构恢复:从块到章节、列表、表格和引用关系
结构恢复的对象不仅是标题。一个可用的文档树可能如下:
文档
├── 1 总则
│ ├── 1.1 目的
│ └── 1.2 适用范围
├── 2 报销标准
│ ├── 2.1 交通费
│ └── 表 2-1 交通补贴
└── 附录 A 术语
恢复过程可以分为三步。
第一步:生成原子块
原子块是最小可定位单位,例如:
- 段落;
- 标题;
- 列表项;
- 表格单元格;
- 图片及其说明;
- 脚注;
- 公式区域。
原子块不应过早拼接,因为后续可能需要重新排序或重新关联。
第二步:推断块关系
常见关系包括:
parent_of:标题属于哪个章节;next:阅读顺序中的下一个块;caption_of:图注属于哪张图片;footnote_of:脚注属于哪个正文引用;row_of、column_of:单元格属于哪一行或哪一列;continues_from:跨页表格或段落的延续关系。
第三步:构造多种视图
同一原始结构可以产生不同索引视图:
- 面向语义检索的段落视图;
- 面向精确过滤的字段视图;
- 面向表格问答的行列视图;
- 面向引用展示的页面坐标视图;
- 面向权限过滤的 ACL 视图。
把所有内容强行变成一种纯文本,是结构丢失的主要来源之一。
九、切分不是按字符数截断
切分(chunking)是将结构化文档划分为可检索片段。它同时受两个约束:
- 片段需要足够完整,能独立表达一个事实;
- 片段不能过大,否则向量表示和上下文窗口中会混入过多无关内容。
设一个候选片段包含事实集合 ,用户问题需要事实集合 。理想片段应满足:
同时希望片段长度 不超过预算 。但这通常不可同时满足,因为一个定义可能跨越多个段落,表格的表头和数据也可能距离很远。
9.1 结构优先的切分算法
一个可靠的基本流程是:
- 先按文档结构切分:章节、段落、列表、表格;
- 如果块超过长度预算,在句子或列表项边界切分;
- 给子块继承完整标题路径;
- 对跨块定义保留有限重叠;
- 表格单独序列化,不与相邻正文盲目拼接;
- 为每个片段记录页码、块 ID 和原文偏移。
例如原文:
2 报销标准
2.1 交通费
员工市内交通费用按月报销。P1 等级上限为 500 元,P2 等级上限为 800 元。
不应只切成:
员工市内交通费用按月报销。
P1 等级上限为 500 元,P2 等级上限为 800 元。
而应保留:
章节:报销标准 > 交通费
内容:员工市内交通费用按月报销。P1 等级上限为 500 元,P2 等级上限为 800 元。
否则用户问“P2 等级交通补贴是多少”,检索结果可能缺乏“交通费”的限定。
9.2 按固定窗口切分的反例
原文:
本政策适用于正式员工。外包人员不适用。出差交通费按以下标准执行:
高铁:二等座;
飞机:经济舱。
若每 30 个字符截断,可能产生:
片段 A:本政策适用于正式员工。外包人员不适用。出差交通费按
片段 B:以下标准执行:高铁:二等座;飞机:经济舱。
问题“外包人员能否报销飞机经济舱”需要同时检索适用范围和费用标准。固定窗口虽然可能依靠重叠解决,但重叠不是结构理解;重叠过小会丢语义,过大则增加索引冗余和检索噪声。
十、表格、图片、公式和脚注应走不同表示
表格
表格适合同时保存:
- 原始二维单元格;
- 带表头路径的行记录;
- 可读文本;
- 页面坐标。
例如:
表名:交通补贴标准
等级=P1;项目=市内交通;上限=500 元
等级=P2;项目=市内交通;上限=800 元
这比简单拼接单元格更适合问答,但对于需要精确计算的场景,还应保存数值类型和单位。
图片
图片本身可能包含流程图、架构图、扫描签章或图表。应保存图片区域、图注和 OCR/视觉模型生成的描述,并明确描述是推断结果还是原文文字。模型生成的图片摘要不能当作原始事实的无条件替代。
公式
公式中的符号、上下标和单位不能简单去掉。例如:
如果解析为“总价单价数量”,检索可能找到关键词,却无法用于推理。可以同时保存 LaTeX、线性文本和渲染图片,但要标记来源与转换方式。
脚注和引用
脚注可能改变正文含义,例如“仅限试点地区”。将脚注全部移到文档末尾会使正文片段失去限定条件。更安全的做法是建立 footnote_of 关系,并在生成检索片段时将相关脚注附加到正文。
十一、解析质量必须用“事实任务”评测
只评估“抽取了多少字符”是不够的。建议分别评估以下层次。
字符和词级准确性
适合 OCR 或编码问题:
- 字符错误率;
- 词错误率;
- 数字、日期、金额、编号的精确率。
块级准确性
判断段落、标题、表格和图片区域是否识别正确:
- 区域检测的 precision、recall、IoU;
- 块类型分类准确率;
- 阅读顺序错误率。
结构级准确性
检查:
- 标题层级是否正确;
- 表头与数据是否对应;
- 脚注是否绑定;
- 列表嵌套是否保持;
- 跨页内容是否正确合并。
RAG 任务级准确性
最终要测:
- 相关片段召回率;
- 证据是否包含完整限定条件;
- 引用页码和坐标是否正确;
- 答案是否忠实于证据;
- 解析错误是否导致高风险错误答案。
一个完整算例是:测试集包含 100 个问题,其中 20 个依赖表格,15 个依赖脚注,10 个来自扫描页。若总体检索 Recall@5 为 90%,但表格问题只有 40%,则不能认为解析系统质量良好;总体平均值掩盖了结构性失败。
解析评测还应包含反例集:
- 双栏 PDF;
- 旋转页面;
- 中英文混排;
- 跨页表格;
- 合并单元格;
- 低分辨率扫描;
- 页眉中含关键合规信息;
- 动态渲染 HTML;
- 同一文件的新旧版本。
十二、检索、权限和引用必须在解析阶段建立联系
OpenAI 的 Retrieval 指南强调,检索系统通常会对文件内容进行分块、嵌入和索引;具体分块策略和检索配置会影响结果。无论使用托管检索还是自建向量库,都不能把权限当作生成阶段的提示词。
正确的顺序是:
而不是先从所有文档检索,再要求模型“不要使用无权限内容”。后者既可能泄露信息,也无法保证模型不引用已经看见的内容。
ACL 应绑定到文档块或可继承的结构节点:
{
"chunk_id": "doc-1:p3:b7",
"acl": {
"groups": ["finance"],
"users": [],
"classification": "internal"
}
}
当章节继承文档权限时,切片过程中必须保留继承关系。文档更新、权限变更和删除也要进入索引生命周期:
- 内容哈希变化:重新解析受影响版本;
- 权限变化:立即更新过滤索引;
- 删除请求:删除原文、解析缓存、嵌入和检索索引;
- 版本变化:保留生效日期,避免新旧政策混答。
引用应回到原始可核验位置:
来源:员工手册.pdf,第 3 页,表 2-1,坐标 [72, 310, 520, 490]
“来源是某个向量块”不够,用户无法判断该块是否漏掉了表头、脚注或否定条件。
十三、并发、失败路径与可恢复性
大批量解析通常是异步任务。可以把每个文档建模为状态机:
stateDiagram-v2
[*] --> RECEIVED
RECEIVED --> VALIDATING
VALIDATING --> REJECTED: 类型/安全检查失败
VALIDATING --> EXTRACTING
EXTRACTING --> OCR_REQUIRED: 文本不足或页面为图像
EXTRACTING --> STRUCTURING: 原生内容可用
OCR_REQUIRED --> OCR_RUNNING
OCR_RUNNING --> OCR_FAILED: 超时/资源不足
OCR_RUNNING --> STRUCTURING
STRUCTURING --> CHUNKING
CHUNKING --> INDEXING
INDEXING --> READY
READY --> SUPERSEDED: 新版本到达
OCR_FAILED --> RETRYING
RETRYING --> OCR_RUNNING
每次状态转换应具备幂等性。常见做法是用:
作为任务键。相同文件、解析器版本和配置重复提交时,不应重复产生不一致结果。
典型故障与处理方式包括:
- 文件伪装扩展名:通过 MIME、魔数和容器结构共同检测;
- 损坏 PDF:隔离原文件,记录页级错误,不把空结果当成功;
- OCR 超时:设置页数、像素和 CPU 限制,支持页级重试;
- HTML 无限跳转:限制重定向次数、总下载大小和执行时间;
- 恶意压缩包:限制解压文件数、展开大小和目录深度,防止 Zip Bomb;
- 重复索引:使用文档版本和内容哈希去重;
- 部分页面失败:结果标记为
partial,禁止伪装成完整解析; - 索引成功但元数据失败:采用事务性发布,只有文本、嵌入、权限和来源都准备好后才进入可检索状态。
十四、成本和模型选择是同一个生产决策
解析成本不只来自 OCR 或大模型,还包括:
- 文件下载和对象存储;
- PDF 渲染;
- OCR 的每页计算;
- 表格和版面模型推理;
- 文本嵌入;
- 向量存储;
- 重排和生成;
- 重新解析与版本并存。
可以将单文档成本近似写成:
其中 OCR 和视觉模型通常应按需触发,而不是对所有页面执行。一个常见路由是:
- 先尝试原生抽取;
- 只有文字密度低或结构异常的页面才渲染;
- 只有疑似扫描或关键图像区域才 OCR;
- 只有表格、公式或低置信度区域才升级到更昂贵模型。
但不能只按成本路由。若合同金额、医疗剂量或安全规程对错误极其敏感,应使用更严格的质量阈值和人工复核,即使单页成本更高。
十五、常见误解与诊断方法
误解一:PDF 能复制文字,就说明解析正确
复制功能只说明存在某种文字层,不说明阅读顺序、表格结构和字体编码正确。诊断时应对比页面截图、词坐标、块顺序和关键数字。
误解二:OCR 置信度高,就可以直接入库
置信度不是事实保证。应对金额、日期、否定词、单位和编号做规则检查,并抽样比对原图。
误解三:增加 chunk overlap 就能修复结构丢失
重叠只能缓解边界截断,不能恢复列关系、标题归属或脚注语义。遇到表格和双栏问题,应先修复版面与结构。
误解四:把所有内容转成 Markdown 就完成了结构化
Markdown 对标题、列表和简单表格有帮助,但它不能完整表达坐标、合并单元格、脚注关联、隐藏内容、版本和权限。Markdown 可以是展示格式,不应成为唯一规范数据模型。
误解五:检索不到答案一定是向量模型问题
排查顺序应先看:
- 原文是否真的被解析出来;
- 关键事实是否落在同一结构片段中;
- 片段是否带有标题、表头和脚注;
- ACL 是否误过滤;
- 关键词检索和向量检索是否都失败;
- 重排器是否把正确证据降权;
- 生成模型是否忽略了已召回证据。
一个简单有效的诊断方法是建立“原文—解析块—切片—检索结果—最终引用”的链路追踪。只展示最终答案而不展示这条链路,无法判断系统在哪一层犯错。
十六、一个可执行的最小落地方案
对普通企业文档,可以先实现以下最小闭环:
- 对上传文件做类型、大小、魔数和恶意内容检查;
- PDF 使用原生文本抽取,并按页检测文本密度;
- 对低密度页面渲染后 OCR,保留词框和置信度;
- DOCX 分别读取段落、表格、页眉页脚和图片;
- HTML 清理模板节点,同时保留标题层级和 URL;
- 所有结果转换为统一块模型,保存
document_id、页码或 DOM 路径、坐标、类型和版本; - 先按章节和语义边界切分,再按 token 预算限制大小;
- 表格同时保存二维结构和带表头路径的文本;
- 切片继承 ACL、标题路径、文档版本和生效时间;
- 通过关键词检索、向量检索和引用定位共同验证;
- 对扫描页、表格、脚注和双栏文档建立专门评测集。
最终目标不是得到“看起来通顺的文本”,而是得到一组可以追溯的证据单元:
其中任一箭头断裂,RAG 就可能出现“模型回答流畅,但证据错误”的结果。PDF、Office、HTML 和 OCR 只是不同输入形态;版面恢复与结构恢复才是把这些形态转换为可靠知识系统的核心。
系列导航与关联阅读
- 系列入口:AI 工程完整学习路线:从机器学习与 Transformer 到 RAG、Agent 和生产治理
- 上一篇:Prompt Cache 工程:前缀复用、缓存键、隔离、失效和成本
- 下一篇:RAG 切块策略:固定、语义、层次、表格与上下文窗口
官方资料
本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论