1. 什麼是 Multi‑App 情境,以及為什麼你需要它們
到目前為止,我們把 GiftGenius 視為某個對話中唯一的外部應用:使用者在清單裡選你的 App,ChatGPT 載入你的 tools,而你就是這段故事的「主角」。但在真實的 Store 裡情況不同:使用者可以同時連接多個 Apps,ChatGPT 會決定針對特定請求要呼叫哪一個應用。
例如,在同一個對話中可能同時存在:
- 企業行事曆 App,知道同事們的生日;
- GiftGenius,負責提供禮物靈感;
- 公司的 commerce‑App,能處理下單與付款。
使用者會說:「提醒我同事的生日,並直接提議送什麼、怎麼買。」模型可能會依序呼叫三個不同的 App:一個查行事曆、第二個找禮物靈感、第三個負責結帳。
重點是:使用者沒有一個按鈕可以「請呼叫 App 2,以下是它的 HTTP endpoint」。他是用自然語言溝通,ChatGPT 則扮演路由器——它會閱讀所有可用應用的 descriptions 與中繼資料,來決定要在什麼時候叫誰上場。
因此有三個關鍵要點:
- 你在爭奪對話上下文。你的 App 必須在眾多競品中脫穎而出,依據的是名稱、描述與行為。
- 中繼資料就是你的「LLM 的 SEO」——正是它們決定模型是否會在關鍵時刻注意到 GiftGenius,或是把它忽略。
- 要考慮互通性:你的回覆不只要對人類有用,還要讓其他會讀取相同上下文的 App 也能理解與利用。
本質上,我們把一個孤立的 ChatGPT App 轉變成更大系統中的一個組件。
2. 模型如何選擇 App:路由的心智模型
在 Multi‑App 情境下,路由大致這樣運作(大幅簡化,但對開發很實用):
- ChatGPT 持有可用 App 與其 tools 的清單與中繼資料(名稱、描述、參數的 JSON Schema、註解以及 _meta)。
- 使用者發出訊息。
- 模型建立對意圖(intent)的內部表示,並對工具與應用的 descriptions 進行語意搜尋,以理解哪些工具適用。
- 若條件吻合——就呼叫 tool,或建議開啟某個 App。
這裡有個重要細節:descriptions 必須足夠區辨。像「搜尋商品」幾乎無法和「搜尋禮物」或「搜尋書籍」區分;但「根據 GiftGenius 的合作夥伴資料庫搜尋禮物點子」就能明確縮小領域,提升你的工具在「送禮」相關請求被選中的機率。
第二個重點——避免名稱衝突。名為 get_data 的工具在有數十個 App 的世界毫無資訊量;相反地,giftgenius_get_gift_catalog 就清楚得多,尤其是搭配精準的 description。
最後,模型也仰賴上下文:如果對話裡已提過「禮物」、「生日」,甚至 GiftGenius 這個名字,這些都會在路由器眼裡強化你的 App 的相關性。
3. 中繼資料與 descriptions 作為 LLM‑SEO
不要把中繼資料視為「JSON 裡的必填形式」,更有幫助的思維是把它當作產品文案工作。官方建議也明講:treat metadata like product copy,並且設計「one job per tool」。
概略可分成幾個描述層級:
| 層級 | 對象 | 描述內容 |
|---|---|---|
| Manifest description | 人類 + 模型 | 整個 App 的任務:為什麼要把它接到對話中 |
| Tool description | 模型(路由) | 何時應使用該 tool,以及用於哪些任務 |
| Parameter descriptions | 模型(slot fill) | 如何填入參數、可接受的值 |
|
模型(UI) | 小工具中會顯示什麼、以及是否需要由模型以文字回覆再重述 |
widgetDescription 在小工具世界中特別重要:模型不會「看見」你的 React 程式碼,它只知道你會提供哪些 props、以及用途為何。完善的描述能避免模型「替你瞎猜」,反而會配合已顯示的 UI 來調整文字回覆。
Apps SDK 的文件強調:ChatGPT 會依據中繼資料來決定何時、如何呼叫你的連接器(App)。仔細撰寫 descriptions 與參數文件能提升 recall——也就是模型在多少情境下會想到你的 App——並減少誤觸發。
迷你範例:GiftGenius 的舊版與新版 description
假設我們以前是這樣寫的:
export const appDescription = `
GiftGenius — 尋找與購買禮物的助手。
`;
對人類而言看起來不差,但在 Multi‑App 的世界,要更著重於何時使用 App,以及它不做什麼:
export const appDescription = `
GiftGenius — 禮物點子助手。
當使用者請你幫特定的人或場合想禮物、並需要控制在預算內時,請使用此應用。
不要把它用於一般的線上購物或個人理財規劃。
`;
現在模型更容易把 GiftGenius 與一般的 e‑commerce App 或理財顧問區分開來。
4. _meta["openai/widgetDescription"]:向模型解釋我們的 UI
在上面的表格中我們特別提到 _meta["openai/widgetDescription"]。現在聚焦在這一層:它幫助模型「想像」你的小工具,理解哪些回覆已由 UI 呈現、哪些需要以文字說明。
假設我們的主要工具 suggest_gifts 會回傳禮物清單,而小工具會把它們渲染成橫向的卡片輪播。我們在工具的描述中已說明何時使用它,而在 widgetDescription 要解釋結果會長什麼樣。
以下是(依建議簡化後的)工具描述片段:
const suggestGiftsTool = {
name: "suggest_gifts",
description: "Use this to generate gift ideas within user's budget.",
inputSchema: { /* ... */ },
_meta: {
"openai/widgetDescription":
"顯示含價格與「購買」按鈕的橫向禮物卡片列表。不要在文字回覆中重複列出禮物名稱。"
}
};
這裡我們同時達成幾個目的:
- 模型知道 UI 已顯示名稱與價格——因此文字回覆可以聚焦在解釋與建議,而非重複清單。
- 其他 Apps(透過模型)也能理解 toolOutput 不是一段純文字,而是可被「接續使用」的結構化清單。
坦白說,寫這種描述比寫程式無聊,但它們能為你省下許多除錯模型怪異行為的時間。
5. 工具註解:readOnlyHint、destructiveHint、openWorldHint
在 Multi‑App 世界,不只要知道「何時呼叫」,還要知道呼叫是否安全。為此,Apps SDK 在工具描述中提供了一組註解。
概念是:註解是給模型的柔性提示,描述操作的性質。它們不能取代伺服器端的授權,但會顯著影響 ChatGPT 在多步鏈中的行為。
概念性速覽:
| 註解 | 意義 | 模型的典型行為 |
|---|---|---|
|
不改變資料 | 可頻繁呼叫,通常不需額外確認 |
|
會改變狀態(購買、刪除) | 呼叫前應向使用者確認 |
|
觸及「外部世界」(搜尋、網路) | 模型會更保守地處理結果的數量與品質 |
這些註解(readOnlyHint、destructiveHint、openWorldHint)是工具標準描述的一部分,不僅限於 ChatGPT 使用。欄位 _meta["openai/isConsequential"] 則是更狹義、ChatGPT 特有的信號,幫助模型進一步區分「安全」與「具後果」的呼叫。
以 GiftGenius 的兩個工具為例:
- suggest_gifts —— 讀取型目錄,安全。
- create_checkout_session —— 建立結帳,明確的副作用操作。
工具 suggest_gifts 的描述範例
const suggestGiftsTool = {
name: "suggest_gifts",
description:
"Use this when the user asks for gift ideas for a person or occasion.",
inputSchema: { /* ... */ },
annotations: {
readOnlyHint: true
},
_meta: {
"openai/widgetDescription": "含價格與連結的禮物輪播。",
"openai/isConsequential": false
}
};
這樣的工具,模型可以連續呼叫多次,甚至會「預先」呼叫以準備選項,而不必每一步都向使用者請示。
工具 create_checkout_session 的描述範例
const createCheckoutTool = {
name: "create_checkout_session",
description:
"Finalize purchase of selected gifts via Instant Checkout.",
inputSchema: { /* ... */ },
annotations: {
destructiveHint: true
},
_meta: {
"openai/isConsequential": true
}
};
在此我們明確發出訊號:這是 write 操作,具有後果(扣款、建立訂單),模型應在呼叫前向使用者確認,特別是在包含多個 App 的長鏈路中。
不要高估魔法:即使有 destructiveHint,你仍須在伺服器端重新檢查輸入、權杖與權限,這在我們的安全與授權單元裡已談過。就 Multi‑App 編排而言,註解能幫助模型避免不必要地「亂開火」。
6. 孤立 App vs 生態系:釐清 GiftGenius 的邊界
當 GiftGenius 是對話中的唯一 App 時,你可以放寬範圍:挑禮物、包裝建議、節日提醒,甚至幫忙寫祝福詞。模型反正只會呼叫你的工具。
在 Multi‑App 情境下,這種「萬事通」做法開始有害:
- 路由器更難分辨何時你是最佳人選、何時該用別的 App;
- 你會和行事曆、通用任務管理、理財規劃等產生領域重疊;
- 同時使用多個應用時,模型可能選錯執行者、在工具之間混淆。
更好的方法——明確劃定責任範圍:
- GiftGenius:只做禮物點子 + 透過 ACP/Checkout 協助購買;
- CalendarApp:事件與提醒;
- Finance‑App:使用者的整體預算與個人理財計畫。
在 App 與工具的描述中,既要明寫「Use this when…」,也要寫「Do not use when…」。官方的 discovery‑playbook 正是這麼建議。
工具描述的迷你範例:
description: `
Use this tool when the user explicitly asks for gift suggestions.
Do not use for generic product discovery or price comparison.
`
這類限制不僅幫助路由,也讓你的 App 行為對產品與 QA 更可預期。
7. Apps 的組合樣式:pipeline、handoff、shared context
在 Multi‑App 情境中,實務上常見三個相關概念:
- pipeline —— 多個 App 依序執行(行事曆 → 禮物 → commerce),各司其職;
- handoff —— 一個 App 的輸出成為下一個 App 的輸入;
- shared context —— 全部交接都透過共享的對話文字上下文完成,App 彼此之間沒有直接的 HTTP 呼叫。
如前所述,Multi‑App 並不是「App A 透過 HTTP 呼叫 App B」的魔法。在目前的 ChatGPT Apps 實作中,隔離相當嚴格:應用不會彼此直接呼叫,溝通是透過共享的文字上下文進行。
基本樣式可以這樣表述:
- App A 在對話中回傳文字或 JSON(常見於 structuredContent/小工具)。
- 模型閱讀這個輸出。
- 下一步它可能呼叫 App B,並把 A 的回覆細節填入 B 的工具參數。
這稱為 text/context handoff:「App A 的輸出 → 模型 → App B 的輸入」。
範例:CalendarApp + GiftGenius + CommerceApp
我們來分解一個具體情境。
使用者:「老闆明天生日,幫我挑禮物並直接結帳。」
逐步流程:
-
模型先判斷需要瞭解日期與人是誰。它呼叫行事曆 App 的工具,假設為 corporate_calendar.list_upcoming_birthdays,並取得如下結構:
[ { "name": "Aleksey Bykov", "date": "2025-11-22", "relation": "manager" } ] -
接著模型判斷該呼叫 GiftGenius。它用從行事曆拿到的資料呼叫你的 suggest_gifts:
{ "recipientName": "Aleksey", "occasion": "birthday", "budget": 150, "relationship": "manager" }GiftGenius 小工具會顯示禮物輪播,而文字回覆會解釋為何這些點子合適。
-
使用者在小工具中用按鈕選了一兩個方案(→ widgetState),接著模型就會呼叫 commerce‑App 的工具,例如 corp_checkout.create_gift_order,並帶上所選 SKU 的 ID 與收件地址。
在 ChatGPT 看來這是三個不同的應用,但對使用者而言是同一段對話。要讓這一切順暢運作的關鍵在於:
- 清楚的工具 descriptions;
- 清楚的命名(corporate_calendar.list_upcoming_birthdays,而不是 list_events);
- 一致的結構化資料格式(讓禮物點子的描述足以讓 commerce‑App 理解)。
視覺化示意
這個 pipeline 可以這樣畫:
sequenceDiagram
participant U as 使用者
participant C as ChatGPT (Router)
participant Cal as CalendarApp
participant G as GiftGenius
participant Com as CommerceApp
U->>C: 老闆明天生日,幫我挑選並直接下單購買
C->>Cal: tools.call(list_upcoming_birthdays)
Cal-->>C: [{ name, date, relation }]
C->>G: tools.call(suggest_gifts, { recipient, occasion, budget })
G-->>C: gift suggestions (+ widget)
C-->>U: 說明 + GiftGenius 小工具
U->>C: 我喜歡選項 #2,幫我買
C->>Com: tools.call(create_gift_order, { skuId, address })
Com-->>C: Order confirmation
C-->>U: 完成,下單成功
作為 GiftGenius 的開發者,你的任務是讓你的聲音在這場合奏中清楚而到位,而不是與其他人混在一起。
8. 互通性:讓回覆可被其他 Apps 機器處理
在 Multi‑App 世界,僅「對使用者說得漂亮」還不夠。最好讓你的 toolOutput能被機器處理,以便其他應用(commerce‑App、分析代理、工作流程編排器等)使用。
這意味著幾點實務建議:
- 在工具回覆中使用結構化 JSON,而非序列化的人類可讀文字;
- 盡量遵循穩定、易懂的欄位。
例如,可以這樣為 suggest_gifts 的結果定型:
export type GiftSuggestion = {
id: string;
title: string;
description: string;
price: number;
currency: string;
forPerson: string;
occasion: string;
purchaseUrl: string;
};
並在工具回覆中返回此類物件的陣列:
{
"gifts": [
{
"id": "sku_123",
"title": "桌上型星象儀",
"description": "迷你星空投影機……",
"price": 89.99,
"currency": "USD",
"forPerson": "Aleksey",
"occasion": "birthday",
"purchaseUrl": "https://shop.example.com/sku_123"
}
]
}
GiftGenius 小工具會把這些作為 props 來優雅地渲染卡片,而 commerce‑App 在上下文中看到這段 JSON,也能接續取用 id 與 purchaseUrl 來完成結帳。
Multi‑App 的實務告訴我們:好的 App 會以便於他人「食用」的方式回傳資料,而不只讓人眼看得懂。
9. 將 GiftGenius 針對 Multi‑App 做實務重構
我們把上面內容收斂成幾個對教學應用的具體修改。
精煉 manifest‑description
假設我們有一個 openai-app.json(或 Next.js 範本中的對應檔)描述如下:
{
"name": "GiftGenius",
"description": "Gift assistant for finding and buying presents."
}
將它改得更利於路由:
{
"name": "GiftGenius",
"description": "Assistant for gift ideas and purchase flows. Use this app when the user asks what to gift a specific person or for a specific occasion within a budget. Do not use for generic online shopping or personal finance planning."
}
現在從這段文字就能看出它不是一般購物、不是理財顧問、也不是行事曆。
重寫工具的 descriptions
尋找禮物的工具:
const suggestGiftsTool = {
name: "giftgenius_suggest_gifts",
description: `
Use this when the user asks for gift ideas for a specific person or group,
optionally with a budget or occasion.
Do not use for non-gift product recommendations or travel booking.
`
};
從你的目錄拉取 SKU 細節的工具(read‑only):
const getGiftDetailsTool = {
name: "giftgenius_get_gift_details",
description: `
Use this to fetch more details for a gift suggested earlier by GiftGenius,
for example when the user asks “tell me more about option #2”.
`,
annotations: { readOnlyHint: true }
};
購買工具——加上 destructiveHint,如前所示。
更新 _meta["openai/widgetDescription"]
假設我們的小工具卡片已經有「購買」的 CTA。向模型提示這點:
const giftWidgetMeta = {
_meta: {
"openai/widgetDescription": `
顯示包含描述、價格與「購買」按鈕的禮物卡片清單。
模型不應在文字中重複完整清單,而是評論選擇並協助決策。
`
}
};
如此一來,如果清單已在小工具中可見,模型就不會在對話裡攤開十個禮物的大段文字,而會專注於解釋與決策輔助——對 UX 與 token 成本都更有利。
10. 為你未來產品建立 Multi‑App 思維
重要的是把思維從「如何打敗所有競爭者、成為使用者唯一的 App」切換到「如何讓我的 App 成為大型生態系中的理想模組」。
這種作法帶來幾個實際好處:
- 更容易向使用者與 Store 審查者說清楚你的 App 用來做什麼、何時適用;
- 模型更容易做路由決策:更少混淆、更少「錯誤呼叫」;
- 你能主動設計組合:今天與行事曆與 commerce 搭配,明天與企業 HR 機器人或內部 CRM 搭配。
官方 discovery 指南強調:設計「one job per tool」,並把中繼資料視為需要持續測試與更新的活文件,而不是第一個 commit 的靜態文字。
這裡,你在第 20 模組先前已完成的工作——golden cases、LLM evals、CI 執行——會非常有幫助。你可以把測試場景擴展成「對話同時有 GiftGenius 與 CalendarApp」,並追蹤描述文字的變更如何影響 App 的選取與回覆品質。
11. 使用 Multi‑App 與組合時的常見錯誤
錯誤 #1:App 描述寫成「我什麼都會」。
如果你在 manifest‑description 中寫「可處理各種任務的智慧助理」,你不只在和其他 App 競爭,也在和基礎的 ChatGPT 競爭。路由器會更難判斷何時必須找你、何時只靠內建功能即可。在 Multi‑App 世界,具體且聚焦的應用會勝出:「禮物挑選」、「行事曆管理」、「日誌分析」等。
錯誤 #2:工具描述模糊、名稱衝突。
像 get_data、process_request 這類名稱,加上「處理使用者資料」這種解釋,最擅長的就是讓模型困惑。在多個 App 的世界中,你的 tool 很容易在完全不同領域被錯用。正確做法是把領域與動作綁在一起(giftgenius_get_gift_catalog、calendar.list_birthdays),並明確寫出「Use this when… / Do not use when…」。
錯誤 #3:忽略 _meta["openai/widgetDescription"]。
開發者常只填 description,最好的情況也只是拿 _meta 來處理在地化。結果就是模型不理解小工具在顯示什麼,於是要嘛用文字把 UI 重述一遍,要嘛承諾使用者會看到「含價格的表格」,而你的小工具根本沒有。widgetDescription 裡的幾句話可以避免許多誤會。
錯誤 #4:缺少註解 readOnlyHint/destructiveHint。
如果你的所有工具都看起來一視同仁、毫無差異,模型就不會分辨哪些能經常呼叫、哪些需要使用者確認。在包含多個 App 的多步場景中,這尤其關鍵:可能會在沒有明確人為介入的情況下連做好幾個 write 操作。別忘了標示 read‑only 工具,並清楚指出具後果/破壞性的行為。
錯誤 #5:回覆只為人設計,沒有考慮其他 Apps。
從工具回傳一行「禮物清單」的純文字很誘人,但其他 App 就更難利用結果。以清楚欄位構成的結構化 JSON(id、price、currency、purchaseUrl、occasion)同時能提升 UI 與組合的品質:模型可以不經自然語言剖析,直接把資料填入其他工具的參數。
錯誤 #6:試圖在一個 App 裡做使用者可能需要的一切。
有時會想:「既然我做了 GiftGenius,就讓它也管行事曆、群發同事信、做預算規劃吧。」在孤立世界或許勉強可行,但在 Multi‑App 上下文,你會變成與其他專精、聚焦的 App 產生衝突的「萬能機」。更好的方式是和自己約定:我的 App 只做 X,並做到完美;其他是別人的責任。這種設計大幅簡化 UX 與整個生態的演進。
錯誤 #7:沒有在其他 App 的環境中測試行為。
開發者常在 Dev Mode 的「乾淨」對話中測試,裡面沒有其他 App。但在 Store,使用者可以同時連接十個應用,其中一些和你領域重疊。請務必建立測試情境,讓對話中同時存在相鄰的 App(行事曆、通用購物、理財),並執行 golden cases:模型是否在禮物相關請求中正確選擇 GiftGenius?有沒有把它和其他參與者混淆?
GO TO FULL VERSION