在 Alpic,我們相信下一代產品與服務將圍繞 以 AI 為核心的體驗打造。在這樣的介面中,使用者與模型協作,而不是按照傳統 UI 預先設定的工作流程操作。
OpenAI 推出 Apps SDK 後,我們立即開始用它開發。在三個月內,我們開發了 24 個 ChatGPT 應用程式,供內部使用,也服務 旅遊、零售和 SaaS 等 B2B 與 B2C 領域的客戶。
我們很早就發現, 打造 ChatGPT 應用程式,與開發傳統網頁或行動應用程式有根本上的不同。在網頁上行之有效的模式,例如需要時才擷取資料、由 UI 驅動狀態、讓使用者明確設定選項等,到了智慧體環境中往往行不通,甚至會損害使用體驗。
本文整理了我們在打造實際使用的 ChatGPT 應用程式時,學到的 最重要的 15 個心得 ,接著介紹我們如何將這些心得融入為社群打造的開源框架 Skybridge 與一項 Codex 技能,協助開發人員大幅加快構思、開發、測試及推出應用程式的速度。
三體問題
傳統網頁應用程式的情況很簡單:只有 使用者 和 UI。但在 ChatGPT 應用程式中,系統多了第三個角色: 模型。
為 ChatGPT 開發時,最困難的事情之一就是管理這三者之間的資訊流動。如果使用者在小工具中按下「選取」按鈕,UI 的畫面會更新,但作為對話大腦的模型並不知情,除非你明確將這些上下文傳遞給它。如果使用者接著問: 「請提供這項產品的更多詳細資訊。」 模型根本不知道使用者正在看什麼。
我們將這種情況稱為 上下文不對稱 :每個角色都只知道系統的部分資訊,沒有任何一方掌握全貌。打造好的 ChatGPT 應用程式,重點不在於讓所有資訊保持同步,而在於決定應該分享 哪些 資訊、 何時 分享,以及 誰 需要知道。能否解決這個問題,決定了應用程式是操作不順,還是能提供流暢的智慧體體驗。
1. 並非所有上下文都應該分享
我們最初的直覺是「把所有資訊分享到所有地方就好」。結果,這成了我們最早犯的錯誤之一。
實務上,ChatGPT App 的不同部分往往需要針對同一個狀態, 刻意呈現不同的資訊 。為什麼?
- 效能考量: UI 小工具所需的資料量,通常遠超過模型應該需要的範圍。例如,旅遊預訂應用程式可能需要圖片、不同價格方案,以及預先載入的選項。將這些資料全部傳送給模型,會增加 Token 用量、延遲,以及干擾理解的雜訊。
- 邏輯考量: 有些資訊在設計上就必須保持不對稱。在我們最早開發的應用程式之一,也就是 Murder in the Valleys 推理遊戲中,模型需要知道誰是兇手,才能正確扮演角色,但 UI 和使用者都不能知道。在 Time’s Up 類型的遊戲中,情況則相反:UI 會向使用者顯示謎底詞語,但模型必須毫不知情。
我們學到的不是「隨時同步所有資訊」,而是: 明確決定誰需要知道什麼。我們透過不同的 工具輸出 欄位,將這個原則落實下來:
| 欄位 | 用途 | 可見對象 |
|---|---|---|
| structuredContent | 供小工具和模型使用的具型別資料 | 小工具與模型皆可見(透過 toolOutput 和 callTool 函式) |
| _meta | 回應中繼資料 | 僅小工具可見,對模型隱藏 |
例如,在 Time’s Up 遊戲中,我們只透過 _meta 欄位將謎底詞語傳給小工具,讓模型根據使用者的提示猜出詞語。
2. 延遲載入不太適合 AI 應用程式
由於過去從事網頁開發,我們習慣採用延遲載入:等使用者點擊時才擷取資料、按需載入詳細資訊,並盡量減少初次載入的資料量。
但在 ChatGPT 中,情況恰好相反:工具呼叫會帶來延遲,而且因為安全沙盒和模型推理的緣故,往往需要數秒才能完成。
實務上,我們學會了盡可能提前載入資料:在初次工具回應中傳送盡可能多的資料,並透過 window.openai.toolOutput 將資料填入小工具。這幾乎總能帶來更快速、反應更靈敏的體驗。
當然,如果小工具可以安全地從公開 API 端點擷取資料,而且不需要與模型分享資訊,你仍然可以在小工具內使用傳統的 XHR 呼叫。但大多數時候,你會希望模型能夠自主呼叫工具,讓使用者持續以對話方式操作。
3. 模型需要掌握介面狀態
當使用者與小工具互動,例如在清單中選取某項產品,接著在對話中提問時,就會出現一個不易察覺卻很關鍵的問題。如果模型不知道使用者指的是 UI 的哪個部分,就無法正確回答。
為此,我們使用了 window.openai.setWidgetState(state)。它能儲存特定的狀態資料,並在使用者下次與模型互動時,將這些資料加入模型的上下文。
隨著應用程式變得更複雜,我們發現自己在許多地方加入了 setWidgetState,好讓模型掌握使用者的導覽動向。因此,我們決定引入宣告式的方式來描述 UI 上下文。我們不再於每次互動時以命令式方式更新模型,而是直接在元件上附加 data-llm 屬性:
<div
data-llm={
selectedTab === "details"
? "User is viewing product details"
: "User is viewing reviews"
}
>
為了讓這套機制在幕後自動運作,我們開發了一個 Vite 外掛程式,擷取這些屬性並自動更新 widgetState。對模型而言,它只會在適當的時機收到相關 UI 上下文,開發人員不必再為每次互動手動同步資訊。
你可以在我們為了與社群分享心得而建立的開源框架中,找到這個 Vite 外掛程式,以及本文分享的許多其他技巧。
4. 不同互動需要不同的 API
ChatGPT 應用程式的小工具、伺服器和模型之間,有多條互動路徑。這些路徑無法互相替代:每條路徑都是為了支援不同類型的互動而存在。
打造 ChatGPT 應用程式的一項重要心得,就是明確定義這些通訊路徑,並仔細決定由哪個機制負責體驗中的哪個部分。
將這些路徑畫出來,大致如下:

這些心得確立了 ChatGPT App 的基礎:如何分享上下文、如何讓模型掌握資訊,以及不同互動如何在系統中傳遞。下一節將以此為基礎,探討這些做法對 UI 設計的影響。
為 AI 重新設計 UI
ChatGPT 應用程式是一個全新的環境,因此我們很快就學會放下對 UI 的既有想法,充分運用新的能力。本節將介紹為了打造實用的應用程式,我們需要學習,以及需要放下的介面設計觀念。
5. UI 必須適應多種顯示模式及其限制
ChatGPT 應用程式並不局限於單一版面配置。視啟用的方式與時機而定,同一個小工具可以透過三種不同的顯示模式呈現。
應用程式可以 內嵌 在對話中、以 子母畫面(PiP) 懸浮於對話上方,或在需要更多空間時以 全螢幕 顯示。PiP 和全螢幕雖然能呈現更豐富的介面,卻也會帶來小工具無法控制的 UI 覆蓋元素。設計時必須考慮各裝置的安全顯示區域,例如為行動裝置上固定顯示的關閉按鈕預留空間,才能避免內容遭到裁切,並改善互動體驗。
隨著經驗累積,我們歸納出各種顯示模式的特點與適用時機:
| 呈現方式 | 使用時機 | |
|---|---|---|
| 內嵌 | 預設顯示模式。小工具會保留在對話記錄中。 | 適合快速互動 |
| 全螢幕 | 小工具佔滿整個螢幕,對話列位於底部。 | 小工具較複雜且需要大量空間時,例如地圖 |
| 子母畫面 | 尺寸與內嵌模式相同,但小工具會持續懸浮於對話上方 | 小工具產生後,在後續對話中仍會用到時 |
6. 在嵌入式環境中,UI 一致性很重要
一開始,我們不確定 ChatGPT App 的視覺設計應該有多大的自由度。對使用者而言,這是全新的介面,卻仍需要讓人感到熟悉且一致,不僅我們自己的各個應用程式要保持一致,也要與周圍的 ChatGPT 生態系統協調。小工具與獨立產品不同,它存在於既有的介面之中,任何視覺上的不一致都會立刻顯得突兀。
幸好,OpenAI Apps SDK UI Kit 為我們提供了明確的基準。
它以 Tailwind CSS 為基礎,提供符合 ChatGPT 設計系統的現成元件、圖示與設計 Token。使用這套工具讓我們能快速開發,同時確保小工具自然融入周圍介面,維持一致的視覺風格;即使開發自訂元件(例如整合 Mapbox 所需的元件)也是如此。
7. 以自然語言為主的篩選方式
傳統儀表板的側邊欄充滿核取方塊與範圍滑桿。但在智慧體式 UI 中,這往往反而是一種退步。當使用者可以直接以自然語言表達意圖,例如「預算低於 $200、陽光充足的歐洲目的地」,卻仍被迫操作多個 UI 控制項,只會增加操作阻力。他們應該只要說出需求就好。
因此,我們決定讓大多數應用程式採用「不設篩選器」的做法。我們不在側邊欄提供篩選與排序選項,而是向模型提供工具參數的 值清單(LOV) 。
這讓模型能直接以使用者的訊息作為輸入,不必「猜測」有哪些可用選項。換句話說,模型可以將自然語言直接對應到後端 API 的要求。如果使用者說「陽光充足」,模型就知道要以 weather="sunny" 呼叫工具。
8. 檔案能帶來更豐富的互動
隨著我們開發的應用程式日益複雜,我們體會到不該把檔案當作次要輸入。在 ChatGPT 應用程式中,檔案能帶來新的互動方式。使用體驗不必從表單或篩選器開始,也可以從使用者手邊已有的東西開始。
例如,在電子商務應用程式中,使用者可以在對話中上傳商品照片,讓模型辨識,再直接於小工具中尋找相符商品或探索其他商品。
要做到這一點,關鍵是讓檔案能在系統的兩端流通。在模型端,工具可以透過 openai/fileParams 直接使用對話中上傳的檔案,讓模型針對圖片或使用者提供的其他素材進行推理。在 UI 端,小工具也能透過 window.openai.uploadFile 與 window.openai.getFileDownloadUrl 直接處理檔案,在 UI 流程中要求使用者上傳檔案,或產生可供使用者下載並重複使用的檔案。
邁向正式環境
接著,當應用程式不再只在本機開發時,就需要考量安全性、組態與工具等不同面向的問題。這正是第三組經驗要談的內容。
9. CSP 成了新一代的 CORS 課題
基於安全性考量,OpenAI 會在雙層巢狀 iframe 中呈現應用程式。內容安全性政策(CSP)是 iframe 隔離的原生機制,而這種架構會嚴格執行政策,因此經常出現典型的「本機能跑,正式環境卻出問題」現象。
在傳統網頁開發中,寬鬆的政策或許還能應付,但 Apps SDK 要求你精確設定。
這表示你必須在應用程式資訊清單中,仔細宣告每種互動類型允許使用哪些網域:
| 欄位 | 用途 | 範例 | 常見錯誤 |
|---|---|---|---|
| connectDomains | API 與 XHR 請求 | https://api.weather.com | 忘了區分預備環境與正式環境的 API。 |
| resourceDomains | 圖片、字型、指令碼 | https://cdn.jsdelivr.net | 使用 delivr.net 等通用 CDN,卻未將其加入允許清單 |
| frameDomains | 嵌入 iframe | https://www.youtube.com | 嵌入 YouTube 影片或 Mapbox 執行個體,卻未將其加入允許清單。 |
| redirectDomains | 開啟時不顯示警告的外部連結 | https://app.alpic.ai | 遺漏結帳或 OAuth 回呼的網域。 |
從一開始就重視 CSP 組態,讓我們後來省下了大量在正式環境中除錯的時間。
10. 小工具旗標雖小,影響卻很大
除了 CSP 之外,還有少數小工具層級的設定,會決定小工具、模型與主機環境之間如何分配控制權。這些旗標很容易被忽略,卻界定了導覽、工具存取與發布的重要邊界。
主機與導覽的邊界
- 提交時必須提供
widgetDomain。它定義了全螢幕模式下「在 <App> 中開啟」按鈕的預設目標位置,也用於來源允許清單,因為小工具是在<widgetDomain>.web-sandbox.oaiusercontent.com下呈現。我們使用setOpenInAppUrl,根據上下文將使用者導向適當路徑。
模型與工具的邊界
- 工具註解 必須遵循發布準則。
readOnly、destructiveHint與openWorldHint等旗標是必填項目,提交時也會進行驗證。 - 工具可見性 很重要:不應讓模型呼叫的工具,必須明確標記為私有。
小工具執行的邊界
widgetAccessible控制小工具是否能自行透過callTool呼叫工具。
這些設定單看都是小細節,但合在一起,就決定了應用程式發布後能否正確運作。
為快速迭代做好準備
Apps SDK 正快速演進,能在它持續發展的同時投入開發,讓我們相當興奮。為了讓開發工作流程更順暢、更有效率,我們決定開發自己的開源框架,並分享給社群。以下是我們整理的一些經驗,幫助大家避開我們初期遇到的開發體驗問題。
11. 快速迭代需要熱重載
迭代速度是我們最先著手改善的問題之一。資源快取的 TTL 很長,加上資源透過 JSON-RPC 轉送,使得 Vite 或 Next.js 中常見的標準模組熱重載,無法直接用於 ChatGPT 應用程式。
我們花了不少時間了解 Vite 的內部運作後,開發了一個 Vite 外掛程式,讓小工具能直接在 ChatGPT 內即時重新載入。這個外掛程式會攔截送往 MCP 伺服器的資源請求,並將即時更新注入 ChatGPT 的 iframe。在 IDE 中做出的變更能立刻反映在 ChatGPT 裡,大幅縮短了我們取得回饋的時間。

12. 並非所有測試都需要在 ChatGPT 中進行
在 ChatGPT 上測試是最可靠的標準,但在最初幾輪迭代中,本機模擬器能幫助你更快推進,尤其是在修改工具定義、需要於開發人員模式中重新載入應用程式時。
為了加快初期迭代,我們打造了一個輕量的本機模擬器,用來模擬 ChatGPT 主機環境,並配備除錯工具與應用程式專用記錄。這讓我們能以毫秒級的速度反覆調整 React 狀態與版面配置,再到真正的 ChatGPT 環境中驗證模型互動與邊界情況。
13. 行動裝置測試需要專門支援
行動裝置測試帶來了另一個挑戰:在 ChatGPT 中測試時,必須為本機伺服器建立通道連線,但 Vite 預設使用 localhost,導致其他裝置無法存取同一個 URL。
我們擴充了 Vite 外掛程式,讓它支援通道連接埠上的網域轉送,藉此解決問題。這讓我們能在 iOS 與 Android 裝置上測試,也讓行動裝置驗證成為日常工作流程的一部分。
14. 熟悉的抽象介面(例如 React 掛勾)能加快前端開發
Apps SDK 提供了強大的能力,但主要透過低階 JavaScript API 開放使用。身為長期使用 React 的開發者,我們希望能透過已經熟悉的概念來使用這些能力。
因此,我們加入了一些適合 React 的抽象介面,包括 useCallTool、useWidgetState 與 useLocale 等掛勾,以及以 Zustand 為基礎、用來處理複雜資料流程的 createStore 等進階狀態管理工具。重新引入熟悉的前端模式,減少了樣板程式碼,也讓小工具開發更接近現代網頁開發的工作流程。
將經驗轉化為 Codex 技能
15. 將經驗轉化為可重複使用的工具
當這些模式在多個應用程式中反覆出現,我們逐漸意識到,一再重新摸索相同做法正在拖慢開發速度。為了讓 ChatGPT App 開發更快速、過程更可預期,我們決定將這些經驗直接融入工具中,不僅供自己使用,也分享給社群。
這促成了兩項相輔相成的成果:
- Skybridge Framework: 這個開源 React 框架將本文介紹的許多模式封裝成可重複使用的構件,包括我們的掛勾(
useCallTool、useToolInfo)、開發工具(HMR 和本機模擬器),以及 data-llm 屬性。 - chatgpt-apps-builder Codex 技能: 我們在框架的基礎上打造了專用的 Codex 技能,支援應用程式的完整生命週期:
- 構思: 腦力激盪,思考如何讓應用程式具備智慧體特性,而不只是移植網頁應用程式。
- 程式碼生成: 同時編寫 React 前端與 MCP 伺服器後端,並預先套用所有合適的 UX 與 UI 模式。
- 本機測試: 啟動開發伺服器,將本機應用程式連接至 ChatGPT,透過熱重載即時反覆調整。
- 品質工程與發布: 依照 OpenAI 的提交指南進行有系統的檢查,包括 CSP 驗證、安全區域考量,以及正式環境測試。
- 應用程式部署: 協助完成應用程式上線與後續改進所需的最後步驟。
若要安裝並使用這項技能,只需使用以下指令:
npx skills add alpic-ai/skybridge
結語
打造 ChatGPT 應用程式,需要重新思考上下文如何傳遞、介面如何運作,以及使用者與模型如何協作。本文的許多經驗,都來自熟悉的網頁開發模式與智慧體系統實際運作之間的落差。
我們分享這些經驗,並將它們融入開源框架與 Codex 技能,希望能讓團隊少花時間重複摸索相同問題,多花時間探索這種新互動模式帶來的可能性。最吸引人的 ChatGPT 應用程式不會只是現有產品的簡單移植,而是從這種以 AI 為優先的全新體驗出發,精心設計而成。