[NLP/AIGC/GPT] RAG综述 : 检索增强型生成技术,整合智能体的外挂知识库
0 序
-
RAG 检索增强生成(Retrieval Augmented Generation),已经成为当前最火热的LLM应用方案。
-
理解起来不难,就是通过自有垂域数据库检索相关信息,然后合并成为提示模板,给大模型生成漂亮的回答。
1 概述 : RAG
RAG技术的产生背景
- 业界不知道大模型的基本没有,大家对大模型的能力也有一定的了解,但是当我们将大模型应用于实际业务场景时会发现,通用的基础大模型基本无法满足实际业务需求,主要有以下几方面原因:
- 知识的局限性/时效性:模型自身的知识完全源于它的训练数据,而现有的主流大模型(ChatGPT、文心一言、通义千问…)的训练集基本都是构建于网络公开的数据,对于一些实时性的、非公开的或离线的数据是无法获取到的,这部分知识也就无从具备。
- 幻觉问题:所有的AI模型的底层原理都是基于数学概率,其模型输出实质上是一系列数值运算,大模型也不例外,所以它有时候会一本正经地胡说八道,尤其是在大模型自身不具备某一方面的知识或不擅长的场景。而这种幻觉问题的区分是比较困难的,因为它要求使用者自身具备相应领域的知识。
- 数据安全性:对于企业来说,数据安全至关重要,没有企业愿意承担数据泄露的风险,将自身的私域数据上传第三方平台进行训练。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。
而RAG是解决上述问题的有效方案。
RAG技术 是什么?
一句话总结:
RAG(检索增强生成) = 检索技术 + LLM 提示
例如,向 LLM 提问一个问题(answer),RAG 从各种数据源检索相关的信息,并将检索到的信息和问题(answer)注入到 LLM 提示中,LLM 最后给出答案。
- RAG 是2023年基于
LLM的系统中最受欢迎的架构。
许多智能产品基于 RAG 构建,从基于 web 搜索引擎和 LLM 的问答服务到使用私有数据的应用程序。
早在2019年,向量数据库
Faiss就实现了基于嵌入的向量搜索技术,现在RAG推动了向量搜索领域的发展。
比如 chroma、http://weaviate.io 和 pinecone 这些基于开源搜索索引引擎(主要是faiss和nmslib)向量数据库初创公司,最近增加了输入文本的额外存储和其他工具。

- 在这个过程中,有3个主要步骤:
- 文本转嵌入向量、并存储到向量数据库 (预处理)
- 语义搜索:在语义搜索步骤中,从知识库中找到与要回答的查询最相关的部分内容。
- 生成输出:针对查询结果,结合提示词模板,用LLM生成最终结果。
RAG 技术的概念、起源
大家每天都会看到各种 RAG 框架、论文和开源项目,也都知道 RAG (Retrieval-Augmented Generation) 是检索增强型生成。
但大家还记得 RAG 这个概念源自哪里吗?
RAG 概念来自 Facebook AI Research在 2020 年的一篇论文:
《Retrieval-Augmented Generation forKnowledge-Intensive NLP Tasks》
https://arxiv.org/pdf/2005.11401 | 2021.03.12
为什么2020年提出,如今才火起来,很大部分原因是由于基线大模型能力变强,让大模型+知识库的方式,可以回答的问题确实能够满足企业的相当部分需求。

- 内容小结
- RAG概念:
RAG(Retrieval-Augmented Generation)是检索增强型生成,来源于Facebook AI Research 2020年的一篇论文,用于知识密集型NLP任务。
- 兴起原因:
基线大模型能力的增强使得大模型+知识库的方式可以满足企业部分需求,从而推动了RAG概念的普及。
RAG 技术的发展历程

- 2020年左右就出现了RAG技术,代表了LLM领域内增强生成任务的一种新范式。
RAG通过结合检索(Retrieval)和生成(Generation)两大核心技术,增强了大型语言模型的功能。
Naive RAG

缺点与局限性
- 性能和效率问题:RAG技术在实际应用中,尤其是在数据丰富且复杂的企业环境中,可能会遇到性能和效率的问题
比如,检索数据量大,比较耗时,然后大模型多轮对话和评分也比较费时间。
-
低命中率问题:当用户意图明确时,RAG技术可能无法提供高召回率或精度,导致命中率较低。此外,如果用户意图不明确,RAG系统可能无法直接作答,存在语义gap,简单的检索方式难以找到答案。
-
语义搜索的不准确:语义搜索的难点在于如何理解用户的问题和文档的语义,以及如何衡量问题和文档之间的语义相似度。
向量空间中的距离或相似度并不一定能反映真实的语义相似度,检索能力没有大模型的语义理解能力,导致检索出的内容相关性不强
当然,比如GraphRAG能缓解很多。
- 冗余和重复也是一个问题:特别是当多个检索的段落包含相似的信息时,会导致生成的响应中出现重复的内容。
技术原理与架构

RAG的大致流程包括:(如图示)
- 知识提取:从用户给定的文档、图片、表格和外部URL等资源中提取内容
涉及到pdf数据提取,表格数据提取,还有图片ocr识别后的数据提取,还有网页文档数据的提取,包括结构化数据和非结构化数据。
- 知识索引:通过chunking(分块,可认为是将连续的文本分成一个个小块)进行合理切割,再使用embedding将文本变成向量数据存入向量数据库或elasticsearch等数据库中。
结合这些非结构化文件所附带的元数据(时间、文件名、作者、副标题、文件类型等)进行索引(高级索引可以是树结构、图结构等)创建。
- 知识检索:当RAG接受到用户提问内容,先将内容通过embedding转化为向量数据,然后和前面建立的索引进行相似度匹配,系统优先检索与query最相似的前K个块。
比如从百万的chunks中找出匹配度较高的100个。然后再将这100个chunk进行更耗时也更精准的重排序(使用交叉熵校验的Rerank算法),将最相关的top3结果找到。
- 生成:最后RAG会用户问题、经过技术处理的top3的chunks,还有prompt,一起送入大语言模型,让它生成最终的答案。
如下开始详细解释:
RAG原理与实现过程
- 目前已经知道RAG融合是一种用于(可能)提升RAG应用检索阶段的技术。
下面这张图片展示了大概的工作流程。
基本上,主要思路就是利用LLM来生成多个查询,期望能够通过这些查询让问题的各个方面在上下文中显现出来。
之后你可以使用生成的查询进行向量搜索(如本系列之前的部分所述),并且基于其在结果集中的显示方式来对内容进行重新排序(rerank)。

- 可以用下面提示词生成额外问题:
You are a helpful assistant that generates multiple search queries based on a single input query.
Generate multiple search queries related to: {USER_INPUT}
OUTPUT (4 queries):

如上所述,LLM能够生成覆盖原问题多个方面的查询。
这样可以在数据库中找到包含各个相关方面的信息,从而潜在地提高从RAG应用得到的结果。
RAG架构

RAG的架构如图中所示
简单来讲,RAG就是通过向量检索获取相关的知识、并将其融入Prompt,让大模型能够参考相应的知识从而给出合理回答。
因此,可以将RAG的核心理解为“检索+生成”,前者主要是利用向量数据库的高效存储和检索能力,召回目标知识;
后者则是利用大模型和Prompt工程,将召回的知识合理利用,生成目标答案。
- 完整的RAG应用流程,主要包含:
- 数据准备阶段:数据提取 --> 文本分割 --> 向量化(embedding) --> 数据入库
- 应用阶段:用户提问 -->数据检索(召回) --> 注入Prompt --> LLM生成答案
数据准备阶段
- 数据准备一般是一个离线的过程,主要是将【私域数据】向量化后构建索引并存入向量数据库的过程。
主要包括:数据提取、文本分割、向量化、数据入库等环节。

-
数据提取
-
- 数据加载:包括多格式数据加载、不同数据源获取等,根据数据自身情况,将数据处理为同一个范式。
- 数据处理:包括数据过滤、压缩、格式化等。
- 元数据获取:提取数据中关键信息,例如文件名、Title、时间等 。
-
文本分割:
文本分割主要考虑几个因素:
1)embedding模型的Tokens限制情况;
2)语义完整性对整体的检索效果的影响。
详情参见: 本文文本分块策略章节。
- 向量化(embedding):
- 向量化是一个将文本数据转化为向量矩阵的过程,该过程会直接影响到后续检索的效果。
- 目前常见的embedding模型如表中所示,这些embedding模型基本能满足大部分需求,但对于特殊场景(例如涉及一些罕见专有词或字等)或者想进一步优化效果,则可以选择开源Embedding模型微调或直接训练适合自己场景的Embedding模型。
详情参见: 本文嵌入模型章节。
- 数据入库:
数据向量化后构建索引,并写入数据库的过程可以概述为数据入库过程
适用于RAG场景的(向量)数据库包括:FAISS、Chromadb、ES、milvus等。
一般可以根据业务场景、硬件、性能需求等多因素综合考虑,选择合适的数据库。
详情参见: 本文向量数据库章节。
应用阶段
- 在应用阶段,可以根据用户的提问,通过高效的检索方法,召回与提问最相关的知识,并融入Prompt;
- 大模型参考当前提问和相关知识,生成相应的答案。
- 关键环节包括:数据检索、注入Prompt等。

- 数据检索
常见的数据检索方法包括:相似性检索、全文检索等,根据检索效果,一般可以选择多种检索方式融合,提升召回率。
-
- 相似性检索:即计算查询向量与所有存储向量的相似性得分,返回得分高的记录。常见的相似性计算方法包括:余弦相似性、欧氏距离、曼哈顿距离等。
- 全文检索:全文检索是一种比较经典的检索方式,在数据存入时,通过关键词构建倒排索引;在检索时,通过关键词进行全文检索,找到对应的记录。
-
注入Prompt:
Prompt作为大模型的直接输入,是影响模型输出准确率的关键因素之一。- 在
RAG场景中,Prompt一般包括任务描述、背景知识(检索得到)、任务指令(一般是用户提问)等,根据任务场景和大模型性能,也可以在Prompt中适当加入【其他指令优化大模型】的输出。
一个简单知识问答场景的Prompt如下所示:
【任务描述】
假如你是一个专业的客服机器人,请参考【背景知识】,回答
【背景知识】
{content} // 数据检索得到的相关文本
【问题】
石头扫地机器人P10的续航时间是多久?
Prompt的设计只有方法、没有语法,比较依赖于个人的专业领域经验,在实际应用过程中,往往需要根据大模型的实际输出进行针对性的Prompt调优。
详情参见:本文
Prompt/提示词工程章节
关键技术:文本分块策略
文本分块算法的产生背景
- 文本分块技术的产生背景、意义
遇到很多人在使用RAG功能时,都知道一些概念,大概知道怎么做,拿到原始文档,导到系统中,进行自动分块,自动embedding,结果召回效果并不好。
这时候有些人可能会抱怨功能不行,然后探索怎样才能达到好的效果,这时候我们注意到原始文档的分块对于检索是非常重要的。
- RAG 开发者在使用RAG框架时,最常常疑惑:如何制定正确的分块策略?
文本分块的作用 / 为什么要文本分块?
- 文本分块的定义:
从技术角度来说,“分块”是指将大量文档分割成更小、更易于管理的部分,以便模型有效地检索和处理。
分块策略至关重要,原因如下:
- 相关性和精确度:
适当分块的文档可确保检索到的信息与查询高度相关。
如果分块太大,它们可能包含大量不相关的信息,从而稀释有用的内容。
相反,如果分块太小,它们可能会错过更广泛的背景,导致响应准确但不够全面。
- 效率和性能:
区块的大小和结构会影响检索过程的效率。
较小的区块可以更快地检索和处理,从而减少系统的整体延迟。
但是,需要取得平衡,因为太多的小区块可能会使检索系统不堪重负并对性能产生负面影响。
- 生成质量:
生成的输出质量在很大程度上取决于检索到的输入。
分块良好的文档可确保RAG生成器能够访问连贯且上下文丰富的信息,从而产生更具信息性、连贯性和上下文恰当的响应。
- 可扩展性:
随着语料库规模的增长,分块变得更加重要。
经过深思熟虑的分块策略可确保系统能够有效扩展,管理更多文档,而不会显著降低检索速度或检索质量。
不同的领域和查询类型可能需要不同的分块策略。灵活的分块方法允许 RAG 系统适应各种领域和信息需求,从而最大限度地提高其在不同应用程序中的有效性。
文本分块算法:固定大小切分
- 最直观的切分方法是根据预定的字符数、单词数或Token数量将文本均匀分割成若干段落
由于直接切分可能会破坏语义流畅性,建议在连续段落间保留一些重叠
这是最粗暴、最简单的文本分块方法。它将文本分解为指定字符数的块,而不考虑其内容或结构。
- 方法特点:
- 这种方法最简单粗暴、易于实现,而且所有段落大小相同,有助于简化批处理
- 但它存在一个大问题:通常会在句子(或想法)中途切分,导致重要信息可能分散在不同段落中
文本分块算法:语义切分 (句子/段落/主题部分/...)
概念很简单
-
根据句子、段落或主题部分等有意义的单元来切分文档
-
接着,为每个段落生成嵌入
-
假设从第一个段落及其嵌入开始
-
如果第一个段落的嵌入与第二个段落的嵌入余弦相似度较高,则两个段落组成一个切片,这个过程持续进行,直到余弦相似度显著下降
-
一旦下降,我们就开始一个新切片、并重复此过程
-
此分块方法旨在从嵌入中提取语义含义,然后评估这些块之间的语义关系。
-
核心思想是将语义相似的块放在一起。

余弦相似度,又称余弦相似性,是通过计算两个向量的夹角余弦值来评估二者的相似度。
余弦相似度将向量根据坐标值,绘制到向量空间中.如: 最常见的二维空间。

import numpy as np
vector1 = np.array([1,2,3])
vector2 = np.array([4,5,6])
cos_similarity = np.dot(vector1, vector2) / ( np.linalg.norm(vector1) * np.linalg.norm(vector2) )
print(f"余弦相似度:", cos_similarity)
- 方法特点:
- 概念简单
- 这种方式与固定大小切片不同,能够保持语言的自然流畅性,并保留完整的思想
- 由于每个切片语义更为丰富,它提高了检索准确度,进而使LLM生成的响应更加连贯
- 且相关 一个小问题是,确定余弦相似度下降的阈值在不同文档间可能有所不同
文本分块算法:递归切分
虽然固定大小分块更容易实现,但它没有考虑文本的结构;递归分块提供了一种替代方案。
在此方法中,我们使用一组分隔符以分层和迭代的方式将文本划分为较小的块。
如果首次尝试拆分文本未产生所需大小的块,则:该方法将使用不同的分隔符对生成的块进行递归调用,直到达到所需的块大小。
- 切分步骤
- 首先,基于内在的分隔符(如段落或章节)进行切分
- 然后,如果某个切片的大小超过预定义的切片大小限制,就将其进一步分割。如果切片符合大小限制,则不再进行切分
- 方法特点
非常简单
- 开源框架的实现情况
Langchain框架:提供了RecursiveCharacterTextSplitter类,它使用默认分隔符(“\n\n”、“\n”、“,”)拆分文本。
文本分块算法:基于文档固有结构的分块
- 在这种分块方法中,我们根据文档的固有结构对其进行拆分。
利用文档内在的结构(如标题、章节或段落)定义切片边界
典型文档: 高度结构化的 markdown 文本、csv 表格文档
- 方法特点
- 这种方法考虑了内容的流程和结构,但对于缺乏清晰结构的文档可能不那么有效。
该方法假设文档结构清晰,但这可能并非总是如此。
此外,切片长度可能不同,甚至超过LLM模型的Token限制。
可以尝试与递归切分结合使用
带有Markdown的文档
- Langchain提供MarkdownTextSplitter类来分割以markdown为分隔符的文档。
使用 Python/JS 的文档
- Langchain 提供
PythonCodeTextSplitter来根据类、函数等拆分 Python 程序,并且我们可以将语言提供给RecursiveCharacterTextSplitter 类的 from_language 方法。
包含表格的文档
- 处理表格时,基于 1 级和 2 级的拆分可能会丢失行和列之间的表格关系。
- 为了保留这种关系,请以语言模型可以理解的方式格式化表格内容(例如,使用HTML 中的标签、以“;”分隔的 CSV 格式等)。
- 在语义搜索期间,直接从表格中匹配嵌入可能具有挑战性。
- 开发人员通常在提取后总结表格,生成该摘要的嵌入,并将其用于匹配。
文本分块算法:基于LLM的切分
由于每种方法都有其优缺点,为什么不让LLM来生成切片呢
LLM可以通过提示词生成语义隔离且有意义的切片
显然,这种方法确保了高语义准确性,因为LLM能理解上下文和意义,远超简单的启发式方法
唯一的问题是,这种方式的计算成本是五种方法中效率最低的
LumberChunker
我们在实际使用过程中,有些人想将长篇的文章甚至小说导入
RAG中,基于长篇叙述文档分割,这边论文提供了一个方法:
《LumberChunker: Long-Form Narrative Document Segmentation / LumberChunker:长篇叙事文件分割》 https://arxiv.org/pdf/2406.17526
LumberChunker是一种利用LLM将文档动态分割成语义独立的块的方法。
这种方法基于一个前提:当内容块的大小可以变化时,检索效率会提高,因为这样可以更好地捕捉内容的语义独立性。
它以迭代方式提示LLM识别一组连续段落中内容开始转变的点,从而确保每个块在上下文中是连贯的,但与相邻块有所区别。
- LumberChunker遵循一个三步流程:
首先,按段落对文档进行分割。
其次,通过追加连续的块,创建一个组(Gi),直到超过预定义的标记计数θ。
最后,将Gi作为上下文输入到Gemini等LLM,LLM确定显著内容转变开始出现的ID,从而定义了Gi+1的开始和当前块的结束。
这个过程在整个文档中循环重复。

LumberChunker也有不足:
- 尽管它在性能上更优,但它需要使用LLM,这使得它在成本和速度上比传统方法要更高、更慢。
- LumberChunker专门设计用于叙事文本,对于高度结构化的文本,可能不是最优解决方案。
开源框架的切分算法实现
- 我们在使用
Llamindex、Langchain等框架时,都提供了一些封装好的分块技术。
Chunking Techniques For RAG
| Technique | Usecase | Pros | Cons |
|---|---|---|---|
| Character splitter | Text | - Versatile: Handles various separators - Flexible: Adapts to different languages - Cost-Effective: Does not require a ML model |
- Performance: May have increased computational load - Complexity: Requires parameter tuning - Sentence Interruption: May cut sentences midway |
| Recursive character splitter | Text, code | - Versatile: Handles various separators - Flexible: Adapts to different languages - Cost-Effective: Does not require a ML model |
- Performance: Recursive nature may increase computational load - Complexity: Requires parameter tuning - Sentence Interruption: May cut sentences midway |
| Sentence splitter | Text | - Considers Sentence Boundaries: Avoids cutting sentences prematurely - Customizable: Parameters for stride and overlap - Cost-Effective: Works with light sentence segmenter |
- Lack of Versatility: Limited to sentence-based chunks - Overlap Issues: May lead to redundancy |
| Semantic splitter | Text, Chat | - Contextual Grouping: Organizes text based on semantic similarity - Overcomes Challenges: Handles chunk size and overlap |
- Complexity: Requires similarity model and tuning - Parameter Dependency: Relies on setting appropriate parameters - Resource Intensive: Demands computational resources |
| Propositions | Text, Chat | - Atomic Expression: Introduces novel retrieval unit (propositions) - Distinct Factoids: Each proposition is self-contained - Contextualization: Provides necessary context |
- Complexity: Requires LLM model - Parameter Dependency: Relies on setting appropriate prompt - Resource Intensive: Demands computational resources |
最佳实践
- 网友经验[1]:
首先,在上传文档的时候需要对文档进行处理,将文档相关的内容做为一段,每段之间使用特殊符号分隔,例如“||||”;
然后,在自定义参数中按照上述配置。虽然这样,前期工作会比较复杂,但是这样处理之后检索的效率会高很多。
- 分块技术作为RAG中不可或缺的一环,选择一个合适自己知识库的分块方式,尤其重要。
理解和利用这些方法可以优化文本处理和分析,从而提高 RAG 模型和类似任务的性能。
关键技术:嵌入模型(文本转向量)
| 模型名称 | 描述 | 获取地址 |
|---|---|---|
| ChatGPT-Embedding | ChatGPT-Embedding由OpenAI公司提供,以接口形式调用。 | https://platform.openai.com/docs/guides/embeddings/what-are-embeddings |
| ERNIE-Embedding V1 | ERNIE-Embedding V1由百度公司提供,依赖于文心大模型能力,以接口形式调用。 | https://cloud.baidu.com/doc/WENXINWORKSHOP/s/alj562vvu |
| M3E | M3E是一款功能强大的开源Embedding模型,包含m3e-small、m3e-base、m3e-large等多个版本,支持微调和本地部署。 | https://huggingface.co/moka-ai/m3e-base |
| BGE | BGE由北京智源人工智能研究院发布,同样是一款功能强大的开源Embedding模型,包含了支持中文和英文的多个版本,同样支持微调和本地部署。 | https://huggingface.co/BAAI/bge-base-en-v1.5 |
关键技术:向量数据库(存储向量数据)
数据向量化后构建索引,并写入数据库的过程可以概述为数据入库过程
适用于RAG场景的(向量)数据库包括:FAISS、Chromadb、ES、milvus等。
一般可以根据业务场景、硬件、性能需求等多因素综合考虑,选择合适的数据库。
关键技术:提示词工程
原始 RAG/Naive RAG
- 本文
RAG管道从一个文本文档语料库开始,直接跳过如何通过数据加载器从Youtube等数据源获取步骤。

- 标准的 RAG 流程简介:
首先,将文本分块;
然后,使用一些Transformer Encoder模型将这些块嵌入到向量中,将所有向量放入索引中;
最后,创建一个LLMPrompt,告诉模型根据搜索步骤中找到的上下文回答用户的查询。
- 在运行时,通过使用同一编码器模型对用户的查询进行向量化,然后搜索该查询向量的索引,找到
top-k个结果,从数据库中检索相应的文本块,并将它们作为上下文输入到LLM提示中。
提示与下边内容类似:
def question_answering(context, query):
prompt = f"""
Give the answer to the user query delimited by triple backticks ```{query}```\
using the information given in context delimited by triple backticks ```{context}```.\
If there is no relevant information in the provided context, try to answer yourself,
but tell user that you did not have any relevant context to base your answer on.
Be concise and output the answer of size less than 80 tokens.
"""
response = get_completion(instruction, prompt, model="gpt-3.5-turbo")
answer = response.choices[0].message["content"]
return answer
- 提示工程是提升 RAG 流程性能的一种简便有效的方法。
可以查阅 OpenAI 等LLM大模型厂商提供的详尽的提示工程指南。
虽然 OpenAI 是 LLM 提供商的领头羊,但还有其他不少选择,例如deepseek,qwen, Anthropic 的 Claude,Mistral 的小型但功能强大的模型Mixtral,Microsoft 的Phi-2,以及如Llama2,OpenLLaMA,Falcon等众多开源模型,可以选择最合适的,作为 RAG 管道大脑。
高级 RAG
- 接下来深入讲解高级 RAG 技术,包括所涉及的核心步骤和算法的方案,但是省略了一些逻辑循环和复杂的多步代理行为,以保持方案的可读性。

上图中绿色部分是接下来详细探讨的核心 RAG 技术。
一张图并不能全部展示所有的高级 RAG 技术,比如这里省略了上文扩展技术。
1:分块 (Chunking) & 向量化 (Vectorisation)
首先需要为文档内容创建向量索引,然后在运行时搜索与查询向量余弦距离最近的向量索引,这样就可以找到与查询内容最接近语义的文档。
1.1 分块 (Chunking)
Transformer 模型具有固定的输入序列长度,即使输入上下文窗口很大,一个句子或几个句子的向量也比几页文本的向量更能代表其语义含义,因此对数据进行分块—— 将初始文档拆分为一定大小的块,而不会失去其含义。有许多文本拆分器实现能够完成此任务。
块的大小是一个需要重点考虑的问题。块的大小取决于所使用的嵌入模型以及模型需要使用 token 的容量。如基于 BERT 的句子转换器,最多需要 512 个 token,OpenAI ada-002 能够处理更长的序列,如 8191 个 token,但这里的折衷是 LLM 有足够的上下文来推理,而不是足够具体的文本嵌入,以便有效地执行搜索。有一项关于块大小选择的研究。在 LlamaIndex 中,NodeParser 类很好支持解决这个问题,其中包含一些高级选项,例如定义自己的文本拆分器、元数据、节点/块关系等。
1.2 向量化 (Vectorisation)
下一步是选择一个搜索优化的模型来嵌入块。有很多选项,比如 bge-large 或 E5 嵌入系列。只需查看 MTEB 排行榜以获取最新更新即可。
有关分块和向量化步骤的 end2end 实现,请查看 LlamaIndex 中完整数据摄取管道的示例。
2. 搜索索引
2.1 向量存储索引

RAG 管道的关键部分是搜索索引,它存储了在上一步中获得的向量化内容。最原始的实现是使用平面索引 — 查询向量和所有块向量之间的暴力计算距离。
为了实现1w+元素规模的高效检索,搜索索引应该采用向量索引,比如 faiss、nmslib 以及 annoy。这些工具基于近似最近邻居算法,如聚类、树结构或HNSW算法。
此外,还有一些托管解决方案,如 OpenSearch、ElasticSearch 以及向量数据库,它们自动处理上面提到的数据摄取流程,例如Pinecone、Weaviate和Chroma。
取决于索引选择、数据和搜索需求,还可以存储元数据,并使用元数据过滤器来按照日期或来源等条件进行信息检索。
LlamaIndex 支持多种向量存储索引,同时也兼容其他简单的索引类型,如列表索引、树索引和关键词表索引。
2.2 分层索引

在大型数据库的情况下,一个有效的方法是创建两个索引——一个由摘要组成,另一个由文档块组成,然后分两步进行搜索,首先通过摘要过滤掉相关文档,然后只在这个相关组内搜索。
2.3 假设性问题和 HyDE
另一种方法是让 LLM 为每个块生成一个问题,并将这些问题嵌入到向量中,在运行时对这个问题向量的索引执行查询搜索(将块向量替换为索引中的问题向量),然后在检索后路由到原始文本块并将它们作为 LLM 获取答案的上下文发送。
这种方法提高了搜索质量,因为与实际块相比,查询和假设问题之间的语义相似性更高。
还有一种叫做 HyDE 的反向逻辑方法——你要求 LLM 在给定查询的情况下生成一个假设的响应,然后将其向量与查询向量一起使用来提高搜索质量。
2.4 内容增强
这里的内容是将相关的上下文组合起来供 LLM 推理,以检索较小的块以获得更好的搜索质量。
有两种选择:一种是围绕较小的检索块的句子扩展上下文,另一种是递归地将文档拆分为多个较大的父块,其中包含较小的子块。
2.4.1 语句窗口检索器
在此方案中,文档中的每个句子都是单独嵌入的,这为上下文余弦距离搜索提供了极大的查询准确性。
为了在获取最相关的单个句子后更好地推理找到的上下文,将上下文窗口扩展为检索到的句子前后的 k 个句子,然后将这个扩展的上下文发送到 LLM。

绿色部分是在索引中搜索时发现的句子嵌入,整个黑色 + 绿色段落被送到 LLM 以扩大其上下文,同时根据提供的查询进行推理。
2.4.2 自动合并检索器(或父文档检索器)
这里的思路与语句窗口检索器非常相似——搜索更精细的信息片段,然后在在LLM 进行推理之前扩展上下文窗口。文档被拆分为较小的子块,这些子块和较大的父块有引用关系。

首先在检索过程中获取较小的块,然后如果前 k 个检索到的块中有超过 n 个块链接到同一个父节点(较大的块),将这个父节点替换成给 LLM 的上下文——工作原理类似于自动将一些检索到的块合并到一个更大的父块中,因此得名。请注意,搜索仅在子节点索引中执行。
2.5 融合检索或混合搜索
这是一个很早以前的思路:结合传统的基于关键字的搜索(稀疏检索算法,如 tf-idf 或搜索行业标准 BM25)和现代语义或向量搜索,并将其结果组合在一个检索结果中。
这里唯一的关键是如何组合不同相似度分数的检索结果。这个问题通常通过 Reciprocal Rank Fusion 算法来解决,该算法能有效地对检索结果进行重新排序,以得到最终的输出结果。

在 LangChain 中,这种方法是通过 Ensemble Retriever 来实现的,该类将你定义的多个检索器结合起来,比如一个基于 faiss 的向量索引和一个基于 BM25 的检索器,并利用 RRF 算法进行结果的重排。
在 LlamaIndex 中,这一过程也是以类似的方式 实现 的。
混合或融合搜索通常能提供更优秀的检索结果,因为它结合了两种互补的搜索算法——既考虑了查询和存储文档之间的语义相似性,也考虑了关键词匹配。
3. 重排(reranking)和过滤(filtering)
使用上述任何算法获得了检索结果,现在是时候通过过滤、重排或一些转换来完善它们了。在 LlamaIndex 中,有各种可用的后处理器,根据相似性分数、关键字、元数据过滤掉结果,或使用其他模型(如 LLM)、sentence-transformer 交叉编码器,Cohere 重新排名接口或者基于元数据重排它们。
这是将检索到的上下文提供给 LLM 以获得结果答案之前的最后一步。
现在,将讨论更高级的 RAG 技术,比如查询转换和路由。这些技术涉及到大语言模型的使用,代表了一种更复杂的逻辑思维——在 RAG 流程中融合了 LLM 的推理能力。
4. 查询转换
查询转换是一系列技术,使用 LLM 作为推理引擎来修改用户输入以提高检索质量。有很多技术实现可供选择。

对于复杂的查询,大语言模型能够将其拆分为多个子查询。比如,
- 当你问:“在 Github 上,Langchain 和 LlamaIndex 这两个框架哪个更受欢迎?”,
一般不太可能直接在语料库找到它们的比较,所以将这个问题分解为两个更简单、具体的合理的子查询:
- “Langchain 在 Github 上有多少星?”
- “Llamaindex 在 Github 上有多少星?”
这些子查询会并行执行,检索到的信息随后被汇总到一个 LLM 提示词中。这两个功能分别在 Langchain 中以多查询检索器的形式和在 Llamaindex 中以子问题查询引擎的形式实现。
- Step-back prompting 使用 LLM 生成一个更通用的查询,以此检索到更通用或高层次的上下文,用于为原始查询提供答案。同时执行原始查询的检索,并在最终答案生成步骤中将两个上下文发送到 LLM。这是 LangChain 的一个示例实现。
- 查询重写使用 LLM 来重新表述初始查询,以改进检索。LangChain 和 LlamaIndex 都有实现,个人感觉LlamaIndex 解决方案在这里更强大。
5. 聊天引擎
关于构建一个可以多次用于单个查询的完美 RAG 系统的下一件工作是聊天逻辑,就像在 LLM 之前时代的经典聊天机器人中一样考虑到对话上下文。
这是支持后续问题、代词指代或与上一个对话上下文相关的任意用户命令所必需的。它是通过查询压缩技术解决的,将聊天上下文与用户查询一起考虑在内。
与往常一样,有几种方法可以进行上述上下文压缩——一个流行且相对简单的 ContextChatEngine,首先检索与用户查询相关的上下文,然后将其与内存缓冲区中的聊天记录一起发送到 LLM,以便 LLM 在生成下一个答案时了解上一个上下文。
更复杂的情况是 CondensePlusContextMode——在每次交互中,聊天记录和最后一条消息被压缩到一个新的查询中,然后这个查询进入索引,检索到的上下文与原始用户消息一起传递给 LLM 以生成答案。
需要注意的是,LlamaIndex 中还支持基于 OpenAI 智能体的聊天引擎,提供更灵活的聊天模式,Langchain 还支持 OpenAI 功能 API。

还有像 ReAct 智能体 这样的其他聊天引擎类型,但接下来将直接跳转到第 7 节,讨论智能体本身。
6. 查询路由
查询路由是 LLM 驱动的决策步骤,决定在给定用户查询的情况下下一步该做什么——选项通常是总结、对某些数据索引执行搜索或尝试许多不同的路由,然后将它们的输出综合到一个答案中。
查询路由器还用于选择数据存储位置来处理用户查询。这些数据存储位置可能是多样的,比如传统的向量存储、图形数据库或关系型数据库,或者是不同层级的索引系统。在处理多文档存储时,通常会用到摘要索引和文档块向量索引这两种不同的索引。
定义查询路由器包括设置它可以做出的选择。
选择特定路由的过程是通过大语言模型调用来实现的,其结果按照预定义的格式返回,以路由查询指定的索引。如果是涉及到关联操作,这些查询还可能被发送到子链或其他智能体,如下面的多文档智能体方案所展示的那样。
LlamaIndex 和 LangChain 都提供了对查询路由器的支持。
7. 智能体(Agent)
智能体( Langchain 和 LlamaIndex 均支持)几乎从第一个 LLM API 发布开始就已经存在——这个思路是为一个具备推理能力的 LLM 提供一套工具和一个要完成的任务。这些工具可能包括一些确定性功能,如任何代码函数或外部 API,甚至是其他智能体——这种 LLM 链接思想是 LangChain 得名的地方。
智能体本身就是一个复杂的技术,不可能在 RAG 概述中深入探讨该主题,所以我将继续基于 agent 的多文档检索案例,并简要提及 OpenAI 助手,因为它是一个相对较新的概念,在最近的 OpenAI 开发者大会上作为 GPTs 呈现,并在下文将要介绍的 RAG 系统中发挥作用。
OpenAI 助手基本上整合了开源 LLM 周边工具——聊天记录、知识存储、文档上传界面。最重要的是函数调用 API, 其提供了将自然语言转换为对外部工具或数据库查询的 API 调用的功能。
在 LlamaIndex 中,有一个 OpenAIAgent 类将这种高级逻辑与 ChatEngine 和 QueryEngine 类结合在一起,提供基于知识和上下文感知的聊天,以及在一个对话轮次中调用多个 OpenAI 函数的能力,这真正实现了智能代理行为。
来看一下多文档智能体的方案—— 这是一个非常复杂的配置,涉及到在每个文档上初始化一个Agent(OpenAIAgent),该智能体能进行文档摘要制作和传统问答机制的操作,还有一个顶层智能体,负责将查询分配到各个文档智能体,并综合形成最终的答案。
每个文档智能体都有两个工具:向量存储索引和摘要索引,它根据路由查询决定使用哪一个。对于顶级智能体来说,所有文档智能体都是其工具。
该方案展示了一种高级 RAG 架构,其中每个智能体都做路由许多决策。这种方法的好处是能够比较不同的解决方案或实体在不同的文档及其摘要中描述,以及经典的单个文档摘要和 QA 机制——这基本上涵盖了最常见的与文档集合聊天的用例。

这种复杂配置的缺点可以通过图片发现 —— 由于需要在智能体内部的大语言模型之间进行多次往返迭代,其运行速度较慢。顺便一提,LLM 调用通常是 RAG 管道中耗时最长的操作,而搜索则是出于设计考虑而优化了速度。因此,对于大型的多文档存储,我建议考虑对此方案进行简化,以便实现扩展。
8. 响应合成
这是任何 RAG 管道的最后一步——根据检索的所有上下文和初始用户查询生成答案。
最简单的方法是将所有获取的上下文(高于某个相关性阈值)与查询一起连接并提供给 LLM。但是,与往常一样,还有其他更复杂的选项,涉及多个 LLM 调用,以优化检索到的上下文并生成更好的答案。
响应合成的主要方法有:
- 通过将检索到的上下文逐块发送到 LLM 来优化答案
- 概括检索到的上下文,以适应提示
- 根据不同的上下文块生成多个答案,然后将它们连接或概括起来。
RAG 融合
和其他软件世界的架构决策一样,RAG融合也有权衡取舍,你需要清楚它们,以便为具体情境做出最好的决定。不过首先,看一下RAG融合的优缺点。
优点:
- 提供多样化的上下文:因为你可以从不同的角度查看用户的查询,所以顶级结果里会出现更多样化的内容片段。与专注于单一视角的内容段落相比,你更有可能看到能够涵盖话题多个方面的内容作为顶级结果出现。
- 额外的控制层面:像其他机器学习解决方案一样,RAG融合提供了额外的控制手柄,让你可以微调你的应用,并让它更加符合你的期望目标。
- 自动校正:通过使用LLM作为用户在文本框中输入内容与实际在数据库中搜索内容之间的中间人,你可以纠正拼写错误,添加与用户查询相关的上下文信息(关于用户的信息、时间、他们的账户状态等),以及/或潜在地筛选特定类型的内容。
- 成本:这是一个有些争议的问题,因为成本既是RAG融合的优点也是缺点,让我来解释一下。你大概知道,向量搜索比LLMs要便宜得多,那么用于RAG融合的额外LLM调用是不是应该会增加应用的整体成本呢?不过……让再来看看LLM的成本。你大概也知道,你可能使用的大多数主流LLMs都采用基于token的计费模式。即使是自己托管LLM,你的成本也会与处理的token数量大致成正比。基本上在这儿向LLM发送两次请求,一次大概有100个token用来生成相似的查询,另一次则会有数千个token,提供相关文本块并希望从LLM那里得到适当的回应。所以基本上第一次对LLM的调用要比第二次便宜10倍到100倍。所以即使RAG融合每10次查询节省一次后续问题,你在成本上还是能领先的。
缺点:
- 延迟:正如你现在可能知道的,LLMs需要大量的计算资源,因此它们的速度相对较慢(相对于应用程序中的其他部分)。根据你的应用程序,向LLM发送一次额外的请求可能会让用户的体验变得糟糕,因为他们可能需要等待几百毫秒的时间。
- 自动纠错:是的,这是一个优点,但是当它不起作用时,也可能是一个缺点。这通常发生在你的内容包含内部术语或行话,而这些术语或行话没有出现在LLM学习过的数据中。在这种情况下,LLM可能会出现困惑并生成完全无关的查询,从而影响到最后的结果。
- 成本:正如之前讨论的,如果RAG融合对你应用程序的整体效益贡献不大,你最终可能会花费更多的费用,但收益却很有限。
有了以上的介绍,接下来讨论一下在什么情况下最有可能通过实现RAG融合得到好的效果。如果你的应用程序的内容主要基于常见概念,那么你更有可能从使用RAG融合中获益良多。

RAG融合n不适用场景
- 如果您拥有的内容包含大量内部行话或与知名品牌重复的词语,则您可能需要调整RAG融合提示以获得良好的效果,或者最好避免使用。
以下是一个例子来说明这一点。
如今,所有的知名LLMs都是基于“注意力就是你所需要”的论文中首次引入的transformer架构,这是一种根据语境中其他单词对生成下一个token的重要性的度量方法。
现在,如果我在该论文的基础上构建一个RAG应用并添加RAG融合功能,其工作方式可能是这样的:(绿色文字表示对RAG融合有贡献的LLM生成的查询)

由于在这个语境下理解“注意力”的含义依赖于上下文,LLM对这个含义产生了误解,并生成了一些与之完全无关的搜索查询。这可能导致您的应用得出糟糕的结果。现在,来看一下通过更改系统提示,将其改为“您是一个有用的助手,可以根据单一输入查询为试图解释transformer架构概念的应用生成多个搜索查询”,是如何在这个特定例子中解决这个问题的。

根据您的具体情况,微调提示可能有用也可能没用。您也可以试试以下技巧,然后才放弃:
- 使用语义搜索找到相关的查询:这个选项适合较为成熟的使用场景,但是如果你有很多用户基础,你可以尝试搜索一个相似的查询数据库,利用它们来寻找相关的内容。
- 通过少量示例学习实现上下文理解:有时候,在给出提示前先给LLM展示几个例子也许能帮助提升效果。
- 微调小型LLM:如果上述方法对你特定的使用场景都没有用(而测试这些方法相对较快),那么你可以考虑微调自己的LLM。这样可能会有效果,因为即使是很小的LLM(即使只有几百万个参数),也有可能足够好到能在特定模板下生成几个相似的查询。请注意,这种方法相比于前面提到的方法,会花费更多的成本和时间,但可能会给你带来最好的效果(代价是增加了复杂性)。
正如您所见,在这种情况下(像HyDe、TF-IDF、BM25或混合搜索等许多其他方法一样),不清楚这种方法是否会胜过针对您具体用例的基本语义搜索功能。但是,就像人们常说的那样,“如果你不衡量,你就无法改进”。因此,我的建议是:一旦构建了基本的RAG应用程序,就要立即创建一个评估过程。在这一过程中,你会有大量的想法去优化你的提示或搜索功能,而每一种改变所带来的附带影响是不清楚的。有时候,提升某一组查询的效果会以牺牲另一组查询为代价。在这种情况下,最好的办法就是把它当作另一种机器学习问题,看看数据告诉你什么。
编码器和 LLM 微调
这种方法主要是对 Transformer 编码器 和 LLM 进行微调。其中,编码器影响嵌入质量,从而影响上下文检索质量。LLM 负责最好地使用提供的上下文来回答用户查询。
如今的一大优势是可以使用像 GPT-4 这样的高端 LLM 来生成高质量的数据集。但是必须清楚,使用小型合成数据集进微调基础模型,可能会降低基础模型的通用能力。
编码器微调
作者进行了一项测试,对 bge-large-en-v1.5 编码器进行微调,发现对于检索效果提升影响有限。因为针对搜索优化的最新 Transformer 编码器已经非常高效。
排序器微调
如果不完全信任基础编码器,可以使用交叉编码器对检索到的结果进行重排。这个过程是这样的:你把查询和每个前 k 个检索到的文本块一起送入交叉编码器,中间用 SEP (分隔符) Token 分隔,并对它进行微调,使其对相关的文本块输出 1,对不相关的输出 0。
LLM 微调
最近,OpenAI 开始提供 LLM 微调 API,LlamaIndex 有一个关于在 RAG 设置中微调 GPT-3.5-turbo 的教程。RAG 管道评估的 ragas 框架显示,忠实度指标增加了 5%,这意味着微调后的 GPT 3.5-turbo 模型比原始模型更好地利用了提供的上下文来生成答案。
Meta AI Research 最近的论文 RA-DIT: Retrieval Augmented Dual Instruction Tuning 展示了一种更复杂的方法,提出了一种同时调整 LLM 和 Retriever 的技术(原始论文中的双编码器)关于查询、上下文和答案的三元组。该技术被用于通过微调 API 微调 OpenAI LLM。也被用于微调了Llama2 开源模型(在原始论文中),结果与带有 RAG 的 Llama2 65B 相比,知识密集型任务指标增加 ~5%和常识推理任务增加几个百分点。
评估
- RAG 系统性能评估的多个框架,都包含了几项独立的指标,例如总体答案相关性、答案基础性、忠实度和检索到的上下文相关性。
- 在之前章节提到的 Ragas,使用真实性和答案相关性来评价生成答案的质量,并使用经典的上下文精准度和召回率来评估 RAG 方案的检索性能。
- 最近推出的课程构建和评估高级 RAG中,以及 LlamaIndex 和评估框架Truelens,他们提出了RAG 三元组评估模式 — 分别是对问题的检索内容相关性、答案的基于性(即大语言模型的答案在多大程度上被提供的上下文的支持)和答案对问题的相关性。
- 最关键且可控的指标是检索内容的相关性 — 实际上是上述高级 RAG 管道的前 1-7 部分加上编码器和排名器的微调部分,这些都是为了提高这个指标。而第 8 部分和大语言模型的微调则专注于提高答案的相关性和基于性。
- 简单有效的检索器评估管道可以应用于编码器的微调部分。一个更高级的方法不仅考虑命中率,还包括了常用的搜索引擎评估指标平均倒数排名 (Mean Reciprocal Rank),以及生成答案的质量指标,如真实性和相关性,这在 OpenAI 的实用指南中有所展示。
- LangChain 提供了一个颇为先进的评估框架 LangSmith。在这个框架中,你不仅可以实现自定义的评估器,还能监控 RAG 管道内的运行,进而增强系统的透明度。
如果你正在使用 LlamaIndex 进行构建,可以尝试 rag_evaluator llama pack。
RAG开源项目
-
有两个最著名的基于 LLM 的管道和应用程序的开源库——LangChain 和 LlamaIndex,它们在 2022 年 10 月和 11 月创立,并在 2023 年受到广泛的采用。
-
本文的目的是参考 LlamaIndex实现,来系统讲解关键的高级 RAG 技术,以方便大家深入研究。
-
问题在于,大多数教程只会针对个别技术进行详细讲解,而不是整体全面地系统化归纳总结。
-
另一件事是,LlamaIndex 和 LangChian 都是了不起的开源项目,他们的开发速度非常快,其各自积累文档已经比2016年的机器学习教科书还要厚。
总结
本文概述 RAG 的核心原理和算法,并举例说明其中的一些方法。
-
RAG融合是一个强大的功能,能够提高RAG应用的语义搜索效率。通过使用语言模型生成多个查询并对搜索结果进行重新排序,RAG融合可以呈现更丰富多样的内容,并提供了一个额外的层次,用于调整应用。此外,RAG融合还可以实现自动纠正、节省成本以及增加内容多样性。但是,需要注意一些权衡,比如潜在的延迟问题、自动纠正的挑战以及成本影响。对于依赖常见概念但可能出现内部行话或重叠词汇的应用来说,RAG融合尤其有用。重要的是要评估RAG融合的表现并测量其影响,以确定它是否适合特定应用场景。
-
还有很多其他的事情需要考虑,比如基于网络搜索的 RAG(LlamaIndex 的 RAG、webLangChain 等),更深入地研究智能体架构以及关于 LLM 长期记忆的一些想法。
-
除了答案相关性和忠实度之外,RAG 系统的主要生产挑战是速度。ChatGPT 和大多数其他助手使用的这种流式特性不是随机的赛博朋克风格,而只是一种缩短感知答案生成时间的方法。
-
这就是为什么我认为小参数规模的 LLM 有一个非常光明的未来,最近发布的 Mixtral 和 Phi-2 正在引领朝着这个方向前进。
Y 推荐文献
2020年,RAG 技术的首篇重磅论文
大型语言模型的检索增强生成:综述
大型语言模型 (LLM) 展示了令人印象深刻的功能,但遇到了幻觉、过时的知识以及不透明、无法追踪的推理过程等挑战。检索增强生成 (RAG) 通过整合来自外部数据库的知识,已成为一种很有前途的解决方案。这提高了生成的准确性和可信度,特别是对于知识密集型任务,并允许持续的知识更新和特定领域信息的集成。RAG 协同地将 LLM 的内在知识与庞大的动态外部数据库存储库相结合。这篇全面的综述论文详细研究了 RAG 范式的进展,包括Naive RAG、Advanced RAG和Modular RAG。它仔细审查了 RAG 框架的三方基础,包括检索、生成和增强技术。
本白皮书重点介绍了嵌入每个关键组件的最新技术,提供了对 RAG 系统进步的深刻理解。
此外,本文还介绍了最新的评估框架和基准。最后,本文描述了目前面临的挑战,并指出了潜在的研发途径。
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号