PDF 丢失版面信息,为何会拖垮 RAG
将 PDF 展平后直接切块,会破坏表格行列关系,并可能混合多栏文档的阅读顺序,降低检索上下文质量。文章据此提出保留二维空间信息的解析方向,重点讨论复杂文档的入库问题。
AI 深度解读
PDF 问答答错了,先别急着换 embedding 模型或改提示词。作者指出,问题可能出在更早的入库阶段:PDF 被压成纯文本时,表格关系、分栏顺序和出处坐标已经丢了,后面的检索与生成只能接着处理这些失真的材料。
作者给出的方案是 RasgaPDF,一个保留二维位置的 Rust PDF 引擎。思路有价值,尤其适合需要表格问答和原文定位的产品;但文中的速度与准确性主张,还需要分开看。
为什么切成 500-token,也救不了表格关系
常见 RAG(检索增强生成)流程是:提取所有文本,拼成一个字符串,再切成带重叠的 500-token 片段,生成向量并存入数据库。单栏文章通常能这样处理,财报、论文和合同却容易出问题。
第一处损失是表格。PDF 中的表格通常不是 HTML 表格或 Markdown 网格,而是放在绝对坐标上的独立文字,加上一些矢量线条。坐标被删除后,某个数字属于哪一行、哪一列,就可能无法判断。分块即使保留了数字,也未必保留数字之间的关系。
第二处是阅读顺序。双栏、三栏页面如果按水平位置直接扫过,左栏和右栏的句子可能交错。向量化不会自动把这些句子拼回去。
第三处是核验。回答给出了利率或 EBITDA,用户需要知道它来自哪一页、哪个单元格。只有文本片段,界面最多展示一句引文,难以准确定位原文。
分块只能切分已有信息,无法恢复解析时丢掉的结构。
二维坐标怎样变成可用的检索材料
作者的出发点是:对于内容流里已有文字位置的 PDF,不必先把页面变成图片,再让模型重新猜测布局,可以直接利用文件中的几何信息。
RasgaPDF 据此重建页面、文本块、行和表格之间的空间层级。下面这份输出展示了它如何把文本和位置放在一起:
{
"page_number": 1,
"width": 612.0,
"height": 792.0,
"rotation": 0,
"text_blocks": [
{
"bbox": { "x0": 60.0, "y0": 70.6, "x1": 384.6, "y1": 82.7 },
"lines": [
{
"bbox": { "x0": 60.0, "y0": 70.6, "x1": 384.6, "y1": 82.7 },
"text": "Revenue increased by 14% year-over-year..."
}
]
}
],
"tables": [
{
"bbox": { "x0": 60.0, "y0": 121.0, "x1": 540.0, "y1": 245.0 },
"markdown": "| Metric | 2025 | 2026 |\n| --- | --- | --- |\n..."
}
]
}
这里要看的是 bbox:文本块、行、表格都带有自己的边界框,而表格另有 markdown 表达。示例中的 14% 仍是一段文本,坐标则告诉系统这段文字在页面上的位置。
这就是文中所谓的二维定位(2D Grounding):让检索材料能对应回文档中的具体区域。
对前端最直接的价值是可视化引用。保留页码和 [x0, y0, x1, y1] 后,界面可以展示原始 PDF,并在来源区域覆盖半透明高亮。用户不必在一份 50 页的文件里反复搜索。
实际接入仍有一步要补:解析器坐标如何映射到 PDF 查看器的缩放、旋转和显示坐标。原文给出了页面尺寸与 rotation,但没有展示这部分实现。
Rust 和现成 Loader,分别解决什么问题
作者把现有方案分成两类:PyMuPDF、Poppler、PDFium 等传统解析器速度快;Marker、Docling、Nougat 等视觉或 OCR 路线通过图像与模型判断布局,计算成本更高。
RasgaPDF 的卖点是用 safe Rust 原生实现 ISO 32000-1,减少 C/C++ 工具链依赖。作者称,cargo build 和 pip install 不需要安装 MSBuild 或 C 编译器,并报告一份密集的 12 页学术论文,在单 CPU 核上约 15 ms 完成解析。
集成层面,原文宣称支持 LangChain 和 LlamaIndex,并给出了保留位置元数据的加载方式:
from rasgapdf import RasgaPDFLoader
# 加载时在每个 Document 片段中保留物理二维元数据
loader = RasgaPDFLoader("financial_report.pdf", mode="elements")
docs = loader.load()
first_chunk = docs[0]
print(first_chunk.page_content)
print(first_chunk.metadata["bbox"])
# {'x0': 60.0, 'y0': 70.6, 'x1': 384.6, 'y1': 82.7}
关键是 mode="elements",以及随后读取的 metadata["bbox"]。接入后还要检查后续分块、存储和检索返回过程是否继续保留这些元数据,否则引用链仍会中断。
我的判断
我认同作者把排查顺序往前移:复杂文档问答出错,应该先看解析结果,再判断模型能力。表格列对应错了,继续调整提示词很难稳定补救。
但「有坐标」与「正确理解结构」之间,还有距离。如何判断跨行单元格、跨页表格和复杂分栏,原文没有展开,也没有提供准确率对照。对于没有文字层的扫描件,直接读取已有文字坐标的前提也不成立,文中没有交代覆盖方式。
传统解析器与视觉方案的划分同样偏粗。不能仅凭这篇文章,就认定前者无法提供空间信息,或后者在所有配置下都必须使用 GPU。
约 15 ms 是作者报告的单个案例,缺少硬件型号、样本集和计时范围,也不是完整 RAG 链路的耗时。至于减少幻觉、提高表格可靠性,目前更接近方案判断,不能当成已有对照实验支持的结论。
坐标能支持原文核验,却不能单独证明答案正确。
能拿来做什么
- 给 PDF 问答建立一小组真实样本,覆盖表格、双栏和三栏页面,先人工检查解析文本的数字对应关系与阅读顺序,再决定是否调整 embedding 或提示词。
- 在资料入库时保留页码、
bbox、页面尺寸和旋转信息,并验证这些字段能一路传到检索结果和前端引用组件。 - 为财报或论文问答增加「查看原文」入口,点击引用后跳转对应页面并高亮来源区域,同时验证缩放和旋转后的定位是否准确。
- 评估 RasgaPDF 时分别测量解析速度、表格结构和引用定位;扫描件单独测试,避免用一份有文字层的论文代表全部文档。