掌握工具调用(Function Calling):让LLM从聊天机器进化为高效Agent,收藏学习必备!

张开发
2026/4/14 9:06:52 15 分钟阅读

分享文章

掌握工具调用(Function Calling):让LLM从聊天机器进化为高效Agent,收藏学习必备!
本文深入解析了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 responseawait 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}}]}}这里有几个坑finish_reason是tool_calls而不是stop你需要判断这个arguments是字符串不是对象需要手动JSON.parse()LLM 可能一次返回多个 tool_calls并行调用3、 完整工具调用循环多轮对话机制单次工具调用只是开始。真实场景里一次用户请求可能需要多次工具调用才能完成。这就是为什么叫循环Loop。┌─────────────────────────────────────────────────┐ │ 工具调用完整循环 │ │ │ │ ┌─────────┐ │ │ │ 用户提问 │ │ │ └────┬────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ LLM 决策 │ │ │ │ finish_reason tool_calls│ │ │ └──────────┬───────────────────┘ │ │ │ │ │ ┌─────▼─────┐ │ │ │ 执行工具 │ ← 你的代码 │ │ └─────┬─────┘ │ │ │ ToolMessage │ │ │ │ │ ┌─────▼───────────┐ │ │ │ 把结果加入消息历史 │ │ │ └─────┬───────────┘ │ │ │ │ │ ┌─────▼──────────────────────────┐ │ │ │ LLM 再次决策 │ │ │ │ finish_reason stop → 结束 │ │ │ │ finish_reason tool_calls │ │ │ │ → 继续循环 │ │ │ └────────────────────────────────┘ │ └─────────────────────────────────────────────────┘手动实现这个循环原始 OpenAI SDKimportOpenAI fromopenai;const openainew OpenAI();asyncfunctionrunToolLoop(userMessage: string){const messages: OpenAI.Chat.ChatCompletionMessageParam[][{role:user, content: userMessage}];while(true){const responseawait openai.chat.completions.create({model:gpt-4, messages, tools, tool_choice:auto});const choiceresponse.choices[0];messages.push(choice.message);// 把 AI 回复加入历史 // 如果不需要调用工具循环结束if(choice.finish_reasonstop){returnchoice.message.content;}// 执行所有工具调用for(const toolCall of choice.message.tool_calls ??[]){const argsJSON.parse(toolCall.function.arguments);const resultawait 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 用BaseTool和bind_tools做了标准化。┌──────────────────────────────────────────────────┐ │ LangChain 工具抽象层 │ │ │ │ ┌─────────────┐ ┌──────────────────────────┐ │ │ │ BaseTool │ │ bind_tools(tools) │ │ │ │ │ │ │ │ │ │ .name │───▶│ 自动转换为各厂商格式 │ │ │ │ .description│ │ - OpenAI: tools[] │ │ │ │ .schema │ │ - Claude: tools[] │ │ │ │ ._call() │ │ - Gemini: functionDecl │ │ │ └─────────────┘ └──────────────────────────┘ │ │ │ │ 你只写一次工具定义适配所有 LLM │ └──────────────────────────────────────────────────┘三种定义工具的方式import{tool}fromlangchain/core/tools;import{z}fromzod;import{ChatOpenAI}fromlangchain/openai;// 方式1用 tool()函数最推荐类型安全 const getWeathertool(async({city, unitcelsius}){// 真实实现 const tempawait fetchWeatherAPI(city);return${city}当前温度${temp}°${unitcelsius?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}fromlangchain/core/tools;class DatabaseQueryTool extends BaseTool{namequery_database;description查询数据库中的用户信息;constructor(private db: DatabaseConnection){super();}async _call(input: string): Promisestring{const resultawait this.db.query(input);returnJSON.stringify(result);}}// 方式3DynamicStructuredToolZod schema 动态import{DynamicStructuredTool}fromlangchain/core/tools;const searchToolnew 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 modelnew ChatOpenAI({model:gpt-4o});const tools[getWeather, searchTool];// 绑定工具到模型 const modelWithToolsmodel.bindTools(tools);// 调用时LLM 会自动决定是否要调工具 const resultawait 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: contentnull, tool_calls[{id:call_1, name:get_weather, args:{city:上海}}] ↓ ToolMessage: content上海当前温度22°C, tool_call_idcall_1 ↓ AIMessage: content上海今天22度比较舒适适合户外活动。用 ToolNode 自动执行工具LangGraph 的正确打开方式import{ToolNode}fromlangchain/langgraph/prebuilt;import{HumanMessage}fromlangchain/core/messages;// ToolNode接收包含 tool_calls 的 AIMessage自动执行并返回 ToolMessage const toolNodenew ToolNode(tools);// 模拟 LLM 输出了一个 tool_calls const aiMessagenew AIMessage({content:, tool_calls:[{id:call_001, name:get_weather, args:{city:上海, unit:celsius}, type:tool_call}]});// ToolNode 自动执行并返回 ToolMessage const toolResultawait toolNode.invoke({messages:[aiMessage]});//{messages:[ToolMessage{content:上海当前温度22°C, tool_call_id:call_001}]}并行工具调用Parallel Tool Calls// LLM 可能一次返回多个 tool_calls const aiMessagenew 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_id6 、在 LangGraph 中组装完整工具调用 Agent把前面所有东西组合起来搭一个完整的 ReAct AgentimportStateGraphMessagesAnnotationfromlangchain/langgraphimportToolNodefromlangchain/langgraph/prebuiltimportChatOpenAIfromlangchain/openaiimportfromlangchain/core/toolsimportfromzodimportAIMessagefromlangchain/core/messages// 定义工具consttoolasynctry// 简单计算器生产环境用更安全的方式constFunctionuse strict; return (${expression})return计算结果${result}catchreturn计算失败请检查表达式格式namecalculatordescription执行数学计算支持加减乘除和括号schemaobjectexpressionstringdescribe数学表达式例如(35)*2consttoolasync// 实际实现调用搜索 APIreturn搜索结果关于${query}的最新信息...namesearchdescription搜索最新信息schemaobjectquerystringdescribe搜索关键词constconstnewToolNode// LLM 绑定工具constnewChatOpenAImodelgpt-4otemperature0constbindTools// 判断是否需要继续调用工具functionshouldContinuestate: typeof MessagesAnnotation.Stateconstmessagesmessageslength1asAIMessageiftool_callstool_callslength0returntools// 有 tool_calls → 去执行工具return__end__// 没有 tool_calls → 结束// 调用 LLM 节点asyncfunctioncallModelstate: typeof MessagesAnnotation.Stateconstawaitinvokemessagesreturnmessages// 构建 GraphconstnewStateGraphMessagesAnnotationaddNodeagentaddNodetoolsaddEdge__start__agentaddConditionalEdgesagentaddEdgetoolsagent// 工具执行完 → 回到 agentcompile// 运行constawaitinvokemessagesroleusercontent帮我查一下今日 GPT-4o 的最新进展然后算一下 (125 * 8 32) / 4 等于多少consolelogmessagesmessageslength1content整个执行流程 用户提问两个需求搜索 计算 ↓ agent 节点LLM 分析输出两个 tool_calls并行 ↓ tools 节点并行执行 search calculator ↓ 返回两条 ToolMessage 到 agent ↓ agent 节点LLM 综合工具结果生成最终回答 ↓ 结束finish_reason stop7、 工具调用常见坑与最佳实践坑1工具描述写得太短// ❌ 差的描述consttoolnamequery_dbdescription查数据库schemaobjectsqlstring// ✅ 好的描述——告诉 LLM 什么时候用、用来干什么consttoolnamequery_databasedescription查询用户数据库。 适用场景需要查找用户信息、订单记录、消费历史时。 输入 SQL 查询语句只支持 SELECT不支持修改操作。 返回 JSON 格式的查询结果最多返回 100 条记录。schemaobjectsqlstringdescribeSQL SELECT 语句例如SELECT * FROM users WHERE id 123坑2忘记处理工具执行失败consttoolasynctryconstawaitriskyOperationreturnJSONstringifycatch// ⚠️ 工具报错了别直接 throw// LLM 会收到 ToolMessage需要能读懂这个错误return工具执行失败${error.message}。请尝试修改参数后重试。name...description...schemaobject坑3tool_choice 设置不当// 强制调用某个工具适合测试constbindToolstool_choicetypefunctionfunctionnameget_weather// 完全禁用工具调用constbindToolstool_choicenone// 自动判断生产环境推荐constbindToolstool_choiceauto// 默认值坑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 限制最大循环次数constcompileconstawaitinvokemessagesroleusercontent...recursionLimit10// 最多循环 10 次防止死循环总结这篇我们从底层到工程完整拆解了 Function Calling 与 LangChain 工具链Function Calling 的本质LLM 只输出 JSON 描述该调哪个工具、传什么参数执行权在你手里完整调用循环用户提问 → LLM 决策 → 执行工具 → 结果回传 → LLM 再决策直到 finish_reason 为 stopLangChain 的价值用bind_toolsBaseTool统一了各厂商差异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%免费】

更多文章