掌握工具调用(Function Calling):让LLM从聊天机器进化为高效Agent,收藏学习必备!
2026/4/14 9:06:50 网站建设 项目流程

本文深入解析了LLM的“工具调用”(Function Calling)机制,揭示LLM如何通过输出结构化JSON来指定所需功能及参数,而实际执行则由开发者代码完成。文章详细介绍了OpenAI的原始协议、多轮对话工具调用循环,以及LangChain如何通过bind_tools和BaseTool等工具简化实现。此外,还探讨了ToolMessage的消息结构、并行工具调用、常见坑与最佳实践,以及工具安全边界设计原则。理解并应用工具调用,是构建高效AI Agent的关键一步。

1、 先搞清楚:Function Calling 到底是什么

很多人把 Function Calling 理解成"LLM 帮你调函数",这个理解是错的

LLM 没有执行权限,它不会真的去调用任何函数。它做的事情只有一件:输出一段结构化 JSON,告诉你"我想调用哪个函数、传什么参数"

真正执行函数的,是你的代码。

用户提问 │ ▼ ┌─────────────────────────────────────┐ │ LLM(只做文本预测) │ │ │ │ 输入:用户问题 + 工具描述(JSON Schema)│ │ │ │ 输出:{ │ │ "tool_name": "search_web", │ │ "arguments": { │ │ "query": "今天上海天气" │ │ } │ │ } │ └─────────────────────────────────────┘ │ ▼ ← 你的代码负责执行 ┌─────────────┐ │ search_web │ ← 真实函数,调真实 API └─────────────┘ │ ▼ 工具返回结果,再喂给 LLM │ ▼ LLM 生成最终自然语言回复

这里有一个关键认知:LLM 能输出结构化 JSON,是因为它被训练成了这样。OpenAI 在 GPT-3.5/4 的 fine-tune 阶段专门加入了 Function Calling 的训练数据,让模型学会"识别何时该调工具、该传什么参数"。

2、 OpenAI Function Calling 的原始协议长什么样

在 LangChain 包装之前,我们先看原始 API 长什么样。理解了底层,才能明白 LangChain 帮你省了多少事。

定义工具(JSON Schema 格式):

const tools=[{type:"function", function:{name:"get_weather", description:"获取指定城市的当前天气", parameters:{type:"object", properties:{city:{type:"string", description:"城市名称,例如:北京、上海"}, unit:{type:"string", enum:["celsius","fahrenheit"], description:"温度单位"}}, required:["city"]}}}];

发送请求:

const response=await openai.chat.completions.create({model:"gpt-4", messages:[{role:"user", content:"上海今天多少度?"}], tools: tools, tool_choice:"auto"// auto|none|{type:"function", function:{name:"..."}}});

LLM 返回的内容(不是普通文本!):

{finish_reason:"tool_calls", // ← 注意这个字段 message:{role:"assistant", content: null, tool_calls:[{id:"call_abc123", type:"function", function:{name:"get_weather", arguments:'{"city": "上海", "unit": "celsius"}'// ↑ 注意:arguments 是字符串,不是对象!需要 JSON.parse}}]}}

这里有几个坑:

  1. finish_reason"tool_calls"而不是"stop",你需要判断这个
  2. arguments字符串,不是对象,需要手动JSON.parse()
  3. LLM 可能一次返回多个 tool_calls(并行调用)

3、 完整工具调用循环:多轮对话机制

单次工具调用只是开始。真实场景里,一次用户请求可能需要多次工具调用才能完成。这就是为什么叫"循环(Loop)"。

┌─────────────────────────────────────────────────┐ │ 工具调用完整循环 │ │ │ │ ┌─────────┐ │ │ │ 用户提问 │ │ │ └────┬────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ LLM 决策 │ │ │ │ finish_reason = "tool_calls"│ │ │ └──────────┬───────────────────┘ │ │ │ │ │ ┌─────▼─────┐ │ │ │ 执行工具 │ ← 你的代码 │ │ └─────┬─────┘ │ │ │ ToolMessage │ │ │ │ │ ┌─────▼───────────┐ │ │ │ 把结果加入消息历史 │ │ │ └─────┬───────────┘ │ │ │ │ │ ┌─────▼──────────────────────────┐ │ │ │ LLM 再次决策 │ │ │ │ finish_reason = "stop" → 结束 │ │ │ │ finish_reason = "tool_calls" │ │ │ │ → 继续循环 │ │ │ └────────────────────────────────┘ │ └─────────────────────────────────────────────────┘

手动实现这个循环(原始 OpenAI SDK):

importOpenAI from"openai";const openai=new OpenAI();asyncfunctionrunToolLoop(userMessage: string){const messages: OpenAI.Chat.ChatCompletionMessageParam[]=[{role:"user", content: userMessage}];while(true){const response=await openai.chat.completions.create({model:"gpt-4", messages, tools, tool_choice:"auto"});const choice=response.choices[0];messages.push(choice.message);// 把 AI 回复加入历史 // 如果不需要调用工具,循环结束if(choice.finish_reason==="stop"){returnchoice.message.content;}// 执行所有工具调用for(const toolCall of choice.message.tool_calls ??[]){const args=JSON.parse(toolCall.function.arguments);const result=await executeToolCall(toolCall.function.name, args);// 把工具结果加入消息历史 messages.push({role:"tool", tool_call_id: toolCall.id, // ← 必须对应,LLM 靠这个id区分多个工具调用 content: JSON.stringify(result)});}}}

这段代码每次都要手写。LangChain 把这个循环封装掉了


4 、LangChain 的工具链设计:bind_tools 做了什么

LangChain 的核心抽象是:统一不同 LLM 厂商的工具调用接口

OpenAI、Claude、Gemini、百川——每家的 Function Calling 格式都不一样。LangChain 用BaseToolbind_tools做了标准化。

┌──────────────────────────────────────────────────┐ │ LangChain 工具抽象层 │ │ │ │ ┌─────────────┐ ┌──────────────────────────┐ │ │ │ BaseTool │ │ bind_tools(tools) │ │ │ │ │ │ │ │ │ │ .name │───▶│ 自动转换为各厂商格式: │ │ │ │ .description│ │ - OpenAI: tools[] │ │ │ │ .schema │ │ - Claude: tools[] │ │ │ │ ._call() │ │ - Gemini: functionDecl │ │ │ └─────────────┘ └──────────────────────────┘ │ │ │ │ 你只写一次工具定义,适配所有 LLM │ └──────────────────────────────────────────────────┘

三种定义工具的方式:

import{tool}from"@langchain/core/tools";import{z}from"zod";import{ChatOpenAI}from"@langchain/openai";// 方式1:用 tool()函数(最推荐,类型安全) const getWeather=tool(async({city, unit="celsius"})=>{// 真实实现 const temp=await fetchWeatherAPI(city);return`${city}当前温度:${temp}°${unit==="celsius"?"C":"F"}`;},{name:"get_weather", description:"获取指定城市的当前天气", schema: z.object({city: z.string().describe("城市名称"), unit: z.enum(["celsius","fahrenheit"]).optional().describe("温度单位")})});// 方式2:继承 BaseTool(适合复杂工具,有状态)import{BaseTool, ToolParams}from"@langchain/core/tools";class DatabaseQueryTool extends BaseTool{name="query_database";description="查询数据库中的用户信息";constructor(private db: DatabaseConnection){super();}async _call(input: string): Promise<string>{const result=await this.db.query(input);returnJSON.stringify(result);}}// 方式3:DynamicStructuredTool(Zod schema + 动态)import{DynamicStructuredTool}from"@langchain/core/tools";const searchTool=new DynamicStructuredTool({name:"search_web", description:"搜索网页内容", schema: z.object({query: z.string(), maxResults: z.number().optional().default(5)}), func: async({query, maxResults})=>{returnawait webSearch(query, maxResults);}});

bind_tools 绑定工具:

const model=new ChatOpenAI({model:"gpt-4o"});const tools=[getWeather, searchTool];// 绑定工具到模型 const modelWithTools=model.bindTools(tools);// 调用时,LLM 会自动决定是否要调工具 const result=await modelWithTools.invoke("上海今天多少度?");// result 可能是: //1. AIMessage with tool_calls → LLM 决定要调工具 //2. AIMessage with content → LLM 直接回答 console.log(result.tool_calls);//[{name:"get_weather", args:{city:"上海"}, id:"call_xxx"}]


5 、ToolMessage 消息结构:工具结果如何回传 LLM

工具执行完之后,结果怎么传回 LLM?这里有个容易踩的坑:消息类型是 ToolMessage,不是 HumanMessage

消息历史的完整结构: HumanMessage: "上海今天多少度?" ↓ AIMessage: content=null, tool_calls=[{id:"call_1", name:"get_weather", args:{city:"上海"}}] ↓ ToolMessage: content="上海当前温度:22°C", tool_call_id="call_1" ↓ AIMessage: content="上海今天22度,比较舒适,适合户外活动。"

用 ToolNode 自动执行工具(LangGraph 的正确打开方式):

import{ToolNode}from"@langchain/langgraph/prebuilt";import{HumanMessage}from"@langchain/core/messages";// ToolNode:接收包含 tool_calls 的 AIMessage,自动执行并返回 ToolMessage const toolNode=new ToolNode(tools);// 模拟 LLM 输出了一个 tool_calls const aiMessage=new AIMessage({content:"", tool_calls:[{id:"call_001", name:"get_weather", args:{city:"上海", unit:"celsius"}, type:"tool_call"}]});// ToolNode 自动执行并返回 ToolMessage const toolResult=await toolNode.invoke({messages:[aiMessage]});//{messages:[ToolMessage{content:"上海当前温度:22°C", tool_call_id:"call_001"}]}

并行工具调用(Parallel Tool Calls):

// LLM 可能一次返回多个 tool_calls const aiMessage=new AIMessage({content:"", tool_calls:[{id:"call_001", name:"get_weather", args:{city:"上海"}},{id:"call_002", name:"get_weather", args:{city:"北京"}},{id:"call_003", name:"search_web", args:{query:"今日股市行情"}}]});// ToolNode 会并行执行这三个工具 // 返回三条 ToolMessage,分别对应三个 tool_call_id

6 、在 LangGraph 中组装完整工具调用 Agent

把前面所有东西组合起来,搭一个完整的 ReAct Agent:

importStateGraphMessagesAnnotationfrom"@langchain/langgraph"importToolNodefrom"@langchain/langgraph/prebuilt"importChatOpenAIfrom"@langchain/openai"importfrom"@langchain/core/tools"importfrom"zod"importAIMessagefrom"@langchain/core/messages"// 定义工具consttoolasynctry// 简单计算器(生产环境用更安全的方式)constFunction`"use strict"; return (${expression})`return`计算结果:${result}`catchreturn"计算失败,请检查表达式格式"name"calculator"description"执行数学计算,支持加减乘除和括号"schemaobjectexpressionstringdescribe"数学表达式,例如:(3+5)*2"consttoolasync// 实际实现调用搜索 APIreturn`搜索结果:关于"${query}"的最新信息...`name"search"description"搜索最新信息"schemaobjectquerystringdescribe"搜索关键词"constconstnewToolNode// LLM 绑定工具constnewChatOpenAImodel"gpt-4o"temperature0constbindTools// 判断是否需要继续调用工具functionshouldContinuestate: typeof MessagesAnnotation.Stateconstmessagesmessageslength1asAIMessageiftool_callstool_callslength0return"tools"// 有 tool_calls → 去执行工具return"__end__"// 没有 tool_calls → 结束// 调用 LLM 节点asyncfunctioncallModelstate: typeof MessagesAnnotation.Stateconstawaitinvokemessagesreturnmessages// 构建 GraphconstnewStateGraphMessagesAnnotationaddNode"agent"addNode"tools"addEdge"__start__""agent"addConditionalEdges"agent"addEdge"tools""agent"// 工具执行完 → 回到 agentcompile// 运行constawaitinvokemessagesrole"user"content"帮我查一下今日 GPT-4o 的最新进展,然后算一下 (125 * 8 + 32) / 4 等于多少"consolelogmessagesmessageslength1content
整个执行流程: 用户提问(两个需求:搜索 + 计算) ↓ agent 节点:LLM 分析,输出两个 tool_calls(并行) ↓ tools 节点:并行执行 search + calculator ↓ 返回两条 ToolMessage 到 agent ↓ agent 节点:LLM 综合工具结果,生成最终回答 ↓ 结束(finish_reason = stop)

7、 工具调用常见坑与最佳实践

坑1:工具描述写得太短

// ❌ 差的描述consttoolname"query_db"description"查数据库"schemaobjectsqlstring// ✅ 好的描述——告诉 LLM 什么时候用、用来干什么consttoolname"query_database"description`查询用户数据库。 适用场景:需要查找用户信息、订单记录、消费历史时。 输入 SQL 查询语句(只支持 SELECT,不支持修改操作)。 返回 JSON 格式的查询结果,最多返回 100 条记录。`schemaobjectsqlstringdescribe"SQL SELECT 语句,例如:SELECT * FROM users WHERE id = 123"

坑2:忘记处理工具执行失败

consttoolasynctryconstawaitriskyOperationreturnJSONstringifycatch// ⚠️ 工具报错了,别直接 throw!// LLM 会收到 ToolMessage,需要能读懂这个错误return`工具执行失败:${error.message}。请尝试修改参数后重试。`name"..."description"..."schemaobject

坑3:tool_choice 设置不当

// 强制调用某个工具(适合测试)constbindToolstool_choicetype"function"functionname"get_weather"// 完全禁用工具调用constbindToolstool_choice"none"// 自动判断(生产环境推荐)constbindToolstool_choice"auto"// 默认值

坑4:并行工具调用时 tool_call_id 对不上

// ✅ ToolMessage 必须设置正确的 tool_call_id// 否则 LLM 不知道哪个结果对应哪个工具调用forconstoftool_callsconstawaitexecuteToolpushnewToolMessagecontenttool_call_idid// ← 必须和 AIMessage.tool_calls[i].id 对应

8 、工具安全边界:别让 LLM 干坏事

工具调用是双刃剑。LLM 能帮你查数据库、也能帮你删数据库。工具设计必须有安全边界

安全工具设计原则: ┌────────────────────────────────────────────────┐ │ 原则1:最小权限 │ │ - 只暴露必要的操作,SELECT 而非全 SQL │ │ - 读写分离,危险操作单独工具并加二次确认 │ │ │ │ 原则2:参数校验(在工具内,不依赖 LLM) │ │ - 用 Zod 做 schema 校验 │ │ - 业务规则在函数内部验证,不在 description 里约定│ │ │ │ 原则3:限制工具调用深度 │ │ - 设置最大循环次数(recursionLimit) │ │ - 避免无限循环调用 │ │ │ │ 原则4:审计日志 │ │ - 记录每次工具调用的参数和结果 │ │ - 异常调用告警 │ └────────────────────────────────────────────────┘
// LangGraph 限制最大循环次数constcompileconstawaitinvokemessagesrole"user"content"..."recursionLimit10// 最多循环 10 次,防止死循环

    总结

    这篇我们从底层到工程,完整拆解了 Function Calling 与 LangChain 工具链:

    • Function Calling 的本质:LLM 只输出 JSON 描述"该调哪个工具、传什么参数",执行权在你手里
    • 完整调用循环:用户提问 → LLM 决策 → 执行工具 → 结果回传 → LLM 再决策,直到 finish_reason 为 stop
    • LangChain 的价值:用bind_tools+BaseTool统一了各厂商差异,ToolNode 把执行循环封装干净
    • ToolMessage 对应关系:tool_call_id 是关键,确保工具结果和调用请求一一对应
    • 安全边界:工具要遵循最小权限原则,参数校验在代码里做,不靠 description 约束 LLM

    最后

    对于正在迷茫择业、想转行提升,或是刚入门的程序员、编程小白来说,有一个问题几乎人人都在问:未来10年,什么领域的职业发展潜力最大?

    答案只有一个:人工智能(尤其是大模型方向)

    当下,人工智能行业正处于爆发式增长期,其中大模型相关岗位更是供不应求,薪资待遇直接拉满——字节跳动作为AI领域的头部玩家,给硕士毕业的优质AI人才(含大模型相关方向)开出的月基础工资高达5万—6万元;即便是非“人才计划”的普通应聘者,月基础工资也能稳定在4万元左右

    再看阿里、腾讯两大互联网大厂,非“人才计划”的AI相关岗位应聘者,月基础工资也约有3万元,远超其他行业同资历岗位的薪资水平,对于程序员、小白来说,无疑是绝佳的转型和提升赛道。


    对于想入局大模型、抢占未来10年行业红利的程序员和小白来说,现在正是最好的学习时机:行业缺口大、大厂需求旺、薪资天花板高,只要找准学习方向,稳步提升技能,就能轻松摆脱“低薪困境”,抓住AI时代的职业机遇。

    如果你还不知道从何开始,我自己整理一套全网最全最细的大模型零基础教程,我也是一路自学走过来的,很清楚小白前期学习的痛楚,你要是没有方向还没有好的资源,根本学不到东西!

    下面是我整理的大模型学习资源,希望能帮到你。

    👇👇扫码免费领取全部内容👇👇

    1、大模型学习路线

    2、从0到进阶大模型学习视频教程

    从入门到进阶这里都有,跟着老师学习事半功倍。

    3、 入门必看大模型学习书籍&文档.pdf(书面上的技术书籍确实太多了,这些是我精选出来的,还有很多不在图里)

    4、AI大模型最新行业报告

    2026最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

    5、面试试题/经验

    【大厂 AI 岗位面经分享(107 道)】

    【AI 大模型面试真题(102 道)】

    【LLMs 面试真题(97 道)】

    6、大模型项目实战&配套源码

    适用人群

    四阶段学习规划(共90天,可落地执行)
    第一阶段(10天):初阶应用

    该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

    • 大模型 AI 能干什么?
    • 大模型是怎样获得「智能」的?
    • 用好 AI 的核心心法
    • 大模型应用业务架构
    • 大模型应用技术架构
    • 代码示例:向 GPT-3.5 灌入新知识
    • 提示工程的意义和核心思想
    • Prompt 典型构成
    • 指令调优方法论
    • 思维链和思维树
    • Prompt 攻击和防范
    第二阶段(30天):高阶应用

    该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

    • 为什么要做 RAG
    • 搭建一个简单的 ChatPDF
    • 检索的基础概念
    • 什么是向量表示(Embeddings)
    • 向量数据库与向量检索
    • 基于向量检索的 RAG
    • 搭建 RAG 系统的扩展知识
    • 混合检索与 RAG-Fusion 简介
    • 向量模型本地部署
    第三阶段(30天):模型训练

    恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

    到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

    • 为什么要做 RAG
    • 什么是模型
    • 什么是模型训练
    • 求解器 & 损失函数简介
    • 小实验2:手写一个简单的神经网络并训练它
    • 什么是训练/预训练/微调/轻量化微调
    • Transformer结构简介
    • 轻量化微调
    • 实验数据集的构建
    第四阶段(20天):商业闭环

    对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

    • 硬件选型

    • 带你了解全球大模型

    • 使用国产大模型服务

    • 搭建 OpenAI 代理

    • 热身:基于阿里云 PAI 部署 Stable Diffusion

    • 在本地计算机运行大模型

    • 大模型的私有化部署

    • 基于 vLLM 部署大模型

    • 案例:如何优雅地在阿里云私有部署开源大模型

    • 部署一套开源 LLM 项目

    • 内容安全

    • 互联网信息服务算法备案

    • 👇👇扫码免费领取全部内容👇👇

    3、这些资料真的有用吗?

    这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。

    资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。

    这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

    需要专业的网站建设服务?

    联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

    立即咨询