2026/9/2 10:27:58

LangGraph工具调用:从Function Calling到智能体图编排

LangGraph工具调用:从Function Calling到智能体图编排 LangGraph 里的工具调用Tool Calling是整个智能体开发链路里最核心、也最容易被忽略的一环。很多人第一次接触 LangGraph 时看到ToolNode、tools_condition、bind_tools这几个名词就懵了其实拆开看并不复杂。这一次我们就围绕“LangGraph 如何实现工具调用”这个课题把原理、代码、调试、接口封装到常见坑完整过一遍。这是 AI 编程与智能体开发课程中 LangGraph 部分的一个重点章节课程体系里反复强调同一个观点大模型本身不具备执行动作的能力工具调用才是让智能体从“会聊天”走向“会干活”的关键机制。你掌握了工具调用就能让模型去查数据库、调接口、读取文件、执行计算任务甚至控制一个测试环境。为了照顾不同基础的读者文章会从最底层的函数调用机制讲起再切换到 LangGraph 的图编排方式。最后会给出一个可以直接跑通的 FastAPI 接口示例方便你把它接到自己的项目中。全程保持短句、少废话读完就能照做。1. LangGraph 工具调用的核心能力速览先把课程里反复出现的几个关键词串起来LangGraph 是 LangChain 团队推出的智能体编排框架它的核心思想是用图结构来表达“模型什么时候调用工具、工具结果如何回到模型”的流程。工具调用则是这个大框架里的基础能力。能力项说明项目定位基于图结构的 LLM 应用编排框架适合构建可观测、可控制的智能体核心机制模型生成结构化工具调用参数LangGraph 负责调度执行并回填结果关键组件StateGraph、ToolNode、tools_condition、bind_tools模型适配支持 OpenAI 风格接口、Anthropic、本地部署模型关键是模型要支持 function calling / tool calling运行环境Python 库形式框架本身不需要 GPU推理部分由模型决定启动方式Python 脚本直接运行也可用 FastAPI 封装为 HTTP 服务是否支持批量支持可以通过多线程、异步对多个输入并发执行可观测性LangSmith 或自定义日志查看模型输出、工具结果和执行轨迹适合场景需要查询外部数据、操作业务系统、执行计算任务、自动化测试的智能体开发从能力速览可以看出来LangGraph 工具调用本质上是“模型决策 代码执行”的协作机制。它不像一个一键启动的 WebUI 工具更像是一个需要你写代码来编排的开发框架但好处是结构清晰、可控性强。2. 工具调用到底是解决什么问题在进入代码之前先明确一个容易被误解的点工具调用不是说模型自己运行了你的函数而是模型在生成回答时额外输出一个结构化的“调用意图”。这个意图包含了函数名和参数真正去执行这个函数的是你的应用程序代码。举个例子你问模型“北京今天天气怎么样”模型没有能力实时获取天气数据但如果它被允许调用一个get_weather工具它就会输出类似这样的结构{ name: get_weather, arguments: { city: 北京 } }然后你的代码拿到这个结构化结果去真实的天气接口查询数据再把查询结果当作一条新消息返回给模型。模型根据工具返回的结果生成最终的自然语言回答。这个流程和专业术语叫 Function Calling也常被称为 Tool Calling。没有这个机制大模型就只是一个知识库文本生成器无法接入实时数据也无法操作外部系统。有了这个机制大模型才变成了一个“会思考的调度中心”。LangGraph 在这个基础上做了两件事用图结构把“模型调用工具 → 工具结果返回模型”这个循环清晰表达出来。提供ToolNode等预构建组件避免你每次手动解析tool_calls字段。所以学习 LangGraph 工具调用本质上是学习如何用图的方式组织智能体的循环逻辑。3. 环境准备与前置条件先准备环境。LangGraph 本身是一个纯 Python 库安装起来不复杂但有下面几个前置条件需要确认。3.1 基础环境操作系统Windows、macOS、Linux 均可课程演示以 Linux 和 macOS 为主。Python 版本建议 Python 3.9 及以上新版本 LangGraph 对 Python 版本有一定要求。包管理工具推荐使用 pip有 conda 环境隔离也可以。3.2 安装 LangGraph 相关依赖pip install langgraph langchain-core langchain-openai如果希望把智能体封装成 HTTP 接口再安装一个 FastAPIpip install fastapi uvicorn3.3 模型服务配置工具调用要求模型具备 function calling 能力。推荐四种方式OpenAI 官方接口设置OPENAI_API_KEY环境变量示例模型gpt-4o-mini。Anthropic 接口使用 Claude 系列模型。国内大模型平台如果使用兼容 OpenAI 格式的服务可以修改base_url。本地模型通过 Ollama、vLLM、llama.cpp 等方式加载带 tool calling 能力的模型。本地部署时要注意模型文件本身需要较大的存储空间和合理的显存但这是模型推理侧的问题LangGraph 框架不会额外占用大量显存。环境准备好之后可以先确认一下能否正常调用模型接口再进入代码部分。4. 先看最底层的手动实现为了理解 LangGraph 的封装逻辑先不急着使用ToolNode而是手动实现一个最简单的“模型调用工具”循环。这样你能看清整个过程到底有几个步骤。4.1 定义工具函数from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市当天天气 return f{city}今天多云气温 24 摄氏度适合出行。这里用tool装饰器把普通函数包装成 LangChain 工具。函数签名里的类型注解和 docstring 会被转成 JSON Schema模型就是根据这些信息决定何时调用、传什么参数。4.2 绑定模型并处理调用结果from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini) llm_with_tools llm.bind_tools([get_weather]) response llm_with_tools.invoke(北京今天天气怎么样) # 判断模型是否产生了工具调用 if response.tool_calls: tool_call response.tool_calls[0] print(模型要调用:, tool_call[name]) print(参数是:, tool_call[args]) # 真正执行函数 result get_weather.invoke(tool_call[args]) print(工具返回:, result) else: print(模型直接回答:, response.content)手动版的核心逻辑就三步bind_tools告诉模型存在哪些工具。模型返回tool_calls字段里面是工具名和参数。应用代码执行工具并把结果回填给模型。这种方式能跑通但存在一个问题如果模型先调用一次工具拿到结果后还想继续调用另一个工具就需要自己写循环逻辑。如果模型最多连续调用三次、五次代码会变得很啰嗦。LangGraph 的图结构就是为这个循环而设计的。5. 用 LangGraph 实现工具调用智能体现在进入正题用 LangGraph 实现一个带工具调用循环的智能体。这里会用到三个关键组件StateGraph定义图结构和状态。ToolNode自动执行工具调用。tools_condition判断模型输出中是否包含工具调用请求决定下一步走向。5.1 定义状态和节点from typing import Annotated from typing_extensions import TypedDict from langchain_core.messages import AnyMessage from langgraph.graph import StateGraph, MessagesState, START, END from langgraph.prebuilt import ToolNode, tools_condition from langchain_core.tools import tool from langchain_openai import ChatOpenAI tool def get_weather(city: str) - str: 查询指定城市当天天气 return f{city}今天多云气温 24 摄氏度。 tool def get_city_air_quality(city: str) - str: 查询指定城市空气指数 return f{city}今日 AQI 为 45空气质量优。LangGraph 预置了MessagesState它的messages字段带有自动合并逻辑适合保存整个对话历史。接下来定义模型节点tools [get_weather, get_city_air_quality] llm ChatOpenAI(modelgpt-4o-mini) llm_with_tools llm.bind_tools(tools) def assistant(state: MessagesState): return {messages: [llm_with_tools.invoke(state[messages])]}5.2 构建图builder StateGraph(MessagesState) builder.add_node(assistant, assistant) builder.add_node(tools, ToolNode(tools)) builder.add_edge(START, assistant) builder.add_conditional_edges(assistant, tools_condition) builder.add_edge(tools, assistant) graph builder.compile()这段配置的逻辑很清晰启动后先进入assistant节点让模型生成回复。模型回复后tools_condition检查messages最后一条是否含有tool_calls。如果包含工具调用就前往tools节点执行工具。工具执行完毕后把结果作为新消息追加到状态中并回到assistant节点让模型继续分析。如果不再需要调用工具就直接结束。这是一种典型的循环图结构tools - assistant - tools可以反复执行直到模型认为任务完成。5.3 运行验证result graph.invoke({ messages: [{role: user, content: 分别查询北京天气和空气指数然后总结一下}] }) for message in result[messages]: print(message.type, message.content or message.tool_calls)如果看到输出中依次出现模型调用工具、工具返回结果、模型最终总结就说明工具调用链路已经跑通。这个例子比手动版多了两个价值循环不需要自己写图结构天然支持。多个工具、连续多轮调用都可以被同一套代码处理。6. 多工具选择与条件路由在上面的配置里tools_condition是 LangGraph 预设的条件函数它会根据模型是否产生了tool_calls返回两个节点名之一进入tools或结束。但对复杂场景你可能需要自定义路由逻辑。6.1 自定义条件边举例来说如果某个工具执行失败你希望智能体不直接结束而是返回assistant让模型换一种方式处理。def custom_condition(state: MessagesState): last_message state[messages][-1] if not last_message.tool_calls: return END return tools然后把它替换到条件边上builder.add_conditional_edges(assistant, custom_condition)这样控制权就回到了你的代码里。你完全可以基于自己的业务规则决定下一步执行哪个节点。6.2 子图与分支课程中另一个重点是子图和并行分支。工具调用并不一定只有一个主图你完全可以把某个工具单独封装成一个子图再在主图中引用它。这样做的意义在于子图内部有独立的节点和状态便于复用。在不同场景下组合不同子图能构建更灵活的智能体。from langgraph.graph import StateGraph, START, END # 定义一个子图 sub_builder StateGraph(MessagesState) sub_builder.add_node(sub_tools, ToolNode(tools)) sub_builder.add_edge(START, sub_tools) sub_builder.add_edge(sub_tools, END) sub_graph sub_builder.compile() # 在主图中引用子图 builder.add_node(weather_subgraph, sub_graph)这种写法在多智能体协作场景下非常实用。不过初次学习时不必急着上子图先把单图循环跑通再考虑拆分子图。7. 接口封装与批量调用工具调用智能体写完之后下一步通常就是接入业务系统。这里提供两个方向把智能体封装为 HTTP 接口或者直接批量处理输入。7.1 用 FastAPI 封装接口from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): message: str app.post(/chat) def chat(req: ChatRequest): result graph.invoke({ messages: [{role: user, content: req.message}] }) return { reply: result[messages][-1].content, full_trace: [m.type for m in result[messages]] }启动服务uvicorn main:app --host 0.0.0.0 --port 8000请求接口curl -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d {message: 北京空气怎么样}这个接口是所有工具调用链路的统一出口。内部图结构增加新的工具外部调用方不需要感知任何变化。7.2 批量任务处理如果需要一次性处理多条输入例如批量查询多个城市的天气可以用 Python 循环或线程池from concurrent.futures import ThreadPoolExecutor cities [北京, 上海, 深圳, 杭州, 成都] def ask_city(city: str): result graph.invoke({ messages: [{role: user, content: f{city}天气如何}] }) return city, result[messages][-1].content with ThreadPoolExecutor(max_workers3) as executor: results list(executor.map(ask_city, cities)) for city, reply in results: print(city, reply)批量调用时要注意两件事控制并发数量避免触发模型服务的频率限制。每个请求的对话状态要相互隔离不能共享同一个messages列表。8. 资源占用与性能观察LangGraph 本身是一个 Python 图执行引擎资源占用很低主要开销集中在模型推理上。这里有几个值得观察的角度。8.1 Token 消耗工具调用链路越长产生的 Token 越多。每次工具返回结果都会作为新消息加入上下文如果工具输出很长下一轮模型调用会消耗更多输入 Token。建议在工具函数里控制返回内容的长度只把关键信息返回给模型。8.2 调用耗时如果使用云端大模型接口单次工具调用通常需要几百毫秒到几秒不等具体取决于模型大小和网络延迟。如果使用本地模型推理耗时会更长且会占用显存资源。观察耗时最简单的方式是在图调用前后记录时间import time start time.time() result graph.invoke({messages: [{role: user, content: 北京天气}]}) print(总耗时:, time.time() - start)8.3 本地模型与 API 模型课程中提到了本地模型方案。如果你使用 Ollama 加载支持工具调用的模型LangGraph 同样可以通过ChatOllama对接代码结构基本不变。但本地模型对 GPU 显存有要求模型参数越大需要的显存越多这个要按实际的模型规格来评估。9. 常见问题与排查方法工具调用的报错种类比较集中下面这张表格覆盖了大多数常见场景。问题现象可能原因排查方式解决方案bind_tools报错模型接口不支持 function calling查看模型文档确认是否支持 tool calling换支持 tool calling 的模型或用兼容接口中转ToolNode导入失败langgraph 版本过旧检查 langgraph 版本升级 langgraph 到最新版工具调用了但没执行条件路由配置错误打印tools_condition的返回值检查条件边返回的节点名是否与add_node一致智能体陷入无限循环工具返回结果仍未满足模型停止条件在tools_condition中增加最大轮数限制设置recursion_limit或自定义循环计数工具参数错误模型生成的 JSON 参数不符合工具函数签名查看报错信息里的实际参数优化函数 docstring让 Schema 更明确中文回答乱码终端编码问题检查运行环境的编码设置设置PYTHONIOENCODINGutf-8接口请求超时模型推理耗时过长查看服务日志和模型调用时间调整 FastAPI 超时配置或升级模型服务批量任务卡住某条输入触发了异常给每个任务加入 try/except 和日志记录失败输入并设置单条任务超时最容易被忽略的是工具描述质量。模型决定是否调用工具、传什么参数完全依赖函数的 docstring 和参数 Schema。描述写得含糊模型就会频繁传错参数或拒绝调用。10. 最佳实践与合规边界作为智能体开发的基础能力工具调用会让模型获得“操作外部系统”的能力。权限越大边界管理越重要。10.1 最小权限原则不要给工具过高的权限。比如一个天气查询工具只需要只读权限就不应该允许写入操作。生产环境里尤其要注意模型可能被 prompt 注入恶意用户可能诱导模型调用危险工具。建议在工具内部增加参数校验tool def delete_file(path: str) - str: 删除指定路径文件仅限 /tmp/sandbox 目录 if not path.startswith(/tmp/sandbox): return 路径不允许 # 执行删除10.2 日志与审计每次工具调用都应该记录完整的入参和出参方便回溯问题。LangSmith 可以做到可视化追踪自建项目时至少要在工具函数里打日志。import logging logger logging.getLogger(tool_calls) def _safe_call(func, **kwargs): logger.info(调用工具 %s, 参数 %s, func.name, kwargs) result func.invoke(kwargs) logger.info(工具返回: %s, str(result)[:200]) return result10.3 数据隐私与版权合规如果工具需要访问业务数据库或第三方接口注意用户隐私数据的脱敏处理。涉及版权素材时必须确认是否有合法授权。智能体输出内容如果用于商用也要进行人工复核不能完全依赖模型自动输出。11. 总结与下一步LangGraph 工具调用是目前 AI 编程与智能体开发里最值得先跑通的能力。它回答了一个核心问题大模型如何从“只会生成文本”升级为“能够执行动作”。课程里通过ToolNode、tools_condition、图结构循环向我们展示了一条清晰的实现路径。建议你在本地环境先完成一次最简单的天气查询智能体确认模型能生成tool_calls工具能执行结果能回填然后再逐步加入多工具、子图和接口封装。最容易踩的坑集中在两点一是模型本身不支持 tool calling导致bind_tools阶段就报错二是工具描述写得太模糊模型不知道在什么情况下调用。把这两个问题提前解决后面的开发会顺畅很多。接下来可以扩展的方向包括多智能体协作、长期记忆、复杂业务系统中的权限控制和审计、本地模型部署与性能调优。工具调用是所有这些方向的地基地基稳了后面的智能体才能真的在业务里跑起来。