1. 為什麼 ChatGPT App 的釋出流程比一般部署更複雜
在上一部分,我們談了如何觀察 App 的品質與穩定性(日誌、指標、SLO)。現在來看,如何設計釋出流程,避免每次部署都把這些指標搞壞。
在一般 Web 應用中,事情相對單純:你部署了新的後端與前端——使用者重新整理頁面就跑在新版本上。若出問題,常常只要回滾部署就好。
在 ChatGPT App 的堆疊裡情況更微妙。至少有四個彼此節奏不同的層:
- Manifest 與工具的 schema(MCP tools / OpenAPI);
- 基礎設施:Next.js 應用與 MCP/Agents/ACP 伺服器;
- System‑prompt 與其他提示詞;
- 資料:product feed、設定、組態。
問題在於,模型活在自己的「心智世界」。聊天上下文可以延續數小時乃至數天。Manifest 與 tools 的描述會由 OpenAI 端載入 並會快取,且不會在所有現有對話裡即時更新。若你修改了工具的簽章(例如從 input‑schema 刪除欄位),在舊對話中模型仍會送出舊的 payload,而你的新後端會拒收。結果就是 400 錯誤、聊天中怪異的回覆,以及非常不開心的使用者。
因此,在 ChatGPT App 的世界裡,「釋出」不是「部署一個新的 Docker」那麼簡單。它是多個層面的協同變更,需要謹慎的版本管理、功能旗標,以及可隨時回滾的能力。
2. 版本矩陣:到底該對哪些東西版控
與其只看「App 1.3 的版本」,不如同時看各層的版本矩陣。以 GiftGenius 為例,大致如下。
| 層 | 要版控什麼 | 範例值 | 存放位置 |
|---|---|---|---|
| App / Next.js | 程式碼與建置 | |
package.json、Git 標籤 |
| MCP / API 介面 | 工具集合及其 schema | |
程式中的常數、註解 |
| System / prompts | System‑prompt、輔助提示、範例 | |
獨立檔案 + 中繼資料 |
| Commerce / ACP / feed | product feed 與 ACP 合約的格式 | |
資料儲存庫中的 schema |
請注意,這是不同的維度。你可以發佈應用版本 1.4.2,其中 tools 的 schema 仍是 v1.7,而 prompt 升到 v3.2。這些應在日誌中可見;之後就更容易追查為什麼在 prompt v3.2 之後結帳轉換率突然下滑。
對於程式碼,建議採用語意化版本(SemVer):MAJOR.MINOR.PATCH。MAJOR=破壞性變更,MINOR=不造成破壞的新功能,PATCH=修補錯誤。對介面類的版本(schema、prompts)邏輯也相似:MAJOR 代表有 breaking‑change。
尤其要單獨管理工具介面的版本。對 LLM 來說這極其重要:若你變更了工具契約,而模型「以為」契約仍舊,災難就來了。因此常見政策是:在 MINOR 釋出中只新增選用欄位,不重新命名或刪除舊欄位;破壞性變更只能透過新工具,例如 suggest_gifts_v2。
現在我們把各層分出版本後,來看看這些版本在釋出週期中如何運作——從 dev 到 prod。
3. GiftGenius 的基礎釋出流程
先約定環境階段。你可能已經有不同環境(在部署模組裡提過),但這次改用釋出的視角來看。
通常會區分:
- dev——本機開發、ChatGPT 的 Dev Mode、tunnel;
- staging——盡量貼近生產:相同類型的資料庫、相同類型的 MCP/ACP,但使用測試資料與 sandbox 支付;
- prod——生產環境,即連接正式版 ChatGPT App/商店上架頁的那個環境。
流程可能如下:
flowchart TD A[Dev:分支 feature/*] --> B[PR → main] B --> C[CI: unit + contract + lint] C --> D[部署到 Staging] D --> E[Smoke/E2E + 人工檢查] E --> F[部署到 Prod] F --> G[依據指標與日誌觀察]
在每一步都加上「保險」。在 dev 環境,開發者可以自由嘗試,但每次合併到 main 都會觸發 CI,跑 unit/contract 測試。若一切正常——自動部署到 staging。在 staging 上跑一小套 E2E/煙霧測試:例如一次完整的禮物挑選流程,和在測試金流中的「假結帳」。
之後才按下部署到 prod 的按鈕。最好加上版本標記與 CHANGELOG 連結。進入生產後,持續觀察 p95、錯誤率與商業指標(從推薦到結帳的轉換率)。若釋出後有指標下滑——我們要有清楚的回滾計畫,稍後會談。
4. Release notes 與 changelog:要記錄什麼
如果沒有釋出筆記,一個月後你看著圖表:「為什麼三週前我們的結帳 p95 翻倍?」答案會是:「不清楚,但那時有個大釋出。」這不是好策略。
通常至少有兩種筆記。
內部 changelog——技術向的。用於開發者、SRE 與所有看程式的人。內容包含新增了哪些功能、修了哪些 bug、是否有 breaking changes。格式可採 Keep a Changelog 標準:分區 Added、Changed、Fixed、Removed。
外部 release notes——給使用者與商店看的。無需寫「migrate MCP SDK 0.4 → 0.5」;更好的是寫「新增感恩節的推薦」、「修正某些禮物無法加入購物車的罕見錯誤」。
GiftGenius 的 CHANGELOG.md 片段範例:
## [1.4.0] - 2025-11-20
### Added
- 新的工具 `suggest_gifts_v2`,支援興趣標籤。
- 新 system-prompt 的 A/B 測試(旗標:GG_PROMPT_V3)。
### Changed
- 改善 ACP checkout 的錯誤處理(訊息更友善)。
### Fixed
- 修正一個導致沒有圖片的商品會從推薦中被隱藏的錯誤。
把提示詞的版本也放在附近很有用:哪怕只是 system‑prompt 檔案的 commit‑hash,之後就能回想:「啊,更新到提示詞 b3f9c2d 後,使用者比較少按「購買」。」
5. SDK 與規格的遷移:如何在快速演進的生態裡求生
圍繞 Apps SDK、MCP、Agents 的生態變化很快:新的版本 SDK、MCP 協議變更、ACP 更新等等。這是好事(帶來新能力),但也痛(很容易壞)。
固定版本與「不要在釋出當天更新」
第一個簡單建議:鎖定相依版本。不要用 ^0.4.0,而是 0.4.0。對那些常破壞 API 的 SDK(MCP/Agents/Apps SDK)尤為關鍵。我研究過相關主題,文獻中特別提到「SDK fatigue」——因持續不相容更新而產生的疲勞,特別是讓相依採用浮動版本(例如 ^0.4.0)的人,某天突然在 npm install 後就遇上大量錯誤。
小例子:
// package.json(片段)
{
"dependencies": {
"@modelcontextprotocol/sdk": "0.4.2",
"@openai/applications-sdk": "0.3.1"
}
}
第二個建議:不要把商業功能與大型 SDK 遷移塞在同一個釋出裡。如果要把 MCP SDK 從 0.3.x 升到 0.4.x,最好做一次獨立的技術性釋出:先更新 SDK、跑測試、穩定之後,再推新的結帳流程。
SDK 遷移策略
合理的計畫通常如下:
在 dev 環境建立獨立分支 upgrade/mcp-sdk-0.4。更新相依、修正程式、跑所有 unit/contract 測試,並在本機透過 Dev Mode 跑 GiftGenius 的主要流程。
接著把這個分支部署到獨立的 staging‑URL,並對其跑 E2E/煙霧測試。甚至可以做小規模壓測:50–100 次連續呼叫 suggest_gifts。
若一切正常——合併到 main,部署到主要的 staging,再跑一次煙霧測試,最後才進 prod。
若不正常——回滾很明確:別合併此分支或把它還原。這就是把 SDK 遷移與產品釋出分開的意義。
工具與 API schema 的遷移
最棘手的是介面層變更:模型不會瞬間得知。研究特別強調一條規則:「只做擴充、不做破壞」(additive‑only)。若你要在 suggest_gifts 加入新參數 interests: string[],請把它做成選用,而不是必填;舊情境才能繼續運作。
Zod 輸入 schema 的演進示例:
// v1
const suggestGiftsInputV1 = z.object({
recipientName: z.string(),
budget: z.number()
});
// v1.1(新增選用欄位)
const suggestGiftsInputV1_1 = suggestGiftsInputV1.extend({
interests: z.array(z.string()).optional(),
occasion: z.string().optional()
});
注意:我們不動既有欄位,只做擴充。
若真的需要打破契約(例如把 budget 改成 minBudget + maxBudget),最好新增一個工具 suggest_gifts_v2,並在描述中標註這是改良版。舊 API 可標為已棄用並逐步關閉,待確定模型與使用者都已遷移。
資料遷移:product feed 與 ACP
前面談過 SDK 與工具 schema 的遷移。Product feed 也是一種契約。若你改了 SKU、幣別、在地化的格式,必須協調進行:同時更新 feed 的 schema、MCP/ACP 的處理,以及任何前置處理器。在 ChatGPT App 的 commerce 文件中指出,feed 的錯誤(欄位損壞、重複、奇怪的價格)即使程式完美也會毀掉推薦品質。
典型做法:
- 先在 feed 的 schema 新增欄位為選用,並讓 GiftGenius 在存在時加以使用。
- 然後更新產生 feed 的管線,讓它開始填入這些欄位。
- 跑 feed 驗證器(資料的 contract‑測試),之後才在邏輯中依賴這些欄位。
6. Feature flags:把部署和釋出解耦
功能旗標(feature flags)是 ChatGPT App 世界裡的生存利器。核心概念很簡單:你可以先部署程式碼,但不一定立刻開啟新功能。先把它「放在旗標下」——只對開發者開、或只對 1% 使用者開,甚至完全關閉。
以下情境特別重要:
- 推出新的推薦演算法;
- 更換 system‑prompt(可能劇烈改變模型行為);
- 接上昂貴或緩慢的工具;
- 嘗試新的結帳流程。
用環境變數實作的簡易旗標
最簡單的方式是用環境變數:
// lib/features.ts
export const isNewRecoEnabled =
process.env.NEXT_PUBLIC_GG_NEW_RECO === "1";
接著在 MCP 工具的程式中這樣用:
if (isNewRecoEnabled) {
return runNewRecoAlgorithm(input);
}
return runOldRecoAlgorithm(input);
好處是簡單。壞處是要在執行期切換旗標較麻煩:需要部署新的環境,或至少重新初始化。
集中化的 helper 與情境感知
再成熟一點的做法——有一個集中化的 helper,不只考慮全域旗標,還納入使用者情境(租戶、分群、A/B 群組)。
// lib/featureFlags.ts
type Feature = "new-reco" | "checkout-v2";
type Context = { userId: string; tenantId?: string };
export function isFeatureEnabled(
feature: Feature,
ctx: Context
): boolean {
// 這裡可以放邏輯:環境變數、資料庫、外部旗標服務
if (feature === "new-reco") {
return process.env.GG_NEW_RECO === "1";
}
return false;
}
在 MCP handler:
const enabled = isFeatureEnabled("new-reco", { userId });
const result = enabled
? await runNewReco(input)
: await runOldReco(input);
若將來接上外部旗標服務(LaunchDarkly、Statsig 等),只要更換 isFeatureEnabled 的實作,而非重寫整個程式。
GiftGenius 的實際情境
A/B 測試 system‑prompt。 你建立 PROMPT_V2,僅對特定名單中 10%、具有 tenantId 的使用者開啟。其間 MCP 工具與小工具不變,你比較轉換率。
昂貴工具的 kill‑switch。 假設你做了一個需要呼叫外部服務的工具(例如某個昂貴的推薦 ML 模型)。如果外部服務開始故障或成本暴增,你希望能一秒關掉它,而不必停掉 GiftGenius 整體。功能旗標是最簡單的方式。
逐步 rollout 新的結帳流程。 先只對公司員工與少數信任客戶開啟 Checkout v2。若指標良好——逐步擴大,直到全量開啟。
7. 回滾:當一切失火時如何快速回復
即便測試與旗標都做得很好,總會有東西壞掉。關鍵是要有明確策略:在發現錯誤尖峰或指標下滑後的前 5–15 分鐘,你要做什麼。
快速回滾程式碼
若問題是純技術性 bug(NPE、變數錯了、外部服務 URL 錯),通常只要回滾部署到前一版即可。像 Vercel 就有回滾到上一個 deployment 的即時功能。
你的任務是永遠知道哪個 deployment 對應哪個版本,以及如何回滾。理想情況下,這在 README(for on‑call)裡有寫:「若在 1.4.0 釋出後一切起火,回滾到 deployment X」。
第二個快速槓桿——功能旗標。若只是一個新功能(checkout-v2)掛了,與其立刻回滾整個釋出,不如先用旗標把它關掉。
高風險變更:manifests 與 schemas
Manifest 與 schema 更棘手。若你把新的、錯誤的工具 schema 上傳到 Store,且已被 OpenAI 核可,回滾可能要花上好幾天。原因很簡單:每次變更 manifest 也需要 OpenAI 審查。在各種 Store 的分析中都指出,manifest 與 schema 的變更是「高風險」釋出,必須特別小心地準備與測試。
因此最好區分:
- 安全的釋出:MCP/Next.js 程式碼變更、提示詞修正、以旗標隱藏的新功能;
- 高風險的釋出:tools 清單、input/output‑schema、ACP 契約的變更。
「高風險」釋出應單獨推出,做額外檢查,並可先經由 Dev Mode 與 staging‑App(不提交到 Store)驗證。
資料與 feed 的回滾
資料更棘手(也更痛)。若你把 product feed 遷到新 schema 並刪除了舊欄位,想回頭不一定容易。因此資料遷移應該可冪等且可逆:要嘛保留舊副本,要嘛切成兩步(先複寫資料,再切換讀取)。
簡單的做法是同時保留新舊欄位一段時間,並透過功能旗標在兩者間切換。若出問題——切回舊讀取。
8. GiftGenius 釋出流程的小型設計
把上述(版本、SDK 遷移、feature flags、回滾)組成一個實務情境。
假設你要做釋出 1.4.0,其中:
- 新增工具 suggest_gifts_v2,輸入擴充;
- 對部分使用者開啟新的 system‑prompt;
- 把 MCP SDK 從 0.3 → 0.4;
- 變更 product feed 格式(新增 tags 欄位)。
合理計畫如下。
先做獨立的技術性釋出 1.3.1:更新 MCP SDK + 最小幅度的程式修正,不改 schema 與功能。跑 CI、staging、煙霧測試。若一切穩定——先以此版本運行幾天。
接著建立分支 feature/reco-v2。在那裡新增 suggest_gifts_v2 作為新工具(舊的 suggest_gifts 保留)。其輸入 schema 只往「新增選用欄位」的方向擴充。準備新的 system‑prompt,但以旗標 GG_PROMPT_V3 包起來。在 ACP/feed 加入新欄位 tags 作為非必填,並讓讀取在缺少該欄位時依然能運作。
在 CI 增加一些新的 contract‑測試:確認 suggest_gifts_v2 可接受舊與新的 payload;確認含 tags 的 feed 有效,且缺少 tags 的舊資料也不會把伺服器弄掛。
合併到 main 後:
- CI 跑 unit/contract;
- 在 staging 透過新工具跑 1–2 個 E2E 腳本;
- 透過功能旗標,只對測試用租戶開啟新的 prompt 與工具。
觀察指標:工具的 p95、錯誤率、結帳轉換率。若一切良好——擴大旗標覆蓋,直到開給更多使用者。只有在確認穩定後,才更新 Store 所需的 manifest(若需要)與應用的宣傳描述。
若過程中哪裡壞了——你知道怎麼回滾:要嘛手動關掉旗標、要嘛回滾 deployment,極端情況下才回退 manifest 版本(不過最好避免走到這一步)。
9. ChatGPT App 釋出流程中的常見錯誤
錯誤一:只有一個籠統的「App 版本」,而沒有版本矩陣。
若你只有「GiftGenius v1.4」,卻沒記錄 tools‑schema、提示詞與 product feed 的版本,就無法回答:「我們在什麼變更之後結帳下滑?」請把版本按層拆開,並在結構化日誌中記錄。
錯誤二:在工具上做了破壞性變更,卻沒有新的名稱/版本。
最痛的情況:你把 input‑schema 的欄位重新命名或刪除,卻不改工具名稱。在舊聊天中模型仍會送出舊的 payload,後端回傳 400,GPT 開始回「幻覺」,使用者也霧煞煞。任何破壞性變更請用新工具(foo_v2)或新 API 版本來做,且在過渡期保留舊介面。
錯誤三:在做新功能的途中順手升 SDK。
經典翻車法:你在新增業務功能的同時,順手把 @modelcontextprotocol/sdk 從 0.3 升到 0.5,「就想讓一切都很新」。結果如果壞了,搞不清是新程式、新 schema,還是新 SDK 惹的禍。SDK 遷移最好獨立成技術釋出,有明確測試計畫與回滾方案。
錯誤四:沒有功能旗標與即時 kill‑switch。
把新推薦演算法一次開給 100% 使用者很刺激,直到它產出怪結果或壓垮外部服務。沒有功能旗標,你唯一的槓桿就是整個釋出回滾,這可能牽連十來個無辜的小改進。至少用環境變數或小型設定檔做些簡單旗標吧。
錯誤五:以為 manifest 能「即時」更新。
常見誤解——以為一改了 tools 或 openapi.yaml,模型就會立刻知道新 schema。實務上 manifest 與工具描述會被快取,而在已開啟的聊天裡可能長時間存在。忽略這點會導致不明顯的 bug:新對話一切正常,舊對話卻在失敗。請在設計 schema 變更時考量此行為,並先透過 Dev Mode 與 staging 測試,再提交到 Store。
錯誤六:沒有清楚的回滾計畫與釋出文件。
若團隊裡沒人能回答「如何在 5 分鐘內回滾?」「要回到哪個版本?」「如何回到舊的 feed‑schema?」——就等於你們沒有回滾。On‑call 應該有一份簡短但具體的指引:按哪些按鈕、改哪些變數、到哪裡檢查回滾成功。
錯誤七:沒有 release notes、也沒有與指標關聯的「沉默釋出」。
沒有任何 changelog 就釋出,幾個月後你只能靠猜。當 p95 突然升高、轉換率下降時,你會想:「那時到底改了什麼?」養成至少寫最小限度釋出筆記、並把它與部署日期與版本連結的習慣,不只讓品質稽核容易,也讓整個團隊更輕鬆。
GO TO FULL VERSION