# RAPTOR：递归摘要与树形检索的结合，提升RAG检索性能

## **摘要**

随着 LLM 技术的发展，RAG 的价值也来越明显，可以视作 LLM 应用、落地的一个主要方向。RAG通过结合检索系统和生成模型，在生成回答时先从外部知识库种检索相关信息，辅助 LLM 进行更准确的生成。知识的粒度是多样的、零散的。如何**从知识库中精准地检索到相关的知识片段**是一个极具挑战性地问题。

## 概述

在目前构建 RAG 系统的流程中，基本都会涉及到对文档进行分块（有没有不需要进行分块的方法呢？）。现行的方式主要是通过滑动窗口进行分块，调一调分块的大小等。如何进行分块是一个很重要的问题。这在需要整合来自文本多个部分知识的主题性问题中尤为重要。

目前检索增强的方法中的一个主要缺点，也是论文主要**想解决的问题**：检索到的主要是一些短的、连续的块。这限制了它们表示和利用大规模话语结构的能力。这在需要整合来自文本多个部分知识的主题性问题中尤为重要，比如理解整本书的情况.对于需要阅读整个文档才能回答的问题，检索到的块是不能包含足够的信息的。

**RAG 系统的关键问题**：分块的大小、如何分块是一个依赖于用户输入的问题。有些查询，只需要某个、某些块就可以回答，或者只需要块中的某句话就可以回答。而且，**文本一般是涵盖多个主题的**，并且具有**层次化的结构**。

作者引入了一种新颖的方法，即递归嵌入、聚类和总结文本片段，从底部开始构建具有不同摘要级别的树。

作者设计了一个树结构的索引和检索系统 RAPTOR（Recursive Abstractive Processing for Tree-Organized Retrieval）.RAPTOR对文本片段进行聚类，生成这些聚类的文本摘要，然后重复此过程，从底部生成树。这种结构使得RAPTOR能够加载不同级别代表文本的片段到LLM的上下文中，从而能够有效地回答不同级别的问题。捕捉文本的多尺度，不同层次的信息。通过对文本块进行总结，向 LLM 提供不同层次的信息。

![image-20251223105952383](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/image-20251223105952383.png)

## **相关工作**

使用检索：在实际应用中，使用长上下文既昂贵又缓慢。这表明，为知识密集型任务选择最相关的信息仍然至关重要。

**检索方法** 检索增强语言模型（Retrieval-augmented language models, RALMs）在各个组成部分中取得了改进：检索器(retriever)、阅读器(reader)和端到端系统训练。

**递归摘要作为上下文** 摘要(Summarization，总结)技术提供了对文档的简洁视图，使内容更加集中。Gao等人的摘要模型使用段落(passage)的摘要和片段，提高了大多数数据集的正确性，但有时可能是一种高失真的压缩手段。然而，由于这两种方法依赖于相邻节点的分组或摘要，可能仍会忽视文本内的远程相互依赖关系，而RAPTOR可以找到并组合这些关系。

## RAPTOR方法介绍

### RAPTOR概览

建立在长文本通常呈现子主题和分层结构的思想基础上，RAPTOR通过构建一个递归树结构来解决阅读中的语义深度和连接问题，该结构平衡了更广泛的主题理解和细粒度细节，允许根据语义相似性而不仅仅是文本顺序对节点进行分组。

RAPTOR树的构建始于将检索语料库分割成长度为100的短连续文本，类似于传统的检索增强技术。如果一句话超过100个标记的限制，我们将整个句子移至下一个块(chunk)，而不是在句子中途截断。这保留了每个块内文本的上下文和语义连贯性。然后使用SBERT嵌入这些文本。这些块及其对应的SBERT嵌入形成了树结构的叶节点。


### 分块与嵌入：

建立在长文本通常呈现子主题和分层结构的思想基础上，RAPTOR通过构建一个递归树结构来解决阅读中的语义深度和连接问题，该结构平衡了更广泛的主题理解和细粒度细节，允许根据语义相似性而不仅仅是文本顺序对节点进行分组。

### 聚类

为了将相似的文本块分组，采用了聚类算法。一旦分组，将使用语言模型对分组文本进行总结。然后重新嵌入这些总结文本，并继续嵌入、聚类和总结循环，直到进一步聚类变得不可行为止，最终形成原始文档的结构化、多层树表示。RAPTOR的一个重要特点是其计算效率。该系统在构建时间和标记消耗方面呈线性缩放，适用于处理庞大和复杂的语料库。

RAPTOR 在文本块的基础上，构建了一个递归的树结构，树中的节点标识不同粒度的语义信息。

作者聚类方法的一个独特之处在于使用软聚类，其中节点可以属于多个簇，而不需要固定数量的簇。这种灵活性是必不可少的，因为单个文本段通常包含与各种主题相关的信息，因此需要将其纳入多个摘要中。

聚类算法基于高斯混合模型(GMMs)，这种方法提供了灵活性和概率框架。GMMs假设数据点是从多个高斯分布混合中生成的。

给定一个包含N个文本片段的集合，每个表示为一个d维稠密向量嵌入，假设文本向量$\mathbf{x}$属于第  $k^{th}$ 个高斯分布的概率表示为$P(\mathbf{x}|k) = \mathcal{N}(\mathbf{x};\mu_k,\pmb{\Sigma}_k)$。整体概率分布是加权和$P(\mathbf{x}) = \sum_{k=1}^{K}\pi_k\mathcal{N}(\mathbf{x};\mu_k,\pmb{\Sigma}_k)$,表示第k个高斯分布的混合权重。

向量嵌入的高维度对传统GMMs构成挑战，因为在高维空间中使用距离度量可能表现不佳。为了缓解这一问题，作者采用均匀流形近似和投影(Uniform Manifold Approximation and Projection,UMAP)，一种用于降维的流形学习技术。UMAP中的最近邻参数n_neighbors决定了保留局部结构和全局结构之间的平衡。作者的算法通过变化n_neighbors来创建层次聚类结构：首先识别全局聚类，然后在这些全局聚类中执行局部聚类。这种两步聚类过程捕捉了文本数据之间的广泛关系，从广泛主题到具体细节。

如果局部聚类的组合上下文超过了总结模型的标记阈值，作者的算法会在该聚类内递归应用聚类，确保上下文保持在标记阈值内。

为了确定最佳聚类数，使用贝叶斯信息准则(Bayesian Information Criterion,BIC)进行模型选择。BIC不仅惩罚模型复杂性，还奖励拟合优度。对于给定的GMM，BIC为  $BIC = \ln(N)k - 2\ln(\hat{L})$ ，其中N为文本片段(或数据点)的数量，k为模型参数的数量， $\hat{L}$ 
 为模型似然函数的最大化值。在GMM的情境中，参数数量k 是输入向量的维度和聚类数量的函数。

得到由BIC确定的最佳聚类数量后，使用EM算法来估计GMM参数，即均值、协方差和混合权重。虽然GMM中的高斯假设可能与文本数据的性质不完全符合，后者通常表现出稀疏和偏斜的分布，但实证观察表明，它是一种有效的模型。



### 查询：

对于在该树内查询，引入了两种不同的策略：树遍历和折叠树。树遍历方法逐层遍历树，在每个级别修剪和选择最相关的节点。折叠树方法评估所有级别上的节点，以找到最相关的节点。

下面详细阐述了RAPTOR所采用的两种查询机制：树遍历和折叠树(collapsed tree，坍塌树)。这些方法提供了在多层RAPTOR树中获取相关信息的独特方式，每种方法都有其各自的优势和权衡。在附录F中提供了这两种方法的伪代码。使用SBERT对所有节点进行嵌入。

**树遍历方法**首先基于查询嵌入与根节点的余弦相似性选择top-k个最相关的根节点。这些所选节点的子节点在下一层被考虑，然后再次基于它们的子节点与查询向量的余弦相似性选择top-k个节点。该过程重复进行，直到达到叶节点。最后，将从所有选定节点中提取的文本连接起来形成检索到的上下文。算法步骤如下：

在RAPTOR树的根层开始。计算查询嵌入与该初始层中所有节点的嵌入之间的余弦相似性。
基于最高余弦相似性得分选择前k个节点，形成集合$S_{1}$

继续处理集合$S_{1}$ 中元素的子节点。计算查询向量与这些子节点的向量嵌入之间的余弦相似性。
根据与查询的最高余弦相似性得分选择top-k个子节点，形成集合$S_2$。

递归地继续这个过程达到d层，生成集合  $S_{1}, S_{2}, \ldots, S_{d}$ . 

将集合$S_1$ 至$S_d$

 连接起来组装成与查询相关的上下文。
通过调整深度d和每层选择的节点数k ，树遍历方法提供了对检索到的信息的特定性和广度的控制。该算法从树的顶层开始具有宽泛的视野，并随着向下穿越到较低层而逐渐聚焦于更细节的内容。

图2：树遍历和折叠树检索机制的示意图。树遍历从树的根层开始，并基于余弦相似性与查询向量检索top-k个(在此处为top-1个节点)。在每层，它从前一层top-k的子节点中检索top-k的节点。折叠树将树折叠成单层，并基于与查询向量的余弦相似性检索节点，直到达到标记数的阈值。这两种示意图中都突出显示了进行余弦相似性搜索的节点。

**折叠树方法**提供了一种更简单的搜索相关信息的方式，通过同时考虑树中的所有节点，如图2所示。这种方法不是逐层进行，而是将多层树折叠成单层，实质上将所有节点置于同一水平进行比较。该方法的步骤如下所述：

首先将整个RAPTOR树折叠为单层。这个新的节点集合，表示为C，包含原始树的每一层中的节点。
接下来，计算查询嵌入与折叠集合C 中所有节点的嵌入之间的余弦相似性。
最后，选择与查询具有最高余弦相似性得分的top-k个节点。继续将节点添加到结果集，直到达到预定义的最大标记数，确保不超出模型的输入限制。   

![image-20251223110022273](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/image-20251223110022273.png)

### 基于模型的摘要 

在使用高斯混合模型对节点进行聚类后，将每个聚类中的节点发送到语言模型进行总结。这一步允许模型将大块文本转化为所选节点的简明、连贯摘要。作者使用gpt-3.5-turbo生成摘要。总结步骤将检索到的大量信息压缩为可管理的大小。

### 基本流程：

- 分块与嵌入。
  - 按 100 的大小对文本进行分块，如果一个句子长度超过 100，则直接将句子作为一个文本块，保证块内语义的一致性。（如何断句也很重要！）
  - 对文本块进行 embedding。
- 递归的构建 RAPTOR 树。聚类。文本块及其 embedding 作为树的叶子节点。
  - 通过聚类把相似的块聚在一起。
  - 利用语言模型为簇内的文本生成总结，并为总结生成 embedding，也作为树的一个节点。
  - 递归执行上述过程。
- 查询。即如何检索相关的块。文中提供了两种策略：
  - Tree Traversal Retrieval。遍历树的每一层，剪枝并选择最相关的节点。
  - Collapsed Tree Retrieval。评估整个树中每个节点的相关性，找到最相关的几个。

RAPTOR 树构建流程：

![image-20251126095637344](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/image-20251126095637344.png)

RAPTOR 树的两种查询策略：

![image-20251126095908088](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/image-20251126095908088.png)





整个流程很简洁，方法也很直观。其中比较重要的环节不言而喻：聚类。

即，通过聚类来自底向上构建树，每个叶子结点通过聚类构造其父节点。如[1,2]->[8],那么8就是它们的父节点。

文中选择了软聚类的方式：每个节点可以从属于多个簇。直接上来看，如果簇作为话题，那么每个文本块并不是只属于每个话题的，选择软聚类也是很直观的。文中的聚类算法是基于 GMM 聚类的，且聚类前通过 UMAP（Uniform Manifold Approximation and Projection ） 对文本表征进行了降维。论文对聚类的过程进行了一些改造，并通过 BIC（Bayesian Information Criterion）来选择最优簇数，一些细节可以参考原文。

使用UnifiedQA 3B作为阅读器，SBERT、BM25和DPR作为嵌入模型，在QASPER、NarrativeQA和QuALITY三个数据集上进行受控对比。如表1和表2所示，结果表明，当RAPTOR与任何检索器结合时，在所有数据集上均表现出优于各自检索器的一致性。RAPTOR与SBERT结合具有最佳性能。

比较了RAPTOR与BM25和DPR，在三种不同的LLM上的表现。如表3所示，在QASPER数据集上，RAPTOR始终优于BM25和DPR。

在QuALITY数据集中，如表4所示，RAPTOR的准确率达到62.4%，比DPR和BM25分别提高了2%和5.1%。当使用UnifiedQA时也观察到类似的趋势。

在受控比较的基础上，研究了RAPTOR相对于其他最先进模型的性能。如表5所示，搭配GPT-4的RAPTOR在QASPER上创造了一个新的基准，获得了55.7%的F-1分数，超过了CoLT5 XL的53.9%。

在NarrativeQA数据集中，如表6所示，与UnifiedQA搭配的RAPTOR创造了一个新的最先进的METEOR分数。



在QuALITY数据集中，如表7所示，搭配GPT-4的RAPTOR创造了一个新的最先进，准确率达到82.6%，超过了之前62.3%的最佳结果。

**. 模型配置**

- `qa_model`: 问答模型（默认 GPT3TurboQAModel）
- `embedding_model`: 嵌入模型
- `summarization_model`: 摘要模型

**B. 树构建配置** (前缀 `tb_`)

- `tb_tokenizer`: 分词器
- `tb_max_tokens`: 最大token数（默认100）
- `tb_num_layers`: 树的层数（默认5）
- `tb_threshold`: 聚类阈值（默认0.5）
- `tb_top_k`: 每层保留的top节点数（默认5）
- `tb_selection_mode`: 选择模式
- `tb_summarization_length`: 摘要长度（默认100）
- `tb_cluster_embedding_model`: 聚类嵌入模型（默认OpenAI）

**C. 树检索配置** (前缀 `tr_`)

- `tr_tokenizer`: 检索分词器
- `tr_threshold`: 检索阈值（默认0.5）
- `tr_top_k`: 检索top节点数（默认5）
- `tr_selection_mode`: 检索选择模式
- `tr_context_embedding_model`: 上下文嵌入模型（默认OpenAI）
- `tr_num_layers`: 检索层数
- `tr_start_layer`: 检索起始层





**1. TreeRetrieverConfig 的作用**

`TreeRetrieverConfig` 定义了检索阶段需要的所有参数：

| 参数                    | 作用                                        |
| ----------------------- | ------------------------------------------- |
| tokenizer               | 用于计算 token 长度（限制 max_tokens 时用） |
| threshold               | 阈值选择模式下的相似度阈值                  |
| top_k                   | “top_k”模式下从当前层挑选多少个节点         |
| selection_mode          | “top_k”或“threshold”                        |
| context_embedding_model | 从节点中提取哪种 embedding                  |
| embedding_model         | 用于对 Query 做 embedding 的模型            |
| num_layers              | 检索时往下要遍历几层                        |
| start_layer             | 从第几层开始（通常是最高层）                |

**默认 embedding_model = OpenAIEmbeddingModel**
 这意味着如果你没有传入 embedding_model，它会自动用 OpenAI。

## 关键属性解释

| 属性                  | 用途                                                         |
| --------------------- | ------------------------------------------------------------ |
| tree_builder_config   | TreeBuilder 的详细配置（tokenizer、max_tokens、num_layers、top_k、阈值、摘要长度等） |
| tree_retriever_config | TreeRetriever 的配置（tokenizer、threshold、top_k、层数、起始层、嵌入模型等） |
| embedding_model       | 默认嵌入模型，如果不提供 tb_embedding_models，会自动填充 TreeBuilder/TreeRetriever |
| summarization_model   | 默认摘要模型，如果不提供 tb_summarization_model，会自动填充 TreeBuilder |
| tree_builder_type     | 构建树的类型（cluster 或其他方法）                           |
| log_config()          | 打印当前配置，方便调试                                       |

Node 的用途是什么？

Node 在树构建的不同阶段承担不同职责。

1. 叶节点（Layer 0）

从长文本切出来的 chunks → 每个 chunk 创建一个 leaf Node

你传入的 text 是片段原文：

text = "The economic crisis began with..."


children 是空集合：

children = {}


embeddings = 所有 embedding_models 的 embedding(text)

2. 中间层节点（Layer 1, Layer 2, …）

这些是通过 summarization 生成的：

比如：

summaries_of(chunk_A + chunk_B + chunk_C)


创建为：

Node(text = "Summary of A+B+C", children = {A.index, B.index, C.index})


这样就构成了树结构。

3. 根节点（Final Layer）

当你不断 summarization，最后只剩 1~n 个节点，它们就是 root nodes。

文本内容是整个文档的高层摘要：

"Overall, this document discusses the economic crisis..."\

{

layer 0 (leaf)

  0:  Node(text=chunk0, children={}, embedding=...),
  1:  Node(text=chunk1, children={}, embedding=...),
  2:  Node(text=chunk2, children={}, embedding=...),
  ...
  9:  Node(text=chunk9, children={}, embedding=...),

layer 1

  10: Node(text="summary of chunk0+chunk1", children={0,1}),
  11: Node(text="summary of chunk2+chunk3", children={2,3}),
  12: Node(text="summary of chunk4+chunk5", children={4,5}),
  13: Node(text="summary of chunk6+chunk7", children={6,7}),
  14: Node(text="summary of chunk8+chunk9", children={8,9}),

layer 2

  15: Node(text="summary of nodes {10,11}", children={10,11}),
  16: Node(text="summary of nodes {12,13}", children={12,13}),
  17: Node(text="summary of nodes {14}",    children={14}),

layer 3

  18: Node(text="summary of nodes {15,16}", children={15,16}),
  19: Node(text="summary of node {17}", children={17}),

layer 4 (root)

  20: Node(text="root summary", children={18,19}),
}

          			  [20]
                     /    \
                  [18]    [19]
                 /   \      |
              [15]  [16]    [17]
              / \    / \       |
           [10][11][12][13]   [14]
           / \  / \  / \  / \   / \
          0  1 2 3  4 5  6 7  8   9

🧬 Node 是整个 RAPTOR 架构的核心概念

TreeBuilder、ClusterTreeBuilder、Retriever 全部围绕 Node 工作。

- TreeBuilder 生成 Node（叶节点 → 中间节点 → 根节点）
- Retriever 通过 Node 的 embedding 找到相关节点
- Node.children 构建了层次结构
- Node.text 用于最终召回时的上下文
- Node.embedding 用于向量搜索

假设第 5 个节点是由 chunk1 + chunk2 生成的摘要，它的 Node 可能像这样：

 **Node 节点是什么？**

一个 Node 就是 **树中的一个节点**，用来表示：

- 一段文本
- 对应的 embeddings（多种 embedding 模型）
- 子节点（下一层的节点）
- 节点所在的层
- 节点在树中的唯一 index

换句话说：

**Node = 文本片段 + embeddings + 指向子节点的链接**

它是构建树状语义结构的基本单元。

```python
Node(
    text="In summary, the economic downturn was caused by...",
    index=5,
    children={1, 2},
    embeddings={
        "openai-embedding": [0.23, 0.55, 0.11, ...],
        "bge-large": [0.99, -0.48, 0.22, ...]
    }
)
```

## 总结

对于一个查询，在不考虑性能和其他问题的条件下，最理想的块应该是文本中与查询最相关的那段话、几句话甚至一句话（这就有点像抽取式的文本摘要了）。 





