概述
由大语言模型(LLMs)实现的最强大应用之一是复杂的问答(Q&A)聊天机器人。这些应用能够回答关于特定源信息的问题。它们使用一种称为检索增强生成(Retrieval Augmented Generation)或 RAG 的技术。 本教程将展示如何基于非结构化文本数据源构建一个简单的问答应用。我们将演示:概念
我们将涵盖以下概念:- 索引:一个从源摄取数据并为其建立索引的流程。这通常在一个单独的进程中发生。
- 检索与生成:实际的 RAG 过程,它在运行时接收用户查询,从索引中检索相关数据,然后将其传递给模型。
预览
在本指南中,我们将构建一个回答关于网站内容问题的应用。我们将使用的具体网站是 Lilian Weng 的 LLM Powered Autonomous Agents 博客文章,它允许我们就该文章的内容提问。 我们可以用大约 40 行代码创建一个简单的索引流程和 RAG 链来实现这一点。完整代码片段如下:展开查看完整代码片段
展开查看完整代码片段
设置
安装
本教程需要以下 langchain 依赖项:LangSmith
你使用 LangChain 构建的许多应用将包含多个步骤和多次 LLM 调用。随着这些应用变得越来越复杂,能够检查你的链或智能体内部究竟发生了什么变得至关重要。最好的方法是使用 LangSmith。 在以上链接注册后,请确保设置你的环境变量以开始记录追踪记录:组件
我们将需要从 LangChain 的集成套件中选择三个组件。 选择一个聊天模型:- OpenAI
- Anthropic
- Azure
- Google Gemini
- Bedrock Converse
- OpenAI
- Azure
- AWS
- VertexAI
- MistralAI
- Cohere
- Memory
- Chroma
- FAISS
- MongoDB
- PGVector
- Pinecone
- Qdrant
1. 索引
索引通常按以下方式工作:- 加载:首先我们需要加载数据。这通过文档加载器完成。
- 分割:文本分割器将大的
Document对象分解成更小的块。这对索引数据和将其传递给模型都很有用,因为大块内容更难搜索,并且可能无法放入模型的有限上下文窗口中。 - 存储:我们需要一个地方来存储和索引我们的分割块,以便以后可以搜索它们。这通常使用向量存储和嵌入模型完成。

加载文档
我们首先需要加载博客文章的内容。我们可以使用文档加载器来完成这个任务,这些对象从源加载数据并返回一个 Document 对象列表。DocumentLoader:从源加载数据作为 Document 列表的对象。
- 集成:160+ 个集成可供选择。
BaseLoader:基础接口的 API 参考。
分割文档
我们加载的文档超过 42k 个字符,这对于许多模型的上下文窗口来说太长了。即使对于那些可以将完整文章放入其上下文窗口的模型,模型也可能难以在很长的输入中找到信息。 为了处理这个问题,我们将把Document 分割成块以便进行嵌入和向量存储。这应该有助于我们在运行时仅检索博客文章中最相关的部分。
与语义搜索教程中一样,我们使用 RecursiveCharacterTextSplitter,它将使用常见分隔符(如换行符)递归地分割文档,直到每个块达到适当的大小。这是通用文本用例推荐的文本分割器。
存储文档
现在我们需要索引我们的 66 个文本块,以便我们可以在运行时搜索它们。遵循语义搜索教程,我们的方法是嵌入每个文档分割块的内容,并将这些嵌入插入到向量存储中。给定一个输入查询,我们可以使用向量搜索来检索相关文档。 我们可以使用在本教程开始部分选择的向量存储和嵌入模型,通过一个命令来嵌入和存储所有的文档分割块。Embeddings:文本嵌入模型的包装器,用于将文本转换为嵌入。
VectorStore:向量数据库的包装器,用于存储和查询嵌入。
这完成了流程的索引部分。此时,我们有一个可查询的向量存储,其中包含我们博客文章的块内容。给定一个用户问题,我们理想情况下应该能够返回回答该问题的博客文章片段。
2. 检索与生成
RAG 应用通常按以下方式工作:
RAG 智能体
RAG 应用的一种形式是作为一个带有检索信息工具的简单智能体。我们可以通过实现一个包装我们向量存储的工具来组装一个最小的 RAG 智能体:- 生成一个查询来搜索任务分解的标准方法;
- 收到答案后,生成第二个查询来搜索其常见扩展;
- 收到所有必要的上下文后,回答问题。
RAG 链
在上面的基于智能体的 RAG 表述中,我们允许 LLM 自行决定是否生成工具调用来帮助回答用户查询。这是一个很好的通用解决方案,但也有一些权衡:| ✅ 优点 | ⚠️ 缺点 |
|---|---|
| 仅在需要时搜索 – LLM 可以处理问候语、后续问题和简单查询,而不会触发不必要的搜索。 | 两次推理调用 – 当执行搜索时,需要一次调用来生成查询,另一次调用来产生最终响应。 |
上下文相关的搜索查询 – 通过将搜索视为带有 query 输入的工具,LLM 可以构建自己的查询,融入对话上下文。 | 控制力减弱 – LLM 可能在确实需要搜索时跳过搜索,或者在不需要时进行额外搜索。 |
| 允许多次搜索 – LLM 可以为支持单个用户查询而执行多次搜索。 |
返回源文档
返回源文档
上面的 RAG 链 将检索到的上下文合并到该次运行的单个系统消息中。与基于智能体的 RAG 表述一样,我们有时希望将原始源文档包含在应用程序状态中,以便访问文档元数据。对于两步链的情况,我们可以通过以下方式实现:
- 向状态添加一个键来存储检索到的文档
- 通过一个模型前钩子添加一个新节点来填充该键(并注入上下文)。
后续步骤
现在我们已经通过 @[create_agent] 实现了一个简单的 RAG 应用,我们可以轻松地加入新功能并进行更深入的探索:
- 流式传输令牌和其他信息以实现响应式用户体验
- 添加对话记忆以支持多轮交互
- 添加长期记忆以支持跨对话线程的记忆
- 添加结构化响应
- 使用 LangSmith Deployments 部署你的应用