LangGraph工具调用实战:从聊天机器人到智能体的核心机制

发布时间:2026/9/1 11:11:56
LangGraph工具调用实战:从聊天机器人到智能体的核心机制 之前在做智能体开发时我反复卡在一个问题上大模型“只会说不会做”。你问它“厦门今天天气怎么样”它能给出非常流畅的回复但它并不知道真实天气也不会主动去查天气接口。后来在项目中接入 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_URLhttps://api.openai.com/v1 OPENAI_MODELgpt-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.pyLangGraph 图的构建与编译。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( modelgpt-4o-mini, api_keyapi_key, base_urlbase_url, temperature0 ) 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( modelos.getenv(OPENAI_MODEL, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), temperature0 ) 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(contentresult, tool_call_idtool_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( modelos.getenv(OPENAI_MODEL, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), temperature0 ) 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( modelos.getenv(OPENAI_MODEL, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), temperature0 ) tools [get_weather, multiply] agent create_react_agent(modelllm, toolstools) 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结构这对理解模型的行为方式非常有帮助。如果本文对你有帮助可以先收藏备用后续实战中遇到问题时随时回来对照排查。