打完跟 xl-ai + 地端 Gemma 的那場硬仗之後,我心裡留下一個結論:地端模型根本不會 tool-calling。Gemma 3 27B 連穩定吐個 JSON 都做不到,xl-ai 那種「期待模型乖乖講 tool-call 格式」的套件一接就炸,最後是靠後端 adapter 手工翻譯才救回來的。

然後我就靈光一閃:既然每個做地端 AI 的人都會撞這面牆,那做一個「tool-calling 修復 proxy」不就是藍海?

構想很簡單:一個 OpenAI-compatible 的中間層,夾在你的 app 跟地端模型之間。App 照常用標準 tools 格式發請求,proxy 負責把 tool 定義翻成模型啃得動的 prompt、把模型吐的爛 JSON 撈回來修好、用 schema 驗證、錯了自動重試——讓「不會 tool-calling 的模型」對外表現得像會。

聽起來很美。每個用 Vercel AI SDK / LangChain 接地端模型的人都在抱怨這件事,而市面上沒有一個獨立的修復層。

但這次我學乖了。動手做之前,先花一天寫個 benchmark 驗證假設。

結果這個 benchmark 親手殺死了我的點子。這篇就是完整的驗屍報告——順便附上你現在(2026 年 6 月)挑地端模型做 tool-calling 的實用對照表。

(會想先驗證,就是因為上次那場仗的教訓:官方套件的美好 demo 背後都假設了一個很強的後端。這次換我自己要當「套件作者」了,得先確認我假設的那個「很爛的後端」真的存在。)


Benchmark 怎麼設計#

25 個測試案例,六個類別,全部都是真實世界會撞到的形狀:

類別數量在測什麼
simple5單工具直球:查天氣、發信、下 SQL(含中文 prompt)
complex5巢狀 schema:建立行事曆事件,attendees 是物件陣列、有 required 欄位
html4HTML-in-JSON:就是 xl-ai 那個 applyDocumentOperations——block 欄位塞 HTML 字串,引號跳脫的地獄
parallel3一句話要發兩個以上的 call:「比較東京和倫敦的天氣」
nocall4不該 call 的時候忍不忍得住:「天氣跟氣候差在哪?」(手上有 get_weather 工具)
select4四個工具擺一起,挑得對嗎

每個模型跑兩個 condition:

  • native:直接走 Ollama 的 OpenAI-compatible API + tools 參數——就是 Vercel AI SDK、LangChain 這些框架今天實際拿到的東西。
  • shim:模擬「修復 proxy」——不給 tools,改用 system prompt 要求模型只吐 JSON,收到後做 fence 剝除、<think> 區塊清理、括號平衡掃描抽 JSON、jsonrepair 修復,再用 ajv 做 schema 驗證,錯了帶著錯誤訊息重試一次。
js
// shim 的核心:抽 JSON(剝 think 區塊、剝 fence、括號平衡掃描)→ 修復 → 驗證 → 重試
function parseShimOutput(text) {
  const raw = extractJson(text)        // 找第一個括號平衡的 {...}
  let obj
  try {
    obj = JSON.parse(raw)
  } catch {
    obj = JSON.parse(jsonrepair(raw))  // 爛 JSON 的急救室
  }
  // ...之後還有 ajv schema 驗證,失敗就帶錯誤訊息 retry 一次
}

評分不是「有 call 就算過」:工具名要對、參數要過 ajv schema 驗證、內容要過語意檢查(信真的寄給 bob@example.com 了嗎)、parallel 要真的發滿兩個、nocall 類發了任何 call 就是死。temperature: 0,全部在一張 RTX 5060 Ti 16GB 上用 Ollama 跑,量化是各模型的預設 Q4。

(先講清楚:25 題、單次跑、Q4 量化,這是 smoke test 不是學術 paper。但你看完下面的數據會同意——0/25 跟 25/25 的差距,不是 noise 能解釋的。)


結果總表#

依 native 分數排序:

模型nativeshim備註
qwen3.5:4b25/2522/25滿分,而且只有 4B
qwen3.5:9b25/2525/25滿分
granite4.1:8b ✨24/2524/25IBM 新世代
ministral-3:8b ✨24/2524/25Mistral 邊緣線
granite4:tiny-h22/2512/25shim 反而砍半
lfm2.5:8b ✨22/255/25自稱為 tool calling 而生;shim 崩到 5
mistral-nemo22/2525/252024 的老將,意外能打
nemotron-3-nano:4b ✨21/2523/25NVIDIA 代理線
qwen3.6:27b ✨21/2523/25輸給自家 4B 小老弟,見下文
gemma4:e4b20/2524/25
gemma4:e2b19/2518/25
gemma4:12b18/2520/25html 類 0/4,全家死穴
hermes3:8b18/2523/25
qwen3:4b16/2517/25跟 qwen3.5:4b 對照:一個世代差 9 題
phi4-mini12/2522/25
llama3.2:3b9/2516/25
functiongemma:270m ✨7/250/25270M 特化模型,只認原生格式
command-r7b4/2511/25native 的 4 分全是 nocall「沉默得分」
deepseek-r1:8b4/2523/25thinking 模型的奇特分裂,見下文
glm4:9b4/2513/25同 command-r7b,靠沉默得分
gemma3:12b0/2523/25API 直接 400:not support tools
gemma3:4b0/2519/25同上

(✨ = 2026 年 5–6 月發布的新模型,寫文當週現拉現測)


發現一:上一篇的仗打得不冤,Gemma 3 是真的 0 分#

先看最下面兩列。Gemma 3 的 native 是 0/25——而且不是答錯,是 Ollama 的 API 直接回 400:

registry.ollama.ai/library/gemma3:12b does not support tools

模型的 chat template 裡根本沒有 tool 這個概念。所以上一篇我跟 xl-ai 打的那場仗,從第一天就是不可能贏的:不是我接得不好,是那個東西在 API 層就不存在。 看到這個 0/25 的當下有種遲來的平反感。

而 shim 把 gemma3:12b 從 0 分拉到 23/25。換句話說——我的修復 proxy 點子,在 2025 年世代的模型上是完全成立的。prompt 策略 + JSON 修復 + 重試,真的能讓一個「API 層不支援 tools」的模型跑出 92% 的成績。

如果這個 benchmark 是 2025 年中跑的,我大概已經開工了。


發現二:世代斷層——窗口在 Qwen3.5 發布那天就關了#

然後看最上面兩列。

qwen3.5:4b,native 25/25,滿分。 4B 的模型。3.4GB。在我這張中產階級顯卡上隨便跑。

不是「大致能用」,是 25 題全對:巢狀 schema 全過、HTML-in-JSON 全過(連 <b>final</b> 這種要保留 tag 的都對)、該平行就平行、該閉嘴就閉嘴、四選一全選對。我盯著結果 JSON 找了半天想抓它一題錯的,沒有。

世代內對照更殘酷:

  • qwen3 → qwen3.5(同是 4B):16/25 → 25/25
  • gemma3 → gemma4(同是 12B):0/25 → 18/25(從「API 不支援」到「大致能用」)

一年之內,native tool-calling 從「地端模型的集體殘疾」變成「新世代的標配能力」。我在 blog 33 裡那句「我們地端的 Gemma 根本不穩定 tool-calling」——對 2025 的模型是事實,對 2026 的模型已經是過時情報。


發現三:修復層碰到新模型,反而扣分#

這是對 proxy 點子的第二刀,也是更深的一刀。

看 qwen3.5:4b:native 25/25,shim 22/25。同一個模型,套上我的「修復層」反而掉了三題。granite4:tiny-h 更慘:22/25 被 shim 砍到 12/25。

原因不難理解:這些新模型在訓練時就深度對齊了官方的 tool-calling 格式(自家的 chat template、自家的特殊 token)。我的 shim 把工具定義翻成「請只輸出 JSON」的土法 prompt,等於強迫一個受過正規訓練的人改用我發明的方言講話——它原生的能力被我的「幫助」干擾了。

這對 proxy 的價值主張是致命的:

  • 對舊模型(gemma3):shim 大勝,0 → 23。但你為什麼不直接 ollama pull qwen3.5 就好?
  • 對新模型(qwen3.5):shim 是負資產。

修復 proxy 唯一的生存空間,是「被鎖死在舊模型上、又需要 tool-calling、又不能換模型」的使用者。這個交集小到撐不起一個專案。


發現四:個案觀察,各有各的死法(和活法)#

deepseek-r1:8b 的人格分裂:native 4/25(它根本不吐 native tool call,reasoning 完就直接用自然語言回答),shim 23/25(要求它吐 JSON 它就乖乖吐,而且品質很高)。Thinking 模型是 shim 條件下的最大受益者——<think> 區塊剝掉之後,裡面的 JSON 反而乾淨。

command-r7b 和 glm4:9b 的「沉默得分」:native 各 4/25,而那 4 分全部來自 nocall 類——也就是說它們從頭到尾一個 tool call 都沒發過,在「不該 call」的題目上靠沉默白拿四分。掛著 tools 標籤但實際上是裝飾。

HTML-in-JSON 是 Gemma 全家的死穴:gemma4 三個尺寸在 html 類通通 0/4,典型錯誤是 schema 驗證抓到 operations[0]type 欄位——結構在 HTML 跳脫的壓力下散架。這正是 xl-ai 那個場景:所以就算你用 gemma4 接 BlockNote,血淚還是會重演。同場 qwen3.5 是 4/4。模型挑錯,框架再怎麼接都是白工。

mistral-nemo 的老兵不死:2024 年中的 12B 老模型,native 22/25。當年 Mistral 把 function calling 當賣點認真做了,兩年後還能跟新世代掰手腕。訓練目標有放 tool-calling 跟沒放,差距比參數量大。


2026 年 6 月的新兵:整個世代都站在點子的對立面#

寫這篇的當週,我把 ollama library 上 2026 年 5–6 月的新模型(塞得進 16GB 卡的)全部拉下來補測。如果說 qwen3.5 滿分是一個點,那新兵們連成的就是一條線:

granite4.1:8b 和 ministral-3:8b 雙雙 24/25。 IBM 和 Mistral 的新世代,native 都只差一題滿分。tool-calling 已經不是 Qwen 的獨門絕活,是新世代的入場標配。

lfm2.5:8b 是整張表上最有戲的一列:Liquid AI 的官方文案就寫「為消費級硬體上快速可靠的 tool calling 而生」,native 22/25 對得起這句話——但 shim 直接崩到 5/25,全表最大的反向落差。一個為原生格式特訓過頭的模型,你硬餵它土法 JSON prompt,它反而連話都不會講了。這比 qwen3.5 掉 3 題更兇狠地證明了同一件事:修復層對新模型不是中性的,是毒藥。

qwen3.6:27b,本次最貴的一題,21/25——輸給自家 4B 小老弟。 17GB 的模型塞 16GB 的卡,14% 的層被擠到 CPU 上,推論速度掉一個數量級,光跑完 50 次推論就花了我快兩小時。然後分數還比 qwen3.5:4b(3.4GB,全進 VRAM,飛快)低四題。給所有在消費卡上做地端 AI 的人一句話:小而新、塞得進 VRAM,贏過大而擠。 參數量的虛榮在 offload 面前一文不值。

functiongemma:270m 是彩蛋:Google 出的 270M function-calling 特化模型,native 7/25(以這個尺寸算能看了),shim 0/25——它整個模型就是圍著原生格式蒸餾出來的,離開那個格式連一題都活不下來。特化模型的極端版本,剛好幫「格式鎖定」這個現象做了對照組。



結論:點子死了,但死得很值#

回到那個修復 proxy。判決書三條:

  1. 問題正在自然消失。tool-calling 修復是一個「模型世代問題」,不是「結構性問題」。新一代開源模型(qwen3.5 為首)已經原生解掉它,而且會越來越好。
  2. 修復層對新模型是負資產。你的目標客群一升級模型,你的產品就從幫助變成干擾。
  3. 替代方案是一行命令ollama pull qwen3.5:4b,3.4GB,免費。沒有任何 proxy 的安裝成本能贏過這個。

所以這個專案在寫下第一行產品 code 之前就被處決了。行刑的是我自己寫的 25 個測試案例,全程一天。

如果你今天就要在地端做 tool-calling,我的實用建議濃縮成四行:

  • 無腦選:qwen3.5(4b 就滿分,VRAM 預算夠就上 9b);granite4.1:8b 和 ministral-3:8b 是合格的第二志願
  • 塞不進 VRAM 的大模型不要硬上:qwen3.6:27b 在 16GB 卡上又慢又輸 4B
  • 要 thinking 模型:deepseek-r1 必須自己包 JSON 輸出層,別碰它的 native tool call
  • 避雷:gemma 全系列做結構化輸出(尤其內容含 HTML/markup 時)、所有 2025 上半年以前的模型

最後想說#

上一篇的結尾我寫:「官方套件的美好 demo 背後通常假設了一個很強的後端」。這篇算是它的鏡像:創業點子的美好藍圖背後,通常假設了一個不會變的世界。 我假設「地端模型不會 tool-calling」是個會持續存在的痛,但模型世代翻新的速度比我寫 proxy 的速度快。

一天的 benchmark,殺死一個會吃掉我一兩個月的專案。聽起來是失敗,其實是我今年做過最划算的交易——驗證的價值不在於證明你是對的,在於趁便宜的時候發現你是錯的。

benchmark 的 code 和全部 22 個模型的原始結果 JSON 都在 GitHub,25 個 case 涵蓋你在真實產品裡會撞到的形狀(包括 xl-ai 那種 HTML-in-JSON 殺手題)。歡迎拿去測你手上的模型——如果你發現哪個模型把我的表打臉了,告訴我,我很樂意被打臉,反正已經習慣了。