十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LangGraph工具调用实战:从工作原理到可运行Agent

LangGraph工具调用实战:从工作原理到可运行Agent 如果你已经让大模型写过代码、整理过文档很快就会撞上一个尴尬的事实它聊得再流畅也查不了今天厦门的天气读不了你数据库里的真实订单更不可能替你提交一条工单。原因很简单——大语言模型本质上只会“生成文本”它并不具备执行动作的能力。真正的智能体Agent应用恰恰需要让模型学会“调用工具”把决策交给模型把执行交给函数。这个能力就是工具调用Tool Calling / Function Calling。在众多 Agent 编排框架里LangGraph 把“模型 → 工具 → 模型”这条循环链路做成了标准化的图结构既保留了传统流程编排的确定性又获得了大模型自主决策的灵活性。本文以厦门大学林子雨老师《AI编程与智能体开发》课程第 9 章的 LangGraph 工具调用为背景不只看概念更重要的是带你把一个可运行的工具调用 Agent 完整跑通。读完本文你会得到四样东西工具调用的底层运行机制、可直接复制运行的代码示例、常见问题的排查清单、以及生产环境里真正值得注意的工程经验。1. 工具调用到底解决了什么问题1.1 大模型的能力边界只会说不会做很多人第一次用大模型时会有一种错觉它什么都知道。但实际上大模型的训练数据存在时间截点模型本身也无法感知实时信息。你问它“今天广州天气怎么样”它只能凭训练数据里的大致规律猜测而不是真正去查一次天气服务。更关键的是模型没有“操作系统权限”。它不能创建文件、不能写数据库、不能调用你公司的内部 API。换句话说模型是一个“大脑”但缺少“手脚”。工具调用要解决的核心问题就是给这个大脑接上手脚。技术上来说工具调用分成两部分模型侧能力模型在生成回答时除了生成普通文本还可以输出一个结构化的“调用意图”即它希望调用哪个函数、传入什么参数。应用侧执行你的程序识别到这个意图后真正去执行对应函数再把执行结果当作一条消息返回给模型让模型基于结果继续回答。“会调用工具”和“只是聊天”是智能体与聊天机器人的分水岭。1.2 Function Calling 与工具调用是同一个概念吗很多文章会把 Function Calling 和工具调用混着说其实它们的侧重点略有不同。Function Calling 通常指模型接口层面的能力。以 OpenAI 为例当你把一组函数的名称、描述、参数结构JSON Schema传给模型模型在需要时会返回一个tool_calls字段。这个字段不是自然语言而是一段结构化 JSON明确告诉程序“请调用get_weather参数是{city: 厦门}”。工具调用则更偏应用层它是你程序里的一套完整处理流程接收模型返回的tool_calls根据函数名找到对应函数用模型给出的参数执行函数把执行结果封装成ToolMessage回传给模型模型读取结果生成最终回答这套流程听起来不难但如果你自己手写会很快遇到一个麻烦模型的调用不一定一次成功它可能根据工具结果需要再调用一次、两次甚至多次。也就是说这天然是一个循环。写循环不是问题问题是这个循环里包含状态管理、分支判断、异常处理一旦业务复杂起来代码会很快失控。1.3 为什么选择 LangGraph 来做工具调用LangGraph 是 LangChain 团队推出的一个面向 Agent 编排的框架它的核心思想是把智能体应用建模成一张有向图。在 LangGraph 里做工具调用优势非常明显把“模型循环调用工具”这个流程画成图逻辑清楚可见而不是淹没在一堆while和if里。官方提供了ToolNode和tools_condition两个预构建组件工具执行和路由判断不需要自己重复造轮子。图结构天然支持条件分支、子图、并行节点后续扩展到复杂业务流程时不会推倒重来。配合 LangGraph 的状态机制每一轮消息都能正确追加、保留不会出现多轮对话“失忆”的问题。这也是为什么越来越多的 AI 编程课程开始把 LangGraph 作为智能体开发主线的底层框架。它把“智能体怎么做”变成了一张可以调试、可以观察、可以部署的工程图。2. 核心概念与工作机制在写代码之前有必要先把 LangGraph 工具调用里的几个核心概念讲清楚。因为直接看代码容易陷入“会跑但不懂为什么能跑”的状态。2.1 工具Tool工具在 LangGraph 中的本质是一个普通的 Python 函数但需要额外的元信息函数名、函数描述、参数说明。这些元信息不只有文档价值它们会被序列化成模型能理解的 JSON Schema模型正是根据这些描述来决定“什么时候调用这个函数、传什么参数”。举个例子一个查询天气的工具函数可能只有几行实现tool def get_weather(city: str) - str: 查询指定城市的当前天气情况。 ...那个三引号里的中文描述会被 LangChain 框架提取出来和city参数的类型一起形成工具的 JSON Schema。你写的描述越清楚模型调用越准确。2.2 bind_tools模型如何“知道”工具存在你定义好了工具但模型本身并不知道这些工具的存在。bind_tools就是完成这一步的桥梁。llm_with_tools llm.bind_tools(tools)bind_tools会把你定义的一组工具转换成模型接口要求的格式并绑定到模型对象上。之后每次调用这个模型它在推理时都会把工具列表纳入考虑范围。关键点在于绑定工具后模型会多出一个判断能力——这个问题我直接回答还是需要调用某个工具如果模型认为需要调用工具它会在返回的AIMessage对象上带上tool_calls字段。注意这时的模型输出不是最终答案而是一个“计划”。2.3 ToolNode真正执行工具的地方拿到了模型的调用计划后谁来负责执行答案是ToolNode。ToolNode是 LangGraph 官方提供的一个预构建节点你只需要在初始化时把工具列表传给它tools_node ToolNode(tools)当图执行到tools节点时它会读取上一条 AI 消息里的tool_calls逐条执行对应的函数然后把每个执行结果封装成ToolMessage追加到消息列表中。这里有个非常容易忽略的细节ToolMessage必须携带一个tool_call_id用于和模型发出的调用请求一一对应。ToolNode会自动处理这个匹配关系不需要你手写。这也是为什么用预构建组件能省掉大量重复代码。2.4 消息循环Agent 的核心运行模式有了模型节点和工具节点LangGraph 里的 Agent 循环就建立了用户输入作为HumanMessage进入图。agent节点调用绑定了工具的模型模型可能直接回答也可能返回tool_calls。如果模型要求调用工具条件路由tools_condition会把执行流转到tools节点。tools节点执行工具生成ToolMessage然后执行流回到agent节点。模型看到了工具执行结果可以继续调用下一个工具也可以整理结果生成最终回答。当模型不再返回tool_calls时路由指向END整个图运行结束。这个过程类似人类在解决问题时“思考 → 查资料 → 再思考 → 再查资料 → 得出结论”的节奏。LangGraph 把这个循环明确地建模成了图中的一条回路。2.5 消息状态与 add_messagesLangGraph 的状态机制是让这个循环正常工作的基础。在定义图的状态时通常会这样写class AgentState(TypedDict): messages: Annotated[list, add_messages]add_messages是 LangGraph 提供的一个消息合并函数。它保证了每一轮节点返回的消息都能追加到已有的消息列表中而不是覆盖旧消息。这样Agent 在每一轮循环中都能看到完整的对话历史模型也才能基于前一轮的工具执行结果去做出下一步判断。不用add_messages会怎样如果只是简单的messages: list那么每次节点返回都会覆盖上一步的消息模型看到的上下文是不连续甚至缺失的多轮工具调用自然无法工作。3. 环境准备与前置条件3.1 运行环境本文示例基于 Python 环境开发。建议使用 Python 3.9 及以上版本并创建独立的虚拟环境避免依赖冲突。python -m venv .venv source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate3.2 安装 LangGraph 相关依赖需要安装的核心库包括langgraph、langchain-core、langchain-openai。如果你的模型接入的是其他供应商可以替换对应的库但消息结构和图构建逻辑是通用的。pip install -U langgraph langchain-core langchain-openai版本以当前 PyPI 上的稳定版本为准。LangGraph 的 API 在 0.2 以上已经比较稳定本文代码按这套 API 编写。3.3 模型接入配置工具调用依赖模型侧支持 function calling。OpenAI 的 GPT 系列、通义千问、智谱 GLM、DeepSeek 等模型基本都支持 OpenAI 兼容的接口配置方式类似。使用 OpenAI 时直接设置环境变量export OPENAI_API_KEY你的_api_key如果使用 OpenAI 兼容接口的国内模型则通过base_url指定接口地址llm ChatOpenAI( modelqwen-plus, # 模型名称以实际账号支持的为准 api_key你的_api_key, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1, )注意模型名称和接口地址以各家平台官方文档为准本文只演示通用写法。4. 最小示例让 Agent 学会查询天气现在开始写第一个可运行的示例。这个示例包含一个天气查询工具以及完成一次工具调用的完整图流程。4.1 定义工具函数创建一个文件weather_agent.py# 文件路径weather_agent.py from typing import Annotated, TypedDict from langchain_core.tools import tool from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode, tools_condition # 1. 定义工具查询城市天气 tool def get_weather(city: str) - str: 查询指定城市的当前天气情况。 weather_map { 厦门: 小雨24℃东南风2级, 北京: 多云22℃北风3级, 广州: 晴28℃微风, } return weather_map.get(city, f暂未收录 {city} 的天气信息)这里tool装饰器会把函数转换成 LangChain Tool 对象。函数名get_weather会成为工具名参数city被解析为必填字符串参数注释内容则作为工具描述提供给模型。4.2 定义图状态和模型继续在同一个文件中添加状态定义和模型初始化代码# 2. 定义图状态messages 使用 add_messages 增量合并 class AgentState(TypedDict): messages: Annotated[list, add_messages] # 3. 初始化模型并绑定工具 llm ChatOpenAI(modelgpt-4o-mini, temperature0) tools [get_weather] llm_with_tools llm.bind_tools(tools) # 4. Agent 节点把当前消息列表交给模型并返回模型的响应 def agent_node(state: AgentState): response llm_with_tools.invoke(state[messages]) return {messages: [response]}agent_node是图的第一个节点它的职责很简单读取当前全部消息让模型决定是直接回答还是调用工具。4.3 构建图并添加循环路由接下来是 LangGraph 工具调用最关键的部分——构建图# 5. 构建 StateGraph builder StateGraph(AgentState) # 添加两个节点agent 负责推理tools 负责执行工具 builder.add_node(agent, agent_node) builder.add_node(tools, ToolNode(tools)) # 入口用户消息先进 agent builder.add_edge(START, agent) # 条件路由模型返回 tool_calls 就进入 tools否则直接结束 builder.add_conditional_edges(agent, tools_condition) # 工具执行完回到 agent进入下一轮推理 builder.add_edge(tools, agent) graph builder.compile()tools_condition是 LangGraph 预置的路由函数。它的逻辑很简单检查最后一条 AI 消息是否包含tool_calls。如果有返回tools没有则返回__end__。最后一定要加builder.add_edge(tools, agent)。工具执行完成后必须回到 Agent 节点让模型看工具结果否则整个流程就断了。4.4 运行与验证在主程序中调用图# 6. 运行 if __name__ __main__: result graph.invoke({ messages: [HumanMessage(content厦门今天天气怎么样)] }) for msg in result[messages]: print(f[{msg.type}] {msg.content})运行方式python weather_agent.py预期输出类似[human] 厦门今天天气怎么样 [ai] 好的我帮你查询一下厦门今天的天气。 [tool] 厦门小雨24℃东南风2级 [ai] 厦门今天有小雨气温约 24℃东南风 2 级出门建议带伞。注意这里出现了四种类型的消息human、ai、tool、ai。第二条ai是模型第一次看到问题后生成的内容同时它决定调用get_weather。第三条tool是工具执行结果。第四条ai是模型读取天气结果后生成的最终回答。如果输出里出现了tool类型的消息说明工具调用链路是通的。如果只有一条ai直接回答很可能模型认为不需要调用工具或者工具描述不够清晰无法触发调用。5. 进阶实战多工具协同与自定义路由第一小节跑通后我们来看一个更贴近真实业务的场景用户同时需要两个工具协同完成一次请求。这也是 LangGraph 工具调用中很容易忽略但极具价值的能力。5.1 业务场景假设用户提出这样一个问题“帮我算一下 2 的 10 次方是多少顺便看看北京天气。”这是一个典型的复合问题需要两个工具一个数学计算器一个天气查询器。模型在一次输出中可能返回多次tool_calls也就是同时请求调用两个工具。5.2 工具定义与安全检查创建新文件multi_tool_agent.py# 文件路径multi_tool_agent.py import ast from typing import Annotated, TypedDict from langchain_core.tools import tool from langchain_core.messages import HumanMessage from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode tool def get_weather(city: str) - str: 查询指定城市的当前天气情况。 weather_map { 厦门: 小雨24℃东南风2级, 北京: 多云22℃北风3级, 广州: 晴28℃微风, } return weather_map.get(city, f暂未收录 {city} 的天气信息) tool def calculate(expression: str) - str: 计算数学表达式的值例如 1 2 * 3。 # 仅允许算术运算相关的 AST 节点避免任意代码执行 allowed_nodes ( ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod, ast.USub, ast.UAdd, ) try: tree ast.parse(expression, modeeval) for node in ast.walk(tree): if not isinstance(node, allowed_nodes): return 表达式包含不支持的运算 return str(eval(expression, {__builtins__: {}}, {})) except Exception as e: return f计算失败: {e}这里特意给calculate加了一层 AST 安全检查。实际场景中如果工具函数要执行用户传来的代码字符串必须做白名单校验否则任何能访问工具的人都可能通过表达式注入执行任意代码。开放工具调用的同时安全边界必须同步做好。5.3 自定义路由函数tools_condition适用于大多数场景但为了说明路由原理这里我写一个等价的自定义路由函数# 图状态 class AgentState(TypedDict): messages: Annotated[list, add_messages] # 自定义路由判断最后一条消息是否需要调用工具 def should_continue(state: AgentState): last_message state[messages][-1] if last_message.tool_calls: return tools return endlast_message.tool_calls是AIMessage上的一个属性列表类型。当模型决定调用工具时里面会有调用信息否则为空列表。5.4 构建并运行多工具图# 初始化模型并绑定两个工具 llm ChatOpenAI(modelgpt-4o-mini, temperature0) tools [get_weather, calculate] llm_with_tools llm.bind_tools(tools) def agent_node(state: AgentState): response llm_with_tools.invoke(state[messages]) return {messages: [response]} # 构建图 builder StateGraph(AgentState) builder.add_node(agent, agent_node) builder.add_node(tools, ToolNode(tools)) builder.add_edge(START, agent) builder.add_conditional_edges( agent, should_continue, { tools: tools, end: END, }, ) builder.add_edge(tools, agent) graph builder.compile() if __name__ __main__: result graph.invoke({ messages: [HumanMessage(content帮我算一下 2 的 10 次方是多少顺便看看北京天气)] }) for msg in result[messages]: print(f[{msg.type}]) if msg.tool_calls: for call in msg.tool_calls: print(f 调用工具: {call[name]}, 参数: {call[args]}) if msg.content: print(f 内容: {msg.content})运行python multi_tool_agent.py预期输出会出现类似这样的流程[human] 内容: 帮我算一下 2 的 10 次方是多少顺便看看北京天气 [ai] 调用工具: calculate, 参数: {expression: 2 ** 10} 调用工具: get_weather, 参数: {city: 北京} [tool] 内容: 1024 [tool] 内容: 北京多云22℃北风3级 [ai] 内容: 2 的 10 次方是 1024。北京今天多云气温 22℃北风 3 级。注意一个关键细节第一条ai消息同时返回了两个tool_calls而ToolNode会逐个执行这两个工具并把两个结果都封装成ToolMessage追加到消息列表。然后流程回到agent模型看到了两个工具结果最终汇总成一段自然的回答。如果这个过程中某个工具报错ToolNode会把错误文本也封装成ToolMessage传给模型由模型根据错误信息决定是换个参数重试还是直接向用户说明失败原因。这正是 LangGraph 适合构建 Agent 的原因错误处理逻辑也可以被模型纳入“思考”范围。6. 常见问题与排查思路工具调用看起来很直观实际运行起来会遇到不少问题。下面是一份按经验整理的高频问题排查表。问题现象可能原因排查方式解决方案模型永远不返回 tool_calls工具描述不清晰模型不支持 function callingbind_tools 未生效打印模型原始输出检查是否包含 tool_calls 字段优化工具名称和描述确认选择支持工具调用的模型检查 tools 是否传入 bind_tools模型返回了 tool_calls 但工具没执行条件路由配置错误节点名不匹配检查 add_conditional_edges 中返回值和节点名是否一致让路由函数返回值与 builder.add_node 的节点名保持一致图运行后无限循环不结束缺少“无 tool_calls 就结束”的路由分支检查路由函数是否有返回 END 的分支确保最后一条 AI 消息没有 tool_calls 时能正确路由到 ENDToolMessage 报 tool_call_id 不匹配手动构造消息时未携带 tool_call_id检查代码中是否直接构造 ToolMessage优先使用 ToolNode它会自动处理 tool_call_id 对应关系多轮工具调用时模型“失忆”状态里的 messages 字段没有使用 add_messages 合并检查状态定义是否为 Annotated[list, add_messages]修正状态定义确保消息增量追加工具执行报了业务异常模型无法理解异常未封装成工具结果而是直接抛给了图查看完整堆栈确认异常发生在工具内还是框架内在工具函数内部捕获异常返回可读文本而不是抛异常调用 OpenAI 兼容接口 401API Key 或 base_url 配置错误检查环境变量检查模型是否有接口访问权限重新配置 api_key 和 base_url确认模型名正确最容易被忽略的是第一条。很多初学者往往把重点放在图构建和路由上却忘了真正决定模型“会不会调用工具”的是工具的描述质量。一个含糊的get_weather(city)模型可能只有在用户明确提到“天气”两个字时才会想起它而一个写清楚“查询指定城市当前天气情况城市用中文名称”的描述会让模型在更多场景下主动触发调用。7. 工具调用的最佳实践与工程建议7.1 工具设计描述比实现更重要模型是靠函数名和描述来理解工具的。工具命名要简短准确描述要说明两方面工具能做什么以及适合在什么场景下使用。比如“查询指定城市的当前天气情况”比“天气”好得多如果再补充“返回结果包含温度和风力城市名使用中文”触发准确率会更高。参数设计也有讲究。能拆成多个参数的不要合成一个大 JSON 字符串。参数名要见名知义并尽量给每个参数设置明确的类型。LangChain 会根据类型自动生成 JSON Schema类型越准确模型传参就越规范。7.2 工具内必须做参数校验与异常兜底工具会接收模型生成的参数而模型生成的参数并不会完全遵守约束。比如某个城市不在你的数据表里比如表达式包含非法字符。这些情况必须在工具内部处理好。一个好的工具函数应该是“永不抛异常”的把所有可能的失败都转成一条可读的返回文本让模型根据反馈决定下一步。如果工具直接抛异常整个图会中断程序可能陷入不可恢复的状态。这在生产环境里是不可接受的。7.3 权限与安全边界工具调用意味着大模型获得了“执行动作”的能力这是一个高风险能力。在生产环境接入工具时必须遵循最小权限原则只开放当前业务真正需要的工具不要一股脑绑定所有函数。工具内部应做二次校验不要把权限校验完全托付给模型。如果工具涉及写入操作、删除操作或资金相关操作一定要加入人工确认环节。读者在 LangGraph 后续章节会看到这个概念官方叫 Human-in-the-loop语言模型生成调用计划可以但执行必须经过人工审批。涉及数据库操作时先确认连接、先备份、在测试环境中验证任何变更都要能回滚。7.4 日志与可观测性调试 Agent 应用最大的痛点是“不确定模型为什么做出了某个决定”。因此每一步都需要日志记录模型收到了哪些消息、输出了哪些 tool_calls、工具返回了什么结果。可以把这些信息输出到结构化日志里方便回溯。# 在 agent_node 里加日志 def agent_node(state: AgentState): response llm_with_tools.invoke(state[messages]) if response.tool_calls: print(f[Agent 决策] 调用 {len(response.tool_calls)} 个工具) for call in response.tool_calls: print(f - {call[name]}({call[args]})) return {messages: [response]}这里的日志逻辑简单但能大幅降低调试难度。更复杂的项目可以接入 LangSmith 或自建 trace 平台。7.5 控制工具数量与调用深度每次把工具列表发送给模型都会增加输入 Token 消耗。工具数量过多也会让模型选择变得不稳定。实际项目中不要把所有内部服务都绑定到一个 Agent 上而是按业务域拆分多个专注的 Agent每个 Agent 只持有少量工具。这也对应了后面会遇到的“多 Agent 架构”话题。同时可以为工具的调用次数设定上限防止模型在某个异常场景下陷入“工具调用死循环”。比如用计数器记录循环次数超过阈值就返回固定提示并结束。7.6 何时不要用工具调用需要明确的是并不是所有函数都要做成工具。如果某个操作是流程里固定执行的不需要模型来做决策那么就不应该通过工具调用触发。例如“用户登录后必然要初始化用户缓存”这种确定性流程应该直接写在业务代码里不该让模型决定是否执行。工具调用的价值在于“让模型自主判断何时需要外部信息”而不是把整个业务逻辑交给模型。8. 总结与后续学习方向LangGraph 工具调用的核心是把大模型从“只能生成文本”升级为“可以执行动作”。模型负责决策工具负责执行LangGraph 负责把这两者之间的循环编排成一张可调试的图。本文带你完成了从最小天气 Agent 到多工具协同的完整实践这套逻辑是后续学习条件路由、子图、人工介入、多 Agent 协作的基础。如果你想继续深入建议按下面顺序推进先改造本文示例增加不同领域的工具观察工具描述变化后模型调用准确率的变化。学习 LangGraph 的checkpointer让 Agent 拥有多轮对话记忆。学习子图Subgraph把复杂的工具流程拆成多个可复用的子图。学习 Human-in-the-loop 机制为高风险工具加入人工审批环节。一个实用的建议是把本文的天气工具换成你工作里真实的数据查询接口、内部 API 或者计算服务跑通一遍属于你自己的工具调用 Agent。只有当你亲手调试过模型误调用、参数传错、工具返回异常这些真实问题之后才算真正掌握了智能体开发里的这一节关键内容。
返回列表