第二章 高级RAG知识
高级基础知识
RAG(中文为检索增强生成) = 检索技术 + LLM 提示
我们向 LLM 提问一个问题(answer),RAG 从各种数据源检索相关的信息,并将检索到的信息和问题注入到 LLM 提示中,LLM 最后给出答案。
因此,常规的RAG流程包括indexing(建立索引)、retrieval(查询)和generation(生成)三部分。
简单的RAG图示
高级RAG应用图示
RAG的几个主要内容:
分块 (Chunking) & 向量化 (Vectorisation)
首先我们需要为文档内容创建向量索引,然后在运行时搜索与查询向量余弦距离最近的向量索引,这样就可以找到与查询内容最接近语义的文档。
1.1 分块 (Chunking)
Transformer 模型具有固定的输入序列长度,即使输入上下文窗口很大,一个句子或几个句子的向量也比几页文本的向量更能代表其语义含义,因此对数据进行分块—— 将初始文档拆分为一定大小的块,而不会失去其含义。有许多文本拆分器实现能够完成此任务。
块的大小是一个需要重点考虑的问题。
块的大小取决于所使用的嵌入模型以及模型需要使用 token 的容量。
如基于 BERT 的句子转换器,最多需要 512 个 token,OpenAI ada-002 能够处理更长的序列,如 8191 个 token,但这里的折衷是 LLM 有足够的上下文来推理,而不是足够具体的文本嵌入,以便有效地执行搜索。
1.2 向量化 (Vectorisation)
下一步是选择一个搜索优化的模型来嵌入我们的块。有很多选项,比如 bge-large 或 E5 嵌入系列。只需查看 MTEB 排行榜以获取最新更新即可。
搜索索引
2.1 向量存储索引
RAG 管道的关键部分是搜索索引,它存储了我们在上一步中获得的向量化内容。最原始的实现是使用平面索引 — 查询向量和所有块向量之间的暴力计算距离。
为了实现1w+元素规模的高效检索,搜索索引应该采用向量索引,比如 faiss、nmslib 以及 annoy。这些工具基于近似最近邻居算法,如聚类、树结构或HNSW算法。
此外,还有一些托管解决方案,如 OpenSearch、ElasticSearch 以及向量数据库,它们自动处理上面提到的数据摄取流程,例如Pinecone、Weaviate和Chroma。
取决于你的索引选择、数据和搜索需求,还可以存储元数据,并使用元数据过滤器来按照日期或来源等条件进行信息检索。
2.2 分层索引
在大型数据库的情况下,一个有效的方法是创建两个索引——一个由摘要组成,另一个由文档块组成,然后分两步进行搜索,首先通过摘要过滤掉相关文档,然后只在这个相关组内搜索。
2.3 假设性问题和 HyDE
另一种方法是让 LLM 为每个块生成一个问题,并将这些问题嵌入到向量中,在运行时对这个问题向量的索引执行查询搜索(将块向量替换为索引中的问题向量),然后在检索后路由到原始文本块并将它们作为 LLM 获取答案的上下文发送。
这种方法提高了搜索质量,因为与实际块相比,查询和假设问题之间的语义相似性更高。
还有一种叫做 HyDE 的反向逻辑方法——你要求 LLM 在给定查询的情况下生成一个假设的响应,然后将其向量与查询向量一起使用来提高搜索质量。
2.4 内容增强
这里的内容是将相关的上下文组合起来供 LLM 推理,以检索较小的块以获得更好的搜索质量。
有两种选择:
- 一种是围绕较小的检索块的句子扩展上下文,
- 一种是递归地将文档拆分为多个较大的父块,其中包含较小的子块。
2.4.1 语句窗口检索器
在此方案中,文档中的每个句子都是单独嵌入的,这为上下文余弦距离搜索提供了极大的查询准确性。
为了在获取最相关的单个句子后更好地推理找到的上下文,我们将上下文窗口扩展为检索到的句子前后的 k 个句子,然后将这个扩展的上下文发送到 LLM。
2.4.2 自动合并检索器(或父文档检索器)
这里的思路与语句窗口检索器非常相似——搜索更精细的信息片段,然后在在LLM 进行推理之前扩展上下文窗口。文档被拆分为较小的子块,这些子块和较大的父块有引用关系。
首先在检索过程中获取较小的块,然后如果前 k 个检索到的块中有超过 n 个块链接到同一个父节点(较大的块),我们将这个父节点替换成给 LLM 的上下文——工作原理类似于自动将一些检索到的块合并到一个更大的父块中,因此得名。
2.5 融合检索或混合搜索
这是一个很早以前的思路:结合传统的基于关键字的搜索(稀疏检索算法,如 tf-idf 或搜索行业标准 BM25)和现代语义或向量搜索,并将其结果组合在一个检索结果中。
这里唯一的关键是如何组合不同相似度分数的检索结果。这个问题通常通过 Reciprocal Rank Fusion 算法来解决,该算法能有效地对检索结果进行重新排序,以得到最终的输出结果。
在 LangChain 中,这种方法是通过 Ensemble Retriever 来实现的,该类将你定义的多个检索器结合起来,比如一个基于 faiss 的向量索引和一个基于 BM25 的检索器,并利用 RRF 算法进行结果的重排。
在 LlamaIndex 中,这一过程也是以类似的方式 实现 的。
混合或融合搜索通常能提供更优秀的检索结果,因为它结合了两种互补的搜索算法——既考虑了查询和存储文档之间的语义相似性,也考虑了关键词匹配。
重排(reranking)和过滤(filtering)
我们使用上述任何算法获得了检索结果,现在是时候通过过滤、重排或一些转换来完善它们了。
在 LlamaIndex 中,有各种可用的后处理器,根据相似性分数、关键字、元数据过滤掉结果,或使用其他模型(如 LLM)、sentence-transformer 交叉编码器,Cohere 重新排名接口或者基于元数据重排它们。
这是将检索到的上下文提供给 LLM 以获得结果答案之前的最后一步。
现在,我们将探索更高级的 RAG 技术,比如查询转换和路由。
这些技术涉及到大语言模型的使用,代表了一种更复杂的逻辑思维——在 RAG 流程中融合了 LLM 的推理能力。
查询转换
查询转换是一系列技术,使用 LLM 作为推理引擎来修改用户输入以提高检索质量。有很多技术实现可供选择。
对于复杂的查询,大语言模型能够将其拆分为多个子查询。比如,
- 当你问:“在 Github 上,Langchain 和 LlamaIndex 这两个框架哪个更受欢迎?”,
我们不太可能直接在语料库找到它们的比较,所以将这个问题分解为两个更简单、具体的合理的子查询:
- “Langchain 在 Github 上有多少星?”
- “Llamaindex 在 Github 上有多少星?”
这些子查询会并行执行,检索到的信息随后被汇总到一个 LLM 提示词中。这两个功能分别在 Langchain 中以多查询检索器的形式和在 Llamaindex 中以子问题查询引擎的形式实现。
- Step-back prompting 使用 LLM 生成一个更通用的查询,以此检索到更通用或高层次的上下文,用于为我们的原始查询提供答案。同时执行原始查询的检索,并在最终答案生成步骤中将两个上下文发送到 LLM。
这是 LangChain 的一个示例实现。
- 查询重写使用 LLM 来重新表述初始查询,以改进检索。LangChain 和 LlamaIndex 都有实现,个人感觉LlamaIndex 解决方案在这里更强大。
聊天引擎
关于构建一个可以多次用于单个查询的完美 RAG 系统的下一件工作是聊天逻辑,就像在 LLM 之前时代的经典聊天机器人中一样考虑到对话上下文。
这是支持后续问题、代词指代或与上一个对话上下文相关的任意用户命令所必需的。它是通过查询压缩技术解决的,将聊天上下文与用户查询一起考虑在内。
与往常一样,有几种方法可以进行上述上下文压缩——一个流行且相对简单的 ContextChatEngine,首先检索与用户查询相关的上下文,然后将其与内存缓冲区中的聊天记录一起发送到 LLM,以便 LLM 在生成下一个答案时了解上一个上下文。
更复杂的情况是 CondensePlusContextMode——在每次交互中,聊天记录和最后一条消息被压缩到一个新的查询中,然后这个查询进入索引,检索到的上下文与原始用户消息一起传递给 LLM 以生成答案。
查询路由
查询路由是 LLM 驱动的决策步骤,决定在给定用户查询的情况下下一步该做什么——选项通常是总结、对某些数据索引执行搜索或尝试许多不同的路由,然后将它们的输出综合到一个答案中。
查询路由器还用于选择数据存储位置来处理用户查询。这些数据存储位置可能是多样的,比如传统的向量存储、图形数据库或关系型数据库,或者是不同层级的索引系统。
在处理多文档存储时,通常会用到摘要索引和文档块向量索引这两种不同的索引。
定义查询路由器包括设置它可以做出的选择。
选择特定路由的过程是通过大语言模型调用来实现的,其结果按照预定义的格式返回,以路由查询指定的索引。
如果是涉及到关联操作,这些查询还可能被发送到子链或其他智能体,如下面的多文档智能体方案所展示的那样。
LlamaIndex 和 LangChain 都提供了对查询路由器的支持。
智能体(Agent)
智能体( Langchain 和 LlamaIndex 均支持)几乎从第一个 LLM API 发布开始就已经存在——这个思路是为一个具备推理能力的 LLM 提供一套工具和一个要完成的任务。
这些工具可能包括一些确定性功能,如任何代码函数或外部 API,甚至是其他智能体——这种 LLM 链接思想是 LangChain 得名的地方。
智能体本身就是一个复杂的技术,不可能在 RAG 概述中深入探讨该主题。
这种复杂配置的缺点可以通过图片发现 —— 由于需要在智能体内部的大语言模型之间进行多次往返迭代,其运行速度较慢。顺便一提,LLM 调用通常是 RAG 管道中耗时最长的操作,而搜索则是出于设计考虑而优化了速度。因此,对于大型的多文档存储,我建议考虑对此方案进行简化,以便实现扩展。
响应合成
这是任何 RAG 管道的最后一步——根据我们检索的所有上下文和初始用户查询生成答案。
最简单的方法是将所有获取的上下文(高于某个相关性阈值)与查询一起连接并提供给 LLM。
响应合成的主要方法有:
- 通过将检索到的上下文逐块发送到 LLM 来优化答案
- 概括检索到的上下文,以适应提示
- 根据不同的上下文块生成多个答案,然后将它们连接或概括起来。
评估
如何评价RAG的效果
对RAG的评估方法多种多样,主要包括三个质量分数:上下文相关性、答案准确性和答案相关性。此外,评估还涉及四项关键能力:抗噪声能力、拒绝能力、信息整合以及反事实鲁棒性。这些评价维度将传统的定量指标与针对RAG特点的专门评估标准相结合,尽管这些标准尚未得到标准化。
RAG 系统性能评估的多个框架,都包含了几项独立的指标,例如总体答案相关性、答案基础性、忠实度和检索到的上下文相关性。
在之前章节提到的 Ragas,使用真实性和答案相关性来评价生成答案的质量,并使用经典的上下文精准度和召回率来评估 RAG 方案的检索性能。
最近推出的课程构建和评估高级 RAG中,以及 LlamaIndex 和评估框架Truelens,他们提出了RAG 三元组评估模式 — 分别是
- 对问题的检索内容相关性、
- 答案的基于性(即大语言模型的答案在多大程度上被提供的上下文的支持)
- 答案对问题的相关性。
LangChain 提供了一个颇为先进的评估框架 LangSmith。在这个框架中,你不仅可以实现自定义的评估器,还能监控 RAG 管道内的运行,进而增强系统的透明度。
如果你正在使用 LlamaIndex 进行构建,可以尝试 rag_evaluator llama pack。
总结
本章试图勾勒出 RAG 的核心算法方法,并展示其中的一些,希望这能激发你在 RAG 流程中尝试一些新思路,或者为今年涌现的众多技术带来一定的系统性。
还有许多其他因素需要考虑,例如 基于网络搜索的 RAG(如 LlamaIndex 的 RAGs、webLangChain 等),更深入地探索 智能体架构(以及最近 OpenAI 在这个领域的投资),以及一些关于 大语言模型长期记忆 的想法。
RAG 系统的主要生产挑战除了回答的相关性和忠实度外,还有速度。
RA/SD 衍生者AI训练营。发布者:chris,转载请注明出处:https://www.shxcj.com/archives/6885