🔑 關鍵洞察
✦ AI·GEN這篇文章記錄了作者在嘗試開發一個「tool-calling 修復 proxy」的過程中,如何透過設計一個 benchmark 測試 22 個地端模型的 tool-calling 能力,最終放棄這個創業點子的經歷。測試結果顯示,新一代模型(如 Qwen3.5)已經原生解決了 tool-calling 的問題,修復層對這些模型反而成為干擾,且模型世代的快速進化使得這類修復工具的市場需求迅速萎縮。文章提供了詳細的測試方法、結果分析以及對各模型的具體觀察,並總結出地端模型選擇的實用建議。最後,作者強調了驗證假設的重要性,認為早期的測試能有效避免浪費時間和資源。
打完跟 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 個測試案例,六個類別,全部都是真實世界會撞到的形狀:
| 類別 | 數量 | 在測什麼 |
|---|---|---|
| simple | 5 | 單工具直球:查天氣、發信、下 SQL(含中文 prompt) |
| complex | 5 | 巢狀 schema:建立行事曆事件,attendees 是物件陣列、有 required 欄位 |
| html | 4 | HTML-in-JSON:就是 xl-ai 那個 applyDocumentOperations——block 欄位塞 HTML 字串,引號跳脫的地獄 |
| parallel | 3 | 一句話要發兩個以上的 call:「比較東京和倫敦的天氣」 |
| nocall | 4 | 不該 call 的時候忍不忍得住:「天氣跟氣候差在哪?」(手上有 get_weather 工具) |
| select | 4 | 四個工具擺一起,挑得對嗎 |
每個模型跑兩個 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 分數排序:
| 模型 | native | shim | 備註 |
|---|---|---|---|
| qwen3.5:4b | 25/25 | 22/25 | 滿分,而且只有 4B |
| qwen3.5:9b | 25/25 | 25/25 | 滿分 |
| granite4.1:8b ✨ | 24/25 | 24/25 | IBM 新世代 |
| ministral-3:8b ✨ | 24/25 | 24/25 | Mistral 邊緣線 |
| granite4:tiny-h | 22/25 | 12/25 | shim 反而砍半 |
| lfm2.5:8b ✨ | 22/25 | 5/25 | 自稱為 tool calling 而生;shim 崩到 5 |
| mistral-nemo | 22/25 | 25/25 | 2024 的老將,意外能打 |
| nemotron-3-nano:4b ✨ | 21/25 | 23/25 | NVIDIA 代理線 |
| qwen3.6:27b ✨ | 21/25 | 23/25 | 輸給自家 4B 小老弟,見下文 |
| gemma4:e4b | 20/25 | 24/25 | |
| gemma4:e2b | 19/25 | 18/25 | |
| gemma4:12b | 18/25 | 20/25 | html 類 0/4,全家死穴 |
| hermes3:8b | 18/25 | 23/25 | |
| qwen3:4b | 16/25 | 17/25 | 跟 qwen3.5:4b 對照:一個世代差 9 題 |
| phi4-mini | 12/25 | 22/25 | |
| llama3.2:3b | 9/25 | 16/25 | |
| functiongemma:270m ✨ | 7/25 | 0/25 | 270M 特化模型,只認原生格式 |
| command-r7b | 4/25 | 11/25 | native 的 4 分全是 nocall「沉默得分」 |
| deepseek-r1:8b | 4/25 | 23/25 | thinking 模型的奇特分裂,見下文 |
| glm4:9b | 4/25 | 13/25 | 同 command-r7b,靠沉默得分 |
| gemma3:12b | 0/25 | 23/25 | API 直接 400:not support tools |
| gemma3:4b | 0/25 | 19/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。判決書三條:
- 問題正在自然消失。tool-calling 修復是一個「模型世代問題」,不是「結構性問題」。新一代開源模型(qwen3.5 為首)已經原生解掉它,而且會越來越好。
- 修復層對新模型是負資產。你的目標客群一升級模型,你的產品就從幫助變成干擾。
- 替代方案是一行命令。
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 殺手題)。歡迎拿去測你手上的模型——如果你發現哪個模型把我的表打臉了,告訴我,我很樂意被打臉,反正已經習慣了。
還沒有留言
✨ 成為第一個留言的人吧