File size: 8,945 Bytes
4a8fc49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# RAG PDF 提取与切分优化总结

这次优化的目标是提升当前 RAG 系统对金融 PDF,尤其是包含大量数学公式、章节标题和图表内容的 PDF 的解析质量。原始实现能完成基础向量检索,但 PDF 提取、公式保留、chunk 切分和 metadata 管理都比较粗糙,导致检索结果不够稳定。

## 一、整体背景

项目使用 `LlamaIndex + Chroma + HuggingFaceEmbedding` 构建本地知识库,原始 PDF 文档是一本期权/波动率相关书籍。最开始的流程大致是:

```text
pypdf 提取每页文本
  -> SentenceSplitter 固定长度切分
  -> HuggingFace embedding
  -> Chroma 向量库
  -> QueryKnowledgeTool 检索返回片段
```

这个流程对普通纯文本还可以,但面对金融教材类 PDF 会遇到很多问题:公式被拆散、章节边界丢失、页眉页脚干扰、图表文字混入正文、数学符号顺序错乱等。

## 二、遇到的主要问题

### 1. PDF 基础文本提取能力弱

最初只使用:

```python
page.extract_text()
```

问题是:

- 页眉、页码、版权信息会混进正文。
- 断行、断词严重,比如单词被 PDF 换行拆开。
- 多栏、图表、公式附近的文本顺序容易错乱。
- 数学公式经常被压成一行,或者符号顺序不对。

解决方法:

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

### 2. 数学公式提取不理想

金融教材中大量公式包含:

- 希腊字母,如 `𝜎``𝜇``𝜌`
- 上标、下标
- 分式结构
- 积分、求和、根号
- 公式编号,如 `(21.23)`

普通 PDF 文本提取很难还原这些结构。例如:

```text
d𝜎 = a𝜎 dt + b𝜎 dZ
```

可能会被提取成符号粘连、顺序错乱,或者和前后正文混在一起。

解决方法:

- 先做 `pypdf` 数学感知优化:
  - 识别公式行。
  - 对短公式行、括号行、根号行保留换行。
  - 尝试根据字号和垂直偏移标记上标/下标。

后来发现 `pypdf` 仍然不够,所以进一步接入 `PyMuPDF`### 3. PyMuPDF 初次接入后公式误判过多

接入 `PyMuPDF` 后,可以通过:

```python
page.get_text("dict", sort=True)
```

拿到 block、line、span、bbox、font 等信息。这比 `pypdf` 更适合定位公式区域。

但初版公式识别遇到一个问题:误判过多。

例如:

- 版权页中的电话号码。
- 普通正文中的 `Black-Scholes-Merton`- 普通段落里出现一个 `𝜎``F=ma`- 图表坐标轴上的数字。

都可能被误识别为公式。

解决方法:

- 从 block 级公式识别改为 line 级公式识别。
- 不再把普通斜体字体当作数学字体。
- 收紧公式触发条件:
  - 单独的希腊字母不算公式。
  - 普通 `-``/` 不作为强数学信号,避免把英文连字符误判为公式。
  - 重点识别 `=``∫``∑``√``≤``≥``∕`、公式编号等强信号。
- 增加 `is_useful_formula_text()`,过滤掉太短、太碎、无核心公式结构的片段。
- 对公式续行做合并,避免根号、分母、括号被拆成多个孤立公式 chunk。

最终实现了:

```text
正文 chunk
公式 chunk: content_type=formula
公式位置: formula_bbox
公式编号: formula_id
```

### 4. 章节和标题切分缺失

原始系统只用固定长度切分:

```python
SentenceSplitter(chunk_size=1000, chunk_overlap=150)
```

问题是:

- chunk 可能跨章节。
- 一个小节的标题和正文可能被分开。
- 检索结果不知道来自哪一章、哪一节。
- 回答时引用不够清楚。

解决方法:

在 `SentenceSplitter` 前增加一层章节/标题感知分段:

- 识别 `CHAPTER ...`
- 识别 `APPENDIX ...`
- 识别全大写标题
- 识别标题式大小写小节名
- 过滤图表标题、坐标轴、公式短行、脚注、普通解释句

并写入 metadata:

```python
chapter_title
section_title
section_path
page_number
content_type
formula_id
```

这样检索结果可以返回:

```text
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 太长。

报错类似:

```text
Metadata length is longer than chunk size.
Consider increasing the chunk size or decreasing metadata size.
```

原因是 `SentenceSplitter` 会把 metadata 长度也计入 chunk 长度。

解决方法:

- 不再存所有行的 bbox。
- 将多个 bbox 合并成一个外接矩形:

```text
x0,y0,x1,y1
```

这样既保留了公式位置,又避免 metadata 过长。

### 6. Hugging Face 模型加载反复联网

本地已经有 embedding 模型缓存,但 `sentence-transformers` 仍尝试访问 Hugging Face 做 HEAD 检查。在网络受限环境下,会反复 retry,导致索引构建卡住。

解决方法:

- 检测本地 snapshot 是否存在。
- 如果存在,直接把本地 snapshot 路径传给 embedding 模型。
- 设置离线环境变量:

```python
HF_HUB_OFFLINE=1
TRANSFORMERS_OFFLINE=1
```

这样索引构建可以稳定使用本地缓存。

### 7. 旧索引不会自动更新

PDF 提取逻辑升级后,如果 Chroma 里还是旧版本文本,RAG 实际不会变好。

解决方法:

- 增加 `PDF_EXTRACTION_METHOD` 版本号。
- 当前版本为:

```python
pymupdf_formula_blocks_v5
```

- 启动时检查 Chroma 中 metadata 的 `extraction_method`- 如果版本不一致,自动重建索引。

## 三、最终方案

最终 PDF RAG 流程变为:

```text
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 中同时保存:

```python
formula_text_raw
formula_latex
formula_bbox
page_number
section_path
```

5. 检索时对公式 query 单独加权,或者做 hybrid search。
6. 增加 reranker,提高公式相关问题的排序质量。

## 六、一句话总结

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