{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "30233352-e612-44ad-b42e-8f0df455428f",
   "metadata": {},
   "source": [
    "# <center>大模型 AI Agent 开发实战"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7644ad5b-c3fd-4a00-8c80-c225b4735246",
   "metadata": {
    "tags": []
   },
   "source": [
    "## <center>Ch.10 LangGraph 实现自治循环代理（ReAct）及事件流的使用"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "083e91c5-1b61-451b-90f9-ac71bf62da21",
   "metadata": {},
   "source": [
    "&emsp;&emsp;上节课介绍的 `Router Agent` 和 `Tool Calling Agent`，我们通过两个实际的案例证明了**随着任务需求的复杂性增加，代理架构中对中间流程的控制自由度也必须相应提高。**之所以要从 `Router Agent` 调整到 `Tool Calling Agent` 架构的原因在于，**当路由分支中的节点涉及多个工具时，需要`Agent`可以根据用户的实际输入灵活选择工具，这一需求是 `Router Agent` 无法满足的。然而，`Tool Calling Agent` 的局限性又在于：虽然它可以自主选择工具，但在其架构中，每次仅能执行一次函数调用（无论是单个外部函数还是多个外部函数）。**因此，当任务需要依次执行 A 工具、B 工具和 C 工具时，它无法支持这种自主控制的过程。因此，面对这种更复杂的需求，就需要引入了 `Full Autonomous`（自治循环代理）架构，即如下图所示："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e3f8aad-e069-481d-8236-f894546cec53",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241029145623453.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "429c955a-46fa-4cad-a62c-5c60c034d91f",
   "metadata": {},
   "source": [
    "&emsp;&emsp; `Full Autonmonous` 以两种主要的方式去扩展了`Agent`对工作流的控制，分别是：\n",
    "\n",
    "- 多步骤决策： `Agent`可以控制一系列决策，而不仅仅是一个决策。\n",
    "- 工具访问： `Agent`可以选择并使用多种工具来完成任务。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fb88227-87e0-48d3-8ce1-ca516cd2bd7e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;**满足上述两个条件，典型且通用的代理架构，就是基于`ReAct`思想而形成的代理模式**。在《Ch.3 ReAct Agent 基本理论与项目实战》课程中，我们重点讲解了`ReAct`的核心理念，并手动实现了基于该思想构建的自治循环代理流程。整体来看，其核心还是在于**为大模型配备足够丰富的外部工具，使用合适的提示词，引导大模型在接收到用户输入后，进入自主思考和循环执行的状态，以实现最终目标。** 当然，这是我们从非技术角度通俗的解释和理解自治循环代理的这个过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f303749a-6083-4624-9d75-f3cbf4b31518",
   "metadata": {},
   "source": [
    "# 1. LangGraph中ReAct的构建原理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a55cdd26-cf95-4237-995f-81b9eeddfb33",
   "metadata": {},
   "source": [
    "&emsp;&emsp;在 `LangGraph` 开发框架中有一些预构建的组件。上节课介绍的 `ToolNode` 是其中一个，它用于处理外部函数调用，其内部结合了 `LangGraph` 底层的图结构，能够接收 `JSON Schema` 形式的数据，执行工具函数并返回结果。除此之外，`LangGraph`的预构建组件中还包含了 `ReAct` 代理架构，该架构与我们在《Ch.3 ReAct Agent 基本理论与项目实战》中手动实现的思路和流程基本一致。不同之处在于，**在 `LangGraph` 框架中，`ReAct` 组件被改造成适配图结构的循环代理，其具体过程是：大模型可以在一个 `while` 循环中被重复调用。每一步，代理来自主决定调用哪些工具及其输入，然后执行这些工具，并将输出作为观察结果反馈给大模型。当代理判断不再需要调用更多工具时，`while` 循环便会终止，输出最终的结果。**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8171c380-e5d7-4ba3-91c9-2edd5037bfa4",
   "metadata": {},
   "source": [
    "&emsp;&emsp;**因此，我们需要理解的关键概念是：`LangGraph`预构建的`ReAct`组件，其实就是通过接入大模型，搭配着`Tool Calling Agent`，再结合`Router Agent` 共同构建起来的图，这个图以自治循环代理的架构形式提供服务。**其图结构如下图所示："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d317441-45d1-4658-b45c-d93fb4b944d4",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/20241029002.png\" width=80%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d57b011-2029-4ebf-a158-4ebc15c65ce5",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如上图所示的代理架构在 `LangGraph` 中的实现机制类似于 `LangChain` 中的 `ReAct Agent`，毕竟 `LangGraph` 的底层语言是 `LangChain` 的 `LCEL` 表达式语言。因此，该 `ReAct Agent` 架构是从 `LangChain` 已实现的 `ReAct Agent` 迁移而来，不同的是在`LangGraph`框架中适配的是图结构，而非`AgentExecuter`。其本质依然基于一种规划（Planning）的思想："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e569d5d3-5db5-4553-8481-a5820ab6c12f",
   "metadata": {},
   "source": [
    "> LangChain Agents Type： https://python.langchain.com/v0.1/docs/modules/agents/agent_types/\n",
    ">\n",
    "> Agent Planning：https://smith.langchain.com/hub/hwchase17/react?organizationId=33612d73-91c5-5140-b8a0-f3155ff5dc45"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88f5c294-d1e7-4d04-8c9f-3387c1d8bdda",
   "metadata": {},
   "source": [
    "```json\n",
    "Answer the following questions as best you can. You have access to the following tools:\n",
    "\n",
    "{tools}\n",
    "\n",
    "Use the following format:\n",
    "\n",
    "Question: the input question you must answer\n",
    "\n",
    "Thought: you should always think about what to do\n",
    "\n",
    "Action: the action to take, should be one of [{tool_names}]\n",
    "\n",
    "Action Input: the input to the action\n",
    "\n",
    "Observation: the result of the action\n",
    "\n",
    "... (this Thought/Action/Action Input/Observation can repeat N times)\n",
    "\n",
    "Thought: I now know the final answer\n",
    "\n",
    "Final Answer: the final answer to the original input question\n",
    "\n",
    "Begin!\n",
    "\n",
    "Question: {input}\n",
    "\n",
    "Thought:{agent_scratchpad}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73386feb-ff05-4093-b2fc-33981010ac34",
   "metadata": {},
   "source": [
    "&emsp;&emsp;这种代理实现的机制表明了，在`LangGraph`中实现的预构建`ReAct`代理结构，它支持：\n",
    "\n",
    "- **Tool calling ：允许大模型根据需要选择和使用各种工具。**\n",
    "- **Memory：使代理能够保留和使用先前步骤中的信息。**\n",
    "- **Planning ：授权大模型制定并遵循多步骤计划以实现目标。**\n",
    "\n",
    "\n",
    "&emsp;&emsp;而其在图结构中的具体构建的工作流如下图所示："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88178062-598d-4007-9468-5d732875e44d",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241028180303239.png\" width=80%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "780a7fcf-1dd4-4dca-8180-be1a03e06fcd",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如图所示，`Agent`节点使用消息列表的形式来调用大语言模型，`Messages Modifier`指的是在传递到大模型之前，修饰用户的原始输入内容，可以是`SystemMessage`（作为背景信息添加的消息列表的开头）、`Runnable`（可运行）等不同状态，如果生成的 `AIMessage` 包含`tool_calls`，则该图将调用 `tools` 。 `tools` 节点执行工具（每个 `tool_call` 1 个工具）并且将响应作为`ToolMessage`对象添加到消息列表。然后`Agent`节点再次调用大语言模型。重复该过程，直到响应中不再存在`tool_calls` ，最终由`Agent`节点将完整的消息列表作为包含键 `messages`的字典返回。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "619e2891-0879-43fa-8107-ba9d08a905f1",
   "metadata": {},
   "source": [
    "&emsp;&emsp;那么如何实现上述这个非常复杂的过程呢？非常简单，既然我们一直提到的是预构建组件，则说明整个过程已经由`LangGraph`内部封装好了，其提供给开发者使用的接口就是：`create_react_agent`方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a226434d-6e8d-4b4c-9fd4-f26a15f9ed93",
   "metadata": {},
   "source": [
    "> LangGraph create_react_agent：[点击查看源码参数介绍](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=create+react+agent#langgraph.prebuilt.chat_agent_executor.create_react_agent)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e4b20e5-8c38-4297-a4e3-8720e6b3b282",
   "metadata": {},
   "source": [
    "# 2. 案例实操：构建复杂工具应用的ReAct自治代理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8150470-93bc-4801-af47-0379fd4fa3d8",
   "metadata": {},
   "source": [
    "&emsp;&emsp;在这个案例中，我们将通过一个多工具场景需求来测试 `LangGraph` 中 `ReAct` 代理的构建方法和效果。我们设计了几个工具，以实现实时数据的查询和管理。首先，用户可以通过一个工具根据城市名称实时获取当前天气信息。接着，如果用户希望将查询到的天气数据保存到本地数据库中，可以使用另一个工具完成数据的插入操作。此外，我们还提供了一个工具，允许用户基于本地数据库中的天气数据进行提问数据进行提问。通过这些工具的组合，我们能够快速验证如何在复杂的应用场景中有效地整合不同功能，并实际的感知`LangGraph`框架下`ReAct`代理模式带来的开发便捷性和可扩展性。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4260e55-769d-42fc-bd5a-a850db6a7b9c",
   "metadata": {},
   "source": [
    "&emsp;&emsp;首先，我们设置一下`LangSmith`的配置，用于追踪和可视化`ReAct`的中间过程。这里新建了一个项目，命名为`langGraph_ReAct`，导入基础配置的代码如下图所示："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7b1fefa-62a7-484c-ae84-905165e8e1cd",
   "metadata": {},
   "source": [
    "> LangSmith：https://www.langchain.com/langsmith"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "e286aaec-10b8-411d-9f13-19137df2f06c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "true\n",
      "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\n",
      "langGraph_ReAct\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "# 设置环境变量\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_API_KEY\"] = \"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\"  # 需要更改为自己的 langsmith API_KEY\n",
    "os.environ[\"LANGCHAIN_PROJECT\"]=\"langGraph_ReAct\"          # 需要更改为自己实际创建的项目名称\n",
    "\n",
    "# 验证环境变量是否设置成功\n",
    "print(os.getenv(\"LANGCHAIN_TRACING_V2\"))\n",
    "print(os.getenv(\"LANGCHAIN_API_KEY\"))\n",
    "print(os.getenv(\"LANGCHAIN_PROJECT\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4ade83b-0251-4f90-8216-a95559add591",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241029162049660.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6133063-ecfd-479a-915f-27cde63c6f27",
   "metadata": {},
   "source": [
    "&emsp;&emsp;首先，我们接入实时天气数据查询的在线API，代码定义如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b07e8cb-c9b5-447d-b4b9-52b1776f8262",
   "metadata": {},
   "source": [
    "> OpenWeather API的注册与使用，请参考赠送课程的大模型技术实战部分 - 《Ch.10 借助Function calling调用外部工具API方法》"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "9a3aad1b-55ed-4343-913b-0c5ebd610272",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "\n",
    "def get_weather(loc):\n",
    "    \"\"\"\n",
    "    Function to query current weather.\n",
    "    :param loc: Required parameter, of type string, representing the specific city name for the weather query. \\\n",
    "    Note that for cities in China, the corresponding English city name should be used. For example, to query the weather for Beijing, \\\n",
    "    the loc parameter should be input as 'Beijing'.\n",
    "    :return: The result of the OpenWeather API query for current weather, with the specific URL request address being: https://api.openweathermap.org/data/2.5/weather. \\\n",
    "    The return type is a JSON-formatted object after parsing, represented as a string, containing all important weather information.\n",
    "    \"\"\"\n",
    "    # Step 1.构建请求\n",
    "    url = \"https://api.openweathermap.org/data/2.5/weather\"\n",
    "\n",
    "    # Step 2.设置查询参数\n",
    "    params = {\n",
    "        \"q\": loc,               \n",
    "        \"appid\": \"7b34ea15a881668d4255910e5899920c\",    # 输入API key\n",
    "        \"units\": \"metric\",            # 使用摄氏度而不是华氏度\n",
    "        \"lang\":\"zh_cn\"                # 输出语言为简体中文\n",
    "    }\n",
    "\n",
    "    # Step 3.发送GET请求\n",
    "    response = requests.get(url, params=params)\n",
    "    \n",
    "    # Step 4.解析响应\n",
    "    data = response.json()\n",
    "    return json.dumps(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc560293-1fdc-4b5d-a8ea-698e6ca7676c",
   "metadata": {},
   "source": [
    "&emsp;&emsp;测试一下`get_weather`函数的有效性，正常情况下可以得到输入城市名的实时天气信息。测试代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "40c98bf7-0ad7-4a47-a95a-6ab2d31332e3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"coord\": {\"lon\": 116.3972, \"lat\": 39.9075}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01d\"}], \"base\": \"stations\", \"main\": {\"temp\": 20.5, \"feels_like\": 19.28, \"temp_min\": 19.94, \"temp_max\": 20.5, \"pressure\": 1016, \"humidity\": 26, \"sea_level\": 1016, \"grnd_level\": 1010}, \"visibility\": 10000, \"wind\": {\"speed\": 1.95, \"deg\": 270, \"gust\": 5.15}, \"clouds\": {\"all\": 0}, \"dt\": 1730190567, \"sys\": {\"type\": 2, \"id\": 2000403, \"country\": \"CN\", \"sunrise\": 1730155186, \"sunset\": 1730193377}, \"timezone\": 28800, \"id\": 1816670, \"name\": \"Beijing\", \"cod\": 200}'"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_weather('beijing')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa0c16fd-1b76-4438-bb3d-538d5f0badbc",
   "metadata": {},
   "source": [
    "&emsp;&emsp;从返回的结果是`Json`数据类型，包含了非常丰富的实时天气数据，如天气条件、温度、湿度、风速、天气描述等信息，这里我们选择一些重要的数据参数进行存储操作（存储至Mysql数据库中）。提取的参数如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37014aeb-9378-4b31-a0b3-2bef9cca9a9d",
   "metadata": {},
   "source": [
    "| 字段名称      | 描述                       |\r\n",
    "|---------------|----------------------------|\r\n",
    "| city_id       | 城市的唯一标识符           |\r\n",
    "| city_name     | 城市名称                   |\r\n",
    "| main_weather  | 主要天气状况               |\r\n",
    "| description   | 天气的详细描述             |\r\n",
    "| temperature    | 当前温度                   |\r\n",
    "| feels_like    | 体感温度                   |\r\n",
    "| temp_min      | 最低温度                   |\r\n",
    "| temp_max      | 最高温度                  |\r\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca3110b1-2373-420e-b13e-e8d500caf497",
   "metadata": {},
   "source": [
    "&emsp;&emsp;接下来，设计一个用于存储实时天气信息的表。这里我们定义一个新的模型 `Weather`，并包括上述所提取出来的的字段。连接 `Mysql`数据库及创建表的代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "a0c351a6-e10e-44a8-b54e-dfb750b6b105",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sqlalchemy import create_engine, Column, Integer, String, Float\n",
    "from sqlalchemy.orm import sessionmaker, declarative_base\n",
    "\n",
    "# 创建基类\n",
    "Base = declarative_base()\n",
    "\n",
    "# 定义 WeatherInfo 模型\n",
    "class Weather(Base):\n",
    "    __tablename__ = 'weather'\n",
    "    city_id = Column(Integer, primary_key=True)  # 城市ID\n",
    "    city_name = Column(String(50))                # 城市名称\n",
    "    main_weather = Column(String(50))             # 主要天气状况\n",
    "    description = Column(String(100))              # 描述\n",
    "    temperature = Column(Float)                    # 温度\n",
    "    feels_like = Column(Float)                    # 体感温度\n",
    "    temp_min = Column(Float)                      # 最低温度\n",
    "    temp_max = Column(Float)                      # 最高温度\n",
    "\n",
    "\n",
    "# 数据库连接 URI，这里要替换成自己的Mysql 连接信息，以下是各个字段的对应解释：\n",
    "# root：MySQL 数据库的用户名。\n",
    "# snowball950123：MySQL 数据库的密码。\n",
    "# 192.168.110.131：MySQL 服务器的 IP 地址。\n",
    "# langgraph_agent：要连接的数据库的名称。\n",
    "# charset=utf8mb4：设置数据库的字符集为 utf8mb4，支持更广泛的 Unicode 字符\n",
    "DATABASE_URI = 'mysql+pymysql://root:snowball950123@192.168.110.131/langgraph_agent?charset=utf8mb4'    \n",
    "engine = create_engine(DATABASE_URI)\n",
    "\n",
    "# 如果表不存在，则创建表\n",
    "Base.metadata.create_all(engine)\n",
    "\n",
    "# 创建会话\n",
    "Session = sessionmaker(bind=engine)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0625574f-7582-4118-91c8-1184e7dad3e4",
   "metadata": {},
   "source": [
    "&emsp;&emsp;接下来，使用`LangChain`的`tool` 装饰器将普通的函数注册为`LangGraph`中支持的工具服务，根据需求的设计，我们要依次创建三个外部函数，分别是：\n",
    "1. `get_weather`工具：用于根据城市名称实时查询该城市的当前天气数据。\n",
    "2. `insert_weather_to_db`工具：如果用户想要把查询到的天气数据插入到数据库的表中，则使用此函数完成数据库的插入操作。\n",
    "3. `query_weather_from_db`工具：如果用户想基于本地数据库的天气数据直接进行提问，则使用此函数完成数据库的查询操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4ba315e-c210-412e-ae0f-d832221df5b3",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如上节课实践的流程一样，我们依然使用`pydantic`来做工具的参数校验和结构化输出。三个工具函数的定义代码依次如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "f039aea7-7e8d-4f09-9d62-27b36d0b5a9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "from typing import Union, Optional\n",
    "from pydantic import BaseModel, Field\n",
    "import requests\n",
    "\n",
    "class WeatherLoc(BaseModel):\n",
    "    location: str = Field(description=\"The location name of the city\")\n",
    "\n",
    "\n",
    "class WeatherInfo(BaseModel):\n",
    "    \"\"\"Extracted weather information for a specific city.\"\"\"\n",
    "    city_id: int = Field(..., description=\"The unique identifier for the city\")\n",
    "    city_name: str = Field(..., description=\"The name of the city\")\n",
    "    main_weather: str = Field(..., description=\"The main weather condition\")\n",
    "    description: str = Field(..., description=\"A detailed description of the weather\")\n",
    "    temperature: float = Field(..., description=\"Current temperature in Celsius\")\n",
    "    feels_like: float = Field(..., description=\"Feels-like temperature in Celsius\")\n",
    "    temp_min: float = Field(..., description=\"Minimum temperature in Celsius\")\n",
    "    temp_max: float = Field(..., description=\"Maximum temperature in Celsius\")\n",
    "\n",
    "class QueryWeatherSchema(BaseModel):\n",
    "    \"\"\"Schema for querying weather information by city name.\"\"\"\n",
    "    city_name: str = Field(..., description=\"The name of the city to query weather information\")\n",
    "\n",
    "\n",
    "@tool(args_schema=WeatherInfo)\n",
    "def insert_weather_to_db(city_id, city_name, main_weather, description, temperature, feels_like, temp_min, temp_max):\n",
    "    \"\"\"Insert weather information into the database.\"\"\"\n",
    "    session = Session()  # 确保为每次操作创建新的会话\n",
    "    try:\n",
    "        # 创建天气实例\n",
    "        weather = Weather(\n",
    "            city_id=city_id,\n",
    "            city_name=city_name,\n",
    "            main_weather=main_weather,\n",
    "            description=description,\n",
    "            temperature=temperature,\n",
    "            feels_like=feels_like,\n",
    "            temp_min=temp_min,\n",
    "            temp_max=temp_max\n",
    "        )\n",
    "        # 使用 merge 方法来插入或更新（如果已有记录则更新）\n",
    "        session.merge(weather)\n",
    "        # 提交事务\n",
    "        session.commit()\n",
    "        return {\"messages\": [f\"天气数据已成功存储至Mysql数据库。\"]}\n",
    "    except Exception as e:\n",
    "        session.rollback()  # 出错时回滚\n",
    "        return {\"messages\": [f\"数据存储失败，错误原因：{e}\"]}\n",
    "    finally:\n",
    "        session.close()  # 关闭会话\n",
    "\n",
    "\n",
    "@tool(args_schema=QueryWeatherSchema)\n",
    "def query_weather_from_db(city_name: str):\n",
    "    \"\"\"Query weather information from the database by city name.\"\"\"\n",
    "    session = Session()\n",
    "    try:\n",
    "        # 查询天气数据\n",
    "        weather_data = session.query(Weather).filter(Weather.city_name == city_name).first()\n",
    "        if weather_data:\n",
    "            return {\n",
    "                \"city_id\": weather_data.city_id,\n",
    "                \"city_name\": weather_data.city_name,\n",
    "                \"main_weather\": weather_data.main_weather,\n",
    "                \"description\": weather_data.description,\n",
    "                \"temperature\": weather_data.temperature,\n",
    "                \"feels_like\": weather_data.feels_like,\n",
    "                \"temp_min\": weather_data.temp_min,\n",
    "                \"temp_max\": weather_data.temp_max\n",
    "            }\n",
    "        else:\n",
    "            return {\"messages\": [f\"未找到城市 '{city_name}' 的天气信息。\"]}\n",
    "    except Exception as e:\n",
    "        return {\"messages\": [f\"查询失败，错误原因：{e}\"]}\n",
    "    finally:\n",
    "        session.close()  # 关闭会话"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "494ef630-205f-4448-aea3-5b7025b34828",
   "metadata": {},
   "source": [
    "&emsp;&emsp;然后，定义实时联网检索外部工具，通过该函数获取最新的网络数据信息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "847cd8a7-1a6e-4846-87bb-9d97b240a0f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "class SearchQuery(BaseModel):\n",
    "    query: str = Field(description=\"Questions for networking queries\")\n",
    "\n",
    "\n",
    "@tool(args_schema = SearchQuery)\n",
    "def fetch_real_time_info(query):\n",
    "    \"\"\"Get real-time Internet information\"\"\"\n",
    "    url = \"https://google.serper.dev/search\"\n",
    "    payload = json.dumps({\n",
    "      \"q\": query,\n",
    "      \"num\": 1,\n",
    "    })\n",
    "    headers = {\n",
    "      'X-API-KEY': 'cd872fca99047eb9165242365c65b858bc8970c0',\n",
    "      'Content-Type': 'application/json'\n",
    "    }\n",
    "    \n",
    "    response = requests.post(url, headers=headers, data=payload)\n",
    "    data = json.loads(response.text)  # 将返回的JSON字符串转换为字典\n",
    "    if 'organic' in data:\n",
    "        return json.dumps(data['organic'],  ensure_ascii=False)  # 返回'organic'部分的JSON字符串\n",
    "    else:\n",
    "        return json.dumps({\"error\": \"No organic results found\"},  ensure_ascii=False)  # 如果没有'organic'键，返回错误信息"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1dc9f46e-12c6-4a90-9853-04064eb9226e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;然后把所有定义的工具存储在一个列表中，如下代码所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "67ecaf1b-22ac-42a5-afb3-f1eeb7eca146",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [fetch_real_time_info, get_weather, insert_weather_to_db, query_weather_from_db]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "c45325e4-c099-41c0-8af5-b2789239a8cd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[StructuredTool(name='fetch_real_time_info', description='Get real-time Internet information', args_schema=<class '__main__.SearchQuery'>, func=<function fetch_real_time_info at 0x00000222A40605E0>),\n",
       " <function __main__.get_weather(loc)>,\n",
       " StructuredTool(name='insert_weather_to_db', description='Insert weather information into the database.', args_schema=<class '__main__.WeatherInfo'>, func=<function insert_weather_to_db at 0x00000222A4060C20>),\n",
       " StructuredTool(name='query_weather_from_db', description='Query weather information from the database by city name.', args_schema=<class '__main__.QueryWeatherSchema'>, func=<function query_weather_from_db at 0x00000222A4060CC0>)]"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "baec2cbb-6fae-4b50-86d4-25b43a589180",
   "metadata": {},
   "source": [
    "&emsp;&emsp;准备好工具后，接下来定义用于构建`AI Agent`的大模型实例，这里我们使用`OpenAI`的在线`GPT-4`模型。代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "0f7e7fc6-c097-463d-b5bd-fbd44855e29d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "if not os.environ.get(\"OPENAI_API_KEY\"):\n",
    "    os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Enter your OpenAI API key: \")\n",
    "\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-4o\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2733c866-cbd2-4969-bdee-2a58503b50b7",
   "metadata": {},
   "source": [
    "&emsp;&emsp;当有了工具列表和模型后，就可以通过`create_react_agent`这个`LangGraph`框架中预构建的方法来创建自治循环代理（ReAct）的工作流，其必要的参数如下：\n",
    "\n",
    "- model： 支持工具调用的LangChain聊天模型。\n",
    "- tools： 工具列表、ToolExecutor 或 ToolNode 实例。\n",
    "- state_schema：图的状态模式。必须有`messages`和`is_last_step`键。默认为定义这两个键的`Agent State`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29c369b4-9c06-474d-9c3e-b7d94fdd67f7",
   "metadata": {},
   "source": [
    "&emsp;&emsp;上述三点我们均在前面的课程中详细且作为重点介绍过，大家应该是比较容易理解的。所以，创建`ReAct`代理的代码就如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "id": "735fbb39-2872-4da6-80c2-bf9918dcbb2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "graph = create_react_agent(llm, tools=tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "id": "2379bee8-5f61-4946-9900-d846a4f6ae34",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<langgraph.graph.state.CompiledStateGraph at 0x222a674c610>"
      ]
     },
     "execution_count": 115,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f477365-9f76-4590-ae53-bc0c8c55eaf6",
   "metadata": {},
   "source": [
    "&emsp;&emsp;我们可以逐步的分析和解释一下这一行代码中涉及的图构建过程："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cbf93aa-c0ca-416c-b3a3-d8c0825566c1",
   "metadata": {},
   "source": [
    "- **Step 1. 定义图状态模式**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4064428b-60f5-4adf-9501-fb6b3ff29d60",
   "metadata": {},
   "source": [
    "&emsp;&emsp;`LangGraph`中的主要图类型是`StateGraph`。每个节点通过`State`中的参数获取有效信息，执行完节点的内部逻辑后，更新该`State`状态中的值。不同的状态模式，可以通过注释设置状态的特定属性（例如覆盖现有值）或添加到现有属性。伪代码如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac76a896-52f3-4e10-8811-d6228565f18f",
   "metadata": {},
   "source": [
    "```python\n",
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7616662f-553c-42e4-b55c-027a892a1474",
   "metadata": {},
   "source": [
    "- **Step 2. 定义`Router Function`**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b559a066-d4c1-41ec-93fd-78ae7798f578",
   "metadata": {},
   "source": [
    "&emsp;&emsp;设置边缘条件，有条件的原因是，根据节点的输出，可以采用多个路径之一。在该节点运行之前，所采用的路径是未知的（由大模型决定）。\n",
    "- 条件边缘：调用代理后，如果代理说要采取行动，那么应该调用调用工具的函数。如果代理说已经完成，那么就应该完成。\n",
    "- 正常边：调用工具后，它应该始终返回给代理来决定下一步做什么。\n",
    "\n",
    "&emsp;&emsp;伪代码如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9156fb50-2421-4b22-980c-91b3485ffd39",
   "metadata": {},
   "source": [
    "```python\n",
    "# 定义决定是否继续执行任务的路由函数\n",
    "def should_continue(state: State):\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    # 如果不是工具调用，则结束\n",
    "    if not last_message.tool_calls:\n",
    "        return END\n",
    "    # 如果是的话，则进入工具库中选择函数执行\n",
    "    else:\n",
    "        return \"tools\"\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3bfd4ed-1807-48ba-a64b-7ccdc9d223f4",
   "metadata": {},
   "source": [
    "- **Step 3. 定义大模型的交互函数**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "126aad2c-2703-447c-99f8-e473942a72e3",
   "metadata": {},
   "source": [
    "&emsp;&emsp;接下来需要通过一个节点函数加载我想要使用的大模型。它需要满足两个标准：\n",
    "- 应该与消息一起使用，因为图的状态主要是消息列表（聊天历史记录）。\n",
    "- 需要与工具调用一起使用，其内部使用的是预构建的ToolNode。\n",
    "\n",
    "&emsp;&emsp;伪代码如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba357f1f-b1e2-48ba-951d-87ccb672e104",
   "metadata": {},
   "source": [
    "```python\n",
    "from typing import Literal\n",
    "\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "\n",
    "# 定义大模型交互的节点函数\n",
    "async def call_model(state: State, config: RunnableConfig):\n",
    "    messages = state[\"messages\"]\n",
    "    response = await model.ainvoke(messages, config)\n",
    "    # 将调用大模型后得到的响应，追加到消息列表中\n",
    "    return {\"messages\": response}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94fde33f-986d-445d-8fec-d4129e569c42",
   "metadata": {},
   "source": [
    "- **Step 4. 构建图结构**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0370f6a-9b37-48b2-9748-297ec63319c1",
   "metadata": {},
   "source": [
    "&emsp;&emsp;最后，把上述所有的组件放在一起构建图结构，这与我们手动构建图的方式基本一致，伪代码如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "932fc1de-e3d2-49e6-a159-74e572025b7b",
   "metadata": {},
   "source": [
    "```python\n",
    "from langgraph.graph import END, START, StateGraph\n",
    "\n",
    "# 定义一个新图\n",
    "workflow = StateGraph(State)\n",
    "\n",
    "# 添加两个节点\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", tool_node)\n",
    "\n",
    "# 设置起始节点为 agent\n",
    "workflow.add_edge(START, \"agent\")\n",
    "\n",
    "# 添加条件边 -- > Router Agent\n",
    "workflow.add_conditional_edges(\n",
    "    \"agent\",\n",
    "    should_continue,\n",
    "    [\"tools\", END],\n",
    ")\n",
    "\n",
    "# 添加回调边\n",
    "workflow.add_edge(\"tools\", \"agent\")\n",
    "\n",
    "# 编译图\n",
    "app = workflow.compile()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c79ff959-3be3-46ae-a5f7-19119307a4b4",
   "metadata": {},
   "source": [
    "&emsp;&emsp;理解了上面的`create_react_agent`方法内部的构建原理后，其实就能明白：当通过`create_react_agent(llm, tools=tools)`一行代码的执行，现在得到的已经是一个编译后、可执行的图了。我们可以通过`mermaid`方法来可视化经过`create_react_agent`方法构造出来的图结构，代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "id": "cb7f6a67-7089-46fe-a0b8-55a97980ec10",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "[encoded data, 10236 characters]",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc637164-7a86-4ed3-bf89-b7d28b4dbf3a",
   "metadata": {},
   "source": [
    "&emsp;&emsp;返回的是编译好的`LangGraph`可运行程序，可直接用于聊天交互。调用方式则和之前使用的方法一样，我们可以依次针对不同复杂程度的需求依次进行提问。首先是测试是否可以不使用工具，直接调用大模型生成响应。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 139,
   "id": "2b397283-67f3-4bfb-b1cb-f67dcee97ac8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='你好，请你介绍一下你自己', additional_kwargs={}, response_metadata={}, id='268158b3-ec6d-44a7-bbd5-bb43d449ff7e'),\n",
       "  AIMessage(content='你好！我是一个由人工智能驱动的助手，旨在帮助解答问题、提供信息并协助完成各种任务。我可以处理广泛的主题，包括但不限于科技、教育、娱乐、天气、新闻等。如果你有任何特定的问题或需要帮助的地方，请随时告诉我！', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 67, 'prompt_tokens': 343, 'total_tokens': 410, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-968ec2f8-eb4b-45cf-ac55-398deba6e6fe-0', usage_metadata={'input_tokens': 343, 'output_tokens': 67, 'total_tokens': 410, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 139,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# query=\"你好，请你介绍一下你自己\"\n",
    "# input_message = {\"messages\": [HumanMessage(content=query)]}\n",
    "\n",
    "# 可以自动处理成 HumanMessage 的消息格式\n",
    "finan_response = graph.invoke({\"messages\":[\"你好，请你介绍一下你自己\"]})\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "id": "85500ff9-9656-472d-8eca-77a642e7d82c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好！我是一个由人工智能驱动的助手，旨在帮助解答问题、提供信息并协助完成各种任务。我可以处理广泛的主题，包括但不限于科技、教育、娱乐、天气、新闻等。如果你有任何特定的问题或需要帮助的地方，请随时告诉我！'"
      ]
     },
     "execution_count": 144,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response[\"messages\"][-1].content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b71144e7-678a-486b-9649-99f6b2d06940",
   "metadata": {},
   "source": [
    "&emsp;&emsp;加大输入问题的复杂度，接下来我们提问的问题希望它能够自动找到正确的工具函数，基于工具的执行结果作为既定的事实，引导生成最终的回复。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 147,
   "id": "729dcc03-f51e-451d-b60c-6ce74a7494f8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Weather object at 0x00000222A5896390>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='北京今天的天气怎么样？', additional_kwargs={}, response_metadata={}, id='3ea29a69-311d-4003-935f-18ca24bc9e6f'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_BMA4Jp782AMVBw4VS1x3nv8h', 'function': {'arguments': '{\"city_name\":\"Beijing\"}', 'name': 'query_weather_from_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 342, 'total_tokens': 360, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-eb43ab41-161c-4acb-b8d7-b5890745ce55-0', tool_calls=[{'name': 'query_weather_from_db', 'args': {'city_name': 'Beijing'}, 'id': 'call_BMA4Jp782AMVBw4VS1x3nv8h', 'type': 'tool_call'}], usage_metadata={'input_tokens': 342, 'output_tokens': 18, 'total_tokens': 360, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='{\"city_id\": 1816670, \"city_name\": \"Beijing\", \"main_weather\": \"Clouds\", \"description\": \"晴，少云\", \"temperature\": 14.94, \"feels_like\": 13.59, \"temp_min\": 14.94, \"temp_max\": 14.94}', name='query_weather_from_db', id='8b21e310-8581-4a4f-b3a0-c3c2b4b22195', tool_call_id='call_BMA4Jp782AMVBw4VS1x3nv8h'),\n",
       "  AIMessage(content='北京今天的天气是多云的。具体情况如下：\\n\\n- 天气描述：晴，少云\\n- 当前温度：14.94°C\\n- 体感温度：13.59°C\\n- 最低温度：14.94°C\\n- 最高温度：14.94°C', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 67, 'prompt_tokens': 440, 'total_tokens': 507, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-25dabd3b-195e-49ce-a431-b9f535fdd01c-0', usage_metadata={'input_tokens': 440, 'output_tokens': 67, 'total_tokens': 507, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 147,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response = graph.invoke({\"messages\":[\"北京今天的天气怎么样？\"]})\n",
    "\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 149,
   "id": "808cff4b-98d7-46ef-a464-5922d8ee9ca1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'北京今天的天气是多云的。具体情况如下：\\n\\n- 天气描述：晴，少云\\n- 当前温度：14.94°C\\n- 体感温度：13.59°C\\n- 最低温度：14.94°C\\n- 最高温度：14.94°C'"
      ]
     },
     "execution_count": 149,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response[\"messages\"][-1].content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 147,
   "id": "b9e816fc-3468-468b-9a71-c2c80c54564d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Weather object at 0x00000222A5896390>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='北京今天的天气怎么样？', additional_kwargs={}, response_metadata={}, id='3ea29a69-311d-4003-935f-18ca24bc9e6f'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_BMA4Jp782AMVBw4VS1x3nv8h', 'function': {'arguments': '{\"city_name\":\"Beijing\"}', 'name': 'query_weather_from_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 342, 'total_tokens': 360, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-eb43ab41-161c-4acb-b8d7-b5890745ce55-0', tool_calls=[{'name': 'query_weather_from_db', 'args': {'city_name': 'Beijing'}, 'id': 'call_BMA4Jp782AMVBw4VS1x3nv8h', 'type': 'tool_call'}], usage_metadata={'input_tokens': 342, 'output_tokens': 18, 'total_tokens': 360, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='{\"city_id\": 1816670, \"city_name\": \"Beijing\", \"main_weather\": \"Clouds\", \"description\": \"晴，少云\", \"temperature\": 14.94, \"feels_like\": 13.59, \"temp_min\": 14.94, \"temp_max\": 14.94}', name='query_weather_from_db', id='8b21e310-8581-4a4f-b3a0-c3c2b4b22195', tool_call_id='call_BMA4Jp782AMVBw4VS1x3nv8h'),\n",
       "  AIMessage(content='北京今天的天气是多云的。具体情况如下：\\n\\n- 天气描述：晴，少云\\n- 当前温度：14.94°C\\n- 体感温度：13.59°C\\n- 最低温度：14.94°C\\n- 最高温度：14.94°C', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 67, 'prompt_tokens': 440, 'total_tokens': 507, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-25dabd3b-195e-49ce-a431-b9f535fdd01c-0', usage_metadata={'input_tokens': 440, 'output_tokens': 67, 'total_tokens': 507, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 147,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response = graph.invoke({\"messages\":[\"北京今天的天气怎么样？\"]})\n",
    "\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 159,
   "id": "c11e26eb-32b2-44cf-9dec-27d08be1b1ca",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='你知道 cloud 3.5 发布的 computer use 吗？请用中文回复我', additional_kwargs={}, response_metadata={}, id='c1ba742a-2c74-4aed-b774-b176ab60e8e1'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_xt4I3LDtRRzOYKyb01fA4Rv5', 'function': {'arguments': '{\"query\":\"cloud 3.5 发布的 computer use\"}', 'name': 'fetch_real_time_info'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 355, 'total_tokens': 379, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cb591e6e-f410-4ab0-a77a-ece56767b728-0', tool_calls=[{'name': 'fetch_real_time_info', 'args': {'query': 'cloud 3.5 发布的 computer use'}, 'id': 'call_xt4I3LDtRRzOYKyb01fA4Rv5', 'type': 'tool_call'}], usage_metadata={'input_tokens': 355, 'output_tokens': 24, 'total_tokens': 379, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='[{\"title\": \"升级了来自Anthropic 的Claude 3.5 Sonnet（现已推出） - AWS\", \"link\": \"https://aws.amazon.com/cn/blogs/china/upgraded-claude-3-5-sonnet-from-anthropic-available-now-computer-use-public-beta-and-claude-3-5-haiku-coming-soon-in-amazon-bedrock/\", \"snippet\": \"计算机使用– Claude 3.5 Sonnet 目前在Amazon Bedrock 中提供计算机使用功能（公测版），这使得Claude 能够感知并与计算机界面进行交互。开发人员可以 ...\", \"date\": \"5 days ago\", \"position\": 1}]', name='fetch_real_time_info', id='dba1ac63-46d2-4b19-926f-3c28da7b29f6', tool_call_id='call_xt4I3LDtRRzOYKyb01fA4Rv5'),\n",
       "  AIMessage(content='关于 Cloud 3.5 发布的 computer use 功能，目前的信息显示，Claude 3.5 Sonnet 现在在 Amazon Bedrock 中提供计算机使用功能（公测版）。这使得 Claude 能够感知并与计算机界面进行交互。更多详情可以查看 [AWS 官方博客](https://aws.amazon.com/cn/blogs/china/upgraded-claude-3-5-sonnet-from-anthropic-available-now-computer-use-public-beta-and-claude-3-5-haiku-coming-soon-in-amazon-bedrock/)。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 122, 'prompt_tokens': 541, 'total_tokens': 663, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45c6de4934', 'finish_reason': 'stop', 'logprobs': None}, id='run-e45a1550-0c7d-4096-b1a0-238ca7cc8248-0', usage_metadata={'input_tokens': 541, 'output_tokens': 122, 'total_tokens': 663, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 159,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response = graph.invoke({\"messages\":[\"你知道 cloud 3.5 发布的 computer use 吗？请用中文回复我\"]})\n",
    "\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "id": "740d12ba-768a-4ef3-afc0-680ead5286ee",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'关于 Cloud 3.5 发布的 computer use 功能，目前的信息显示，Claude 3.5 Sonnet 现在在 Amazon Bedrock 中提供计算机使用功能（公测版）。这使得 Claude 能够感知并与计算机界面进行交互。更多详情可以查看 [AWS 官方博客](https://aws.amazon.com/cn/blogs/china/upgraded-claude-3-5-sonnet-from-anthropic-available-now-computer-use-public-beta-and-claude-3-5-haiku-coming-soon-in-amazon-bedrock/)。'"
      ]
     },
     "execution_count": 161,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response[\"messages\"][-1].content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "172fe2f1-9880-48ac-882b-bde549d6f220",
   "metadata": {},
   "source": [
    "&emsp;&emsp;继续加大问题的难度，我们要在一个问题中涉及多个工具的使用，比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "id": "7107dae6-9737-4d42-910e-5ea053939d99",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='帮我查一下北京、上海，哈尔滨三个城市的天气，告诉我哪个城市最适合出游。同时，把查询到的数据存储到数据库中', additional_kwargs={}, response_metadata={}, id='7da34790-3f00-46d0-859e-b6f7cef4ee5d'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ro4igb9igPivvs0HZXKHAhwG', 'function': {'arguments': '{\"loc\": \"Beijing\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_IRBeslzB7okZa28rAnrNdNbB', 'function': {'arguments': '{\"loc\": \"Shanghai\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_J3yXYOErmnZmooW7A0Wrsh5I', 'function': {'arguments': '{\"loc\": \"Harbin\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 60, 'prompt_tokens': 370, 'total_tokens': 430, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-e630e698-033f-463c-8b48-2107eaa616c2-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Beijing'}, 'id': 'call_ro4igb9igPivvs0HZXKHAhwG', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Shanghai'}, 'id': 'call_IRBeslzB7okZa28rAnrNdNbB', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Harbin'}, 'id': 'call_J3yXYOErmnZmooW7A0Wrsh5I', 'type': 'tool_call'}], usage_metadata={'input_tokens': 370, 'output_tokens': 60, 'total_tokens': 430, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='{\"coord\": {\"lon\": 116.3972, \"lat\": 39.9075}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01n\"}], \"base\": \"stations\", \"main\": {\"temp\": 17.72, \"feels_like\": 16.36, \"temp_min\": 14.94, \"temp_max\": 17.72, \"pressure\": 1017, \"humidity\": 31, \"sea_level\": 1017, \"grnd_level\": 1012}, \"visibility\": 10000, \"wind\": {\"speed\": 1.3, \"deg\": 282, \"gust\": 4.19}, \"clouds\": {\"all\": 0}, \"dt\": 1730195826, \"sys\": {\"type\": 2, \"id\": 2000403, \"country\": \"CN\", \"sunrise\": 1730155186, \"sunset\": 1730193377}, \"timezone\": 28800, \"id\": 1816670, \"name\": \"Beijing\", \"cod\": 200}', name='get_weather', id='969b2663-e3db-4b18-b5ce-433b65adf673', tool_call_id='call_ro4igb9igPivvs0HZXKHAhwG'),\n",
       "  ToolMessage(content='{\"coord\": {\"lon\": 121.4581, \"lat\": 31.2222}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01n\"}], \"base\": \"stations\", \"main\": {\"temp\": 17.92, \"feels_like\": 17.31, \"temp_min\": 17.92, \"temp_max\": 17.93, \"pressure\": 1019, \"humidity\": 59, \"sea_level\": 1019, \"grnd_level\": 1017}, \"visibility\": 10000, \"wind\": {\"speed\": 5, \"deg\": 20}, \"clouds\": {\"all\": 0}, \"dt\": 1730196000, \"sys\": {\"type\": 1, \"id\": 9659, \"country\": \"CN\", \"sunrise\": 1730153230, \"sunset\": 1730192904}, \"timezone\": 28800, \"id\": 1796236, \"name\": \"Shanghai\", \"cod\": 200}', name='get_weather', id='75725f62-f9d2-4fe2-8865-fea606416b6a', tool_call_id='call_IRBeslzB7okZa28rAnrNdNbB'),\n",
       "  ToolMessage(content='{\"coord\": {\"lon\": 126.65, \"lat\": 45.75}, \"weather\": [{\"id\": 500, \"main\": \"Rain\", \"description\": \"\\\\u5c0f\\\\u96e8\", \"icon\": \"10n\"}], \"base\": \"stations\", \"main\": {\"temp\": 8.62, \"feels_like\": 8.62, \"temp_min\": 8.62, \"temp_max\": 8.62, \"pressure\": 1016, \"humidity\": 70, \"sea_level\": 1016, \"grnd_level\": 999}, \"visibility\": 10000, \"wind\": {\"speed\": 0.91, \"deg\": 129, \"gust\": 1.16}, \"rain\": {\"1h\": 0.36}, \"clouds\": {\"all\": 97}, \"dt\": 1730196009, \"sys\": {\"country\": \"CN\", \"sunrise\": 1730153336, \"sunset\": 1730190306}, \"timezone\": 28800, \"id\": 2037013, \"name\": \"Harbin\", \"cod\": 200}', name='get_weather', id='6b49bbfe-e45d-449b-84b2-314f760467ca', tool_call_id='call_J3yXYOErmnZmooW7A0Wrsh5I'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_izOMSYgvfloxuJkXgDB1Voz6', 'function': {'arguments': '{\"city_id\": 1816670, \"city_name\": \"Beijing\", \"main_weather\": \"Clear\", \"description\": \"晴\", \"temperature\": 17.72, \"feels_like\": 16.36, \"temp_min\": 14.94, \"temp_max\": 17.72}', 'name': 'insert_weather_to_db'}, 'type': 'function'}, {'id': 'call_Bc8WJdfNZMzo3hXCyunaCznj', 'function': {'arguments': '{\"city_id\": 1796236, \"city_name\": \"Shanghai\", \"main_weather\": \"Clear\", \"description\": \"晴\", \"temperature\": 17.92, \"feels_like\": 17.31, \"temp_min\": 17.92, \"temp_max\": 17.93}', 'name': 'insert_weather_to_db'}, 'type': 'function'}, {'id': 'call_NanSRD2EDHeOlUktShccuSaW', 'function': {'arguments': '{\"city_id\": 2037013, \"city_name\": \"Harbin\", \"main_weather\": \"Rain\", \"description\": \"小雨\", \"temperature\": 8.62, \"feels_like\": 8.62, \"temp_min\": 8.62, \"temp_max\": 8.62}', 'name': 'insert_weather_to_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 202, 'prompt_tokens': 1177, 'total_tokens': 1379, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-8a14762c-6309-4f27-8eae-a422867a293d-0', tool_calls=[{'name': 'insert_weather_to_db', 'args': {'city_id': 1816670, 'city_name': 'Beijing', 'main_weather': 'Clear', 'description': '晴', 'temperature': 17.72, 'feels_like': 16.36, 'temp_min': 14.94, 'temp_max': 17.72}, 'id': 'call_izOMSYgvfloxuJkXgDB1Voz6', 'type': 'tool_call'}, {'name': 'insert_weather_to_db', 'args': {'city_id': 1796236, 'city_name': 'Shanghai', 'main_weather': 'Clear', 'description': '晴', 'temperature': 17.92, 'feels_like': 17.31, 'temp_min': 17.92, 'temp_max': 17.93}, 'id': 'call_Bc8WJdfNZMzo3hXCyunaCznj', 'type': 'tool_call'}, {'name': 'insert_weather_to_db', 'args': {'city_id': 2037013, 'city_name': 'Harbin', 'main_weather': 'Rain', 'description': '小雨', 'temperature': 8.62, 'feels_like': 8.62, 'temp_min': 8.62, 'temp_max': 8.62}, 'id': 'call_NanSRD2EDHeOlUktShccuSaW', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1177, 'output_tokens': 202, 'total_tokens': 1379, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='{\"messages\": [\"天气数据已成功存储至Mysql数据库。\"]}', name='insert_weather_to_db', id='3faca92c-6a82-40ba-9f59-a5f62326400f', tool_call_id='call_izOMSYgvfloxuJkXgDB1Voz6'),\n",
       "  ToolMessage(content='{\"messages\": [\"天气数据已成功存储至Mysql数据库。\"]}', name='insert_weather_to_db', id='fafbdce3-6ea1-4d93-8ec3-8a32606e6ca7', tool_call_id='call_Bc8WJdfNZMzo3hXCyunaCznj'),\n",
       "  ToolMessage(content='{\"messages\": [\"天气数据已成功存储至Mysql数据库。\"]}', name='insert_weather_to_db', id='0c98e550-8562-4125-9e08-2391c7c1b703', tool_call_id='call_NanSRD2EDHeOlUktShccuSaW'),\n",
       "  AIMessage(content='以下是北京、上海和哈尔滨的天气情况：\\n\\n1. **北京**:\\n   - 天气：晴\\n   - 温度：17.72°C\\n   - 体感温度：16.36°C\\n   - 最低温度：14.94°C\\n   - 最高温度：17.72°C\\n\\n2. **上海**:\\n   - 天气：晴\\n   - 温度：17.92°C\\n   - 体感温度：17.31°C\\n   - 最低温度：17.92°C\\n   - 最高温度：17.93°C\\n\\n3. **哈尔滨**:\\n   - 天气：小雨\\n   - 温度：8.62°C\\n   - 体感温度：8.62°C\\n   - 最低温度：8.62°C\\n   - 最高温度：8.62°C\\n\\n根据天气情况，北京和上海的天气都是晴朗的，比较适合出游。哈尔滨有小雨，不太适合出游。如果一定要选择一个最适合出游的城市，我会推荐上海，因为气温稍微高一点，更加舒适。\\n\\n所有查询到的天气数据已经成功存储到数据库中。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 267, 'prompt_tokens': 1558, 'total_tokens': 1825, 'prompt_tokens_details': {'cached_tokens': 1280}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-51e04ca7-3475-49fb-a5c0-8493faf347a1-0', usage_metadata={'input_tokens': 1558, 'output_tokens': 267, 'total_tokens': 1825, 'input_token_details': {'cache_read': 1280}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 164,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response = graph.invoke({\"messages\":[\"帮我查一下北京、上海，哈尔滨三个城市的天气，告诉我哪个城市最适合出游。同时，把查询到的数据存储到数据库中\"]})\n",
    "\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "id": "3beb83cd-4bfc-4c93-8772-4c5779630ddf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'以下是北京、上海和哈尔滨的天气情况：\\n\\n1. **北京**:\\n   - 天气：晴\\n   - 温度：17.72°C\\n   - 体感温度：16.36°C\\n   - 最低温度：14.94°C\\n   - 最高温度：17.72°C\\n\\n2. **上海**:\\n   - 天气：晴\\n   - 温度：17.92°C\\n   - 体感温度：17.31°C\\n   - 最低温度：17.92°C\\n   - 最高温度：17.93°C\\n\\n3. **哈尔滨**:\\n   - 天气：小雨\\n   - 温度：8.62°C\\n   - 体感温度：8.62°C\\n   - 最低温度：8.62°C\\n   - 最高温度：8.62°C\\n\\n根据天气情况，北京和上海的天气都是晴朗的，比较适合出游。哈尔滨有小雨，不太适合出游。如果一定要选择一个最适合出游的城市，我会推荐上海，因为气温稍微高一点，更加舒适。\\n\\n所有查询到的天气数据已经成功存储到数据库中。'"
      ]
     },
     "execution_count": 166,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response[\"messages\"][-1].content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c5522a5-26af-484e-ab2c-b28b4f2442b3",
   "metadata": {},
   "source": [
    "&emsp;&emsp;同时，可以在数据库中查看数据的插入情况："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be607416-30bd-4c0a-bf68-eb8430d27b68",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241029183322973.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 170,
   "id": "90c00079-83ad-45cb-a81c-d0daf39a78c2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Weather object at 0x00000222A72E6350>\n",
      "<__main__.Weather object at 0x00000222A6F4AC50>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='帮我分析一下数据库中北京和哈尔滨城市天气的信息，做一个详细的对比，并生成出行建议', additional_kwargs={}, response_metadata={}, id='de625a6f-c247-41dd-9bf0-e5e2af41459f'),\n",
       "  AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_f2h95mzFmIAZ7LwcBaL1QUHP', 'function': {'arguments': '{\"city_name\": \"Beijing\"}', 'name': 'query_weather_from_db'}, 'type': 'function'}, {'id': 'call_8P3qlq6yM9kelaHqRZ9zZ4O0', 'function': {'arguments': '{\"city_name\": \"Harbin\"}', 'name': 'query_weather_from_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 52, 'prompt_tokens': 361, 'total_tokens': 413, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-33535fcc-cb90-4bc5-b8e2-421d7b20e68b-0', tool_calls=[{'name': 'query_weather_from_db', 'args': {'city_name': 'Beijing'}, 'id': 'call_f2h95mzFmIAZ7LwcBaL1QUHP', 'type': 'tool_call'}, {'name': 'query_weather_from_db', 'args': {'city_name': 'Harbin'}, 'id': 'call_8P3qlq6yM9kelaHqRZ9zZ4O0', 'type': 'tool_call'}], usage_metadata={'input_tokens': 361, 'output_tokens': 52, 'total_tokens': 413, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}),\n",
       "  ToolMessage(content='{\"city_id\": 1816670, \"city_name\": \"Beijing\", \"main_weather\": \"Clear\", \"description\": \"晴\", \"temperature\": 17.72, \"feels_like\": 16.36, \"temp_min\": 14.94, \"temp_max\": 17.72}', name='query_weather_from_db', id='d1b400d9-02f4-4ffd-9f00-0cfc2298bf92', tool_call_id='call_f2h95mzFmIAZ7LwcBaL1QUHP'),\n",
       "  ToolMessage(content='{\"city_id\": 2037013, \"city_name\": \"Harbin\", \"main_weather\": \"Rain\", \"description\": \"小雨\", \"temperature\": 8.62, \"feels_like\": 8.62, \"temp_min\": 8.62, \"temp_max\": 8.62}', name='query_weather_from_db', id='d84861cd-fc8f-44f8-8faa-5b5b8218bace', tool_call_id='call_8P3qlq6yM9kelaHqRZ9zZ4O0'),\n",
       "  AIMessage(content='### 天气对比分析\\n\\n#### 北京\\n- **天气状况**: 晴\\n- **详细描述**: 晴\\n- **当前温度**: 17.72°C\\n- **体感温度**: 16.36°C\\n- **最低温度**: 14.94°C\\n- **最高温度**: 17.72°C\\n\\n#### 哈尔滨\\n- **天气状况**: 雨\\n- **详细描述**: 小雨\\n- **当前温度**: 8.62°C\\n- **体感温度**: 8.62°C\\n- **最低温度**: 8.62°C\\n- **最高温度**: 8.62°C\\n\\n### 出行建议\\n\\n#### 北京\\n1. **穿着建议**: 北京天气晴朗，温度适中，建议穿轻便的春秋装。\\n2. **活动建议**: 适合户外活动，如散步、慢跑或骑行。\\n3. **防晒措施**: 即使是晴天，依然建议使用防晒霜和佩戴太阳镜。\\n\\n#### 哈尔滨\\n1. **穿着建议**: 哈尔滨气温较低且有小雨，建议穿保暖的衣物，并携带雨具。\\n2. **活动建议**: 降雨可能影响户外活动，建议选择室内活动。\\n3. **行车安全**: 雨天路滑，驾车请注意安全，保持车距。\\n\\n这两座城市当前的天气差异较大，出行前请根据实际天气情况做好相应准备。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 355, 'prompt_tokens': 566, 'total_tokens': 921, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45c6de4934', 'finish_reason': 'stop', 'logprobs': None}, id='run-af1a77c2-2fd4-4549-8683-d8df867221d1-0', usage_metadata={'input_tokens': 566, 'output_tokens': 355, 'total_tokens': 921, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}"
      ]
     },
     "execution_count": 170,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "finan_response = graph.invoke({\"messages\":[\"帮我分析一下数据库中北京和哈尔滨城市天气的信息，做一个详细的对比，并生成出行建议\"]})\n",
    "\n",
    "finan_response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 174,
   "id": "1290b10e-186c-41cd-9778-f9d556b217a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "### 天气对比分析\n",
      "\n",
      "#### 北京\n",
      "- **天气状况**: 晴\n",
      "- **详细描述**: 晴\n",
      "- **当前温度**: 17.72°C\n",
      "- **体感温度**: 16.36°C\n",
      "- **最低温度**: 14.94°C\n",
      "- **最高温度**: 17.72°C\n",
      "\n",
      "#### 哈尔滨\n",
      "- **天气状况**: 雨\n",
      "- **详细描述**: 小雨\n",
      "- **当前温度**: 8.62°C\n",
      "- **体感温度**: 8.62°C\n",
      "- **最低温度**: 8.62°C\n",
      "- **最高温度**: 8.62°C\n",
      "\n",
      "### 出行建议\n",
      "\n",
      "#### 北京\n",
      "1. **穿着建议**: 北京天气晴朗，温度适中，建议穿轻便的春秋装。\n",
      "2. **活动建议**: 适合户外活动，如散步、慢跑或骑行。\n",
      "3. **防晒措施**: 即使是晴天，依然建议使用防晒霜和佩戴太阳镜。\n",
      "\n",
      "#### 哈尔滨\n",
      "1. **穿着建议**: 哈尔滨气温较低且有小雨，建议穿保暖的衣物，并携带雨具。\n",
      "2. **活动建议**: 降雨可能影响户外活动，建议选择室内活动。\n",
      "3. **行车安全**: 雨天路滑，驾车请注意安全，保持车距。\n",
      "\n",
      "这两座城市当前的天气差异较大，出行前请根据实际天气情况做好相应准备。\n"
     ]
    }
   ],
   "source": [
    "print(finan_response[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d25ad8f8-0de0-41f1-a3e2-c4e1819f557e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;通过对不同复杂程度输入问题的测试，我们发现当前架构能够非常准确且快速地完成任务目标。在涉及多个任务的顺序执行时，`ReAct` 代理能够自主决策并执行，真正实现了完全的自治循环代理。此外，其可扩展性也十分出色。**对于不同的业务需求，我们只需调整接入的大模型实例（可使用其他开源或在线模型）作为 `ReAct` 的基础模型。对于工具的配置，也无需特别进行复杂的编排，只需明确定义每个工具的输入和输出，然后通过工具列表的形式直接注册到大模型实例及 `ToolNode` 实例中**。这种方法在快速构建智能代理方面，非常值得大家尝试。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "711cdc2d-92cf-42b3-a3ea-1a17436dd335",
   "metadata": {},
   "source": [
    "# 3. LangGraph中的事件流"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bfc0e23-f020-4a2a-a730-92c0e4d006ae",
   "metadata": {},
   "source": [
    "&emsp;&emsp;大模型的流式输出功能我们在《Ch.6 OpenAI Assistant API 高阶应用 - 流式输出功能》中首次提到。这一功能与非流式输出不同，后者在 `Agent` 内部处理完成后一次性输出结果。**流式输出的作用在于，它能实时捕捉并输出任务处理过程中的状态变化。这意味着，任何中间过程中的新状态和值都可以被即时获取到。**所以，流式输出功能本质上不直接参与`Agent`的执行过程，仅仅是用来追踪、记录`Agent`在处理不同任务时产生的各个事件、状态和值。\n",
    "\n",
    "&emsp;&emsp;在实际应用中，流式输出尤其适用于需要快速反馈的业务场景，如聊天机器人，因为**大语言模型可能需要几秒钟才能生成对查询的完整响应，这远远慢于应用程序对最终用户的响应速度约为 200-300 毫秒的阈值**，如果是涉及多个大模型调用的复杂应用程序，这种延时会变得更加明显。让应用程序感觉响应更快的关键策略是显示中间进度；即，通过 `token` 流式传输大模型`Token`的输出，以此来显著提升用户体验。而在开发阶段，利用流式输出功能可以准确追踪到事件的具体执行阶段，并捕获相关数据，从而接入不同逻辑的数据处理和决策流程。是我们在应用开发中必须理解和掌握的技术点。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99ad66b0-d49f-4adf-bbe0-387be88131b2",
   "metadata": {},
   "source": [
    "&emsp;&emsp;流式输出功能在`LangGraph` 框架中的实现方式，相较于`Assistant API`是简单很多的，但基本思路一样。因为`LangGraph`底层是基于 `LangChain` 构建的，所有就直接把`LangChain`中的回调系统拿过来使用了。**在`LangChain`中的流式输出是：以块的形式传输最终输出，即一旦监测到有可用的块，就直接生成它。**最常见和最关键的流数据是大模型本身生成的输出。 大模型通常需要时间才能生成完整的响应，通过实时流式传输输出，用户可以在生成时看到部分结果，这可以提供即时反馈并有助于减少用户的等待时间。如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 201,
   "id": "63ca6fb8-1c00-41eb-b379-33a5439e4482",
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "if not os.environ.get(\"OPENAI_API_KEY\"):\n",
    "    os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Enter your OpenAI API key: \")\n",
    "\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-4o\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 205,
   "id": "b4e2a8c7-ed27-4aeb-a32e-a20f62a5e757",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|你好|！|我是|一个|由|人工|智能|驱|动|的|虚|拟|助手|，|旨|在|帮助|回答|问题|和|提供|信息|。我|能够|处理|各种|主题|，包括|常|识|问题|、|技术|支持|、|语言|翻|译|、|写|作|建议|等等|。|我的|设计|目标|是|尽|量|理解|你的|问题|并|提供|有|用|的|答案|。\n",
      "\n",
      "|由于|我是|基|于|大|规模|的|语言|模型|构|建|的|，因此|我的|知识|和|能力|是|基|于|对|大量|文本|数据|的|分析|。我|没有|个人|经验|或|感|情|，但|我|会|尽|力|为|你|提供|准确|和|及时|的信息|。\n",
      "\n",
      "|如果|你|有|任何|问题|或|需要|帮助|，请|随|时|告诉|我|！||"
     ]
    }
   ],
   "source": [
    "chunks = []\n",
    "async for chunk in llm.astream(\"你好，请你详细的介绍一下你自己。\"):\n",
    "    chunks.append(chunk)\n",
    "    print(chunk.content, end=\"|\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 207,
   "id": "e86e1e1f-5728-46d8-9656-493ff332f2f8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessageChunk(content='', additional_kwargs={}, response_metadata={}, id='run-034a91b3-9509-4442-8c0d-3d17774451bf')"
      ]
     },
     "execution_count": 207,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chunks[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21a13fd7-fd41-4d00-82b0-bb6ee6bc545e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;每一个块，都是一个`AIMessageChunk`对象，用来代表`AIMessage`对象的一部分。消息块在设计上是可加的，比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 211,
   "id": "58b4e258-97fa-45e9-89ac-f8c1fd13004b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessageChunk(content='你好！我是一个', additional_kwargs={}, response_metadata={}, id='run-034a91b3-9509-4442-8c0d-3d17774451bf')"
      ]
     },
     "execution_count": 211,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chunks[0] + chunks[1] + chunks[2] + chunks[3] + chunks[4]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc4c9ef6-ac98-4fa8-85f3-16b6fb537d17",
   "metadata": {},
   "source": [
    "&emsp;&emsp;而进一步的，除了流式传输大模型的输出之外，通过更复杂的工作流程或管道流式传输进度也很有用，比如 `AI Agent` 中的中间处理过程，这就涉及到工作流概念。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72fa8a55-fb39-4987-9845-0b886a9d2c28",
   "metadata": {},
   "source": [
    "## 3.1 LangGraph使用流输出"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00436412-28f9-44dc-819c-fa88c19d45c4",
   "metadata": {},
   "source": [
    "&emsp;&emsp;`LangGraph`框架中的工作流中由各个步骤的节点和边组成。这里的流式传输涉及在各个节点请求更新时跟踪图状态的变化。这样可以更精细地监控工作流中当前处于活动状态的节点，并在工作流经过不同阶段时提供有关工作流状态的实时更新。其实现方式也是和`LangChain`一样通过`.stream`和`.astream`方法执行流式输出，只不过适配到了图结构中。调用`.stream`和`.astream`方法时可以指定几种不同的模式，即：\n",
    "\n",
    "- \"values\" ：在图中的每个步骤之后流式传输**状态**的完整值。\n",
    "- \"updates\" ：在图中的每个步骤之后将更新流式传输到状态。如果在同一步骤中进行多个更新（例如运行多个节点），则这些更新将单独流式传输。\n",
    "- \"debug\" ：在整个图的执行过程中流式传输尽可能多的信息，主要用于调试程序。\n",
    "- \"messages\"：记录每个`messages`中的增量`token`。\n",
    "- \"custom\"：自定义流，通过`LangGraph 的 StreamWriter`方法"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "291ffc21-146c-4b34-9ba9-c631af02241b",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241028182221186.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a87990aa-6175-4c87-9fc5-9ca8e7db17de",
   "metadata": {},
   "source": [
    "&emsp;&emsp;首先来看`Stream`方法，该方法返回一个迭代器，在生成输出块时同步生成它们。我们可以使用`for`循环来实时处理每个块。生成的块的类型取决于正在流式传输的组件。例如，当从大模型流式传输时，每个组件将是一个`AIMessageChunk`，但是，对于其他组件，块可能会有所不同。其`LangGraph`框架中实现的源码如下："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17230196-5732-44e7-bd59-f61aafd556f2",
   "metadata": {},
   "source": [
    "> LangGraph Graph stream：https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.graph.CompiledGraph.stream"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5923fef6-5e03-4fed-ab16-f27e0aae1764",
   "metadata": {},
   "source": [
    "```python\n",
    "def stream(\n",
    "    self,\n",
    "    input: Union[dict[str, Any], Any],  # 图中的输入，从状态中取值\n",
    "    config: Optional[RunnableConfig] = None,\n",
    "    *,\n",
    "    stream_mode: Optional[Union[StreamMode, list[StreamMode]]] = None,\n",
    "    output_keys: Optional[Union[str, Sequence[str]]] = None,  # 流媒体的键，默认为所有非上下文通道。\n",
    "    interrupt_before: Optional[Union[All, Sequence[str]]] = None,  # 中断之前的节点，默认为图中的所有节点。\n",
    "    interrupt_after: Optional[Union[All, Sequence[str]]] = None,   # 中断之后的节点，默认为图中的所有节点。\n",
    "    debug: Optional[bool] = None,   #  执行过程中是否打印调试信息，默认为False。\n",
    "    subgraphs: bool = False,  # 是否流式传输子图\n",
    ") -> Iterator[Union[dict[str, Any], Any]]:\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a4f2ed1-4c92-4f7a-ae6b-4893ecda82a2",
   "metadata": {},
   "source": [
    "- **values ：在图表的每个步骤之后流式传输状态的完整值。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 237,
   "id": "b643a981-ba97-4138-b630-34f5e08c5593",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "你好，南京现在的天气怎么样？\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  query_weather_from_db (call_xfRWSscdr2S6Fgo1GnxAQUFk)\n",
      " Call ID: call_xfRWSscdr2S6Fgo1GnxAQUFk\n",
      "  Args:\n",
      "    city_name: Nanjing\n",
      "None\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: query_weather_from_db\n",
      "\n",
      "{\"messages\": [\"未找到城市 'Nanjing' 的天气信息。\"]}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  get_weather (call_Wd4DNds56FewTYwx7Z6SEBnw)\n",
      " Call ID: call_Wd4DNds56FewTYwx7Z6SEBnw\n",
      "  Args:\n",
      "    loc: Nanjing\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather\n",
      "\n",
      "{\"coord\": {\"lon\": 118.7778, \"lat\": 32.0617}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\u6674\", \"icon\": \"01d\"}], \"base\": \"stations\", \"main\": {\"temp\": 18.75, \"feels_like\": 18.12, \"temp_min\": 18.75, \"temp_max\": 18.75, \"pressure\": 1023, \"humidity\": 55, \"sea_level\": 1023, \"grnd_level\": 1020}, \"visibility\": 10000, \"wind\": {\"speed\": 4.89, \"deg\": 77, \"gust\": 6.84}, \"clouds\": {\"all\": 0}, \"dt\": 1730257590, \"sys\": {\"country\": \"CN\", \"sunrise\": 1730240389, \"sunset\": 1730279826}, \"timezone\": 28800, \"id\": 1799962, \"name\": \"Nanjing\", \"cod\": 200}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  insert_weather_to_db (call_rcwMjfSIFM5S1WihC2RD8xIC)\n",
      " Call ID: call_rcwMjfSIFM5S1WihC2RD8xIC\n",
      "  Args:\n",
      "    city_id: 1799962\n",
      "    city_name: Nanjing\n",
      "    main_weather: Clear\n",
      "    description: 晴\n",
      "    temperature: 18.75\n",
      "    feels_like: 18.12\n",
      "    temp_min: 18.75\n",
      "    temp_max: 18.75\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: insert_weather_to_db\n",
      "\n",
      "{\"messages\": [\"天气数据已成功存储至Mysql数据库。\"]}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "南京现在的天气是晴天，气温为18.75°C，体感温度约为18.12°C。空气清新，能见度良好。\n"
     ]
    }
   ],
   "source": [
    "def print_stream(stream):\n",
    "    for sub_stream in stream:\n",
    "        # print(sub_stream)  # 就是上面的示例中非流式直接调用的全部信息\n",
    "        message = sub_stream[\"messages\"][-1]\n",
    "        message.pretty_print()\n",
    "\n",
    "input_message = {\"messages\": [\"你好，南京现在的天气怎么样？\"]}\n",
    "print_stream(graph.stream(input_message, stream_mode=\"values\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee406825-bcf7-4c9a-85e1-880177651b4e",
   "metadata": {},
   "source": [
    "- **updates ：在图中的每个步骤之后将更新流式传输到状态。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 246,
   "id": "91b66439-6c7b-433a-983a-527a64f7faf1",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Ma7bxsvHjIXCXozwmJieVtJJ', 'function': {'arguments': '{\"loc\": \"Tianjin\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_uM3KOsUgIogoBdJbcXwPYEqp', 'function': {'arguments': '{\"loc\": \"Inner Mongolia\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 47, 'prompt_tokens': 347, 'total_tokens': 394, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5cd03389-b67e-416d-a92f-9c02e4ca75b0-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Tianjin'}, 'id': 'call_Ma7bxsvHjIXCXozwmJieVtJJ', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Inner Mongolia'}, 'id': 'call_uM3KOsUgIogoBdJbcXwPYEqp', 'type': 'tool_call'}], usage_metadata={'input_tokens': 347, 'output_tokens': 47, 'total_tokens': 394, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}}\n",
      "{'tools': {'messages': [ToolMessage(content='{\"coord\": {\"lon\": 117.1767, \"lat\": 39.1422}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01d\"}], \"base\": \"stations\", \"main\": {\"temp\": 18.97, \"feels_like\": 18.46, \"temp_min\": 18.97, \"temp_max\": 18.97, \"pressure\": 1025, \"humidity\": 59, \"sea_level\": 1025, \"grnd_level\": 1024}, \"visibility\": 10000, \"wind\": {\"speed\": 7, \"deg\": 140}, \"clouds\": {\"all\": 0}, \"dt\": 1730258008, \"sys\": {\"type\": 1, \"id\": 9619, \"country\": \"CN\", \"sunrise\": 1730241393, \"sunset\": 1730279591}, \"timezone\": 28800, \"id\": 1792947, \"name\": \"Tianjin\", \"cod\": 200}', name='get_weather', id='d667c453-d237-438f-9735-1b6d7854717d', tool_call_id='call_Ma7bxsvHjIXCXozwmJieVtJJ'), ToolMessage(content='{\"cod\": \"404\", \"message\": \"city not found\"}', name='get_weather', id='c81bf119-461b-4956-aab7-4bcfd67dc461', tool_call_id='call_uM3KOsUgIogoBdJbcXwPYEqp')]}}\n",
      "{'agent': {'messages': [AIMessage(content='天津的天气情况如下：\\n- 天气：晴\\n- 温度：18.97°C\\n- 体感温度：18.46°C\\n- 风速：7 m/s\\n- 湿度：59%\\n\\n内蒙古的天气信息暂时无法获取，可能是因为城市名不够具体或者数据暂时不可用。如果你有具体的内蒙古城市名称，可以提供给我以便查询。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 90, 'prompt_tokens': 659, 'total_tokens': 749, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-e89e91d2-14a3-420c-b19d-08e59a57883f-0', usage_metadata={'input_tokens': 659, 'output_tokens': 90, 'total_tokens': 749, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}}\n"
     ]
    }
   ],
   "source": [
    "def print_stream(stream):\n",
    "    for sub_stream in stream:\n",
    "        print(sub_stream)  # 就是上面的示例中非流式直接调用的全部信息\n",
    "\n",
    "input_message = {\"messages\": [\"你好，天津、内蒙现在的天气怎么样？\"]}\n",
    "print_stream(graph.stream(input_message, stream_mode=\"updates\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "488464c6-a476-4662-bca7-78382db3a7dd",
   "metadata": {},
   "source": [
    "- **debug ：在整个图中的执行过程中流式传输尽可能多的信息**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 249,
   "id": "abf2bf2a-b259-4132-b46f-3626fc23216f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'type': 'task', 'timestamp': '2024-10-30T03:14:44.538467+00:00', 'step': 1, 'payload': {'id': 'ec74ada5-a269-f1ab-3344-64ef7aa0cb0d', 'name': 'agent', 'input': {'messages': [HumanMessage(content='你好，天津、内蒙现在的天气怎么样？', additional_kwargs={}, response_metadata={}, id='2b048b3c-6ff7-4576-9e97-f5e1986ae085')], 'is_last_step': False}, 'triggers': ['start:agent']}}\n",
      "{'type': 'task_result', 'timestamp': '2024-10-30T03:14:47.434922+00:00', 'step': 1, 'payload': {'id': 'ec74ada5-a269-f1ab-3344-64ef7aa0cb0d', 'name': 'agent', 'error': None, 'result': [('messages', [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'function': {'arguments': '{\"loc\": \"Tianjin\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'function': {'arguments': '{\"loc\": \"Inner Mongolia\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 47, 'prompt_tokens': 347, 'total_tokens': 394, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6c23f36c-e854-4e3f-beef-04be5ae46041-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Tianjin'}, 'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Inner Mongolia'}, 'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'type': 'tool_call'}], usage_metadata={'input_tokens': 347, 'output_tokens': 47, 'total_tokens': 394, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})])], 'interrupts': []}}\n",
      "{'type': 'task', 'timestamp': '2024-10-30T03:14:47.434922+00:00', 'step': 2, 'payload': {'id': '29849df1-72f9-e753-4982-c0ca4403a93d', 'name': 'tools', 'input': {'messages': [HumanMessage(content='你好，天津、内蒙现在的天气怎么样？', additional_kwargs={}, response_metadata={}, id='2b048b3c-6ff7-4576-9e97-f5e1986ae085'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'function': {'arguments': '{\"loc\": \"Tianjin\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'function': {'arguments': '{\"loc\": \"Inner Mongolia\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 47, 'prompt_tokens': 347, 'total_tokens': 394, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6c23f36c-e854-4e3f-beef-04be5ae46041-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Tianjin'}, 'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Inner Mongolia'}, 'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'type': 'tool_call'}], usage_metadata={'input_tokens': 347, 'output_tokens': 47, 'total_tokens': 394, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})], 'is_last_step': False}, 'triggers': ['branch:agent:should_continue:tools']}}\n",
      "{'type': 'task_result', 'timestamp': '2024-10-30T03:14:48.719462+00:00', 'step': 2, 'payload': {'id': '29849df1-72f9-e753-4982-c0ca4403a93d', 'name': 'tools', 'error': None, 'result': [('messages', [ToolMessage(content='{\"coord\": {\"lon\": 117.1767, \"lat\": 39.1422}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01d\"}], \"base\": \"stations\", \"main\": {\"temp\": 18.97, \"feels_like\": 18.46, \"temp_min\": 18.97, \"temp_max\": 18.97, \"pressure\": 1025, \"humidity\": 59, \"sea_level\": 1025, \"grnd_level\": 1024}, \"visibility\": 10000, \"wind\": {\"speed\": 7, \"deg\": 140}, \"clouds\": {\"all\": 0}, \"dt\": 1730258008, \"sys\": {\"type\": 1, \"id\": 9619, \"country\": \"CN\", \"sunrise\": 1730241393, \"sunset\": 1730279591}, \"timezone\": 28800, \"id\": 1792947, \"name\": \"Tianjin\", \"cod\": 200}', name='get_weather', id='ed06d7f2-4d33-4511-be9c-3325dccdc5de', tool_call_id='call_NaK0YDbmUSm1pSPgmcuaglHl'), ToolMessage(content='{\"cod\": \"404\", \"message\": \"city not found\"}', name='get_weather', id='d4f47099-64d4-4071-8956-515ae231cf79', tool_call_id='call_870GtpjxM8Rgw36N7wIqfRDq')])], 'interrupts': []}}\n",
      "{'type': 'task', 'timestamp': '2024-10-30T03:14:48.719462+00:00', 'step': 3, 'payload': {'id': 'c0a70cf0-9581-d545-c4db-564234efbf12', 'name': 'agent', 'input': {'messages': [HumanMessage(content='你好，天津、内蒙现在的天气怎么样？', additional_kwargs={}, response_metadata={}, id='2b048b3c-6ff7-4576-9e97-f5e1986ae085'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'function': {'arguments': '{\"loc\": \"Tianjin\"}', 'name': 'get_weather'}, 'type': 'function'}, {'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'function': {'arguments': '{\"loc\": \"Inner Mongolia\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 47, 'prompt_tokens': 347, 'total_tokens': 394, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6c23f36c-e854-4e3f-beef-04be5ae46041-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Tianjin'}, 'id': 'call_NaK0YDbmUSm1pSPgmcuaglHl', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'loc': 'Inner Mongolia'}, 'id': 'call_870GtpjxM8Rgw36N7wIqfRDq', 'type': 'tool_call'}], usage_metadata={'input_tokens': 347, 'output_tokens': 47, 'total_tokens': 394, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}), ToolMessage(content='{\"coord\": {\"lon\": 117.1767, \"lat\": 39.1422}, \"weather\": [{\"id\": 800, \"main\": \"Clear\", \"description\": \"\\\\u6674\", \"icon\": \"01d\"}], \"base\": \"stations\", \"main\": {\"temp\": 18.97, \"feels_like\": 18.46, \"temp_min\": 18.97, \"temp_max\": 18.97, \"pressure\": 1025, \"humidity\": 59, \"sea_level\": 1025, \"grnd_level\": 1024}, \"visibility\": 10000, \"wind\": {\"speed\": 7, \"deg\": 140}, \"clouds\": {\"all\": 0}, \"dt\": 1730258008, \"sys\": {\"type\": 1, \"id\": 9619, \"country\": \"CN\", \"sunrise\": 1730241393, \"sunset\": 1730279591}, \"timezone\": 28800, \"id\": 1792947, \"name\": \"Tianjin\", \"cod\": 200}', name='get_weather', id='ed06d7f2-4d33-4511-be9c-3325dccdc5de', tool_call_id='call_NaK0YDbmUSm1pSPgmcuaglHl'), ToolMessage(content='{\"cod\": \"404\", \"message\": \"city not found\"}', name='get_weather', id='d4f47099-64d4-4071-8956-515ae231cf79', tool_call_id='call_870GtpjxM8Rgw36N7wIqfRDq')], 'is_last_step': False}, 'triggers': ['tools']}}\n",
      "{'type': 'task_result', 'timestamp': '2024-10-30T03:14:50.707154+00:00', 'step': 3, 'payload': {'id': 'c0a70cf0-9581-d545-c4db-564234efbf12', 'name': 'agent', 'error': None, 'result': [('messages', [AIMessage(content='天津的天气情况如下：\\n\\n- 天气：晴\\n- 温度：18.97°C\\n- 体感温度：18.46°C\\n- 风速：7 m/s\\n- 湿度：59%\\n\\n很遗憾，无法获取内蒙古的具体天气信息。如果你能提供一个具体的城市名称，我可以为你查询更详细的天气信息。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 82, 'prompt_tokens': 659, 'total_tokens': 741, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-461964bb-3be1-41b2-9766-305990a9ffec-0', usage_metadata={'input_tokens': 659, 'output_tokens': 82, 'total_tokens': 741, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})])], 'interrupts': []}}\n"
     ]
    }
   ],
   "source": [
    "def print_stream(stream):\n",
    "    for sub_stream in stream:\n",
    "        print(sub_stream)  # 就是上面的示例中非流式直接调用的全部信息\n",
    "\n",
    "input_message = {\"messages\": [\"你好，天津、内蒙现在的天气怎么样？\"]}\n",
    "print_stream(graph.stream(input_message, stream_mode=\"debug\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9f87102-bffa-494f-bad8-384a0f7bf5e3",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如果在异步开发环境中，则可以使用`astream`方法来实现流式传输，是专为非阻塞工作流程而设计。可使用的模式和`stream`是一致的，只不过需要调整为异步函数的定义方法，代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 303,
   "id": "cbc96b82-4435-4347-9f72-a347b332e145",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "你好，四川的天气怎么样？\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  query_weather_from_db (call_jElzMBgWJamba4swVQlUZ5IW)\n",
      " Call ID: call_jElzMBgWJamba4swVQlUZ5IW\n",
      "  Args:\n",
      "    city_name: Sichuan\n",
      "<__main__.Weather object at 0x00000222AAA89490>\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: query_weather_from_db\n",
      "\n",
      "{\"city_id\": 1794299, \"city_name\": \"Sichuan\", \"main_weather\": \"Clouds\", \"description\": \"多云\", \"temperature\": 1.67, \"feels_like\": -0.58, \"temp_min\": 1.67, \"temp_max\": 1.67}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "四川目前的天气是多云，气温为1.67°C，体感温度接近-0.58°C。\n"
     ]
    }
   ],
   "source": [
    "async for chunk in graph.astream(input={\"messages\": [\"你好，四川的天气怎么样？\"]}, stream_mode=\"values\"):\n",
    "    message = chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4a6fc1e-0072-445b-a679-e548cb0b0de5",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如果只想得到最终结果，可以使用相同的方法并只跟踪收到的最后一个值，代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 305,
   "id": "2c66c00e-a47f-4ac4-9a77-cf65b21380ad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Weather object at 0x00000222AAD43F90>\n"
     ]
    }
   ],
   "source": [
    "async for chunk in graph.astream(input={\"messages\": [\"你好，四川的天气怎么样？\"]}, stream_mode=\"values\"):\n",
    "    final_result = chunk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 309,
   "id": "6481a55c-b1e3-4792-a7ef-b309bb827cc0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "四川目前的天气是多云。当前气温为1.67°C，体感温度是-0.58°C。\n"
     ]
    }
   ],
   "source": [
    "final_result[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 311,
   "id": "d5d35ee8-da28-4681-9166-1b2b04f48ab8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "接收到的更新节点: 'agent'\n",
      "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ieilOQoqoNZDnjLzDwtSiSGa', 'function': {'arguments': '{\"city_name\":\"Urumqi\"}', 'name': 'query_weather_from_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 346, 'total_tokens': 365, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-98d46758-afbd-4c95-965b-8f44d83fe959-0', tool_calls=[{'name': 'query_weather_from_db', 'args': {'city_name': 'Urumqi'}, 'id': 'call_ieilOQoqoNZDnjLzDwtSiSGa', 'type': 'tool_call'}], usage_metadata={'input_tokens': 346, 'output_tokens': 19, 'total_tokens': 365, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}\n",
      "\n",
      "\n",
      "\n",
      "None\n",
      "接收到的更新节点: 'tools'\n",
      "{'messages': [ToolMessage(content='{\"messages\": [\"未找到城市 \\'Urumqi\\' 的天气信息。\"]}', name='query_weather_from_db', id='0852ae9f-b8a8-4a21-926e-765c7f084400', tool_call_id='call_ieilOQoqoNZDnjLzDwtSiSGa')]}\n",
      "\n",
      "\n",
      "\n",
      "接收到的更新节点: 'agent'\n",
      "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_FA2ZvwygZsZLNgQfWE6zBH8X', 'function': {'arguments': '{\"loc\":\"Urumqi\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 393, 'total_tokens': 409, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-5cd79453-e69d-47b0-a8d8-198be000ad1e-0', tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Urumqi'}, 'id': 'call_FA2ZvwygZsZLNgQfWE6zBH8X', 'type': 'tool_call'}], usage_metadata={'input_tokens': 393, 'output_tokens': 16, 'total_tokens': 409, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}\n",
      "\n",
      "\n",
      "\n",
      "接收到的更新节点: 'tools'\n",
      "{'messages': [ToolMessage(content='{\"coord\": {\"lon\": 87.6005, \"lat\": 43.801}, \"weather\": [{\"id\": 801, \"main\": \"Clouds\", \"description\": \"\\\\u6674\\\\uff0c\\\\u5c11\\\\u4e91\", \"icon\": \"02d\"}], \"base\": \"stations\", \"main\": {\"temp\": 5.53, \"feels_like\": 3.99, \"temp_min\": 5.53, \"temp_max\": 5.53, \"pressure\": 1029, \"humidity\": 81, \"sea_level\": 1029, \"grnd_level\": 926}, \"visibility\": 10000, \"wind\": {\"speed\": 2, \"deg\": 110}, \"clouds\": {\"all\": 20}, \"dt\": 1730260508, \"sys\": {\"type\": 1, \"id\": 9677, \"country\": \"CN\", \"sunrise\": 1730248978, \"sunset\": 1730286202}, \"timezone\": 28800, \"id\": 1529102, \"name\": \"\\\\u00dcr\\\\u00fcmqi\", \"cod\": 200}', name='get_weather', id='57b6b3b8-cfc4-435b-ba94-65380b28cf11', tool_call_id='call_FA2ZvwygZsZLNgQfWE6zBH8X')]}\n",
      "\n",
      "\n",
      "\n",
      "接收到的更新节点: 'agent'\n",
      "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_RiMagTnBGHBCVFdy2ApzG2qt', 'function': {'arguments': '{\"city_id\":1529102,\"city_name\":\"Ürümqi\",\"main_weather\":\"Clouds\",\"description\":\"晴，少云\",\"temperature\":5.53,\"feels_like\":3.99,\"temp_min\":5.53,\"temp_max\":5.53}', 'name': 'insert_weather_to_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 68, 'prompt_tokens': 672, 'total_tokens': 740, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-d3fb79a3-b429-4c47-8d72-19d2901f21dc-0', tool_calls=[{'name': 'insert_weather_to_db', 'args': {'city_id': 1529102, 'city_name': 'Ürümqi', 'main_weather': 'Clouds', 'description': '晴，少云', 'temperature': 5.53, 'feels_like': 3.99, 'temp_min': 5.53, 'temp_max': 5.53}, 'id': 'call_RiMagTnBGHBCVFdy2ApzG2qt', 'type': 'tool_call'}], usage_metadata={'input_tokens': 672, 'output_tokens': 68, 'total_tokens': 740, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}\n",
      "\n",
      "\n",
      "\n",
      "接收到的更新节点: 'tools'\n",
      "{'messages': [ToolMessage(content='{\"messages\": [\"天气数据已成功存储至Mysql数据库。\"]}', name='insert_weather_to_db', id='124ec142-2441-4865-b75b-b597e2023ce1', tool_call_id='call_RiMagTnBGHBCVFdy2ApzG2qt')]}\n",
      "\n",
      "\n",
      "\n",
      "接收到的更新节点: 'agent'\n",
      "{'messages': [AIMessage(content='乌鲁木齐当前的天气情况是：晴，少云。气温为5.53°C，体感温度为3.99°C。天气数据已成功存储到数据库中。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 766, 'total_tokens': 810, 'prompt_tokens_details': {'cached_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014', 'finish_reason': 'stop', 'logprobs': None}, id='run-c551db7f-4bd0-4c06-bbe0-59b98bd89fb9-0', usage_metadata={'input_tokens': 766, 'output_tokens': 44, 'total_tokens': 810, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}\n",
      "\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "inputs = {\"messages\": [(\"human\", \"你好，乌鲁木齐的天气怎么样？\")]}\n",
    "async for chunk in graph.astream(inputs, stream_mode=\"updates\"):\n",
    "    for node, values in chunk.items():\n",
    "        print(f\"接收到的更新节点: '{node}'\")\n",
    "        print(values)\n",
    "        print(\"\\n\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f74a5bb-74b1-44ed-af4b-2afc67db2553",
   "metadata": {},
   "source": [
    "&emsp;&emsp;而如果我们想流式传输每个过程中的 `Tokens`， 代码如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 324,
   "id": "f8c9e92c-80d0-49e5-a860-bf6fa66d2ca9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'name': 'fetch_real_time_info', 'args': {}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': ''}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有城市'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有城市的'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有城市的天气'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有城市的天气数据'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{'name': 'fetch_real_time_info', 'args': {'query': '查询数据库中所有城市的天气数据'}, 'id': 'call_fgn93Rd0qvQrb2g8CSBASi5h', 'type': 'tool_call'}]\n",
      "[{\"title\": \"天气和气象数据网站集合原创 - CSDN博客\", \"link\": \"https://blog.csdn.net/qq_912917507/article/details/104323432\", \"snippet\": \"我们可以使用天气网，全球天气网(www.tianqi.com)提供全国各大城市的历史天气预报查询,历史气温查询,历史天气数据来源于城市当天的天气预报信息。 ... 在IT ...\", \"date\": \"Feb 15, 2020\", \"position\": 1}]|目前|我|无法|直接|从|数据库|中|查询|所有|城市|的|天气|数据|。如果|您|需要|查询|某|个|特|定|城市|的|天气|数据|，请|提供|城市|名称|，我|将|为|您|查询|。|"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import AIMessageChunk, HumanMessage\n",
    "\n",
    "inputs = [HumanMessage(content=\"what is the weather in sf\")]\n",
    "\n",
    "first = True\n",
    "async for msg, metadata in graph.astream({\"messages\": [\"你好，帮我查询一下数据库中都有哪些城市的天气数据\"]}, stream_mode=\"messages\"):\n",
    "    if msg.content and not isinstance(msg, HumanMessage):\n",
    "        print(msg.content, end=\"|\", flush=True)\n",
    "\n",
    "    if isinstance(msg, AIMessageChunk):\n",
    "        if first:\n",
    "            gathered = msg\n",
    "            first = False\n",
    "        else:\n",
    "            gathered = gathered + msg\n",
    "\n",
    "        if msg.tool_call_chunks:\n",
    "            print(gathered.tool_calls)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4d7ebd0-4710-4205-831e-d718c63c0c96",
   "metadata": {},
   "source": [
    "&emsp;&emsp;`astream`中其他的模式大家可以自行尝试，这里不重复进行说明，总体而言，我们要理解的是，同步`stream`和异步`astream`都是流式传输的默认实现，用于流式传输链中的最终输出。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fca23c1-4d88-481b-abca-2145568a0c5c",
   "metadata": {},
   "source": [
    "## 3.2 LangGraph中的事件流"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4255ab2-99e8-4678-9543-11d1c851ccd0",
   "metadata": {},
   "source": [
    "&emsp;&emsp;对于上述使用的`.stream()`或`.astream()`仅流式传输链中最后一步的输出，这对于一些对话聊天类的应用程序来说基本就足够了，但是当我们的`AI Agent`是一个使用了多个大模型调用的更复杂的链时，我们有时希望在最终输出中也使用到一些中间值。例如，在构建RAG对话应用程序时，很多场景都是最终生成的响应 + 检索到的源文档一起返回给用户，例如："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3c8b0f0-ee6c-4452-bbd6-0fd279f7d742",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241030125431836.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fb90e27-b924-4a9a-90df-495b040b4da7",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如果想获取到这样的中间事件和步骤，可以使用`LangGraph`框架中的 `astream_events `方法，注意：此方法仅支持异步。用来访问自定义事件和中间输出。使用该方法运行图时，可以得到如下相关事件："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4e04242-4be1-4e25-80ef-bae79a118f42",
   "metadata": {},
   "source": [
    "> LangChain CallBack：https://python.langchain.com/docs/concepts/callbacks/"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a397012-8e15-4f4e-8512-3ceeaae2fa86",
   "metadata": {},
   "source": [
    "<div align=center><img src=\"https://muyu001.oss-cn-beijing.aliyuncs.com/img/image-20241029114040337.png\" width=100%></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c233eca6-3106-4469-9d1b-7c3bf38a6a9e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;如下代码可以打印包含流式聊天模型输出的事件，其中 version=\"v2\" 参数是指定使用 测试版 API 的版本，现在必须指定。 \r\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 339,
   "id": "dadce5a7-9bb3-4a52-acf3-82a280dde01b",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "on_chain_start: LangGraph\n",
      "on_chain_start: __start__\n",
      "on_chain_end: __start__\n",
      "on_chain_start: agent\n",
      "on_chain_start: call_model\n",
      "on_chain_start: RunnableSequence\n",
      "on_chain_start: StateModifier\n",
      "on_chain_end: StateModifier\n",
      "on_chat_model_start: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_stream: ChatOpenAI\n",
      "on_chat_model_end: ChatOpenAI\n",
      "on_chain_end: RunnableSequence\n",
      "on_chain_end: call_model\n",
      "on_chain_start: _write\n",
      "on_chain_end: _write\n",
      "on_chain_start: should_continue\n",
      "on_chain_end: should_continue\n",
      "on_chain_stream: agent\n",
      "on_chain_end: agent\n",
      "on_chain_stream: LangGraph\n",
      "on_chain_end: LangGraph\n"
     ]
    }
   ],
   "source": [
    "async for event in graph.astream_events({\"messages\": [\"你好，请你介绍一下你自己\"]}, version=\"v2\"):\n",
    "    kind = event[\"event\"]\n",
    "    print(f\"{kind}: {event['name']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfdf3b30-0fde-4a82-a414-2e75809ccf23",
   "metadata": {},
   "source": [
    "&emsp;&emsp;这个过程明确标识了`Agent`执行的每个阶段。从`on_chain_start: LangGraph `开始，写入`__start__`节点，启动`call_model`节点（ on_chain_start: call_model ）。然后开始聊天模型调用（ on_chat_model_start: ChatOpenAI ), 按`token`的增量流式返回 ( on_chat_model_stream: ChatOpenAI ），直到聊天模型（ on_chat_model_end: ChatOpenAI ）输出完全部内容后停止。继而将结果写回通道（ ChannelWrite<call_model,messages> ），再次回到`call_model`节点做决策，最终完成整个图的运行流程。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b491e5a3-e04a-45bd-adbd-cea65e466622",
   "metadata": {},
   "source": [
    "&emsp;&emsp;我们可以从中提取具体的某个 `event`（事件），比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 345,
   "id": "cb9afb24-7fb0-4f5f-8324-3d90c40f0a0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "events = []\n",
    "async for event in graph.astream_events({\"messages\": [\"你好，请你介绍一下你自己\"]}, version=\"v2\"):\n",
    "    events.append(event)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 358,
   "id": "9275af64-24d5-46fd-bb99-4276dc430cb4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'event': 'on_chain_start',\n",
       " 'data': {'input': {'messages': ['你好，请你介绍一下你自己']}},\n",
       " 'name': 'LangGraph',\n",
       " 'tags': [],\n",
       " 'run_id': '82a8cc1d-4607-47bc-88e5-cf4027857fd0',\n",
       " 'metadata': {},\n",
       " 'parent_ids': []}"
      ]
     },
     "execution_count": 358,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "events[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 360,
   "id": "a2321c00-63da-4465-9467-d68ed780e4ff",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'event': 'on_chat_model_stream',\n",
       " 'data': {'chunk': AIMessageChunk(content='您好', additional_kwargs={}, response_metadata={}, id='run-fd2157b0-6ff4-40df-8749-84deab32adbd')},\n",
       " 'run_id': 'fd2157b0-6ff4-40df-8749-84deab32adbd',\n",
       " 'name': 'ChatOpenAI',\n",
       " 'tags': ['seq:step:2'],\n",
       " 'metadata': {'langgraph_step': 1,\n",
       "  'langgraph_node': 'agent',\n",
       "  'langgraph_triggers': ['start:agent'],\n",
       "  'langgraph_path': ('__pregel_pull', 'agent'),\n",
       "  'langgraph_checkpoint_ns': 'agent:b43bb514-1964-53fd-a747-94d646ccd17c',\n",
       "  'checkpoint_ns': 'agent:b43bb514-1964-53fd-a747-94d646ccd17c',\n",
       "  'ls_provider': 'openai',\n",
       "  'ls_model_name': 'gpt-4o',\n",
       "  'ls_model_type': 'chat',\n",
       "  'ls_temperature': 0.7},\n",
       " 'parent_ids': ['82a8cc1d-4607-47bc-88e5-cf4027857fd0',\n",
       "  '4d367cb2-9d0a-4d5b-9f31-b0295ea3fce1',\n",
       "  'aa31ad82-b577-4b8c-bbb9-cb8879b42dda',\n",
       "  'aa2387eb-9371-4e02-a238-bb7fd8185cba']}"
      ]
     },
     "execution_count": 360,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "events[10]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1be8087-6205-4fdb-b256-70fd921daaaa",
   "metadata": {},
   "source": [
    "&emsp;&emsp;所有事件都会包含`event` 、 `name`和`data`字段，其中：\n",
    "- event ：正在发出的事件类型。\n",
    "- name ：这是事件的名称\n",
    "- data ：这是与事件关联的数据。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52e1b958-2eb0-4e79-9a08-784087c45ee6",
   "metadata": {},
   "source": [
    "&emsp;&emsp;基于此就可以按照`name`、`tags`或`type`等不同的字段来进行事件过滤，比如我们现在选择仅包含聊天模型的输出："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 367,
   "id": "d611ab76-b98c-416a-9c0c-f808620de149",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='你好', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='！', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='我是', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='一个', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='由', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='人工', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='智能', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='驱', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='动', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='的', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='助手', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='，可以', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='帮助', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='回答', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='问题', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='、', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='提供', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='信息', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='、', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='进行', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='任务', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='自动', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='化', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='等', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='。我', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='可以', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='处理', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='多', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='种', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='语言', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='的', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='文本', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='，并', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='能', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='与', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='各种', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='应用', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='程序', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='和', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='工具', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='进行', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='集', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='成', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='，以', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='满足', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='不同', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='的', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='需求', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='。如果', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='你', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='有', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='任何', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='问题', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='或者', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='需要', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='帮助', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='，', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='随', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='时', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='告诉', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='我', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='！', additional_kwargs={}, response_metadata={}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|{'event': 'on_chat_model_stream', 'data': {'chunk': AIMessageChunk(content='', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_72bbfa6014'}, id='run-599ff39e-910f-48a7-a87b-eaabd1154d29')}, 'run_id': '599ff39e-910f-48a7-a87b-eaabd1154d29', 'name': 'ChatOpenAI', 'tags': ['seq:step:2'], 'metadata': {'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_path': ('__pregel_pull', 'agent'), 'langgraph_checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'checkpoint_ns': 'agent:79d639f6-6c89-f63f-9d09-4c0be3bd44d7', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.7}, 'parent_ids': ['c1ebffad-910f-4fe7-914f-832bf6967cad', '85a346c6-9632-4039-902b-abad71845000', 'e7c1e9cf-b472-4e13-bdac-197b1981df38', 'b7763813-9d0b-488c-b6bf-cc035dacad80']}|"
     ]
    }
   ],
   "source": [
    "async for event in graph.astream_events({\"messages\": [\"你好，请你介绍一下你自己\"]}, version=\"v2\"):\n",
    "    kind = event[\"event\"]\n",
    "    if kind == \"on_chat_model_stream\":\n",
    "        print(event, end=\"|\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5508aaaf-ec6c-49f9-af51-bf5b1de86766",
   "metadata": {},
   "source": [
    "&emsp;&emsp;每种类型的事件都包含不同格式的数据。而其中`data`是一个非常重要的，包含此事件的实际数据。在`on_chat_model_stream`事件中，就是需要响应的流式`Token`，如上图所示是一个 `AIMessageChunk`，其中包含消息的`content`以及`id` ,提取的代码就非常简单了，和我们上面实现的方式一致，即直接采用如下代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 372,
   "id": "f1a6c28b-55f4-4d50-924a-9295b4bba43a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好|！|我是|一个|由|人工|智能|驱|动|的|助手|，|旨|在|帮助|回答|各种|问题|和|执行|特|定|任务|。我|能够|进行|自然|语言|处理|，|提供|信息|查询|、|天气|预|报|、|简单|计算|等|服务|。如果|你|有|任何|问题|或|需要|帮助|，|随|时|可以|告诉|我|！|"
     ]
    }
   ],
   "source": [
    "first = True\n",
    "async for msg, metadata in graph.astream({\"messages\": [\"你好，请你介绍一下你自己\"]}, stream_mode=\"messages\"):\n",
    "    if msg.content and not isinstance(msg, HumanMessage):\n",
    "        print(msg.content, end=\"|\", flush=True)\n",
    "\n",
    "    if isinstance(msg, AIMessageChunk):\n",
    "        if first:\n",
    "            gathered = msg\n",
    "            first = False\n",
    "        else:\n",
    "            gathered = gathered + msg\n",
    "\n",
    "        if msg.tool_call_chunks:\n",
    "            print(gathered.tool_calls)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d485b8a7-d747-4d70-bc58-3c8532ce0949",
   "metadata": {},
   "source": [
    "&emsp;&emsp;stream_mode=\"messages\"模式是直接做的格式化提取的实现过程，当然，理解了上述事件流，我们也可以直接在当前的流程下自定义数据流，比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 382,
   "id": "5867047a-ba03-4b28-b860-0ad8363d0c85",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|你好|！|我是|一个|由|人工|智能|驱|动|的|助手|，|旨|在|帮助|回答|问题|、|提供|信息|和|执行|任务|。|我的|能力|包括|自然|语言|理解|、|信息|检|索|、|数据|处理|等等|。如果|你|有|任何|问题|或者|需要|帮助|，|随|时|可以|问|我|！||"
     ]
    }
   ],
   "source": [
    "async for event in graph.astream_events({\"messages\": [\"你好，请你介绍一下你自己\"]}, version=\"v2\"):\n",
    "    kind = event[\"event\"]\n",
    "    if kind == \"on_chat_model_stream\":\n",
    "        print(event[\"data\"][\"chunk\"].content, end=\"|\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e114d6b6-20ab-457b-9bc5-9f8f33987d3e",
   "metadata": {},
   "source": [
    "&emsp;&emsp;由此可见，在处理事件流中的信息时，我们可以根据实际需求灵活地选择输出和展示的内容格式。这种灵活性正是在复杂业务流程中引入事件流的核心原因。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea2d58e7-4569-47b6-8d17-829e0424541b",
   "metadata": {},
   "source": [
    "&emsp;&emsp;至此，我们就完整实现了在`LangGraph`中`ReAct`自治代理的完整构建，对于这个预构建的`ReAct`组件，它是集成了外部工具、记忆和规划三个核心概念，所以除了我们可以自定义外部工具以外，还可以给它制定不同的步骤规划方式，即`Planning`，以及通过`Memory`去赋予`Agent`多轮对话的能力，而这两部分的内容，我们将在下一节课程中展开详细的探讨和实践。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "agent",
   "language": "python",
   "name": "agent"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
