之前在做智能体开发时,我反复卡在一个问题上:大模型“只会说,不会做”。你问它“厦门今天天气怎么样”,它能给出非常流畅的回复,但它并不知道真实天气,也不会主动去查天气接口。后来在项目中接入 LangGraph 的工具调用(Tool Calling)机制后,模型才真正从“聊天机器人”变成了“能干活的智能体”。
这篇文章围绕《AI编程与智能体开发》课程中 LangGraph 工具调用这一主题,整理了一份完整的实战笔记。文章会覆盖工具调用的核心概念、环境准备、工具定义、模型绑定、ToolNode 节点、ReAct 智能体搭建,以及高频报错排查和工程落地建议。无论你是刚开始接触 LangGraph 的初学者,还是准备把智能体接入业务系统的开发者,都可以直接参考本文的代码和排错思路。
1. 工具调用是什么,为什么智能体离不开它
1.1 从“只会聊天”到“能干活”
大模型的本质是一个文本生成模型,它的所有知识都来自训练数据。这就带来两个天然限制:
- 它不知道训练数据截止之后的实时信息,比如今天的天气、最新的股票价格。
- 它只能输出文字,不能直接查数据库、调接口、执行代码。
工具调用就是为了解决这两个问题而设计的。它的核心思想是:模型不负责执行动作,只负责“决定要调用哪个工具、传什么参数”,真正执行动作的是你的代码。
整个流程可以拆成四步:
- 你提前定义好一批工具,例如“查天气”“算乘法”“查订单”。
- 调用模型时,把工具的描述、参数结构一起发给模型。
- 模型根据用户问题,判断是否需要调用工具。如果需要,就返回一个结构化的“工具调用请求”,里面包含工具名和参数。
- 你的程序执行这个工具,把结果返回给模型。模型基于结果生成最终回答。
在 LangGraph 中,这四步被封装成了图(Graph)上的节点和边。模型节点负责“思考”,工具节点负责“执行”,两者通过条件边形成循环,直到模型认为任务完成。
1.2 LangGraph 与 LangChain 的关系
很多初学者会把 LangGraph 和 LangChain 搞混,这里先做一个简单区分。
LangChain 是一个组件库,提供了模型封装、提示词模板、向量存储、文档加载器等能力。你可以用 LangChain 快速调用 OpenAI、DeepSeek、通义千问等模型,也可以用它提供的@tool装饰器来定义工具。
LangGraph 则是一个编排引擎,它把智能体流程建模成一张有向图。图的每个节点是一个函数或逻辑单元,每条边定义节点之间的流转关系。LangGraph 的重点是状态管理、循环控制、条件路由和持久化,这些正是 LangChain 早期版本最薄弱的地方。
在实际项目中,两者通常是配合使用的:
- 工具定义用 LangChain 的
@tool。 - 模型调用用 LangChain 的
ChatOpenAI等封装。 - 流程编排用 LangGraph 的
StateGraph、ToolNode、create_react_agent。
简单说,LangChain 提供“零件”,LangGraph 负责“组装和调度”。
1.3 工具调用的典型应用场景
工具调用的应用场景非常广,常见的有下面几类:
- 实时数据查询:天气、新闻、股票、航班信息。
- 企业内部系统对接:查订单、查库存、创建工单、查询员工信息。
- 计算与数据处理:数学计算、SQL 查询、文件格式转换。
- 操作类任务:代发邮件、创建日历日程、提交审批。
- 知识库检索:在 RAG 流程中,把检索器封装成工具,让模型按需检索。
可以说,只要你的智能体需要“接触外部世界”,就离不开工具调用。这也是 LangGraph 工具调用成为智能体开发核心技能的原因。
2. 环境准备与版本说明
2.1 Python 环境与依赖安装
LangGraph 是基于 Python 的框架,建议使用 Python 3.9 及以上版本。为了不影响系统全局环境,推荐创建独立的虚拟环境。
python -m venv .venv source .venv/bin/activateWindows 系统激活命令为:
.venv\Scripts\activate本文需要安装以下核心依赖:
pip install langgraph langchain-core langchain-openai python-dotenv各包的作用:
langgraph:负责图的构建、状态管理和节点调度。langchain-core:提供@tool装饰器、消息类型等基础抽象。langchain-openai:提供 OpenAI 兼容接口的模型封装。python-dotenv:用于读取.env文件中的环境变量。
LangGraph 目前还处于 0.x 快速迭代阶段,API 在不同小版本之间可能有细微差异。本文代码以常见稳定写法为例,安装时建议使用最新发布版本。如果你的项目已经存在其他版本的 LangGraph,升级前务必先阅读官方更新日志。
2.2 模型 API 的配置
工具调用依赖模型的原生 Function Calling 能力。目前主流的 OpenAI 兼容接口都支持这一能力,包括 OpenAI 官方模型、DeepSeek、通义千问、智谱等。
推荐把 API Key 写入.env文件,避免硬编码在代码中。
# 文件路径:.env OPENAI_API_KEY=你的密钥 OPENAI_BASE_URL=https://api.openai.com/v1 OPENAI_MODEL=gpt-4o-mini如果你的项目使用的是国内大模型厂商提供的 OpenAI 兼容接口,只需要把OPENAI_BASE_URL和OPENAI_MODEL换成对应的值即可。不同厂商的兼容地址和模型名不同,请以官方文档为准。
在代码中通过下面的方式加载配置:
import os from dotenv import load_dotenv load_dotenv() api_key = os.getenv("OPENAI_API_KEY") base_url = os.getenv("OPENAI_BASE_URL") model_name = os.getenv("OPENAI_MODEL")需要注意,OPENAI_API_KEY属于敏感信息,不要提交到 Git 仓库。生产环境中建议使用密钥管理服务或者 CI/CD 的 Secrets 配置。
2.3 示例项目结构
为了便于后续实战,我们先规划好项目结构:
langgraph-tool-demo/ ├── .env ├── requirements.txt └── src/ ├── tools.py ├── agent.py └── run.pytools.py:所有自定义工具函数。agent.py:LangGraph 图的构建与编译。run.py:入口脚本,接收用户问题并运行智能体。
requirements.txt内容如下:
langgraph langchain-core langchain-openai python-dotenv3. LangGraph 工具调用的核心机制
在动手写代码之前,有必要先理解 LangGraph 工具调用的四个核心概念:工具本身、模型绑定、工具节点、条件路由。
3.1 工具的本质:函数 + 描述 + 参数结构
在 LangChain / LangGraph 中,一个工具就是一个普通 Python 函数,加上名称、描述和参数结构三部分元信息。
最常用的定义方式是@tool装饰器:
from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的当前天气情况。 Args: city: 城市名称,例如“厦门” """ return f"{city}:晴,气温 22~28 摄氏度,东南风 3 级"这里的关键点是:
- 函数名
get_weather就是工具名。 - 函数的 docstring 就是工具描述,模型会据此判断何时调用这个工具。
- 参数
city: str会被解析成 JSON Schema,模型会按照这个结构生成参数。
如果你想知道模型看到的工具结构是什么样,可以打印get_weather.name、get_weather.description和get_weather.args来查看。这就是模型在请求中收到的全部工具信息。
3.2 用 bind_tools 让模型“看到”工具
定义好工具后,需要把工具列表绑定到模型上,这一步通过bind_tools完成。
from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="gpt-4o-mini", api_key=api_key, base_url=base_url, temperature=0 ) tools = [get_weather] llm_with_tools = llm.bind_tools(tools)需要特别注意的是,bind_tools只是把工具定义附加到模型的请求参数中,并不会执行任何工具。它改变的是模型的“能力边界”:模型现在知道存在get_weather这个工具,并且知道它的参数格式。
调用绑定了工具的模型后,如果模型认为需要查天气,它会返回一个tool_calls字段。这个字段是结构化的调用请求,而不是自然语言。
from langchain_core.messages import HumanMessage response = llm_with_tools.invoke([HumanMessage(content="厦门今天天气怎么样?")]) print(response.tool_calls)输出大致如下:
[{'name': 'get_weather', 'args': {'city': '厦门'}, 'id': 'call_abc123', 'type': 'tool_call'}]其中name是工具名,args是模型生成的参数,id是本次调用请求的唯一标识,后续工具执行结果的回传需要用到它。
3.3 工具节点 ToolNode 与条件路由 tools_condition
在 LangGraph 中,真正执行工具的不是模型,而是ToolNode。它接收模型返回的tool_calls,逐个调用对应工具,并把结果包装成ToolMessage写回图的状态。
from langgraph.prebuilt import ToolNode tool_node = ToolNode(tools)ToolNode内部会根据tool_calls里的name找到对应的工具函数,用args作为参数调用,然后把结果封装成消息。
那么图怎么知道“什么时候该调用工具,什么时候该结束”呢?答案是tools_condition,它是 LangGraph 预置的一个条件路由函数。
from langgraph.prebuilt import tools_conditiontools_condition的逻辑很简单:检查图中最后一条 AIMessage 是否包含tool_calls。
- 如果包含,返回字符串
"tools",表示下一步应该进入工具节点。 - 如果不包含,返回
END,表示模型已经可以直接给出最终回答,流程结束。
3.4 完整的执行链路
把上面几个概念组合起来,就是一个标准的工具调用循环。
用一个最简单的图来说明:
用户输入 --> agent 节点 --> tools_condition 判断 | 有 tool_calls ----> tools 节点(执行工具) 没有 tool_calls ---> 结束,输出最终回答当tools节点执行完工具后,结果会追加到消息列表里,流程再次回到agent节点,让模型基于工具结果继续推理。这个“思考 -> 调用工具 -> 观察结果 -> 再思考”的循环,就是 ReAct 模式的核心,也是智能体能够完成多步任务的原因。
这里需要理解一个关键点:图中的状态是消息列表的累积,而不是覆盖。每一轮模型输出、工具结果都被追加到messages中,模型因此能记住之前发生过什么。
4. 从零开始:手写第一个工具调用
为了彻底理解工具调用的执行过程,我们先不急着搭建完整图,而是手动模拟一遍“模型调用工具 -> 执行工具 -> 返回结果给模型”的流程。
4.1 安装依赖并创建项目
先创建项目目录并激活虚拟环境:
mkdir langgraph-tool-demo && cd langgraph-tool-demo python -m venv .venv source .venv/bin/activate pip install langgraph langchain-core langchain-openai python-dotenv创建.env文件并填入你的模型配置,然后创建src目录。
4.2 定义工具函数
在src/tools.py中写入第一个工具:
# 文件路径:src/tools.py from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的当前天气情况。 Args: city: 城市名称,例如“厦门” """ # 生产环境中应替换为真实天气 API,例如和风天气、高德天气等 return f"{city}:晴,气温 22~28 摄氏度,东南风 3 级" @tool def multiply(first: float, second: float) -> float: """计算两个数字的乘积。 Args: first: 第一个乘数 second: 第二个乘数 """ return first * second这里定义了两个工具:一个查天气,一个做乘法。multiply是为了后面演示多工具场景准备的。
4.3 绑定模型并查看工具调用请求
在src/manual_test.py中手动触发一次工具调用:
# 文件路径:src/manual_test.py import os from dotenv import load_dotenv from langchain_core.messages import HumanMessage, ToolMessage from langchain_openai import ChatOpenAI from tools import get_weather load_dotenv() llm = ChatOpenAI( model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"), api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), temperature=0 ) llm_with_tools = llm.bind_tools([get_weather]) # 第一步:发送用户问题 messages = [HumanMessage(content="厦门今天天气怎么样?适合出门吗?")] ai_msg = llm_with_tools.invoke(messages) print("模型返回的工具调用请求:") print(ai_msg.tool_calls)运行脚本:
cd src python manual_test.py如果一切正常,你会看到模型输出了一个tool_calls列表,其中包含工具名get_weather和参数{"city": "厦门"}。
4.4 手动执行工具并回传结果
接下来手动执行这个工具调用,把结果封装成ToolMessage送回模型:
# 继续在上面的文件中追加 if ai_msg.tool_calls: messages.append(ai_msg) for tool_call in ai_msg.tool_calls: tool_name = tool_call["name"] tool_args = tool_call["args"] if tool_name == "get_weather": result = get_weather.invoke(tool_args) messages.append(ToolMessage(content=result, tool_call_id=tool_call["id"])) # 第二步:把工具结果发回模型,让模型生成最终回答 final_msg = llm_with_tools.invoke(messages) print("最终回答:") print(final_msg.content)这里有两个容易忽略的细节:
- 我们需要把
ai_msg本身也追加到messages,因为模型需要看到自己刚才的调用请求。 ToolMessage必须携带tool_call_id,并且这个 id 要和tool_calls里的id一一对应。否则模型无法把工具结果和调用请求关联起来。
运行后,期望的输出类似:
最终回答: 根据查询结果,厦门今天晴,气温 22~28 摄氏度,东南风 3 级。天气不错,适合出门。到这里,你已经手动完成了一次完整的工具调用流程。这个流程虽然能用,但存在几个问题:循环要靠自己写、分支逻辑要自己维护、消息状态要自己管理。如果任务涉及多轮工具调用,代码会迅速变得混乱。这正是下一步引入 LangGraph 图结构的原因。
5. 用 LangGraph 实现完整的 ReAct 智能体
5.1 为什么需要图结构
手动流程适合理解原理,但不适合工程落地。真实场景中的智能体往往需要:
- 根据用户问题决定是否调用工具。
- 一次调用多个工具。
- 根据工具结果决定是否继续调用其他工具。
- 在工具调用失败时重试或换一种方式。
这些需求本质上是一个带循环和条件分支的状态机。LangGraph 用图来建模,把“模型调用”“工具执行”抽象成节点,用边和条件函数控制流转,状态管理也由框架自动完成。
5.2 基于 StateGraph 搭建工具调度图
在src/agent.py中基于StateGraph搭建完整的工具调度图:
# 文件路径:src/agent.py import os from dotenv import load_dotenv from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from langgraph.graph import MessagesState, StateGraph, START, END from langgraph.prebuilt import ToolNode, tools_condition from tools import get_weather, multiply load_dotenv() def create_agent(): llm = ChatOpenAI( model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"), api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), temperature=0 ) tools = [get_weather, multiply] llm_with_tools = llm.bind_tools(tools) # 模型节点:调用模型,返回新的消息 def call_model(state: MessagesState): response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} # 构建图 graph = StateGraph(MessagesState) graph.add_node("agent", call_model) graph.add_node("tools", ToolNode(tools)) graph.add_edge(START, "agent") graph.add_conditional_edges("agent", tools_condition) graph.add_edge("tools", "agent") return graph.compile() if __name__ == "__main__": app = create_agent() result = app.invoke({ "messages": [HumanMessage(content="厦门和北京今天的天气怎么样?")] }) for message in result["messages"]: message.pretty_print()这段代码有几个关键点:
MessagesState是 LangGraph 预置的状态类型,内部使用add_messages归约器管理消息列表。你不需要手动维护消息累积逻辑。call_model是 agent 节点,它从状态中取出全部消息,调用绑定工具的模型,并返回新的消息。ToolNode(tools)接收工具列表,自动执行模型返回的tool_calls。tools_condition是预置条件函数,负责在“结束”和“进入 tools 节点”之间做选择。
运行脚本:
cd src python agent.py你会看到类似下面的输出(具体内容取决于模型返回):
================================ Human Message ================================= 厦门和北京今天的天气怎么样? ================================== AI Message ================================== Tool Calls: get_weather (call_xxx) Args: city: 厦门 get_weather (call_yyy) Args: city: 北京 ================================= Tool Message ================================= 厦门:晴,气温 22~28 摄氏度,东南风 3 级 ================================= Tool Message ================================= 北京:晴,气温 18~26 摄氏度,西北风 2 级 ================================== AI Message ================================== 厦门今天晴,气温 22~28 摄氏度;北京今天也是晴天,气温 18~26 摄氏度。可以看到,模型自动并行发起了两个get_weather调用请求,ToolNode分别执行并返回结果,最终模型基于两个工具结果生成了汇总回答。这就是 LangGraph 处理多工具并行的能力。
5.3 使用 create_react_agent 快速搭建
如果你不想手动定义节点和边,LangGraph 提供了一个更高级的封装create_react_agent,它内部已经实现了标准的 ReAct 循环。
# 文件路径:src/quick_agent.py import os from dotenv import load_dotenv from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from tools import get_weather, multiply load_dotenv() llm = ChatOpenAI( model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"), api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), temperature=0 ) tools = [get_weather, multiply] agent = create_react_agent(model=llm, tools=tools) result = agent.invoke({ "messages": [HumanMessage(content="厦门天气怎么样?顺便算一下 12 乘以 8 等于多少。")] }) for message in result["messages"]: message.pretty_print()create_react_agent内部做了几件重要的事情:
- 自动调用
bind_tools把工具绑定到模型。 - 自动创建 agent 节点和
ToolNode。 - 自动添加
tools_condition条件路由。 - 内部处理了循环、状态和消息累积。
对于标准工具调用场景,create_react_agent是最省事的方案。对于需要自定义流程、添加人工审批节点、插入条件分支的复杂业务,则建议使用StateGraph手动构建。
5.4 多工具场景的执行逻辑
上面的例子同时用到了get_weather和multiply两个工具。模型会根据用户问题的语义自动选择调用哪个工具。
用户问“厦门天气怎么样”,模型只调用get_weather。用户问“12 乘以 8”,模型只调用multiply。用户同时问两件事,模型可能并行发起两个工具调用。
这个“自主决策”能力来自模型本身。在实际项目中需要注意,模型选择工具的结果并不总是符合预期,因此工具的设计是否清晰直接影响到调用准确率。
5.5 运行与验证
确保当前在项目虚拟环境中,执行:
cd src python quick_agent.py如果输出里同时出现了天气查询结果和96.0的乘法结果,说明多工具智能体已经正常运行。
6. 常见问题与排查思路
工具调用的开发过程中,最常见的问题集中在“模型不调用工具”“参数解析失败”“图循环异常”几个方面。下面按问题现象、可能原因、解决思路三个维度整理。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型始终不调用工具,直接文字回答 | 模型不支持 Function Calling,或未调用 bind_tools | 确认模型支持工具调用,检查是否执行了 bind_tools |
| 工具参数解析报错,缺少必填参数 | 工具参数名对模型不友好,或 docstring 描述不清晰 | 简化参数名,在 docstring 中说明每个参数含义 |
| 图进入死循环,工具被反复调用 | 工具返回内容让模型误以为需要继续调用,或工具状态异常 | 让工具返回明确结果,检查是否触发了不必要的循环 |
| 工具返回内容过长,耗尽上下文 | 工具返回了整张表或大段日志 | 精简工具返回内容,只返回核心字段 |
| ToolMessage 关联失败 | tool_call_id 未正确传递 | 使用 ToolNode,它自动处理 id 关联 |
| 导入 ToolNode 报错 | langgraph 版本过旧 | 升级 langgraph 到最新稳定版 |
6.1 模型始终不调用工具
这是最常见的问题。首先确认你使用的模型是否支持原生的 Function Calling / Tool Calling 能力。老版本的模型或部分轻量模型不支持这个功能,模型只能把工具调用要求当作普通文字生成,导致永远不返回tool_calls。
排查步骤:
- 确认已经执行了
llm.bind_tools(tools)。 - 打印最终发给模型的请求,确认
tools字段是否出现在请求中。 - 确认模型名称正确,部分模型需要特定的开关参数才能启用工具调用。
- 检查
temperature是否过高,建议机器类任务设置为 0。
6.2 工具参数解析失败
模型生成参数时偶尔会出现格式问题,比如缺少必填字段、类型错误、字段名拼写错误。
解决方案:
- 在工具函数的 docstring 里写清楚每个参数的用途和示例值。
- 参数名尽量用完整单词,不要用
c、x这种无意义缩写。 - 工具内部做好参数校验,对异常参数返回友好的错误信息,而不是直接抛异常。
工具内部建议这样处理:
@tool def get_weather(city: str) -> str: """查询指定城市的当前天气情况。 Args: city: 城市名称,例如“厦门” """ if not city or not city.strip(): return "参数错误:city 不能为空" # 正常查询逻辑 return f"{city}:晴,气温 22~28 摄氏度"6.3 图循环卡死或无限调用工具
LangGraph 默认有递归限制,超过限制会抛出GraphRecursionError。发生这种情况,通常意味着智能体陷入了“调用工具 -> 观察结果 -> 再次调用工具”的死循环。
排查方向:
- 工具返回内容是否足够明确?如果工具总是返回空字符串,模型可能反复尝试调用。
- 工具内部是否有异常导致每次返回相同错误?应该让模型感知到失败并停止继续调用。
- 是否某些工具真的可能被调用无限次?可以手动设置
recursion_limit作为兜底。
result = app.invoke( {"messages": [HumanMessage(content="厦门天气怎么样?")]}, config={"recursion_limit": 20} )6.4 工具返回内容太大导致上下文溢出
有些工具会返回数据库全表、大段日志或完整文件内容,这些内容会迅速占满上下文窗口。
解决方案是限流和精简:
- 在工具内部做数据截断,只返回前 N 条记录。
- 返回摘要而非原文。
- 把大数据写入临时文件或对象存储,工具只返回文件地址。
工具设计的黄金原则是:返回给模型的内容越精炼,模型的理解越准确,上下文消耗也越少。
7. 最佳实践与工程建议
7.1 工具设计原则
工具的质量直接决定智能体的上限。在定义工具时,建议遵循以下原则:
- 单一职责:一个工具只做一件事。把“查天气”和“发邮件”拆成两个工具,而不是放在一个工具里按参数分支。
- 参数尽量少:工具参数越多,模型生成参数的出错概率越高。能合并的参数尽量合并。
- 描述要清晰:docstring 要写明工具用途、每个参数的含义和单位、返回内容的格式。
- 返回结构化数据:在可能的情况下,返回 JSON 字符串或字典,方便模型解读。
一个反面例子是工具描述写成“处理数据”,模型根本不知道何时调用它。正面例子应该写清楚触发条件、输入和输出。
7.2 错误处理与超时控制
工具执行是智能体流程中最容易出错的环节,因为外部接口不稳定、参数可能非法、服务可能超时。工具内部必须做异常兜底。
import time @tool def query_order(order_id: str) -> str: """根据订单号查询订单状态。 Args: order_id: 订单号 """ try: # 模拟外部 API 调用 time.sleep(0.5) return f"订单 {order_id} 状态:已发货" except Exception as e: return f"订单查询失败:{str(e)}"注意这里的关键设计:工具出错时返回一条描述失败原因的消息,而不是抛异常让整个图崩溃。模型可以基于这条错误信息决定是重试、更换工具还是直接告知用户失败。
对于超时类的外部调用,建议在工具内部设置超时时间。需要特别注意,工具的返回结果不要包含敏感信息,比如完整密钥、数据库连接串等,防止这些信息被回传给模型。
7.3 安全边界与权限控制
这是工具调用工程化中最重要的一点。工具一旦暴露给模型,模型就拥有了执行真实操作的能力。必须遵守最小权限原则:
- 只注册当前场景确实需要的工具,不要把所有函数都暴露给模型。
- 涉及删除、修改、转账、审批等敏感操作,添加人工确认环节。例如在工具调用前插入一个
human_approval节点,人工确认后才执行。 - 所有外部调用都要做参数校验,防止模型生成恶意参数。
- 在生产环境使用真实工具前,先在测试环境验证一遍完整的调用链。
如果你在@tool里使用了eval、exec、直接执行 shell 命令等能力,务必意识到这相当于给了模型代码执行权限,安全风险极高,生产环境要格外谨慎。
7.4 日志与可观测性
智能体的运行过程很难调试,因为涉及模型、工具、状态多轮交互。建议在关键节点埋点:
- 记录模型每次返回的
tool_calls内容。 - 记录每个工具的入参、耗时和返回摘要。
- 记录图执行的最大深度和总轮次。
在call_model和工具节点中加日志是常见的做法:
def call_model(state: MessagesState): response = llm_with_tools.invoke(state["messages"]) if response.tool_calls: print(f"[agent] 模型发起工具调用: {response.tool_calls}") return {"messages": [response]}线上系统建议接入成熟的日志系统,并给单次智能体运行生成一个trace_id,便于把一次完整交互链路串起来排查问题。
8. 总结与后续学习路线
到这里,你已经完整走通了 LangGraph 工具调用的全流程:理解了工具调用的背景和原理,掌握了@tool定义工具、bind_tools绑定模型、ToolNode执行工具、tools_condition条件路由这几个核心概念,并且用StateGraph和create_react_agent分别实现了可运行的 ReAct 智能体。
接下来的学习建议按下面的顺序推进:
- 自己动手添加一个真实工具,比如对接一个公开天气 API 或数据库查询接口,体验真实环境下的参数解析和异常处理。
- 学习 LangGraph 的状态定义和自定义 State,理解
add_messages归约器之外的消息管理模式。 - 研究条件路由的高级用法,比如根据工具结果决定是否结束流程或进入人工审核节点。
- 学习子图(Subgraph)与并行分支,把复杂任务拆解成多个子智能体。
- 尝试接入 LangGraph 的持久化能力,实现多轮对话的长期记忆。
工具调用是智能体开发的第一个门槛,迈过这道门槛后,你会发现真正复杂的不是“调用工具”,而是设计一套稳定、安全、可观测的工具调用体系。建议你在学习过程中多打印模型返回的原始请求和tool_calls结构,这对理解模型的行为方式非常有帮助。如果本文对你有帮助,可以先收藏备用,后续实战中遇到问题时随时回来对照排查。