First_agent_template / rag_pdf_optimization_notes.md
mathidot's picture
加强pdf提取能力,增加rag评测模块
4a8fc49

A newer version of the Gradio SDK is available: 6.16.0

Upgrade

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 换行拆开。
  • 多栏、图表、公式附近的文本顺序容易错乱。
  • 数学公式经常被压成一行,或者符号顺序不对。

解决方法:

  • 增加 pypdflayout 模式作为候选。
  • 增加坐标级提取,利用 visitor_text 获取文字的 x/y 坐标,按视觉行重组。
  • 增加文本清洗逻辑:
    • 去除空行、页码、重复页眉页脚。
    • 修复连字符断词。
    • 处理常见 ligature,例如
    • 保留公式行的换行,不把公式硬合并成普通段落。

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 公式识别。后续可以继续做:

  1. formula_bbox 区域裁图。
  2. 接入公式 OCR 模型,例如 LaTeX OCR。
  3. 把公式图片转成 LaTeX。
  4. metadata 中同时保存:
formula_text_raw
formula_latex
formula_bbox
page_number
section_path
  1. 检索时对公式 query 单独加权,或者做 hybrid search。
  2. 增加 reranker,提高公式相关问题的排序质量。

六、一句话总结

这次优化的核心不是简单换一个 PDF parser,而是把 PDF 解析从“按页提取纯文本”升级成“结构化解析正文、章节和公式区域”,让 RAG 的 chunk 更接近人阅读文档时的语义边界。