1. なぜ ChatGPT App のリリースプロセスは通常のデプロイより難しいのか
前回は App の品質と安定性をどう観測するか(ログ、メトリクス、SLO)について話しました。今回は、それらのメトリクスが毎回のデプロイで崩れないよう、リリースプロセス自体をどう設計するかを扱います。
一般的な Web アプリでは比較的シンプルです。新しいバックエンドとフロントエンドをデプロイすれば — ユーザーはページを更新して新バージョンを使います。何か壊れても、しばしば単純なデプロイのロールバックで済みます。
しかし ChatGPT App のスタックはもう少し厄介です。最低でも次の4つのレイヤーが、それぞれ別のライフサイクルで動いています。
- マニフェストとツールのスキーマ(MCP tools / OpenAPI);
- インフラストラクチャ: Next.js アプリケーションと MCP/Agents/ACP サーバー;
- System プロンプトやその他のプロンプト;
- データ: プロダクトフィード、設定、コンフィグ。
問題は、モデルが独自の「認知世界」で生きていることです。チャットのコンテキストは数時間から数日に及ぶことがあります。マニフェストと tools の記述は OpenAI 側で読み込まれてキャッシュされ、既存の会話すべてで即時に更新されるわけではありません。もしあなたがツールのシグネチャ(たとえば input スキーマからフィールドを削除)を変更した場合、古い会話ではモデルが古いペイロードを送り続け、新しいバックエンドはそれを拒否します。その結果として 400 エラー、チャットでの奇妙な応答、そしてとても悲しいユーザーが生まれます。
このため、ChatGPT App における「リリース」は「新しい Docker をデプロイする」だけではありません。複数レイヤーを調整して変更し、バージョン、フィーチャーフラグを丁寧に管理し、かつロールバックできるようにすることです。
2. バージョン行列: そもそも何をバージョニングするか
「App のバージョンは 1.3」とだけ見るのではなく、レイヤーごとのバージョン行列で見るのが有用です。GiftGenius の例はおおよそ次のとおりです。
| レイヤー | 何をバージョニングするか | 値の例 | 保管場所 |
|---|---|---|---|
| App / Next.js | コードとビルド | |
package.json、Git タグ |
| MCP / API インターフェイス | ツールのセットとそのスキーマ | |
コード内の定数、アノテーション |
| System / prompts | System プロンプト、ヘルパー、例 | |
個別ファイル+メタデータ |
| Commerce / ACP / feed | プロダクトフィードと ACP 契約のフォーマット | |
データリポジトリ内のスキーマ |
これは別々の軸です。アプリケーションのバージョンを 1.4.2 にリリースしても、tools スキーマは v1.7 のままで、プロンプトは v3.2 に上がる、といったことが起こり得ます。ログにこれが見えるようにしておくと便利です。あとから「prompt v3.2 の後になぜチェックアウトのコンバージョンが落ちたのか」を追いやすくなります。
コードにはセマンティックバージョニング(SemVer)MAJOR.MINOR.PATCH を使うのが便利です。MAJOR は破壊的変更、MINOR は後方互換のある新機能、PATCH はバグ修正。インターフェイスのバージョン(スキーマ、プロンプト)でも似た考え方で、MAJOR は破壊的変更を示します。
特にツールのインターフェイスバージョンは分けて考えましょう。LLM にとって極めて重要です。ツールの契約を変更したのに、モデルが「契約は旧版だ」と思い込んでいると、混乱が起きます。よってポリシーとしては、MINOR リリースでは新しい任意(オプション)フィールドの追加のみで、既存のフィールドの改名/削除は行わない。破壊的変更は新しいツール、例えば suggest_gifts_v2 を通じて行います。
レイヤーごとにバージョンを分離できたので、これらのバージョンが dev から prod までのリリースサイクルの中でどう「生きる」のかを見ていきましょう。
3. GiftGenius の基本的なリリースフロー
まずフェーズについて合意しましょう。すでに環境(デプロイの章)をお持ちでしょうが、ここではそれらをリリースの観点で見ます。
一般には次を分けます。
- dev — ローカル開発、ChatGPT の Dev Mode、トンネル;
- staging — 可能な限り本番に近い: 同じ種類の DB、同じ種類の MCP/ACP、ただしテストデータと決済は sandbox;
- prod — 本番環境。Store 掲載/本番 ChatGPT App が接続されている先。
プロセスの例:
flowchart TD A[Dev: ブランチ feature/*] --> B[PR → main] B --> C[CI: unit + contract + lint] C --> D[Deploy to Staging] D --> E[Smoke/E2E + 手動確認] E --> F[Deploy to Prod] F --> G[メトリクスとログを監視]
各ステップに「安全装置」を入れます。dev 環境では開発者は何でもできますが、main へのマージごとに CI が走り、unit/contract テストを実行します。問題なければ自動で staging にデプロイ。staging では E2E/スモークの短いセットを回します。たとえば、ギフト提案のフルフロー1本と、テスト決済での「偽の」チェックアウトです。
その後で prod にデプロイボタンを押します。できればバージョンの注記と CHANGELOG へのリンク付きで。本番では p95、エラーレート、ビジネスメトリクス(レコメンドからチェックアウトへのコンバージョン)を引き続き監視します。リリース後に何か落ちたら — 後述のとおり、明確なロールバック計画が必要です。
4. リリースノートと changelog: 何をどう記録するか
リリースノートがないと、1か月後にグラフを見ながら「3週間前になぜチェックアウトの p95 が2倍になった?」に対して「分からない。でかいリリースはあったけど」と答える羽目になります。良い戦略ではありません。
最低でも2種類のノートがあります。
内部向け changelog — 技術的な記録。開発者、SRE、コードを触る人のためのものです。どの機能が追加され、どのバグが直り、破壊的変更があったかなどを書きます。形式は Keep a Changelog の定番を使えます。セクションは Added、Changed、Fixed、Removed など。
外部向けリリースノート — ユーザーと Store 向けの読みやすいノート。ここでは「migrate MCP SDK 0.4 → 0.5」のような話は不要で、「サンクスギビング向けのおすすめを追加」「一部のギフトがカートに入らない稀な不具合を修正」のように書くとよいです。
GiftGenius の CHANGELOG.md の一部例:
## [1.4.0] - 2025-11-20
### Added
- 新しいツール `suggest_gifts_v2`(興味タグに対応)を追加。
- 新しい system-prompt の A/B テストを開始(flag: GG_PROMPT_V3)。
### Changed
- ACP チェックアウトのエラー処理を改善(より親切なメッセージ)。
### Fixed
- 画像のない商品がおすすめから隠れてしまう不具合を修正。
プロンプトのバージョンもどこかに併記しておくと有用です。たとえば system プロンプトファイルのコミットハッシュを残し、後で「なるほど、b3f9c2d のプロンプト以後、ユーザーが『購入』を押す頻度が下がった」などと振り返れます。
5. SDK と仕様の移行: 変化の速いエコシステムでどう生きるか
Apps SDK、MCP、Agents を取り巻くエコシステムは速く進化しています。新しい SDK、MCP プロトコルの変更、ACP の更新など。これは良い(できることが増える)一方で、壊れるのも早いという痛みがあります。
バージョンの固定(ピン止め)と「リリース当日にアップデートしない」
最初の簡単な推奨は依存関係を固定することです。^0.4.0 ではなく 0.4.0。とりわけ API を頻繁に壊す SDK(MCP/Agents/Apps SDK)では重要です。このテーマに関する調査でも「SDK fatigue(SDK 疲労)」が指摘されており、可変バージョン(^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 にデプロイ、もう一度スモークテストを回し、それから本番へ。
もしダメならロールバックは明快です。このブランチをマージしない(あるいはマージを戻す)だけ。SDK 移行とプロダクトリリースを分ける意義はここにあります。
ツール/ API スキーマの移行
最も厄介なのはインターフェイス変更です。モデルはそれを即座に認知しません。この領域の調査でも強調される重要ルールは「拡張はしても破壊しない(additive‑only)」。もし suggest_gifts に新しい引数 interests: string[] を追加するなら、それを必須ではなくオプションにします。そうすれば従来のシナリオは動き続けます。
Zod スキーマの進化例(入力用):
// 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 は非推奨にして徐々に停止し、モデルとユーザーの移行が完了したら完全に外します。
データ移行: プロダクトフィードと ACP
SDK とツールスキーマの移行について述べましたが、プロダクトフィードも契約です。SKU、通貨、ローカライズのフォーマットを変えるなら、連携して行う必要があります。フィードのスキーマ、MCP/ACP での処理、前処理すべてを更新します。ChatGPT App のコマース系ドキュメントでも、フィードのエラー(壊れたフィールド、重複、奇妙な価格)は、コードが完璧でもレコメンド品質を損なうと指摘されています。
典型的な進め方:
- まずフィードスキーマに新フィールドをオプションで追加し、GiftGenius が存在する場合にそれを使えるようにする。
- 次にフィードを生成するパイプラインを更新し、そのフィールドを埋め始める。
- フィードのバリデータ(データに対する契約テスト)を回し、その後でのみロジックがそのフィールドに依存するよう切り替える。
6. フィーチャーフラグ: デプロイとリリースを分離する
フィーチャーフラグ(feature flags)は、ChatGPT App の世界で生き残るための重要ツールのひとつです。基本アイデアは簡単。コードはデプロイしても、新機能はすぐには有効化しない。最初は「フラグの下」に置く — 開発者のみ、1% のユーザーのみ、あるいは完全に無効。
特に次のようなときに重要です。
- 新しいレコメンドアルゴリズムをロールアウトするとき;
- system プロンプトを変更するとき(モデルの振る舞いが大きく変わり得ます);
- 高コスト/低速なツールを有効化するとき;
- 新しいチェックアウトフローを試すとき。
環境変数による簡易フラグ
最小構成では、環境変数でフィーチャーフラグを実装できます。
// lib/features.ts
export const isNewRecoEnabled =
process.env.NEXT_PUBLIC_GG_NEW_RECO === "1";
MCP ツール側のコードでの使い方:
if (isNewRecoEnabled) {
return runNewRecoAlgorithm(input);
}
return runOldRecoAlgorithm(input);
利点はシンプルさ。欠点は、ランタイムでの切り替えがやや難しいことです。新しい環境をデプロイするか、少なくとも再初期化が必要になります。
コンテキスト対応の集中ヘルパー
もう少し成熟したやり方は、グローバルフラグだけでなく、ユーザーのコンテキスト(テナント、セグメント、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 {
// ここにロジックを書く: env, DB, 外部フラグサービス など
if (feature === "new-reco") {
return process.env.GG_NEW_RECO === "1";
}
return false;
}
MCP ハンドラーで:
const enabled = isFeatureEnabled("new-reco", { userId });
const result = enabled
? await runNewReco(input)
: await runOldReco(input);
将来 LaunchDarkly や Statsig などの外部フラグサービスを接続する場合でも、isFeatureEnabled の実装を差し替えるだけで済み、全コードを変える必要はありません。
GiftGenius のシナリオ例
system プロンプトの A/B テスト。 PROMPT_V2 を作成し、特定リストの tenantId を持つユーザーの 10% にのみ有効化します。MCP ツールやウィジェットは変更せず、コンバージョンを比較します。
高コストなツール用のキルスイッチ。 例えば高価な外部 ML レコメンドモデルに依存するツールを作ったとします。その外部サービスが落ちたり急に高騰した場合、GiftGenius 全体を止めずに一瞬で無効化したい。フィーチャーフラグが最も簡単です。
新しいチェックアウトフローの段階的ロールアウト。 Checkout v2 をまず社内と数社の信頼できる顧客にだけ有効化。メトリクスに問題なければ、対象を広げ、最終的に全員に有効化します。
7. ロールバック: 何かが燃えているとき、素早く戻すには
完璧なテストやフラグがあっても、何かは壊れます。重要なのは、エラー急増やメトリクス低下を検知してから最初の 5–15 分で何をするかの明確な戦略を持つことです。
コードの迅速なロールバック
問題が純粋な技術的バグ(NPE、変数ミス、外部サービスの URL 誤り)であれば、通常は前のバージョンにデプロイを戻すだけで十分です。例えば Vercel には直前のデプロイメントへの瞬時ロールバックがあります。
課題は、どのデプロイメントがどのバージョンに対応しているか、そしてどう戻すかを常に把握しておくこと。理想的には on‑call 向けの README に記載します。「リリース 1.4.0 後に燃え始めたら、deployment X にロールバック」など。
もうひとつの即効レバーはフィーチャーフラグです。新機能(checkout-v2)だけが落ちている場合、リリース全体を戻すより、そのフラグを切る方が簡単です。
危険な変更: マニフェストとスキーマ
マニフェストとスキーマはより厄介です。もし誤ったツールスキーマの新マニフェストを Store にアップロードし、OpenAI に承認されてしまったら、ロールバックに数日かかることがあります。理由は簡単で、マニフェストの各変更も OpenAI の審査を通るためです。各種 Store の分析でも、マニフェストとスキーマの変更は「危険な」リリースであり、特に入念な準備とテストが必要だと明記されています。
したがって次を分けるのがよいでしょう。
- 安全なリリース: MCP/Next.js のコード変更、プロンプト修正、フラグの後ろに隠した新機能;
- 危険なリリース: ツール一覧、input/output スキーマ、ACP 契約の変更。
「危険な」リリースは、追加の検証を行い、可能ならまず Dev Mode と staging App だけで(Store 公開なしに)段階的に展開します。
データとフィードのロールバック
データはさらに難しく(そして痛い)です。もし新スキーマへプロダクトフィードを移行する際に旧フィールドを削除した場合、元に戻すのは容易ではありません。よってデータ移行は冪等かつ可逆にすべきです。古いコピーを保持するか、2段階移行(まずデータを複製し、その後で読み取りを切り替える)にします。
シンプルな方法は、旧フィールドと新フィールドをしばらく併存させ、フィーチャーフラグでどちらを読むかを切り替えられるようにしておくことです。問題があれば旧読み取りに戻すだけです。
8. GiftGenius のためのミニ設計: リリースプロセス
ここまで(バージョン、SDK 移行、フィーチャーフラグ、ロールバック)をひとつの実践シナリオにまとめましょう。
リリース 1.4.0 を作るとします。内容は:
- 拡張された input を持つ新ツール suggest_gifts_v2 を追加;
- 一部ユーザーに新しい system プロンプトを有効化;
- MCP SDK を 0.3 → 0.4 に更新;
- プロダクトフィードのフォーマットを変更(tags フィールドを追加)。
妥当な計画は次のようになります。
まずは別の技術リリース 1.3.1 を出す: MCP SDK 更新+最小限のコード修正のみ。スキーマや機能の変更はなし。CI、staging、スモークテストを回します。安定していれば数日そのまま運用。
次に feature/reco-v2 ブランチ。ここで suggest_gifts_v2 を新ツールとして追加(旧 suggest_gifts は残す)。その input スキーマは新しいオプションフィールドの追加のみで拡張。新しい system プロンプトを準備しつつ、フラグ GG_PROMPT_V3 の背後に置く。ACP/フィードでは新フィールド tags を任意として追加し、未存在でも動く読み取りにしておく。
CI にいくつかの新しい契約テストを追加: suggest_gifts_v2 が旧/新のペイロードを受け付けること、tags を含むフィードが妥当であること、tags のない旧レコードでもサーバーが壊れないこと、など。
main にマージした後:
- CI が unit/contract を実行;
- staging で新ツール経由の E2E シナリオを 1–2 本回す;
- テスト用テナントに対してのみ、フラグで新プロンプトと新ツールを有効化。
メトリクス(ツールの p95、エラーレート、チェックアウトへのコンバージョン)を観測。問題なければ対象を拡大。十分に安定を確認してから、必要に応じて Store 向けのマニフェスト更新とアプリのプロモ説明を更新します。
どこかで壊れたら — どう戻すかは分かっています。フラグを切る、デプロイをロールバックする、あるいは最終手段としてマニフェストのバージョンを戻す(ただし、これはそもそも発生させないのが最善)。
9. ChatGPT App のリリースプロセスでよくあるミス
ミス1: 単一の「App バージョン」で済ませ、バージョン行列にしない。
「GiftGenius v1.4」しかなく、tools スキーマ・プロンプト・プロダクトフィードのバージョンがどこにも記録されていない状態では、「どの変更の後にチェックアウトが落ちたのか」に答えられません。レイヤーごとにバージョンを分け、構造化ログに出力しましょう。
ミス2: ツールの破壊的変更を新しい名前/バージョンなしで行う。
最悪のパターンは、input スキーマのフィールド名変更や削除をツール名を変えずにやってしまうこと。古いチャットではモデルが古いペイロードを送り続け、バックエンドは 400 を返し、GPT はハルシネーションを起こし、ユーザーは混乱します。破壊的変更は必ず新しいツール(foo_v2)や新しい API バージョンで行い、旧インターフェイスは移行期間は残しましょう。
ミス3: 機能開発の「ついで」に SDK を更新する。
ありがちな話: 新機能を追加するついでに @modelcontextprotocol/sdk を 0.3 から 0.5 に上げて「最新にしておく」。結果として、壊れた原因が新コードなのか、新スキーマなのか、新 SDK なのか分からなくなります。SDK 移行は、テスト計画とロールバック手段を明確にした技術リリースとして切り出しましょう。
ミス4: フィーチャーフラグや即時のキルスイッチがない。
新しいレコメンドアルゴリズムを 100% のユーザーにいきなり展開するのは、うまく動く間は楽しいですが、奇妙な結果を出したり外部サービスを落としたりすると地獄です。フラグがなければ、唯一のレバーはリリース全体のロールバックで、無害な改善まで巻き戻してしまいます。最低限、環境変数や小さなコンフィグでフラグを用意しましょう。
ミス5: マニフェストが「即時更新」されると期待する。
よくある誤解は、tools や openapi.yaml を変えたら、モデルがすぐ新スキーマを認識するというもの。実際にはマニフェストとツールの記述はキャッシュされ、既存チャットでは長く生きます。これを無視すると、同じ機能が新規チャットでは動き、古いチャットでは落ちる、といったバグに繋がります。スキーマ変更はこの挙動を前提に計画し、Store に出す前に Dev Mode と staging で検証しましょう。
ミス6: 明確なロールバック計画とリリースドキュメントがない。
チームの誰も「5 分でどうやってロールバックする?」「どのバージョンに戻す?」「旧フィードスキーマへどう戻す?」に答えられないなら、ロールバックは存在しないも同然です。オンコールには短くても具体的な手順が必要です。どのボタンを押し、どの変数を変え、どこを見て成功を確認するか。
ミス7: リリースノートなし、メトリクスとの紐付けなしの「無言リリース」。
changelog すら書かないリリースを続けると、数か月後には推測ゲームをすることになります。p95 が突然上がり、コンバージョンが落ちたとき、「そのとき何が変わった?」が分からないのです。最低限のリリースノートを書き、デプロイ日やバージョンと紐づける習慣は、品質監査だけでなくチーム全体の負担を軽くします。
GO TO FULL VERSION