想象一下,一位博学多才的“幕僚长”坐在办公室里,他读过成千上万本书,能回答任何理论问题,但他的手不能提、肩不能扛,连开个门都办不到。老板让他订机票、查天气、发邮件,他只能干瞪眼——因为他只会“说话”,不会“做事”。现在,公司给他配了一本《服务手册》,上面写着每个部门能干什么、需要什么材料。幕僚长只需要填一张“工单”,交给对应的执行部门,再把结果带回来汇报给老板。这套机制落到大模型身上,就是“工具调用”(Tool Calling):大模型不亲自执行动作,而是输出一个结构化的“调用请求”,由你的代码去调用真实世界的 API 或函数,再把结果喂回给模型。这就是 Agent(智能体)从“会聊天”走向“能办事”的关键一跃。本文就用“幕僚长 + 服务手册 + 工单流转”的故事,带你彻底看懂工具调用的来龙去脉。
对 AI 从业者而言,掌握工具调用是构建 Agent 的必修课:它决定了模型能否接入真实系统、执行有实际价值的任务,是对话式 AI 走向生产力的核心能力。
可视化演示
展示LLM工具调用全流程(user→LLM→JSON→API→resp含结果回环)、自然语言到结构化参数的映射构造、以及多工具选择编排策略与评分排序。
本节配有交互动效(进入视口后自动加载,约 3KB)。
工具调用五阶段流水线 用户查询(user)经LLM推理输出结构化函数调用(call),包含函数名name和参数args的JSON 外部执行环境接收JSON调用真实API,结果(rslt)回传LLM生成最终响应(resp) resp=LLM(query,API(call(query))) 工具调用让LLM从纯文本推理走向可操作真实世界的智能体
工具调用本质
我们的故事从幕僚长说起。他知道天文地理,能写一手漂亮的奏折,但老板要他把文件送到隔壁大楼,他只能尴尬地站在原地——因为他只是一台“会说话的机器”。幕僚长缺的不是智慧,而是“手”。给 LLM(大语言模型)装上工具调用,就是给它配了一双可以借来的手。
LLM 本身只会做一件事:根据输入的 token 预测下一个 token,最终生成文本。这意味着它无法真正查询数据库、调用支付接口、控制机器人。工具调用的本质,是让模型在文本输出里夹带一份“结构化请求”:我要调用哪个工具、传入什么参数。真正动手执行的,不是模型,而是你写的宿主程序。这种“意图与执行分离”的设计,让模型从“知道”走向“做到”,也让 Agent 第一次有能力影响真实世界。
在工程实现上,模型会输出类似 tool_calls 的字段,其中包含工具名和 arguments(参数)。宿主程序看到这个字段后,就去调用对应的函数,拿到返回值,再把这个返回值以系统消息的形式放回对话里。于是模型可以“看到”结果,并据此给出最终回答。整个过程可以简洁地表示成一个三元组:
类比到幕僚长的故事里:他不需要亲自去送文件,只需要在工单上写下“送往哪个部门、交给谁”,自然有跑腿专员完成后面的事。工具调用就是把这套“只写工单、不亲自动手”的智慧,变成了 AI 的默认能力。
- 选工具模型先从工具清单里选出最合适的函数名,记为 f。
- 填参数模型按照工具的 Schema 生成参数向量 x,就像填写工单的各个字段。
- 执行并返回宿主程序调用函数,得到结果 r,再把它交给模型继续推理。
工具定义规范
工单要能流转,前提是有一本大家都能看懂的《服务手册》。幕僚长之所以敢填单,是因为手册上每项服务都写清楚了三件事:服务叫什么、负责做什么、需要客人提供什么材料。工具定义规范,就是给模型看的服务手册。
在代码里,一个工具通常被定义成一个 JSON 对象,包含三个核心字段:name(函数名)、description(自然语言描述)、parameters(参数 Schema)。name 是唯一标识,description 告诉模型这个工具什么时候该用,parameters 用 JSON Schema 描述参数结构。描述写得越清楚,模型越不容易把“查天气”和“查日历”搞混。
工具集合可以形式化地写成一组三元组:
就像服务手册不允许手写模糊词,工具的 description 也必须精确。公司的部门越来越多,手册越规范,幕僚长出错越少;同理,工具定义越规范,大模型调用工具的准确率就越高。
- 集合表示花括号表示这是一个工具集合,i 从 1 取到 M,代表 M 个候选工具。
- 三要素每个工具是一个三元组,n 是函数名,d 是给模型看的描述,S 是参数约束。
- 约束能力模型看到 S 后,才知道参数该传字符串还是数字、哪些必填。
调用流程流转
有了手册和工单,接下来就是标准的办公室流程:幕僚长写好工单 → 交给跑腿专员 → 专员去执行部门办事 → 拿到回执 → 回来向幕僚长汇报 → 幕僚长再向老板汇报。工具调用的完整流程,几乎一模一样。
一个标准流程通常是:用户提问 → 模型判断需要工具,输出 tool_calls → 宿主程序解析并调用真实函数 → 工具返回结果 → 结果被拼回对话 → 模型基于结果生成最终回答。如果信息还不够,模型可以再发起一轮工具调用,形成多轮迭代。这个循环会一直持续,直到模型不再输出 tool_calls。
从状态角度看,每一轮对话都可以看作一个状态 s,工具结果 r 会更新这个状态:
这套流程的价值在于“可复现、可追踪”。幕僚长每一次派单都有记录,老板可以随时查看;工程上每一次工具调用也都有日志,方便调试和审计。工单流转的严谨性,正是工具调用流程能够成为 Agent 标准动作的原因。
- 当前状态s_t 表示第 t 轮时,对话历史、用户问题、之前工具结果组成的上下文。
- 工具结果r_t 是刚执行完的工具返回值,它会被序列化成文本加入上下文。
- 状态更新模型 M 读取新上下文,决定是再次调用工具还是输出最终答案。
结构化参数构造
幕僚长填工单时,最怕什么?最怕申请表上写着“姓名(必填):”,他却填了个数字;写着“年龄(数字):”,他填了个“年轻”。参数填错,服务部门就没法干活。工具调用里的结构化参数构造,就是确保这张表不被填错。
JSON Schema 就是这张表的“填写规范”。它定义了每个参数的类型(string、number、object 等)、是否必填、取值范围、嵌套结构。模型在生成 arguments 时,必须遵循这套规范;宿主程序在调用前也可以做一次校验。一个合法的参数对象必须满足所有约束条件:
一旦参数出错,轻则工具报错,重则产生错误的业务动作。现实中,优秀的 Agent 框架会在调用工具前增加一层参数校验器,就像办公室前台先检查工单格式再盖章。毕竟,幕僚长填错的代价,不能由执行部门承担。
- 必填集合R 是所有必填参数的集合,比如查询天气时
city必填。 - 存在性检查
k ∈ x表示参数对象里必须真的有这个键,缺了就会校验失败。 - 类型检查
type(x_k) = τ_k表示值的类型必须和 Schema 声明的一致。
结果解析融合
专员办事回来,不会把一摞原始单据直接甩给老板,而是先回到幕僚长这里,把单据翻译成老板能听懂的汇报。工具调用的结果,同样需要被“翻译”一遍才能发挥价值。
工具返回的通常是一段 JSON 字符串。宿主程序要把它解析成结构化对象,判断是正常结果还是错误信息,再序列化成一段文本消息,放回模型的上下文中。这样模型才能“读懂”结果并组织自然语言回复。如果解析失败或结果异常,不能默默吞掉,而要把错误信息原样回传给模型,让它自己纠错或换一个工具。上下文更新可以写作:
这一步最容易被忽略。很多初学者以为工具返回了 JSON,模型就会自动理解;实际上,如果不把结果拼接回对话,模型就还是“看不见”结果。幕僚长要做的不是把单据贴在脸上,而是把关键信息提炼进自己的汇报里,这才是结果解析融合的精髓。
- 旧上下文h_t 是模型当前已知的全部信息,包括用户问题、历史轮次。
- 序列化结果
serialize(r_t)把工具返回的 JSON 变成一行或一段可读文本。 - 拼接融合⊕ 表示拼接,新上下文把旧历史和工具结果放在一起。
多工具编排策略
一个复杂的任务,往往不是单个部门能完成的。比如老板说:“帮我订明天去上海的机票,再订一家离机场近的酒店。”幕僚长需要同时联系航空公司订票、联系酒店订房;但如果酒店想派接送机,就必须等航班号出来才能订。这就是多工具编排。
编排策略主要有三种:并行调用——多个工具之间没有依赖,可以同时执行;顺序依赖——后一个工具的参数依赖前一个工具的结果,必须排队执行;工具路由——模型根据用户意图和工具描述,动态决定调用哪个工具,而不是写死调用顺序。并行执行能大幅节省时间:
幕僚长的价值正在于此:他不只是跑腿,而是知道哪个部门可以同时联系、哪个必须等前一个回执。Agent 的多工具编排能力越强,处理复杂任务的能力就越强;这就像一位经验丰富的幕僚长,能把一摊乱事安排得井井有条。
- 并行情形所有工具一起开工,总时间等于最慢的那个工具的耗时。
- 串行情形一个工具执行完才能执行下一个,总时间是所有耗时之和。
- 依赖判断到底用哪种方式,取决于不存在数据依赖(parallel)还是存在依赖(sequential)。
开放挑战与标准化
故事还没结束。公司扩张得很快,每天都有新部门成立,服务手册也在不断更新。幕僚长偶尔会遇到完全没见过的部门,只能凭手册上的寥寥数语推断怎么填单。能不能办成事,考验的就是“开放世界泛化”和“鲁棒性”。
对工具调用来说,开放世界泛化指的是:模型在新工具从未出现在训练数据中时,能不能仅凭 name 和 description 推断出正确用法。鲁棒性则指:当参数类型不匹配、工具返回异常、或模型幻觉编造了不存在的工具名时,系统能不能优雅地恢复。目前业界正通过统一接口规范和评估基准(比如 MCP、函数调用基准测试)来推动生态成熟。这个挑战可以形式化地表示为泛化误差:
幕僚长不可能背下所有部门的所有规则,但他读得懂手册背后的逻辑;模型也不可能记住所有工具,但它可以通过语义理解新工具的 Schema。标准化的工作,就是让每一本新手册都采用同一种“语言”,让幕僚长面对新部门时也能从容应对。
- 期望符号E 表示在所有可能的新工具和问题组合上取平均,代表平均表现。
- 预测对比ŷ 是模型猜测的调用,y 是真实正确的调用,两者用不等号比较。
- 指示函数1[……] 表示如果预测错了就记 1,否则记 0,用来统计错误率。
AI 应用场景
智能客服:查单与退换货
阶段:Agent 工具调用 + 任务执行
解决的问题:传统客服机器人只能根据固定话术回复,遇到“我的快递到哪了”这类问题就答非所问。引入工具调用后,模型可以输出调用“查询物流 API”的请求,宿主程序拿到真实物流信息,再组织成自然语言回复。
具体案例:用户说“订单 12345 为什么还没到”,Agent 调用 get_order_status(order_id="12345"),API 返回“运输中,预计明天到达”,模型据此回复用户。整个流程就像幕僚长替老板打电话问物流公司。
数据助手:自然语言转查询
阶段:工具路由 + 结果解析融合
解决的问题:业务人员不会写 SQL,数据分析师又被重复查询淹没。Agent 可以根据用户问题路由到数据库查询工具,执行 SQL,把返回的结果解析后生成图表或摘要。
具体案例:用户问“本月各区域销售额对比”,模型调用 query_database(sql=...),工具返回 JSON 数据,模型再调用 render_chart(data) 生成图表。这就像幕僚长同时拉通了数据部门和设计部门。
智能运维:故障定位与通知
阶段:多工具编排 + 多轮迭代
解决的问题:运维人员需要登录多台机器查指标、翻日志,效率低。Agent 可以并行调用监控 API 和日志查询工具,拿到线索后再调用脚本执行诊断,最后把结论发给值班群。
具体案例:告警触发后,Agent 先并行调用两个指标查询工具,发现 CPU 飙高,再按依赖顺序调用日志分析工具,定位到慢查询,最后调用消息推送工具通知负责人。就像一个经验老练的幕僚长同时调度多个部门。
Python 演示
import json def get_weather(city): table = {"北京": "晴, 26度", "上海": "多云, 29度"} return table.get(city, "未知城市") def calculator(expr): try: return str(eval(expr)) except Exception as e: return "计算错误: %s" % e TOOLS = { "get_weather": {"fn": get_weather, "params": ["city"]}, "calculator": {"fn": calculator, "params": ["expr"]}} def call_tool(name, arguments): tool = TOOLS.get(name) if tool is None: return json.dumps({"error": "unknown tool: %s" % name}) try: result = tool["fn"](**arguments) except Exception as e: result = "执行异常: %s" % e return json.dumps({"name": name, "result": result}) def llm_reply(user_input): calls = [] if "天气" in user_input: calls.append({"name": "get_weather", "arguments": {"city": "北京"}}) if "计算" in user_input: calls.append({"name": "calculator", "arguments": {"expr": "1+2"}}) if calls: return {"tool_calls": calls} return {"content": "需要我帮你查天气或做计算吗?"} def main(): user_input = "北京天气怎么样? 顺便计算 1+2" reply = llm_reply(user_input) if "tool_calls" in reply: results = [call_tool(c["name"], c["arguments"]) for c in reply["tool_calls"]] final_answer = "工具返回: " + " | ".join(results) else: final_answer = reply["content"] print("用户: %s" % user_input) print("Assistant: %s" % final_answer) if __name__ == "__main__": main()
TOOLS = {...}模拟“服务手册”,登记每个工具的函数和可接收参数名。llm_reply(user_input)模拟模型输出tool_calls结构化请求,这里用规则替代真正的大模型。call_tool(name, arguments)宿主程序根据工具名查找函数,并用参数调用它。tool["fn"](**arguments)Python 的**语法把字典展开成关键字参数,等价于按工单执行。json.dumps(...)把工具返回结果序列化成 JSON 字符串,方便回填给模型。if "tool_calls" in reply:判断模型是否要求调用工具,这是流程的转折点。results = [call_tool(...) for ...]支持一次请求并行处理多个工具调用,体现多工具编排。final_answer = "工具返回: " + ...简单演示把多工具结果拼成最终输出。
常见误区
误区我应该让大模型直接执行 Python 代码或者调用 API,只要告诉它“去查一下数据库”就行了。
正解大模型没有任何执行环境,它只会生成文本。工具调用中,模型负责输出“我想调用 function_name,参数是 xxx”的请求,真正执行函数的是你的宿主代码。就像幕僚长写工单,跑腿的是专员,不是幕僚长本人。理解了这一点,你才能正确设计 Agent 架构,把执行权放到宿主侧。
为何常见:因为“AI 帮我做事”的直觉印象太强,很多人忽略了模型和宿主程序之间的分工,导致他们找不到“执行到底发生在哪一层”。
误区反正 Python 是动态类型语言,我传字符串 "123" 和传数字 123 差不多吧,工具应该能自动转换。
正解工具定义的 JSON Schema 明确约定了参数类型、必填项和取值范围。如果模型生成 {"city": 123} 而 Schema 要求 string,轻则校验失败,重则触发类型转换导致业务数据错乱。结构化参数构造是工具调用的“质量红线”。你可以像工单审核员一样,在调用前做一次 schema 校验。
为何常见:日常编程中动态类型很自由,让人低估了异构系统对接时严格类型约束的重要性。
误区工具返回什么,模型就会自动看到什么,所以不需要额外处理结果。
正解工具结果必须由宿主程序读取、解析、序列化,再以文本消息的形式拼回对话上下文,模型才能“看到”。如果你的代码没有把结果传给模型,模型只会继续给出笼统回答。这就像专员把报表放在门口没拿进办公室,幕僚长根本不知道已经有人送过材料。
为何常见:很多人把“返回值”和“上下文”混为一谈,忘了模型能感知的只有它的输入文本。
误区如果任务需要两个工具,我只能让用户分两轮问,或写死先后顺序。
正解现代工具调用协议允许模型在一次回复中输出多个 tool_calls,宿主可以并行执行无依赖的工具,也可以按依赖关系编排执行顺序。多工具编排让 Agent 能像幕僚长一样同时联系多个部门,而不是低效地一件件跑腿。
为何常见:初识工具调用时,大多数教程只展示单个工具的例子,容易让人形成“一轮只能调一次”的思维定式。
面试题
Q1. 在工具调用流程中,真正负责执行外部工具的是谁?
- A.大语言模型自身
- B.宿主程序或运行时← 正确答案
- C.用户手工编写代码
- D.操作系统内核心
答案:B。模型只输出调用请求,不执行任何程序;宿主程序根据 tool_calls 找到对应函数并调用它。就像幕僚长写工单、跑腿专员去执行,角色边界非常清晰。
Q2. 工具定义中通常不包含以下哪一项?
- A.函数名和签名
- B.自然语言描述
- C.参数结构 Schema
- D.模型的训练语料← 正确答案
答案:D。工具定义是给模型看的“服务手册”,包含函数名、描述和参数 Schema;训练语料不是某个工具的属性。没有哪本服务手册会写员工的童年往事,对吧?
Q3. 模型输出 tool_calls 之后,宿主程序第一步应该做什么?
- A.直接把输出展示给用户
- B.解析工具名和参数并执行← 正确答案
- C.把该输出加入训练集
- D.丢弃请求并重新生成
答案:B。tool_calls 是结构化调用请求,宿主程序要解析出工具名和 arguments,然后执行对应函数。好比幕僚长写好工单,下一步就是把工单派出去,而不是把工单裱起来给老板看。
Q4. JSON Schema 中 required 字段的作用是什么?
- A.指定返回值的编码格式
- B.声明调用时必须提供的参数← 正确答案
- C.定义函数在系统中的别名
- D.限制工具的每秒调用次数
答案:B。required 列出必填参数,缺失会让校验失败。就像申请表注明“手机号必填”,漏填就交不了单;这个字段确保模型填的参数足够完整。
Q5. 工具返回的 JSON 解析失败时,合理的做法是什么?
- A.忽略错误并继续正常对话
- B.捕获异常并把错误回传模型← 正确答案
- C.随机生成一个替代结果
- D.立即终止整个 Agent 进程
答案:B。解析失败时,把错误信息回传给模型,让它知道发生了什么、尝试换一种参数或换工具,这才是鲁棒性的体现。幕僚长发现专员没办成事,不会假装办成,而是回去如实汇报并重新协调。
Q6. 多工具编排中,以下哪些策略是合理的?
- A.无依赖工具并行调用← 正确答案
- B.有依赖工具顺序执行← 正确答案
- C.根据意图动态路由选择← 正确答案
- D.永远只调用第一个工具
答案:ABC。并行、顺序、动态路由都是常见编排策略;“永远只调用第一个工具”忽略了用户实际意图,是错误的。幕僚长会按需调部门,而不是只找电话本上第一个号码。
Q7. 关于工具调用的作用,下列说法正确的是哪一项?
- A.它让模型具备读写外部世界的能力← 正确答案
- B.它让模型拥有无限大的上下文
- C.它让模型不再需要预训练数据
- D.它让模型完全不会产生幻觉
答案:A。工具调用让模型通过宿主程序访问外部 API 和数据,是“借来的手”;它不能解决上下文容量、预训练依赖或幻觉问题。幕僚长虽然能跑腿办事,但不可能因此变成全知全能。
Q8. 工具调用在开放世界中的泛化能力,主要指什么?
- A.模型能根据新工具的描述推断用法← 正确答案
- B.模型生成工具调用速度足够快
- C.模型能记住所有历史工具名称
- D.模型能自动修改工具源码
答案:A。开放世界泛化是指面对训练时没见过的工具,模型仅凭 name 和 description 就能正确构造参数。如同幕僚长第一次见到新部门,也能根据手册推敲出怎么填单;背下所有工具名不是泛化。
Q9. 关于工具调用的标准化,下列哪些方向正在推进?
- A.统一接口规范← 正确答案
- B.统一评估基准← 正确答案
- C.共享参数 Schema 标准← 正确答案
- D.禁止第三方工具接入
答案:ABC。标准化包括接口规范、评估基准、Schema 标准等;禁止第三方工具接入恰恰与开放生态背道而驰。就像全行业统一工单格式,而不是让大家别用新部门。
Q10. 以下哪些情形会显著降低工具调用的鲁棒性?
- A.参数类型与 Schema 不匹配← 正确答案
- B.工具返回空值或异常结构← 正确答案
- C.模型虚构不存在的工具名← 正确答案
- D.用户提问的句子长度较长
答案:ABC。参数类型错误、异常返回、幻觉工具名都会破坏调用链;用户问题较长并不必然降低鲁棒性。幕僚长最怕的是工单格式错、部门不存在、对方乱回复,而不是老板多问了几句。