LangGraph工具调用实战:从零构建大模型智能体核心逻辑

发布时间:2026/9/2 4:53:27
LangGraph工具调用实战:从零构建大模型智能体核心逻辑 各位读者朋友好今天我们围绕 LangGraph 的工具调用Tool Calling做一期完整实战拆解。这个话题来自智能体开发中非常核心的一环当大模型只负责“思考”时谁来负责“行动”答案就是工具调用。无论你是在学习 AI 编程还是准备把智能体落地到实际业务系统LangGraph 的工具调用机制都是绕不开的关键技能。本文会从基础概念讲起再逐步深入到代码实现、条件路由、多工具协作、以及生产环境常见的坑点。内容偏实操建议你打开 Python 环境跟着敲一遍效果会比只看不练好很多。1. 什么是 LangGraph为什么需要工具调用1.1 从普通对话到大模型智能体我们先从一个大背景说起。过去我们用大模型大多数场景是“你问我答”用户输入 Prompt模型返回一段文本。这种模式适合写作辅助、代码生成、内容总结但它有一个明显的问题模型只会“说”不会“做”。什么是“做”比如查天气、查数据库、调后端 API、操作文件、发请求、执行一段计算逻辑。这些动作大模型本身无法直接完成因为它没有执行环境也没有实时数据的访问权限。智能体Agent的出现就是为了解决这个问题。智能体让大模型变成一个“决策大脑”它通过工具调用把具体的操作委托给函数、API 或者外部服务去执行然后把执行结果拿回来继续推理。这样大模型就从“聊天机器人”升级成了“能干活的工作流引擎”。1.2 LangGraph 的定位LangGraph 是 LangChain 生态中面向智能体编排的框架它把整个智能体流程建模成一张图Graph图中的节点是“要执行的逻辑”边是“状态流转的方向”。相比早期的 LangChain Agent 执行器LangGraph 更强调可控性和可视化。你可以非常清楚地看到模型在哪个节点被调用、工具执行结果如何回流、走哪条分支、是否需要循环重试。这对于生产级智能体的调试和维护非常重要。打个比方LangChain 的传统 Agent 像“自动挡”开起来方便但不好控制LangGraph 更像“手动挡”每个换挡逻辑都透明可控。对于需要精细编排的智能体项目LangGraph 是更稳妥的选择。1.3 工具调用的核心价值工具调用Tool Calling指的是让大模型在生成回复的过程中输出一个“调用某个工具”的指令由程序侧解析这个指令并执行对应的函数再把函数返回值作为上下文交还给模型继续生成。这样做有三大好处第一能力扩展。模型可以访问实时数据、私有业务接口、数据库等原本接触不到的信息源。第二结果可控。工具的执行逻辑是开发者编写的普通函数会有明确的输入输出规范和异常处理不会像模型生成内容那样不可控。第三可审计。每一次工具调用都有明确的请求参数和执行结果方便日志追踪、权限控制和人工审核。2. 环境准备与版本说明在开始写代码之前我们先准备好运行环境。本节内容基于常见的 Python 开发环境版本信息方面需要结合你本机的实际环境进行调整但整体思路不变。2.1 安装 Python 环境LangGraph 是基于 Python 的框架建议使用 Python 3.9 及以上版本。如果你还没有安装 Python可以直接去 Python 官网下载安装包安装时记得勾选“Add Python to PATH”。安装完成后在命令行中确认版本python --version预期输出类似Python 3.11.92.2 创建虚拟环境为了隔离依赖建议为每个项目创建独立的虚拟环境。这里以 Python 内置的 venv 为例mkdir langgraph-tool-calling cd langgraph-tool-calling python -m venv venvWindows 系统激活虚拟环境venv\Scripts\activatemacOS 或 Linux 系统激活虚拟环境source venv/bin/activate激活成功后命令行前面会显示(venv)字样。2.3 安装 LangGraph 与相关依赖本文示例需要安装以下核心库langgraph智能体编排框架langchain-core提供 Tool、Message 等基础数据结构langchain-openaiOpenAI 兼容接口的 LangChain 适配层安装命令pip install langgraph langchain-core langchain-openai如果你的网络环境不允许直接访问 OpenAI可以使用兼容 OpenAI 接口的国内模型服务只要配置对应的 base_url 和 api_key 即可。本文示例代码会以环境变量的方式读取配置便于切换。2.4 获取 API Key工具调用依赖大模型的 function calling 能力。你需要准备一个支持工具调用的大模型 API Key。如果你是使用 OpenAI在终端设置环境变量export OPENAI_API_KEY你的API KeyWindows PowerShell 设置方式$env:OPENAI_API_KEY你的API Key如果你使用的是国内大模型平台的 OpenAI 兼容接口需要额外设置 base_urlexport OPENAI_API_KEY你的API Key export OPENAI_BASE_URL模型服务商提供的接口地址这里提醒一下国内的模型服务商接口地址和模型名称各有差异请以官方文档为准。本文的代码逻辑是通用的只需要调整模型名称和 base_url 就能适配。3. LangGraph 工具调用的核心原理3.1 从“模型直接回答”到“模型发起工具调用”在没有工具调用的情况下一次完整的模型请求是用户输入 → 模型生成文本 → 返回给用户引入工具调用之后链路变成用户输入 → 模型判断是否需要工具 → 如果需要则输出结构化工具调用指令 → 程序执行对应函数 → 将结果返回给模型 → 模型继续生成最终回复这里有个关键点模型本身并不直接执行函数。它生成的是一个“调用请求”例如“我要调用 get_weather 这个工具参数是 city厦门”。真正执行的是我们的 Python 代码。LangGraph 负责把模型输出的调用请求解析出来路由到对应的工具函数再把工具结果封装成消息重新发给模型。3.2 LangGraph 的核心组件LangGraph 把智能体流程组织成一张有状态图。以下三个核心概念必须理解。State状态智能体运行过程中的共享数据结构相当于“全局变量池”。每一步节点的输出都会更新 State图的状态在节点之间传递。Node节点一个普通 Python 函数接收当前 State处理后返回一个字典字典内容会合并到新的 State 中。Edge边定义节点之间的流转关系。普通边表示固定流转条件边则根据 State 动态决定下一个节点。工具调用场景下我们通常设计成这样的结构初始化 State调用 Agent 节点也就是大模型节点大模型输出后判断是否有工具调用请求如果有进入 Tool 执行节点执行完成把结果写回 State回到 Agent 节点再次推理如果没有工具调用直接输出最终答案流程结束这就是一个典型的 ReAct 循环思考Reasoning→ 行动Acting→ 观察Observation→ 再思考直到得到最终结论。3.3 function calling 与 Tool Calling 的关系在 LangChain 体系中function calling 通常指大模型侧的原生能力也就是模型在训练时学会了输出结构化工具调用指令。Tool Calling 则是 LangChain/LangGraph 对这些指令的统一封装层。你定义好一个 ToolLangGraph 会自动生成模型需要的函数描述格式并处理结果的解析。这意味着我们只需要用tool装饰器定义一个普通 Python 函数LangGraph 会帮我们完成和模型之间的协议对接。4. 完整实战构建一个带工具调用的智能体下面进入本文的核心环节。我们将从零开始构建一个能调用外部工具的智能体并且逐步加入状态管理、条件路由、多工具等能力。4.1 实战场景定义为了让例子贴近实际开发我设计一个日常场景一个“生活助手智能体”支持查询当前时间和查询城市天气。这两个信息对模型来说不是训练数据能实时覆盖的必须通过工具函数获取。4.2 创建项目结构项目目录结构如下langgraph-tool-calling/ ├── venv/ ├── agent.py └── requirements.txt其中agent.py是我们的主程序文件requirements.txt记录依赖。创建requirements.txtlanggraph0.2.0 langchain-core0.3.0 langchain-openai0.2.04.3 编写核心代码我们分步骤来写不直接堆一大段代码。第一步导入依赖并定义工具函数。# 文件路径agent.py from datetime import datetime from langchain_core.tools import tool使用tool装饰器定义工具。每个工具函数都需要有清晰的 docstring因为 LangGraph 会把函数名、docstring、参数签名发送给大模型作为模型决定是否调用该工具的依据。tool def get_current_time() - str: 获取当前服务器时间。当用户询问“现在几点”“当前时间”时调用。 now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S) tool def get_weather(city: str) - str: 查询指定城市的天气情况。当用户询问天气时调用。 参数说明 city: 城市名称例如“厦门”“北京”“上海”。 # 这里为了演示返回模拟数据。 # 实际项目中可以接入真实天气 API例如和风天气、高德天气等。 weather_data { 厦门: 晴28度东南风2级, 北京: 多云22度北风3级, 上海: 小雨25度东风2级, } return weather_data.get(city, f暂无{city}的天气数据请确认城市名称是否正确。)这里要解释一下为什么工具函数要写 docstring。大模型并不理解 Python 注释它看到的是由 LangChain 生成的 JSON Schema 函数描述。tool装饰器会自动解析函数名、参数类型、docstring 并转换成 OpenAI 兼容的工具描述格式。如果 docstring 不清晰模型很可能在“该不该调用工具”的判断上出错。第二步创建工具列表并绑定到模型。from langchain_openai import ChatOpenAI tools [get_current_time, get_weather] # 使用环境变量中的 OPENAI_API_KEY 和 OPENAI_BASE_URL llm ChatOpenAI( modelgpt-4o-mini, temperature0 ) # 把工具绑定到大模型模型就能感知到这些工具的存在 llm_with_tools llm.bind_tools(tools)需要说明的是modelgpt-4o-mini只是示例你需要根据实际使用的模型服务商调整模型名称。bind_tools是 LangChain 的方法它会将工具列表转换为大模型接口要求的参数格式。第三步定义图的 State。from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages]这里用到了 TypedDict 定义状态结构。messages字段存储对话消息列表add_messages是 LangGraph 提供的消息合并处理器它会把新节点返回的消息追加到原有消息列表中而不是直接覆盖。第四步定义图节点。我们设计两个核心节点agent节点调用大模型生成回复或工具调用指令。tools节点执行模型请求的工具调用。from langgraph.prebuilt import ToolExecutor, ToolNode from langgraph.graph import StateGraph, END tool_executor ToolExecutor(tools) def agent_node(state: AgentState): 调用大模型核心节点 messages state[messages] response llm_with_tools.invoke(messages) return {messages: [response]} def tools_node(state: AgentState): 执行大模型发起的工具调用 messages state[messages] last_message messages[-1] # 遍历模型输出的所有工具调用请求 tool_outputs [] for tool_call in last_message.tool_calls: action ToolExecutor(tool_executor).execute(tool_call) tool_outputs.append(action)等一下上面的tools_node写法有点绕。实际上 LangGraph 提供了现成的ToolNode我们直接使用即可这样更简洁严谨from langgraph.prebuilt import ToolNode tools_node ToolNode(tools)ToolNode会自动读取上一条 AI 消息中的tool_calls字段找到对应的工具函数并执行然后把结果包装成ToolMessage返回。它的内部逻辑正是我们手动写的那种遍历执行逻辑但封装得更好异常处理也更完善。第五步构建图结构。from langgraph.graph import StateGraph, START, END def should_continue(state: AgentState): 条件路由函数判断是否继续执行工具调用 messages state[messages] last_message messages[-1] if last_message.tool_calls: return tools return end graph StateGraph(AgentState) # 添加节点 graph.add_node(agent, agent_node) graph.add_node(tools, tools_node) # 添加入口边START - agent graph.add_edge(START, agent) # 添加条件边agent 节点根据状态决定下一步走向 graph.add_conditional_edges( agent, should_continue, { tools: tools, end: END } ) # 工具执行结束后回到 agent 节点继续推理 graph.add_edge(tools, agent) # 编译图 app graph.compile()第六步编写 main 函数运行整个流程。def main(): questions [ 现在几点钟了, 厦门今天的天气怎么样, 帮我查一下深圳的天气, ] for question in questions: print( * 50) print(f用户问题{question}) print(- * 50) result app.invoke( {messages: [(human, question)]} ) final_message result[messages][-1] print(f智能体回答{final_message.content}\n) if __name__ __main__: main()注意这里传入的初始 State 结构是{messages: [(human, question)]}LangGraph 会自动将(human, question)转换为HumanMessage对象并存入状态。4.4 运行与验证在命令行执行python agent.py预期输出会分为三种情况第一种不需要调用工具。比如询问“你好”模型直接回复不会经过 tools 节点。第二种需要调用工具。比如询问“现在几点钟了”agent 节点会判断需要调用 get_current_time然后 tools 节点执行该函数把执行结果交还给 agentagent 基于工具返回的数据生成最终回答。第三种多个工具连续调用。如果用户提问涉及多个工具LangGraph 会重复执行 agent → tools → agent 的循环直到 agent 不再发起新的工具调用为止。4.5 结果说明从上面的例子可以看到LangGraph 工具调用的完整流程非常清晰用户消息进入图。agent 节点调用大模型模型返回带tool_calls的消息。条件函数检测到需要调用工具路由到 tools 节点。tools 节点执行函数生成 ToolMessage。ToolMessage 进入 agent 节点模型结合工具结果生成最终回答。最终回答输出给用户。这就是一个最基本的 ReAct 智能体循环。理解了这个循环LangGraph 的绝大多数功能都能顺着这个思路展开。5. 进阶实战多工具协作与条件路由5.1 定义更复杂的工具实际开发中工具往往不只是返回固定字典而是需要访问数据库、调用第三方 API、处理文件等。我们扩展一个例子增加一个“计算器”工具和一个“获取用户订单状态”的工具演示一下带有参数校验和多工具组合的场景。tool def calculate(expression: str) - str: 计算数学表达式的值。当用户需要计算时调用。 参数说明 expression: 数学表达式字符串例如 1 2 * 3。 try: result eval(expression) return f计算结果{result} except Exception as e: return f计算失败{str(e)}请检查表达式格式。 tool def get_order_status(order_id: str) - str: 查询订单的物流状态。当用户查询订单时调用。 参数说明 order_id: 订单号例如 ORD123456。 # 模拟数据实际项目可接入订单系统数据库 order_db { ORD123456: 已发货预计明天送达, ORD789012: 正在拣货中, } return order_db.get(order_id, f未找到订单 {order_id}请确认订单号是否正确。)真实场景的注意事项eval函数存在安全风险直接在生产环境中执行不可信输入可能导致代码注入。上面的calculate工具仅用于教学演示生产环境应该使用安全的表达式解析库例如asteval或者自己写词法/语法解析器。5.2 注册多个工具把新工具加入工具列表tools [get_current_time, get_weather, calculate, get_order_status]后续的llm.bind_tools(tools)和ToolNode(tools)代码无需修改框架自动处理多工具的绑定和执行。这也是 LangGraph 设计得比较好的地方工具的增删不会影响图结构骨架。5.3 并行工具调用的行为如果用户提问“现在几点顺便查一下厦门的天气”模型可能会在一次响应中输出两个工具调用请求。ToolNode会依次执行这两个调用然后把所有ToolMessage一起返回给 agent 节点。这种设计减少了 agent 循环的次数降低了延迟。但要注意如果两个工具之间有依赖关系后一个工具需要前一个工具的输出作为参数那就不能并行调用需要拆分成多个 agent 循环步骤或者手动设计专门的编排节点。5.4 条件路由的进阶写法前面should_continue返回的是字符串。LangGraph 的条件边本质是一个从 State 到“下一节点名”的映射。我们可以设计更丰富的路由规则比如根据工具调用结果决定下一步进入人工审核节点还是直接结束。def should_continue(state: AgentState): messages state[messages] last_message messages[-1] if not last_message.tool_calls: return end # 示例如果某个工具调用返回错误信息则进入人工处理节点 tool_calls last_message.tool_calls for call in tool_calls: if call[name] get_order_status and 未找到 in call.get(args, {}).get(order_id, ): return human_review return tools这个示例说明路由判断完全可以基于业务逻辑来定制。图结构不只是“模型决定一切”开发者可以在关键节点上插入自己的规则这在大模型智能体落地时非常重要因为有些业务逻辑不能全部交给模型判断。6. LangGraph 与 LangChain 的关系梳理很多初学智能体开发的朋友容易混淆 LangGraph 和 LangChain这里做一个简单梳理。LangChain 是更底层的工具集它提供了模型封装ChatOpenAI、提示词模板PromptTemplate、文本分割器、向量存储封装、Agent 的早期实现等。你可以用 LangChain 拼装一条“链”Chain但链是线性或简单的串行结构难以表达复杂的条件和循环。LangGraph 则是在 LangChain 基础之上构建的图编排框架。它使用图的数据结构来表达节点之间的依赖关系支持条件路由、循环、子图、并行分支等复杂流程。两者不是替代关系而是互补关系。LangGraph 中的节点内部通常还是调用 LangChain 的组件。比如我们上文中的ChatOpenAI就是 LangChain 的模型封装tool装饰器来自 LangChain Core。用一句话概括LangChain 提供“积木”LangGraph 提供“搭建图纸和施工流程”。对于复杂智能体项目LangGraph 是更合适的选择对于简单链式调用LangChain 反而更轻量。7. 常见问题与排查思路工具调用的开发过程中报错和异常行为集中在几个方面。下面整理高频问题。问题现象常见原因解决思路模型从不调用工具工具描述不清晰或模型不支持 function calling检查工具 docstring 是否明确确认模型服务商是否支持工具调用尝试更换模型工具执行报错导致流程中断工具函数内部未捕获异常在工具函数内部捕获所有异常返回可读错误信息避免直接抛出异常模型虚构工具返回结果工具结果没有被正确传回模型检查 State 中 messages 是否使用了 add_messages 处理器确认 ToolNode 是否返回 ToolMessage循环调用工具停不下来条件路由函数逻辑有误打印每次 last_message.tool_calls 内容检查路由分支是否符合预期提示缺少 api_key环境变量未设置检查 OPENAI_API_KEY 或对应服务商的环境变量是否正确设置模型返回格式解析失败使用的大模型工具调用接口与 LangChain 版本不兼容检查 langchain-openai 版本模型服务商是否使用标准 OpenAI 格式7.1 如何调试 LangGraph 流程调试智能体流程时建议先打印每一步的 State 变化。可以编写一个辅助函数def debug_invoke(question: str): 带调试信息的运行函数 print(f\n用户问题{question}) print(- * 40) # 逐步遍历每个节点的事件 config {recursion_limit: 10} for event in app.stream( {messages: [(human, question)]}, configconfig, stream_modeupdates ): print(f图事件: {event}) print( * 40)app.stream会逐步产出每个节点的运行结果stream_modeupdates模式输出的是每个节点的状态更新。通过这个输出你能清楚地看到 agent 节点产生了什么消息、tools 节点调用了哪些工具、路由是如何决策的。7.2 recursion_limit 错误LangGraph 默认限制图的最大递归次数通常为 25。如果你的智能体在 agent 和 tools 之间循环过多会触发类似RecursionError: Maximum recursion limit reached的错误。解决方法有两种第一种检查为什么循环次数过多。通常是工具返回结果没有解决模型的问题导致模型反复尝试调用同一个工具。这时应该检查工具返回的信息质量。第二种调整递归上限result app.invoke( {messages: [(human, question)]}, config{recursion_limit: 50} )注意调大上限只是临时缓解根本解决办法是优化工具调用逻辑。7.3 工具参数格式错误如果模型传给工具的参数类型不对比如定义了整数参数却传入字符串工具执行时会报错。建议在工具函数内部做一层容错转换tool def get_user_age(user_id: str) - str: 根据用户ID查询年龄。 try: age int(user_id) 10 # 模拟查询结果 return f用户年龄{age} except (ValueError, TypeError): return f用户ID格式不正确{user_id}更稳妥的方式是使用 Pydantic 模型定义工具输入结构LangChain 的tool装饰器支持通过args_schema参数指定输入校验模型。不过对于大多数内部工具简单的 try-except 已经能满足需求。8. 最佳实践与工程建议8.1 工具命名与描述规范工具的命名和描述直接影响模型调用工具的准确率。这里给出三条建议第一条工具名使用动词开头的蛇形命名例如get_weather、send_email、create_order避免使用含糊的名称如action1、util。第二条docstring 要写清楚“什么场景下使用”和“关键参数的含义”。不要只写“获取天气”而是要写“当用户询问某个城市的天气、温度、降水情况时调用城市参数使用中文名称”。第三条如果多个工具功能类似避免让模型混淆。例如有get_weather和get_air_quality两个工具描述中要明确区分两者的边界。8.2 错误处理与容错设计工具函数是智能体感知外部世界的重要通道。外部世界充满不确定性网络超时、数据库连接失败、参数异常、权限不足等。建议每个工具函数都遵循以下模式捕获所有预期异常。返回可读的错误信息而不是抛出异常中断流程。对于无法恢复的错误可以在返回消息中明确提示“需要人工介入”。这样做的好处是即使某个工具失败智能体依然可以基于错误信息给用户一个合理的回复而不是整个流程崩溃。8.3 日志与可观测性生产环境的智能体必须有完整的日志记录。建议为每个工具调用记录以下信息模型生成的工具调用原始信息工具名、参数。工具执行耗时。工具执行结果或错误信息。整个图的运行轨迹哪个节点、哪个分支、递归次数。LangGraph 本身支持在节点函数中打日志也可以接入 LangSmith 这类可观测性平台。对于个人项目最简单的做法是在节点函数里打印或写入日志文件。8.4 权限与安全边界工具调用赋予了大模型“行动”的能力也就意味着它可能在没有任何人类监督的情况下执行代码、调用 API、修改数据。这在生产环境中是必须严肃对待的问题。我的建议是遵循最小权限原则每个工具只暴露完成该任务所需的最小能力。例如一个订票智能体模型应该只能调用“查询航班”和“创建订单”接口而不应该接触“取消订单”或“修改用户信息”等高危操作。同时涉及写操作创建、修改、删除的工具应该在执行前增加确认机制或者使用人工审核节点进行把关。8.5 工具状态与幂等性在设计工具时尽量保证工具的幂等性。所谓幂等就是同一个请求执行一次和执行多次产生的结果是相同的。这个特性在智能体的重试场景下非常重要因为模型可能会因为网络超时而重发同一个工具调用如果不做幂等处理就可能产生重复订单、重复发送消息等问题。8.6 重视模型选择与成本控制工具调用能力在不同模型上有明显差异。较新的模型在遵循工具调用指令、参数解析、多工具并行方面通常表现更好。如果你的智能体频繁出现“不调用工具”或“参数传错”的问题可以优先考虑升级模型。另外每次 agent 和 tools 之间的循环都会消耗大模型的 Token。合理控制循环次数、精简工具描述、避免让模型反复调用同一工具都是成本优化的方向。如果你使用的是按量计费的大模型 API建议在代码中限制单次对话的最大工具调用次数MAX_TOOL_CALLS 5 def track_tool_calls(state: AgentState): 统计工具调用次数超过上限则停止 tool_call_count 0 for message in state[messages]: if hasattr(message, tool_calls) and message.tool_calls: tool_call_count len(message.tool_calls) return tool_call_count当然更优雅的做法是在 State 中增加一个计数器字段在工具节点中递增并把它纳入路由判断条件。9. 总结与后续学习建议本文围绕 LangGraph 的工具调用机制一步步搭建了一个可运行的智能体应用覆盖了工具定义、模型绑定、图构建、条件路由、多工具协作、调试排错和生产建议等关键环节。你现在应该能回答这几个问题了LangGraph 为什么用图来组织智能体流程工具调用和普通函数调用的区别在哪里一个带工具调用的智能体是如何在 agent 节点和 tools 节点之间循环的条件路由函数如何处理“有工具调用”和“无工具调用”两种分支多工具并行调用时LangGraph 是怎么处理返回结果的如果这篇文章的代码你已经全部跑通下一步可以尝试以下扩展方向第一加入记忆持久化。LangGraph 支持基于 Checkpoint 的状态持久化这样跨会话的对话历史和工具调用状态都能保存下来。第二尝试子图Subgraph。当智能体流程复杂到一定程度可以把一组节点封装成子图作为主图的一个节点使用。第三研究并行分支。LangGraph 支持在一次执行中并行走多个节点适合处理多个工具调用相互独立的场景。第四接入真实业务工具。把天气 API、订单数据库、内部系统接口接入工具函数让智能体真正开始解决实际问题。最后想提醒的是大模型智能体的开发本质上是一个系统工程。模型能力只是一部分更重要的是工具设计的质量、状态管理的清晰度、错误处理的完善度。希望这篇文章能帮你把 LangGraph 工具调用这块基础打牢以后无论多复杂的智能体系统你都能从图和节点两个层次快速理解其运转逻辑。如果你在实操中遇到问题欢迎对照第七节的排查表格逐项检查通常能快速定位到原因。祝编码顺利。