1. 模型如何「看見」你的工具
先說在前面,對模型而言,tool 並不是「你用 TypeScript 寫的漂亮函式」,而是如下風格的結構化說明:
- name:技術名稱,例如 "search_gifts";
- description:給人看的自然語言文字,解釋何時以及為什麼要使用此工具;
- inputSchema:包含欄位的 JSON Schema,每個欄位也可以有 description、型別與限制。
非常簡化地說,模型大概會這樣做(GPT 腦中的偽代碼):
1. 讀取使用者請求(可為任何語言)。
2. 讀取工具清單:name + description + 參數說明。
3. 為每個 tool 估計是否適合這個任務。
4. 若需要 tool —— 依據 schema 產生帶參數的 JSON。
5. 否則 —— 以文字回覆。
這裡有兩個重點結論。
首先,工具的 description 並不是給開發者看的註解,而是模型與你後端之間的介面。如果描述含糊、資訊不全,或與使用者語言不一致,模型更容易出錯:挑錯 tool、填錯參數、該用工具時卻直接回文字等。
其次,JSON Schema 欄位裡的 description 同樣重要,不亞於工具本身的描述。模型真的會讀每個屬性的 description,並據此判斷該把「年齡」放在哪個欄位、「預算」放哪裡,以及哪裡該是 id。
GiftGenius 小範例
拿我們的工具 search_gifts 來說。在原始「僅 EN」版本中,它可能長這樣:
// server/tools/searchGifts.ts
export const searchGiftsTool = {
name: "search_gifts",
description: "Search for gift ideas based on user preferences.",
inputSchema: {
type: "object",
properties: {
recipient_age: {
type: "integer",
description: "Age of the recipient in years.",
},
budget: {
type: "number",
description: "Maximum budget in user's currency.",
},
},
required: ["budget"],
},
};
如果使用者寫:「需要一份送給媽媽的禮物,她 60 歲,預算不超過 3000 盧布」,模型應該:
- 判斷 search_gifts 是合適的工具。
- 了解「60 歲」要放在 recipient_age,而「3000 盧布」要放在 budget。
當描述只有英文時,GPT 通常仍能處理,但它需要多做一步「內部翻譯」。在多語環境與較弱模型上,這會影響準確度。
2. 多語 App 中「英語描述」的問題
在模組 9 我們曾略談本地化的「全圖」:UI 小工具、目錄、錯誤、商務文案。現在聚焦當工具描述只有英文,但使用者以俄文或西文互動時會發生什麼——混亂就從這裡開始。
典型流程:
- 使用者:「幫我挑一份給 IT 朋友的禮物,預算 50 歐元以內」。
- 模型查看 tools 清單,發現描述只有 EN。
- 如果請求也是 EN —— 一切順利。
- 如果請求是其他語言,它必須:
- 先理解請求,
- 再把它在腦中對照英文描述,
- 選出工具,
- 還要把參數抽取成 JSON。
在強模型上尚可,但會出現:
- 不調用工具的比例上升——模型「認為」自己能直接回答;
- 參數錯誤機率變高(尤其是幣別、單位、地區限制等);
- 在多工具間的路由邏輯更不穩定(選錯工具)。
一個簡單錯誤:budget 期望「以使用者幣別」表示,但描述完全沒說。模型就假設是預設 USD,於是把應該是 50 歐元的值當成 50 美元發到後端。
這時就輪到描述的本地化上場了。
3. 本地化作法:獨立工具 vs 多語描述
有兩個基本架構作法,兩者都可行。
每種語言各自的工具
這個作法是為不同語言建立多個 tools,各自擁有對應語言的描述。
在 GiftGenius 中可能會像這樣:
export const searchGiftsEn = {
name: "search_gifts_en",
description: "Search for gift ideas based on user preferences.",
// ...
};
export const searchGiftsRu = {
name: "search_gifts_ru",
description: "根據使用者偏好挑選禮物。",
// ...
};
對 ChatGPT App 來說,重要的是可用 tools 清單應取決於 locale。如果 locale = "ru-RU", 你的 MCP 伺服器就只應回傳 search_gifts_ru。如果 locale = "en-US", ——就只回傳 search_gifts_en。
優點是 descriptions 最「乾淨」且單語。你甚至可以把 App 當成多個單語版本,各自擁有獨立 prompts 與描述。當語言數量少且市場差異大時,這很舒服。
缺點是邏輯重複與分析上的複雜度。在後端程式碼中多半仍是同一支處理器,但在 MCP/manifest 層就變成兩個不同工具。每次修改別忘了同步更新兩邊描述。
洞察(資料截至 2025-12-01)
實驗上沒有觀察到明顯的優勢來自於讓 description 使用者語言/locale。工具的被選頻率幾乎相同,與描述語言無關。如果同時存在兩個描述相近但語言不同的工具,反而會讓 ChatGPT 困惑。
此外,你的應用需要通過 Store 的審查。因此建議直接把所有 tool description 與 argument description 都寫成英文。
然而,若未來 ChatGPT 中有成千上萬個應用、對「工具選擇」的競爭加劇,則有可能使用者 locale 的語言描述會取得優勢。拭目以待 Tool Search Optimization 的出現。
單一工具 + 多語描述
第二個作法是保留一個 name(例如 search_gifts),但讓它的 description 與 JSON Schema 欄位說明成為多語版本。
可以有不同寫法:
- 簡短雙語:
description: "Search gifts for a recipient. / 根據收禮者偏好搜尋禮物。", - 以語言區塊標示:
description: "[EN] Search for gifts based on user preferences. [RU] 根據收禮者偏好挑選禮物。", - 以獨立欄位組合為字串(較不方便):
description: `EN: ${enDescription} RU: ${ruDescription}`,
優點:只需一個 tool、單一真實來源(single source of truth),更容易部署帶 MCP Gateway 的架構:不論使用者用什麼語言,你對 ChatGPT 暴露的介面都一致。
缺點:描述會變長。如果語言混用不夠清楚,模型可能有點混淆——特別是英文與在地語句子交錯、缺乏明確標記如 [EN]、[RU] 時。
像 GiftGenius 這樣的教學專案,我們建議採混合方案:以英文為主描述,但加上簡短的在地語補充;而真正的「語意」(用什麼語言、如何稱呼使用者)則透過參數(locale)與 system‑prompt 傳遞。
4. 在地化 JSON Schema:欄位說明
接著更深入到工具的參數本身。
在 JSON Schema 中,每個欄位都可以(也應該)提供 description。模型在產生工具呼叫的 JSON 時會讀取這段文字。
在 GiftGenius 中可這麼做:
export const searchGiftsTool = {
name: "search_gifts",
description:
"Search gifts based on user preferences (RU: 根據收禮者偏好挑選禮物).",
inputSchema: {
type: "object",
properties: {
recipient_age: {
type: "integer",
description:
"Recipient age in years. RU: 收禮者的年齡(整數)。",
},
budget: {
type: "number",
description:
"Maximum budget in user's currency. RU: 以使用者幣別表示的最高預算。",
},
locale: {
type: "string",
description:
"User locale (e.g. 'en-US', 'ru-RU'). RU: 介面與回覆的語言。",
},
},
required: ["budget", "locale"],
},
};
幾點實務觀察:
第一,欄位名稱(recipient_age、budget、locale)通常保留英語。不翻譯名稱,只翻譯 description。如此可保證 JSON 格式不會因語言而改變,你也不用維護兩套不同契約。
第二,在 description 中明確寫出幣別、單位與重要限制很有幫助,能大幅降低「歪掉」的參數。
第三,若你已使用 MCP Gateway,可以約定它會自動把 locale 注入工具參數,讓模型不必自己補上。但即便如此,為 locale 寫清楚描述仍然有用:模型會更理解這個參數是什麼、為何需要。
5. 如何選擇描述語言:真實 App 的策略
關鍵的實務問題:描述以哪個語言為主?什麼時候要完全在地化?
建議與經驗顯示,GPT 模型在英語脈絡中仍然表現最佳,許多開發者因此只保留英文描述。但對多語 App 而言,這是個折衷。
以下幾種策略可參考。
僅 EN 描述
最簡單的做法——全用英文。
優點:單一程式碼基底、單語維護,較容易寫出精確的敘述。當一切皆為英文時,模型通常更「開心」。
缺點:對用其他語言輸入的使用者而言,工具選擇與參數品質可能較差。尤其在「偷懶型」模型或參數眾多的複雜工具中更明顯。
EN + 簡短在地語尾
折衷作法:主要描述用 EN,末尾加上一小段在地語,幫助模型把使用者的詞彙對齊到參數。
範例:
description:
"Search for gifts based on user preferences. RU: 此工具會根據收禮者描述、年齡與預算來挑選禮物。",
對 JSON Schema:
description:
"Age of the recipient in years. RU: 收禮者年齡(以年為單位)。",
優點:模型仍處在「英文世界」,但有在地語提示可對應使用者詞彙。
缺點:描述會變長,不過通常可接受。
依 locale 完整本地化描述
最徹底的做法:工具與欄位描述依你從 ChatGPT 得知的 locale 而變化。對 en-US 回純英文描述、對 ru-RU 回純俄文、對 de-DE 回德文。
這不再是「一份永遠不變的 JSON Schema」,而是 MCP/Gateway 會動態挑選的一組 schemas。
在 MCP 層大概像這樣:
function getSearchGiftsToolDescription(locale: string) {
if (locale.startsWith("ru")) {
return {
name: "search_gifts",
description: "根據收禮者偏好挑選禮物。",
// ru‑schema...
};
}
return {
name: "search_gifts",
description: "Search for gifts based on user preferences.",
// en‑schema...
};
}
優點:模型看到的介面語言與使用者一致,體驗最佳。
缺點:維護與測試更複雜。你需要流程確保所有在地化版本在語意上保持同步,別在英文新增欄位後忘了更新德文那份。
6. 在我們的 GiftGenius 中的實作
進入具體做法。我們在 GiftGenius 採混合方案:維持一個 search_gifts 工具,描述以 EN 為主但帶俄語提示,並加入參數 locale。
假設你有一個使用 MCP SDK 描述工具的 TypeScript MCP 伺服器。
// mcp/tools/searchGifts.ts
import { z } from "zod";
export const searchGiftsInputSchema = z.object({
recipient_age: z
.number()
.int()
.describe(
"Age of the recipient in years. RU: 收禮者的年齡(整數)。"
),
budget: z
.number()
.describe(
"Maximum budget in user's currency. RU: 以使用者幣別表示的最高預算。"
),
locale: z
.string()
.describe(
"User locale (e.g. 'en-US', 'ru-RU'). RU: 介面與回覆的語言。"
),
});
export const searchGiftsTool = {
name: "search_gifts",
description:
"Search for gifts based on user preferences (RU: 根據收禮者偏好挑選禮物).",
inputSchema: searchGiftsInputSchema,
// execute(...) ...
};
重點在:
- locale 是必填。如果小工具知道它(我們可從 _meta["openai/locale"] 得知),要嘛在呼叫 callTool 時自行帶上,要嘛由 MCP Gateway 在它那邊自動注入;
- 描述已包含關鍵在地語詞「年齡」「預算」「介面語言」,模型更容易把使用者的文字對上對應欄位。
在 Apps SDK 端,例如可以寫個函式直接呼叫這個 tool(若開啟 widgetAccessible),把小工具的 locale 傳進去。
// widget/hooks/useSearchGifts.ts
export async function searchGiftsFromWidget(params: {
recipientAge: number;
budget: number;
locale: string;
}) {
const openai = (window as any).openai;
const result = await openai.callTool("search_gifts", {
recipient_age: params.recipientAge,
budget: params.budget,
locale: params.locale,
});
return result;
}
這條鏈路固化了架構:locale 由 ChatGPT 傳入 → 進入 tool → tool 選對目錄與價格格式,最後你在前端漂亮地渲染。
7. 行為實驗:如何衡量本地化的影響
最有趣的是:如何判斷工具與描述的本地化真的改善了模型行為,而不只是你白忙一場?
可以直接在 GiftGenius 的 Dev Mode 做個小「實驗」。
兩個版本:base vs localized
準備兩份 App 設定:
- base —— 工具與 JSON Schema 描述只用 EN;
- localized —— 描述為 EN+RU(或若你願意,也可做完整 ru 版本)。
其餘(目錄、UI、prompts)保持一致,以免混入其他變因。
為了簡化:
- 在 Dev Mode(以及更進一步在 Store)只保留 localized 版本;
- 把 base 在本機另開分支,對一組預先準備的請求做對比。
要量什麼
三個關鍵指標:
第一——工具選擇的正確率。對一組中文(與/或其他語言)的測試請求,觀察模型:
- 在需要工具時是否有調用;
- 是否選到正確的 search_gifts,而不是別的 tool。
第二——參數正確性。檢查呼叫 JSON 是否符合預期:欄位不混淆、預算幣別正確、年齡為整數、locale 未遺失。
第三——怪異或無意義呼叫的數量。例如對「現在幾點?」卻呼叫 search_gifts,或把 recipient_age 填成 3000(本該是預算)。
可以手動測,也可以透過 MCP/Agents 的日誌。反正日誌未來都用得上,愈早建立這類分析習慣愈好。
如何建立手動測試集
可以準備一個小型「golden prompt set」專測本地化:
1.「我需要一份不超過 30 歐元的平價禮物,送給 10 歲女孩,她喜歡畫畫。」
2.「幫我為 35 歲的同事(程式設計師)挑一份禮物,預算 100 美元。」
3.「需要一份送給奶奶的週年紀念禮物,70 歲,預算不超過 5000 盧布。」
然後把它們分別丟進 base 與 localized 兩個版本中觀察:
- 模型選了哪些 tools;
- 填了哪些參數;
- 若未調用工具,文字回覆有何差異。
半專業小撇步:可寫個簡單腳本把這些請求用 ChatGPT API 來回跑。不過在本課程中,Dev Mode 的手動測就足夠了。特別值得納入的請求類型,是語言混雜的訊息與奇怪的 locale 組合;我們會在下一節專談。
若你正在開發嚴肅的商業應用、關係到數百萬美元,那就務必針對你的應用把這些點測清楚。模組 20 專門討論專業的「golden prompt set」——務必熟悉這個主題。
8. 語言混用與奇怪的 locale 組合
沒有什麼比同時混用兩種語言的使用者更能「娛樂」LLM 開發者了。例如:
「需要禮物 for my friend,他喜歡 Star Wars,budget 100€」
我們已知道模型是多語的,通常能處理。但在語言混用與「英文描述」並存時,出錯機率會上升。
幾個常見情境:
第一——使用者用中文輸入,但工具描述是 EN。模型能理解,但偶爾會混淆,尤其在專有詞(分類名稱、罕見的欄位標籤)上。
第二——locale = "ru-RU",但使用者偏偏用英文寫。ChatGPT 給了你用俄語建 UI 的信號,但實際文字是 EN。你可以:
- 仍回傳俄語描述,把 locale 視為第一手真相;
- 或加入訊息語言偵測作為額外信號,依實際語言調整描述。
第三——locale = "en",但使用者偶爾插入中文詞。通常英語描述在這種情況下也表現良好。
實務上選定一套明確政策就夠了。例如:
- 若 locale 以 "ru" 開頭——你就在描述中加入俄語片段;
- 否則——描述就維持純英文。
明確規則的好處是你能針對每條分支專門測試,而不是猜今天為什麼描述突然變成了某種語言。
9. 文件、流程與「正典」語言
描述本地化不是一次性工作,而是個流程。使用者希望功能持續推出,而你希望既有東西不要壞。所以要先跟自己約法三章:
- 哪個語言是「正典」(canonical),其他語言都以它為準;
- 在何處保存在地化後的描述;
- 如何檢查一致性。
通常英語是正典語言。所有新工具與欄位先用 EN 描述、通過審查,之後再本地化到其他語言。程式碼庫中可以這樣做:
- 一個 tools.en.json 檔,完整描述 name/description/欄位;
- tools.ru.json、tools.de.json 等檔作為各語言的「衍生版」;
- 一個小型產生器,基於這些字典組出給 MCP 用的最終 JSON Schema。
簡化版本可以先寫在程式碼字串裡,但要有結構,之後才能輕鬆抽離成獨立字典。
別忘了描述本身也是產品文字。審查要和 UI 文案一樣嚴格:易懂、無歧義、少廢話。特別是在多語版本中,我們不希望在地語尾巴與英文主體相互矛盾。
10. 視覺化:語言如何穿越整個堆疊
為了總結,來看一張簡化的請求流程圖,納入工具描述的本地化。
flowchart TD
U[使用者以 RU 輸入] --> C[ChatGPT UI]
C -->|"_meta.openai/locale = 'ru-RU'"| W[GiftGenius 小工具]
W -->|"locale = 'ru-RU'"| T["工具描述(EN+RU)"]
T --> M[GPT 模型]
M -->|callTool search_gifts| MCP[MCP / Gateway]
MCP -->|"locale = 'ru-RU'"| B[Backend / RU 目錄]
B --> MCP --> M2["GPT 模型(回覆)"]
M2 --> C2[ChatGPT UI + RU 小工具]
在這裡,使用者語言與 locale 會決定:
- 小工具的 UI 用什麼語言呈現;
- 模型看到的工具與欄位描述是什麼語言;
- 後端選用哪些目錄與貨幣;
- 回覆如何格式化(模型與小工具端)。
11. 本地化工具與描述時的常見錯誤
錯誤一:把 description 當成「技術註解」,完全不做本地化。
只有英語使用者時或許還行,但一旦出現其他語言,模型更常不調用工具,或按錯 schema 傳了奇怪參數。你把 UI 都翻好了,App 還是在「英語腦」裡行事。
錯誤二:依語言改動 JSON 欄位名稱。
有時會想把 age → vozrast、budjet 等。這會讓後端變成噩夢:不同 schema、不同格式、日誌分析困難。最好把欄位 name 穩定不變,只在描述上做本地化。
錯誤三:在描述中無序混用語言。
像「以中文描述 Search gifts for user preferences」這類混搭,既不幫助模型也不幫助人。如果要做多語描述,請明確分區:[EN] ... [RU] ...。模型會看到結構,而不是一團混雜。
錯誤四:不把 locale 傳進工具。
即使你做了描述本地化,但沒有把 locale 傳給 tool(或 MCP Gateway 沒有代你傳),後端就不知道該用哪個目錄與格式。最後變成模型想「多語」,伺服器卻只回某一個市場的資料。
錯誤五:用機器翻譯直接產生描述、且不做審查。
看似把描述丟進機器翻譯就能收工。實務上這類翻譯常不準,尤其在術語與參數部分。模型可能因此誤解工具或欄位的意義。寧可有一份精心打磨的 EN 版本與少數精準的在地化版本,也不要二十種「機器譯」。
錯誤六:沒有為不同 locale 做測試/實驗。
如果你沒有至少針對每個 locale 做基本請求測試,有些問題可能要等到第一位真實使用者上門才會暴露。準備小型 golden 集合與 Dev Mode 的手動測,能大幅降低風險。
錯誤七:正典與在地化描述之間不同步。
你在英文 schema 新增了欄位 occasion(「送禮場合」),卻忘了更新俄語版本。於是 RU locale 下模型根本不知道這個欄位,也就不會填。後端想依場合過濾禮物,得到 null,只好回非常寬泛的清單——在 EN 下一切正常,在 RU 下則悄悄「壞掉」。因此任何工具描述變更都該經過簡單但固定的流程:先更新 EN → 再更新各語言 → 快速跑一輪測試。
GO TO FULL VERSION