# 一个金融研报 RAG 应用：「离线解析」和「在线问答」双链路详解

> 作者：晨旭｜发布：2025-12-05｜系列：AI技术理解
> 来源：https://chenxu.xin/writing/financial-rag-dual-pipeline

复盘一套真实的金融 RAG 项目：离线链路怎么把多栏排版、扫描件和跨页表格解析成干净的知识，在线链路怎么通过 Query 优化、混合检索和重排把答案找准。

---

在 AI 产品的落地中，RAG 是知识库的标配，且是 Agent 应用中的核心组件。尤其是在金融、保险等专业领域，多栏排版、扫描件模糊、跨页表格，以及用户模糊的提问意图，这些非结构化数据带来的挑战，是每一个 AI 产品经理和技术团队必须攻克的深水区。

这篇文章复盘自一套真实的金融 RAG 实战项目。跳出代码细节，从「离线解析」与「在线问答」这两条核心链路出发，拆解构建 RAG 系统的底层逻辑。

对于 AI 产品经理而言，拥有系统性的优化思维至关重要：知道了数据在管道中是如何流动的，才能在产品出现 Bad Case 时，精准定位是解析出了问题，还是检索走了神。

## 全链路架构

做一个 RAG 产品，首先要建立上帝视角，不能只停留在前端的对话框，还要看透后端的「双流」架构。RAG 系统本质上由两条异步的流水线组成。

**离线处理流程（数据入库）**，这是系统的消化系统：

- **提取**：从 PDF、PPT、TXT 等异构数据源中提取内容。
- **向量化**：通过 Embedding 模型将文本转化为向量。
- **存储**：将文本和向量存入向量数据库（如 Milvus / ElasticSearch）和关系型数据库（如 PostgreSQL）。

**在线查询流程（实时问答）**，这是系统的大脑：

- **查询处理**：对用户 Query 进行编码、重写和意图识别。
- **检索**：通过语义检索和关键词检索获取相关片段。
- **生成**：LLM 基于增强的上下文生成最终回答。

离线阶段是提前做好知识库的解析；当用户来问问题的时候，进入第二阶段的在线检索。通过这两步操作，最终将用户的问题扩充为「检索回来的精确文本块 + 用户的原始问题」，再一起送给 LLM 作为 user prompt。



## 离线解析：精细化治理

RAG 领域的名言 Garbage In, Garbage Out，在金融研报这种专业领域体现得淋漓尽致，解析质量直接决定了系统的上限。可以从解析、分块、层级结构这三个维度进行优化。

### 1、深度解析：从「看到」到「看懂」

在金融 RAG 中，PDF 解析不能是单纯的文字提取，需要对文档结构进行逆向工程，集成 OCR、深度学习布局识别和机器学习文本合并的复杂流水线。

**（1）视觉化与 OCR：让机器「看」到**

金融研报中大量存在扫描件或图片格式的图表。解析的第一步是将 PDF 页面转化为高分辨率图像：使用 pdfplumber 将页面转为图像，同时提取原始字符坐标。如果检测到是扫描件而非电子原生 PDF，系统会启动 OCR 引擎（如 Tesseract），识别图像中的文字并生成新的文本框，与原始信息融合。

**（2）布局识别：让机器「分类」**

系统需要知道哪块是正文，哪块是页眉，哪块是图表。调用基于深度学习目标检测模型的 LayoutRecognizer，对页面进行区域划分，给不同区域打上 Title、Text、Table、Figure 等标签。

这解决了多栏排版读乱序的问题——因为这里是按区域读取，而不是按行读取。

**（3）表格结构还原**

金融数据都在表格里，因此不仅要提取表格里的字，还要还原行列关系。TableStructureRecognizer 会裁剪出表格区域，识别单元格边界，将图片中的表格重构为 HTML 或结构化数据，确保「表头—数值」的对应关系不丢失。

**（4）智能文本合并：让机器「连贯」**

这是最体现技术深度的环节。OCR 出来的往往是破碎的单字或单行，需要把它们拼成完整的段落：

- **横向合并**：将同一行内距离极近的文本框拼接。
- **纵向合并**：基于行距和对齐方式，将同一自然段的多行文本拼接。
- **基于 XGBoost 的上下文合并**：这是一个高级特性。训练一个 XGBoost 模型，根据文本特征（是否以句号结尾、行间距、字体大小变化）来预测「下一行是否属于当前段落」，这比写死的规则要准确得多。



### 2、智能切块：数据入库前的精修

切分不是简单的「每 500 字切一刀」。错误的切分会斩断语义，导致检索失效。在 RAG 系统中，文档切块是最容易被忽视、却直接决定检索效果的环节：切得太碎，模型看不懂上下文；切得太长，检索噪音大且浪费 Token。

需要一种从宏观到微观再回归宏观的处理策略，原始文档变成机器可读的向量要经历三个阶段。

**第一步：格式识别与初步解析**。系统首先充当一个分拣员，根据文件后缀调用不同的解析器（如 PdfParser、DocxParser），将文档拆解为自然的段落（Sections）。

**第二步：碎片化拆分**。为了保证后续合并的灵活性，不能直接用段落当 Chunk，因为有的段落长达几千字，有的只有几个字。因此需要先打碎：利用分隔符（换行符、句号、感叹号）将 Section 进一步拆解为更小的文本碎片，产物是一堆细粒度的句子或短语。

**第三步：基于 Token 预算的动态合并**。这是最核心的一步，我们像装箱子一样，设定一个标准箱子大小（例如 128 Tokens），然后把刚才打碎的句子一个个装进去。

这里涉及的核心算法是 `naive_merge`，它的目标是在保持语义连贯性（不把一句话切断）的前提下，最大化利用每个 Chunk 的容量：

1. **初始化**：创建一个空的 Chunk 容器，设定阈值（例如 `chunk_token_num = 128`）。
2. **循环填装**：遍历文本碎片列表，计算「当前 Chunk 已有的 Token 数 + 新碎片的 Token 数」。小于等于阈值就装入；大于阈值就封箱，把当前 Chunk 保存，创建一个新 Chunk 并把这个碎片作为第一个元素放进去。
3. **最终输出**：生成 Chunks 列表。

举个例子：假设阈值是 128，当前 Chunk 里已经装了 120 个 Token，下一个句子有 15 个 Token。120 + 15 = 135 > 128，于是系统把这 120 个 Token 打包成 Chunk A，然后开启 Chunk B，把这 15 个 Token 的句子放进去。

经过上述处理，我们得到的不仅仅是一段段文本字符串，而是一个结构化的对象列表。每个最终的 Chunk 都包含：

- **Content**：文本块的字符串内容，用于给大模型阅读。
- **Tokens**：分词后的 Token 列表，用于计算成本和上下文窗口。
- **Metadata**：位置信息（页码、在原文中的偏移量），这对于产品界面上展示引用来源至关重要。

**为什么不直接按字符数或段落切分？** 直接切字符可能会把「人工智能」切成「人工」和「智能」两个 Chunk，导致语义崩坏；直接切段落则有的极短导致向量过于稀疏检索匹配不到，有的极长超过 Embedding 模型的窗口限制。

`naive_merge` 是一种动态平衡：先利用标点符号保护句子级的语义完整性，再利用 Token 计数控制 Chunk 的大小颗粒度。



### 3、重建层级结构与元数据

单纯的文本切片是孤立的，我们需要为每个切片找回它的坐标。

比如，当模型检索到「第三条：赔付金额为 50 万」这个切片时，如果丢失了它所属的「一级标题：重大疾病险」，模型可能会张冠李戴，把它当成意外险的条款。

解决方案是元数据增强。在解析时维护一个层级栈，给每一个 Chunk 打上标签：

```json
{
  "source": "理赔手册.pdf",
  "page": 5,
  "section_path": "总则/第二章/第三条",
  "type": "text"
}
```

那么在线问答检索时，就可以让 LLM 利用这些元数据进行精准过滤（例如「只看第二章的内容」），或者在回答时准确引用出处。

## 在线检索：组合优化

有了高质量的数据，下一步是让系统听懂人话，并精准找到答案。

### 1、Query 理解与优化

用户的问题往往是模糊的（如「怎么报销？」），直接检索效果会比较差。

- **意图识别**：判断用户是在查流程、查数据还是闲聊，可通过规则匹配或 BERT 分类模型实现。
- **Query 重写**：将口语化的「怎么报销保险费用？」改写为规范的「保险费用报销流程是什么？」，去除冗余词，补全上下文。
- **Query 扩写**：引入同义词。用户搜「理赔」，系统自动扩展搜索「索赔」「赔付」，扩大召回范围。
- **HyDE（假设文档嵌入）**：对于复杂问题，先让 LLM 生成一个假设答案，再用这个假设答案去检索真实文档，这能显著提升长尾问题的召回率。

### 2、混合检索

单一的检索方式在金融场景下往往捉襟见肘。向量检索擅长语义匹配（如「推销」匹配「销售」），但对专有名词（如「A 款产品」）不够敏感；关键词检索（BM25）擅长精确匹配，但无法理解语义。

解决方案是 BM25 与向量检索并行，将两者的得分进行归一化和加权融合（例如 0.6 × 向量分 + 0.4 × BM25 分），取长补短。

### 3、领域微调

通用的 Embedding 模型（如 BGE）可能不懂「保单现金价值」是什么。策略是使用金融领域的私有数据（问答对、专业术语）对 Embedding 模型进行微调，拉近专业术语在向量空间中的距离，显著提升召回准确率。

### 4、重排序：精度的最后一道防线

初步检索为了不漏掉信息，通常会召回 Top 50 甚至 Top 100 个片段，但这其中包含大量噪声。

Cross-Encoder 重排引入一个更精细的模型，将用户 Query 和候选文档拼在一起进行深度打分，能识别细微的语义差异。例如查询「最新车险流程」，初步检索可能混入了「2020 年旧流程」，重排模型能精准识别出「2023 年修订版」更相关，将其排在第一位。

总结来看，检索是一个漏斗模型：Query 优化（扩充漏斗口）→ 混合检索（粗筛）→ 重排序（精选）→ LLM 生成。需要关注每个环节的转化率。通过建立评估指标（如 MRR、NDCG、Precision@K），就可以量化出「引入重排后，Top 3 召回率提升了 15%」这样的结论。



## 最后

- 离线解析的颗粒度，决定了知识库的纯度。
- 在线检索的重排策略，决定了用户体验的精度。

也许一个 AI 产品的护城河，就是藏在这些最不起眼的脏活累活、某一个参数的调优里。看似枯燥的技术细节优化，最终都会转化为用户感知到的「这个 AI 真的懂我」。

## 核心结论

- RAG 系统本质上由两条异步流水线组成：离线处理（提取、向量化、存储）是消化系统，在线查询（Query 处理、检索、生成）是大脑。建立这个上帝视角，才能在出 Bad Case 时判断问题出在哪一条链路。（判断 · 把握较大）
  永久链接：https://chenxu.xin/writing/financial-rag-dual-pipeline#rag-is-two-async-pipelines
- 文档切块是 RAG 系统中最容易被忽视、却直接决定检索效果的环节。切得太碎模型看不懂上下文，切得太长检索噪音大且浪费 Token。（判断 · 把握较大）
  永久链接：https://chenxu.xin/writing/financial-rag-dual-pipeline#chunking-is-most-overlooked
- 按 Token 预算动态合并（naive_merge）是工程落地中高性价比的切块方案——先用标点符号保护句子级的语义完整性，再用 Token 计数控制 Chunk 的颗粒度，避免了纯字符切分和纯段落切分各自的缺陷。（判断）
  永久链接：https://chenxu.xin/writing/financial-rag-dual-pipeline#naive-merge-dynamic-balance
- 孤立的文本切片会导致张冠李戴。检索到「第三条：赔付金额为 50 万」时如果丢失了它所属的一级标题，模型可能会把重疾险条款当成意外险。层级栈加元数据是找回坐标的解法。
  永久链接：https://chenxu.xin/writing/financial-rag-dual-pipeline#metadata-prevents-mismatch
- 检索是一个漏斗模型：Query 优化扩充漏斗口、混合检索做粗筛、重排序做精选、最后交给 LLM 生成。每个环节的转化率都要单独看，才能量化出「引入重排后 Top 3 召回率提升多少」这样的结论。（判断 · 把握较大）
  永久链接：https://chenxu.xin/writing/financial-rag-dual-pipeline#retrieval-is-a-funnel

---

本文出自晨光里的AI（https://chenxu.xin），作者晨旭。
引用时请保留来源链接。文中标注「判断」「假设」的部分是作者的个人看法，不是事实。
