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

加载文档
我们需要首先加载博客文章内容。我们可以使用文档加载器来完成此操作,它们是从来源加载数据并返回 Document 对象列表的对象。 在本例中,我们将使用WebBaseLoader,它使用 urllib 从 Web URL 加载 HTML,并使用 BeautifulSoup 将其解析为文本。我们可以通过 bs_kwargs 传递参数给 BeautifulSoup 解析器来自定义 HTML -> 文本解析(参见 BeautifulSoup 文档)。在本例中,只有 class 为 “post-content”、“post-title” 或 “post-header” 的 HTML 标签是相关的,因此我们将移除所有其他标签。
分割文档
我们加载的文档超过 42k 个字符,这对于许多模型的上下文窗口来说太长了。即使对于那些可以将完整文章放入其上下文窗口的模型,模型在很长的输入中也很难找到信息。 为了处理这个问题,我们将把Document 分割成块以进行向量嵌入和向量存储。这应该有助于我们在运行时仅检索博客文章中最相关的部分。
与语义搜索教程一样,我们使用 RecursiveCharacterTextSplitter,它会使用常见的分隔符(如换行符)递归分割文档,直到每个块的大小合适。这是通用文本用例的推荐文本分割器。
存储文档
现在我们需要索引我们的 66 个文本块,以便在运行时可以搜索它们。按照语义搜索教程,我们的方法是嵌入每个文档分割的内容并将这些向量嵌入插入到向量存储中。给定一个输入查询,我们可以使用向量搜索检索相关文档。 我们可以使用在教程开始时选择的向量存储和向量嵌入模型,通过一个命令嵌入和存储所有文档分割。2. 检索和生成
RAG 应用程序通常按以下方式工作:
RAG 智能体
RAG 应用程序的一种形式是作为带有检索信息工具的简单智能体。我们可以通过实现一个包装向量存储的工具来组装一个最小的 RAG 智能体:- 生成查询以搜索任务分解的标准方法;
- 收到答案后,生成第二个查询以搜索该方法的常见扩展;
- 收到所有必要的上下文后,回答问题。
RAG 链
在上面的智能体式 RAG 中,我们允许 LLM 自行决定是否生成工具调用来帮助回答用户查询。这是一个好的通用解决方案,但有一些权衡:
另一种常见方法是两步链,其中我们始终运行搜索(可能使用原始用户查询)并将结果作为上下文纳入单个 LLM 查询。这导致每次查询只有一次推理调用,以灵活性为代价换取更低的延迟。
在这种方法中,我们不再循环调用模型,而是进行单次传递。
我们可以通过从智能体中移除工具并将检索步骤合并到自定义提示词中来实现此链:
安全性:间接提示词注入
缓解措施:- 使用防御性提示词:明确指示模型将检索到的上下文仅视为数据,忽略其中的任何指令。本教程中的提示词包含了此类指令。
- 使用分隔符包装上下文:使用清晰的结构标记(例如 XML 标签如
<context>...</context>)来分隔检索到的数据和指令,使模型更容易区分它们。 - 验证响应:检查模型的输出是否符合预期格式(例如纯文本)并优雅地处理意外格式。
后续步骤
现在我们已经通过create_agent 实现了一个简单的 RAG 应用程序,我们可以轻松地引入新功能并深入探索:
- 流式输出 Token 和其他信息以获得响应式用户体验
- 添加对话记忆以支持多轮交互
- 添加长期记忆以支持跨对话线程的记忆
- 添加结构化响应
- 使用 LangSmith 部署部署你的应用程序
连接这些文档到 Claude、VSCode 等工具,通过 MCP 获取实时答案。

