恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

LangGraph工具调用实战:从最小闭环到工程化Agent

  • 首页
  • 资讯中心
  • /
  • LangGraph工具调用实战:从最小闭环到工程化Agent

相关资讯

STM8 SPI从机通讯程序设计与联调实战 2026/9/1 9:50:43
用Claude搭建个人知识库写作助手:从Projects到API调用 2026/9/1 9:50:43
STM32与XW12A电容触摸按键方案:I2C驱动、灵敏度调节与低功耗设计 2026/9/1 9:50:43

最新资讯

AI Agent驱动软件自主进化:从大模型到工程实践
MKVToolNix:无损处理MKV视频的利器,快速合并提取音轨字幕
SKILL.state:用显式执行状态破解Agent上下文膨胀难题
PyInstaller一键打包:从Python脚本到exe的自动化方案
LangGraph多智能体编排实战:条件路由、子图与并行分支详解
GLM-OCR模型构成拆解:CogViT视觉编码器+轻量连接器+GLM-0.5B解码器完整指南

今日推荐

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

LangGraph工具调用实战:从最小闭环到工程化Agent

发布时间:2026/9/1 9:50:43
LangGraph工具调用实战:从最小闭环到工程化Agent LangGraph 的工具调用听起来像是给模型多接几个函数但真正决定一个 Agent 能不能用起来的不是函数本身而是调用链路的组织方式。最近在看 AI 编程与智能体开发相关内容时我再次确认了一个判断很多初学者学到这里会卡住不是因为不会写tool而是模型确实输出了tool_calls但消息没有传回模型、工具结果没有回填、循环没有终止条件最后整个 Agent 变成了一次性的跑通 demo。这篇文章想从一次完整调用出发把 LangGraph 里工具调用的几个关键环节拆开讲清楚。你会看到工具、模型和 Agent 是怎么协作的也会看到从最小例子到工程化落地之间到底差在哪几步。1. 先搞清楚工具调用到底解决的是哪一类问题1.1 模型缺的不是知识而是行动入口语言模型的训练对象是静态文本它知道很多常识但无法实时读取数据库、查询天气、修改订单、执行命令。你问它“厦门明天适合出门吗”它可能凭常识给一个模糊答案但如果你给它一个query_weather(city)工具它就能把“查询厦门天气”这个意图转换成一个结构化的调用请求由你的程序真正去执行。这个机制通常被称为 Tool Calling 或 Function Calling。关键点在于模型不是真的在调用 Python 函数它是在生成一段结构化的调用请求比如工具名、参数、调用 ID。真正的执行发生在模型之外也就是你的代码里。LangGraph 做的是把这一段“决策、执行、反馈、再决策”的过程编排成一个可循环的图。放在 AI 编程场景里会更直观。Coding Agent 要读文件、跑测试、查报错、改代码它不是靠模型凭空输出完整源代码而是通过read_file、run_command、search_files这类工具去和真实项目环境交互。没有工具调用模型就只是一个会说话的编辑器有了工具调用它才可能变成一个能动手的智能体。1.2 LangChain 与 LangGraph一个是工具箱一个是调度系统刚接触的人很容易把 LangChain 和 LangGraph 搞混。我理解它们的差异在于LangChain 提供组件LangGraph 负责编排。LangChain 里有tool、ChatOpenAI、bind_tools这些是零件。LangGraph 里有StateGraph、节点、边、状态、条件路由这些是流程。工具调用在 LangChain 里是“把 tools 传给模型”在 LangGraph 里则是“如何在图的状态里安排一次或多次调用”。如果你只是做一个单轮问答直接调模型加bind_tools就够了。但如果一个任务需要模型反复调用多个工具、中途可能失败、需要人工确认或者要根据结果决定走哪条分支LangGraph 就更合适。它不是给模型增加了一个新功能而是给工具调用加上了状态和流程控制。2. 从一次工具调用的完整流程看 LangGraph 的设计2.1 一次工具调用的完整链路决策、执行、反馈、再决策在 LangGraph 里一次工具调用至少要经过这几步用户消息进入 State也就是 Agent 的全局状态。模型节点读取当前messages判断要不要调用工具。如果模型输出了tool_callsAgent 不会直接执行函数而是先进入工具节点。工具节点根据调用名称找到对应函数执行参数得到结果。工具结果被包装成ToolMessage追加到 State 的messages里。图回到模型节点模型看到工具返回结果后生成最终回答或继续发起下一次工具调用。这个链路看起来简单真正容易出问题的是第 5 步。很多人只把工具执行后的内容 print 出来却没有把ToolMessage追加回消息列表。模型看不到工具结果自然就无法继续回答。LangGraph 的价值就在这里它强制你把消息流当成状态的一部分来管理。2.2 先用 create_react_agent 跑通最小流程如果你只是想先看效果LangGraph 官方 prebuilt 里提供了一个高层入口create_react_agent。它把上面那套循环封装好了。pip install langgraph langchain-core langchain-openai然后可以写一个最小示例这里用天气工具做演示from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent tool def query_weather(city: str) - str: 查询指定城市当天的天气。 city 使用中文城市名例如厦门、北京、上海。 # 真实场景这里可以调用天气 HTTP API return f{city} 今天晴25 摄氏度。 tool def get_temperature(city: str) - str: 获取指定城市当前的温度。 city 使用中文城市名。 return f{city} 当前温度 25 摄氏度 tools [query_weather, get_temperature] llm ChatOpenAI(modelgpt-4o-mini, temperature0) agent create_react_agent(llm, tools) result agent.invoke({ messages: [{role: user, content: 厦门今天天气怎么样}] }) print(result[messages][-1].content)这个示例没有真正请求天气 API但流程已经完整了。模型看到问题后判断需要调用query_weather工具返回结果模型再基于结果生成“厦门今天晴25 摄氏度”的回答。在实际项目里我会先跑通这样一个最小闭环再决定要不要换更底层的StateGraph自定义流程。2.3 不用 prebuilt怎么用 StateGraph 手工搭create_react_agent把很多细节隐藏了。如果你需要自定义分支、人工审批、并行工具或者只是想真正理解工具调用建议手工搭一次StateGraph。下面是一个常见的自定义实现使用add_messages来累加消息from typing import Annotated, Literal from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langchain_core.messages import AIMessage, ToolMessage class AgentState(TypedDict): messages: Annotated[list, add_messages] tools_by_name {tool.name: tool for tool in tools} model_with_tools llm.bind_tools(tools) def call_model(state: AgentState) - dict: response model_with_tools.invoke(state[messages]) return {messages: [response]} def call_tool(state: AgentState) - dict: last_message state[messages][-1] if not isinstance(last_message, AIMessage) or not last_message.tool_calls: return {messages: []} tool_results [] for tool_call in last_message.tool_calls: tool tools_by_name[tool_call[name]] tool_output tool.invoke(tool_call[args]) tool_results.append(ToolMessage( contentstr(tool_output), tool_call_idtool_call[id], )) return {messages: tool_results} def should_continue(state: AgentState) - Literal[tools, end]: last_message state[messages][-1] if isinstance(last_message, AIMessage) and last_message.tool_calls: return tools return end builder StateGraph(AgentState) builder.add_node(model, call_model) builder.add_node(tools, call_tool) builder.add_edge(START, model) builder.add_conditional_edges( model, should_continue, {tools: tools, end: END}, ) builder.add_edge(tools, model) graph builder.compile() result graph.invoke({ messages: [{role: user, content: 厦门今天天气怎么样}] })这个版本里最关键的是三件事add_messages会追加消息而不是覆盖消息。call_tool根据模型给出的tool_calls执行函数并生成带tool_call_id的ToolMessage。should_continue决定模型是要继续调用工具还是结束流程。ToolMessage如果没有和原始tool_call_id对应上模型就无法把工具结果和某一次调用关联起来这是自定义工具链路上最常见的坑之一。3. 关键参数和容易被忽略的工程细节3.1 工具描述决定模型什么时候用、怎么用模型不是真的“理解”你的 Python 函数它看到的是工具的名称、描述和参数 Schema。因此工具描述要写清楚两件事什么时候用这个工具参数应该怎么填。举个例子差描述“获取天气”好描述“查询指定城市当天的天气。city 使用中文城市名例如厦门、北京、上海。当用户问天气、温度、是否适合出行时使用。”很多第一次跑 LangGraph 的人发现模型不调用工具不是因为代码写错了而是因为工具描述太模糊。模型不知道这个工具能帮助用户解决哪类问题自然就“懒得用”。如果工具参数复杂还可以用 Pydantic 模型定义结构让模型看到更清晰的字段、默认值和约束。这比让模型从一段自然语言里猜参数要可靠得多。3.2 工具返回给模型的信息必须让模型读得懂工具执行后的返回值不是给人看的而是给模型看的。实际操作中我经常犯一个错误工具返回了一长串 JSON字段名不清模型根本不知道哪个值是结果。一个更稳妥的做法是在工具函数里就把结果加工成模型容易理解的形式。比如返回“查询成功厦门今天晴25 摄氏度”返回“查询失败城市参数为空”返回“查询超时天气服务 5 秒内无响应”如果工具内部发生异常不要直接让它中断整个 Agent。可以捕获异常把错误信息转换成字符串返回给模型。模型看到错误后可能会修正参数重试或者直接告诉用户当前无法完成。这种做法不会让流程断开也会让 Agent 的行为更接近真实人类。3.3 递归上限不是越大越好LangGraph 的 Agent 是循环结构。工具调用之后回到模型节点模型可能再次调用工具。如果某个工具一直返回模型无法处理的结果循环就会卡住。LangGraph 默认对递归深度有限制。如果你需要调整可以在invoke时传入配置result agent.invoke( {messages: [{role: user, content: ...}]}, config{recursion_limit: 10}, )这里要注意recursion_limit不是越大越好。它更像是一个安全边界避免模型在无效工具调用上一直打转。如果你的工具带有副作用比如发消息、改数据、调外部付费接口更不要把它调得很大。在 LangGraph 里工具调用的循环必须要有终止条件。没有终止条件的 Agent不是智能而是失控。4. 常见报错和排查链路4.1 模型一直不调用工具这是新手最先遇到的问题。现象是用户问了一个需要工具的问题模型却直接基于常识回答或者承认自己不知道。可能原因有以下几种现象可能原因处理方式有工具但模型不用工具描述不清楚模型不知道何时调用把描述改成“什么时候用、参数怎么填”有工具但模型不用模型根本没有绑定工具检查llm.bind_tools(tools)或在create_react_agent里传入的是绑定后的模型模型不支持工具调用某些本地模型或旧版模型不具备 Function Calling 能力换支持工具调用的模型或确认推理服务开启相关能力先确认一件事模型输出里是否真的有tool_calls字段。如果没有问题通常出在模型能力、工具描述或绑定关系上而不是 LangGraph 的图结构。4.2 工具执行了但 Agent 无法继续回答这种情况更隐蔽。工具函数确实被调用了结果也打印出来了但最终回答没有生成或者 Agent 直接断掉。问题往往出在消息回填没有生成ToolMessage。ToolMessage的tool_call_id和模型输出的调用 ID 不一致。工具节点返回后图没有回到模型节点。排查时把state[messages]完整打印出来看最后几条消息是什么。如果是AIMessage后面应该跟着对应的ToolMessage。如果只有AIMessage而没有ToolMessage说明工具节点没有正确回传结果。4.3 模型调用了一个不存在的工具模型生成tool_calls时工具名可能和你定义的不一致。尤其当你有多个长工具名时模型可能出现幻觉。最好的方式是在call_tool里先做一次查找tool tools_by_name.get(tool_call[name]) if tool is None: tool_results.append(ToolMessage( contentf不存在名为 {tool_call[name]} 的工具, tool_call_idtool_call[id], ))把“工具不存在”也当成一次工具结果回传模型就能知道调用出错并可能修正自己的下一步动作。这也是一种比较温和的容错方式。4.4 一套针对工具调用的问题排查链路遇到问题不要急着调提示词。先按下面的顺序缩小范围看输入用户消息是否清楚是否真的需要工具。看模型输出是否有tool_calls工具名和参数是否正确。看绑定模型是否真的绑定了 tools不是只把 tools 传给了 Agent 概念里的某个抽象对象。看消息流AIMessage后有没有ToolMessagetool_call_id是否对应。看工具执行单独调用一次工具函数能不能返回正确结果。看循环条件should_continue是不是正确判断了结束条件。看外部系统网络、超时、限流、API Key、权限、资源占用。只要你能把messages从头到尾打印出来大部分问题都能在一分钟内定位。与其猜不如看链路。遇到工具调用问题不要急着改提示词。先把 state 里的 messages 完整打印出来看模型到底有没有生成 tool_calls工具返回有没有回到模型节点。5. 单次调用到可复用 Agent 的边界5.1 LangGraph 给工具调用带来的不是新功能是可控性你不用 LangGraph 也能实现工具调用。写一个while True模型输出tool_calls就执行工具然后拼接消息再调模型直到没有工具调用为止。这是最原始的 Agent 循环。LangGraph 的真正价值在于可控性。它把节点、边、状态、循环、条件路由都变成了显式结构。你可以中途查看状态可以在工具节点前暂停可以决定某类问题走哪条分支也可以把整个 Agent 的状态持久化下来。工具调用在这种图结构里不是一次性的“问答加速器”而是一个长期可维护的执行链路。5.2 从 demo 到生产需要补的几块拼图如果你已经跑通了上面的最小示例先别急着加新工具。要进入真实项目还差几块关键拼图日志记录每次工具调用的工具名、入参、出参、耗时、报错。校验模型传入的参数不一定合法工具内部仍然要做输入校验。超时和重试外部 API 可能超时工具要设置超时必要时做有限次重试。权限不要把一个能删除数据、发邮件、执行命令的万能工具直接暴露给模型。人工确认如果工具副作用不可逆应该加入人工审批节点。测试工具函数本身要做单元测试不能用“模型调用几次没问题”代替。这些能力听起来很基础但往往决定一个 Agent 能不能长期稳定运行。Demo 里一个工具挂了可以重来生产环境里一个工具挂掉会让整个对话链路的信任度下降。5.3 哪些场景不适合立刻上工具调用工具调用不是越早越好也不是越多越好。如果模型本身已经能给出足够好的答案而且不依赖实时数据和私有数据没必要强行套工具。如果工具会带来昂贵或不可逆的操作比如发邮件、删数据、改配置就必须有人工确认机制。如果模型本身不支持工具调用或者本地推理框架没有开启对应能力也需要先处理好模型层的问题。一套方案好用只代表它在合适场景里解决了正确问题。不要把它当成所有 Agent 问题的万能钥匙。6. 一套可复用的落地框架先跑通、再加边界、最后工程化6.1 我的落地顺序1 个工具、最小闭环、打日志我建议你不要一开始就设计十几个工具的复杂 Agent。先做一次最小闭环只写一个无副作用的小工具比如查询时间、查询天气、查一条本地数据。用create_react_agent跑通“用户提问 - 工具调用 - 结果回传 - 最终回答”全链路。打印messages确认每一轮消息都有意义。再切换成手工StateGraph理解循环和状态。这样做的原因很简单如果最小闭环都跑不稳后面加再多的工具也只是增加排查难度。工具越多模型选错工具的概率越高工具返回格式不统一的问题也会被放大。6.2 给工具加上异常捕获和超时而不是裸调用真实工具很少像天气示例那么顺利。它要访问外部服务可能会超时、限流、返回异常格式。裸调用会直接让 Agent 中断。一个更稳妥的工具函数结构是这样的import requests tool def fetch_page_title(url: str) - str: 获取一个网页的 title 标签文本。 url 必须是完整的 http 或 https 地址。 try: requests.get(url, timeout5).raise_for_status() content requests.get(url, timeout5).text if title in content: return content.split(title)[1].split(/title)[0][:200] return 未找到 title except Exception as e: return f获取失败{e}这段代码只是示例结构。实际项目里还应该注意访问域名白名单、响应体大小限制、并发控制、错误分类和日志记录。它的核心思路是工具要能失败而且失败后要返回模型能理解的信息而不是抛异常打断整个 Agent。6.3 工具调用自检清单每次新增一个工具我都会对照以下清单工具名称是否可读能看出它负责什么。工具描述是否写清楚了“什么时候用”和“参数怎么填”。参数 Schema 是否覆盖了必填字段和常见边界。工具内部是否有输入校验、超时、异常捕获。模型是否能通过返回结果判断下一步是该重试还是该结束。ToolMessage是否能正确回填到消息列表。这个工具是否有权限边界是否会造成不可逆副作用。是否记录了一次调用的入参和出参。是否配置了合理的递归上限和终止条件。这些条目看起来琐碎但它们决定了一个工具是稳定能力还是一个随机炸弹。如果只能记住一句话我希望是工具调用的核心不是让模型学会调用函数而是让你的程序围绕模型的决策、执行和反馈形成一条可靠闭环。先把这个闭环做稳后面的多 Agent、记忆、持久化和人工审批才有继续往上的基础。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号