RAG 应用开发完整指南
从零开始构建 RAG 应用的系统教程,涵盖核心步骤和最佳实践。适合程序员快速掌握 RAG 系统设计的关键环节。
从零开始构建 RAG 应用的系统教程,涵盖核心步骤和最佳实践。适合程序员快速掌握 RAG 系统设计的关键环节。
生成式AI的出现为我们构建的应用带来了新的可能性。LLM能够以惊人的水平回答用户的问题。那么,为什么不把它们作为我们系统的一部分呢?如果用户需要帮助浏览应用,我们可以添加一个聊天功能,LLM会回答用户的所有问题。如果我们的应用有解释重要概念的博客文章,用户就不必阅读所有文章来获得所需知识,而是可以直接提问并立即获得回答。
我们决定将LLM集成到应用中以给用户提供这些功能。但我们很快发现模型无法回答用户的问题。它没有关于我们应用的任何信息!如果回答所需的信息不在LLM的训练数据中,它就无法回答。更糟的是,如果它不知道答案,可能会幻觉出完全错误的事实!这很糟糕,那我们怎么解决呢?具有Transformer架构的LLM已经展示了很强的上下文学习能力。所以我们只需要在提示中把它需要的所有事实和问题一起传递给它!哎呀,在每个提示中填充所有数据肯定会非常昂贵。那我们该怎么做呢?
RAG代表检索增强生成(Retrieval Augmented Generation)。RAG与Transformer一起诞生。最初,它被用来用额外事实增强LLM的预训练数据。一旦Transformer的上下文学习能力变得明显,在推理时增强提示也成为了普遍做法。
基础RAG管道包括三个步骤:索引、检索和生成。LLM回答所需的所有信息都被索引在向量数据库中。当用户提出问题时,我们可以从向量数据库中检索相关的信息部分。最后,将相关信息和用户的问题一起提供给LLM,它可以基于我们提供的上下文信息来生成答案。让我们更详细地看看如何实现这个过程。
首先,我们从任何位置提取模型需要的信息。生成式模型与纯文本一起工作(有些模型也可以与图像或其他格式一起工作,这些也可以被索引,但这是另一个话题)。如果信息已经是纯文本格式,我们很幸运。但它也可能在PDF文档、Word文档、Excel、Markdown等中。我们必须将这些数据转换为纯文本并清理它,使其可用于模型。
一旦信息是文本格式,我们可以将其存储在向量数据库中。向量数据库将存储该文本的向量表示。这将允许我们搜索具有与另一文本相似的向量表示的文本部分,因此它们涉及相似的概念。我们将整个文本分成较小的部分或块,计算每个部分的向量表示,最后将其存储在向量数据库中。
当用户问我们一个问题时,我们可以将该问题转换为向量表示,使用我们用来索引数据的相同向量模型。利用该向量表示,我们将计算问题与存储在向量数据库中的每个块之间的相似度。我们将选择与查询最相似的前K个块,因此它们的内容与问题涉及相同的概念(因此它们可能包含答案)。
构建一个提示,将用户的问题和相关上下文放在一起以帮助LLM回答。我们也可能包括用户与AI助手之间的对话中的先前消息。LLM基于上下文为用户生成答案,而不是基于其之前学到的预训练数据。
对于这个例子,我们将摄入一篇名为"Retrieval-Augmented Generation for Large Language Models: A Survey"的论文。我们将使用此论文中包含的信息查询LLM,以便它可以回答用户关于其内容的问题。您可以在为本文提供的Google Colab笔记本中跟随此示例。
首先,我们将使用LangChain的PyPDF连接器加载并解析PDF文档。
一旦我们从文档中获得了文本,我们必须将其分割成较小的块。我们可以使用LangChain提供的分割器,例如在这种情况下的RecursiveCharacterSplitter:
我们将使用BGE-small,一个开源向量模型。我们将从HuggingFace Hub下载它,并在所有块上运行它以计算它们的向量表示。
一旦我们拥有所有块的向量表示,我们可以创建一个内存向量数据库并在其中存储所有向量。对于这个例子,我们将使用FAISS数据库。
现在数据库已经设置好了。现在,我们将接收用户对此信息的查询。在这种情况下,用户询问Naive RAG的缺点是什么。我们使用与之前相同的向量模型对该查询进行编码。然后,我们检索与该查询最相似的前5个块。
检索到相关上下文后,我们使用此信息和用户的原始查询构建一个提示。对于本例,我们将使用Claude的Haiku作为LLM:
正如标题所示,这个解决方案是一个基础或朴素的RAG实现。它将使您的应用能够充分利用所使用的LLM和您的数据。但它不适用于所有情况。这些只是RAG最常见的一些问题:
检索不相关的信息。 如果检索器从向量数据库中获取与问题无关的数据,它将会混淆试图回答问题的模型。这可能导致要么不使用上下文来回答问题,要么回答与所问不同的内容。
遗漏重要信息。 也许回答问题所需的信息不在数据库中。也许检索机制未能找到相关的块。我们必须找到方法帮助检索器更容易、更可靠地找到它需要的信息。
生成不受上下文支持的响应。 如果上下文有模型需要的信息,但它不使用它,而是依赖自己的预训练数据,那么所有这一切都白费了。来自预训练数据的信息可能已过时或错误。我们必须使模型倾向于始终使用上下文来回答,或者如果它无法从上下文回答,则回答"我不知道"。
与查询无关的响应。 LLM可能使用您给它的所有信息来生成响应,但这并不意味着它回答了用户的问题。重要的是模型坚持用户的原始问题,而不是陷入大量信息中。
由相似上下文引起的冗余响应。 当我们摄入多个包含相似信息的文档时,检索器有可能获得多个几乎说相同事情的信息块。这可能导致LLM在其响应中多次重复相同的信息。
为了避免这些问题,朴素的RAG管道可能不够。我们需要建立一个更先进和复杂的RAG系统。存在经过验证的技术来解决我们列出的问题。我们可以将它们纳入我们的RAG管道中以改进RAG应用的性能。
另一个重要的问题是,为了改进您的RAG应用,您需要能够测量和评估整个过程。你无法改进无法测量的东西。此外,当您进行评估时,您可能会发现基本的RAG设置足以满足您的使用情况,您不需要过度复杂化它。毕竟,即使是非常基础的RAG实现也可以极大地改进您的LLM驱动应用。
在未来的文章中,我将更详细地解释高级RAG技术,这将帮助我们避免常见问题并将我们的RAG应用提升到下一个级别。
某些评论可能仅对登录用户可见。登录以查看所有评论。
如需进一步操作,您可以考虑屏蔽此人和/或举报滥用行为。