工具调用:模型自己不动手

拆开一次真实的工具调用:模型只写调用请求,执行工具的是客户端,要让模型用上工具的结果,至少得请求它两次。用本地 Qwen3 1.7B 和 Claude Sonnet 5.5 实测。

Agent 工程工具调用AgentJSON6:05 · 2026-10-08

文字稿约 1900 字 · 按画面章节整理

同一个模型,配上工具,就答上来了?

问一个小模型:“东京现在几点?”它说,它无法直接知道东京当前的时间。还是同一个模型,给它配上一个查时间的工具,再问一遍,它就答上来了:东京现在是03:53。那么,是模型自己跑去查了时间吗?并不是。

今天,我们就把一次真实的工具调用拆开,看看每一步,到底是谁在动手。

打个比方:关在房间里的顾问

先打个比方。模型,就像一位关在房间里的顾问:懂得很多,但出不了门,外面的东西一样也碰不到。你也见不到他,你说的话,都要交给门外的助手,从门缝递进去。助手递进去的,除了你的问题,还有一张单子,上面写着,助手能帮忙做哪些事,比如“查某个城市的时间”。顾问自己不知道东京几点,就写了一张纸条递出来:请帮我查东京的时间。助手照着纸条,去看了一眼钟,再把结果塞回门缝。顾问看到结果,才写出回答,由助手转交给你。换成大模型:门外的助手,就是调用模型的程序,叫客户端,比如聊天软件,或者你自己写的代码;那张单子,叫工具定义;顾问写的纸条,就叫工具调用。

两个词

再认识两个词。工具调用,英文叫 Tool Calling,也有人叫 Function Calling,函数调用。接下来看到的请求和回复,都写成 JSON。JSON,全称是 JavaScript Object Notation,JavaScript 对象表示法:用花括号,把一对一对的“名字”和“值”写在一起。

第一步,客户端发出第一次请求。messages,也就是消息列表,里面是你的问题。tools 里,就是那张单子:工具叫 get_time,用途是“查询某个城市的当前时间”,它需要一个参数,叫 city,填城市名。这张单子可不是白带的:不带工具,这次输入只有 20 个 token;带上工具,变成了 139 个。多出来的 119 个,是工具定义,再加上一段教模型怎么调用工具的说明。

第二步,看模型的回复。content,也就是回答的正文,是空的:它没有直接回答。它只写了一张纸条:调用 get_time,参数 city,填的是东京。其实,模型自己写出来的,就是右边这样一段文字,是运行模型的服务,把它整理成了左边的 JSON。finish_reason 写着 tool_calls,意思是:我先停下,等你去执行。这张纸条还有一个编号,待会儿会用上。注意,到这里,时间还没有查。模型只是提了一个请求。

第三步,轮到客户端。它收到纸条,运行自己的一段普通代码:按城市找到时区,读一下本机的时钟。结果是:2026-10-08 03:53。执行之前,客户端完全可以先把一道关:参数对不对,要不要先问你一声,甚至直接拒绝。很多编程助手,运行命令、改文件之前,会先弹窗问你,就是在这一步。所以,真正动手的是客户端,不是模型。

第四步,客户端发出第二次请求。messages 里,有你的问题,有模型那张纸条,还多了一条新消息:角色是 tool,内容就是查到的时间。它用 tool_call_id 注明,这是哪一张纸条的结果。工具单子,也又带了一遍。还记得那位顾问吗?他其实还有个毛病:记性特别差,上一次聊过什么,转头就忘。所以,客户端每次都得把全部内容从头递进去。这一次,输入从 139 个 token,涨到了 187 个。

第五步,模型这才写出回答:东京现在是03:53。finish_reason 变成了 stop,这一次,才是真的说完了。

五步连起来看

把五步连起来看:第一次请求,模型回一张纸条,客户端执行工具,第二次请求,模型给出回答。一次工具调用,模型被请求了两次;中间执行工具的,是客户端。模型从头到尾,只做了一件事:读文字,写文字。

前面这套写法,叫 OpenAI 兼容格式,很多模型服务都支持。换一家模型呢?我们用 Claude Sonnet 5.5,问同一个问题,配同一个工具。工具的参数说明,在这边叫 input_schema;纸条放在回复的 content 里,类型叫 tool_use;左边的参数,被包成了一串文字,右边直接就是一个对象。停下的原因,也从 finish_reason,换成了 stop_reason。送回结果时,角色写的是 user,类型叫 tool_result,用 tool_use_id 配对。字段名各家不一样,但套路完全相同:给单子、写纸条、客户端执行、送回结果、给出回答。

带上工具,不等于一定会调用

还有一点:带上工具,不等于一定会调用。同样带着这张单子,问它“1 加 1 等于几”,它直接就答了,一张纸条也没写。默认情况下,调不调用,调哪一个,参数填什么,都由模型自己决定。它也可能判断错,所以,客户端拿到纸条,最好先看一眼,再执行。

三句话

总结三句话。第一,模型不执行工具,它只写调用请求。第二,执行在客户端,检查、确认、拒绝,都发生在这一侧。第三,想让模型用上工具的结果,至少要请求它两次,每次的输入,都包括全部历史和工具定义。

今天,我们拆开了一次工具调用。下一期,聊一个 Agent 任务,到底发了多少次请求。