對應原題(Common ‧ Agents and Tool Use)
- What is the difference between structured output and function calling? — Asked at: Mistral AI, Apple
- What is MCP (Model Context Protocol) and how does it differ from traditional function calling? — Asked at: Microsoft
- How does an agent decide when to call a tool versus answer from its own knowledge?
🎯 什麼情境該想到我
當你「要讓模型輸出機器能解析的東西、或要接工具」的時候。這三個名詞常被混用,能把它們分清楚就是這題的全部分數。
⚙️ 怎麼用
★ 一、三個概念在不同的層
| 結構化輸出 | Function Calling | MCP | |
|---|---|---|---|
| 是什麼 | 輸出格式的約束 | 模型決定「呼叫誰、帶什麼參數」 | 工具怎麼被提供的協定 |
| 所在層 | 解碼層 | 模型 API 層 | 整合層 |
| 終點 | 資料 | 執行 | 工具的來源 |
| 問「什麼」 | 「請用這個 schema 回答我」 | 「你需要做什麼?」 | 「你有哪些工具可用?」 |
關係:
結構化輸出 ⊂ Function Calling 的實作手段
(函式參數本身就必須符合 schema)
MCP ──提供──▶ 工具定義 ──餵給──▶ Function Calling
★ 一句話區分:
結構化輸出的終點是「一份資料」,function calling 的終點是「一個動作」。
前者你拿到 JSON 就結束了;後者你拿到 JSON 之後還要去執行它,並把結果餵回模型(見 ReAct 與 Agent Loop)。技術上兩者高度重疊——OpenAI 早期的 JSON mode 就是用 function calling 的機制實作的。
但設計意圖不同,用錯會讓架構變怪:用 function calling 做純資料抽取,會多出不必要的迴圈;用結構化輸出做動作分派,會缺少執行與回饋。
二、怎麼保證輸出合法:約束解碼
| 做法 | 保證 |
|---|---|
| Prompt 要求(「請輸出 JSON」) | ❌ 沒有保證。會加開場白、會少括號、長輸出時尤其容易壞 |
| 重試 + 驗證 | ⚠️ 可用但浪費,且長尾永遠存在 |
| ★ 約束解碼(grammar-constrained decoding) | ✅ 語法層保證 |
約束解碼的機制(值得會講):
每一步生成前:
1. 依 JSON Schema 編譯出的狀態機,算出「此刻哪些 token 是合法的」
2. 把所有不合法 token 的 logit 設成 -inf
3. 在剩下的合法 token 上取樣
例如已經輸出 {"age": 時,狀態機只允許數字與空白——模型在物理上無法輸出 "twenty"。
★ 它保證的是「語法合法」,不是「語意正確」。
{"age": 999, "name": "asdf"}完全符合 schema,但完全是胡說。
schema 驗證通過 ≠ 內容正確——這是 structured output 最大的誤解,也是必考的追問。
副作用:
- 約束會改變機率分布,可能把模型推離它原本想說的內容 → 有時反而降低內容品質
- 它蓋過取樣參數的一部分效果(見 取樣與解碼策略)
- 複雜 schema 的編譯與逐步查表有延遲成本
實務建議:schema 要盡量簡單扁平。深層巢狀 + 大量 enum + 遞迴定義會讓模型(和解碼器)都變差。
二點五、三家 API 怎麼開,以及失敗路徑
(補充自 https://masonailab.com/tech/structured-output/;參數與限制逐條對過三家官方文件,查證 2026-10-11。參數名會改版,實作前再看一次文件)
| 怎麼開 | 拒答 | 輸出被截斷 | Schema 限制(重點) | |
|---|---|---|---|---|
| OpenAI(Responses API) | text.format: {type: "json_schema", name, strict: true, schema} | message content 裡出現 type: "refusal" | status === "incomplete" 且 incomplete_details.reason === "max_output_tokens" | 只支援 JSON Schema 子集 |
| Claude | output_config.format: {type: "json_schema", schema};工具參數用 strict: true | stop_reason: "refusal"(仍回 200、照樣計費),輸出可能不符 schema | stop_reason: "max_tokens",輸出可能不完整 | additionalProperties 必須是 false;不支援遞迴、minimum/maximum、minLength/maxLength;違反直接回 400 |
| Gemini(Interactions API) | response_format: {mime_type: "application/json", schema} | — | — | 支援 minimum/maximum、minItems/maxItems;太大或太深的 schema 可能被拒 |
- Claude 舊的
output_format參數已移到output_config.format,舊參數 deprecated,要用得加 beta header,否則回 400。- JSON Schema 的
additionalProperties預設是允許——不明寫false,模型(或 validator)可以合法地多吐欄位。正式資料契約要明確關掉。- Claude 不支援數值範圍約束,所以「金額 ≥ 0」只能放到業務驗證層,這正好說明下面那條:schema 只管形狀。
★ 成功條件不是「JSON.parse 沒丟例外」。拒答與截斷都會讓你拿到一段「不符 schema 的東西」,直接 parse 要嘛炸掉、要嘛拿到半截資料。處理順序固定是:
1. 先看結束原因(OpenAI: status / incomplete_details;Claude: stop_reason)
├─ 截斷 → 不 parse;加大輸出上限重試,或拆小 schema
└─ 拒答 → 不 parse;顯示拒答或轉人工,不要硬塞進原 schema
2. parse(try/catch)
3. schema 驗證(Zod / Pydantic,型別與 schema 同一份定義產生)
4. 業務驗證(ID 存在嗎?金額合理嗎?信心不足就轉人工)
監控時把這三件事分開記:schema 驗證通過率、業務驗證通過率、任務實際正確率。只看第一個會以為一切正常。
★ 三、MCP vs Function Calling(Microsoft 那題)
最常見的錯誤答案是「MCP 是更新版的 function calling」。不是。它們在不同層,不互相取代。
┌──────────────────────────────────────┐
模型層 │ Function Calling │
│ 模型決定:呼叫 search_docs(q="RAG") │ ← 模型的能力
└──────────────────────────────────────┘
▲
工具定義從哪來?
│
┌──────────────────────────────────────┐
整合層 │ MCP │
│ 標準化「工具如何被發現、描述、呼叫」 │ ← 生態的協定
│ 任何 MCP client ←→ 任何 MCP server │
└──────────────────────────────────────┘
| 傳統 function calling | MCP | |
|---|---|---|
| 工具定義 | 寫死在你的應用程式裡 | 由 MCP server 動態提供 |
| 新增工具 | 改程式碼、重新部署 | 接一個 server 就好 |
| 複用 | 每個應用各寫一份 | 一個 server 所有 client 通用 |
| 傳輸 | 無(同一個 process) | stdio / HTTP,有明確的協定 |
| 能提供什麼 | 只有函式 | Tools + Resources + Prompts 三種原語 |
面試答法:「MCP 解的是 M×N 問題——M 個 AI 應用要接 N 個資料源,原本要寫 M×N 個整合,MCP 讓它變成 M+N。
模型端仍然是用 function calling 的方式決定要呼叫什麼,MCP 只是換了工具的來源與描述方式。」
→ 深入:MCP、工具-MCP架構與資料流、工具-MCP三大伺服器原語、工具-MCP安全防護要點
★ 四、何時該呼叫工具、何時自己回答
兩種對稱的失效:
| 失效 | 症狀 | 代價 |
|---|---|---|
| 呼叫不足 | 該查不查,直接用記憶回答 | 幻覺。「你們的退款政策是 14 天」——它在編 |
| 過度呼叫 | 什麼都查一遍 | 慢、貴。「1+1 等於多少」也去搜尋 |
模型的判斷依據(都不太可靠):工具描述、系統提示、以及它對自己知識的信心——而 LLM 的信心校準本來就很差。所以不要只靠模型判斷。
五個控制手段(由弱到強):
| # | 手段 | 說明 |
|---|---|---|
| 1 | 工具描述寫清楚「何時用 / 何時不要用」 | 最便宜。描述是 prompt 的一部分,不是文件 |
| 2 | 系統提示定規則 | ★ 「任何涉及日期、價格、庫存、帳號狀態的問題,一律查詢,不得依賴記憶」 |
| 3 | tool_choice 參數 | auto / required(強制至少呼叫一個)/ none / 指定某個工具 |
| 4 | 前置路由分類器 | 用小模型或規則先分類,再決定給不給工具。便宜、可控、可測 |
| 5 | 直接不給 | 某些對話階段就不提供該工具 |
★ 一條好用的原則:
會變的東西永遠查;不會變的東西永遠不查。
- 永遠查:時間、價格、庫存、使用者資料、系統狀態、公司政策、近期事件
- 永遠不查:算術、格式轉換、文法、翻譯、一般常識推理
這條規則可以直接寫進系統提示,而且可以被評估——建一組「應該查/不該查」的測試集,量它的決策正確率。
⚠️ 注意 / 什麼時候不適用
-
工具描述就是 prompt。寫得爛,模型就選錯工具。這是最高投報率的一行字(見 工具集設計)。
-
約束解碼不是萬靈丹。schema 太複雜時,模型會被逼著填出語法合法但語意荒謬的值。寧可簡單 schema + 事後驗證。
-
schema 驗證只是第一關。通過之後還要驗業務規則(日期合理嗎?ID 存在嗎?金額在範圍內嗎?)。
-
各家 API 的行為不一致:平行工具呼叫、strict mode、
tool_choice的語義、拒絕時的回應格式都不同。換供應商要重測。 -
「模型只提出請求、執行都在你這邊」不一定成立,要看工具在哪裡跑。Anthropic 把工具分成兩種:
- client tools:你自訂的工具,加上
bash、text_editor這類 Anthropic 定義好 schema 的工具。模型回tool_use,你的程式執行,再用tool_result回傳。 - server tools:
web_search、web_fetch、code_execution、MCP connector 等。在 Anthropic 的基礎設施上執行,回應裡是server_tool_use加上結果,你不用處理、也攔不到執行過程。
OpenAI 同樣有內建工具(web search、file search 等)。所以做權限盤點時,要把「供應商代為執行的工具」另列一欄:它們能讀到什麼、會把資料送去哪裡,都不經過你的 dispatch 白名單。OpenAI 官方另外明寫:模型產出的參數不一定是合法 JSON,也可能編造 schema 沒定義的參數,執行前一律要驗證。(Anthropic Tool use overview、OpenAI Function calling guide,查證 2026-09-27;補充自 https://signals.tw/articles/what-is-tool-calling/)
- client tools:你自訂的工具,加上
-
模型會編造工具名與參數。dispatch 端必須白名單驗證,不能相信模型給的名字(見 ReAct 與 Agent Loop)。
-
MCP server 是新的信任邊界。第三方 server 能看到你餵進去的內容,工具描述本身也可能是注入載體(見 工具-MCP安全防護要點)。
-
結構化輸出會降低 CoT 空間。強制 JSON 會壓縮模型的思考餘地——需要推理時,先讓它自由思考,再用第二次呼叫格式化。
🧪 我實際套用的紀錄
- 2026-09-20:從面試題庫展開。待辦:把「會變的永遠查」寫進系統提示,並建一組決策正確率測試集。
🔗 相關
- ReAct 與 Agent Loop —— 呼叫之後怎麼執行與回饋
- 工具集設計 —— 工具數量與 schema 設計
- 取樣與解碼策略 —— 約束解碼與取樣參數的交互
- MCP —— 協定本身的深入
- 工具-讓LLM產出可驗證的產物 —— 決策層:怎麼讓產出可被機器檢查
- 工具-MCP安全防護要點 —— MCP 的信任邊界