概述
LLMs(大型语言模型)带来的最强大的应用之一是复杂的问答(Q&A)聊天机器人。这些应用能够回答关于特定信息源的问题。这些应用使用一种称为检索增强生成(RAG)的技术,或称RAG。 本教程将展示如何在一个非结构化文本数据源上构建一个简单的问答应用。我们将演示:概念
我们将介绍以下概念:- 索引:从源数据中摄取数据并对其进行索引的管道。这通常在单独的进程中发生。
- 检索与生成:实际的RAG过程,在运行时获取用户查询,从索引中检索相关数据,然后将这些数据传递给模型。
预览
在本指南中,我们将构建一个应用,用于回答有关网站内容的疑问。我们将使用的特定网站是Lilian Weng撰写的LLM驱动的自主智能体博客文章,该文章允许我们就文章内容提出问题。 我们可以创建一个简单的索引管道和RAG链,用大约40行代码就能完成这个任务。下面是完整的代码片段:Expand for full code snippet
Expand for full code snippet
安装
安装
本教程需要以下 LangChain 依赖项:LangSmith
许多您使用LangChain构建的应用程序将包含多个步骤和多次调用LLM。随着这些应用程序变得更加复杂,能够检查您的链或智能体内部究竟发生了什么变得至关重要。最佳方式是使用LangSmith。 在您通过上述链接注册后,请确保设置环境变量以开始记录跟踪信息:组件
我们需要从LangChain的集成套件中选择三个组件。 选择聊天模型:- OpenAI
- Anthropic
- Azure
- Google Gemini
- AWS Bedrock
- OpenAI
- Azure
- Google Gemini
- Google Vertex
- AWS
- HuggingFace
- Ollama
- Cohere
- MistralAI
- Nomic
- NVIDIA
- Voyage AI
- IBM watsonx
- Fake
- In-memory
- AstraDB
- Chroma
- FAISS
- Milvus
- MongoDB
- PGVector
- PGVectorStore
- Pinecone
- Qdrant
1. 索引
索引通常按以下方式工作:- 加载:首先,我们需要加载我们的数据。这通过文档加载器来完成。
- 分割:文本分割器将大的
Documents分割成更小的块。这对于索引数据和将其传递给模型都很有用,因为大块更难搜索,而且不会适合模型有限的上下文窗口。 - 存储:我们需要一个地方来存储和索引我们的分割数据,以便以后可以搜索。这通常使用向量存储和嵌入模型来完成。
加载文档
我们需要首先加载博客文章内容。我们可以使用DocumentLoaders来完成这项任务,这些对象从源加载数据并返回一个Document对象的列表。 在这种情况下,我们将使用 WebBaseLoader,它使用urllib 从网络 URL 加载 HTML,并使用 BeautifulSoup 将其解析为文本。我们可以通过将参数传递给 BeautifulSoup 解析器通过 bs_kwargs 来自定义 HTML -> 文本解析(参见 BeautifulSoup 文档)。在这种情况下,只有具有类名“post-content”、“post-title”或“post-header”的 HTML 标签是相关的,因此我们将删除所有其他标签。
DocumentLoader:从源加载数据为Documents列表的对象
分割文档
我们的加载文档超过42k个字符,这对于许多模型的上下文窗口来说太长了,无法全部容纳。即使是那些能够将整个帖子放入其上下文窗口的模型,在处理非常长的输入时也可能难以找到信息。 为了处理这个问题,我们将Document分割成块以进行嵌入和向量存储。这应该有助于我们在运行时只检索博客文章中最相关的部分。
如语义搜索教程中所述,我们使用一个RecursiveCharacterTextSplitter,它将递归地使用常见的分隔符(如换行符)将文档分割,直到每个块达到适当的大小。这是通用文本用例中推荐的文本分割器。
TextSplitter:将Document对象列表分割成更小的块以进行存储和检索的对象。
存储文档
现在我们需要对66个文本块进行索引,以便在运行时进行搜索。遵循语义搜索教程,我们的方法是嵌入每个文档分割的内容,并将这些嵌入插入到向量存储中。给定一个输入查询,然后我们可以使用向量搜索来检索相关文档。 我们可以使用在教程开始时选择的向量存储和嵌入模型,通过单个命令嵌入和存储我们所有的文档拆分。Embeddings:围绕文本嵌入模型的外包装,用于将文本转换为嵌入。
VectorStore:围绕向量数据库的包装器,用于存储和查询嵌入。
这完成了管道的索引部分。在此阶段,我们拥有一个可查询的向量存储,其中包含我们博客文章的块状内容。给定一个用户问题,我们理想上应该能够返回回答问题的博客文章片段。
2. 检索与生成
RAG应用程序通常按以下方式工作:RAG智能体
一种RAG应用的表述方式是作为一个简单的智能体,它带有检索信息的工具。我们可以通过实现一个工具来封装我们的向量存储,从而组装一个最小的RAG智能体。- 生成一个查询以搜索任务分解的标准方法;
- 收到答案后,生成第二个查询以搜索其常见扩展;
- 收到所有必要上下文后,回答问题。
RAG 链
在上面的智能体RAG公式中,我们允许LLM在生成工具调用以帮助回答用户查询时行使自主权。这是一个很好的通用解决方案,但也有一些权衡:| ✅ 优点 | ⚠️ 缺点 |
|---|---|
| 仅在需要时进行搜索 – LLM 可以处理问候语、后续问题和简单查询,而不会触发不必要的搜索。 | 两次推理调用 – 当执行搜索时,需要一次调用生成查询,另一次调用生成最终响应。 |
上下文搜索查询 – 将搜索视为具有 query 输入的工具,LLM 将构建自己的查询,这些查询结合了对话上下文。 | 控制减少 – LLM 可能会跳过实际需要的搜索,或者在不需要时发起额外的搜索。 |
| 允许多次搜索 – LLM 可以执行多个搜索以支持单个用户查询。 |
Returning source documents
Returning source documents
下一步
现在我们已经通过create_agent 实现了一个简单的 RAG 应用程序,我们可以轻松地集成新功能和深入探索:
- 流令牌和其他信息以实现响应式用户体验
- 添加会话记忆以支持多轮交互
- 添加长期记忆以支持跨对话线程的记忆
- 添加结构化响应
- 使用LangSmith 部署部署您的应用程序