详细解释
工具调用(Tool Use / Function Calling,FC) 一句话定义:让 LLM 在生成文本的过程中,遇到”自己没法直接回答”的事情时,主动停下来,生成一段严格符合 JSON Schema 的”工具参数”,然后调用外部函数/HTTP API,把返回结果继续喂回模型上下文,接着生成最终答案。没有工具调用的 LLM 是”只活在训练数据里的书呆子”——问它今天上海的天气,它只能根据训练数据里的上海 1 月偏冷瞎编。有了工具调用,它可以生成:
{
"name": "get_weather",
"arguments": {"city": "上海", "date": "2026-08-24"}
}
→ 服务端用这段参数调第三方天气 API → 把返回 {temperature: 31, condition: "多云"} 塞回对话 history → 模型再根据真实天气生成回答,就是所谓的” Grounded Generation(落地生成)”。
一次典型工具调用的 6 步消息轮次
以用户问 “2025 年 Q1 唯元智创的平均 RPM 是多少?按同比和环比算一下增长率”为场景:
- Step 1 用户消息 User:上面那句话。
- Step 2 模型第一轮返回 Assistant(content=null, tool_calls=[…]):模型决定要调用”取内部指标数据库 query”和”数学计算”两个工具,生成两个 tool_call JSON,带每个 tool_call 的 id。
- Step 3 服务端并发调两次函数:
internal_kpi(query='weimeta RPM Q1 2024/2025')返回数据行;math_calculator(formula='(y25-y24)/y24*100')等待数据。 - Step 4 把工具结果以 Role=tool 的消息回塞:每个 tool_call 对应一条
role=tool, tool_call_id=xxx, content='实际返回的 JSON 字符串'。 - Step 5 模型第二轮生成 Assistant(content=文本回答):拿到真实数据后开始做解读、生成回答文本。
- Step 6 流式返回给用户:把 Step 5 生成的回答通过 Streaming SSE 流到前端。
中间可能迭代多轮(上一步工具返回说”参数不够,还需要时间范围”→ 模型再生成第二条 Tool Call 补充参数),直到模型觉得已经足够回答,就停止调用工具。
主流 API 协议版本差异
2023 年 OpenAI 第一次推出 Function Calling 之后,协议经历了三代演进:
| 版本(时间) | 参数组织方式 | 支持多工具并行调用 | 流式 tool_calls 支持 |
|---|---|---|---|
| v1 初代(2023 Q2) | functions=[{name, description, parameters}] 顶层字段 + function_call 返回 role=assistant | ❌ 一次只能调一个 | ❌ 只能非流式 |
| v2 现代(2023 Q4) | tools=[{type:'function', function:{...}}] 顶层字段 + 返回 tool_calls[] 数组,每条带 id,回复时 role=tool + tool_call_id | ✅ 并行调用任意多个 | ✅ 流式:先吐 delta.tool_calls[i].index/.id/.function.name/.arguments |
| v3 严格结构化(2024 Q3) | tools 字段相同,但新增 strict=true 强制模型”parameters JSON Schema 字段一个不能漏/一个不能加”,FC 准确率从 92% 提升到 99.5%+ | ✅ | ✅ |
唯元智创 兼容的 /chat/completions 端点完全支持 v2/v3 协议:strict=true 下函数调用漏参率 < 0.4%(我们自己的内部测试集),适合生产级代码工具调用流水线不用写大量 if-else 兜底。
写好工具定义(Tool Schema)的 4 条黄金法则
90% 的 FC 失败是因为你 Schema 写得太烂,不是模型能力不行。记住以下四条能把你的 Tool Call 成功率从 70% 拉到 98%:
- Description 要用”何时调、何时 NOT 调”的负例写法,不要写”这个函数取天气”。
- ❌ Bad:
description: "获取指定城市天气" - ✅ Good:
description: "当用户询问未来 7 天内某个具体城市的实时或预测天气,且问题中出现明确城市名时调用。如果用户只是泛泛说今天天气真好/聊气候话题/或没有给出城市名时,不要调用此工具。"
- ❌ Bad:
- 参数 Enum 能写尽写:对
city字段如果你的 API 只支持 31 个省辖市,就把 31 个都列成 Enum,模型就不会瞎编 “浦东市” 这种不存在的城市。 - Required 字段必须和真实后端校验一致:后端实际需要
city + date两个参数,Schema 里 Required 只写了 city → 模型 40% 会漏 date,你就会查不出来。 - 每个参数都要有独立的 description:对
date参数写ISO 8601 格式 YYYY-MM-DD,仅支持今天之后 7 天内,模型就不会给你传date='2026年9月1号'这种自由格式。
与 Agent / Computer Use 的关系
- 单工具调用(Function Calling) = 原子能力(一次调用一个 HTTP API)。是积木块。
- Agent(智能体) = 多工具 + 多轮 + 规划(ReAct / ToT / Self-Consistency 等)。是积木拼出来的乐高机器人。
- Computer Use(计算机使用) = Agent 的特殊形态,工具集合变成”鼠标点击 / 键盘输入 / 截图”,本质上就是 3 个通用工具(mouse_click / type_text / take_screenshot)。
唯元智创 的 Agentic SDK 把这三个层级做了统一:先用 Function Calling 包每个原子 API,再套 ReAct Agent 规划层,最后 Computer Use 也是复用同一套 SDK 的 BaseTool 类,不用写两套实现。
常见问题
模型经常”该调用工具时不调用,不该调用时乱调”怎么办?
一个请求里最多能并行调用多少个工具?
结构化输出(response_format=json_object)和 Function Calling 是什么关系?
{"name":"xxx", "arguments":{...}} 是同一能力,只是 Structured Output 把 “arguments 部分” 直接当成最终答案返回给你,没有后续 role=tool 的下一轮调用。所以如果你的业务是纯”抽取 / 分类 / 返回结构化数据给前端渲染”,不需要让模型”再调用外部函数”,直接开 response_format=json_schema 最省事;如果需要”先调外部 API 拿结果,再生成最终回答”的多步链路 → 上 Function Calling / Tool Use。