<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python on Renxin's Blog</title><link>https://zh.renxinblog.cn/tags/python/</link><description>Recent content in Python on Renxin's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 17 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zh.renxinblog.cn/tags/python/index.xml" rel="self" type="application/rss+xml"/><item><title>第二话｜Function Calling：谁把“调用工具”做成了一套协议？</title><link>https://zh.renxinblog.cn/post/c02-function-calling/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/c02-function-calling/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/c02-function-calling-cover.png" alt="Featured image of post 第二话｜Function Calling：谁把“调用工具”做成了一套协议？" /&gt;&lt;p&gt;上一话我们搞懂了两件事：模型只会补全文字；想让它干活，得让它把“想干什么”说成某种格式，再由外面的代码去执行。&lt;/p&gt;
&lt;p&gt;但上一话那个办法很脆——用提示词逼模型输出 JSON，模型经常给你残缺的 JSON、在前后多写几句废话、或者干脆忘了格式。于是就有了 Function Calling：它把“让模型提出一次工具调用”这件事，从碰运气的提示词技巧，做成了一套规规矩矩的协议。&lt;/p&gt;
&lt;p&gt;这一话就讲这套协议：它长什么样，谁先做出来的，以及为什么它能让模型“说清楚要干什么”。&lt;/p&gt;
&lt;h2 id="一协议要解决的就是上一话那个脆"&gt;一、协议要解决的，就是上一话那个“脆”
&lt;/h2&gt;&lt;p&gt;先看上一话的提示词 hack 是怎么翻车的。你在 system prompt 里写“想查天气就输出 &lt;code&gt;{&amp;quot;tool&amp;quot;:&amp;quot;get_weather&amp;quot;,&amp;quot;city&amp;quot;:&amp;quot;...&amp;quot;}&lt;/code&gt;”，然后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型输出的不是纯 JSON，前后还夹着“好的，我帮你查一下”这种话；&lt;/li&gt;
&lt;li&gt;JSON 本身残缺，少个引号、少个逗号，&lt;code&gt;json.loads&lt;/code&gt; 直接崩；&lt;/li&gt;
&lt;li&gt;换了个问法，模型就把格式忘得一干二净。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次翻车，你都得靠更长的提示词、更严的正则去兜，兜到最后还是不可靠。&lt;/p&gt;
&lt;p&gt;协议的意义就在这里：&lt;strong&gt;别再用文字去“暗示”模型输出格式，而是给一个结构化的位置，让模型把调用填进去。&lt;/strong&gt; 这个位置是 API 里明确规定好的，模型要么填对，要么 API 直接拒绝，没有“碰运气”的中间地带。&lt;/p&gt;
&lt;h2 id="二协议长什么样tools-进tool_calls-出"&gt;二、协议长什么样：&lt;code&gt;tools&lt;/code&gt; 进，&lt;code&gt;tool_calls&lt;/code&gt; 出
&lt;/h2&gt;&lt;p&gt;整个协议，一进一出两件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求里传 &lt;code&gt;tools&lt;/code&gt;&lt;/strong&gt;：你告诉模型“现在有哪些工具可以用”。这是一份菜单，写清楚每个工具叫什么、干什么、参数长什么样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应里收 &lt;code&gt;tool_calls&lt;/code&gt;&lt;/strong&gt;：模型告诉你“我想调哪个工具、带什么参数”。这是一张申请单。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是一份声明了本地天气工具的菜单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TOOLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;查询指定城市的天气演示数据&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;parameters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;城市名，如北京&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;required&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;additionalProperties&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，&lt;code&gt;tools&lt;/code&gt; 不是把 Python 函数“上传”给模型。它只是一份描述：函数叫 &lt;code&gt;get_weather&lt;/code&gt;、查天气的、需要一个 &lt;code&gt;city&lt;/code&gt; 字符串参数。模型拿到的是这些文字，不是那个函数本身。&lt;a class="link" href="https://api-docs.deepseek.com/api/create-chat-completion" target="_blank" rel="noopener"
 &gt;Chat Completions API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;把菜单连同问题一起发过去：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;请查询北京现在的天气。&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tool_choice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;required&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;响应里我们关心的不是自然语言，而是这张申请单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;call_00_...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{\&amp;#34;city\&amp;#34;: \&amp;#34;北京\&amp;#34;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;拆开看每个字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;：这条调用的唯一标识，回传结果时要靠它关联；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;：固定是 &lt;code&gt;function&lt;/code&gt;，标记这是一次函数调用；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function.name&lt;/code&gt;：想调哪个工具；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function.arguments&lt;/code&gt;：一个 &lt;strong&gt;JSON 字符串&lt;/strong&gt;——注意它还是字符串，不是对象，代码里得再 &lt;code&gt;json.loads&lt;/code&gt; 一层才能用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它表达的就是一句话：“请调用 &lt;code&gt;get_weather&lt;/code&gt;，参数是北京。”到这里，模型既没有运行 Python，也没有拿到天气数据。它只是把“想干什么”用协议规定的格式写清楚了。&lt;/p&gt;
&lt;h2 id="三这套协议是谁先做出来的"&gt;三、这套协议，是谁先做出来的
&lt;/h2&gt;&lt;p&gt;它不是我编的，也不是模型厂商某天灵机一动。时间线很清晰：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2022 年，&lt;strong&gt;ReAct&lt;/strong&gt;（Yao et al., arXiv 2210.03629）提出“推理 + 行动”交替，用提示词让模型按 &lt;code&gt;Thought / Action&lt;/code&gt; 的格式输出，再让代码去执行；&lt;/li&gt;
&lt;li&gt;2023 年 2 月，&lt;strong&gt;Toolformer&lt;/strong&gt;（Meta AI, arXiv 2302.04761）证明“模型自己决定何时调 API”这个能力是能被训练出来的；&lt;/li&gt;
&lt;li&gt;2023 年 5 月，&lt;strong&gt;Gorilla&lt;/strong&gt;（UC Berkeley, arXiv 2305.15334）开始微调模型学写 API 调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几篇走的都是“提示词 + 解析”的路线——也就是上一话讲的那个脆办法。&lt;/p&gt;
&lt;p&gt;真正的转折在 2023 年 6 月 13 日：&lt;strong&gt;OpenAI 首次发布原生 Function Calling&lt;/strong&gt;，随 &lt;code&gt;gpt-4-0613&lt;/code&gt; 和 &lt;code&gt;gpt-3.5-turbo-0613&lt;/code&gt; 两个模型快照一起推出。从这一天起，“让模型提工具调用”不再靠提示词暗示，而是变成了 API 里的标准字段——就是我们第二节看到的 &lt;code&gt;tools&lt;/code&gt; 进、&lt;code&gt;tool_calls&lt;/code&gt; 出。&lt;/p&gt;
&lt;h2 id="四别看就一个-tool_calls背后做了很多工作"&gt;四、别看就一个 &lt;code&gt;tool_calls&lt;/code&gt;，背后做了很多工作
&lt;/h2&gt;&lt;p&gt;有个问题自然冒出来：同样是“让模型输出调用”，为什么提示词 hack 天天翻车，而协议一出来，模型就能稳定地输出合法参数、不再给你残缺 JSON？&lt;/p&gt;
&lt;p&gt;不是因为提示词写得好了。是厂商在背后做了工作——&lt;strong&gt;模型是被训练成“会正确提出调用”的&lt;/strong&gt;，这个能力不是运气，是训练出来的。&lt;/p&gt;
&lt;p&gt;这里只说结论，不展开原理（那是下一话的事）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;微调&lt;/strong&gt;：训练阶段喂了大量“工具调用对话”样本，让模型学会判断要不要调、选哪个工具、生成合法参数；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;约束解码&lt;/strong&gt;：生成的时候，在输出层卡住模型，让它只能吐出符合 schema 的内容，从根上杜绝“残缺 JSON”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两件事把“正确提出调用”从提示词的玄学，变成了模型本身的能力。至于模型内部到底是怎么学会的、这两件事的具体机制——那是第三话要专门讲的问题，这里先按下不表。&lt;/p&gt;
&lt;h2 id="五协议只负责提出不负责执行"&gt;五、协议只负责“提出”，不负责“执行”
&lt;/h2&gt;&lt;p&gt;回到那张申请单。&lt;code&gt;tool_calls&lt;/code&gt; 拿回来，协议的部分就结束了。接下来是你的代码接手：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;不允许执行未知工具：&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get_weather 参数必须且只能包含 city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;city&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;city 必须是非空字符串&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型输出在这里被当成&lt;strong&gt;外部输入&lt;/strong&gt;处理：先校验工具名，再校验参数，然后才真正执行。&lt;code&gt;25℃&lt;/code&gt; 来自 &lt;code&gt;get_weather()&lt;/code&gt; 的返回值，不是模型编的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;北京&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;多云，25℃，东北风3级&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;上海&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;小雨，22℃，东南风2级&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;暂无&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;的天气演示数据&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;协议让模型“说清楚要干什么”，执行永远在模型外面——这是上一话那条结论的延续，在这里变成了可运行的代码。&lt;/p&gt;
&lt;h2 id="六把结果交回去也是协议的一部分"&gt;六、把结果交回去，也是协议的一部分
&lt;/h2&gt;&lt;p&gt;函数执行完，Python 手里有了天气文本，但用户还没看到一句话。模型上一次只停在“我想调什么工具”，所以要把两样东西放回消息历史：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 模型提出的调用请求&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 工具执行结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;tool_call_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 关联到上面那条 tool_calls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;role=tool&lt;/code&gt; 这一步&lt;strong&gt;不是可选的，是协议强制要求的&lt;/strong&gt;。这里有个真实的坑：有人把 &lt;code&gt;role=tool&lt;/code&gt; 写成了 &lt;code&gt;role=user&lt;/code&gt;，API 直接报 HTTP 400——&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;An assistant message with &amp;#39;tool_calls&amp;#39; must be followed by
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tool messages responding to each &amp;#39;tool_call_id&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;意思很直白：你给模型看了一张 &lt;code&gt;tool_calls&lt;/code&gt; 申请单，就必须逐条配上一份对应的 &lt;code&gt;tool&lt;/code&gt; 结果，用 &lt;code&gt;tool_call_id&lt;/code&gt; 对上号。少一条、错一条，协议层面就拒绝，根本轮不到模型来猜。这个报错本身，就是“协议不是约定俗成、而是硬约束”的最好证据。&lt;/p&gt;
&lt;p&gt;补上结果后再问一次模型，它才能把“多云，25℃”组织成一句人话。事实来自函数，句子由模型生成，两边分得清清楚楚。&lt;/p&gt;
&lt;h2 id="七回到最开始的问题到底谁调用了工具"&gt;七、回到最开始的问题：到底谁调用了工具？
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;角色&lt;/th&gt;
					&lt;th&gt;输入&lt;/th&gt;
					&lt;th&gt;输出&lt;/th&gt;
					&lt;th&gt;不负责什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;模型&lt;/td&gt;
					&lt;td&gt;用户问题与 &lt;code&gt;tools&lt;/code&gt; 菜单&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;tool_calls&lt;/code&gt; 申请单&lt;/td&gt;
					&lt;td&gt;不执行本地函数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;你的代码&lt;/td&gt;
					&lt;td&gt;工具名、参数与本地能力&lt;/td&gt;
					&lt;td&gt;校验后的工具结果&lt;/td&gt;
					&lt;td&gt;不应把模型请求原样放行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模型&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;role=tool&lt;/code&gt; 结果&lt;/td&gt;
					&lt;td&gt;面向用户的回答&lt;/td&gt;
					&lt;td&gt;不产生工具返回的外部事实&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Function Calling 没有把函数“装进模型”。它做的，是把上一话那个脆弱的提示词 hack，变成了一套 &lt;code&gt;tools&lt;/code&gt; 进、&lt;code&gt;tool_calls&lt;/code&gt; 出的标准协议——模型提请求，你的代码执行，模型再说话。&lt;/p&gt;
&lt;p&gt;跑通一次，你会看到这三步按顺序发生：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;模型请求：get_weather({&amp;#34;city&amp;#34;:&amp;#34;北京&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;程序执行：多云，25℃，东北风3级
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;模型回答：北京现在多云，气温 25℃，东北风 3 级。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配套 Python 与 Java 实现在 &lt;a class="link" href="https://github.com/renxin2024/GYA/tree/main/c02-function-calling" target="_blank" rel="noopener"
 &gt;GYA 仓库&lt;/a&gt;与 &lt;a class="link" href="https://github.com/renxin2024/GYA-Java/tree/main/c02-function-calling" target="_blank" rel="noopener"
 &gt;GYA-Java 仓库&lt;/a&gt;的 &lt;code&gt;c02-function-calling&lt;/code&gt; 目录，按 README 的命令跑，不复制这篇文章里零散的代码块。&lt;/p&gt;
&lt;p&gt;协议跑通了，但一个更根本的问题还悬着：&lt;strong&gt;模型凭什么知道，该从菜单里挑 &lt;code&gt;get_weather&lt;/code&gt;、该填北京？&lt;/strong&gt; 这已经不是协议能回答的了——得回到模型训练本身。下一话。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DeepSeek, &lt;a class="link" href="https://api-docs.deepseek.com/guides/tool_calls/" target="_blank" rel="noopener"
 &gt;Tool Calls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DeepSeek, &lt;a class="link" href="https://api-docs.deepseek.com/api/create-chat-completion" target="_blank" rel="noopener"
 &gt;Chat Completions API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Yao et al., &lt;a class="link" href="https://arxiv.org/abs/2210.03629" target="_blank" rel="noopener"
 &gt;ReAct: Synergizing Reasoning and Acting in Language Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Schick et al., &lt;a class="link" href="https://arxiv.org/abs/2302.04761" target="_blank" rel="noopener"
 &gt;Toolformer: Language Models Can Teach Themselves to Use Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Patil et al., &lt;a class="link" href="https://arxiv.org/abs/2305.15334" target="_blank" rel="noopener"
 &gt;Gorilla: Large Language Model Connected with Massive APIs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>