# RAG策略
## 前言
![RAG技术架构](./image/image%20(13).png)

途中绿色的元素是需要进一步讨论的核心 RAG 技术，黄色的是文本，蓝色的是数据库（向量数据库，结构化数据库等）



RAG 的基本流程： 你将文本分割成块，然后使用某个 Transformer 编码器模型将这些块嵌入到向量中，将所有这些向量放入索引中，最后你为 LLM 创建一个提示，告诉模型根据搜索步骤中找到的上下文来回答用户的查询。

## 一、分块与向量化
### 1.1、分块
分块的大小是需要考虑的一个参数，这个参数取决于使用的嵌入模型能够处理的最大 token 长度，不同的嵌入模型的最大 Token 是不同的 Qwen/Qwen3-Embedding-8B 嵌入模型最大可以处理 32k 的 Token 数量，BAAI/bge-m3嵌入模型最大可以处理的只有 8k 的 Token 数量

关于分块最重要的是：**分块要足够的长，能让大模型在推理时有足够的上下文，同时分块又要足够的具体，以便生成的向量能更准确地表达语义，从而搜索更高效**

所以在分块时的大小权衡需要根据具体的场景去考虑，同时结合一下使用的嵌入模型的最大 Token 数就可以

### 1.2、向量化
当文本块分好之后，我们需要使用嵌入模型将文本块转换为机器能理解的符号，也就是向量

+ 一句话，或者一个段落 → 输入到嵌入模型（embedding model）。
+ 模型会输出一个固定维度的数字数组，比如 `[0.12, -0.53, 0.88, ...]`。
+ 这个数组就是向量，通常有几百到几千个维度。

**向量其实就是一个高维度的坐标，也就是一个固定维度的数字数组**



在选择嵌入模型时，需要考虑两个方面

1. 向量维度的大小（维度过低会语义丢失，维度过高增加存储和计算成本）
2. 模型支持的语言范围（确保支持你的文本语言）

## 二、搜索索引
### 2.1、向量存储索引

![向量存储索引](./image/image%20(14).png)

图中，为了核心流程的清晰，有。省略一些模块

1. 嵌入模型：查询的时候会先将文本使用嵌入模型转换为向量，进入到向量数据库查询
2. 查询结果：查询出来的结果其实是**前 k 个向量，**而不是文本块，这里系统会自动使用向量 ID 去映射回原始的文本块



RAG 流程关键的部分是搜索索引，其有多种实现方式

1. 平面索引：直接用暴力方式计算查询向量和所有片段向量的距离
2. 向量索引：**通常采用近似最近邻（ANN）的方法**，比如聚类，树结构和 HNSW 算法

> ANN 是一类**搜索策略**，目标是在海量向量里快速找到最接近的候选，而不是逐一比对
>
> + **聚类倒排（IVF）**：先把空间分块，缩小候选范围。
> + **树结构（Annoy 等）**：用分割空间的树来加速查找。
> + **图结构（HNSW）**：构建近邻图，贪心搜索路径找到候选。
>



🌟 **同时你也可以根据你的需求和场景，在存储向量的同时保存元数据**

元数据的作用有两个：

1. 可以用来作为向量查询的附加值，也就是元信息
2. 可以利用元数据过滤器来限定检索范围，例如：根据日期或者来源筛选



### 2.2、分层索引


![分层索引](./image/image%20(15).png)

分层索引的核心思路：先大再小，先粗筛再细筛

分层索引会创建两个索引，一个是“大文档”的摘要索引，一个是“小文档”的文本块索引，摘要索引里面的向量和文本块索引里面的向量是有关联关系，搜索的时候分为两步搜索：

1. 先通过摘要索引过滤出相关的文档
2. 只在这些相关文档内部进行片段级的检索



### 2.3、假设性问题(Hypothetical Questions)与假设性文档(HyDE)


![假设性问题与假设性文档](./image/image%20(16).png)

**1、 假设性问题(Hypothetical Questions)，源头是文档片段**

+ 对于每个文档块，让 LLM 生成几个可能有人会问的问题，把这些问题拿去向量化建立索引
+ 在检索的时候，用户的问题去检索上一步构建的“文档问题”向量数据库，命中合适的值之后，根据“文档问题”找到文档块

例子：

1. 文档块：支持 7 天无理由退货，定制商品除外
2. 生成的问题：
    1. 多久可以退货
    2. 定制商品能退吗
    3. 退货运费谁承担
3. 当用户问：“不合适能退吗？”，搜索就可以命中生成的文档问题块



**2、假设性文档(Hypothetical Document Embeddings)，源头是用户的问题**

+ 用户输入的问题，使用 LLM 生成一段假设性的答案文档，
+ 把这个“答案”拿去文档向量数据库中进行检索，同时也可以把问题也拿去检索，最后合并一下结果即可

例子：

1. 用户问题：什么是量子纠缠？
2. 生成的答案：“量子纠缠是指两个粒子状态相关联，即使相距遥远，测量一个会立即影响另一个。”
3. 根据这个答案再去文档向量数据库中进行检索，这样检索出来的极大概率都可以和问题解答相关



### 2.4、上下文增强
检索时尽量取更小的片段，以提升搜索精度，**但在提供给 LLM 时，再补充周围的上下文，以充足的上下文供 LLM 进行更好的推理**



目前有两种实现的方式：

1. **句子扩展**：在检索到较小的片段的时候，把该片段的上下的片段都加上，组合成为一个完整的上下文块输入给 LLM
2. **父文档检索**：先把文档分割为较大的父级片段，每一个父级片段再包含更小的子片段，**检索的时候使用子片段组成的向量数据库，但是在使用的时候回溯到相应的父级片段，把父级片段周围上下文输入给 LLM，子片段只做一个中间的检索步骤**

#### 2.4.1、句子扩展


![句子扩展](./image/image%20(17).png)

最终的效果会得到一些改良：

+ 如果没有前后两句文档块，只给中间一句，那模型的回答可能只是一个观点或者一句话：“是的，苹果可能降低心脏病的风险
+ 这个时候加了前后两句文档块，那效果或者观点就会扩大，模型可能会输出：“苹果不仅能增强免疫系统，还能降低心脏病风险。研究显示，经常吃苹果的人往往更长寿。”

🌟 **核心思路：检索时保持足够小的文档块，在输入给 LLM 之前，进行上下文的扩展和增强，输入更完整的上下文给 LLM 进行推理**

#### 2.4.2、父文档检索- Auto-merging Retriever


![父文档检索](./image/image%20(18).png)

这个方法其实和上面的句子扩展的思路是一样的，最后都是将更充足的上下文输入给 LLM 进行推理。

文档被分割成一个分层结构，

+ 子文档块，更小的片段，用于承担检索任务，
+ 父文档块，更大的片段，与子文档是有联系的，用于决定最正输入给 LLM 的上下文



在查询的操作中，流程如下：

1. 先根据问题检索子文档块的向量数据库，这里的文档块都比较小，更有利于精准的检索
2. 得到前 k 个相关的子文档块
3. 由前 k 个相关的子文档块，查找或者合并它们相应的父文档块
4. 最后将整理出的父文档块输入给 LLM，用于生产最终答案



例子：

```plain
章节：心脏健康与饮食

段落1：
水果和蔬菜中的抗氧化剂可以减少心脏疾病风险。
苹果尤其被广泛研究，它们含有丰富的类黄酮。

段落2：
类黄酮能帮助降低血压，改善血管功能。
研究表明，每天食用一个苹果可以降低冠心病的发生率。

```

将上面的文档按照向量存储来分块

父块的向量数据库

+ 父块 A=段落 1
+ 父块 B=段落 2

子块的向量数据库

+ 父块 A 再拆为两个子块
    - 子块A1 = “水果和蔬菜中的抗氧化剂可以减少心脏疾病风险。”
    - 子块A2 = “苹果尤其被广泛研究，它们含有丰富的类黄酮。”
+ 父块 B 再拆为两个子块
    - 子块B1 = “类黄酮能帮助降低血压，改善血管功能。”
    - 子块B2 = “研究表明，每天食用一个苹果可以降低冠心病的发生率。”



用户的问题：“苹果对心脏有帮助吗？”

系统在子块层级搜索，命中子块 B2:

> “研究表明，每天食用一个苹果可以降低冠心病的发生率。”
>

然后系统向上回溯到父块 B，将完整的上下文输入给 LLM

> 类黄酮能帮助降低血压，改善血管功能。
>
> 研究表明，每天食用一个苹果可以降低冠心病的发生率。
>



### 2.5、融合检索或混合搜索


![融合检索](./image/image%20(19).png)

一个相对较早提出的想法：从两种不同的检索方式中汲取优点：第一种是基于关键词的传统搜索，第二种是基于现代的语义搜索和向量搜索

混合检索：结合这两种方式的优点并整理融合两种方式的结果



混合检索的难点在：**如何正确的融合来自不同检索方式，且相似度评分标准不同的结果**

通常会使用**Reciprocal Rank Fusion（RRF）算法来正确融合两种结果，它能对检索结果进行重新排序，生成最终的输出**

## 三、重新排序和过滤
通过上述的任意一种检索方式和思路，我们获得了检索结果，现在需要**通过过滤、重排序或某些转换来优化这些结果**

目前常见的优化方式是：

1. 根据相似度分数排序之后，取出前 k 个值
2. 根据元信息中的时间、用户 id 等数据进行过滤筛选
3. 也可以直接把向量结果中的文本输入给 LLM，让 LLM 进行最有效的排序
4. 可以使用**重排序(re-ranking)模型**来对结果再次进行筛选优化



## 四、查询转换


![查询转换](./image/image%20(20).png)

查询转换：**通过使用 LLM 作为推理引擎来修改用户输入，以提高检索质量，有几种不同的方法可以实现这一点。**

如果查询比较复杂，LLM 可以将它分解成多个子查询

例如用户会问：

> “在 Github 上，Langchain 或 LlamaIndex 哪个框架的星标更多？”
>

由于在语料库中几乎找不到直接的对比文本，因此将这个问题分解为两个更简单、更具体的子查询是有意义的：

+ “Langchain 在 Github 上有多少颗星？”
+ “LlamaIndex 在 Github 上有多少颗星？”

这两个子查询会并行执行，然后将检索到的上下文合并在一起，作为一个上下文输入到 LLM 中



还有下面两种思路可以实现查询转换

**1、 转换通用查询（Step-back Prompting）：这种方法先让 LLM 生成一个更通用的查询，**然后利用这个通用查询检索到更通用，更高层次的上下文，这些上下文是有助于为原始查询提供语义基础的

**同时，系统也会执行对原始查询的检索，并且在最终生成答案，把这两类上下文一起输入给 LLM 进行推理**

****

**2、 查询重写（Query Re-writing）：这种方法先利用 LLM 来对初始查询进行改写或重试，**以提升检索效果



## 五、引用来源
在 LLM 生成答案的时候，可以将答案的来源标注出来，方便回溯到原始来源

**这种引用来源的方式是非常重要的，可以极大的增加答案的准确性，为用户提供一层“信息审核”的机制**

目前可以有以下几种方式实现：

1. **在提示词中加入引用任务，要求 LLM 在答案中提到所使用来源的 ID**
2. **将 LLM 生成的响应与向量数据库中检索出的原始文本块进行匹配**

****

## 六、查询路由


![查询路由 ](./image/image%20(21).png)

**查询路由：是一个由 LLM 驱动的决策步骤，根据用户的查询，决定下一步要做什么，**提供给 LLM 的选择通常包括：摘要索引，某些文档块数据索引或结构化数据库，最后在一个答案中综合它们的输出

查询路由还可以运用到搜索策略中去，例如是选择分层检索的方式，还是选择句子扩展的方式，或者父文档检索的方式呢



**查询路由最重要的是定义路由器它能够做出的选择范围**，也就是能给路由器提供哪些搜索策略或者搜索方式



