我第一次让 ChatGPT 查天气,心里想的是:“它肯定内置了一个天气插件,我一问,它就去调接口。” 后来看文档才发现,根本不是这么回事。模型自己不会调任何 API,它只是在输出一段特殊的 JSON 文本——然后由你的代码去执行那个 JSON 里描述的函数调用,再把结果喂回给模型。这个误解让我意识到,工具调用根本不是模型“会”用工具,而是我们教会了它一种格式,让它能说出“我想调这个函数,参数是这些”。

那这个“教”是怎么发生的?为什么有的模型能精准输出 {"name": "get_weather", "parameters": {"city": "Beijing"}},有的模型却给你编一个不存在的函数名?这件事要从训练数据和推理时的提示工程两条线一起看。
模型不是天生就会写 JSON
一个预训练的大语言模型,本质上只做一件事:根据前面的文本,预测下一个 token。它看到过无数代码、文档、论坛帖子,所以它知道 JSON 长什么样,也知道函数调用的概念。但如果你直接问它“帮我查一下北京的天气”,它大概率会返回一段自然语言:“抱歉,我无法实时查询天气,建议你打开天气 App。” 因为它的训练目标从来不是“调用工具”,而是“生成有帮助的文本”。
让模型能够输出工具调用指令,核心是 instruction tuning(指令微调)阶段。训练数据里会加入大量类似这样的对话:
用户: 北京今天天气怎么样?
助手: 好的,我需要调用天气查询工具。
{
"function": "get_current_weather",
"arguments": {
"location": "Beijing, China",
"unit": "celsius"
}
}
模型通过大量这样的示例学会了:当用户问天气,我应该输出一个 JSON 对象,而不是直接编一个天气结果。OpenAI 的 Function Calling 文档 里明确说,模型本身并不执行函数,它只是生成参数。Anthropic 的 Tool Use 文档 也强调,Claude 被训练成“当用户请求可能需要工具辅助时,输出一个格式正确的工具使用块”。
推理时,模型怎么知道该调哪个函数?
这就是提示工程发挥作用的地方。你在调用 API 时,会把可用函数的定义(名称、描述、参数 schema)放在系统提示或工具列表里。比如:
tools = [{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定城市的当前天气",
"parameters": { ... }
}
}]
模型看到这些定义后,会将其当作上下文的一部分,然后在生成时决定是否输出一个工具调用。这个过程很像带约束的文本生成:模型不仅要符合对话逻辑,还要时刻考虑“我有没有可用的工具,以及当前需求是否匹配某个工具的描述”。
我最早以为模型内部有一个专门的“工具调用决策模块”,后来读了 Toolformer 论文 才明白,其实还是自回归生成,只不过训练数据里插入了特殊标记(比如 [weather_api(Beijing)]),模型学会了在适当位置插入这些标记,然后由外部解析器执行。Gorilla 这个专门研究 API 调用的模型,也是通过构建大量 指令-API 对的数据集,让模型学会从自然语言映射到具体 API 调用(Gorilla 项目主页)。
一场格式战争:不同厂商的“方言”
虽然原理相通,但各家的实现细节完全不同。看下面这张表:
| 厂商 | 工具定义方式 | 模型输出格式 | 特色 |
|---|---|---|---|
| OpenAI | tools 参数,JSON Schema |
tool_calls 块,包含函数名和 JSON 参数 |
支持 strict 模式强制 JSON 输出;并行调用 |
| Anthropic | tools 参数,JSON Schema |
tool_use 内容块,独立于文本输出 |
工具结果自动注入对话;支持多轮交互 |
| Cohere | tools 参数,类似 JSON Schema |
工具调用事件,JSON 格式 | 直接集成在 Chat API 中,更强调多步推理 |
| Google Gemini | function_declarations |
functionCall 对象 |
与 Vertex AI 生态深度绑定 |
这让我想起一个坑:有一次我用 OpenAI 的格式去调 Anthropic 的模型,返回的 JSON 结构完全对不上,调试了半天才发现,工具调用的“方言”不互通。虽然都是 JSON,但字段名和嵌套方式不同,提示里的工具描述格式也不同。所以,如果你的应用需要多模型切换,必须单独适配每种模型的工具调用格式。
模型为什么会“幻觉”一个不存在的函数?
你可能遇到过这种情况:模型输出 {"function": "send_email", "arguments": {...}},但你根本没定义 send_email 这个工具。这是工具调用中最经典的失败模式。
原因在于:模型在预测 token 时,并不会严格检查函数名是否在你提供的工具列表中。它只是基于训练数据里的模式,觉得“这个场景下通常应该调用 send_email”。如果你的工具描述写得不够清晰,或者用户请求和你提供的某个工具描述有模糊匹配,模型就可能“脑补”出一个函数名。
解决这个问题,目前有几个有效手段:
- 在工具描述中写清楚唯一标识:比如不要只写“发送邮件”,要写“发送邮件工具:send_email”。
- 使用 few-shot 示例:在提示里给一两个完整的工具调用示例,让模型看到“正确格式”应该长什么样。
- 在调用侧做校验:代码里必须检查函数名是否在定义列表中,参数类型是否匹配,不要盲目信任模型输出。
我自己踩过这个坑:给模型一个“查询数据库”的工具,用户问“帮我查一下最近的订单”,模型却返回了一个叫 search_orders 的函数,而我根本没定义过。后来我把工具描述改成“数据库查询工具:query_database”,并加入了一个示例,问题就解决了。
从“输出 JSON”到“真正用工具”还有多远?
现在大部分工具调用实现,流程都是这样的:
- 用户发送消息,附带工具定义。
- 模型决定是否调用工具,如果调用,输出结构化指令。
- 你的代码执行这个指令,拿到实际结果。
- 把结果作为新一轮消息发给模型,让模型生成最终回答。
这个流程里,模型本身仍然是“盲人”——它不知道工具执行的真实结果,只能靠你喂给它的文本。如果工具返回了错误信息,模型可能会尝试解释错误,或者重新发起调用(如果支持多步)。Anthropic 的 Claude 在这方面做得比较完善,它允许在工具结果返回后,模型继续生成下一步行动,形成“工具调用-结果反馈-再决策”的循环。
但这里面藏着另一个问题:模型对工具返回结果的信任度是 100%。它不会质疑工具返回的数据是否准确、是否被篡改。所以,如果你的工具依赖外部 API(比如天气、汇率),那么模型最终输出的可靠性就完全取决于那个 API 的可靠性。
能力边界:什么时候工具调用会失效?
经过大量实践,我总结出工具调用在以下场景容易翻车:
- 参数类型复杂:比如嵌套对象、数组,模型可能生成错误的 JSON 结构,导致解析失败。
- 工具数量过多:超过 10 个函数时,模型容易选错函数,或者忽略某些工具。OpenAI 建议工具数量控制在 20 个以内,并尽量让描述区分度大。
- 需要多步推理的工具组合:比如“先查用户 ID,再查订单,再查物流”,模型可能直接跳过中间步骤,幻想一个最终结果。这需要外部编排逻辑(如 LangChain 的 Agent)来强制分步执行。
- 工具依赖外部状态:比如“获取当前时间”,模型不知道“当前”是什么,它只是输出了参数。如果时间戳错了,是因为你的系统时间错了,而不是模型错了。
相比之下,RAG(检索增强生成) 和工具调用经常被混着用,但区别很大:RAG 是给模型塞一段背景知识,让它基于那段知识回答;工具调用是让模型请求执行一个外部动作,然后基于结果回答。RAG 适合“引用文档”,工具调用适合“查实时数据、执行操作”。
FAQ:几个高频误解
模型调用工具时,是直接联网了吗?
不是。模型只是输出文本,你的代码才去联网。模型本身没有网络访问能力。
为什么模型有时输出一半 JSON 就停了?
可能是 max_tokens 设置太小,导致 JSON 还没生成完就被截断。可以尝试增大输出长度限制,或者使用流式输出时手动拼接完整 JSON 再解析。
可以让模型同时调用多个工具吗?
OpenAI 支持并行工具调用,模型会输出多个 tool_calls 块。但要注意,这些调用是独立的,如果它们之间有依赖关系,模型可能并不会正确处理,需要外部逻辑确保顺序。
工具调用和 Agent 是一回事吗?
不是。工具调用是 Agent 的一个子能力。Agent 通常包含记忆、规划、多工具编排等,而工具调用只是其中“执行动作”这一环。
为什么我的工具调用经常返回空参数?
检查参数描述是否清晰,以及是否在工具描述中明确要求必须提供参数。有时候模型认为参数是可选的,就会省略。用 required 字段约束。
我最终理解工具调用的本质,是在看到 Cohere 的文档 里的一句话:“The model is trained to output structured JSON that can be parsed by your application.” 这句话点醒了我:模型只是一个文本生成器,它能输出 JSON 是因为我们教了它格式,而不是因为它理解了“调用”的含义。所以,下次当你惊叹于 AI 能帮你订机票、查天气时,别忘了,它的背后是一堆精心设计的提示词和健壮的异常处理代码。工具调用的未来,不是让模型自己学会更多 API,而是让我们的编排系统更聪明——直到有一天,模型能真正理解“调用”不是输出一段 JSON,而是改变世界的一步动作。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/83.html