以编程方式使用文档

概述

在本教程中,我们将构建一个 检索 智能体。

LangChain 提供内置 智能体 实现,采用 LangGraph 原语构建。如果需要更深入的定制,可以直接在 LangGraph 中实现智能体。本指南演示了检索智能体的示例实现。 检索 智能体在您希望 LLM 决定是从向量存储中检索上下文还是直接回复用户时很有用。

在本教程结束时,我们将完成以下操作:

  1. 获取并预处理将用于检索的文档。
  2. 为语义搜索索引这些文档,并为智能体创建检索器工具。
  3. 构建一个可以决定何时使用检索器工具的智能体 RAG 系统。

!混合 RAG

概念

我们将涵盖以下概念:

设置

让我们下载所需的包并设置 API 密钥:

pip install -U langgraph langchain-anthropic langchain-text-splitters bs4 requests

1. 预处理文档

1. 获取要在 RAG 系统中使用的文档。我们将使用 Lilian Weng 优秀的博客中最近的三篇文章。我们将首先使用基于 requests 构建的最小辅助函数获取页面的内容 BeautifulSoup. 2. 将获取的文档分割成更小的块,以便索引到向量存储中:

2. 创建检索器工具

现在我们已经有了分割好的文档,可以将它们索引到用于语义搜索的向量存储中。

1. 使用内存向量存储和 OpenAI 嵌入: 2. 使用以下方式创建检索工具 @tool decorator: 3. 测试该工具:

  retriever_tool.invoke({"query": "types of reward hacking"})
  

3. 生成查询

现在我们开始构建组件(节点)用于我们的代理式 RAG 图。

请注意,这些组件将对 MessagesState——图状态进行操作,该状态包含一个 messages 键,其值为 聊天消息.

1. 构建一个 generate_query_or_respond 节点。它将调用 LLM 根据当前图状态(消息列表)生成响应。根据输入消息,它将决定使用检索工具进行检索,或直接响应用户。请注意,我们通过 retriever_tool 向聊天模型提供我们之前创建的 .bind_tools: 2. 用随机输入测试:

  input = {"messages": [{"role": "user", "content": "hello!"}]}
  generate_query_or_respond(input)["messages"][-1].pretty_print()
  

Output:

  ================================== Ai Message ==================================

  Hello! How can I help you today?
  

3. 提出一个需要语义搜索的问题:

  input = {
      "messages": [
          {
              "role": "user",
              "content": "What does Lilian Weng say about types of reward hacking?",
          }
      ]
  }
  generate_query_or_respond(input)["messages"][-1].pretty_print()
  

Output:

  ================================== Ai Message ==================================
  Tool Calls:
  retrieve_blog_posts (call_tYQxgfIlnQUDMdtAhdbXNwIM)
  Call ID: call_tYQxgfIlnQUDMdtAhdbXNwIM
  Args:
      query: types of reward hacking
  

4. 给文档评分

1. 添加一个 条件边grade_documents——用于确定检索到的文档是否与问题相关。我们将使用具有结构化输出模式的模型 GradeDocuments 进行文档评分。该 grade_documents 函数将根据评分决定返回要前往的节点名称(generate_answer or rewrite_question): 2. 使用工具响应中无关的文档运行此操作:

  from langchain_core.messages import convert_to_messages

  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {"role": "tool", "content": "meow", "tool_call_id": "1"},
          ]
      )
  }
  grade_documents(input)
  

3. 确认相关文档被正确分类:

  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {
                  "role": "tool",
                  "content": "reward hacking can be categorized into two types: environment or goal misspecification, and reward tampering",
                  "tool_call_id": "1",
              },
          ]
      )
  }
  grade_documents(input)
  

5. 重写问题

1. 构建 rewrite_question 节点。检索工具可能返回潜在不相关的文档,这表明需要改进原始用户问题。为此,我们将调用 rewrite_question node: 2. 试试看:

  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {"role": "tool", "content": "meow", "tool_call_id": "1"},
          ]
      )
  }

  response = rewrite_question(input)
  print(response["messages"][-1].content)
  

Output:

  What are the different types of reward hacking described by Lilian Weng, and how does she explain them?
  

6. 生成答案

1. 构建 generate_answer 节点:如果我们通过了评估器检查,我们可以根据原始问题和检索到的上下文生成最终答案: 2. 试一试:

  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {
                  "role": "tool",
                  "content": "reward hacking can be categorized into two types: environment or goal misspecification, and reward tampering",
                  "tool_call_id": "1",
              },
          ]
      )
  }

  response = generate_answer(input)
  response["messages"][-1].pretty_print()
  

Output:

  ================================== Ai Message ==================================

  Lilian Weng categorizes reward hacking into two types: environment or goal misspecification, and reward tampering. She considers reward hacking as a broad concept that includes both of these categories. Reward hacking occurs when an agent exploits flaws or ambiguities in the reward function to achieve high rewards without performing the intended behaviors.
  

7. 组装图

现在我们将把所有节点和边组装成一个完整的图:

  • * 从一个 generate_query_or_respond 开始,确定是否需要调用 retriever_tool
  • * 根据模型是否调用了工具来路由到下一步:
  • * If generate_query_or_respond 返回 tool_calls,则调用 retriever_tool 来检索上下文
  • * 否则,直接响应用户
  • * 评估检索到的文档内容与问题的相关性(grade_documents)并路由到下一步:
  • * 如果不相关,使用 rewrite_question 重写问题,然后调用 generate_query_or_respond 再次
  • * 如果相关,继续进行到 generate_answer 并使用 ToolMessage 结合检索到的文档上下文生成最终响应

可视化图:

SQL agent graph

8. 运行智能 RAG

现在让我们通过一个问题来测试完整的图: