1. はじめに
ふつうのウェブサービスを作っていると想像してください。仕組みは単純です。URL /search があり、ユーザーがボタンを押すと、あなたはコントローラー searchController を呼び出します。 ChatGPT Apps の世界では、ユーザーは /search をまったく見ません。ユーザーは人間の言葉でこう書きます:
«ゲーマーの兄に 50$ 以内のギフトを選んで»
そして次のように進みます:
- モデルがこう判断します: 「これはギフトの話だ。GiftGenius がそれを得意としている」;
- GPT が自動で「ボタンを押す」——あなたの App のツールを呼び出します;
- 場合によってはユーザーに「GiftGenius を開いて候補を表示しましょうか?」と提案します。
重要な転換点はこれです: ユーザーは意図を表明し、ボタンを押すのはモデル。 このフローを理解していないと、簡単に次のような事態になります:
- 意味のない名前のツール(run_func、doStuff など)を書いてしまう;
- モデルから 一度も提案されない、もしくは場違いに呼び出される App になってしまう;
- 会話を壊す「唐突に現れる」ウィジェットを作ってしまう。
そこでこの講義では、GPT がどのようにあなたの App を見つけ、会話のどの地点でそれを「組み込む」のかというメンタルモデルを構築します。
Insight: アプリは ChatGPT のプラグインである
スマホのアプリや WeChat の mini‑app と違って、ChatGPT のアプリは仕組みが異なります。
ChatGPT は いつあなたのアプリを起動し、そのどの機能を呼び出すかを自ら決めます。 ChatGPT のアプリは(制約はあるものの)チャットのロジックに積極的に関与できます。 主目的かつ強みは——ChatGPT 自体の能力拡張です。
ChatGPT がユーザーの問題を完全に解決できるなら、あなたのアプリを呼ぶ必要はありません。 ChatGPT がまったく解決できない問題なら、そもそもユーザーは質問しないでしょう。 理想的なのは、ChatGPT がユーザーのリクエストを「部分的にしか」解決できない状況です。 つまり、ニーズはある、量も多い、ただし結果は不十分、ということです。
まさにそのとき ChatGPT はあなたのアプリを呼び出し、両者の力でユーザーを満足させます。 ユーザーは幸せに、そしてあなたはより豊かになります。
2. ユーザー視点での ChatGPT App の起動方法
ユーザーには「MCP サーバーを呼んで call_tool」というボタンはありません。その代わりにテキスト入力欄と(ときどき)アプリメニューがあります。 ユーザー視点では、起動方法は大きく 明示的 と 暗黙的 の 2 つです。
明示的起動(explicit)
ユーザーが 意識してあなたの App を選ぶシナリオです。
典型例:
- ChatGPT の ストアで App を見つけて「開く」を押す;
- ランチャーから App を選ぶ(たとえば入力欄(Composer)の + ボタン経由);
- メッセージをアプリ名で始める: 「GiftGenius、ギフトを選んで…」 ——これは named mention と呼ばれます。 プロンプトの冒頭に App 名があると、ChatGPT は自動であなたの App を回答コンテキストに混ぜ込みます。
明示モードでは、モデルは最初から「ユーザーはこの App を使いに来た」と理解しています。したがって:
- GPT はあなたのツールを より頻繁かつ積極的に呼び出す;
- あなたの App の UI ウィジェットは初回の回答から表示されることがある;
- GPT が App を「無視」して自力で答える頻度は下がる。
よくある例: ユーザーはギフト選びで「遊んでみたい」ので GiftGenius を直接開きます。 リストから App を押すと、GPT は次のような挨拶を表示します:
«こんにちは!私は GiftGenius。ギフト選びをお手伝いします。誰に、どのくらいの予算で探しますか?»
その後、検索のためにあなたのツールを積極的に使います。
暗黙的起動(implicit / suggested)
まったく別のシナリオ: ユーザーは App のことを何も考えていません。普通のチャットでただこう書きます:
«母の誕生日用にギフトを選んで。園芸が好きで、予算は 100$ まで»
GPT はリクエストを解析して次を確認します:
- エコシステムに GiftGenius という App があり、そのツールは「ユーザーがギフトのおすすめを求めるときに使う」旨の説明を持っている;
- タスクや制約(ギフト、予算、興味)とこの App の適合度が高い。
この場合、モデルは控えめにこう提案するかもしれません:
«GiftGenius を使って具体的なギフト候補を探し、カード形式で表示できます。開きますか?»
ユーザーが同意すれば、GPT は必要な App のツールを呼び出し、ウィジェットをレンダリングすることもあります。
重要なのは、あなたがどこにも if (prompt.includes("ギフト")) openApp() といったコードは書かない、という点です。 判断は モデル自身が次の情報に基づいて行います:
- リクエスト本文と会話履歴;
- ツールのメタデータ(名前、説明、パラメータのスキーマ);
- ユーザーの App への接続状態(認証済みか、企業ユーザーか、など)。
あなたが影響を与えられるのはアルゴリズムではなく、モデルに対してあなたの App とツールがどう「記述」されるかです。
ハイブリッド: GPT が先に確認してから App を提案する
ときどきユーザーはとても抽象的に書きます:
«同僚に何か用意したいけど、全然アイデアがない»
モデルは GiftGenius が役立つと理解しますが、情報がぼんやりしすぎています。よくあるパターンはこうです:
- GPT が 1〜2 個の確認質問をテキストで行う。
- その後で App の起動を提案する: 「ギフト選びのツールがあります。開いて候補をお見せしましょうか?」。
これは良い UX です。ユーザーは「無理やり別アプリに連れて行かれた」とは感じません。
3. Discovery: GPT はどうやってあなたの App を見つけるか
ここからはモデルの視点でどう見えているかを確認します。
Apps SDK のドキュメントでは、これは Discovery と呼ばれます——ユーザーとモデルがあなたの App の存在を知るすべての経路です。 通常のチャットでの自然なリクエスト、アプリのカタログ、ランチャーのような特別なエントリポイントなどが含まれます。
モデルはあなたの App の存在をどう知るのか
登録時、ChatGPT はあなたの App を起動し、App は(MCP 経由で)自分の情報を伝えます。 つまり、利用可能なツールとそのスキーマ——名前、説明、入力パラメータの JSON スキーマ——を列挙します。 アプリの情報は登録時に指定し、ツールの情報は ChatGPT が MCP メソッド list_tools 経由で取得します。
モデルはあなたのソースコードを見ません。モデルに見えるのは次だけです:
- ツール名(name);
- 説明(description);
- 入力シグネチャ(inputSchema)。
これこそが 「モデルのための API」になります。 もしツールを run_func と名付け、説明が「Executes the function」だけなら、モデルはいつ呼べばいいか分かりません。 一方で suggest_gifts に 「Use this when the user wants gift ideas based on recipient, occasion and budget」 のような説明があれば、状況は一気に明瞭になります。
Named mention と in‑conversation discovery
Apps SDK の公式仕様は 2 つの主要メカニズムを挙げています:
- Named mention ——ユーザーがあなたの App 名でメッセージを始める場合。 このとき App はほぼ確実に起動され、回答に利用されます。
- In‑conversation discovery ——ユーザーが普通にリクエストを書き、モデルが App を接続すべきか判断する場合。 ここでは次が考慮されます:
- 会話のコンテキスト(メッセージ履歴、前回のツール結果、ユーザーの嗜好);
- テキスト内のブランドの明示的な言及;
- ツールのメタデータ——名前、説明、パラメータのドキュメント;
- 「リンク状態」——ユーザーが App に接続済みか(認証、必要なパーミッションの許可)。
開発者がこのプロセスに影響できるのは間接的に——良質なメタデータと UX パターンを通じてであって、コード内の if/else ではありません。
カタログとランチャー
会話以外にも、ChatGPT 内の ストアとコンポーザーからアクセスできる ランチャーがあります。 ユーザーはそこから、通常のアプリストアのように App を明示的に選べます。
GiftGenius のフローを考える際、概念的に重要なのは次です:
- あるユーザーはカタログから入り、最初から App の「内部」にいる;
- 別のユーザーはカタログを一切見ず、会話中の提案としてしか App に出会わない。
これらはすべて App 自体の discovery に関することです。モデルが「この会話であなたのアプリを起動してユーザーに提案すべきか」を判断する瞬間です。
4. 相互作用のサイクル解剖: ひと言からウィジェットまで
前回の講義にあったすべてのレイヤー——ChatGPT の UI とウィジェット、Apps SDK と MCP サーバー——を 1 つの分かりやすい論理サイクルにまとめましょう。
ハイレベルな図式
プロセスの観点では、サイクルは次のように進みます:
sequenceDiagram
participant U as ユーザー
participant G as ChatGPT (モデル)
participant A as App / MCP サーバー
U->>G: テキストのリクエスト
G->>G: リクエストの解析 + ツール選択
G->>A: ツール呼び出し (call_tool)
A-->>G: 応答(データ / structuredContent)
G->>U: テキスト応答 + (任意)App ウィジェット
平たく言えば:
- ユーザーが ChatGPT にメッセージを書く。
- モデルがリクエストと現在のコンテキストを解析して、次を判断する:
- 自力で答えるか、
- あるいは 1 つ以上のツールを呼び出すか。
- あなたの App のツールが選ばれた場合、ChatGPT は構造化リクエスト (call_tool)を作り、MCP サーバーに送る。
- あなたのバックエンド(または MCP サーバー)が処理を行う。DB や外部 API、ACP などにアクセスし、結果を組み立てる。
- 結果は構造化データ(場合によってはウィジェット用 JSON)として返る。
- モデルはそのデータを使って:
- ユーザーに分かりやすいテキストを生成し、
- 必要に応じて App のウィジェットを回答中に描画する。
「いつ何を呼ぶか」「確認質問をするか」「もう一度呼ぶか」といった多段の計画は、AI モデル側にあります。 Apps SDK と MCP はツールのための統一的なコントラクトを提供しているだけです。
どこで私たちはコードを書くのか
このサイクルで、あなたが実際に TypeScript/コードを書く箇所は 3 つあります:
- App とツールの構成——tools の記述(名前、説明、スキーマ)と App のメタデータ(名前、アイコン、カテゴリ)。 あなたのプロジェクトでは、おそらく openai/app-config.ts のようなファイルです。
- MCP サーバー / バックエンド——call_tool の処理。DB 参照、商品のフィルタ、他 API 呼び出し、など。
- ウィジェット(UI)——Next.js アプリ内の React コンポーネント。チャットに描画され、ツール結果を window.openai または Apps SDK のフック経由で読み取る。
それ以外はモデルとプラットフォームの仕事です。
5. GiftGenius 実践編: 2 つのユーザーフロー
フローを「目で見える」ように、より具体的なシナリオに進みます。
シナリオ 1: ユーザーが GiftGenius を明示的に開く
シナリオ:
- ユーザーが ChatGPT の App カタログを開き、GiftGenius を見つける。
- 「開く」を押す。
- ChatGPT は コンテキスト内で GiftGenius との対話を開始する。
会話はおおむね次のとおりです:
ユーザー:
カタログから GiftGenius を開く。
そしてこう書く: «こんにちは!友だちへのギフトを選びたい»
GPT:
«いいですね、ギフト選びを手伝います。誰向けか、予算はいくらか、どんな目的か教えてください»
この時点で GPT は、たとえば start_gift_session のような最初のツールをすぐ呼び、 あなたのバックエンドでセッションを初期化(仮のカート作成、sessionId 発行、など)するかもしれません。
MCP サーバー側のあなたのコードは(あくまで概念的に)次のようになります:
// 擬似的な future-TS の例: GiftGenius のツール記述
const suggestGiftsTool = {
name: "suggest_gifts",
description: "Use this when the user wants gift ideas by recipient, occasion and budget",
inputSchema: {
type: "object",
properties: {
recipient: { type: "string" },
occasion: { type: "string" },
budgetUsd: { type: "number" },
},
required: ["recipient", "occasion", "budgetUsd"],
},
};
MCP/Apps SDK への登録の詳細は別モジュールで扱います。ここで重要なのは次のアイデアです: この記述によって、モデルはそのツールが「ギフトの選定」リクエストに適していると理解すること。
ユーザーの回答のあと、GPT は suggest_gifts を呼び、あなたから候補の配列を受け取り、そのうえで:
- 要点をテキストでまとめ、
- GiftGenius のウィジェットを埋め込み、ギフトのカードをスワイプやフィルタできるようにする。
シナリオ 2: ユーザーが通常のチャットで「ギフトを選んで」と書く
次は別のパターンです。ユーザーは GiftGenius をまったく知りません。
普通のチャットでこう書きます:
«兄にギフトが必要。ボードゲームが大好き。最大 50 ドル»
ChatGPT の内部ではおおむね次が起こります:
- モデルがリクエストと利用可能なツール一覧を解析する。
- suggest_gifts ツールの説明が適合していると認識する。
- App GiftGenius がまさにこの種の課題向けだと理解する。
- そのユーザーがすでにアプリをインストール済みか、認証済みか、どのパーミッションを許可済みかを確認する。
その後の挙動は複数ありえます:
- リクエストが十分に具体的なら、GPT は黙って suggest_gifts を呼び、ウィジェット付きで返す;
- 必要な情報が欠けていれば(たとえば目的や年齢が不明)、まずテキストで確認質問をしてから App を提案する。
この柔軟さこそが、Apps を「厳格なフォーム主体の UI」と区別する点です。モデルは、いつツールを使い、いつ対話するかを自ら選びます。
6. セマンティック・ルーティング: 「LLM はディスパッチャ」
discovery の段階では、モデルは現在のリクエストに App を接続すべきかを決めます。 しかしその後、App が「起動」され、モデルがそのセッションでツールを把握している段階に入ると、次のレベルが動きます—— ツール内のセマンティック・ルーティングです。つまり、次の発話をどのツールで処理するかです。
古典的なウェブバックエンドでは、ルートは URL で決まります。たとえば /checkout なら checkout コントローラーを呼ぶ、といった具合です。 ChatGPT Apps には URL ルーティングはありません。その代わりにあるのが セマンティック・ルーティング。 モデルは、リクエストの意味とツールの説明を照合します。
単純化した流れはこうです:
- セッション開始時に ChatGPT はツール一覧(名前、説明、スキーマ)を取得する。
- これらのデータはモデルのシステム指示に組み込まれる。
- ユーザーがリクエストを書くと、モデルはその意味をツールの説明と比較し、 「ギフト選定」「ホテル検索」「グラフ作成」などを見分ける。
- 十分な一致があれば、必要なツールに対する構造化呼び出しを組み立てる。
ここから導かれる最重要の実践的な結論:
- ツールの説明はモデル向けの API そのもの。もう一度読み返してください。何度でも。
- 「does stuff」のように書けば、本当にモデルはいつ呼べばよいか分かりません。
discovery に関するドキュメントやベストプラクティスは強調します。メタデータはプロダクトコピーの仕事だと。 まさにそれが、どんな会話でモデルがあなたの App を想起するかを決めるのです。
7. App をめぐる会話パターン
ここでは、GPT が 1 つの会話内で App とやり取りするときに生じる典型的な UX パターンを見ていきます。 App を「真空」で作らず、GPT 側の役割を理解するために重要です。
Apps SDK の実践ガイドは、いくつかの特徴的パターンを挙げています:
「ウィザード」(The Wizard)
GPT がユーザーを段階的に導き、ときに App に大きく依存します。
GiftGenius の例:
- GPT: 「誰向けのギフトですか?」
- ユーザー: 「兄、25 歳。ボードゲームが好き。」
- GPT: 「予算は?」
- ユーザー: 「50$ まで。」
- GPT が suggest_gifts を呼び、結果をウィジェットで表示して「いくつか候補を出しました。以下のリストをご覧ください」と伝える。
このパターンでは、App とそのウィジェットは多段の対話に対するビジュアルレイヤーのような存在です。 ユーザーは主にテキストでやり取りし、ウィジェットは選択の可視化を助けます。
「アダプティブ・ウィジェット」(The Adaptive Widget)
テキストが主のチャネルであり続け、App は特別なタスクにピンポイントで接続されます。 たとえばグラフ作成、表の表示、商品のカード描画などです。
例:
- ユーザー: 「ギフトの候補を 3 つ比較して。ボードゲーム、書籍、体験型ギフト。」
- GPT はまずテキストで長所短所を説明する。
- その後、構造化された商品のリストを返すツールを呼び、小さな表やカードをレンダリングする。
ここでの App は補助的なビジュアルであり、「既定の作業モード」ではありません。
「見えないエージェント」(Invisible Agent)
App は UI をまったく見せないこともあります。裏方のデータソースとして働くのです:
- あなたは自社の DB からギフトを検索する MCP ツールを実装する;
- GPT はそれを呼び、リストを受け取り、ウィジェットなしで結果をテキストで要約して伝える。
これはクラシックな「UI なしプラグイン」に似ています。ユーザーは GPT が最新の価格や品揃えを知っていることだけを目にします。
このパターンは、UI が必須でない tool‑first な App に有用です。
8. フローが App デザインに与える影響
フローの理解は哲学だけでなく、きわめて実践的な判断にも効きます。 どのツールを用意するか、どう説明するか、いつウィジェットを出すか、テキストで答えたほうがいいときはいつか、など。
「chat‑first」の原則
エコシステムの核心的な考え方はこうです: チャットが主たるインタラクションであり、UI コンポーネントは補助。
つまり:
- 「丸ごとサイト」を 1 つのウィジェットに押し込まない;
- ウィジェットはチャットが不便なところ——リスト選択、フィルタ、比較、複雑なフォーム——を助けるべき。
GiftGenius なら:
- ギフトのリストを出し、ユーザーがカードを「つつける」ようにする;
- フィルタ(価格、カテゴリ、在庫)を可視化する;
- チェックアウトを分かりやすいステップで手伝う。
一方、「内向的な彼女へのギフトの選び方」のような長文の説明をウィジェット内で書くのは得策ではありません。そこはチャットの仕事です。
いつ App を起動し、いつ起動しないか
もうひとつの帰結: App を「会話ジャック」しないこと。
よくないパターン:
- ユーザーがまじめな議論をしている;
- App が勝手に起動して巨大なフルスクリーンウィジェットを無予告で開く;
- ユーザーが混乱する: 「私のチャットはどこへ?」
望ましいパターン:
- 最初はテキストで話を進め、いくつか確認事項を尋ねる;
- その後、本当に UX を改善できる場合にやんわり App を開くことを提案する(比較、構成、チェックアウトなど)。
ツールセットへの影響
モデルは 説明でツールを選ぶので、各ツールは次を満たすべきです:
- 1 つの明確なタスクを解決する;
- 「Use this when…」スタイルでよく説明されている;
- パラメータは、GPT がユーザーに尋ねるであろう質問から自然に導かれる。
GiftGenius なら、巨大な do_everything 1 本よりも理にかなっているのは:
- suggest_gifts ——候補リストの提示;
- get_gift_details ——単一 ID の詳細情報;
- create_order ——注文手続き。
ツール設計はモジュール 4 で詳しく扱いますが、ここでの要点はすでに重要です。 会話フローが、必要なツールの種類そのものを規定するのです。
9. ミニ例: ツールの説明がフローに与える影響(TypeScript のスケッチ)
仮想の openai/app-config.ts から小さな断片を示します。理論とコードを結びつけるためのものです。 SDK の正確な構文ではありません(次のモジュールで扱います)。今は名前と説明の考え方が重要です。
// GiftGenius の仮の設定断片(将来のコード)
const tools = [
{
name: "suggest_gifts",
description: "Use this when the user wants gift ideas based on recipient, occasion, and budget.",
inputSchema: {/* ... */},
},
{
name: "get_gift_details",
description: "Use this when the user asks for more information about a specific gift from a previous list.",
inputSchema: {/* ... */},
},
];
もし suggest_gifts を run_func に置き換え、説明を「Main function」にしてしまうと、GPT は次のようになります:
- どのリクエストでこのツールを呼ぶべきかの理解が悪化する;
- in‑conversation discovery であなたの App を提案する頻度が低下する可能性がある;
- ユーザーのフォローアップを、すでに表示したギフトリストと結びつけるのが難しくなる。
逆に、良い名前と説明は、必要な瞬間にあなたの App が想起される可能性を高めます。
10. ユーザーフロー設計でありがちなミス
ミス 1: 完全制御の期待——「App をいつ起動するかは自分で決める」。
開発者が「ギフトに関するリクエストを全部捕まえて、自分の App を接続しよう」と考えてしまうことがあります。ChatGPT Apps の世界は違います。決めるのはモデルです。モデルはツールの説明、会話コンテキスト、パーミッション状態、そして「なぜあなたの App を呼ぶとユーザーが満足するのか」を考慮します。
ミス 2: 意味のないツール名と説明。
run、main、tool1 のような名前と「Calls the main function」のような説明は最悪の組み合わせです。モデルは呼び時を理解できず、in‑conversation discovery はほぼ機能せず、あなたの App は「見えない」存在になります。「Use this when the user wants…」スタイルの良い説明と分かりやすい名前は、想像以上に重要です。
ミス 3: 1 つの App に「何でもかんでも」詰め込む。
もしあなたの App が「ギフト選び、ホテル予約、納税計算、猫の表示」を同時にやるなら、モデルはリクエストを安定してルーティングできません。公式の推奨と実践ガイドは「one clear job per tool/App」の原則を強調します。巨大なモノリス 1 つより、専門化された複数のアプリのほうが良いのです。
ミス 4: 重い UI の攻撃的な自動起動。
開発者は自分の美しいフルスクリーンウィジェットを誇り、あらゆる場面で見せたくなります。その結果、ユーザーはチャットが「壊れて」変なウェブアプリに化けたように感じます。はるかに良いのは、GPT がまずテキストで会話し、確認をいくつか行い、必要なときにだけ App を開くことを提案し、その理由を説明することです。
ミス 5: UX レイヤーとしての GPT の役割を無視する。
App を普通の SPA のように設計し、「すべてをウィジェット内でやり、ChatGPT は黙っていてほしい」と考えることもできます。しかしそれはうまくいきません。ChatGPT はあなたのウィジェットを表示しないこともあれば、ツール呼び出しごとに新しいウィジェットを表示することもあります。成功するプロダクトにしたいなら、プラットフォームに合わせましょう。プラットフォームがあなたに合わせてくれるとは期待しないでください。
GO TO FULL VERSION