如何构建基本的 RAG 应用程序
生成式人工智能的出现,为我们构建的应用程序带来了新的功能。LLM 能够以令人难以置信的技巧回答用户的问题。那么,何不将它们融入我们的系统呢?如果用户在使用应用程序时需要帮助,我们可以添加聊天功能,LLM 会解答用户的所有问题。如果我们的应用程序包含解释重要概念的博客文章,那么用户无需阅读所有文章来获取所需知识,只需提问即可立即获得回复。
为什么选择 RAG?
我们决定将 LLM 集成到我们的应用中,以便将这些功能带给用户。然而,我们很快发现模型无法回答用户的问题。它没有任何关于我们应用的信息!如果回答所需的信息不在 LLM 的训练数据中,它就无法回答。更糟糕的是,如果它不知道答案,可能会产生完全错误的事实!这很糟糕,那么我们该如何解决这个问题呢?采用 Transformer 架构的 LLM 已经展现出强大的上下文学习能力。因此,我们只需在提示中传递它需要的所有事实以及问题即可!哎呀,把所有数据都塞进每个提示中肯定很昂贵。那么,我们该怎么做呢?
什么是 RAG?
RAG 代表检索增强生成 (Retrieval Augmented Generation)。RAG 与 Transformers 同时诞生。最初,它用于通过附加事实来增强 LLM 的预训练数据。随着 Transformers 的上下文学习能力逐渐显现,在推理过程中增强提示也成为一种常见做法。
基本的 RAG 流程包含三个步骤:索引、检索和生成。LLM 需要回答的所有信息都已索引到向量数据库中。当用户提出问题时,我们可以从该向量数据库中检索相关信息的相关部分。最后,结合相关信息和用户的问题,我们可以提示 LLM 根据我们提供的上下文信息给出答案。让我们更详细地了解如何实现这一点。
索引
首先,我们从任何位置提取模型所需的信息。生成模型处理纯文本(有些模型也可以处理图像或其他格式,这些格式也可以被索引,但这是另一个话题)。如果信息已经是纯文本,那我们很幸运。但它也可能存在于 PDF 文档、Word 文档、Excel、Markdown 等文件中。我们必须将这些数据转换为纯文本并进行清理,以便模型可以使用。
一旦信息转化为文本格式,我们就可以将其存储在向量数据库中。向量数据库将存储该文本的嵌入表示。这样,我们就可以搜索文本中与其他文本具有相似嵌入表示的部分,从而找到它们所代表的概念。我们将整个文本分成更小的部分或块,计算每个部分的嵌入表示,最终将它们存储在向量数据库中。
检索
当用户向我们提问时,我们可以使用与索引数据相同的嵌入模型,将问题转换为向量表示。利用该向量表示,我们将计算问题与向量数据库中存储的每个块之间的相似度。我们将选择与查询最相似的前 K 个块,因此它们的内容与问题的概念大致相同(因此它们可能包含答案)。
一代
构建一个提示,将用户的问题和相关上下文整合在一起,以帮助 LLM 进行回答。我们可能还会包含用户与 AI 助手之间对话的先前消息。LLM 会根据上下文(而不是之前学习的预训练数据)为用户生成答案。
例子
在本例中,我们将导入一篇名为“检索增强生成语言模型:综述”的论文。我们将使用这篇论文中的信息查询 LLM,以便它能够回答用户关于其内容的问题。您可以在本文提供的 Google Colab 笔记本中按照此示例操作。
首先,我们将加载 PDF 文档并使用 LangChain 的 PyPDF 连接器对其进行解析。
从文档中获取文本后,我们需要将其拆分成更小的块。我们可以使用 LangChain 提供的拆分器,例如本例中的 RecursiveCharacterSplitter:
我们将使用开源嵌入模型 BGE-small。我们将从 HuggingFace Hub 下载它,并在所有块上运行它来计算它们的向量表示。
一旦我们获得了所有块的向量表示,我们就可以创建一个内存向量数据库,并将所有向量存储在其中。在本例中,我们将使用 FAISS 数据库。
数据库现已设置完毕。现在,我们将接收用户关于此信息的查询。在本例中,用户询问朴素 RAG 的缺点是什么。我们使用与之前相同的嵌入模型对此查询进行编码。然后,我们检索与该查询最相似的前 5 个块。
检索到相关上下文后,我们会根据这些信息和用户的原始查询构建一个提示。我们将使用 Claude 的俳句作为本例的 LLM 参考:
常见问题和陷阱
正如标题所示,此解决方案是一个基本的或简单的 RAG 实现。它将使您的应用程序能够充分利用其使用的 LLM 和您的数据。但它并非适用于所有情况。以下只是 RAG 的一些最常见问题:
- 检索不相关的信息。如果检索器从向量数据库中获取与问题不相关的数据,它会干扰试图回答问题的模型。这可能导致模型无法利用上下文来回答问题,或者给出与问题内容不同的答案。
- 错过重要信息。也许回答问题所需的信息不在数据库中。也许检索机制无法找到相关的信息块。我们必须找到方法,帮助检索器更轻松、更可靠地找到所需信息。
- 生成上下文不支持的响应。如果上下文包含模型所需的信息,但模型不使用这些信息,而是依赖于自身的预训练数据,那么这一切都是徒劳的。预训练数据中的信息可能已过时或有误。我们必须让模型始终使用上下文来回答问题,或者如果它无法根据上下文回答问题,就回答“我不知道”。
- 与查询无关的响应。LLM可能会使用你提供的所有信息来生成响应,但这并不意味着它回答了用户的问题。重要的是,模型要紧扣用户的原始问题,而不是迷失在海量信息中。
- 相似上下文导致的冗余响应。当我们提取包含相似信息的多个文档时,检索器可能会得到多个几乎相同的信息块。这可能会导致 LLM 在其响应中多次重复相同的信息。
如何避免这些问题?
为了避免这些问题,单纯的 RAG 流水线可能不够。我们需要建立一个更先进、更复杂的 RAG 系统。目前已经存在一些经过测试的技术来解决我们提出的问题。我们可以将这些技术融入到我们的 RAG 流水线中,以提升 RAG 应用程序的性能。
另一个需要强调的要点是,要改进你的 RAG 应用程序,你需要能够衡量和评估整个过程。你无法改进无法衡量的东西。此外,当你评估时,你可能会发现基本的 RAG 设置足以满足你的用例,你无需将其过于复杂化。毕竟,即使是非常基本的 RAG 实现也能极大地改进你的 LLM 应用程序。
在未来的文章中,我将更详细地解释先进的 RAG 技术,这些技术将帮助我们避免常见问题并将我们的 RAG 应用程序提升到一个新的水平。
文章来源:https://dev.to/rogiia/how-to-build-a-basic-rag-app-h9p