LangGraph 入门教程:构建 AI 工作流 [ 案例二 ]
【案例二】支持搜索的智能代理系统
接下来我们上手第二个实战案例 ——支持搜索的智能代理系统,我们要把这套系统搭建完成,并且将它规范定义为LangGraph图示化工作流架构。
案例介绍
案例一只是为了演示什么是Graph。对于LangGraph,实际上是要调用LLM来完成智能应用系统。因此该案例会将使用Graph API,来完成一个支持搜索、支持调用LLM的智能代理系统。核心功能如下:
- 智能对话与工具调用:基于聊天模型,能够理解用户问题并决定是否需要调用搜索工具
- 自动搜索整合:通过
Tavily搜索工具获取实时信息,并将搜索结果整合到回答中 - 循环决策机制:能够多次调用工具和模型,直到获得满意答案
我们本次依旧使用Tavily搜索工具,这个工具在 LangChain 中已经详细讲解过基础用法,而 LangChain 里的工具、模型等基础组件,都可以直接复用在LangGraph中。和之前 LangChain 链式调用不同,本次核心是把知识搜索系统改造成图结构工作流的方式来编写代码,理解其图示化的构建逻辑。
流程设置如下:
根据实际自行拓展,例如我们可以用它来:
实时信息查询:查询最新新闻、股价、天气等
今天特斯拉的最新股价是多少?还有哪些重要的科技新闻?
事实核查:验证信息的准确性
有人说阿波罗登月是伪造的,请提供证据来验证登月的真实性
深度研究:多轮搜索获取全面信息
我需要关于 ' 人工智能在医疗诊断中的应用 ' 的最新研究论文、临床试验结果和专家观点,请提供全面的综述
知识扩展:补充模型知识库之外的信息
GPT-4o-mini 的训练数据截止到什么时候?之后有哪些重要的 AI 发展事件?
编码思路
构建Graph图,首先需要定义状态,然后定义并添加节点和边,最后编译它。编译提供了对图形结构的一些基本检查(没有孤立节点等)。
正式编码前,我们先梳理整套业务流程:第一步是用户输入提问,我们可以准备两类问题做对比,一类是需要实时数据的股价、天气类问题,一类是1+1等于几这类常识简单问题。
输入问题后,交由大模型自主判断是否需要调用搜索工具:像股价、天气这类有时效性的问题,大模型训练数据存在时间截止,无法给出最新结果,必须触发网络搜索;而基础常识类问题,大模型可直接作答,无需调用工具。
如果判定需要搜索,就调用Tavily搜索工具执行检索,工具调用完成后会生成ToolMessage;再由大模型结合用户原始问题与工具返回的检索结果,做内容整合,生成最终的AIMessage给到用户。结果整合完成后,会再次进入判断逻辑:若已有完整答案,就直接结束流程输出结果;若仍需补充信息,则继续循环检索、整合,直到给出最终答案。
在正式定义 LangGraph 节点和流程前,我们先回顾 LangChain 的消息规范体系:用户输入对应HumanMessage,交给绑定工具的大模型后,会返回两种AIMessage:
一种不带工具调用参数,直接给出最终答案;
另一种携带tool_calls工具调用参数,代表需要执行搜索工具。
一旦返回带工具调用的AIMessage,就必须执行工具调用,工具执行后产出ToolMessage;注意ToolMessage不能直接返回给用户,需要把HumanMessage、带工具调用的AIMessage、ToolMessage整条消息列表再次交给大模型,由大模型整合生成最终的AIMessage,这才是呈现给用户的最终回复。
之前用 LangChain 实现工具调用时,最大的痛点是需要手动维护历史消息列表:要手动把AIMessage、ToolMessage依次追加到消息记录中,多轮调用模型、手动管理消息,写法繁琐且不优雅。而LangGraph完美解决了这个问题:我们可以在状态 State中定义一个消息列表messages,配置追加合并策略,框架会自动帮我们维护对话历史,无需手动逐条添加消息,写法简洁优雅,还能完整留存对话上下文。
除此之外,我们还可以在状态中额外定义llm_calls整型字段,用来记录大模型调用次数,方便监控计费、统计模型调用频次。在业务系统中,状态里定义messages消息列表几乎是标配,核心优势有三点:
- 完整对话记忆:全程留存所有聊天消息,实现多轮对话上下文记忆;
- 全局上下文维护:任意节点都能读取完整历史消息,随时调用上下文信息;
- 状态持久化:图流程流转过程中,对话状态不会丢失,自动追加更新消息记录。
梳理完消息规范和状态设计后,我们再来精简设计LangGraph 节点:整套系统只需要两个核心节点就可以完成全部流程,无需拆分过多精细节点。
设置 Nodes
根据节点的单一职责特性,即每个节点只做一件事,我们可以设置:
第一个是大模型调用节点:负责接收历史消息、调用大模型,返回带工具调用或不带工具调用的两种AIMessage;
第二个是工具执行节点:专门解析工具调用参数、执行 Tavily 搜索,生成并返回ToolMessage。
即:
- 节点 1(
tool_node):专门负责搜索,获取搜索结果。 - 节点 2(
llm_call):专门负责调用LLM,获取最终结果。
根据节点的独立性特性,即每个节点间不直接通信,而是通过State交互。它们接收 State,返回 State 的更新。
设置 State
根据状态的共享性特性,即所有节点都能读取和修改状态。LangGraph在许多情况下,将以前的对话历史记录存储为 State 中的消息列表会很有帮助,这样就能通过 State 中的消息列表来跟踪整个对话的完整历史,这是构建对话系统的关键。
为此,我们可以在图状态中添加一个messages键,如下所示:
from langchain.messages import AnyMessage from typing_extensions import TypedDict, Annotated import operator class MessagesState(TypedDict): # 类型: list[AnyMessage] - 任意消息对象的列表 # 合并策略: operator.add - 使用加法操作符进行状态合并 # 效果: 当状态更新时,新的消息会追加到现有列表中,而不是替换 messages: Annotated[list[AnyMessage], operator.add] # 类型: int - 整数值 # 用途: 跟踪LLM(大语言模型)的调用次数 llm_calls: int注意Annotated[list[AnyMessage], operator.add]这个注解特别重要:
operator.add表示新消息会追加到列表中,而不是替换;- 这确保了对话历史的连续性。
messages的具体作用:
对话记忆
# 存储完整的对话流程 messages = [ HumanMessage(content="你好"), AIMessage(content="你好! 我是AI助手"), HumanMessage(content="什么是机器学习? "), AIMessage(content="机器学习是...") ]上下文维护
当专门负责调用LLM的节点(llm_call节点)需要调用 LLM 时,可以通过 State 将整个messages历史作为上下文:
# LLM 基于完整的对话历史生成回复 response = llm.invoke(state["messages"])状态持久化
State 中的messages字段确保在图的各个节点之间传递时,对话状态不会丢失:
def node(state: MessagesState): # 可以访问完整的对话历史 all_messages = state["messages"] latest_message = state["messages"][-1] # 处理并添加新消息 return {"messages": [new_ai_message]}因此,没有这个字段,系统就无法记住之前的对话内容,每次都会像第一次对话一样。
设置 Edges
节点确定后,再设计工作流的边逻辑:包含固定边和条件边。
- 固定边:流程起始节点固定走向大模型调用节点;工具执行节点执行完搜索后,固定回流到大模型调用节点,重新整合结果生成最终答案。
- 条件边:以大模型调用节点为起点做分支判断 —— 如果返回的
AIMessage带有tool_calls参数,就路由到工具节点执行搜索;如果没有工具调用参数,说明已生成最终答案,直接走向结束节点。
对于流程设置如下所示:
且已经定义了两个节点:
- 节点 1(
tool_node):专门负责搜索,获取搜索结果。 - 节点 2(
llm_call):专门负责调用LLM,获取最终结果。
因此,可以定义以下逻辑:
对于开始逻辑:是当用户进行输入后,直接让LLM进行处理。是否需要搜索工具调用,也是通过LLM进行判断。因此图的入口点也是llm_call节点。
对于结束逻辑:可以根据LLM返回的结构判断是否调用工具,来决定我们是应该继续循环还是停止循环:
- 如果LLM要调用工具,则进入
tool_node节点进行处理; - 如果LLM不要调用工具,则结束。
最终Graph效果如下图所示:
整套 LangGraph 工作流逻辑非常清晰:用户输入问题进入流程 → 进入大模型节点判断是否需要搜索 → 需要搜索则走工具节点执行检索,再回流大模型整合结果 → 无需搜索则直接结束流程输出答案。
最后总结代码实现的标准五步流程:
- 第一步自定义图状态 State;
- 第二步定义两大核心业务节点;
- 第三步创建 StateGraph 图结构并添加节点;
- 第四步配置固定边与条件边;
- 第五步编译图、调用执行并测试效果。
只要吃透 LangChain 消息规范、理解三种消息类型的生成逻辑,就能轻松完成节点与流程设计,多实操几次就能熟练掌握 LangGraph 图示化系统的搭建思路。
代码实现
步骤 1:准备工作,定义聊天模型和搜索工具
# 步骤 1: 定义工具和模型 from langchain.chat_models import init_chat_model from langchain_tavily import TavilySearch search = TavilySearch(max_results=4) tools = [search] # 绑定工具 model = init_chat_model("gpt-4o-mini", temperature=0) model_with_tools = model.bind_tools(tools)步骤 2:定义状态
# 步骤 2: 定义状态 from langchain.messages import AnyMessage from typing_extensions import TypedDict, Annotated import operator class MessagesState(TypedDict): # 类型: list[AnyMessage] - 任意消息对象的列表 # 合并策略: operator.add - 使用加法操作符进行状态合并 # 效果: 当状态更新时,新的消息会追加到现有列表中,而不是替换 messages: Annotated[list[AnyMessage], operator.add] # 类型: int - 整数值 # 用途: 跟踪LLM(大语言模型)的调用次数 llm_calls: int步骤 3:定义模型节点
由于节点不需要返回整个状态模式,只需一个更新。且operator.add表示新消息会追加到列表中,而不是替换。因此模型节点代码如下:
# 步骤 3: 定义模型节点 from langchain.messages import SystemMessage def llm_call(state: dict): """ LLM决定是否调用工具的函数节点 这个函数是LangGraph工作流中的一个节点,负责: 1. 接收当前状态(包含对话历史) 2. 调用绑定了工具的LLM模型 3. 让模型决定是否需要使用工具来回答用户问题 4. 返回更新后的状态 Args: state (dict): 当前的工作流状态,包含以下键: - "messages": 对话历史消息列表 - "llm_calls": 记录LLM被调用的次数(可选) Returns: dict: 更新后的状态,包含: - "messages": 添加了LLM响应后的消息列表 - "llm_calls": 调用次数加1后的计数 """ # 调用绑定了工具的LLM模型 # model_with_tools 是在之前步骤中创建的、绑定了工具列表的模型实例 # invoke() 方法会执行模型推理,让模型分析当前对话并决定: # - 如果需要额外信息,模型会生成一个工具调用请求 # - 如果可以直接回答,模型会生成文本回复 response = model_with_tools.invoke( # 构建输入消息列表,按照 LangChain 的标准格式 [ # 系统消息:设置模型的角色和行为准则 # 这个SystemMessage会告诉模型它的身份和可以使用的工具 SystemMessage( content="你是一个乐于助人的助手,支持调用工具进行搜索。" # 可以在这里添加更多系统指令,比如: # - "搜索时请优先使用中文关键词" # - "如果没有找到相关信息,请诚实告知用户" ) ] # 加上当前的对话历史 # state["messages"] 包含了用户和助手之间的所有历史对话 # 这些消息的类型可能是:HumanMessage(用户)、AIMessage(助手)、ToolMessage(工具结果) + state["messages"] ) # 返回更新后的状态 return { # 更新消息列表:将LLM生成的响应追加到现有消息列表后面 # 这个响应可能是文本回复,也可能是工具调用请求 "messages": [response], # LangGraph会自动将这条消息与state["messages"]合并 # 更新调用计数器:记录LLM被调用的次数 # state.get('llm_calls', 0) 获取当前的调用次数,如果没有则默认为0 # + 1 表示本次调用增加一次计数 "llm_calls": state.get('llm_calls', 0) + 1 }步骤 4:定义工具节点
要点 1:回顾 AIMessage 消息结构
在学习 LangChain 篇章时,我们便知道当 LLM 需要调用工具时,LLM 输出结果AIMessage的结构如下所示:
AIMessage( content='', additional_kwargs={'refusal': None}, response_metadata={ ... }, id='lc_run--30b6a3d7-1bd0-4093-8a36-9b43bea458fc-0', tool_calls=[ { 'name': 'tavily_search', 'args': { 'query': '西安天气', 'search_depth': 'basic' }, 'id': 'call_XAa0MF8j9YQp6FK28tqIvpB', 'type': 'tool_call' } ], usage_metadata={ ... } )其中包含一个tool_calls属性。此属性包括执行工具所需的一切,包括工具名称和输入参数。有了这些内容,便可以进行工具调用。
要点 2:构造ToolMessage
仅仅成功调用工具还不行,我们需要将工具的返回构造成ToolMessage,再传输给聊天模型。才能给我们返回真正需要的答案:
- 将工具输出传递给聊天模型,包括
HumanMessage、AIMessage(工具调用)、ToolMessage - 聊天模型根据以上消息列表的输入,将最终结果
AIMessage返回。
可以使用下面这种方式定义ToolMessage:
# ToolMessage 的创建和使用示例 # 导入必要的类 from langchain_core.messages import ToolMessage # 创建 ToolMessage 对象 tool_message = ToolMessage( content=工具的返回, # 工具执行后返回的结果内容 tool_call_id=tool_calls[n]["id"] # 对应工具调用的唯一标识符 )要点 3:在 State 中访问messages
由于 LangChain 允许传输下面这种格式的消息:
{ "messages": [ { "type": "human", "content": "message" } ] }但 State 中对于 message 的更新,总是会反序列化为 LangChain Message 格式!如将上述格式反序列化为下面这种格式:
{ "messages": [ HumanMessage(content="message") ] }因此,应该使用点表示法来访问消息属性,例如要访问 AIMessage 中的tool_calls属性时,应使用state["messages"][-1].tool_calls来获取。
因此,定义工具节点完整代码如下:
# 步骤 4: 定义工具节点 from langchain.messages import ToolMessage # 创建一个字典,将工具名称映射到工具对象本身 # 这样可以快速根据工具名称找到对应的工具实例 # 格式:{"工具名称1": 工具对象1, "工具名称2": 工具对象2, ...} # 例如:{"tavily_search": TavilySearch对象, "calculator": Calculator对象} tools_by_name = {tool.name: tool for tool in tools} def tool_node(state: dict): """ 执行工具调用的节点函数 这个函数负责: 1. 从状态中提取LLM请求的工具调用信息 2. 根据工具名称找到对应的工具实例 3. 执行工具并获取执行结果 4. 将结果包装成ToolMessage格式返回 工作流程: LLM节点 → 生成tool_calls → 工具节点 → 执行工具 → 返回ToolMessage → 回到LLM节点 Args: state (dict): 当前的工作流状态,必须包含: - "messages": 消息列表,最后一条消息应该是包含tool_calls的AIMessage - AIMessage.tool_calls: 包含多个工具调用请求的列表 Returns: dict: 更新后的状态,包含: - "messages": ToolMessage列表,每个ToolMessage对应一个工具执行结果 Example: 输入state示例: { "messages": [ AIMessage( content="", tool_calls=[ { "id": "call_123", "name": "tavily_search", "args": {"query": "人工智能新闻"} }, { "id": "call_456", "name": "calculator", "args": {"expression": "100 * 0.15"} } ] ) ] } 输出state示例: { "messages": [ ToolMessage(content="搜索到的新闻内容...", tool_call_id="call_123"), ToolMessage(content="15.0", tool_call_id="call_456") ] } """ # 初始化结果列表,用于存储所有工具执行的结果 # 每个结果都会被包装成一个ToolMessage对象 result = [] # 获取最后一条消息(应该是包含tool_calls的AIMessage) # state["messages"][-1] 访问消息列表的最后一项 last_message = state["messages"][-1] # 遍历LLM请求中的所有工具调用 # last_message.tool_calls 是一个列表,可能包含0个、1个或多个工具调用请求 # 每个tool_call的格式: # { # "id": "唯一标识符", # 用于匹配工具调用和结果 # "name": "工具名称", # 对应tools_by_name中的键 # "args": {"参数名": "参数值"} # 传递给工具的参数 # } for tool_call in last_message.tool_calls: # 步骤1: 根据工具名称获取对应的工具实例 # tools_by_name[tool_call["name"]] 快速找到工具对象 # 例如:tool_call["name"] = "tavily_search" # 那么 tool = TavilySearch实例 tool = tools_by_name[tool_call["name"]] # 步骤2: 执行工具,传入参数 # tool.invoke(tool_call["args"]) 调用工具并传入参数 # 例如:search_tool.invoke({"query": "人工智能新闻"}) # observation 是工具返回的原始结果(字符串、字典或其他格式) observation = tool.invoke(tool_call["args"]) # 步骤3: 将工具执行结果包装成ToolMessage # ToolMessage的作用: # - 标准化工具返回结果的格式 # - 通过tool_call_id将结果与请求关联 # - 让LLM能够理解这是工具的执行结果 tool_message = ToolMessage( content=observation, # 工具返回的具体内容 tool_call_id=tool_call["id"] # 重要!必须与请求中的id匹配 ) # 步骤4: 将ToolMessage添加到结果列表 result.append(tool_message) # 返回更新后的状态 # {"messages": result} 会如何处理? # 在LangGraph中,返回的消息会自动与state中的现有消息合并 # 最终state["messages"]会变成:原来的消息 + result中的ToolMessages return {"messages": result}步骤 5:构建图,设置节点与边
对于开始逻辑:是当用户进行输入后,直接让 LLM 进行处理。是否需要搜索工具调用,也是通过 LLM 进行判断。因此图的入口点则是llm_call节点。
对于结束逻辑:可以根据 LLM 返回的结构判断是否调用工具,来决定我们是应该继续循环还是停止循环:
- 如果 LLM 要调用工具,则进入
tool_node节点进行处理; - 如果 LLM 不要调用工具,则结束。
# 步骤 5: 构建图 from langgraph.graph import StateGraph, START, END from typing import Literal, List def should_continue(state: MessagesState) -> Literal["tool_node", END]: """ 条件路由函数:根据LLM的响应决定下一步执行路径 这个函数是LangGraph中的条件边(conditional edge)的核心组件, 它根据当前状态判断工作流应该走向哪个节点。 工作流程: 1. LLM节点执行后,会产生响应(可能包含tool_calls) 2. 这个函数检查LLM的响应 3. 如果LLM请求调用工具 → 路由到工具执行节点 4. 如果LLM直接回答了问题 → 结束流程 Args: state (MessagesState): 当前工作流状态,包含消息列表 state["messages"] 中的最后一条消息通常是LLM的响应 Returns: Literal["tool_node", END]: 返回下一个节点的名称 - "tool_node": 表示需要执行工具 - END: 表示流程结束(LangGraph内置常量,表示终止) Example: 场景1 - 需要工具: state["messages"][-1] = AIMessage( tool_calls=[{"name": "search", "args": {...}}] ) → 返回 "tool_node" 场景2 - 不需要工具: state["messages"][-1] = AIMessage(content="这是最终答案") → 返回 END """ # 从状态中获取消息列表 messages = state["messages"] # 获取最后一条消息(应该是LLM节点的响应) # 在LangGraph中,节点返回的消息会自动追加到消息列表末尾 last_message = messages[-1] # 检查LLM是否请求调用工具 # tool_calls 属性在AIMessage中定义,当LLM绑定了工具并决定使用时, # 这个属性会包含一个列表,每个元素是一个工具调用请求 # 格式示例: # last_message.tool_calls = [ # { # "id": "call_abc123", # "name": "tavily_search", # "args": {"query": "人工智能新闻"} # } # ] if last_message.tool_calls: # 如果LLM请求使用工具,则路由到工具节点 # 工具节点会执行这些工具调用并返回结果 return "tool_node" # 如果LLM没有请求工具(即直接生成了答案),则结束流程 # END 是LangGraph内置常量,表示工作流终止 return END # 创建状态图构建器 # StateGraph 是LangGraph的核心类,用于定义状态机的工作流 # 泛型参数 MessagesState 指定了状态的数据结构 agent_builder = StateGraph(MessagesState) # ===== 添加节点 ===== # 节点是工作流中的处理单元,每个节点都是一个可调用对象(函数) # 节点接收当前状态,返回更新后的状态 # 添加LLM调用节点 # 这个节点负责调用大语言模型,决定是否需要工具 agent_builder.add_node("llm_call", llm_call) # 添加工具执行节点 # 这个节点负责执行LLM请求的工具调用 agent_builder.add_node("tool_node", tool_node) # ===== 定义流程边 ===== # 1. 添加起始边 # START 是LangGraph内置常量,表示工作流的入口点 # 从START连接到"llm_call"节点,意味着工作流开始时自动执行llm_call agent_builder.add_edge(START, "llm_call") # 2. 添加条件边(最重要的部分) # 条件边允许根据状态动态决定下一个节点,实现循环和控制流 agent_builder.add_conditional_edges( # 参数1: source (源节点) # 指定从哪个节点出发,这里是从"llm_call"节点出发 "llm_call", # 参数2: path (路由函数) # 指定用于判断下一个节点的函数 # should_continue 函数会检查LLM响应,返回下一个节点的名称 should_continue, # 参数3: path_map (路径映射,可选) # 明确指定路由函数可能返回的所有节点名称 # 这有助于类型检查和错误验证,不是必须的,但推荐提供 [ "tool_node", # 如果需要执行工具,路由到这个节点 END # 如果已完成,路由到结束 ] ) # 3. 添加普通边 # 从工具节点连接回LLM节点,形成循环 # 工作流程:LLM → 工具 → LLM → (可能再次工具) → 最终结束 agent_builder.add_edge("tool_node", "llm_call") # 这条边的作用: # - 工具节点执行完毕后,将结果(ToolMessages)返回给LLM # - LLM会理解工具结果,然后决定: # a) 如果信息足够,生成最终答案(不再调用工具) # b) 如果还需要更多信息,继续调用其他工具 # ===== 编译图 ===== # compile() 方法将图定义转换为可执行的工作流 # 编译过程会进行验证: # - 检查所有节点是否都已添加 # - 检查边是否形成有效的路径 # - 确保没有孤立节点 # - 验证条件边的返回值有效 agent = agent_builder.compile() # 编译后得到的 agent 是一个可调用对象,可以: # - agent.invoke(state) - 同步执行整个工作流 # - agent.stream(state) - 流式执行,逐步返回结果 # - agent.ainvoke(state) - 异步执行步骤 6:可视化图
我们可以通过 Mermaid 图表,来展示出 Graph 效果。Mermaid 是一种使用文本生成流程图、饼状图、甘特图等图表的描述语言,它可以帮助用户以简单、直观的方式创建各种类型的图表,包括流程图、时序图、甘特图等。Mermaid 在线绘图工具:https://www.yyshare.com/front-end/9729/
使用姿势:
- LangGraph 中,编译后的图提供了获取 Graph 可绘制表示的方法:
get_graph(),其参数xray=True或xray=1,表示显示详细视图。 - 再通过
draw_mermaid_png(),将图形转换为 Mermaid 格式并生成 PNG 图片的二进制数据。 - 最后需要导入
matplotlib库用于图像显示。
代码如下:
import matplotlib.pyplot as plt import matplotlib.image as mpimg # 生成 Mermaid 图表并保存为图片 mermaid_code = agent.get_graph(xray=True).draw_mermaid_png() # 保存文件 with open("./jpg/graph1.jpg", "wb") as f: f.write(mermaid_code) # 使用 matplotlib 显示图像 img = mpimg.imread("./jpg/graph1.jpg") plt.imshow(img) # 显示图片 plt.axis('off') # 关闭坐标轴 plt.show() # 弹出窗口显示图片步骤 7:执行(非流式与流式)
- 使用
invoke()方法进行非流式执行:
from langchain.messages import HumanMessage messages = agent.invoke({ "messages": [ HumanMessage(content="今天西安的天气如何? ") ] }) print(f"调用 LLM 总次数: {messages['llm_calls']}次") for m in messages["messages"]: m.pretty_print()运行结果:
调用 LLM 总次数: 2次 ======================= Human Message ======================= 今天西安的天气如何? ======================= Ai Message ======================= Tool Calls: tavily_search (call_6LQafbVZn9qCzWix40b3auPI) Call ID: call_6LQafbVZn9qCzWix40b3auPI Args: query: 西安天气 time_range: day ======================= Tool Message ======================= {'query': '西安天气', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://feeds-drcn.cloud.huawei.com.cn/landingpage/latest?docid=10512921993454170538700817&to_app=hwbrowser&dy_scenario=relate&tn=8f7efc36ddc74442b888d848e6ae3f3b32b722a839fa5de71583e48ad5dc75fb&channel=HW_JINGXUAN_ZH&ctype=news&cpid=666&r=CN&emuiVer=27', 'title': '陕西省西安市今日天气(11月26日)', 'content': '今晨6时,西安晴,气温0℃,东北风3-4级,相对湿度88%。 预计,今天白天多云,最高气温13.7℃,微风,今天夜间晴,最低气温-1.1℃,微风。 ', 'score': 0.826278, 'raw_content': None}, {'url': 'https://weather.cma.cn/web/weather/V8870.html', 'title': '西安 - 中国气象局-天气预报-城市预报', 'content': '13℃ 2℃ 12℃ 12℃ 12℃ 2℃ 1℃ | 气温 | 13℃ | 5.3℃ | 4℃ | 2.6℃ | 3.5℃ | 10.5℃ | 10.5℃ | 12.2℃ | | 气温 | 10.5℃ | 10.5℃ | 12.2℃ | 10.4℃ | 7.6℃ | 5.5℃ | 4.4℃ | 3.3℃ | | 气温 | 2℃ | 10.2℃ | 11℃ | 11.8℃ | 5.4℃ | 5.2℃ | 4.9℃ | 4.3℃ | | 气温 | 8.3℃ | 16.6℃ | 16.8℃ | 12.8℃ | 8.6℃ | 7.4℃ | 4℃ | 4.3℃ | | 气温 | 4.5℃ | 11℃ | 12℃ | 11.2℃ | 5.9℃ | 5℃ | 3.4℃ | 3.1℃ | | 气温 | 2.2℃ | 7.6℃ | 10.6℃ | 9.6℃ | 8.5℃ | 5.3℃ | 2.1℃ | 1.6℃ |', 'score': 0.82492816, 'raw_content': None}, {'url': 'https://shaanxi.weather.com.cn/xian/index.shtml', 'title': '西安天气预报 - 陕西', 'content': '城市预报列表(11-26 07:30发布)。西安: 17℃/2℃。长安: 14℃/0℃。临潼: 15℃/', 'score': 0.7265231, 'raw_content': None}, {'url': 'https://weather.yahoo.co.jp/weather/world/CN/57036/', 'title': '西安(シーアン)(中国)の天気 - Yahoo!天気・災害', 'content': '西安(シーアン)(中国)の今日・明日・週間天気予報が確認できます。天気、最低気温、最高気温はもちろん、現地時刻、日の出、日の入り時刻までわかります。', 'score': 0.7060491, 'raw_content': None}], 'response_time': 0.86, 'request_id': '4d571ab8-52b8-4628-94d9-e37c5ae9727f'} ======================= Ai Message ======================= 今天西安的天气情况如下: - **今晨6时**: 晴,气温0℃,东北风3-4级,相对湿度88%。 - **白天气温**: 预计最高气温13.7℃,天气多云,微风。 - **夜间气温**: 预计最低气温-1.1℃,天气晴,微风。 如果需要更详细的信息,可以查看以下链接: - [陕西省西安市今日天气](https://feeds-drcn.cloud.huawei.com.cn/landingpage/latest?docid=10512921993454170538700817&to_app=hwbrowser&dy_scenario=relate&tn=8f7efc36ddc74442b888d848e6ae3f3b32b722a839fa5de71583e48ad5dc75fb&channel=HW_JINGXUAN_ZH&ctype=news&cpid=666&r=CN&emuiVer=27) - [中国气象局 - 西安天气预报](https://weather.cma.cn/web/weather/V8870.html) - [西安天气预报 - 陕西](https://shaanxi.weather.com.cn/xian/index.shtml)- 使用
stream()进行流式输出:
from langchain.messages import HumanMessage for chunk in agent.stream({ "messages": [HumanMessage(content="今天西安的天气如何? ")] }): print(chunk)注意,这里的流式,指的是Graph 执行步骤的流式输出,即途径节点的过程。每个节点对 Graph State 的更新值(注:并非是更新后的值),都可以通过这种方式追踪。由此,chunk类型为dict:key 代表节点名称;value 代表将更新的 State 值。
