1. 為什麼需要理解 tool-call
簡化來說,傳統的網頁應用是依循「使用者按下按鈕——我們呼叫函式」的流程。在 ChatGPT Apps 的世界,情況不同:使用者說了某件事,模型思考之後,如果判斷需要,會產生一個結構化的工具呼叫(tool-call)。
也就是說,你不會自行寫:
onClick={() => callSuggestGiftsApi(formData)}
而是改成:
- 描述工具 suggest_gifts(名稱、描述、引數結構)。
- 在 system-prompt 中向模型說明這個工具何時有用。
- 把決策交給模型:由它自行決定何時、如何呼叫。
因此請盡早理解兩件事:
- GPT 看不到你的後端程式碼。它只看到工具的「表頭」:名稱、描述與參數結構。
- 模型能多「聰明」地使用你的 App,幾乎直接取決於你如何撰寫這些描述。好的描述就是你給工具的「提示詞」。
本講正是關於這個位於使用者與你的伺服器之間的「大腦」。
2. tool-call 的心智模型:到底發生了什麼
先看整體流程。以 GiftGenius 的典型情境為例:
- 使用者:「幫我替一位 30 歲的朋友挑選禮物,預算 100 美元,他喜歡電玩。」
- GPT 讀取這則訊息並查看有哪些工具。在我們的 App 裡,例如有 suggest_gifts。
- GPT 決定:「想要好好回答,我需要呼叫這個工具。」
- 它不回傳一般文字,而是產生一個結構:工具名稱 + JSON 參數。
- ChatGPT 客戶端看見「啊,這是 tool-call」,便把它送到你的 MCP/伺服器。
- 你的伺服器執行商業邏輯並回傳結構化輸出。
- GPT 收到結果,閱讀之後,再根據工具的回應形成給使用者的易懂答案,和/或更新小工具(widget)。
就 OpenAI API 而言,這就是同一種 LLM-function-calling 機制:在模型回覆中,會出現帶有工具 name 與 arguments 的物件,且 finish_reason 會標示為 tool_calls。模型不會自己啟動程式碼——它只會提出應該呼叫哪個工具,真正的呼叫由客戶端(ChatGPT/Apps SDK)來做。
大致如下(簡化的時序):
sequenceDiagram
participant U as 使用者
participant G as GPT(模型)
participant C as ChatGPT 用戶端
participant S as 你的 MCP/Backend
U->>G: "幫我挑選給朋友的禮物……"
G->>C: tool-call: { name: "suggest_gifts", args: {...} }
C->>S: HTTP /mcp tools/call (suggest_gifts, args)
S-->>C: 結果(含禮物清單的 JSON)
C-->>G: 工具結果
G-->>U: 回覆 + 已更新的小工具
重點:你不會寫 if (userAskedAboutGifts) callSuggestGifts()。你建立工具及其描述,讓模型來做決策。
3. 模型看到什麼:System Prompt + 工具清單
要理解 GPT 如何決定要做什麼,必須清楚它在當下決策時到底擁有哪些資訊。
簡化來說,模型會看到:
- 你的 App 的 system‑prompt(我們會在模組 5 詳談);
- 對話歷史:使用者訊息、它先前的回覆、過去的 tool-call 結果;
- 可用工具清單(tools),包含工具名稱、描述與參數結構;
- 工具的額外註記(readOnly/destructive 等)。
它看不到:
- 函式的實作;
- SQL 查詢;
- 你的資料表結構;
- 你服務的私有儲存庫內容。
稍後我們會詳細談 MCP。現在只需知道,在 MCP 層級,工具會以描述子宣告:每個都有 name、description 與 inputSchema(JSON Schema)。在握手(handshake)時,ChatGPT 會向 MCP 伺服器請求工具清單,並開始把它們視為可用的「動作」。
GiftGenius 的描述子範例(簡化 JSON):
{
"name": "suggest_gifts",
"description": "根據年齡、興趣與預算挑選禮物點子",
"inputSchema": {
"type": "object",
"properties": {
"age": { "type": "integer" },
"budget": { "type": "number" }
},
"required": ["age", "budget"]
}
}
模型在此只「閱讀」文字與結構:age 是什麼、budget 是什麼、工具整體的用途為何。下一堂課將專講如何妥善描述 inputSchema。現在先理解:模型如何從這些描述長出「來呼叫 suggest_gifts 吧」的決策。
4. 從 API 角度看 tool-call
ChatGPT 呼叫你的 MCP 伺服器上的工具(tools)的方式,與 OpenAI Agent 在你後端呼叫函式相當類似。在 ChatGPT Apps SDK 中雖然包裝得更完整,但基本機制相同。
想像我們在後端對 OpenAI API 發送一般請求,並傳入工具 suggest_gifts,讓模型可以在回覆中選擇呼叫它:
const response = await openai.responses.create({
model: 'gpt-5-mini',
messages: [
{
role: 'user',
content: '需要給 30 歲的朋友準備禮物,預算 100 美元'
}
],
tools: [ // 這裡我們傳入 LLM 可以「呼叫」的函式清單
{
name: 'suggest_gifts',
description: '依據年齡、預算與興趣挑選禮物',
parameters: {
type: 'object',
properties: {
age: { type: 'integer' },
budget: { type: 'number' }
},
required: ['age', 'budget']
}
}
]
});
如果模型決定呼叫工具,你拿到的回覆不會是文字,而是包含類似以下內容的助手訊息:
{
"role": "assistant",
"tool_calls": [
{
"id": "call_1",
"name": "suggest_gifts",
"arguments": "{\"age\":30,\"budget\":100}"
}
],
"content": []
}
透過這種方式,LLM 告訴你的後端它需要呼叫 suggest_gifts(30,100)。
這裡有三件事很重要:
- 工具名稱(name)——模型會實際填入你在最初 tools 描述中提供的那個字串。
- 參數(arguments)——依據 parameters/inputSchema 組成的 JSON 字串。
- 暫時沒有一般的文字回覆——你拿到的是用於呼叫工具的結構。
在 ChatGPT 應用中也是一樣:模型回傳「想呼叫 suggest_gifts,參數如下」,而客戶端(ChatGPT)會對你的 MCP/伺服器發出 HTTP 請求:tools/call,附上工具名稱與參數。
5. 模型如何抉擇:工具或文字
接下來重點來了:GPT 何時會想起你的工具?
機制(簡化)如下:
- 模型看到使用者的新訊息與目前的上下文。
- 它內部有一個「產生下一則助手訊息」的步驟,但它不一定總是產生一般文字;模型可以選擇以下結束方式:
- 一般文字回覆(finish_reason: "stop");
- 一個或多個 tool-call(finish_reason: "tool_calls");
- 有時還會是其他選項(例如「需要更多使用者輸入」)。
- 影響這個選擇的因素包括:
- 使用者請求與你的工具所描述任務的相似度;
- 你的工具描述是否明確告訴模型「在這種情況就使用我」;
- 來自 app system prompt 的資訊(在 Apps SDK 的設定中提供)。
白話地說,模型會把你的工具「套用」到當前請求上。如果描述是「依年齡與興趣挑選禮物」,而使用者要求「分析國家預算」,模型根本不會嘗試呼叫它。如果描述過於模糊——「做很酷的事」——模型就不會知道在哪些請求上該使用。
還有個小細節:即使你提供了工具,模型也不一定要呼叫。GPT 可能認為:「這裡我自己就能很好地回答,不需要 tool‑call。」因此在後續課程我們會積極練習撰寫能讓模型覺得「使用工具最明確、最划算」的描述。
6. 工具名稱:為什麼 tool1 是個壞主意
工具名稱本質上是模型在呼叫時會用到的識別子。看似只是技術欄位,但實務上名稱會強烈影響模型行為。
如果你把工具命名為 tool1,模型完全無法理解它是什麼;對它來說那只是字串。如果命名為 suggest_gifts、search_products 或 fetch_user_orders,名稱本身就提供了強烈的訊號,告訴模型這個工具的用途。
想想你閱讀陌生程式碼時的感受。看到函式 calculateCartTotal,你大概就能預期它會做什麼。模型也需要這樣的「語意錨點」。
對 GiftGenius 而言,合理的工具名稱可以是:
suggest_gifts
search_products
get_product_details
create_order
好的名稱應該:
- 短但有內容;
- 風格一致(snake_case、拉丁字母、動詞_名詞);
- 反映一個明確、單一的動作。
把各種動作混在同一個工具中(如 do_all_gift_stuff)是個壞主意。模型較難理解何時使用它;在接下來的課程中我們會看到,這也會破壞參數結構並讓除錯變得更麻煩。
7. 工具描述:給模型的提示
如果名稱是標題,那 description 就是給 GPT 的迷你文件。開發者可以讀程式碼;模型不會。它依賴描述文字來決定何時呼叫工具、以及該填入哪些參數。
描述要用「使用說明」的寫法:
- 何時使用該工具;
- 它的限制是什麼;
- 它不應該做什麼。
以 suggest_gifts 為例,下面是三種描述。
過於寬泛:
"挑選禮物。"
模型不會知道為誰、為了什麼場合、需要哪些參數。這個工具會與模型對「禮物」的常識「競爭」,而它常常會乾脆直接用文字回答。
過於狹隘:
"只為弟弟的生日挑選禮物。"
這等於把工具幾乎都禁用了。其他所有場景——媽媽、同事、紀念日——都「不適用」,模型就會避免呼叫。
較佳(平衡):
"當你需要根據年齡、關係類型(朋友、伴侶、同事等)、預算與興趣來替某人挑選禮物時,請使用此工具。
不要把它用在與禮物無關的問題(例如政治或天氣)。"
這裡清楚說明了工具做什麼、有哪些參數、以及何時使用,還加入了負面條件——哪些問題不應該使用它。
模型「喜歡」這樣的明確邊界。你越清楚地標示出哪些使用者意圖適合用這個工具,App 的行為就越可預期。
小練習
現在就可以動手:拿你的未來 App(不一定是禮物主題),替其中一個工具想出三種描述:非常寬、非常窄、與平衡版。接著測試 GPT 用不同版本時的行為差異。
8. 參數結構:它如何幫助模型做決策
JSON Schema 的細節我們下節再談,但要理解 tool-call,至少要有個高層直覺。
當模型決定呼叫工具時,它需要:
- 理解該工具接受哪些參數。
- 從使用者文字(或上下文)中抽取對應數值。
- 用這些參數組成 JSON。
為此,工具描述中會提供參數結構(inputSchema),告訴模型:
- 有哪些欄位(age、budget、relationship_type、interests 等);
- 哪些欄位是必填的(required);
- 型別是什麼(integer、number、string、陣列等);
- 有時還會提供允許值(enum)與欄位說明(description)。
最簡單的 TypeScript 參數介面,針對 suggest_gifts 可以像這樣:
interface SuggestGiftsParams {
age: number;
relationship_type: 'friend' | 'partner' | 'colleague';
budget: number;
interests?: string[];
}
在模型層面,這會被轉成 JSON Schema;而模型會根據每個欄位的名稱與描述推斷:
- age 對應像是「30 歲」、「給青少年」之類的語句;
- budget 對應「預算 100 美元」、「最高 50 歐元」;
- relationship_type 對應「朋友」、「同事」;
- interests 對應「喜歡電玩」。
如果你提供沒有描述、且欄位名很抽象的結構(例如 a、b、c),模型在填入參數時就更容易犯錯。我們稍後在本課的在地化與 UX 提示章節會再回到這個議題。關鍵很簡單:結構不只是後端驗證,首先它是給模型的指引,告訴它哪些值要放到哪裡。
我們剛談了結構如何幫模型正確組參數。但除了「做什麼、怎麼叫」之外,還有「現在能不能叫、安不安全」。這就牽涉到工具的權限與中繼資訊。
9. 權限與情境:不是每個工具隨時都可用
除了名稱、描述與參數結構,工具還有另一個重要面向——安全與可存取性。真實 App 的工具在「危險等級」上差異很大。查詢公開商品目錄是一回事,從使用者的卡片扣款又是另一回事。
Apps SDK 與 MCP 允許你在工具描述與註記中反映這些差異——例如把工具標記為 read-only 或 destructive。
概念如下:
- 只讀且只存取公開資料的工具(search_products、get_weather)可以不經過額外確認就呼叫。
- 會改變狀態的工具(create_order、cancel_order、charge_user)要標成「破壞性」。ChatGPT UI 可能會向使用者再度確認(「你確定要下單嗎?」),模型本身也比較不會在沒有明確請求的情況下提出呼叫。
在後續關於 MCP 的模組中,你會看到這些註記(_meta、destructiveHint、readOnlyHint)在實際 JSON 描述子中長什麼樣、如何影響 UX,以及 ChatGPT 如何在呼叫前組出「Are you sure?」對話。現在先理解:
- GPT 不只考量描述文字,也會考量安全相關的中繼資訊。
- 需要驗證的工具,在使用者尚未登入(或 App 尚未取得必要的 token)之前都不會被使用。
這又是影響「要不要呼叫工具」的因素之一:即便語意上很合適,若權限不允許,模型也會改走別的路。
10. 工具在 ChatGPT 中究竟從何而來
從架構角度來看,工具能以兩種主要方式提供給模型。
其一,來自你的 ChatGPT App 設定。註冊 App 時,你會指出綁定了哪些 MCP 伺服器(以及它們的工具),或 App 本身有哪些內建工具。會話啟動時,ChatGPT 取得此設定,於是知道整體有哪些可用工具。
其二,直接來自 MCP。MCP(Model Context Protocol)定義了標準方式,讓客戶端(這裡是 ChatGPT/Apps SDK)得知你的伺服器能做什麼:它會呼叫 tools/list,取得含工具描述的 JSON,並把它們存為能力(capabilities)。详细機制我們會在 MCP 專題模組中解釋,現在先掌握概念即可。
示意:
flowchart LR A[ChatGPT Client] -->|handshake| B[MCP Server] B -->|tools/list| A A -->|傳遞清單| G[GPT Model]
之後,工具清單就成為模型的上下文一部分。如果你在伺服器端修改了工具的結構或描述並重新啟動 App,新描述子會在下一次 handshake 時被 ChatGPT 取回,模型也就會用新規則來做出呼叫決策。
還有個重要的實務提醒:當你只修改後端(工具實作),模型是不知道的;但當你改了 name/description/schema,你其實在改 App 的「大腦」。有時候調整 description 的一句話,會比寫 200 行啟發式程式碼更有幫助。
11. 套用到 GiftGenius:做一個模型會想呼叫的工具
把以上內容連回我們的學習專案 GiftGenius。假設我們已有 MCP 伺服器或後端層,用來註冊工具。讓我們用 server.registerTool(...) 註冊 suggest_gifts。
以下是 TypeScript 的粗略草稿(暫不含實作邏輯):
// pseudo-mcp-server/tools/suggestGifts.ts
server.registerTool(
'suggest_gifts', // 工具名稱
{
title: '禮物挑選',
description:
'當你需要根據年齡、關係類型與預算來挑選禮物點子時,請使用此工具。' +
'對於與禮物無關的問題,請不要呼叫它。',
inputSchema: { // 工具參數描述
type: 'object',
properties: {
age: { type: 'integer', description: '收禮者的年齡(歲)' },
relationship_type: {
type: 'string',
description: '關係類型:friend, partner, colleague'
},
budget: {
type: 'number',
description: '以使用者貨幣計價的禮物最高預算'
}
},
required: ['age', 'budget']
}
},
async ({ age, relationship_type, budget }) => { // 函式/工具的程式碼
// 真實邏輯稍後補上
return { suggestions: [] };
}
);
注意我們在邏輯還是「空殼」時就已經考量了以下細節:
- 名稱:suggest_gifts,而不是 tool1。
- 描述:明白說清楚何時該呼叫、何時不該呼叫。
- 欄位說明:幫助模型把使用者文字正確對映到參數。
因此,當使用者寫下「幫我為同事挑選 50 美元的禮物」時,模型會看到:
- 有個名為 suggest_gifts 的工具,描述與挑選禮物相關;
- 它有 age、relationship_type、budget 這些欄位;
- budget 是「禮物的最高預算」、relationship_type 是「關係類型:friend、partner、colleague」。
就算使用者表達不精確(「五十塊以內」、「專案搭檔」),模型也會有足夠的上下文,嘗試把 JSON 參數組得合理。
等我們的工具在後端與 MCP 模組中真正運作起來,你就會很熟悉整個流程:GPT 會以可預期的方式呼叫它,只因為我們設計了好的介面與描述。
12. 給你的小練習
為了不要只停留在理論,建議你在課後做個小實驗。
先拿一個 GiftGenius 的情境,或自己想一個新 App。用紙筆或編輯器列出一個你明確想交給模型的功能——像是 search_products、find_hotels、calculate_shipping。
然後替同一個工具設計三組「名稱 + 描述」:
- 非常抽象的名稱與描述。
- 過度具體(幾乎是特殊情境)。
- 平衡良好的名稱 + 描述,清楚寫明何時要呼叫、以及它不應該做什麼。
接著(選做)你可以用一般的 OpenAI SDK 實作一個簡單測試,觀察模型的行為如何改變:是否會呼叫工具、它怎麼填參數。相關研究也會提供像 suggest_gifts 這樣的練習素材。
13. 設計 tool-call 與描述時的常見錯誤
錯誤 1:將工具命名為 tool1、handler、doStuff。
這種命名對模型完全沒有幫助。GPT 不會靠檔名來猜「開發者的意圖」;它需要語意清楚的名稱。若你只給一堆 tool1、tool2、tool3 而沒有描述,工具幾乎不會被呼叫:模型根本不懂每個工具做什麼,不是忽略就是隨機挑一個。
錯誤 2:把 description 當成人類閱讀的註解。
不少人只寫形式化的描述,例如「用來挑選禮物的函式」,以為細節反正寫在程式裡。但模型看不到程式碼,它只看到描述文字與參數結構。含糊的描述會成為幻覺的來源:該呼叫工具時 GPT 可能自己回答;或在奇怪的情境下呼叫工具。
錯誤 3:描述太寬或太窄。
寫「做很酷的事」時,模型不知道使用邊界;寫「只處理弟弟 18 歲生日禮物」時,你幾乎把工具禁用了。最佳做法是:清楚界定任務範圍(依多個參數挑禮物)、列出關鍵參數(年齡、關係、預算、興趣),並註明哪些類型的問題不應該使用此工具。
錯誤 4:忽視參數結構也是「提示」的一部分。
有些開發者只把 JSON Schema 當成伺服器端的驗證機制。事實上,模型會積極分析欄位名稱、型別與描述,來判斷該從使用者文字取哪些資料。如果你把欄位命名為 x、不寫描述、還把它設為可選,GPT 不是亂填就是不填。清楚的結構、明確的命名與簡短的說明,能大幅降低無效的 tool-call。
錯誤 5:以為模型「一定會」呼叫工具。
有的開發者會困惑:「為什麼 GPT 沒呼叫我的工具,明明它存在?」答案幾乎總是:從描述或 system‑prompt 看不出在這種問題下應該使用,或者該問題落在模型認為「直接回答更簡單」的區域。
錯誤 6:把多種不同動作混在一個工具裡。
有時候會想做個萬用的 manage_orders,同時查詢、建立與取消訂單。對人類尚可解釋,但對模型而言就是邊界模糊的工具。GPT 較難理解何時要用,而且參數更難填——裡面會變成一堆可選欄位。把動作拆成幾個聚焦的工具(get_order、create_order、cancel_order)並提供清楚描述與結構,會更好。
錯誤 7:在工具設計中忽略權限與安全性。
如果你描述的是可能造成破壞性行為的工具(扣款、刪資料),卻沒有標示為 destructive,也沒有在描述中限制使用範圍,你就製造了風險。ChatGPT UI 不會額外詢問確認,模型也可能在「邊界情境」下嘗試呼叫。正確的註記與謹慎的描述(例如「僅在獲得使用者明確同意後使用」)能在 tool‑call 層面就降低風險。
GO TO FULL VERSION