1. Multi‑App シナリオとは何か、なぜ必要か
これまで私たちは、GiftGenius を特定のチャットにおける単一の外部アプリとして見てきました。ユーザーが一覧からあなたの App を選び、ChatGPT があなたの tools を起動し、あなたが物語の「主役」になる、という前提です。ですが実際の Store では違います。ユーザーは複数の Apps を同時に接続でき、ChatGPT は特定のリクエストに対してどのアプリを呼ぶべきかを判断します。
たとえば、1 つのチャットに次のような App が同居しているかもしれません。
- 同僚の誕生日を把握している社内カレンダー App
- ギフトのアイデアを提案する GiftGenius
- 注文と決済ができる会社の commerce‑App
ユーザーが「同僚の誕生日を思い出させて、贈り物の提案と購入方法も教えて」と書いたとします。モデルは順番に 3 つの App を呼ぶかもしれません。1 つ目はカレンダー、2 つ目はギフト提案、3 つ目はチェックアウトです。
重要なポイントはこれです。ユーザーには「App 2 を呼んで、HTTP エンドポイントはこれ」というボタンはありません。自然言語で話しかけ、ChatGPT がルーターとして働きます。すべての利用可能なアプリの descriptions とメタデータを読み、誰をいつ呼ぶかを決めます。
ここから 3 つの重要な考えが導かれます。
- コンテキストの奪い合いがある。あなたの App は、説明文、名前、ふるまいに基づいて何十ものアプリの中から選ばれなければなりません。
- メタデータが「LLM 向けの SEO」になる — モデルが必要なタイミングで GiftGenius に気づくか、スルーするかはここで決まります。
- 相互運用性を考える必要がある。あなたの応答はチャット中の人間だけでなく、同じコンテキストを読む他の App にとっても有用であるべきです。
要するに、孤立した ChatGPT App を、より大きなシステムのコンポーネントへと変えていくのです。
2. モデルはどう App を選ぶか: ルーティングのメンタルモデル
Multi‑App シナリオでのルーティングは概ね次のように動きます(かなり単純化していますが、開発の助けになります)。
- ChatGPT は利用可能な App の一覧と、各 tools のメタデータ(名前、説明、JSON Schema のパラメータ、アノテーション、_meta)を持っています。
- ユーザーがメッセージを書きます。
- モデルは意図(intent)の内部表現を作り、descriptions(tool とアプリの)に対してセマンティック検索を行い、どのツールが妥当かを判断します。
- 条件が一致すれば、ツールを呼び出すか、App を開く提案をします。
ここで重要な注意点があります。descriptions は十分に区別できる(判別的な)ものであるべきです。「商品の検索」という表現は「ギフトの検索」や「本の検索」と大差ありませんが、「GiftGenius のパートナー基盤に基づくギフトアイデア検索」のように書けばドメインが大きく絞られ、ギフト関連のリクエストであなたのツールが選ばれる確率が高まります。
2 つ目のポイント — 名前の衝突は避けましょう。get_data のような名前のツールは、何十もの App が存在する世界では何も伝えませんが、giftgenius_get_gift_catalog ならはるかに明確です。特に、明快な description と組み合わせればなおさらです。
そして最後に、モデルはコンテキストにも依存します。チャット内で既に「ギフト」「誕生日」や GiftGenius の名前が言及されていれば、ルーターの目にあなたの App が強調されます。
3. メタデータと descriptions は LLM‑SEO
メタデータを「JSON の必須項目」程度に捉えるのではなく、プロダクトのコピーライティングだと考えるのが有効です。公式の推奨も「treat metadata like product copy(メタデータは製品コピーとして扱う)」および「one job per tool(1 ツール 1 役割)」を明言しています。
大まかにいくつかの説明レベルを区別できます。
| レベル | 対象 | 記述内容 |
|---|---|---|
| Manifest description | 人間 + モデル | App 全体のタスク: チャットに組み込む目的 |
| Tool description | モデル(ルーティング) | 特定のツールをいつ、どんな課題で使うか |
| Parameter descriptions | モデル(スロットの充填) | 引数の埋め方、許容値 |
|
モデル(UI) | ウィジェットに何が表示され、モデルのテキスト回答で重複させるべきかどうか |
widgetDescription はウィジェット前提の世界で特に重要です。モデルはあなたの React コードを「見て」いません。渡される props とその意図だけを知っています。ここを適切に埋めると、モデルが勝手に作文するのではなく、表示済み UI を前提にテキスト回答を調整してくれます。
Apps SDK のドキュメントは「ChatGPT はメタデータに基づいて、あなたのコネクタ(App)をいつ・どのように呼ぶかを決める」と強調しています。丁寧な descriptions とパラメータのドキュメントは、リコール(モデルがあなたの App を思い出す状況の割合)を高め、誤呼び出しを減らします。
ミニ例: GiftGenius の旧 description と新 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 がすでに名前と価格を見せるとモデルが理解するため、テキスト回答は一覧の重複ではなく、解説や助言に集中できます。
- 他の App(モデル経由)も、toolOutput が単なるテキスト段落ではなく、別アプリのコンテキストで「引き継げる」構造化リストだと理解できます。
正直に言えば、こうした説明を書くのはコーディングより退屈に感じるかもしれません。しかし、それが後でモデルの不可解な挙動をデバッグする時間を大幅に節約してくれます。
5. ツールのアノテーション: readOnlyHint、destructiveHint、openWorldHint
Multi‑App の世界では、「いつ呼ぶか」だけでなく、特定のツールを呼ぶことがどれだけ安全かも重要です。そのために、Apps SDK はツール記述子にいくつかのアノテーションを導入しています。
考え方はこうです。アノテーションは、操作の性質についてモデルに与えるソフトなヒントです。サーバー側の認可の代わりにはなりませんが、チェーンの中で ChatGPT がどう振る舞うかに強く影響します。
短い要約(概念的に):
| アノテーション | 意味 | モデルの典型的なふるまい |
|---|---|---|
|
データを変更しない | 余計な確認なく、頻繁に呼び出してよい |
|
状態を変更する(購入、削除) | 呼び出し前にユーザーへ確認を求める |
|
「外の世界」(検索、Web)へアクセス | 結果の量と品質により慎重になる |
アノテーション(readOnlyHint、destructiveHint、openWorldHint)は標準的なツール記述の一部であり、ChatGPT に限らず利用できます。_meta["openai/isConsequential"] はより ChatGPT 特化のシグナルで、「安全な」呼び出しか「結果を伴う」呼び出しかの判別をさらに助けます。
GiftGenius の 2 つのツールを見てみましょう。
- 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
}
};
ここでは明確にシグナルしています。これは書き込み操作であり結果を伴う(お金が引き落とされ、注文が作成される)。特に複数の App が絡む長いチェーンの中では、呼び出し前にユーザーの確認が必要です。
魔法を過信しないことも大切です。destructiveHint があっても、サーバー側で入力値・トークン・権限を検証する義務は残ります(セキュリティと認可のモジュールで扱いました)。しかし、Multi‑App オーケストレーションの観点では、アノテーションがモデルに「無闇に撃たない」判断を促します。
6. 単独の App からエコシステムへ: GiftGenius の境界を明確にする
GiftGenius がチャットの唯一の App だった頃は、スコープを広めに取っても問題ありませんでした。ギフト選び、ラッピングの助言、記念日のリマインド、ちょっとしたお祝いメッセージの文案まで。モデルは結局あなたのツールしか呼びませんから。
しかし、Multi‑App シナリオでは「何でもやる」アプローチは害になります。
- ルーターが「いつあなたが最適か」「他の App が良いか」を見分けにくくなる。
- カレンダー、一般的なタスク管理、ファイナンス・プランナー等と領域が重なる。
- 複数アプリの同時利用で、モデルが「間違った実行者」を選びツールを取り違えることがある。
ベストは責任範囲をはっきりさせることです。
- GiftGenius: ギフトのアイデアに特化 + ACP/Checkout による購入支援
- CalendarApp: 予定とリマインダー
- Finance‑App: ユーザーの全体予算、個人の資金計画
App やツールの説明では「Use this when…」だけでなく「Do not use when…」を明示するのが有効です。公式の discovery プレイブックでも推奨されています。
ツール説明のミニ例:
description: `
Use this tool when the user explicitly asks for gift suggestions.
Do not use for generic product discovery or price comparison.
`
こうした制約はルーティングを助けるだけでなく、製品や QA 観点でも App の挙動を予測しやすくします。
7. Apps のコンポジション・パターン: pipeline、handoff、shared context
Multi‑App シナリオでは実務上、次の 3 つの関連概念がよく現れます。
- pipeline — 複数の App が順番に進む(カレンダー → ギフト → commerce)。各アプリが自分のステップを実行。
- handoff — ある App の出力が次の App の入力になる。
- shared context — この受け渡しは、アプリ間の直接 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": "アレクセイ・ビコフ", "date": "2025-11-22", "relation": "manager" } ] -
次にモデルは GiftGenius を呼ぶべきと判断します。カレンダーから得た引数で suggest_gifts を呼びます。
{ "recipientName": "アレクセイ", "occasion": "birthday", "budget": 150, "relationship": "manager" }GiftGenius のウィジェットがギフトのカルーセルを表示し、テキスト回答はなぜそのアイデアが適切かを説明します。
-
ユーザーが 1〜2 個を選択(ウィジェットのボタン → widgetState)し、モデルは commerce‑App のツール(例: corp_checkout.create_gift_order)を、選択された SKU の ID と配送先住所で呼びます。
ChatGPT の視点では 3 つの別アプリですが、ユーザーには 1 つの会話です。これを機能させる鍵は次のとおりです。
- 各 App のツールに対する明確な descriptions
- 丁寧な名前付け(corporate_calendar.list_upcoming_birthdays のように。単なる list_events ではない)
- 合意した構造化データの形式(ギフトのアイデアを commerce‑App が理解できる形で記述する)
ビジュアル図
このパイプラインは次のように描けます。
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": "アレクセイ",
"occasion": "birthday",
"purchaseUrl": "https://shop.example.com/sku_123"
}
]
}
GiftGenius のウィジェットはこれを props として受け取り、カードをきれいにレンダリングします。commerce‑App は、この JSON をコンテキストで見て id と purchaseUrl を拾い、以降のチェックアウトに利用できます。
Multi‑App の実践から言えるのは、優れた App は人間の目だけでなく、他の App が「食べられる」形でデータを返すということです。
9. GiftGenius を Multi‑App に合わせて実用的にリファクタリング
学習用アプリに対して、いくつかの具体的変更に落とし込みましょう。
manifest‑description を明確化
Next.js テンプレート等の openai-app.json に以下の説明があるとします。
{
"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": `
ギフトのカード一覧を、説明・価格・「購入」ボタン付きで表示する。
モデルは全文リストをテキストで繰り返さず、選定理由のコメントや意思決定を助ける説明に集中すること。
`
}
};
これにより、ウィジェットに 10 個のギフトが見えているのに、チャットで長文の羅列をしがちな傾向が和らぎ、説明とロジックに集中します。UX にもトークンコストにも有益です。
10. 将来のプロダクトに向けた Multi‑App 的思考
「すべての競合に勝ってユーザーの唯一の App になる」という発想から、「大きなエコシステムの中で自分の App を最良のモジュールにする」という発想へ、頭を切り替えることが重要です。
このアプローチには実務上の利点がいくつもあります。
- ユーザーや Store のレビュアーに、あなたの App がなぜ必要で、いつ適切かを説明しやすい。
- モデルのルーティング判断が容易になり、取り違えや誤呼び出しが減る。
- コンポジションを意図的に設計できる。今日はカレンダーや commerce、明日は社内 HR ボットや社内 CRM と。
公式の discovery ガイドは、「one job per tool」を設計し、メタデータを静的な初期テキストではなく、テストと更新を続ける「生きたアーティファクト」として扱うよう強調しています。
モジュール 20 の以前のテーマで既に行ったゴールデンケース、LLM‑evals、CI 実行が大いに役立ちます。チャットに GiftGenius と CalendarApp が同居するシナリオをケースに加え、説明文の変更が App の選択や回答品質にどう影響するかを継続的に観測できます。
11. Multi‑App とコンポジションのよくある失敗
失敗 1: App 説明が「何でもできる」。
manifest‑description に「あらゆるタスクの賢いアシスタント」のように書くと、他の App どころかベースの ChatGPT とも競合します。ルーターは「必ずあなたを呼ぶべき場面」と「内蔵機能で十分な場面」を区別しづらくなります。Multi‑App の世界では、用途が明確で狭いアプリ(「ギフト選定」「カレンダー管理」「ログ分析」)が勝ちます。
失敗 2: ツールの description が曖昧で、名前が衝突する。
get_data、process_request といった名前で「ユーザーのデータを処理する」とだけ説明するようなツールは、モデルを混乱させるのに最適です。複数 App の世界では、全く別ドメインでもあなたのツールが呼ばれかねません。正解は、ドメインとアクションを結び付けること(giftgenius_get_gift_catalog、calendar.list_birthdays)と、「Use this when… / Do not use when…」を明記することです。
失敗 3: _meta["openai/widgetDescription"] を無視する。
開発者はしばしば description だけを埋め、_meta はローカライズのために思い出す程度です。その結果、モデルはウィジェットが何を表示するか理解できず、UI をテキストで重複させたり、逆に「価格表を表示する」と約束してしまったり(実際のウィジェットにはない)。widgetDescription に 2〜3 行書くだけで、こうした行き違いの多くを防げます。
失敗 4: readOnlyHint/destructiveHint の欠如。
すべてのツールが一様に「中立」に見えると、モデルはどれが頻繁に安全に呼べ、どれがユーザー確認を要するかを区別できません。複数 App による多段シナリオでは特に致命的です。ユーザー関与なしに書き込み操作が続けざまに走る可能性があります。read‑only ツールの明示や、結果を伴う操作の強調を忘れないでください。
失敗 5: 人間だけを対象にした応答で、他の Apps を考慮していない。
ツールから「ギフトの一覧」を 1 本のテキスト行で返したくなりますが、そうすると他の App がその結果を利用しづらくなります。id、price、currency、purchaseUrl、occasion のような明確なフィールドを持つ構造化 JSON は、UI にもコンポジションにも利点があります。モデルは自然言語のパースなしに、他ツールの引数へこれらの値を差し込めます。
失敗 6: 1 つの App の中で、ユーザーに必要になりそうなものを全部実装しようとする。
「GiftGenius を作ったのだから、カレンダーもメール送信も予算計画も」となりがちです。孤立世界ではまだ許容できても、Multi‑App 文脈では、他の特化型 App と衝突する「なんでも屋」になります。自分と約束しましょう。私の App は X をやる、しかも完璧に。他は他者の責任。こうした設計は、UX とエコシステムの発展を大いに簡素化します。
失敗 7: 他の App が同居する環境でのテスト不足。
開発者はしばしば Dev Mode の「クリーン」チャット(他の App がいない)でだけ自分のアプリをテストします。しかし Store のユーザーは、概念的にあなたと重なるアプリを含め、10 個ほど接続しているかもしれません。面倒でも、隣接 App(カレンダー、汎用ショッピング、ファイナンス)が同居するテストシナリオを作り、ゴールデンケースを回しましょう。ギフト系のリクエストでモデルが正しく GiftGenius を選び、他と取り違えないかを確認します。
GO TO FULL VERSION