当今,大多数的公司主要依靠文档开发运营工作。这或许是近年来检索增强生成系统如此流行的原因之一。这个想法很简单:通过向系统提示和用户查询提供附加信息,克服LLM当前的局限性——例如幻觉和知识截止。
于是,人们可以快速构建和部署第一个RAG应用程序原型。但是,当用户真正与这套首个RAG系统交互时,缺陷开始显现。RAG系统给出的答案显得肤浅,重要的上下文信息被遗漏,检索悄然失败。而且,系统的性能还随着文档数量的增加而下降。
不过,我们最终还是可以通过制定计划来构建、部署和增强先进的RAG应用程序,从而避免这种情况。
本文将提供从最初的客户问题到最终部署的应用程序的清晰路线图,从而简化了开发满足人们需求的高级RAG框架的复杂过程。
本文将上述整个过程划分为10个具体步骤,从而有利于方便指导每一个RAG项目实现从构思到生产的全流程。
1. 需要解决的问题
在进行任何编程之前,我们必须问自己:实际问题是什么?我们对情况了解得越深入,就越容易找到解决方案。
这个问题理解过程的一个重要部分是确定用户的理想或“梦想”结果是什么样的。
明确定义问题和期望结果标志着我们路线图的开始和结束。
解决该问题的一个可能方案是检索增强生成(RAG)系统,该系统能够利用外部知识增强大型语言模型。这有助于克服信息过时或领域专业知识浅薄等局限性。
然而,RAG系统引入了额外的复杂性。现在,需要开发和维护两个独立的组件:索引部分和生成部分。
因此,我们首先应该问:这个问题有没有更简单的解决方案?也许一个基本的关键词搜索、一个基于规则的系统或一个常见问题解答页面就足够了——这些都不需要LLM。
RAG的替代方案是缓存增强生成,它将整个上下文加载到模型的内存中,从而无需完全检索。
解决问题的方法有很多,RAG只是其中之一。不要落入“工具法则”(又称“马斯洛锤”)的陷阱。该法则描述的是一种认知偏差,即过度依赖熟悉的工具,即使它并非最合适的解决方案。
一位留着胡子、戴着帽子的卡通男子在一辆爆胎的汽车旁边弹吉他唱歌,旁边有一个对话框说:“♪我的方向盘掉了,但我的精神很高♪”。
此图展示了马斯洛锤子理论的一个幽默例子:一位音乐家坐在车旁,拿着吉他唱歌。真正的问题是爆胎了;但是,他没有去换,而是开始写一首关于爆胎的歌,因为写歌是他应对任何问题的工具。
2. 为问题提供的数据
最终的RAG系统的质量取决于你输入的文档。
首先,我们必须确保手头有合适的文件来解答最常见情况的问题。如果没有足够且最新的文件,LLM通常会给出“我不知道”的答案,或者在最糟糕的情况下,给出一个不切实际的答案。
收集完所有必需的文档后,我们来检查一下数据的格式和质量。它是纯文本、PDF、HTML页面、图像,还是各种格式的混合?
从文档中提取信息是任何RAG流程中不可避免的一步,而你的LLM答案的质量很大程度上取决于你从来源中提取内容的效率。
换句话说:输入的是垃圾,输出的也是垃圾。
垃圾进,垃圾出(【引文1】)
此图展示的这幅火柴人漫画幽默地解释了机器学习:一个人将“数据”倒入标有“线性代数”的一堆数据中,在另一边收集“答案”,如果答案是错误的,他们只需“搅拌这堆数据,直到看起来正确为止”。
结构化数据(例如数据库或CSV文件)非常适合处理。这些格式更易于查询,并更易于线性化为文本。
相反,非结构化数据则需要更多的处理步骤,因为信息并非以易于使用的形式提供。非结构化文档处理流程可能包含多个步骤,具体取决于所使用的工具。我最喜欢的PDF处理工具是:
3.数据预处理
收集所需数据并选择适当的信息提取方法后,可以设计预处理流程。
这一步至关重要,因为数据的切片和注释方式直接影响检索器为LLM找到的内容。
在数据预处理的设计过程中,应考虑以下几点:
(1)分块策略
分块策略定义了在索引之前如何将文档拆分成多个部分。这是必要的,因为嵌入模型和LLM都有输入标记的限制。此外,所选的块大小对整体系统性能有巨大的影响。
GPT-3.5-Turbo-0613在答案位于20个文档上下文(约4000个标记)的开头或结尾时,准确率最高,中间部分则急剧下降,呈现出U形的位置偏差。其中的虚线表示闭卷准确率(56.1%)(【引文2】)。
此折线图展示了准确率与包含答案的文档在总共20篇检索到的文档中的位置的关系。随着答案从第一位移动到后面的位置,准确率急剧下降,在中间位置触底,并在接近第20位时再次上升。红色虚线标记了闭卷考试的准确率,以便进行比较。图例显示了“GPT-3.5-Turbo-0613”和“GPT-3.5-Turbo-0613(闭卷)”的结果。
(2)元数据提取
每个块可以携带元数据(属性),这些元数据提供上下文信息,但不出现在正文中。例如,元数据可以包括文档标题、作者、日期、章节名称、来源URL等。
这些元数据对于筛选或提升最终检索过程非常有用。它还支持访问控制机制,允许系统确定哪些人可以访问某些信息。
元数据可以手动提供,也可以自动派生,例如,使用命名实体识别(NER)来标记提到的产品或人员,或者对每个块的主题进行分类。
(3)搜索方法
RAG并非只要求向量搜索。任何搜索方法都可以用来检索相关的文本块。常用方法包括向量搜索、关键字搜索和混合搜索。
(4)索引和存储
随着RAG应用的日益普及,提供向量存储功能的公司数量也显著增加。由于选项众多,我在下表中总结了其中最突出的几个,以便你做出更好的决策。
此表总结了主流向量数据库(包括Meta FAISS、Pinecone、Weaviate、Milvus、ChromaDB、Qdrant和Neo4j)的主要特性。表中列出了每个数据库的常规信息、开源状态、开发语言、算法、混合搜索、距离度量、优势和局限性。
这里展示了当前业界领先的向量数据库的功能比较,突出它们的优势、支持的算法以及高效相似性搜索和人工智能应用的关键限制。
我个人最喜欢的是Qdrant,因为它提供了高级搜索功能,让你可以创造性地设计检索过程。
4.原型设计
基于“快速失败”的原则,原型设计在RAG系统的设计过程中起着至关重要的作用。如果没有丰富的经验,我们很可能不知道什么可行,什么行不通。
找出答案的最佳方法是快速构建第一个原型——从Jupyter笔记本中的基本RAG应用程序开始,不断增加复杂性以更接近所需的输出。
这样,我们就获得了关注,获得了实践经验,并确保如果最初的想法表现不佳或没有满足用户的要求,后续的工作不会导致时间的浪费。
这些实验可以涵盖多个方面,具体取决于最终目标和数据源。然而,向量存储的性能和相关性很大程度上取决于所使用的嵌入模型。
选择合适模型的一个很好的起点是参考HuggingFase嵌入排行榜,它根据不同的指标能够比较各种语言的文本和图像嵌入模型。
下面给出了嵌入排行榜界面截图,展示了多语言模型的性能。图表绘制了任务平均得分与参数数量的关系,各种模型以绿色气泡表示,左侧为基准类型的过滤器。
HuggingFase嵌入排行榜通过性能、大小和标记容量比较了1,000多种语言和131个任务的多语言嵌入模型。
关于向量存储,我会在原型设计阶段选择FAISS、ChromaDB或Qdrant,因为这三个数据库都提供了方便的本地实现,使得它们非常适合在Jupyter笔记本中进行实验。
设置初始索引原型后,你可以开始首次手动实验,并根据用户查询检索相关块。请考虑以下几点来手动评估结果:
5. 增强检索过程
如果原型阶段尚未完成,现在是时候根据从原型中获得的见解来提升检索过程了。即使是设计最精良、组织最完善的向量存储,如果没有能够有效利用其丰富结构的检索策略,也是毫无价值的。
与数据处理管道一样,链条的强度取决于其最薄弱的环节——并且在许多RAG应用程序中,该薄弱环节通常是检索过程。
如果你不确定如何进一步改进检索过程,以下给出三个基础型建议:
许多向量数据库使用HNSW(分层可导航小世界图)或其他近似最近邻(ANN)算法。你通常可以调整它们的参数,以在速度和准确性之间取得更好的平衡。
从我的角度来看,Pinecone在解释和可视化HNSW算法方面做得非常出色,如下图所示:
HNSW(分层可导航小世界图,见【引文3】)
该图展示了由箭头连接的三层节点,展示了HNSW(分层可导航小世界)图搜索过程。蓝色“入口点”从顶层开始,向下遍历各层,到达底层的黄色“查询向量”及其最近邻。
选择不同的相似度度量
余弦相似度很常用,但并非唯一选择。其他度量包括内积、欧氏距离和曼哈顿距离。根据你的数据和任务的不同情形,其中一种度量可能比其他度量表现更好。
集成混合搜索方法
混合搜索允许你将基于关键字(稀疏)的搜索与密集向量搜索相结合。许多向量数据库(例如Qdrant)支持混合搜索,并允许你配置复杂的组合,例如其博客文章图片中所示的组合。
此流程图展示了两种混合搜索流程:一种使用维度不断增加的Matryoshka嵌入,并在每个阶段重新排序;另一种使用密集、全密集和稀疏向量,在最终重新排序步骤(称为“后期交互”)之前融合它们的结果,以选择前10个匹配项。
在此图展示的Qdrant混合搜索流程的示例中,该流程将密集和稀疏向量检索与多阶段重排序相结合,以获得最佳搜索结果(【引文4】)。
添加业务逻辑
这是一个广泛的概念,指的是定制检索以更好地适合你的领域或用户的期望。
例如,你可能会提升某些类型的内容:如果你知道官方政策文件比Slack消息更值得信赖,那么你可以为来自“政策”文档的任何块分配更高的分数。
此外,许多向量数据库允许你按元数据(例如创建时间或其他上下文)进行筛选。这允许你将新近度和特定领域的筛选纳入检索过程。
用于重新排序的交叉编码器
交叉编码器将查询和单个文档一起作为输入,并直接输出相关性分数。
交叉编码器通常能更准确地进行排名,因为它们能捕捉查询和文档之间的微妙关系。然而,由于必须对每个文档进行单独评估,因此速度较慢。
下面的示意流程链接了一个图片和网页,完美地概括了双编码器和交叉编码器之间的区别。
双编码器与交叉编码器的比较(【引文5】)
这张图比较了双编码器和交叉编码器。双编码器通过BERT和池化分别处理两个句子,然后测量相似度;交叉编码器通过BERT和分类器同时处理两个句子,以获得相关性分数。
然而,面对所有这些可能性,人们很容易过度设计。在这种情况下,我始终牢记一个原则:保持简单,简洁(KISS)。整个框架应该尽可能简单——并且只在必要时才考虑复杂性。
顺便说一句,如果你决定使用自定义或开源模型进行嵌入、重新排序,甚至将其作为LLM而不是完全托管的服务,欢迎来到MLOps的世界!
除了设计和维护实际的RAG框架之外,还必须部署、监控和持续更新这些模型。
如果你希望避免这些额外的运营开销,不妨从托管服务入手,快速实现创新。最终,这始终还是要在控制力和便捷性之间进行权衡。
6. LLM答案生成
从数据源检索到最相关的文档后,我们将重点转移到RAG的生成部分。这一步骤旨在设计LLM如何使用检索到的信息来生成有用、正确且安全的响应。
LLM类型繁多,各有优缺点。申请LLM类型时,应考虑以下几点:
7.评估
目前为止,我们仅对中间步骤和结果进行了人工检查。然而,为了获得更深入的洞察,我们需要进行更全面、更准确的评估。
在其他软件项目中,单元测试通常在这一阶段编写。然而,对于RAG开发来说,事情有点棘手。
评估步骤从组装“黄金数据集”开始,其中包括你的系统应该能够处理的一组示例问题及其正确答案。
构建这样的数据集本身就是一种挑战,并且极大地受益于领域专家和最终用户之间的对话。
最后,每个问题都与一个基本事实信息源相关联。例如,如果可能的话,问题X应该由文档Y第2部分来回答。
RAG框架的整体评估是不同指标和工具的混合。
例如,信息检索(IR)指标衡量检索器获取相关信息的效果。这一过程常见指标包括:
8.改进和实验
基于评估过程中获得的洞察,RAG框架可以得到进一步增强。此阶段遵循一个迭代循环:实验→评估→改进→重复。
这一步不是要增加随机的复杂性,而是要进行明智的实验。
以下是一些值得尝试的领域:
RAPTOR树展示:分层分块逐层聚类和汇总文本块,构建结构化树,以实现高效的文档理解和检索(【引文12】)
9. RAG框架的部署
到目前为止,我们大多数人只在本地完成了开发。现在,到了一个重要的步骤:在生产基础设施中部署RAG系统。
这一步涉及软件工程和DevOps,以确保你的管道在现实世界中稳定、可扩展且可维护。
这通常需要:
所有上述这些观点其实都只是触及了AI应用程序部署的表面,这至少与RAG框架本身的开发一样是一个广泛的话题,因此值得专门写一篇文章来探讨。
如果你不知道从哪里开始,那么在Hugging Face Spaces上部署和分享你的第一批成果将会给你带来一个快速的胜利体验,并允许你收集初步反馈。
下一步,Google Firebase可能会成为你的一个不错的选择,因为它可以为你自动处理后端。
10.持续改进的反馈循环
然而,在应用程序开发、测试和部署之后,RAG框架并不完整。
在收集初步用户反馈后——无论上一步选择了哪种部署方法——都必须不断整合这些反馈,以便更好地满足用户的需求。
这使得RAG与许多软件应用程序一样,不再是一个静态产品,而是一个不断发展的服务。
本文描述的10个步骤强调了构建高级RAG应用程序不仅仅是检索几个相关的上下文块的问题。这是一项必须根据特定领域及其独特要求进行定制的服务——强调没有一刀切的解决方案。这是因为文档类型多样,并且每个领域都有不同的特定需求。
通过遵循本文概述的10个步骤,在开发RAG应用时,人们可以从基本原型转变为可以投入生产的服务,从而以可靠、可扩展和智能的方式真正帮助终端用户。