最强大的基于大语言模型(LLM)的应用之一是复杂的问答(Q&A)聊天机器人,它通过在推理时提供对一组数据的访问来增强大语言模型。 这些数据可能是私有数据、最新数据,或者不属于大语言模型训练数据的一部分。 这些应用使用一种称为检索增强生成(Retrieval Augmented Generation)的技术,或简称 RAG。 Deep Agents 为您提供了实现 RAG 的基础构件:自定义检索工具、文件系统后端、子代理、技能和评分标准。您可以根据语料库大小、延迟要求以及答案必须严格基于源数据的程度,以不同的方式组合它们。 本指南介绍了几种 RAG 模式,并逐步讲解一个端到端示例:一个文档问答代理,它索引 docs.langchain.com 的一个子集,在查询时检索相关数据块,将其卸载到文件系统,并将分析委托给子代理,从而保持编排者上下文的清晰。

RAG 模式

Deep Agents 允许您以多种方式协调检索、分析和综合过程:
  • 技能引导检索:用户提出问题。代理加载一个描述如何搜索您语料库的技能(使用哪个索引、查询构建方式、引用格式)。代理遵循该指导调用您的检索工具,然后综合生成答案。
  • 评分标准检查的基于事实:用户提出问题。代理检索证据并草拟一个答案。一个配置了 RubricMiddleware 的评分子代理评估回复是否基于检索到的源材料。代理进行修订,直到评分标准通过或达到迭代上限。
  • 待办事项驱动的调查:用户提出问题。如果您选择启用任务规划,代理会使用规划工具创建一个待办事项列表,列出需要调查的文档页面或搜索查询。它为每个项目检索结果,然后根据收集的证据综合生成回复。
  • 检索、卸载与委托:用户提出问题。代理检索匹配的数据块并将其写入文件系统后端,而不是将完整文本保留在编排者上下文中。子代理并行读取、搜索和总结单个文件。对于大型文档,代理可以使用内置搜索工具对文件进行分页浏览,或运行代码解释器从源数据生成表格、时间线或可视化内容。
评分标准需要 deepagents>=0.6.5,并且目前处于测试版。
本教程实现了检索、卸载与委托模式。同样的基础构件也出现在其他模式中:技能通常封装检索工作流,评分标准可以对任何这些流程进行打分,而可选的待办事项规划有助于将复杂问题分解为专注的搜索。

为什么检索很重要

单独的语言模型无法访问您的文档。询问它一个最近更改过的特定 API,它会根据训练数据回答:通常是合理的,有时是错误的,并且绝不会基于您的事实来源。 即使文档可用,您通常也无法将所有内容都放入上下文窗口。因此,您必须只选择与给定问题相关的段落,这本身就是一个非同小可的任务。 本教程贯穿使用一个问题:
如何从子代理流式传输中间工具结果?
将这个问题传递给一个没有自定义工具且无法访问文档语料库的 Deep Agent,看看模型会给出什么答案:
没有检索,代理无法查找当前的 LangChain 文档。回复往往是通用的,可能遗漏像子代理流式传输这样的指导,或者包含过时的信息。 本教程中的示例索引 LangChain 文档,使用向量搜索工具检索证据,在并行子代理中分析每个数据块,并用引用文档的方式回答一个问题。

您将构建什么

  1. 索引:将 LangChain 文档加载到向量存储中。
  2. 搜索:构建一个自定义工具,运行向量相似性搜索并将每个检索到的数据块写入代理文件系统。
  3. 分析:将文件分析委托给一个读取文件并返回重点摘要的子代理。
  4. 综合:使用主代理根据子代理报告得出最终答案。

前提条件

需要以下 API 密钥:

设置

1

创建项目目录

2

安装依赖项

3

设置 API 密钥

对于任何其他提供商,请参阅相应的聊天模型文档。
4

设置 LangSmith

RAG 应用按顺序运行检索和生成。当您运行本教程中的示例时,LangSmith 会为每个查询记录一条跟踪,以便您可以检查检索、工具调用和模型响应。 注册 LangSmith 后,设置您的环境变量以开始记录跟踪:
或者,在 Python 中设置它们:
如果您正在构建生产级代理,我们还建议您设置 LangSmith Engine,它可以监控您的跟踪、检测问题并提出修复建议。

索引 LangChain 文档

在索引步骤中,您将获取源内容并将其_数据块_转换为数值表示。这种数值表示捕获了数据块的语义含义。在 VectorStore 中存储这些数值表示和文档数据块的映射,使您能够根据查询自身的数值表示,在用户发送查询时高效检索相关内容。 索引通常分四个步骤进行:
  1. 加载:将您的数据源加载到 Document 对象中。
  2. 分割:使用文本分割器将大型 Document 分割成较小的数据块。这对于索引数据和将其传递给模型都很有用,因为大型数据块更难搜索,并且要么无法放入模型有限的上下文窗口,要么会消耗比必要的更多的 token。
  3. 嵌入:嵌入模型将每个数据块转换为一个捕获其含义的数值向量,从而实现对您内容的相似性搜索。
  4. 存储:使用 VectorStore 为检索索引数据块及其嵌入。
索引图表 在索引步骤中,获取文档页面,将其分割成数据块,嵌入数据块,并将它们存储在 VectorStore 中。代理在运行时搜索此索引;它不会在每个问题上重新获取整个网站。 LangChain 在 https://docs.langchain.com/{path}.md 发布 markdown 内容。本教程索引一个精选的开源文档路径列表。您可以扩展 DOC_PATHS 或从 llms.txt 解析 URL 以覆盖更多页面。 创建 agent.py:
有关索引、向量存储和检索的更详细教程,请参阅语义搜索。

加载文档

首先将 LangChain 文档页面加载到 Document 对象列表中。 使用 requests 从 https://docs.langchain.com/{path}.md 获取每个页面的 markdown 内容。精选的 DOC_PATHS 列表选择要索引的页面。
如果您运行此代码,它会输出:
您也可以查看页面内容本身:

分割文档

加载的文档很长,总计超过 10 万个 token,这使得它太大而无法放入许多模型的上下文窗口中。 即使对于那些可以将整个语料库放入上下文窗口的模型来说,模型也很难在非常长的输入中找到信息。将上下文窗口用于大量内容也并非高效使用 token。 为了便于使用,将 Document 对象分割成数据块。这些数据块将在下一步用于嵌入和向量存储。 使用 RecursiveCharacterTextSplitter 递归地使用常见分隔符(如换行符)分割文档,直到每个数据块达到适当的大小。 RecursiveCharacterTextSplitter 是通用文本用例推荐的 TextSplitter。
如果您想了解更多关于文本分割器的信息,请查看 TextSplitter 接口和文本分割器集成。

选择嵌入模型

嵌入是一个捕获每个文档数据块含义的数值向量。Embeddings 模型将这些数据块转换为向量,使得相似的含义在向量空间中彼此靠近,从而使您能够在用户提问时检索相关部分。 您可以从许多不同的嵌入集成中选择,它们都使用相同的 Interface:

在 VectorStore 中存储数据块和嵌入

VectorStore 持久化文档数据块及其嵌入,使相似性搜索能够在用户提问时检索相关部分。 您可以从许多不同的向量存储集成中选择,它们都使用相同的 Interface。 使用您在上一步中选择的嵌入模型来配置您的 VectorStore:
然后,使用您上面初始化的 vector_store 嵌入并存储所有文档分割:
运行时,会输出:
在本教程中,索引在启动时运行一次。在生产环境中,将向量存储持久化到磁盘或托管的向量数据库,并在文档更改时按计划刷新它。
这就完成了教程的索引部分。您现在拥有一个可查询的向量存储,其中包含分块的 LangChain 文档。 下一步是构建一个 Deep Agent,它在运行时搜索此索引,将检索到的数据块卸载到文件系统,并将分析委托给子代理。请参阅构建代理。从 RAG 的角度来考虑:
  1. 检索:给定用户输入,使用 Retriever 从存储中检索相关的分割。
  2. 生成:一个模型使用包含问题和检索到的数据的提示词来生成答案。
检索与生成图表

构建代理

将此代码添加到 agent.py:
1

添加搜索工具

search_documentation 工具对索引的语料库运行相似性搜索,然后将每个检索到的数据块写入代理文件系统的 /retrieved/{batch_id}/ 目录下。它返回文件路径,以便编排者可以委托分析,而无需将完整的数据块文本加载到其上下文中。该工具使用 backend.upload_files() 将检索到的数据块写入代理后端。将相同的后端实例传递给 create_deep_agent,以便内置的文件系统工具(如 read_file 和 grep)可以读取保存的路径。
2

添加提示词

将编排者工作流和子代理提示词模板添加到 agent.py:
3

创建代理

将模型初始化和代理创建添加到 agent.py:
主代理保留 search_documentation 工具。chunk-analyst 子代理使用内置的文件系统工具读取数据块文件,但不直接搜索向量存储。

运行代理

使用示例查询运行 RAG 代理:
当代理运行时,它会:
  1. 使用关于子代理流式传输的查询调用 search_documentation。
  2. 接收文件路径,如 /retrieved/a1b2c3d4/chunk_1.md。
  3. 启动一个或多个指向 chunk-analyst 的 task() 调用,每个调用限定在单个数据块文件范围。
  4. 综合生成一个带有相关文档页面链接的最终答案。
如果您在设置中启用了 LangSmith,请打开 LangSmith 并检查跟踪以查看搜索调用、文件系统写入、子代理委托和最终响应。

安全注意事项

RAG 应用容易受到间接提示词注入的影响。检索到的文档可能包含类似指令的文本。因为检索到的数据块与您的系统提示词共享上下文窗口,模型可能会遵循文档中嵌入的指令,而不是您预期的提示词。
没有提示词或分隔符策略能完全防止间接提示词注入。本教程中的编排者和子代理提示词要求模型仅将检索到的内容视为数据,搜索工具在数据块前添加 # Source: 头部以便分析师区分元数据和正文内容。这些模式在某些情况下可能有所帮助,但并不能提供可靠的保护。 在将代理输出呈现给用户之前,请对其进行验证。检查答案是否引用了预期的文档路径,以及声明是否与检索到的源材料匹配。 有关此主题的更多信息,请参阅关于提示词注入的研究。

完整代码

以下是代理的完整脚本: 保存为 agent.py 并使用 python agent.py 运行:

后续步骤

您使用 create_deep_agent 实现了一种 RAG 模式。将其与其他 Deep Agents 功能结合,或尝试 RAG 模式中的其他模式: