LangGraph之工具调用 (ToolNode) 扩展智能体的能力边界

在上一节中,我们重点探讨 LangGraph 中的子图 (Subgraph) 机制:模块化、复用与复杂性管理。通过子图 (Subgraph) 机制,为我们提供了强大的模块化和复用能力,使得我们可以像搭积木一样构建复杂的智能体系统。

本节我们将重点探讨在构建智能体系统的过程中,工具调用 (Tool Calling) 是一项至关重要的能力。智能体通过调用各种外部工具,可以扩展自身的能力边界,完成更复杂、更实用的任务。

LangGraph 框架提供了强大的工具调用支持,并预置了 ToolNode 组件,极大地简化了在 LangGraph 图中集成和使用工具的流程。

1、 ToolNode:LangGraph 的工具调用中心

ToolNode 是 LangGraph 框架预置的核心组件,专门用于处理工具调用操作。可以将其理解为 LangGraph 图中的"工具执行器"或"工具调度中心"。

ToolNode 内部封装了工具的执行逻辑,可以根据 LLM 的工具调用请求,动态地调度和调用预先注册的工具,并将工具的执行结果返回。ToolNode 支持同步和异步工具调用,并自动处理工具执行过程中的错误 (例如,工具不存在、参数校验失败、工具运行时异常)。

ToolNode 会将工具的执行结果封装成 ToolMessage 类型的消息,并将其添加到图的状态 (消息历史列表) 中,作为状态更新输出。

ToolNode 操作 MessagesState。
输入:MessagesState,其中最后一条消息是包含 tool_calls 参数的AIMessage。
输出:MessagesState,更新为执行工具后产生的 ToolMessage。

ToolNode 的核心职责:
    1. 接收来自 LLM 的工具调用请求:与支持工具调用的 LLM 模型集成,接收工具调用请求。
    1. 执行工具调用:动态调度和调用预先注册的工具,并返回执行结果。
    1. 更新图状态:将工具执行结果封装成 ToolMessage 并添加到图状态中。
使用 ToolNode 的优势:
  • 简化工具集成:封装工具调用复杂性,开发者只需简单注册工具列表。
  • 原生支持 LangChain 工具:与 LangChain 工具体系无缝集成。
  • 内置错误处理:自动捕获工具执行异常,提高系统鲁棒性。
  • 兼容 ReAct Agent:与 LangGraph 预置的 ReAct 组件高度兼容。

LangGraph 推荐使用 LangChain 的 @tool 装饰器来定义工具函数。@tool 装饰器可以将普通的 Python 函数快速地转换成 LangChain Tool,并自动生成工具描述信息和参数 Schema,便于LLM 理解和调用工具。

定义工具有以下关键要素:
(1)@tool 装饰器:必须使用 @tool 装饰器将 Python 函数转换为 LangChain Tool 工具。
(2)清晰详细的 docstring:尽可能使用自然语言清晰详细地描述工具的功能、输入参数和输出结果,以便 LLM 更好地理解。
(3)类型提示:建议为工具函数的参数和返回值添加明确的类型提示(TypeHints),例如,location: str、year: int、→ str、→ list[str] 等。
(4)合理的工具名称:可通过 @tool(" 自定义工具名称") 显式指定,应简洁明确,具有描述性,例如,“get_weather_information” “search_wikipedia” 等。

示例 1:使用 ToolNode 构建工具调用智能体
from langchain_core.tools import toolfrom langchain_core.messages import AIMessagefrom langgraph.prebuilt import ToolNode@tool # 使用 @tool 装饰器,将 Python 函数转换为 LangChain Tooldef get_weather(location: str): # 定义工具函数 get_weather, location 参数用于接收城市名称    """Call to get the current weather.""" # 工具函数的 docstring 会被作为工具的描述信息,提供给 LLM    if location.lower() in ["sf", "san francisco"]:        return "It's 60 degrees and foggy."    else:        return "It's 90 degrees and sunny."@tooldef get_coolest_cities():    """Get a list of coolest cities"""    return "nyc, sf"# 创建 ToolNode 实例,注册工具列表 (包含 get_weather 和 get_coolest_cities 两个工具)tools = [get_weather, get_coolest_cities]tool_node = ToolNode(tools)# 构造包含单个工具调用请求的 AIMessagemessage_with_single_tool_call = AIMessage( # 创建 AIMessage    content="", # content 为空字符串,表示该消息主要用于工具调用,不包含文本内容    tool_calls=[ # tool_calls 参数,包含工具调用请求列表        {            "name": "get_weather", #  工具名称,必须与注册的工具名称一致            "args": {"location": "sf"}, #  工具参数,必须与工具函数定义的参数匹配            "id": "tool_call_id", #  工具调用 ID,用于唯一标识工具调用,  可以自定义            "type": "tool_call", #  消息类型,固定为 "tool_call"        }    ],)# 手动调用 ToolNode,输入为包含 AIMessage 的状态字典tool_node_output = tool_node.invoke({"messages": [message_with_single_tool_call]})print(tool_node_output) # 打印 ToolNode 的输出结果{'messages': [ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', tool_call_id='tool_call_id')]}
示例 2:手动调用 ToolNode(并行工具调用)
# 构造包含多个工具调用请求的 AIMessagemessage_with_multiple_tool_calls = AIMessage( # 创建 AIMessage    content="",    tool_calls=[ # tool_calls 参数,包含多个工具调用请求        {            "name": "get_coolest_cities", # 工具 1:get_coolest_cities            "args": {},            "id": "tool_call_id_1",            "type": "tool_call",        },        {            "name": "get_weather", # 工具 2:get_weather            "args": {"location": "sf"},            "id": "tool_call_id_2",            "type": "tool_call",        },    ],)# 手动调用 ToolNode,  输入为包含 AIMessage 的状态字典tool_node_output = tool_node.invoke({"messages": [message_with_multiple_tool_calls]})print(tool_node_output) # 打印 ToolNode 的输出结果{'messages': [ToolMessage(content='nyc, sf', name='get_coolest_cities', tool_call_id='tool_call_id_1'), ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', tool_call_id='tool_call_id_2')]}

在 LangGraph 图中使用 ToolNode

理解了 ToolNode 的工作原理和手动调用方法后,即可将其集成到 LangGraph 图中,构建具备工具调用能力的 ReAct 智能体。ToolNode 通常与 LLM 节点和条件路由节点配合使用,构成 ReAct 智能体的基本运行流程。
(1)LLM 节点 (Agent Node):负责接收用户输入和对话历史,调用 LLM 生成智能体行为。如果 LLM 决定调用工具,则会返回包含 tool_calls 的 AIMessage,指示需要调用哪些工具,以及工具的参数;如果 LLM 决定直接回复用户,则会返回不包含 tool_calls的 AIMessage,指示对话结束,或等待用户进一步输入。
(2)ToolNode (工具节点):负责接收来自 LLM 节点的工具调用请求,调度和执行相应的工具,并将执行结果封装为 ToolMessage 返回。
(3)条件路由节点 (Conditional Edge):负责根据 LLM 节点的输出结果,动态地决定流程走向。若返回了 tool_calls (指示需要调用工具),则路由到 ToolNode节点执行工具调用,否则将路由到 END 节点结束对话流程,或等待用户进一步输入。

示例 3:在 LangGraph 图中使用 ToolNode 构建 ReAct 智能体
from langgraph.graph import StateGraph, MessagesState, START, END # 导入 MessagesStatefrom langgraph.prebuilt import ToolNode # 导入 ToolNodefrom langchain_openai import ChatOpenAI# ... (get_weather, get_coolest_cities 工具函数的定义,此处省略) ...tools = [get_weather, get_coolest_cities] #  工具列表tool_node = ToolNode(tools) # 创建 ToolNode 实例,注册工具列表model_with_tools = ChatOpenAI(model="Qwen/Qwen3-8B", temperature=0).bind_tools(tools) # 绑定工具列表到 LLM 模型def should_continue(state: MessagesState): # 条件路由函数,判断是否继续工具调用    messages = state["messages"]    last_message = messages[-1] # 获取最后一个消息 (LLM 模型的输出)    if last_message.tool_calls: # 判断最后一个消息是否包含 tool_calls (工具调用请求)        return "tools" # 如果包含 tool_calls, 则路由到 "tools" 节点 (ToolNode),执行工具调用    return END # 如果不包含 tool_calls,  则路由到 END 节点,结束流程def call_model(state: MessagesState): #  LLM 模型节点函数    messages = state["messages"]    response = model_with_tools.invoke(messages) # 调用 LLM 模型,生成 AI 消息 (可能包含 tool_calls)    return {"messages": [response]} # 返回包含 AI 消息的状态更新workflow = StateGraph(MessagesState) # 创建 StateGraph 实例,状态 Schema 为 MessagesStateworkflow.add_node("agent", call_model) # 添加 LLM 模型节点,节点名为 "agent"workflow.add_node("tools", tool_node) # 添加 ToolNode 节点,节点名为 "tools"workflow.add_edge(START, "agent") # 定义从 START 节点到 "agent" 节点的边 (流程入口)workflow.add_conditional_edges("agent", should_continue, ["tools", END]) #  定义条件边,根据 "agent" 节点的输出,动态路由到 "tools" 节点或 END 节点workflow.add_edge("tools", "agent") # 定义从 "tools" 节点到 "agent" 节点的边 (ReAct 循环)app = workflow.compile() # 编译 LangGraph 图app.invoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]}){'messages': [HumanMessage(content='what is the weather in sf', additional_kwargs={}, response_metadata={}, id='34ecd7e5-d783-432d-8e10-b88e07ddaf62'),  AIMessage(content='\n\n', additional_kwargs={'tool_calls': [{'id': '0198c62a207bdc8ef9a5afb762ec5225', 'function': {'arguments': ' {"location": "sf"}', 'name': 'get_weather'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 156, 'prompt_tokens': 204, 'total_tokens': 360, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 148, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-8B', 'system_fingerprint': '', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--f86d6a8c-d6a8-4a73-80df-70618ced2b13-0', tool_calls=[{'name': 'get_weather', 'args': {'location': 'sf'}, 'id': '0198c62a207bdc8ef9a5afb762ec5225', 'type': 'tool_call'}], usage_metadata={'input_tokens': 204, 'output_tokens': 156, 'total_tokens': 360, 'input_token_details': {}, 'output_token_details': {'reasoning': 148}}),  ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', id='55196e1a-c8e4-40c3-ba2a-3dae67151012', tool_call_id='0198c62a207bdc8ef9a5afb762ec5225'),  AIMessage(content="\n\nThe current weather in San Francisco is 60 degrees Fahrenheit and foggy. Be sure to carry an umbrella if you're heading out!", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 184, 'prompt_tokens': 246, 'total_tokens': 430, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 155, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': None}, 'model_name': 'Qwen/Qwen3-8B', 'system_fingerprint': '', 'finish_reason': 'stop', 'logprobs': None}, id='run--e85daa98-1594-4ef6-a759-c52de36c9fa6-0', usage_metadata={'input_tokens': 246, 'output_tokens': 184, 'total_tokens': 430, 'input_token_details': {}, 'output_token_details': {'reasoning': 155}})]}

在示例 3 中,我们创建了一个实用的StateGraph 工作流实例。我们定义了agent (LLM 节点) 和 tools(ToolNode 节点)两个核心节点,以及一个条件路由函数should_continue。该函数根据LLM 节点的输出动态地控制流程的走向,在工具调用(路由到 tools 节点)和结束对话(路由到 END 节点)之间进行选择。通过边连接这些节点,构建了基本的 ReAct 循环。
最后,调用 workflow.compile() 方法编译此示例展示了使用 ToolNode 构建的 ReAct 智能体能够成功地接收用户输入,调用 get_weather 工具查询天气信息,并基于工具的执行结果生成最终的回复,完成基于工具调用的 ReAct 循环。ToolNode 组件作为工具调度和执行的核心模块,简化了工具集成和 ReAct 智能体构建流程,使开发者能更专注于智能体核心逻辑和业务功能的开发。

总结

通过以上步骤,用户可以利用 LangGraph 的 ToolNode 实现多种工具调用和集成,增强用户交互体验。这种方法不仅让工具调用过程更加灵活,也使得 AI 模型能够更好地响应用户查询。

最近这几年,经济形式下行,IT行业面临经济周期波动与AI产业结构调整的双重压力,很多人都迫于无奈,要么被裁,要么被降薪苦不堪言。但我想说的是一个行业下行那必然会有上行行业,目前AI大模型的趋势就很不错,大家应该也经常听说大模型,也知道这是趋势,但苦于没有入门的契机,现在他来了,我在本平台找到了一个非常适合新手学习大模型的资源。大家想学习和了解大模型的,可以**点击这里前往查看**

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐