概览
LLM 最强大的应用之一是复杂的问答 (Q&A) 聊天机器人。这些应用程序可以回答关于特定来源信息的问题。这些应用程序使用一种称为检索增强生成 (Retrieval Augmented Generation),或 RAG 的技术。 本教程将展示如何构建一个基于非结构化文本数据源的简单问答应用程序。我们将演示:概念
我们将涵盖以下概念- 索引:一个用于从源摄取数据并对其进行索引的管道。这通常在单独的进程中进行。
- 检索与生成:实际的 RAG 过程,在运行时接收用户查询,从索引中检索相关数据,然后将其传递给模型。
预览
在本指南中,我们将构建一个应用程序来回答有关网站内容的问题。我们将使用的特定网站是 Lilian Weng 的 LLM Powered Autonomous Agents 博客文章,它允许我们提出关于文章内容的问题。 我们可以创建一个简单的索引管道和 RAG 链,在大约 40 行代码中完成此操作。完整代码片段请参见下文:展开查看完整代码片段
展开查看完整代码片段
设置
安装
本教程需要这些 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
- 内存中
- AstraDB
- Chroma
- FAISS
- Milvus
- MongoDB
- PGVector
- PGVectorStore
- Pinecone
- Qdrant
1. 索引
索引通常按以下方式工作- 加载:首先我们需要加载我们的数据。这通过 文档加载器 完成。
- 拆分:文本拆分器 将大型
Documents拆分为更小的块。这对于索引数据和将其传递到模型都很有用,因为大型块更难搜索,并且不适合模型的有限上下文窗口。 - 存储:我们需要一个地方来存储和索引我们的拆分,以便以后可以对其进行搜索。这通常使用 VectorStore 和 Embeddings 模型来完成。

加载文档
我们首先需要加载博客文章内容。我们可以为此使用 DocumentLoaders,它们是加载源数据并返回 Document 对象列表的对象。 在这种情况下,我们将使用WebBaseLoader,它使用 urllib 从网页 URL 加载 HTML,并使用 BeautifulSoup 将其解析为文本。我们可以通过 bs_kwargs 将参数传递给 BeautifulSoup 解析器来定制 HTML -> 文本解析(参见 BeautifulSoup 文档)。在这种情况下,只有带有“post-content”、“post-title”或“post-header”类的 HTML 标签是相关的,所以我们将删除所有其他标签。DocumentLoader:从源加载数据作为 Documents 列表的对象。- 集成:160+ 种集成可供选择。
BaseLoader:基础接口的 API 参考。
拆分文档
我们加载的文档有超过 4.2 万个字符,这对于许多模型的上下文窗口来说太长了。即使对于那些能够将完整文章放入其上下文窗口的模型,模型也可能难以在很长的输入中找到信息。 为了解决这个问题,我们将Document 拆分为多个块,用于嵌入和向量存储。这应该有助于我们在运行时只检索博客文章中最相关的部分。 与 语义搜索教程 中一样,我们使用 RecursiveCharacterTextSplitter,它将使用常见的分隔符(如换行符)递归地拆分文档,直到每个块达到适当的大小。这是通用文本用例推荐的文本拆分器。TextSplitter:将 Document 对象列表拆分为更小的块以进行存储和检索的对象。
存储文档
现在我们需要索引我们的 66 个文本块,以便我们可以在运行时对其进行搜索。遵循 语义搜索教程,我们的方法是 嵌入 每个文档拆分的内容,并将这些嵌入插入到 向量存储 中。给定输入查询,我们就可以使用向量搜索来检索相关文档。 我们可以使用在教程开头选择的向量存储和嵌入模型,在一个命令中嵌入和存储所有文档拆分。Embeddings:文本嵌入模型的包装器,用于将文本转换为嵌入。
VectorStore:向量数据库的包装器,用于存储和查询嵌入。
这完成了管道的 索引 部分。此时,我们有一个可查询的向量存储,其中包含我们博客文章的块化内容。给定用户问题,我们应该能够理想地返回回答该问题的博客文章片段。
2. 检索与生成
RAG 应用程序通常按以下方式工作
RAG 代理
RAG 应用程序的一种形式是作为一个简单的 代理,带有一个检索信息的工具。我们可以通过实现一个包装我们向量存储的 工具 来组装一个最小的 RAG 代理。在这里,我们使用 工具装饰器 来配置工具,将原始文档作为 工件 附加到每个 ToolMessage。这将使我们能够访问应用程序中的文档元数据,与发送给模型的字符串化表示分开。
检索工具不限于单个字符串
query 参数,如上例所示。您可以通过添加参数(例如,类别)来强制 LLM 指定额外的搜索参数- 生成一个查询以搜索任务分解的标准方法;
- 收到答案后,生成第二个查询以搜索其常见的扩展;
- 收到所有必要的上下文后,回答问题。
您可以使用 LangGraph 框架直接添加更深层次的控制和自定义——例如,您可以添加步骤来评估文档相关性并重写搜索查询。请查看 LangGraph 的 Agentic RAG 教程 以获取更高级的公式。
RAG 链
在上述 代理 RAG 公式中,我们允许 LLM 酌情生成 工具调用 来帮助回答用户查询。这是一个很好的通用解决方案,但也存在一些权衡:| ✅ 优点 | ⚠️ 缺点 |
|---|---|
| 仅在需要时搜索——LLM 可以处理问候语、后续问题和简单查询,而不会触发不必要的搜索。 | 两次推理调用——执行搜索时,需要一次调用来生成查询,另一次调用来生成最终响应。 |
上下文搜索查询——通过将搜索视为具有 query 输入的工具,LLM 会创建包含对话上下文的自己的查询。 | 控制减少——LLM 可能会在实际需要时跳过搜索,或者在不必要时发出额外搜索。 |
| 允许多次搜索——LLM 可以执行多次搜索以支持单个用户查询。 |
返回源文档
返回源文档
后续步骤
现在我们已经通过create_agent 实现了一个简单的 RAG 应用程序,我们可以轻松地添加新功能并深入探索
- 流式传输 令牌和其他信息,以提供响应式的用户体验
- 添加 对话记忆 以支持多轮交互
- 添加 长期记忆 以支持跨对话线程的记忆
- 添加 结构化响应
- 使用 LangSmith 部署 部署您的应用程序
以编程方式连接这些文档到 Claude、VSCode 等,通过 MCP 获取实时答案。