Spaces:
Runtime error
A newer version of the Gradio SDK is available: 6.16.0
RAG PDF 提取与切分优化总结
这次优化的目标是提升当前 RAG 系统对金融 PDF,尤其是包含大量数学公式、章节标题和图表内容的 PDF 的解析质量。原始实现能完成基础向量检索,但 PDF 提取、公式保留、chunk 切分和 metadata 管理都比较粗糙,导致检索结果不够稳定。
一、整体背景
项目使用 LlamaIndex + Chroma + HuggingFaceEmbedding 构建本地知识库,原始 PDF 文档是一本期权/波动率相关书籍。最开始的流程大致是:
pypdf 提取每页文本
-> SentenceSplitter 固定长度切分
-> HuggingFace embedding
-> Chroma 向量库
-> QueryKnowledgeTool 检索返回片段
这个流程对普通纯文本还可以,但面对金融教材类 PDF 会遇到很多问题:公式被拆散、章节边界丢失、页眉页脚干扰、图表文字混入正文、数学符号顺序错乱等。
二、遇到的主要问题
1. PDF 基础文本提取能力弱
最初只使用:
page.extract_text()
问题是:
- 页眉、页码、版权信息会混进正文。
- 断行、断词严重,比如单词被 PDF 换行拆开。
- 多栏、图表、公式附近的文本顺序容易错乱。
- 数学公式经常被压成一行,或者符号顺序不对。
解决方法:
- 增加
pypdf的layout模式作为候选。 - 增加坐标级提取,利用
visitor_text获取文字的x/y坐标,按视觉行重组。 - 增加文本清洗逻辑:
- 去除空行、页码、重复页眉页脚。
- 修复连字符断词。
- 处理常见 ligature,例如
fi、fl。 - 保留公式行的换行,不把公式硬合并成普通段落。
2. 数学公式提取不理想
金融教材中大量公式包含:
- 希腊字母,如
𝜎、𝜇、𝜌 - 上标、下标
- 分式结构
- 积分、求和、根号
- 公式编号,如
(21.23)
普通 PDF 文本提取很难还原这些结构。例如:
d𝜎 = a𝜎 dt + b𝜎 dZ
可能会被提取成符号粘连、顺序错乱,或者和前后正文混在一起。
解决方法:
- 先做
pypdf数学感知优化:- 识别公式行。
- 对短公式行、括号行、根号行保留换行。
- 尝试根据字号和垂直偏移标记上标/下标。
后来发现 pypdf 仍然不够,所以进一步接入 PyMuPDF。
3. PyMuPDF 初次接入后公式误判过多
接入 PyMuPDF 后,可以通过:
page.get_text("dict", sort=True)
拿到 block、line、span、bbox、font 等信息。这比 pypdf 更适合定位公式区域。
但初版公式识别遇到一个问题:误判过多。
例如:
- 版权页中的电话号码。
- 普通正文中的
Black-Scholes-Merton。 - 普通段落里出现一个
𝜎或F=ma。 - 图表坐标轴上的数字。
都可能被误识别为公式。
解决方法:
- 从 block 级公式识别改为 line 级公式识别。
- 不再把普通斜体字体当作数学字体。
- 收紧公式触发条件:
- 单独的希腊字母不算公式。
- 普通
-、/不作为强数学信号,避免把英文连字符误判为公式。 - 重点识别
=、∫、∑、√、≤、≥、∕、公式编号等强信号。
- 增加
is_useful_formula_text(),过滤掉太短、太碎、无核心公式结构的片段。 - 对公式续行做合并,避免根号、分母、括号被拆成多个孤立公式 chunk。
最终实现了:
正文 chunk
公式 chunk: content_type=formula
公式位置: formula_bbox
公式编号: formula_id
4. 章节和标题切分缺失
原始系统只用固定长度切分:
SentenceSplitter(chunk_size=1000, chunk_overlap=150)
问题是:
- chunk 可能跨章节。
- 一个小节的标题和正文可能被分开。
- 检索结果不知道来自哪一章、哪一节。
- 回答时引用不够清楚。
解决方法:
在 SentenceSplitter 前增加一层章节/标题感知分段:
- 识别
CHAPTER ... - 识别
APPENDIX ... - 识别全大写标题
- 识别标题式大小写小节名
- 过滤图表标题、坐标轴、公式短行、脚注、普通解释句
并写入 metadata:
chapter_title
section_title
section_path
page_number
content_type
formula_id
这样检索结果可以返回:
source: The_volatility_Smile_Wiley.pdf
page: 379
section: WITH ZERO CORRELATION
content_type: formula
formula_id: formula-378-3
5. metadata 过长导致 LlamaIndex 报错
接入公式 bbox 后,最开始把每一行的 bbox 都放进 metadata,导致 metadata 太长。
报错类似:
Metadata length is longer than chunk size.
Consider increasing the chunk size or decreasing metadata size.
原因是 SentenceSplitter 会把 metadata 长度也计入 chunk 长度。
解决方法:
- 不再存所有行的 bbox。
- 将多个 bbox 合并成一个外接矩形:
x0,y0,x1,y1
这样既保留了公式位置,又避免 metadata 过长。
6. Hugging Face 模型加载反复联网
本地已经有 embedding 模型缓存,但 sentence-transformers 仍尝试访问 Hugging Face 做 HEAD 检查。在网络受限环境下,会反复 retry,导致索引构建卡住。
解决方法:
- 检测本地 snapshot 是否存在。
- 如果存在,直接把本地 snapshot 路径传给 embedding 模型。
- 设置离线环境变量:
HF_HUB_OFFLINE=1
TRANSFORMERS_OFFLINE=1
这样索引构建可以稳定使用本地缓存。
7. 旧索引不会自动更新
PDF 提取逻辑升级后,如果 Chroma 里还是旧版本文本,RAG 实际不会变好。
解决方法:
- 增加
PDF_EXTRACTION_METHOD版本号。 - 当前版本为:
pymupdf_formula_blocks_v5
- 启动时检查 Chroma 中 metadata 的
extraction_method。 - 如果版本不一致,自动重建索引。
三、最终方案
最终 PDF RAG 流程变为:
PyMuPDF 提取 block / line / span / bbox / font
-> 识别公式行
-> 合并公式续行
-> 生成独立公式文档 content_type=formula
-> 正文中保留 [FORMULA id=...] 引用
-> 清洗页眉页脚和噪声
-> 按章节/标题预分段
-> SentenceSplitter 二次切分
-> 写入 Chroma
-> 检索时返回 page / section / content_type / formula_id
核心收益:
- 公式可以作为独立检索单元。
- 正文仍保留公式上下文。
- chunk 不再完全依赖固定长度。
- 检索结果能说明来源页码、小节、内容类型。
- 索引版本可控,避免旧数据污染。
四、面试中可以怎么回答
可以这样概括:
我们一开始的 RAG 只是用
pypdf按页提取文本,然后用固定长度切分。这个方案对普通文档可以,但对金融教材不够,因为里面有大量数学公式、图表和章节结构。主要问题是公式顺序错乱、上下标丢失、页眉页脚混入、chunk 跨章节。
然后讲解决:
我先做了基础清洗,包括页眉页脚去重、断词修复、公式行换行保留。后来发现
pypdf对公式区域的定位能力有限,所以接入了PyMuPDF,利用它返回的 block、line、span、bbox 和 font 信息,单独识别公式区域,并把公式作为content_type=formula的独立 chunk 入库,同时正文里保留[FORMULA id=...],这样检索公式和检索上下文都可以兼顾。
再讲工程取舍:
公式识别不能简单看到希腊字母就判定为公式,否则普通正文会大量误判。所以我把规则收紧到等号、积分、求和、根号、公式编号、比较符等强数学信号,并过滤掉太短的碎片。bbox 也不能直接把所有行都写入 metadata,因为 LlamaIndex 会把 metadata 计入 chunk 长度,所以我把多个 bbox 合并成一个外接矩形。
最后讲效果:
优化后索引从原来的纯文本 chunk,变成了正文 chunk 加公式 chunk 的混合结构。每条检索结果都带 page、section、content_type、formula_id 等 metadata,回答时更容易定位来源,也更适合处理“某个公式是什么意思”这类问题。
五、后续可继续优化
目前已经接入 PyMuPDF,但还不是完整 OCR/LaTeX 公式识别。后续可以继续做:
- 对
formula_bbox区域裁图。 - 接入公式 OCR 模型,例如 LaTeX OCR。
- 把公式图片转成 LaTeX。
- metadata 中同时保存:
formula_text_raw
formula_latex
formula_bbox
page_number
section_path
- 检索时对公式 query 单独加权,或者做 hybrid search。
- 增加 reranker,提高公式相关问题的排序质量。
六、一句话总结
这次优化的核心不是简单换一个 PDF parser,而是把 PDF 解析从“按页提取纯文本”升级成“结构化解析正文、章节和公式区域”,让 RAG 的 chunk 更接近人阅读文档时的语义边界。