# 1. RAG技术综述
# 1.1 RAG基本介绍
# 1.1.1 RAG是什么
开源的基座模型参数量不够大,本身拥有的能力有限。要完成复杂的知识密集型的任务,可以基于语言模型构建一个系统,通过访问外部知识源来做到。这样可以使生成的答案更可靠,有助于缓解“幻觉”问题。
RAG 会接受输入并检索出一组相关/支撑的文档,并给出文档的来源。这些文档作为上下文和输入的原始提示词组合,送给文本生成器得到最终的输出。这样 RAG 更加适应事实会随时间变化的情况,这非常有用,因为 LLM 的参数化知识是静态的,RAG 让语言模型不用重新训练就能够获取最新的信息,基于检索生成产生可靠的输出。
# 1.1.2 RAG发展历程
“RAG”概念由Lewis在2020年引入,其发展迅速,标志着研究旅程中的不同阶段。最初,这项研究旨在通过在预训练阶段为它们注入额外知识来增强语言模型。ChatGPT的推出引发了对利用大型模型进行深度上下文理解的高度兴趣,加速了RAG在推断阶段的发展。随着研究人员更深入地探索大型语言模型(LLMs)的能力,焦点转向提升他们的可控性和推理技巧以跟上日益增长的需求。GPT-4 的出现标志着一个重要里程碑,它革新了 RAG ,采取一种将其与微调技术相结合的新方法,并继续优化预训练策略。
# 1.2 RAG技术实现
# 1.2.1 RAG系统组成
RAG 是一个完整的系统,其工作流程可以简单地分为数据预处理、检索、增强和生成四个阶段:
- 数据处理阶段:对原始数据进行清洗和处理,然后将处理后的数据转化为检索模型可以使用的格式,最后存储在对应的数据库中。
- 检索阶段:将用户的问题输入到检索系统中,从数据库中检索相关信息。
- 增强阶段:对检索到的信息进行处理和增强,以便生成模型可以更好地理解和使用。
- 生成阶段:将增强后的信息输入到生成模型中,生成模型根据这些信息生成答案。
# 1.2.2 RAG技术范式
在RAG的技术发展中,我们从技术范式的角度总结了其演变过程,主要分为以下几个阶段:
- 初级RAG:初级RAG主要包括三个基本步骤:1)索引——将文档语料库切分成更短的片段,并通过编码器建立向量索引。2)检索——根据问题和片段之间的相似性检索相关文档片段。3)生成——依赖于检索到的上下文来生成对问题的回答。
- 高级RAG:初级RAG在检索、生成和增强方面面临多重挑战。随后提出了高级RAG范式,涉及到预检索和后检索阶段额外处理。在检索之前,可以使用查询重写、路由以及扩展等方法来调整问题与文档片段之间语义差异。在检索之后,重新排列已获取到的文档语料库可以避免"迷失在中间"现象,或者可以过滤并压缩上下文以缩短窗口长度。
- 模块化RAG:随着RAG技术进一步发展和演变,模块化RAG的概念诞生了。结构上,它更自由灵活,引入更具体功能模块如查询搜索引擎以及多答案融合。技术层面上,它将信息查找与微调、强化学习等技术集成起来。在流程方面,RAG模块设计并协同工作形成各种不同类型RAG。
然而,模块化 RAG 并非突然出现,这三种范式存在继承与发展关系。高级RAG是模块化RAG的特殊情况,而初级RAG是高级RAG的特殊情况。
# 1.3 朴素RAG与Agentic RAG
# 1.3.1 朴素RAG的流程
朴素RAG的基本流程概述:用户输入问题——>问题重构(补全指代信息,保证多轮对话的能力)——>从检索库检索答案——用LLM总结答案
RAG 由两部分组成:
- 第一部分负责在知识库中,根据 query 检索出匹配的文档。
- 第二部分将 query 和文档拼接起来作为 QA 的 prompt,送入 seq2seq 模型,生成回复。
一部分简单场景下,朴素的 RAG 已经可以满足用户意图明确的场景的要求,因为答案已经包含在检索出来的结果中,只要交给 LLM 即可。然而在更多的情况下用户意图并不明确,无法直接通过检索找到答案,例如一些针对多文档的总结类提问需要进行多步推理等。这类场景就需要引入 Agentic RAG ,也就是在问答的过程中引入任务编排机制。
# 1.3.2 Agentic RAG的流程
Agentic RAG,顾名思义,是基于 Agent 的 RAG。Agent 与 RAG 关系紧密,两者互为基石。Agentic RAG 和简单 RAG 的最大区别在于 Agentic RAG 引入了 Agent 的动态编排机制,因此可以根据用户提问的不同意图,引入反馈和查询改写机制,并进行“多跳”式的知识推理,从而实现对复杂提问的回答。
- 开放域问答:直接通过 LLM 产生答案而无需依赖 RAG 检索。
- 多跳问答:首先将多跳查询分解为更简单的单跳查询,重复访问 LLM 和 RAG 检索器来解决这些子查询,并合并它们的答案以形成完整答案。
- 自适应检索:适用于需要多步逻辑推理的复杂问题。复杂的问答往往需要从多个数据源综合信息并进行多步推理。自适应检索通过迭代地访问 RAG 检索器和 LLM,逐步构建起解决问题所需的信息链。
# 1.4 RAG与微调的选择
# 1.4.1 选型建议
除了RAG之外,LLMs的主要优化策略还包括提示工程和微调(FT)。每种都有其独特的特点。根据它们对外部知识的依赖性以及对模型调整的需求,每种都有适合的应用场景。
RAG就像是给模型提供了一本定制信息检索的教科书,非常适合特定的查询。另一方面,FT就像一个学生随着时间内化知识,更适合模仿特定的结构、风格或格式。通过增强基础模型的知识、调整输出和教授复杂指令,FT可以提高模型的性能和效率。然而,它并不擅长整合新知识或快速迭代新用例。RAG和FT并不互斥,它们相辅相成,并且同时使用可能会产生最好的结果。
# 1.4.2 优缺点对比
多数场景会有数据更新的需求,且开源社区在不断放出新的SOTA基座大模型,对于中小型企业而言,建议采用RAG方案去实现。微调模型需要大量的算力和数据,需要专业人士去做,每次训练的成本都很高,且微调后的效果不一定能达到预期。
对比纬度 | RAG | 微调 |
---|---|---|
改模型吗 | 不改,靠外部知识库 | 要改,重新训练模型 |
知识更新 | 实时更新,超灵活 | 更新得重新训练,费劲 |
回答速度 | 查资料,稍微慢点 | 直接输出,快如闪电 |
成本高低 | 低,省钱省力 | 高,数据和算力都要砸钱 |
适合场景 | 知识变化快、范围广 | 专业性强、知识稳定 |
# 2. 构建领域知识库
# 2.1 知识库构建流程
知识库构建是RAG系统是核心步骤,数据质量的好坏对最终构建的RAG系统效果影响很大。通常会先使用算法程序去解析、清洗、切片,然后再经过人工校对(这一过程费时费力,如果愿意牺牲一定的效果,也可跳过此步骤),然后再向量化入库。
# 2.2 解析文件提取知识
PDF文件并非传统意义上的数据格式,而更像是一系列打印指令的集合。这些指令告诉PDF阅读器或打印机如何在屏幕或纸张上展示文字、图像和其他元素。与HTML或DOCX等结构化文档不同,PDF文件不直接包含元素的逻辑结构,这使得从PDF中提取信息变得尤为困难。主要挑战包括:
- 页面布局的复杂性:PDF文档的布局可以非常多样,包括单栏、双栏、多列等,这增加了提取内容的难度。
- 文本提取的不准确性:由于PDF文件中的文本可能以图像形式嵌入,传统的文本提取方法可能无法准确获取所有内容。
- 图像和表格的识别:图像和表格的准确识别与解析是PDF解析中的另一大难题,特别是当它们与文本内容交织在一起时。
文件解析方面,有很多开源项目可以使用,各自做了很多方面的优化,详见我的另一篇博客:基于深度学习的非结构化文档解析 (opens new window)
# 3. 知识库数据检索
# 3.1 稀疏与稠密检索
稀疏检索(Sparse Retrieval, SR)将文档投射到一个稀疏向量上,顾名思义,这个稀疏向量通常与文档的语言词汇一致,如将一篇文章,对每个词进行向量化,随后在词这个维度上进行执行你的检索策略。当然,这个传统的BM25或者TF-IDF也可以做到,但随着Transformer接管了这一领域,你会看到像 SPLADE 这样的方法,使用神经模型来推断与文档相关的词汇,即使这些词汇并不存在。这种方法的好处是,你可以离线处理文章中的词等细粒度的向量表示,从而大大加速检索的效率。【需要的空间小,查询速度慢】
稠密检索(Dense Retrieval, DR)一般指的是将documents编码为稠密向量(Dense Vector),如今一般都通过预训练模型的encoder进行完成,例如BERT或者T5等(GPT这种decoder架构的也可以做到)。随后基于向量数据库(如FAISS)等进行类似于K近邻的搜索方法,来查找与查询内容接近的高维文档向量。【需要的空间大,查询速度快】
# 3.2 信息检索算法
# 3.2.1 BM25检索
在信息检索领域,BM25算法被广泛认为是一种经典且有效的排名函数,用于估计文档与用户查询之间的相关性。BM25(Best Matching 25)是基于Okapi TF-IDF算法的改进版本,旨在解决一些Okapi算法存在的问题。BM25的核心思想是利用词频(TF)和逆文档频率(IDF)来衡量文档与查询之间的相关性,同时引入文档长度信息来进一步调整相关性的计算。
- 词频(TF):词频是衡量一个词在文档中重要性的基本指标。在BM25算法中,词频是通过计算查询中的词在文档中出现的频率来确定的。词频越高,这个词在文档中的重要性越大。
- 逆文档频率(IDF):逆文档频率用于衡量一个词在整个文档集合中的独特性或信息量。它是由整个文档集合中包含该词的文档数量决定的。一个词在很多文档中出现,其IDF值就会低,反之则高。这意味着罕见的词通常有更高的IDF值,从而在相关性评分中拥有更大的权重。
- 文档长度:除了词频和逆文档频率,BM25还引入了文档长度信息来调整相关性的计算。较长的文档可能仅因为它们的长度就有更高的词频,因此需要用文档长度来调整词频的影响。
# 3.2.2 BGE检索
智源研究院发布了一款开源的中英文语义向量模型BGE(BAAI General Embedding),在中英文语义检索精度与整体语义表征能力方面全面超越了OpenAI、Meta等同类模型。BGE模型的发布,标志着语义向量模型(Embedding Model)在搜索、推荐、数据挖掘等领域的应用迈入了一个新的阶段。
- 项目地址:https://github.com/FlagOpen/FlagEmbedding (opens new window)
- 论文地址:https://arxiv.org/pdf/2309.07597 (opens new window)
BGE的技术亮点:
- 高效预训练和大规模文本微调;
- 在两个大规模语料集上采用了RetroMAE预训练算法,进一步增强了模型的语义表征能力;
- 通过负采样和难负样例挖掘,增强了语义向量的判别力;
- 借鉴Instruction Tuning的策略,增强了在多任务场景下的通用能力。

# 4. RAG效果优化
RAG常常陷入一周出Demo、半年用不好的窘境。现在基于工作流的低代码平台(例如RAGFlow、Dify)也很多了,搞出个60分的极其容易,但要把它做到90分是非常困难的,需要对整个流程做全栈优化,我认为依靠这种平台是做不好的,要效果还是直接写代码吧。
- 数据处理:数据采集、文件解析、数据ETL、切分策略、问答对生成、多模态切片、字段设计、数据权限管理、数据更新机制
- 输入输出:语音图片及文件支持、流式输出、流程输出、多模态输出、引用链接输出、参考资料输出、相关内容输出、插件输出
- 流程机制:意图识别、多轮对话、身份习惯、历史记忆、问题扩展、问题重写、问题分解、插件调用、深度思考、联网检索、检索算法、多路召回、重排序、相关性、自我反思、重试、结构化、知识图谱、Text2SQL、内容安全、开放域问题、多跳问题、复合问题
- 优化评估:模型及向量库选型、针对性训练模型、平衡效果与效率、提示词设计、模型参数配置、效果评估指标、用户反馈迭代
# 4.1 RAG流程机制
一个完整的RAG应用开发流程,涉及到文档加载器、向量数据库、检索器、Prompt、记忆、输出解析器、大语言模型、多个功能模块,如下所示:
# 4.2 RAG优化策略
通常RAG的优化策略主要分为:查询转换、路由、问题构建、索引、检索和生成六个方面进行,如下与所示:
# 4.2.1 查询转换
[1] 查询重写和融合策略
如果直接使用原始问题进行检索,可以因为用户的表述偏差导致检索不到相关的文档。多查询重写策略的核心思想是利用 LLM 对原始问题进行扩展、分解或抽象,生成多个语义相关但视角不同的子查询,从而提高检索系统对用户意图的覆盖能力。这种方法能有效解决单一查询可能存在的表述偏差或信息不全问题。
一个简易的prompt如下所示:
你的任务是为给定的用户问题生成3 - 5个语义等价但表述差异化的查询变体,目的是帮助用户克服基于距离的相似性搜索的一些局限性,以便从向量数据库中检索相关文档。
以下是原始问题:
<question>
{{question}}
</question>
请生成3 - 5个语义与原始问题等价,但表述不同的查询变体,用换行符分隔这些替代问题。
请在<查询变体>标签内写下你的答案。
2
3
4
5
6
7
由于需要转换问题一般较小,以及生成子问题时对 LLM 的能力要求并不高,在实际的 LLM 应用开发中,通常使用参数较小的本地模型+针对性优化的 prompt 即可完成任务,并将 temperature
设置为 0
,确保生成的文本更加有确定性。
调用样例如下:
在多查询重写策略中,每个子问题都会检索出相应的文档片段。针对如何合并这些文档的问题,便延伸出多查询结果融合策略。主要思想对其检索结果进行重新排序(即 reranking)后输出 Top K 个结果,最后再将这 Top K 个结果喂给 LLM 并生成最终答案。通常使用的算法是RRF(Reciprocal Rank Fusion),即倒排序排名算法。公式如下:
- 表示检索到的所有相关文档, 则是一个子文档
- 表示所有子问题检索出来的文档列表, 表示某个子问题检索出来的列表。需要注意的是这个列表除了包含子文档,还表示了子文档的按照相关度的排序结果
- 表示当前文档子d在其子集中的位置。
- 是固定常数60,这个是经过实验的最优值。
该算法会对全集 D 进行二重遍历,外层遍历文档全集 D,内层遍历文档子集,在做内层遍历的时候,我们会累计当前文档在其所在子集中的位置并取倒数作为其权重。也就是说如果该子文档在每个子问题检索位置越靠前,则权重越高。
def rrf(results: list[list], k: int = 60) -> list[tuple]:
"""倒数排名融合RRF算法,用于将多个结果生成单一、统一的排名"""
# 1.初始化一个字典,用于存储每一个唯一文档的得分
fused_scores = {}
# 2.遍历每个查询对应的文档列表
for docs in results:
# 3.内层遍历文档列表得到每一个文档
for rank, doc in enumerate(docs):
# 4.将文档使用langchain提供的dump工具转换成字符串
doc_str = dumps(doc)
# 5.检测该字符串是否存在得分,如果不存在则赋值为0
if doc_str notin fused_scores:
fused_scores[doc_str] = 0
# 6.计算多结果得分,排名越小越靠前,k为控制权重的参数
fused_scores[doc_str] += 1 / (rank + k)
# 7.提取得分并进行排序
reranked_results = [
(loads(doc), score)
for doc, score in sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
]
return reranked_results
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[2] 问题分解策略
当提问的原始问题非常复杂时,无论是使用原始问题进行检索,亦或者生成多个相关联的问题进行检索,往往都很难在向量数据库中找到关联性高的文档,导致 RAG 效果偏差。造成这个问题的原因有几种:
- 复杂问题由多个问题按顺序步骤组成,执行相似性搜索时,向量数据库存储的都是基础文档数据,往往相似度低,但是这些数据在现实世界又可能存在很大的关联(文本嵌入模型的限制,一条向量不可能无损记录段落信息)。
- 问题复杂度高或者涉及到数学问题,导致 LLM 没法一次性完成答案的生成,一次性传递大量的相关性文档,极大压缩了大语言模型生成内容上下文长度的限制
问题分解策略就是将一个复杂问题分解成多个子问题或者子步骤。问题分解后的子问题跟原始问题是“父子”关系,而查询重写跟原始问题则是“兄弟”关系。问题分解策略包含串行模式和并行模式:
- 串行模式适用于逻辑依赖强的问题分解,确保步骤的连贯性。如“RAG都有哪些阶段?”,需要先找到都有哪些阶段,然后再询问各个阶段该做什么事情。
- 并行模式适用于独立子任务的高效处理,提升响应速度。如“如何规划北京到上海的 5 天旅游行程?”,需要分解成交通、住宿、景点三个子问题,分别完成。
两种模式的流程如下所示:
一个简单问题分解的prompt如下所示:
你的任务是针对输入的问题生成多个相关的子问题或子查询,将输入问题分解成一组可以独立回答的子问题或子任务。
以下是输入的问题:
<question>
{{question}}
</question>
请生成3 - 5个与该问题相关的搜索查询,并使用换行符进行分割。生成的子问题/子查询应具有明确的主题和可独立回答的特点。
请在<子问题>标签内写下生成的子问题/子查询。
2
3
4
5
6
7
调用样例如下:
[3] 问题回退策略
问题回退策略和问题分解策略相反,当用户问题非常具体时,可能无法检索的对应文档,就需要将问题进行抽象。比如“李开复在2000年是在哪个公司工作?”,重新抽象成“李开复的工作经历是什么?”。处理流程如下:
下面是一个调用示例:
原始问题:如果理想气体的温度增加2倍,体积增加8倍,压力P会如何变化?
- 直接回答的答案:如果温度增加2倍,体积增加8倍,那么压力将减少16倍。
- 回溯问题:这个问题背后的物理原理是什么?
- 回溯答案:理想气体定律: PV=nRT,其中P是压力,V是体积,n是物质的量,R是气体常数,T是温度。
- 最终答案:压力减少了4倍。
原始问题:1954年8月至11月期间,埃斯特拉・利奥波德就读于哪所学校?
- 直接回答的答案:1954年8月至11月期间,埃斯特拉・利奥波德就读于威斯康星大学麦迪逊分校。
- 回溯问题:埃斯特拉・利奥波德的教育经历是怎样的?
- 回溯答案:1948年,威斯康星大学麦迪逊分校,植物学学士;1950 年,加州大学伯克利分校,植物学硕士;1955 年,耶鲁大学,植物学博士。
- 最终答案:她1955年就读于耶鲁大学植物学博士项目。因此,1954年8月至11月期间,埃斯特拉・利奥波德最可能就读于耶鲁大学
应用场景:
- 复杂推理任务:如 STEM 问题(需公式应用)、时间敏感问题(需整合时间线)。
- 多跳推理:通过抽象减少中间步骤的逻辑错误
prompt举例:
你的任务是分析给定的问题,忽略具体细节,提炼出问题背后涉及的核心概念、原理、知识范畴或通用逻辑。
这是需要分析的问题:
<question>
{{question}}
</question>
分析时,需要提取出问题的本质,将其转化为对某一概念、原理、知识范畴或通用逻辑的探讨。例如,如果问题是“水加热到100℃为什么会沸腾?”,那么分析后的新问题应该是“分析液体沸腾现象的物理原理(如相变、沸点与气压关系、能量传递机制等)”。
请在<回答>标签内写下你的分析结果。
2
3
4
5
6
7
调用样例如下:
[4] HyDE混合策略
在知识库中存储的数据一般都是文档层面上的,文档包含的数据会远远比用户的查询数据要大很多,所以 query
和 doc
之间是不对称检索,能找到的相似性文档相对来说也比较少。
例如:“今天回家的路上看到了美丽风景,非常开心!想学习python该怎么办?”这个请求中,前面的风景、开心等词语均为无关信息,会对真实的请求学习 python 产生干扰。如果直接搜索用户的请求,可能会产生不正确或无法回答的 LLM 响应。因此,有必要使得用户查询的语义空间与文档的语义空间保持一致。
整体流程如下:
HyDE 将检索过程分解为两个阶段:
- 生成假设文档:利用指令遵循的语言模型(如 InstructGPT)根据查询生成虚构但具有相关性的假设文档。生成过程通过自然语言指令(如 “写一个回答问题的段落”)引导,无需标注数据。
- 对比编码检索:使用无监督对比学习的编码器(如 Contriever)将生成的假设文档编码为向量,在语料库嵌入空间中检索最相似的真实文档。编码器的密集瓶颈可过滤假设文档中的错误细节,将生成内容与实际语料对齐。
关键优势:
- 零样本能力:无需相关性标签或微调,直接利用预训练语言模型和对比编码器的能力。
- 跨任务与语言泛化:在 Web 搜索、问答、事实核查等任务及斯瓦希里语、韩语、日语等多语言场景中均表现优异,超越无监督基线 Contriever,并接近有监督微调模型。
prompt参考:
你的任务是实现HyDE零样本检索策略,根据用户输入的查询生成假设文档。生成的内容要反映相关性模式,同时允许存在虚构细节。
以下是用户输入的查询:
<查询>
{{QUERY}}
</查询>
在生成假设文档时,请遵循以下要点:
1. 仔细理解查询的核心内容和意图。
2. 围绕查询构建文档,让文档与查询具有明显的相关性。
3. 可以适当添加一些虚构的细节,但不能偏离查询的主题。
4. 输出的文档应具有一定的逻辑性和连贯性。
请在<生成文档>标签内写下你生成的假设文档。
<生成文档>
[在此生成假设文档]
</生成文档>
2
3
4
5
6
7
8
9
10
11
12
13
14
调用样例如下:
局限性:对于 doc-doc 类型的检索,虽然在语义空间上保持了一致,但是在 query->doc
的过程中,受限于各种因素,仍然可能产生错误信息。
例如提问 Bel是什么?
,在没有执行 HyDE 混合策略而是直接查询得到答案如下:
Bel 是由 Paul Graham 在四年的时间里(2015年3月26日至2019年10月12日),用 Arc 语言编写的一种编程语言。它基于 John McCarthy 最初的 Lisp,但添加了额外的功能。它是一个以代码形式表达的规范,旨在成为计算的形式化模型,是图灵机的一种替代方案。
但是执行 HyDE 混合策略生成假设性 doc
如下:
Bel 是 Paul Graham 的化名,他是这段信息背后的作者,当时需要种子资金以维持生活,并且参与了一项交易,后来成为 Y Combinator 模式的典范。
在这个例子中,HyDE
在没有文档上下文的情况下错误地解释了 Bel,这会导致完全检索不到相关的文档信息。
[5] 混合检索策略
在查询检索中,常见的两种检索方式分别是稀疏检索器和密集检索器:
- 稀疏检索器:基于关键词匹配,利用词频(TF)和逆文档频率(IDF)计算文档与查询的相关性。
- 优点:非常高效,无需训练、对明确关键词匹配效果好;
- 缺点:无法捕捉语义(如同义词、上下文相关性)
- 密集检索器:使用深度学习模型生成密集向量表示,通过向量相似度(如余弦相似度)衡量相关性。
- 优点:捕捉语义信息,解决词汇不匹配问题;
- 缺点:需大量训练数据、计算成本高,对生僻词敏感;
混合检索策略就是将多种检索方式混合起来,可以利用不同算法的优势,从而获得比任何单一算法更好的性能,这也是常用的检索策略。这个流程如下所示:
WRRF是RRF的加权版本,通过赋予不同检索器的权重,从而影响检索文档的排序。
在Langchain中,代码实现样例如下:
doc_list = [
"我喜欢苹果",
"我喜欢橙子",
"苹果和橙子是水果",
]
# 初始化 BM25 检索器和 FAISS 检索器
bm25_retriever = BM25Retriever.from_texts(doc_list)
bm25_retriever.k = 2
embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(doc_list, embedding)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})
# 初始化集成检索器
ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5])
docs = ensemble_retriever.get_relevant_documents("苹果")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.2.2 路由转发
[1] 数据源路由
在 RAG 应用开发中,想根据不同的问题检索不同的向量数据库,其实只需要设定要对应的 Prompt,然后让 LLM 根据传递的问题返回需要选择的向量数据库的名称,然后根据得到的名称选择不同的检索器即可。整体流程如下:
路由数据源的prompt样例如下:
你是一位擅长将用户问题路由到适当数据源的专家。你的任务是根据问题涉及的编程语言,将问题路由到相关的数据源。
首先,请仔细阅读以下数据源信息:
<data_sources>
{{DATA_SOURCES}}
</data_sources>
现在,请仔细阅读以下用户问题:
<question>
{{QUESTION}}
</question>
为了将问题路由到合适的数据源,请按照以下步骤操作:
1. 仔细分析问题,识别其中涉及的编程语言。
2. 查看数据源信息,找出与该编程语言相关的数据源。
3. 如果问题涉及多种编程语言,找出与所有涉及语言都相关或与主要语言相关的数据源。
4. 如果没有合适的数据源,指出“没有合适的数据源”。
请在<回答>标签内写下路由结果。
<回答>
[在此输出路由结果]
</回答>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
调用样例如下:
[2] prompt路由
在RAG应用开发中,针对不同场景的问题使用特定化的prompt模板 效果一般都会比通用模板会好一些,例如在教培场景,制作一个可以教学物理的授课机器人,如果使用通用的 prompt模板,会导致prompt编写变得非常复杂;反过来如果prompt写的简单,有可能没法起到很好的回复效果。
如果能针对用户的提问,例如用户提问的内容是数学相关的则使用数学的模板,提问的内容是物理相关的则使用物理的模板,针对性选择不同的模板,LLM 生成的内容会比使用通用模板会更好,例如下方有两个prompt模板:
物理老师:
你将扮演一位非常聪明的物理教授,以简洁易懂的方式回答物理问题。当你不知道问题的答案时,要坦率承认自己不知道。
以下是需要你回答的物理问题:
<query>
{{query}}
</query>
在回答问题时,请遵循以下指南:
1. 确保回答简洁易懂。
2. 如果不知道问题的答案,直接表明“我不知道这个问题的答案”。
请在<回答>标签内写下你的答案。
2
3
4
5
6
7
8
9
数学老师:
你将扮演一位非常优秀的数学家,专门负责回答数学问题。你需要将复杂的问题分解成多个小步骤,回答这些小步骤,然后将它们整合起来回答更广泛的问题。
这是需要你解答的数学问题:
<问题>
{{query}}
</问题>
在解答问题时,请按照以下步骤进行:
1. 仔细阅读问题,理解问题的核心。
2. 将问题分解成多个小步骤。
3. 依次解答每个小步骤。
4. 最后将小步骤的解答整合起来,给出完整的答案。
请在<回答>标签内写下你的答案,确保答案清晰、全面且包含每一个关键步骤。
2
3
4
5
6
7
8
9
10
11
基于文本向量模型,可以根据查询问题的语义,查找到相似度更高的prompt模板,语义的prompt路由流程如下所示:
# 4.2.3 问题构建
检索外部数据时,最后在执行检索的时候使用的都是固定的筛选条件(没有附加过滤的相似性搜索)。但是在某些情况下,用户发起的原始提问其实隐式携带了 筛选条件,例如提问:请帮我整理下关于2023年全年关于AI的新闻汇总。
在这段 原始提问中,如果执行相应的向量数据库相似性搜索,其实是附加了 筛选条件的,即 year=2023
,但是在普通的相似性搜索中,是不会考虑 2023 年这个条件的(因为没有添加元数据过滤器,2022年和2023年数据在高维空间其实很接近),存在很大概率会将其他年份的数据也检索出来。
那么有没有一种策略,能根据用户传递的原始问题构建相应的元数据过滤器呢?这样在搜索的时候带上对应的元数据过滤器,不仅可以压缩检索范围,还能提升搜索的准确性。这个思想其实就是 查询构建或者称为 自查询。
并且除了 向量数据库,类比映射到 关系型数据库、图数据库也是同样的操作技巧,即:
- 关系型数据库自查询:使用 LLM 将自然语言转换成 SQL 过滤语句。
- 图数据库自查询:使用 LLM 将自然语言转换成图查询语句。
- 向量数据库:使用 LLM 将自然语言转换成元数据过滤器/向量检索器。
在Langchain中,封装了一个自查询检索器(SelfQueryRetriever),执行流程如下:
将对应的prompt翻译后如下所示:
你的任务是根据提供的信息,生成一个符合特定结构的JSON对象。该JSON对象将用于查询和过滤文档。
以下是允许使用的比较器和逻辑运算符:
<allowed_comparators>
{{ALLOWED_COMPARATORS}}
</allowed_comparators>
<allowed_operators>
{{ALLOWED_OPERATORS}}
</allowed_operators>
现在,请根据以下信息构建JSON对象:
<< Data Source >>
```json
{{{{
"content": "{content}",
"attributes": {attributes}
}}}}
```
在构建JSON对象时,请遵循以下规则:
1. 查询字符串应仅包含预期与文档内容匹配的文本。过滤条件中的任何条件不应在查询中提及。
2. 逻辑条件语句由一个或多个比较和逻辑操作语句组成。
- 比较语句的形式为:`comp(attr, val)`,其中`comp`为允许的比较器,`attr`为要应用比较的属性名称,`val`为比较值。
- 逻辑操作语句的形式为:`op(statement1, statement2, ...)`,其中`op`为允许的逻辑运算符,`statement1`, `statement2`, ... 为比较语句或逻辑操作语句。
3. 仅使用上述列出的比较器和逻辑运算符,不使用其他运算符。
4. 过滤条件仅引用数据源中存在的属性。
5. 过滤条件仅使用应用了函数的属性名称及其函数名。
6. 处理日期数据类型的值时,过滤条件仅使用`YYYY - MM - DD`格式。
7. 过滤条件仅在需要时使用。如果没有要应用的过滤条件,`filter`的值应返回 "NO_FILTER"。
8. `limit`必须始终为整数类型的值。如果该参数没有意义,请留空。
请在<回答>标签内输出符合以下格式的JSON对象:
```json
{
"query": "文本字符串,用于与文档内容进行比较",
"filter": "用于过滤文档的逻辑条件语句",
"limit": 要检索的文档数量
}
```
<<样例>>
Data Source:
```json
{{
"content": "Lyrics of a song",
"attributes": {{
"artist": {{
"type": "string",
"description": "Name of the song artist"
}},
"length": {{
"type": "integer",
"description": "Length of the song in seconds"
}},
"genre": {{
"type": "string",
"description": "The song genre, one of \"pop\", \"rock\" or \"rap\""
}}
}}
}}
```
User Query:
What are songs by Taylor Swift or Katy Perry about teenage romance under 3 minutes long in the dance pop genre
Structured Request:
```json
{{
"query": "teenager love",
"filter": "and(or(eq(\\"artist\\", \\"Taylor Swift\\"), eq(\\"artist\\", \\"Katy Perry\\")), lt(\\"length\\", 180), eq(\\"genre\\", \\"pop\\"))"
}}
```
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
# 4.2.4 数据索引
[1] 多表征索引
通常我们会为一个文档生成一个向量信息,并存储到向量数据库中。如果能从多个维度记录该文档块的信息,会大大增加该文档块被检索到的概率,多个维度记录信息等同于为文档块生成多个向量。通常建立的维度有以下几种:
- 把文档切割成更小的块:将一个文档块继续拆分成更小的块,通过检索小的块,定位父文档。
- 存储摘要信息:将一个文档通过LLM生成摘要信息,将其和原文档一起存到向量数据库中,只返回原文档。
- 假设性问题:使用 LLM 为每个文档块生成适合回答的假设性问题,将其和原文档一起嵌入或者代替,返回时返回原文档。
整体流程如下:
[2] 分层索引
在传统的 RAG 中,我们通常依靠检索短的连续文本块来进行检索。但是,当我们处理的是长上下文时,我们就不能仅仅将文档分块嵌入到其中,或者仅仅使用上下文填充所有文档。相反,我们希望为 LLM 的长下文找到一种好的最小化分块方法,这就是 RAPTOR 的用武之地,在 RAPTOR 中,均衡了多文档、超长上下文、高准确性、超低成本等特性。
RAPTOR 其实是一种用树状组织检索的递归抽象处理技术,它采用了一种自下而上的方法,通过对文本片段(块)进行聚类和归纳来形成一种分层结构。
- 对原始文本进行分块,拆分成合适的大小;
- 对拆分的文档块进行嵌入/向量化,向量目前处于高维,并将数据存储到向量数据库;
- 将高维向量进行降维,降低运算成本,例如降低成 2 维或者 3 维;
- 对降维向量进行聚类,找出同一类的文档组;
- 合并文档组的文本,使用 LLM 对合并文档进行摘要汇总得到新的文本,重复2-5的步骤;
- 直到最后只剩下一个文档并且该文档的长度符合大小时,结束整个流程;
构建过程如下图所示:
检索策略分为两种:树遍历检索和折叠树检索。
其中树遍历检索流程如下所示:
- 从树的根节点开始,检索问题和文档的余弦相似性
- 选择最相关的前 k 个节点
- 将选择的节点的子节点放入候选集中,重复1-3的步骤
折叠树检索过程如下图所示:
- 将所有的节点都存储在一个向量数据库,折叠为一层
- 对原始问题进行检索,选择最相关的前 k 个节点
[3] 切块优化
通常被检索知识库中的数据量是远超于LLM所能接受的输入长度的,因此合理的分块应尽可能做到在不超出LLM输入长度限制的情况下,保证块之间的差异性和块内部的一致性。因此我们需要尽可能提供有用的信息给LLM,而不是提供无关的信息分散其注意力,可以采用以下高级的分块方法:
1)递归字符分割
字符分割器就是根据指定分割符,将文档切割成多个文档块。它通常会支持控制文档块的大小,避免超出大模型上下文限制。除此之外,还可以控制块与块之间重叠的内容大小,尽可能保留上下文信息。
但是在划分的过程中,可能会出现文档块过小或者过大的情况,这会让 RAG 变得不可控,例如:
- 文档块可能会变得非常大:由于切分依赖找到分隔符,如果说两个分割符的间隔非常大,就会导致文档块的大小超出限制的值,极端的情况下某个块的内容长度可能就超过了 LLM 的上下文长度限制,这样这个文本块永远不会被引用到,相当于存储了数据,但是数据又丢失了。
- 文档块可能会远远小于窗口大小:如果两个分割符的间隔非常小,则导致文档块的信息密度太低,块内容即使填充到 Prompt 中,LLM 也无法提取出有用的信息。
递归字符分割器对大文档块会使用更多的分隔符使其变小,对小文档块进行合并使其保留更多的信息,整体流程如下所示:
2)句子段落分割:使用 NLTK 或者 spaCy 库提供的句子分割功能,主流开发框架如 LangChain 都有集成。
3)文档语义分割:文档分割器都是使用特定字符对文本进行拆分,这种拆分模式虽然考虑了文档中的上下文切断的问题,但是并没有考虑句子之间的语义相似性,如果有一篇长文本,需要将其分割成语义相关的块,以便更好地理解和处理,这个时候就需要使用语义文档分割器。整体流程如下所示:
4)特殊结构分割:针对特定结构化内容(例如Markdown、LaTeX、JSON等)的专门分割器。这些分割器特别设计来处理这些类型的文档,以确保正确地保留其结构。
5)文档内容转换:将文档内容转化为另一种更适用于特定任务或目标的形式,以便更高效地利用信息。
- 问答转换器:使用大语言模型,对文档信息进行提取,被生成该信息可能出现的问答对,如
{'question': '产品发布活动的日期是什么时候?', 'answer': '7月15日'}
- 文档翻译器:其实是将文档转换成另外一种形式的文档,比如将英文的论文翻译成中文。
# 4.2.5 知识检索
[1] ReRank重排序
在完成对问题的改写、不同数据库查询的构建以及路由逻辑、向量数据库索引方面的优化后,我们可以考虑进一步优化筛选阶段,一般涵盖了重排序、纠正性RAG两种策略。其中重排序是使用频率最高,性价比最高,通常与混合检索一起搭配使用,也是目前主流的优化策略。重排序的核心思想见字知其意,即对检索到的文档调整顺序,除此之外,重排序一般还会增加剔除无关/多余数据的步骤,其中RRF就是重排序中最基础的一种。
[2] CRAG纠正性检索增强生成
纠正性检索增强生成(Corrective Retrieval-Augmented Generation,CRAG)是一种先进的自然语言处理技术,旨在提高检索的生成方法的鲁棒性和准确性。在 CRAG 中引入了一个轻量级的检索评估器来评估检索到的文档的质量,并根据评估结果触发不同的知识检索动作,以确保生成结果的准确性。整体流程如下图所示:
- 检索文档:首先,基于用户的查询,系统执行检索操作以获取相关的文档或信息。
- 评估检索质量:CRAG 使用一个轻量级的检索评估器对检索到的每个文档进行质量评估,计算出一个量化的置信度分数。
- 触发知识检索动作:根据置信度分数,CRAG 将触发以下2个动作之一:
- 正确:如果评估器认为文档与查询高度相关,将采用该文档进行知识精炼。
- 错误:如果文档被评估为不相关或误导性,CRAG将重写生成一个新的问题,然后利用网络搜索寻找更多知识来源。
- 知识精炼:对于评估为正确的文档,CRAG将进行知识精炼,抽取关键信息并过滤掉无关信息。
- 问题重写:通过重写一个新的相似问题,来优化答案的检索。
- 网络搜索:在需要时,CRAG会执行网络搜索以寻找更多高质量的知识来源,以纠正或补充检索结果。
- 生成文本:最后,利用经过优化和校正的知识,传递给 LLM,生成对应文本。
评估节点prompt:
你是一名评分员,负责评估检索到的文档与用户问题的相关性。你的任务是根据给定的标准,判断文档是否与问题相关,并给出“yes”或“no”的二元评分。
以下是用户的问题:
<question>
{{QUESTION}}
</question>
以下是检索到的文档:
<document>
{{DOCUMENT}}
</document>
判断文档是否相关的标准为:如果文档包含与问题相关的关键词或语义含义,则判定为相关。
首先,在<思考>标签中详细分析文档是否包含与问题相关的关键词或语义含义,说明你的分析过程。然后在<回答>标签中给出最终的二元评分(“yes”或“no”)。
<思考>
[在此详细说明你对文档与问题相关性的分析过程]
</思考>
<回答>
[在此给出“yes”或“no”的评分]
</回答>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
问题重写prompt:
你是一个问题改写器,任务是将输入的问题转换为一个更适合网络搜索的优化版本。你需要仔细分析输入问题,挖掘其潜在的语义意图和含义。
以下是需要改写的问题:
<question>
{{QUESTION}}
</question>
在改写问题时,请遵循以下方法:
1. 去除不必要的修饰词和语气词,使问题简洁明了。
2. 提取问题的核心内容,突出关键信息。
3. 调整语序,使问题更符合网络搜索的习惯。
请在<改写后的问题>标签内写下改写后的问题。
2
3
4
5
6
7
8
9
10
知识精炼prompt:
你是一位信息精炼专家,负责从给定文档中提取与特定主题直接相关的关键事实、数据、观点和结论,过滤掉不相关的背景信息、示例和解释。
请仔细阅读以下文档:
<document>
{{DOCUMENT}}
</document>
需要围绕的主题是:
<topic_name>
{{TOPIC_NAME}}
</topic_name>
在精炼信息时,请遵循以下要求:
1. 仅提取与主题直接相关的关键事实、数据、观点和结论。
2. 过滤掉所有不相关的背景信息、示例和解释。
3. 输出尽量保持简洁明了。
请在<回答>标签内写下精炼后的信息。
2
3
4
5
6
7
8
9
10
11
12
13
14
RAG-prompt:
你是一个负责回答问题的助手。你的任务是利用提供的检索到的上下文来回答问题。如果不知道答案,就直接表明不知道。回答最多使用三句话,保持简洁。
以下是检索到的上下文:
<retrieved_context>
{{RETRIEVED_CONTEXT}}
</retrieved_context>
这是问题:
<question>
{{QUESTION}}
</question>
请在<回答>标签内写下你的答案。
<回答>
[在此给出答案]
</回答>
2
3
4
5
6
7
8
9
10
11
12
13
# 4.2.6 内容生成
Self-RAG 全称为自我反思 RAG,见名知其意,即对原始查询、检索的内容、生成的内容进行自我反思,根据反思的结果执行不同的操作,例如:直接输出答案、重新检索、剔除不相关的内容、检测生成内容是否存在幻觉、检测生成内容是否有帮助等,可以把 Self-RAG看成是一个拥有自我反思能力的智能体,这个智能体主要用来依据相关知识库回复用户问题,自我迭代,直到输出满意的结果。
SELF-RAG训练了一个任意的LLM,使其能够在给定任务输入时反思自己的生成过程,同时生成任务输出和临时的特殊标记(称为反思标记)。这些反思标记分为检索和评论标记,分别表示了是否需要检索以及生成的质量。流程如下图所示:
一个 Self-RAG应用主要有三大步骤组成:
- 按需检索(Retrieval as Needed):SELF-RAG在需要的情况下,通过生成一个检索标记,来评估是否需要从相关文本中获取信息。如果需要检索,模型会使用检索器(Retriever)来获取与输入相关的文本段落;相反,当模型被要求写“写一篇关于Python依赖注入的文章”时,大模型会直接生成答案,无需进行检索。
- 以并行方式生成内容(Parallel Generation):模型会同时使用 prompt 和检索到的内容来生成模型输出,在整个过程中,会触发多种类型的反思(Reflection),涵盖了:反思文档是否有关联、反思生成内容是否存在幻觉以及是否能回答问题。
- 如果不关联则重新检索;
- 如果存在幻觉/支持度不够,则重新生成;
- 内容的评估和选择:对步骤 2 中生成的内容进行评估,并选择最佳文档段落作为输出。
判断是否有幻觉prompt:
你是一名评分员,负责评估大语言模型(LLM)的生成内容是否有一组检索到的事实作为依据。你的任务是根据给定的事实集,判断生成内容是否能得到事实的支持,并给出“是”或“否”的二元评分。“是”表示答案有事实依据,“否”则表示没有。
首先,请仔细阅读以下检索到的事实集:
<检索到的事实集>
{{RETRIEVED_FACTS}}
</检索到的事实集>
现在,请仔细阅读以下大语言模型的生成内容:
<大语言模型生成内容>
{{LLM_GENERATION}}
</大语言模型生成内容>
评估这份生成内容时,请考虑生成内容中的所有陈述是否都能在检索到的事实集中找到支持。
在<思考>标签中详细分析你的判断依据,然后在<判断>标签中给出“是”或“否”的判断。例如:
<思考>
[在此详细说明你做出判断的依据]
</思考>
<判断>
[在此给出“是”或“否”的判断]
</判断>
请现在开始你的评估。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
答案评估prompt:
你的任务是评估一个答案是否解决了相应的问题,并给出“yes”或“no”的二元评分。“yes”表示答案解决了问题,“no”表示答案未解决问题。
以下是问题:
<question>
{{QUESTION}}
</question>
以下是答案:
<answer>
{{ANSWER}}
</answer>
在评估时,请仔细对比答案内容与问题,判断答案是否直接回应并解决了问题。
请在<判断>标签内给出你的最终判断,使用“yes”或“no”。
<判断>
[在此给出“yes”或“no”的判断]
</判断>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.3 RAG效果评估
对RAG的评估方法多种多样,主要包括三个质量分数:上下文相关性、答案准确性和答案相关性。此外,评估还涉及四项关键能力:抗噪声能力、拒绝能力、信息整合以及反事实鲁棒性。这些评价维度将传统的定量指标与针对RAG特点的专门评估标准相结合,尽管这些标准尚未得到标准化。在评价框架方面,有RGB和RECALL等基准测试,以及像RAGAS、ARES和TruLens等自动化评价工具,它们帮助全面衡量RAG模型的表现。
# 5. 参考资料
[1] 检索增强生成 (RAG) from Prompt Engineering Guide (opens new window)
[2] 用检索增强生成让大模型更强大,这里有个手把手的Python实现 from 机器之心 (opens new window)
[3] 检索、提示:检索增强的(Retrieval Augmented)自然语言处理 from 知乎 (opens new window)
[5] 一文详解检索增强语言模型新范式REPLUG from CSDN (opens new window)
[6] 大模型+检索增强(RAG、Atlas 和 REPLUG)from CSDN (opens new window)
[7] 检索增强生成RAG的技术趋势调查仓库RAG-Survey from Github (opens new window)
[8] 一文读懂RAG的来源、发展和前沿 from 微信公众号 (opens new window)
[9] 多模态RAG综述 from 知乎 (opens new window)
[10] RAG 分块Chunk技术优劣、技巧、方法汇总(五)from 知乎 (opens new window)
[11] 向量模型BGE与M3E from 知乎 (opens new window)
[12] RAG提效利器——BM25检索算法原理和Python实现 from 知乎 (opens new window)
[13] 稠密检索和稀疏检索分别指的是什么(以向量检索为例)from 知乎 (opens new window)
[14] 基于python的BM25文本匹配算法实现 from Github (opens new window)
[15] 非监督文本匹配算法——BM25 from CSDN (opens new window)
[16] LLM+Embedding构建问答系统的局限性及优化方案 from 知乎 (opens new window)
[17] OpenAI分享他们在RAG技术的最佳实践 from 微信公众号 (opens new window)
[18] 大语言模型何时需要检索?UCLA提出全新自监督选择性检索策略 from 微信公众号 (opens new window)
[19] Agentic RAG 与图任务编排 from AIGC开放社区 (opens new window)
[20] 基于 RAPTOR 实现长上下文 RAG from RAGFlow官方文档 (opens new window)
[21] 一文详谈20多种RAG优化方法 from 微信公众号 (opens new window)
[22] 为什么RAG应用很难落地?细说RAG系统开发关键痛点和解决方案 from 微信公众号 (opens new window)
[23] 我也曾一上来就想微调大模型,直到我发现自己错得离谱 from 微信公众号 (opens new window)
[24] RAG优化策略总结 from 微信公众号 (opens new window)
[25] 一文读透RAG:大语言模型(LLM)的知识增强神器 - 检索增强生成 from 知乎 (opens new window)