1、 什么是RAG
我们按照前面导游的例子,来引出RAG做了哪些事情。你把当导游时无法给游客提供专业、全面信息的困惑告诉了你的主管,于是主管给了你一本志愿者手册。游客询问最近的全聚德烤鸭店在哪里,你拿出了志愿者手册,翻到了全聚德烤鸭店的位置,然后告诉了游客具体的走法。
图1:用系统资料导游
你又遇到了一个游客,游客眼睛不太好,他想知道如何前往银锭桥,此时你也犯难了,志愿者手册并没有该细节内容,但是此时游客手里有一张导览图,你接过地图,经过简单的分析,按照地图指出应该如何前往银锭桥。
图2:用系统资料和用户资料结合来导游
假设我们是开发大模型导游助理的技术团队,我们把导游助理比作志愿者。
在第一种情况下,“志愿者手册”就是我们在开发系统的时候就配置好的知识库,因此导游助理可以从系统默认的知识库中获取烤鸭店的地址,然后生成导航路径给游客。
在第二种情况中,假设我们的系统支持用户上传个性化资料,来更好地满足个性化业务需要。换句话说,系统支持用户添加垂直领域知识,构建私域知识库。那么,当游客向志愿者提供一份个性化导航资料时,系统可以结合游客的垂直领域知识与系统预置的知识共同为游客提供服务。
第一种方案的知识库,可以理解是公司统一配置的知识库;
第二种方案中,每个团队或者用户还可以根据自己的需要来增加私域定制化知识库。
显然,第二种系统更灵活,不需要复杂的操作就能补充了业务知识。但总体来看,这两个系统都是通过知识库来增强导游助理的能力,减少“幻觉”回答的情况(即导游助理不是编造一个像模像样的地址,而是按照已有知识来回答)。这就是我们即将介绍的检索增强生成 Retrieval Augmented Generation。
检索增强生成包括三个步骤,建立索引、检索、生成。如果说大模型导游助理是一位志愿者,那么我们给志愿者们准备“志愿者手册”的过程就是建立知识库索引,志愿者查看资料就是系统在检索知识库,志愿者基于检索到的资料充分思考并回答用户的问题就是生成答案。
2 、RAG的实现原理
那么RAG是怎样将信息检索与文本生成结合起来的呢?请看下图:
图3:大模型RAG基本工作流
如图所示,RAG主要由两个部分构成:
建立索引:首先要清洗和提取原始数据,将 PDF、Docx等不同格式的文件解析为纯文本数据;然后将文本数据分割成更小的片段(chunk);最后将这些片段经过嵌入模型转换成向量数据(此过程叫做embedding),并将原始语料块和嵌入向量以键值对形式存储到向量数据库中,以便进行后续快速且频繁的搜索。这就是建立索引的过程。
检索生成:系统会获取到用户输入,随后计算出用户的问题与向量数据库中的文档块之间的相似度,选择相似度最高的K个文档块(K值可以自己设置)作为回答当前问题的知识。知识与问题会合并到提示词模板中提交给大模型,大模型给出回复。这就是检索生成的过程。
提示词模板类似于:请阅读:{知识文档块},请问:{用户的问题}
3 、如何持续改进RAG应用效果
- 建立评测标准提升索引准确率让问题更好理解改造信息抽取途径回答前反复思考从多种数据源中获取资料持续关注最新技术
随着深入使用,你可能发现你的 RAG 应用可能只是能用了,但还有很多问题,比如:
- 问题比较抽象或者概念比较模糊,导致大模型没有准确理解使用者的问题。例如,使用者问“兰州拉面去哪吃?”,使用者本来想问附近有没有卖兰州拉面的店铺;假如知识库搜索“兰州”和“拉面”之后,结果排序靠前的语料是位于“兰州”的拉面馆地址,而大模型告诉用户要去买飞机票或者火车票,就是答非所问了。通常我们用改造问题,让使用者的问题更好理解的策略来回避这种情况。知识库没有检索到问题的答案,这有可能是由于语料数据没有做好整理就存入知识库,或者是检索策略有问题,参数需要调整导致的。比如,在采用“K个最相似文档块”作为回答的知识这个策略中,如果K值比较小,那么最相似的K个文档块中可能并不包含能解答用户问题的有效知识,那么答案很可能就是错误的。例如,作为旅游手册的知识库中有大量文段是兰州拉面如何制作的菜谱、兰州拉面的产地、兰州有哪些特产等,只有一两条信息描述了附近拉面馆的地址。那么当用户询问“兰州拉面怎么走?”时,知识库检索到的信息可能只是兰州拉面的选材、调味、烹饪方面的信息,而唯独没有检索到前方50米处有一家兰州拉面馆。用户也没有办法获得有效的答案。缺少对答案做兜底验证的机制,假设运气很好,志愿者不仅听懂了游客的问题,也正确查找到了附近最近的两家拉面馆的信息,但是志愿者的回答方式是“向北走200米就到了”。这有可能是一个正确的答案,但不是一个好的答案,实际考察过景区地形后我们可能会发现,志愿者北面是后海,你不太可能穿过湖面去一个地方。实际路径可能是:先向东走50米,再向北走绕过后海,走到湖对面去,才能走到正确位置。那这个“向北走200米...”的回答,从导航的角度就不能算是准确了。
下面我们将一起了解改进标准 RAG 框架的思路与方法。
3.1 建立评测标准
为了持续改进我们的 RAG 应用,首要任务应当是构建一套严谨的评测指标体系,并邀请业务领域专家作为评测方共同参与评测工作,我们可以设置与我们业务相关的多种问题场景,系统性地检查一个RAG系统反应快不快,回答准不准,有没有理解用户问题的意图等方面。通过科学全面的评测,我们可以了解到系统在哪些地方做得好,哪些地方需要改进,从而帮助开发者让RAG系统更好地服务于业务需求。
RAG系统一般包括检索和生成两个模块,我们做评测时就可以从这两个模块分别入手建立评价标准和实施方法,当然你也可以用最终效果为标准,建立端到端的评测。在评测指标设计上,我们主要考察检索模块的准确性,如准确率、召回率、F1值等等;在生成模块,我们主要考察生成答案的价值,如相关性、真实性等等。
我们可以引入业界认可的一些通用评估策略,比如,你可以参考Ragas提及的评测矩阵指南,你也可以建立一些自己的评测指标,这些评测方法将会有助于你量化和改进每一个子模块的表现。
3.2 提升索引准确率
- 优化文本解析过程
在构建知识库的时候,我们首先需要正确地从文档中提取有效语料。因此,优化文本解析的过程往往对提升RAG的性能有很大帮助。例如,从网页中提取有效信息时,我们需要判断哪些部分应该被去掉(比如页眉页脚标签),哪些部分应该被保留(比如属于网页内容的表格标签)。
- 优化chunk切分模式
Chunk就是数据或信息的一个小片段或者区块。当你在处理大量的文本、数据或知识时,如果你一次性全部交给大模型来阅读和处理,效率是非常低的。所以,我们把它们切分成更小、更易管理的部分,这些部分就是chunk。每个chunk都包含了一些有用的信息,这样当系统根据用户的问题寻找某个知识时,它可以迅速定位到与答案相关信息的chunk,而不是在整个数据库中盲目搜索。因此,通过精心设计的chunk切分策略,系统能够更高效地检索信息,就像图书馆里按照类别和标签整理书籍一样,使得查找特定内容变得容易。优化chunk切分模式,就能加速信息检索、提升回答质量和生成效率。具体方法有很多:
利用领域知识:针对特定领域的文档,利用领域专有知识进行更精准的切分。例如,在法律文档中识别段落编号、条款作为切分依据。
基于固定大小切分:比如默认采用128个词或512个词切分为一个chunk,可以快速实现文本分块。缺点是忽略了语义和上下文完整性。
上下文感知:在切分时考虑前后文关系,避免信息断裂。可以通过保持特定句对或短语相邻,或使用更复杂的算法识别并保留语义完整性。最简单的做法是切分时保留前一句和后一句话。你也可以使用自然语言处理技术识别语义单元,如通过句子相似度计算、主题模型(如LDA)或BERT嵌入聚类来切分文本,确保每个chunk内部语义连贯,减少跨chunk信息依赖。通义实验室提供了一种文本切割模型,输入长文本即可得到切割好的文本块,详情可参考:中文文本分割模型。
以上介绍了一些常见策略,你也可以考虑使用更复杂的切分策略,如围绕关键词切分或者采用动态调整的切分策略等,主要目的是为了保证每个chunk中信息的完整性,更好的服务系统提升检索质量。
- 句子滑动窗口检索
这个策略是通过设置window_size(窗口大小)来调整提取句子的数量,当用户的问题匹配到一个chunk语料块时,通过窗函数提取目标语料块的上下文,而不仅仅是语料块本身,这样来获得更完整的语料上下文信息,提升RAG生成质量。
图6:句子滑窗检索获取检索到的句子的上下文
- 自动合并检索
这个策略是将文档分块,建成一棵语料块的树,比如1024切分、512切分、128切分,并构造出一棵文档结构树。当应用作搜索时,如果同一个父节点的多个叶子节点被选中,则返回整个父节点对应的语料块。从而确保与问题相关的语料信息被完整保留下来,从而大幅提升RAG的生成质量。实测发现这个方法比句子滑动窗口检索效果好。
图7:自动合并检索的方法,返回父节点文本作为检索结果
- 选择更适合业务的Embedding模型
经过切分的语料块在提供检索服务之前,我们需要把chunk语料块由原来的文本内容转换为机器可以用于比对计算的一组数字,即变为Embedding向量。我们通过Embedding模型来进行这个转换。但是,由于不同的Embedding模型对于生成Embedding向量质量的影响很大,好的Embedding模型可以提升检索的准确率。
比如,针对中文检索的场景,我们应当选择在中文语料上表现更好的模型。那么针对你的业务场景,你也可以建议你的技术团队做Embedding模型的技术选型,挑选针对你的业务场景表现较好的模型。
- 选择更适合业务的ReRank模型
除了优化生成向量的质量,我们还需要同时优化做向量排序的ReRank模型,好的ReRank模型会让更贴近用户问题的chunks的排名更靠前。因此,我们也可以挑选能让你的业务应用表现更好的ReRank模型。
- Raptor 用聚类为文档块建立索引
还有一类有意思的做法是采用无监督聚类来生成文档索引。这就像通过文档的内容为文档自动建立目录的过程。假如志愿者拿到的文本资料是没有目录的,志愿者一页一页查找资料必然很慢。因此,可以将词条信息聚类,比如按照商店、公园、酒吧、咖啡店、中餐馆、快餐店等方式进行分组,建立目录,再根据汉语拼音字母来排序。这样志愿者来查找信息的时候就可以更快速地进行定位。
图8:Raptor 用聚类为文档块建立索引
3.3 让问题更好理解
我们希望做到能让人们通过口语对话来使用大模型应用。然而,人们在口语化表达自己的目的和意图时,往往会出现一些问题。比如,问题过于简单含糊出现了语义混淆,导致大模型理解错误;问题的要素非常多,而用户又讲得太少,只能在反复对话中不断沟通补全;问题涉及的知识点超出了大模型训练语料,或者知识库的覆盖范围,导致大模型编造了一些信息来回答等等。所以,我们期望能在用户提问的环节进行介入,让大模型能更好地理解用户的问题。针对这个问题进行尝试的论文很多,提供了很多有意思的实现思路,如Multi-Query、RAG-Fusion、Decomposition、Step-back、HyDE等等,我们简要讲解一下这些方法的思路。
- Enrich 完善用户问题
我们首先介绍一种比较容易想到的思路,让大模型来完善用户的原始问题,产生一个更利于系统理解的完善后的用户问题,再让后续的系统去执行用户的需求。通过用大模型对用户的问题进行专业化改写,特别是加入了知识库的支持,我们可以生成更专业的问题。下图展现了一种理想的对用户问题的Enrich流程。我们通过多轮对话逐步确认用户需求。
❖ 一种理想的通过多轮对话补全需求的方案。该设想是通过大模型多次主动与用户沟通,不断收集信息,完善对用户真实意图的理解,补全执行用户需求所需的各项参数。如下图所示。
图9:通过多轮对话完善用户问题的工作流
以下展示一个通过多轮对话来补全用户问题的案例:
但是在实际的生产中,一方面用户可能没有那么大的耐性反复提供程序需要的信息,另一方面开发者也需要考虑如何终止信息采集对话,比如让大模型输出停止语“(End Of Sentence)”。所以在实践中,我们需要采用一些更容易实现的方案。
❖ 让大模型转述用户问题,再进行RAG问答。参考“指令提示词”的思路,我们可以让大模型来转述用户的问题,将用户的问题标准化,规范化。这里我们可以提供一套标准提示词模板,提供一些标准化的示例,也可以用知识库来增强。我们的主要目的是规范用户的输入请求,再生成RAG查询指令,从而提升RAG查询质量。
❖ 让用户补全信息辅助业务调用。有一些应用场景需要大量的参数支撑,(比如订火车票需要起点、终点、时间、座位等级、座位偏好等等),我们还可以进一步完善上面的思路,一次性告诉用户系统需要什么信息,让用户来补全。首先,需要准确理解用户的意图,实现意图识别的手段很多,如使用向量相似度匹配、使用搜索引擎、或者直接大模型来支持。其次,根据用户意图选择合适的业务需求模板。接着,让大模型参考业务需求模版来生成一段对话发给用户,请求用户补充信息。这时,如果用户进行了信息完善,那么大模型就可以基于用户的回复信息结合用户的请求来生成下一步的行动指令,整个系统就可以实现应用系统自动帮助用户订机票、订酒店,完成知识库问答等应用形式。
图11:一个完整的用户信息补全流程示意图
Enrich的方法介绍了一种大模型向用户多次确认需求来补全用户想法的做法。自此,我们假设已经获得了补全过的用户需求,但是由于用户面对的现实问题千变万化,而系统或RAG的知识可能会滞后,对用户问题的理解多少存在一些偏差,我们还可以继续对整个系统进行强化,接下来我们继续介绍“如何让系统更好地理解用户的问题”。
- Multi-Query 多路召回
多路召回的方法不是让大模型进行一次改写然后反复向用户确认,而是让大模型自己解决如何理解用户的问题。所以我们首先一次性改写出多种用户问题,让大模型根据用户提出的问题,从多种不同角度去生成有一定提问角度或提问内容上存在差异的问题。让这些存在差异的问题作为大模型对用户真实需求的猜测,然后再把每个问题分别生成答案,并总结出最终答案。
以下就是能生成多路召回策略的提示词模板,你可以在你的项目里直接使用这段提示词模板,其中{question}就是用户输入的问题,你也可以尝试先翻译成中文再使用:
You are an AI language model assistant. Your task is to generate five different versions of the given user question to retrieve relevant documents from a vector database. By generating multiple perspectives on the user question, your goal is to help the user overcome some of the limitations of the distance-based similarity search. Provide these alternative questions separated by newlines. Original question: {question}
● RAG-Fusion 过滤融合
在经过多路召回获取了各种语料块之后,并不是将所有检索到的语料块都交给大模型,而是先进行一轮筛选,给检索到的语料块进行去重操作,然后按照与原问题的相关性进行排序,再将语料块打包喂给大模型来生成答案。
经过去重复语料筛选,节省了传递给大模型的tokens数量,再经过排序,将更有价值的语料块传递给大模型,从而提升答案的生成质量。
用我们的例子讲就是,志愿者先从多种角度来理解用户的问题,然后对每个问题都去检索资料,查找有用信息,最后把重复信息去掉,再将获取的资料排序。这就能锁定比较接近用户问题的几段语料了,比如“全聚德烤鸭店的地址”,“天外天烤鸭店的地址”,“郭林家常菜店铺的地址”等等,以及这些烤鸭店分布在后海的哪些区域,如何步行走过去等等。
- Step Back 问题摘要
让大模型先对问题进行一轮抽象,从大体上去把握用户的问题,获得一层高级思考下的语料块。
这个策略的提示词写作
You are an expert at world knowledge. Your task is to step back and paraphrase a question to a more generic step-back question, which is easier to answer. Here are a few examples:
假如是医疗咨询的场景,用户描述了一大段病情、现象、感受、担忧;或者在法律服务的场景,用户描述了现场情况、事发双方的背景、纠纷的由来等一大段话的时候,我们就可以用这个策略,让大模型先理解一下用户的意图是什么,这个事情大体上看是什么问题。
- Decomposition 问题分解
这个策略讲究细节,有点像提示词工程中的CoT(Chain of Thoughts,思维链)的概念,是把用户的问题拆成一个一个小问题来理解,或者可以说是RAG中的CoT。这个策略的提示词如
You are a helpful assistant that generates multiple sub-questions related to an input question. \nThe goal is to break down the input into a set of sub-problems / sub-questions that can be answers in isolation. \nGenerate multiple search queries related to: {question} \nOutput (3 queries):
假如是医疗咨询的场景,用户描述了一大段病情、现象、感受、担忧;或者在法律服务的场景,用户描述了现场情况、事发双方的背景、纠纷的由来等一大段话的时候,我们就可以用这个策略,让大模型先理解一下用户的意图是什么,这个事情大体上看是什么问题。
● Decomposition 问题分解
这个策略讲究细节,有点像提示词工程中的CoT(Chain of Thoughts,思维链)的概念,是把用户的问题拆成一个一个小问题来理解,或者可以说是RAG中的CoT。这个策略的提示词如
You are a helpful assistant that generates multiple sub-questions related to an input question. \nThe goal is to break down the input into a set of sub-problems / sub-questions that can be answers in isolation. \nGenerate multiple search queries related to: {question} \nOutput (3 queries):
使用了这段提示词,大模型生成的子问题如下
接下来可以使用并行与串行两个策略来执行子任务。并行执行是将每个子任务抛出去获得一个答案,然后再让大模型把所有子任务的答案汇总起来。串行是依次执行子任务,然后将前一个任务生成的答案作为后一个任务的提示词的一部分。这两种执行策略如下图所示
● HyDE 假设答案
这个策略让大模型先来根据用户的问题生成一段假设答案,然后用这段假设的答案作为新的问题去文档库里匹配新的文档块,再进行总结,生成最终答案。
好比志愿者听到用户的问题“推荐一家烤鸭店”,第一时间想到了“全聚德烤鸭店不错,我前两天刚吃过!”,接下来,志愿者按照自己的思路找到了全聚德烤鸭店的地址,并给用户讲解如何走过去。
3.4 改造信息抽取途径
Corrective Retrieval Augmented Generation (CRAG)是一种改善提取信息质量的策略:如果通过知识库检索得到的信息与用户问题相关性太低,我们就主动搜索互联网,将网络搜索到的信息与知识库搜索到的信息合并,再让大模型进行整理给出最终答案。在工程上我们可以有两种实现方式:
向量相似度,我们用检索信息得到的向量相似度分来判断。判断每个语料块与用户问题的相似度评分,是否高过某个阈值,如果搜索到的语料块与用户问题的相似度都比较低,就代表知识库中的信息与用户问题不太相关;
直接问大模型,我们可以先将知识库检索到的信息交给大模型,让大模型自主判断,这些资料是否能回答用户的问题。
图12:CRAG原理图
采用这个搜索策略,当志愿者遇到一个问题,而手边的资料都不能解答这个问题时,志愿者可以上网搜索答案。比如游客问:“天安门升旗仪式是几点钟?”志愿者可能会打开电脑,搜索一下明天天安门升旗仪式的具体时间,然后再回答给游客。这样,至少能让RAG的回答信息的范围有所扩大,回答质量有了提升。
在CRAG的论文中,当面临知识库不完备的情况时,先从互联网下载相关资料再回答的准确率比直接回答的准确率有了较大提升。
3.5 回答前反复思考
Self-RAG,也称为self-reflection,是一种通过在应用中设计反馈路径实现自我反思的策略。基于这个思想,我们可以让应用问自己三个问题:
‒ 相关性:我获取的这些材料和问题相关吗?
‒ 无幻觉:我的答案是不是按照材料写的来讲,还是我自己编造的?
‒ 已解答:我的答案是不是解答了问题?
图13:Self-RAG原理图
3.6 从多种数据源中获取资料
这个策略涉及系统性的改造数据的存储和获取环节。传统RAG我们只分析文本文档,我们把文档作为字符串存在向量数据库和文档数据库中。但是现实中的知识还有结构化数据以及图知识库。因此,有很多工作者在研究从数据库中通过NL2SQL的方式直接获取与用户问题相关的数据或统计信息;从GraphDB中用NL2Cypher(显然这是在用Neo4J)获取关联知识。这些方法显然将给RAG带来更多新奇的体验。
图14:通过大模型搜索数据库来抽取信息
● 从数据库中获取统计指标
大模型可以将用户问题转化为SQL语句去数据库中检索相关信息,这个能力就是NL2SQL(Natural Language to SQL)。如果搜索的问题比较简单,只有单表查询,并获取简单的统计数据如求和、求平均等等,大模型还能稳定地生成正确SQL。如果问题比较复杂涉及多表联合查询,或者涉及复杂的过滤条件,或复杂的排序计算公式,大模型生成SQL的正确性就会下降。我们一般用Spider榜单来评测大模型生成SQL的性能。合理构造提示词调用Qwen-Max生成SQL,或者使用SFT微调小模型如Qwen-14B来生成SQL,都可以获得可满足应用的NL2SQL能力。
图15:Spider数据集和“执行正确率”评测榜单,榜单上排名靠前的DAIL-SQL+GPT4+Self-Consistency技术,就是使用先检索相似问题构造Few-Shot提示词,再用GPT4来生成SQL,并添加了多路召回策略的方法
● 从知识图谱中获取数据
Neo4j是一款图数据库引擎,可以为我们提供知识图谱构建和计算服务。在知识图谱上,我们调用各种图分析算法,如标签传播、关键节点发现等等,可以快速检索多度关联关系,挖掘隐藏关系。
图16:将用户的问题转化为Neo4j的Cypher查询语句,从知识图谱中获取关键知识