CodeGym /コース /ChatGPT Apps /Voice / Realtime コンテキスト: 音声会話時の App の挙動

Voice / Realtime コンテキスト: 音声会話時の App の挙動

ChatGPT Apps
レベル 8 , レッスン 4
使用可能

1. コンテキスト: ChatGPT App にとって「音声モード」とは何か

まず大事なのは、ChatGPT Apps SDK の範囲では、あなたは独自の音声クライアントを書かないし、マイクを制御したり音声を自前でストリーミングしたりしないという点です。これらは ChatGPT クライアント(Web またはモバイルアプリ)が担当します。

本講義では、すでにウィジェット、callTool、そして過去モジュールの GiftGenius を理解している前提で、同じ要素を音声モードの観点から見直します。

開発者としての視点では、おおむね次のように見えます。

  • ユーザーはマイクに向かって話します。ChatGPT クライアントが音声認識を行い、モデルにはテキストが送られます。
  • あなたが見るイベントの流れは、ユーザーが文字入力している場合と同じです。ただし到着は速く、より「会話的」です。
  • モデルはテキストで返答し、クライアントがそれを読み上げます。
  • 同時にモデルはあなたのツール(callTool)を呼び出し、ウィジェットの displayMode を変更し、widgetState を更新し、フォローアップも提示できます——テキストモードと同様です。

決定的な違いは、ユーザーがほとんど画面を見ない、あるいはスマホを「チラ見」する程度かもしれない点です。つまり UI は主要な対話チャネルではなくなり、音声の補助に変わります(逆ではありません)。

ここから 2 つの帰結が生まれます。

  • 本当に重要なことは「耳」で分かる必要があります。つまり GPT の発話で伝えるべきです。
  • ウィジェットは良い意味で「つややか(glanceable)」であるべきです。ちらっと見ただけでステータスと主要な選択肢がすぐ分かり、細かい文字を読ませないこと。

GiftGenius に当てはめると、たとえば「車で移動中に、母へのプレゼントを選びたい」というシナリオは単なるテキストチャットではありません。音声が主導し、UI が保険として支えるマルチモーダルな対話です。

2. 音声シナリオはテキストとどう違うか

「同じことを声で言っているだけ」といった罠にはまらないために、いくつかの観点でテキストモードと音声モードを比較してみましょう。

側面 テキストモード 音声モード
ユーザーの注意配分 画面を見て読む・スクロールする まったく見ないこともある(ハンズフリー)
リクエストの形 より構造的で、入力を推敲できる 会話調。言いさしや相づち(「うん」「もっとお願い」など)
無音への許容度 1–2秒の無音は許容されやすい 長い無音は苦痛に感じやすい
UI の役割 詳細の主要な媒体 補助。短く状況が把握できる glanceable 表示
入力エラー タイプミス。ただしテキストが見える 不明瞭な発話、ノイズ、誤った「はい/いいえ」

ここからいくつかの重要な示唆が得られます。

  • 「カードに書いてあるから読んでくれるだろう」とは考えない。重要事項は、何を理解し、何をしようとしており、どんな結果が出たのかを、声で明確に伝えるべきです。
  • UI は「1 秒チラ見」に耐えるべき。ステータス、進捗、主要な選択肢は大きく目立つ形で。細部は二次的に。
  • 無音は埋める。MCP サーバーが重いリクエストで処理中なら、モデルは「いま作業中である」ことを口頭で伝え、ウィジェットは進捗を見せて、アシスタントが固まった印象を避けるべきです。

音声モードは「挿絵つきのオーディオブック」と考えることができます。語り手(GPT)と絵(ウィジェット)があり、互いに補完し合い、重複や衝突がないように同期させるのです。

3. 音声モードにおけるウィジェットの役割: 「操作パネル」から「掲示板」へ

テキストシナリオでは、ウィジェットはしばしば完全なインターフェース(フォーム、テーブル、フィルタ付きカルーセル、アクションボタン)として振る舞います。音声モードでは役割が変わります。マルチモーダル設計や VUI の知見では、音声シナリオでは UI はむしろ情報掲示板(glanceable UI)に近く、素早い確認や合意のために使われ、目で密に作業する場ではありません。

GiftGenius の場合、次のようになります。

ユーザーが音声ウィザードを進めているとき、画面ではインラインウィジェットまたはフルスクリーンで以下を表示します。

  • 大きなステータス: 「ステップ 2/3: 予算とギフト種別」。
  • 最小限のテキストだが明確なラベル: 「予算: 最大50ドル」「デジタルギフトを優先」。
  • 音声シナリオでクリックを許可する場合は、目立つ CTA ボタンを 2 つほど: 「予算を変更」「続行」。
  • 小さなインジケーターを多数出すのではなく、シンプルなプログレスバーかステッパーを 1 つ。

音声シナリオ向け、インラインウィジェットの簡単な「掲示板」例(TypeScript + React、あくまで簡略版):

type VoiceUiMode = "default" | "voiceGlance";

interface GiftStepProps {
  step: number;
  totalSteps: number;
  summary: string; // すでに集めた内容の簡潔な要約
  uiMode: VoiceUiMode;
}

export function GiftVoiceStep(props: GiftStepProps) {
  const fontSize = props.uiMode === "voiceGlance" ? "text-lg" : "text-sm";

  return (
    <div className="rounded-xl border p-3 flex flex-col gap-2">
      <div className={`${fontSize} font-semibold`}>
        ステップ {props.step} / {props.totalSteps}
      </div>
      <div className={`${fontSize} text-muted-foreground`}>
        {props.summary}
      </div>
    </div>
  );
}

ここには「音声」特有の処理は何もありませんが、uiMode=== "voiceGlance" のときに全体を大きくシンプルにする、という明確な意図があります。音声モードであることのシグナルは、状況から推測できる場合もあれば、widgetState やツールの返答で明示的なフラグとしてモデルから届く場合もあります。

4. モダリティ同期: GPT が話すことと App が見せること

Apps における音声 UX の核心は、モダリティの同期です。音声とビジュアル UI が同じストーリーを、異なる詳細レベルで語るようにします。

ありがちな誤りは、開発者がモデルにウィジェットの表示内容をそのまま読み上げさせることです。長いギフト一覧やフィルタの JSON 構造などを読み上げるのは苦行です。推奨は、音声は短いサマリー、UI は詳細という分担です。

GiftGenius における良い同期の例。

ユーザー: 「母へのプレゼントを選んで。園芸が好きで、予算は50ドルまで。」

モデル(音声): 「いくつかの候補を選びました。私のおすすめは、45ドルのガーデンツールセットです。画面には他に 2 つの類似候補を表示しています。詳しく説明しましょうか? それともすぐに選びますか?」

ウィジェット(インライン): 3 つのギフトカードを、短い説明と「選ぶ」/「似たものを表示」の CTA とともに表示します。

1 ステップをダイアログ+JSON 風に表すと(実際のプロトコルではなく思考のイメージです):

{
  "user": "母へのプレゼントを選んで...",
  "assistant_text": "いくつかの候補を見つけました...",
  "widget": {
    "displayMode": "inline",
    "state": {
      "view": "gift_list",
      "items": [
        { "id": "g1", "title": "ガーデンツールセット", "price": 45 },
        { "id": "g2", "title": "園芸用エプロン", "price": 30 },
        { "id": "g3", "title": "花の種のセット", "price": 20 }
      ]
    }
  }
}

重要なポイントとして、system‑prompt に UI についての話し方を明示できます。JSON を「読み上げない」ようにするためです。たとえば「もしウィジェットで候補を一覧表示しているなら、すべてを読み上げない。最良の候補を短く述べ、他は画面で見られると伝える」といった指示です。

将来、Realtime API と独自の音声クライアントを使うときも原則は同じです。UI とオーディオストリームを一致させること。違いは、そこでストリーミングを直接制御できる点だけです。

5. Realtime とレイテンシ: 気まずい無音を避けるには

技術的には、音声モードでも tool_calls はテキストと同じです。モデルがツール呼び出しを決め、あなたが応答し、ウィジェットが更新されます。しかし音声では新たな UX 課題——遅延——が生まれます。MCP サーバーが外部 API にアクセスしたり重い集計をすると、その間ユーザーは…何も聞こえません。チャットでテキストを待つより、はるかに悪く感じられます。

ここには音声面とビジュアル面の二重の防御策があります。

  • 音声面では、system‑prompt に「作業中の発話」や、ツールがまだ計算中の間に追加質問をすることを許可(推奨)します。例: 「今からギフトを選びます。数秒かかります。その間、他の条件があれば教えてください」。
  • ビジュアル面では、ウィジェットが進捗をはっきり示すべきです。ローダー、ステータス(「候補を検索中…」など)、現在のステップ。これがないと、ユーザーはフリーズしたと判断し、さらに話しかけて音声フローを乱します。

実務では、遅延をジョブの遅延実行で扱うのが便利です。ツールはすぐに "pending"jobId を返し、実際の選定はバックグラウンドで進めます。ウィジェットは "pending" を見て進捗を表示し、音声は「作業中」であることを伝えます。

完全な結果が出るまでブロックせず、ジョブ ID 付きのプレースホルダーを返すサーバーサイドツールの最小構成は、次のようにできます。

// GiftGenius のサーバーサイド・ツールの擬似コード
export async function startGiftSearch(params: SearchParams) {
  const jobId = await createBackgroundJob(params); // ジョブをキューに投入

  return {
    status: "pending",
    jobId,
    message: "ギフトの検索を開始しました"
  };
}

ウィジェットは status: "pending" を見て、UI を進捗モードに切り替えられます。

if (toolOutput.status === "pending") {
  return (
    <div className="p-4 rounded-xl border flex items-center gap-3">
      <Spinner />
      <div className="text-base">
        ギフトを選定中… 数秒かかります。
      </div>
    </div>
  );
}

同じ tool‑output に対して、モデルも指示どおり同様の内容を口頭で伝え、必要に応じて追加の確認質問を行うでしょう。後でバックグラウンドジョブが完了し、たとえば MCP の通知で job.completed が届いたら、ウィジェットはギフト一覧に更新され、音声はサマリーを読み上げます。

こうすることで、バックエンドが即時でなくても、実運用に近いリアルタイムな挙動を得られます。

6. 音声での安全性と確認

音声インターフェースは、決済・データ削除・設定変更のようなクリティカルな操作で特にトリッキーです。音声認識は完璧ではなく、ユーザーは「ながら」で話しがちで、「うん」が「はい、購入して」に化けることもあります。そのため音声シナリオでは、とりわけ確認フロー(confirmation flows)が重要です。

基本のパターンは 2 つあります。

  • 明示的な音声確認(Explicit Voice Confirmation)。 危険な操作では、特定のフレーズを要求します。例: 「購入を確定するには『購入を確認します』と言ってください」。そして system‑prompt で、曖昧な「ああ」「オーケー」「やって」のような反応では決済を実行しないよう禁止します。
  • ビジュアルのみでの確認(Visual Confirmation Only)。 モデルは音声で操作に誘導します(「注文を用意しました。画面に合計とカートの中身が表示されています」)。ただし実際のトリガーは、ウィジェット上の「支払う」ボタンのクリックです。特にコマース系のシナリオでは重要で、モジュール 14 で再度扱います。

GiftGenius では次のようになります。

モデル: 「45ドルの園芸セットが良さそうです。ChatGPT 経由で購入手続きができます。画面には合計金額と配送先住所を表示しました。音声で確認する場合は『購入を確認します』と言うか、画面の『支払う』ボタンを押してください。」

ウィジェット(フルスクリーン): 注文内容を表示し、金額と住所を太字で強調。さらに「支払う」「キャンセル」の目立つ 2 ボタン。

ウィジェット内で確認のステータスを反映することもできます。

type CheckoutState = "review" | "waiting_voice_confirm" | "confirmed";

if (state.phase === "waiting_voice_confirm") {
  return (
    <div className="space-y-3">
      <h2 className="text-xl font-semibold">ほぼ完了</h2>
      <p className="text-base">
        音声で「購入を確認します」と発話するか、
        「支払う」ボタンを押して購入を確定してください。
      </p>
      <Button variant="primary">支払う</Button>
      <Button variant="ghost">キャンセル</Button>
    </div>
  );
}

こうして、もしモデルが音声を誤って解釈しても、ユーザーにはビジュアルの「セーフティレイヤー」が残ります。

7. シンプルな音声コマンドとツール設計

音声ユーザーは、ツールのパラメータ名と同じ形では話しません。「一番を選んで」「もっと安いの」「機器は除外で」と言うでしょう。開発者の仕事は、こうした発話をあなたのツール呼び出し(callTool)に容易にマッピングできるよう、ツールと system‑prompt を設計することです。

GiftGenius では、たとえば以下のアクションを用意できます。

  • 表示中の候補から、インデックスまたは id で 1 つ選ぶ。
  • 予算の絞り込み: 「もっと安く」「30ドルまで」など。
  • タイプによるフィルタ: 「デジタルギフトのみ」「郵送が必要なものは除外」など。

これは、enum パラメータの action と補助フィールドを持つシンプルなツールで表すと便利です。

// TypeScript によるツールの擬似スキーマ
type VoiceActionInput =
  | { action: "select_item"; itemId: string }
  | { action: "refine_budget"; maxPrice: number }
  | { action: "filter_type"; type: "digital" | "physical" };

export function handleVoiceAction(input: VoiceActionInput) {
  switch (input.action) {
    case "select_item":
      // ギフトを選択済みとしてマーク
      break;
    case "refine_budget":
      // 新しい予算で再計算
      break;
    case "filter_type":
      // 既存リストをフィルタ
      break;
  }
}

system‑prompt では、これらのアクションを音声コマンドとどう対応させるかを記述します。例: 「ユーザーが『一番目を選んで』と言ったら、gift.voiceActionaction="select_item" で呼び、画面上の最初のギフトの識別子を渡す」など。

UX の観点では、これは認知負荷を下げます。ユーザーは「30ドル以下のデジタルギフトのみになるようフィルタを調整して」といった厳密な言い回しを考える必要はありません。人間らしい話し方をすれば、モデルがデータ構造に変換します。

8. GiftGenius の音声シナリオ: 3 ステップ

ここまでをまとめ、Realtime API の低レベルには深入りせず、GiftGenius の完全な音声シナリオを設計してみましょう。

想定ユーザー: 車で移動中に ChatGPT の音声モードを起動し、「母へのプレゼントをお願い。園芸が好きで、予算は50ドルまで」と話します。

ステップ 1. 音声で情報収集

モデル: 「了解です。いくつか確認させてください。ギフトはいつ必要ですか——数日のうちか、それとも後日でも良いですか? また、重い・かさばる物は避けたいなどの制約はありますか?」

ウィジェット(インライン): 「対象: 母、園芸、50ドルまで」といったステータスの小さなパネルのみ。通常より少し大きめのフォントで、ひと目で認識できるようにします。

ウィジェットの状態コード例:

interface GiftSessionState {
  mode: "voice" | "text";
  step: 1 | 2 | 3;
  recipientSummary: string;
  budget?: number;
}

const [state, setState] = useState<GiftSessionState>({
  mode: "voice",
  step: 1,
  recipientSummary: "母、園芸好き"
});

サーバー側はユーザーの回答に応じて recipientSummarybudget を更新し、ウィジェットが反応します。

ステップ 2. 検索と待機

十分な情報を集めたら、モデルはギフト検索ツールを呼び出します。選定が重い場合はバックグラウンドジョブを起動し、status"pending" を返してもよいでしょう。バックグラウンド処理中、モデルは「今から候補を探します。数秒かかります。その間、物理ギフトとデジタル証書のどちらが良いか教えてください」などと話します。

ユーザーが他の画面へ移動したら PiP 風のモードに切り替え、そうでなければインラインのまま進捗(「ギフトを検索中…」)とインジケーターを表示します。

ステップ 3. 結果と選択

結果が整ったら、モデル: 「3 つの候補があります。1 つ目は 45 ドルのガーデンツールセット。2 つ目は 30 ドルの園芸用エプロン。画面に表示しました。『一番を選んで』や『もっと安いものを見せて』と言ってください。」

ウィジェットは、価格と短い説明が付いた大きなカードを 3 枚表示します。各カードには「選ぶ」と「似たもの」の CTA。さらに「他の候補を表示」ボタンも用意します。

ユーザーが「二番を選んで」と言った場合、モデルは voiceAction ツールを action="select_item"、第 2 候補の id で呼び出します。ウィジェットはそのカードを選択状態にし、モデルは「OK、30 ドルの園芸用エプロンを選びました」と音声で伝えます。

任意のステップ 4. チェックアウト

App が決済と統合されている場合(モジュール 14 で扱います)、チェックアウトが始まります。モデルは条件を音声で説明し、音声またはボタンでの確認を求めます。ウィジェットはフルスクリーンのウィザードに遷移し、「注文確認」→「配送先住所」→「確定」というステップを表示します。

重要なのは、各ステップでキー情報を音声で伝え、特にユーザーが画面を見る状態になったらウィジェットが視覚的な支えとして機能することです。

9. 実装メモと Apps SDK の境界

ここまでの GiftGenius の流れは、独自の音声クライアントや WebRTC なしで、通常の ChatGPT App の範囲で実現できます。スタックの境界を忘れないことが重要です。

Realtime API、WebRTC、音声ストリーミングなどに「飛んで」独自の音声プラットフォームを思い描くのは簡単です。これらは別途モジュール 20 で扱います。本講義で大切なのは、ChatGPT クライアント内の ChatGPT App という境界です。

現行アーキテクチャでは:

  • 音声ストリームは ChatGPT クライアントが管理します。ウィジェットは音声バイトを送受信しません。
  • バックエンドは従来どおりのツール呼び出しとテキストメッセージを受け取ります。ただしモデルは音声モードにあり、その返答は読み上げられます。
  • プラットフォームは、現在が音声モードであることを示す間接的な手がかり(user-agent や環境フィールド)を渡すことがあります。しかしこれに強く依存すべきではありません。API は変わり得るし、あなたの App は純テキストでも有用であるべきです。

したがって実装戦略としては、まずテキストにも音声にもうまく機能する UX を設計します。短いステータス、明確な CTA、分かりやすい進捗段階。そのうえで、音声向けの改善を少しだけ追加します。"voiceGlance" モードでのやや大きめのフォント、より明確な進捗、「ステップ 2/3」「確認待ち」のような明快な状態表示などです。

さらに system‑prompt でモデルの音声挙動を説明します。ウィジェット状態をどう言及するか、確認のための定型句、避けるべき言い回し(JSON を読み上げない、リストの枝葉を逐一言わない など)。

将来 Realtime API で独自の Custom Voice Client を作るとしても、これらの UX 上の解決策はそのまま移行できます。違いはイベントやストリーミングへのアクセスレベルであって、原則ではありません。

10. Voice / Realtime コンテキストでの典型的なミス

エラー1: サマリーではなく「UI の読み上げ」になってしまう。
ツール設計が悪いと、モデルが JSON 全文やカードの全リストを読み上げ始めることがあります。音声モードでは UX を破壊します。ユーザーは要点を見失い、トークンも浪費。音声は短い要約、UI は詳細、にしましょう。

エラー2: 音声時にビジュアルのフィードバックが全くない。
「話しているなら聞いている、UI は不要」と考えがちですが、ユーザーは画面をチラ見したり、1 分後に戻ってくることがあります。そのときステータス・進捗・明快な結果がなければ、App が固まった/何もしなかったと判断されます。「考え中」「ステップ 2/3」「結果の準備完了」などを必ず表示しましょう。

エラー3: 強い確認なしの危険操作。
テキストでも「支払う」一発は危険、音声では曖昧な「うん」で購入実行はさらに危険です。明示的な確認フロー(音声および/またはビジュアル)を無視すると、誤購入と信用失墜につながります。どの操作が二重確認を要するかを設計し、system‑prompt と UI に明示してください。

エラー4: 目(読むこと)だけを前提にして耳(聞くこと)を無視する。
ユーザーが常に読む前提で、難解な文言、長いボタン文、情報過多の説明になりがちです。音声モードではそれらを話す必要があり「言葉のスープ」になります。キーの意味は、耳で取りやすい、短く簡潔なフレーズに収めましょう。

エラー5: Apps SDK と独自 Voice クライアントの混同。
Apps SDK に Realtime API のようなマイクイベント・音声ストリーミング・WebRTC を探して落胆する、という混同が起きがちです。理解すべきは、ChatGPT App は ChatGPT クライアント内部で動作し、音声はプラットフォームが管理するということ。あなたはテキスト、ツール呼び出し、ウィジェット状態で仕事をし、音声モードでも「うまく動く UX」を設計します。音声を完全制御したいなら、それは Realtime API による別プロジェクトです。

エラー6: レイテンシ対応の戦略がない。
長時間の処理中にモデルが何を話し、ウィジェットが何を見せるかを設計しないと、ユーザーは割り込み、別の質問をしてフローを壊します。音声の遅延はテキストより強く感じられます。中間ステータス、バックグラウンド処理、音声の「今考えています。その間に…」を使い、無音をバグにしないようにしましょう。

1
アンケート/クイズ
UXとインターフェース、レベル 8、レッスン 4
使用不可
UXとインターフェース
UXとインターフェース(Inline, Fullscreen, Voice)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION