前言

RAG这个词听过很多次,我只是了解他大概是做什么的,今天看到一篇文章详细介绍了RAG的流程和基本原理,觉得很不错,就照着写了这篇文章,算是一种记录和知识扫盲吧。

by the way,感觉我使用的perflexity应该就是使用的这种RAG技术。

RAG(Retrieval Augmented Generation),检索增强生成为大模型提供了从某些数据源检索到信息,并作为大模型输入的上下文来提供答案的方法。

简单来说,就是通过向量进行检索到与query相关的信息,并将这些信息输入给大模型,通过Prompt使大模型基于这些进行进行回答。

基础的RAG技术

  • 将文本分割成块,然后使用基于Transformer decoder的模型将这些块嵌入到向量中,将所有这些向量放入一个索引中,最后为LLM创建一个提示,告诉模型在我们在搜索步骤中找到的上下文中回答用户的查询。
  • 在运行时,我们使用相同的编码器模型将用户的查询向量化,然后对索引执行这个查询向量的搜索,找到前k个结果,从我们的数据库中检索相应的文本块,并将它们作为上下文输入到LLM的提示中。

高级RAG技术

切分和向量化

  • 切分

我们知道一般大模型都有输入长度的限制,所以如何一篇文档很长的话,我们是不能直接将其进行向量化的。另外即使模型的上下文窗口很大,我们将一篇文档切分成段落也比直接将整篇文档进行向量化的效果要好,因为模型最终输入的向量是汇聚了整篇文档的全部信息,向量的维度是固定的,对于一段100个token的段落和10000个token的文档,模型所生成的向量都是一样的大小,但是他们包含的信息是有所的不同的,文本的长度越短,向量所包含的信息越精确。

所以我们尽量将篇幅较大的文档进行切分,切分的策略可以根据大模型的上下文窗口制定,尽量选择比上下文窗口小的长度。除此之外,在切分时要考虑将文档按照段落、句子进行切分,而不是把一句话从中间切开。

  • 向量化

切分后就需要选择一个模型来讲文本进行向量化,可以选择闭源的openAI的接口,也可以选择开源的模型。这里可以推荐一个开源的仓库,可以用来训练自己的embedding。FlagEmbedding,你也可以去huggingface的榜单选择排名靠前的模型。MTEB leaderboard

搜索索引

  • 向量存储索引

我们得到一批文档的向量后需要进行存储,这样才能够进行检索,无论检索是通过暴力的距离计算还是通过开源的faiss计算,你都需要将向量存储起来。

  • 分层索引

如果你需要从很多文档中检索信息,那对你的索引就有比较高的要求,一个有效的方法就是创建两个索引,一个由摘要组成,一个由文档块组成,并分两步进行检索。首先通过摘要筛选出相关的文档,然后在这些相关文档中检索文档块。

  • 假设性问题和HyDE

还有另外一种方法,那就是让大模型为每个块生成一个问题,并将这些问题嵌入向量中,在运行时针对这个问题向量索引进行查询搜索(在我们的索引中用问题向量替换块向量),然后在检索后路由到原始文档块,并将他们作为上下文提供给大模型。这种方法通过查询与假设性问题之间更高的语义相似性,提高了搜索质量。

还有一种逆向逻辑方法称为HyDE(Hypothetical Document Embeddings),让大模型给定查询生成一个假设性回应,然后使用其向量和查询向量来提高搜索质量。

  • 上下文丰富化

上下文丰富化试检索更小块以提高搜索质量,但是添加上下文让大模型进行推理。通常有两种做法,通过在检索到的较小块周围的句子扩展上下文,或者将文档递归地分割成包含较小子块的多个较大的父块。

  • 句子窗口检索

在这个方法中,文档中的乜咯句子都进行向量化,这提高了查询与上下文之间的相似性的准确性。为了在找到最相关的单个句子后更好的进行推理所发现的上下文,我们通过在检索到的句子前后扩展k个句子的上下文窗口,然后将此扩展上下文输入给大模型。

  • 自动合并检索器(又称父文档检索器)

这里的想法与句子窗口检索非常类似,搜索更精准的信息片段,然后将所述上下文提供给大模型进行推理之前扩展上下文窗口。文档被分割成较小的子块,这些子块引用较大的父块。

在这种方法中,首先在更细粒度的子块上进行搜索,找到与query最相关的块。然后,系统会自动将这些子块与他们所属的父块进行结合。这样就能在回答时,使大模型有更丰富的上下文。

  • 融合检索或者混合搜索

这个一个相对较老的想法,即从两个世界中各取所长,基于关键字的传统搜索(稀疏检索算法,如tf-idf和bm25)和现代语义或向量搜索,将这两种方法结合在一个检索结果中。这里唯一的技巧是正确组合具有不同相似性得分的检索结果,这个问题通常通过使用倒数排名融合算法来解决,重新排列检索结果以获得最终输出。

重排和过滤

使用上述任何算法得到检索结果后,需要进行过滤、重排或者一些转换来精炼这些结果了。在LlamaIndex中,有多种可用的后处理器,可以根据相似性分数、关键词、元数据过滤结果,或者使用其他模型进行重排,比如LLM、句子转换器交叉编码器、Cohere重排端点,或者基于日期的最新性等元数据——基本上,你能想到的都可以。

重排和过滤是在将检索到的上下文提供给LLM以获取最终答案之前的最后一步。现在是时候进入更复杂的RAG技术,如查询转换和路由,这两者都涉及到LLM,因此代表了主动性行为——在我们的RAG流程中涉及到一些复杂的逻辑,包括LLM的推理。

查询转换

查询转换是一系列技术,利用LLM作为推理殷勤来修改用户输入,以提高检索质量。

有几种不同的方式可以做到这一点,如果查询很复杂,LLM可以将其分解成几个子查询。

  • 回溯提示

回溯提示是使用LLM生成更一般的query,并检索该query,获得更一般或者高层次的上下文,有助于支撑我们对原始query的回答。

同时,也会对原始query进行检索,两种上下文都会输入给LLM。

  • 查询重写

查询重写使用LLM重构原始query。

聊天引擎

在构建一个能够针对单个查询多次运行的优秀RAG系统中,下一个重要的环节就是聊天逻辑,这与LLM时代的经典聊天机器人一样,需要考虑对话的上下文。这对于支持后续问题、指代消解或与先前对话上下文相关的任意用户命令是有必要的。可以通过查询压缩技术来解决,同时考虑聊天的上下文和用户的query。

上下文压缩方法有几种,一种是流行且相对简单的方法,ContextChatEngine。他首先检索与用户query相关的上下文,然后将其连同聊天记录一起发送给LLM,使LLM在生成下一个回答时能够了解之间的上下文。

另一个更复杂的方法是CondensePlusContextMode,在这种模式中,每次交互都会将聊天历史和最后一条消息压缩成一个新的query,然后这个query会进行检索索引,检索到的上下文连同原始的用户消息一起发送给LLM,生成答案。

查询路由

查询路由是一个以LLM为驱动的决策步骤,决定针对用户查询接下来要做什么,通常是概括总结、针对某些数据索引执行搜索、或尝试不同的路由,然后将他们的输出合并成一个答案。

你可能有多个数据来源,查询路由还用于选择索引,或者说选择数据存储位置,以发送用户query。

LlamaIndex和LangChain都支持查询路由器。

RAG中的代理

LlamaIndex和LangChain都支持的代理(agents),自从第一个LLM api发布以来,就已经存在了。这个想法是为一个能够进行推理的LLM提供一套工具和一个要完成的任务。这些工具可能包括一些确定性函数,如任何代码函数、外部api或者其他代理,LLM链式调用这些代理。

我们来看一个多文档代理方案,每个文档初始化一个代理,能够进行文档概要和经典的问答机制,并有一个顶级代理,负责将查询路由到文档代理,并进行最终答案的合成。

这种方法的好处是能够比较不同的解决方案或实体,缺点就是设计代理中LLM的多次来回迭代,速度比较慢。

响应合成器

这是任何RAG系统的最后一步,基于我们检索的所有上下文和厨师的query生成文档。最简单的方法是将所有获取到的上下文,连同query一起输入给LLM。但是,除此以外,还有其他更负责的选项,涉及多次LLM调用以优化检索到的上下文并生成更好的答案。

  1. 通过逐块将检索到的上下文发送给LLM来迭代地完善答案。
  2. 概括检索到的上下文以适应Prompt。
  3. 基于不同的上下文块生成多个答案,然后将他们连接或者概括起来。

编码器和LLM微调

我们可以对负责编码的模型和负责生成答案的LLM进行微调,幸运的是,后者是一个很好的few-shot leaner。

  • 编码器微调
  • ranker微调,如果你不信任你的编码器,你可以使用交叉编码器对检索结果进行排序。有点类似BERT中的双句任务,使用分隔符将query与文本块合并起来,相似为1,不相似为0。
  • LLM微调

评估

RAG系统性能评估有几个框架,它们共享一个理念,即拥有几个独立的指标,如整体答案相关性、答案的根据性、忠实度和检索到的上下文相关性。

Ragas框架使用忠实度和答案相关性作为生成答案质量的指标,以及经典的上下文精确度和召回率用于RAG方案的检索部分。

在Andrew NG最近发布的精彩短课程《构建和评估高级RAG》中,LlamaIndex和评估框架Truelens建议使用RAG三元组——检索到的上下文与查询的相关性、根据性(LLM答案受提供的上下文支持的程度)以及答案与查询的相关性。

最关键且最可控的指标是检索到的上下文相关性——基本上上面描述的高级RAG流程的部分以及编码器和排名器微调部分旨在改善这一指标,而第响应合成器和LLM微调则专注于答案相关性和根据性。

一个相当简单的检索器评估流程的例子可以在这里找到,并且已应用于编码器微调部分。一种更高级的方法不仅考虑命中率,还考虑了平均倒数排名(一个常见的搜索引擎指标)以及生成答案的指标,如忠实度和相关性,这在OpenAI cookbook中有所展示。

LangChain有一个相当先进的评估框架LangSmith,可以实现自定义评估器,它还监控RAG流程中的运行轨迹,以使你的系统更透明。

如果你在使用LlamaIndex构建,那么有一个rag_evaluator llama包,提供了一个快速工具,用公共数据集评估你的流程。

参考文章

Advanced RAG Techniques: an Illustrated Overview