For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション
Theme

Realtime API での会話

Realtime API の音声変換を使った会話を管理する方法を解説します。

WebRTC または WebSocket で Realtime API に接続すると、gpt-realtime-2.1 などの Realtime モデルを呼び出し、音声変換を使って会話できます。処理を開始するにはクライアントイベントを送信し、Realtime API が実行した処理に応じて動作するにはサーバーイベントをリッスンします。

このガイドでは、音声やテキストの生成、画像入力、Function Calling などのモデル機能を使うためのイベントの流れと、Realtime セッションの状態をどう捉えるかを解説します。

モデルと会話する必要がなく、応答を求めない場合は、Realtime API を文字起こしモードで使用できます。

Realtime の音声変換セッション

Realtime セッションでは、モデルと接続中のクライアントが状態を保持しながらやり取りします。セッションの主な構成要素は次のとおりです。

  • セッションオブジェクトは、使用するモデル、音声出力に使う声など、やり取りに関する設定を管理します。
  • 会話は、現在のセッションにおけるユーザーの入力アイテムと、モデルが生成した出力アイテムを表します。
  • 応答は、モデルが生成して会話に追加される音声またはテキストのアイテムです。

入力音声バッファと WebSocket

WebRTC を使用する場合、モデルとの音声の送受信に必要なメディア処理の多くは WebRTC API が担います。


WebSocket で音声を扱う場合は、Base64 エンコードした音声を JSON イベントに含めてサーバーに送信し、入力音声バッファを明示的に操作する必要があります。

これらの要素を組み合わせたものが Realtime セッションです。クライアントイベントを使ってセッションの状態を更新し、サーバーイベントをリッスンしてセッション内の状態変化に対応します。

Realtime セッションの状態を示す図

セッションのライフサイクルイベント

WebRTC または WebSocket でセッションを開始すると、サーバーは session.created イベントを送信し、セッションの準備が整ったことを通知します。クライアントでは、session.update イベントで現在のセッション設定を更新できます。ほとんどのプロパティはいつでも更新できますが、音声出力に使う voice は、そのセッションでモデルが一度でも音声で応答すると変更できなくなります。Realtime セッションの継続時間は最長 60 分です。

次の例では、session.update クライアントイベントでセッションを更新します。各接続でのクライアントイベントの送信方法は、WebRTC または WebSocket のガイドを参照してください。

このセッションでモデルが使用するシステム指示の更新
const event = {
  type: "session.update",
  session: {
    type: "realtime",
    model: "gpt-realtime-2.1",
    // Lock the output to audio (set to ["text"] if you want text without audio)
    output_modalities: ["audio"],
    audio: {
      input: {
        format: {
          type: "audio/pcm",
          rate: 24000,
        },
        turn_detection: {
          type: "semantic_vad",
        },
      },
      output: {
        format: {
          type: "audio/pcm",
          rate: 24000,
        },
        voice: "marin",
      },
    },
    // Use a server-stored prompt by ID. Optionally pin a version and pass variables.
    prompt: {
      id: "pmpt_123", // your stored prompt ID
      version: "89", // optional: pin a specific version
      variables: {
        city: "Paris", // example variable used by your prompt
      },
    },
    // You can still set direct session fields; these override prompt fields if they overlap:
    instructions:
      "Speak clearly and briefly. Confirm understanding before taking actions.",
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

セッションの更新が完了すると、サーバーは新しいセッション状態を含む session.updated イベントを送信します。

関連するクライアントイベント関連するサーバーイベント

session.update

session.created

session.updated

テキストの入出力

Realtime モデルでテキストを生成するには、現在の会話にテキスト入力を追加し、モデルに応答の生成を要求します。サーバーから送られるイベントをリッスンすると、応答の生成状況を把握できます。テキストを生成するには、セッションの設定で text モダリティを有効にする必要があります(デフォルトで有効です)。

conversation.item.create クライアントイベントを使い、テキストを含む新しい会話アイテムを作成します。これは、REST API の Chat Completions でユーザーメッセージ(プロンプト)を送信する操作に似ています。

ユーザー入力を含む会話アイテムの作成
const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_text",
        text: "What Prince album sold the most copies?",
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));

ユーザーメッセージを会話に追加したら、response.create イベントを送信してモデルの応答を開始します。出力モダリティは 1 つ選びます。audio は音声とその文字起こしを生成し、text はテキストのみを生成します。テキストのみを生成したい場合は、次の例のように response.create クライアントイベントの送信時に指定できます。

テキストのみの応答の生成
const event = {
  type: "response.create",
  response: {
    output_modalities: ["text"],
  },
};

// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));

応答が完了すると、サーバーは response.done イベントを送信します。次の例のように、このイベントにはモデルが生成したテキストの全文が含まれます。

response.done の受信による最終結果の確認
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (serverEvent.type === "response.done") {
    console.log(serverEvent.response.output[0]);
  }
}

// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

モデルが応答を生成している間、サーバーは複数のライフサイクルイベントを送信します。response.output_text.delta などのイベントをリッスンすると、生成中の応答をリアルタイムでユーザーに提示できます。以下の表の関連するサーバーイベント欄に、サーバーが送信するイベントをおおよその送信順に示します。テキスト生成に関連するクライアントイベントも併記しています。

音声の入出力

Realtime API の大きな特長の 1 つは、音声合成や音声認識の中間処理を挟まず、モデルと直接音声でやり取りできることです。これにより音声インターフェースの遅延を抑えられ、モデルは入力音声の口調や抑揚について、より多くの情報を活用できます。

音声の種類

Realtime セッションでは、音声出力に使う声を複数の組み込み音声から選べます。モデルの声は voice で指定します。セッションの作成時、または response.create の送信時に設定できます。現在選べる音声は alloy、ash、ballad、coral、echo、sage、shimmer、verse、marin、cedar です。セッション内でモデルが一度でも音声を出力すると、そのセッションの voice は変更できません。音質を重視する場合は marin または cedar をおすすめします。

WebRTC での音声処理

WebRTC で Realtime API に接続すると、Realtime API はクライアントのピア接続の相手として動作します。モデルの音声出力はリモートメディアストリームとしてクライアントに届きます。モデルへの音声入力はオーディオデバイスから取得し(getUserMedia)、メディアストリームをトラックとしてピア接続に追加します。

WebRTC 接続ガイドから抜粋した次のコードは、ブラウザの API を使ってローカル音声とリモート音声の両方を設定する基本的な例です。

// Create a peer connection
const pc = new RTCPeerConnection();

// Set up to play remote audio from the model
const audioEl = document.createElement("audio");
audioEl.autoplay = true;
pc.ontrack = (e) => (audioEl.srcObject = e.streams[0]);

// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
  audio: true,
});
pc.addTrack(ms.getTracks()[0]);

上のコードで Realtime API とやり取りできます。さらに多様なユーザーインターフェースの実装例は、WebRTC サンプルのリポジトリを参照してください。これらのサンプルはライブデモでも試せます。

ブラウザでメディアキャプチャとストリームを使うと、マイクのオンとオフの切り替えや、入力に使うデバイスの選択などができます。

WebRTC の音声に関するクライアントイベントとサーバーイベント

デフォルトでは、WebRTC クライアントが音声入力の送信前に Realtime API にクライアントイベントを送る必要はありません。ピア接続にローカル音声トラックを追加すれば、ユーザーはすぐに話し始められます。

一方、ピア接続を介してクライアントとサーバーの間で音声を送受信している間も、WebRTC クライアントにはサーバーからさまざまなライフサイクルイベントが届きます。たとえば、次のようなイベントがあります。

メディアストリーム用の WebRTC API を操作すれば、必要な制御をすべて行える場合があります。ただし、音声の入出力に、より低レベルのインターフェースが必要になることもあります。音声入力を細かく制御する方法と必要なイベントの一覧は、以下の WebSocket のセクションを参照してください。

WebSocket での音声処理

WebSocket で音声を送受信する場合、クライアントからメディアを送信する処理と、サーバーからメディアを受信する処理をより細かく実装する必要があります。以下の表に、WebSocket セッション中に音声を送受信するために必要なイベントの流れを示します。

イベントはライフサイクルに沿って並べていますが、delta イベントなど、一部のイベントは並行して発生することがあります。

サーバーへの音声入力のストリーミング

サーバーに音声入力をストリーミングするには、input_audio_buffer.append クライアントイベントを使います。このイベントでは、Base64 エンコードした音声バイト列をチャンクに分け、ソケット経由で Realtime API に送信します。各チャンクのサイズは 15 MB 以下にしてください。

session.audio.input.format は session.update で設定します。PCM の場合は {"type": "audio/pcm", "rate": 24000} を指定し、24 kHz のモノラル音声を送信します。

入力音声のバイト列を会話に追加
import fs from "fs";
import decodeAudio from "audio-decode";

// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
  const buffer = new ArrayBuffer(float32Array.length * 2);
  const view = new DataView(buffer);
  let offset = 0;
  for (let i = 0; i < float32Array.length; i++, offset += 2) {
    const sample = Math.max(-1, Math.min(1, float32Array[i]));
    view.setInt16(
      offset,
      Math.round(sample * (sample < 0 ? 0x8000 : 0x7fff)),
      true
    );
  }
  return buffer;
}

// Converts a Float32Array to base64-encoded PCM16 data
function base64EncodeAudio(float32Array) {
  const arrayBuffer = floatTo16BitPCM(float32Array);
  let binary = "";
  let bytes = new Uint8Array(arrayBuffer);
  const chunkSize = 0x8000; // 32KB chunk size
  for (let i = 0; i < bytes.length; i += chunkSize) {
    let chunk = bytes.subarray(i, i + chunkSize);
    binary += String.fromCharCode(...chunk);
  }
  return btoa(binary);
}

// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
  "fixtures/sample1.wav",
  "fixtures/sample2.wav",
  "fixtures/sample3.wav",
];

// Manual commit and response creation require automatic turn detection off.
ws.send(
  JSON.stringify({
    type: "session.update",
    session: {
      type: "realtime",
      audio: { input: { turn_detection: null } },
    },
  })
);

for (const filename of files) {
  const audioFile = fs.readFileSync(filename);
  const audioBuffer = await decodeAudio(audioFile);
  if (
    audioBuffer.sampleRate !== 24000 ||
    audioBuffer.channelData.length !== 1
  ) {
    throw new Error("Expected 24 kHz mono audio: " + filename);
  }
  const channelData = audioBuffer.channelData[0];
  const base64Chunk = base64EncodeAudio(channelData);
  ws.send(
    JSON.stringify({
      type: "input_audio_buffer.append",
      audio: base64Chunk,
    })
  );
}

ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
ws.send(
  JSON.stringify({
    type: "response.create",
    response: { output_modalities: ["audio"] },
  })
);

音声メッセージの一括送信

録音した音声全体を 1 つの会話メッセージとして送信することもできます。conversation.item.create クライアントイベントを使い、input_audio コンテンツを含むメッセージを作成します。

入力音声全体を含む会話アイテムの作成
const fullAudio = "<a base64-encoded string of audio bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_audio",
        audio: fullAudio,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

WebSocket での音声出力の処理

ウェブブラウザなどのクライアント側で出力音声を再生する場合は、WebSocket よりも WebRTC をおすすめします。ネットワークの状態が不安定でも、WebRTC の方が安定してクライアントデバイスにメディアを送信できます。

サーバー間で通信するアプリケーションで WebSocket を使って音声出力を扱うには、response.output_audio.delta イベントをリッスンします。このイベントには、モデルが出力した音声データのチャンクが Base64 エンコードされて含まれています。チャンクはバッファに蓄積してファイルに書き出すか、Twilio を使った通話などの送信先にそのままストリーミングします。

response.output_audio.done と response.done の各イベントに含まれるのは音声の文字起こしのみで、音声データ自体は含まれません。音声のバイト列を取得するには、response.output_audio.delta イベントをリッスンしてください。

出力チャンクの形式は、セッション全体に対して設定することも、応答ごとに設定することもできます。

  • セッション単位:session.audio.output.format を session.update で設定
  • 応答単位:response.audio.output.format を response.create で設定
response.output_audio.delta イベントのリッスン
function handleEvent(message) {
  const serverEvent = JSON.parse(message.toString());
  if (serverEvent.type === "response.output_audio.delta") {
    const audio = Buffer.from(serverEvent.delta, "base64");
    console.log(`Received ${audio.length} audio bytes`);
  }
}

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

画像入力

gpt-realtime-2 と gpt-realtime は画像入力にも対応しています。ユーザーメッセージにコンテンツの一部として画像を添付すると、モデルは画像の内容を踏まえて応答できます。

会話への画像の追加
const base64Image = "<a base64-encoded string of image bytes>";

const event = {
  type: "conversation.item.create",
  item: {
    type: "message",
    role: "user",
    content: [
      {
        type: "input_image",
        image_url: `data:image/{format};base64,${base64Image}`,
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

音声区間検出

Realtime セッションでは、デフォルトで 音声区間検出(VAD) が有効です。API がユーザーの発話の開始と終了を検出し、自動的に応答します。

VAD の設定方法については、音声区間検出のガイドを参照してください。

VAD の無効化

turn_detection を null に設定する session.update クライアントイベントを送ると、VAD を無効にできます。プッシュトゥトークのように、音声入力を細かく制御したいインターフェースで役立ちます。

VAD が無効の場合、音声応答を生成するには、クライアント側で次のイベントを明示的に送信する必要があります。

VAD を有効にしたまま自動応答を無効化

VAD を使いつつ応答を生成するタイミングを自分で決めたい場合は、turn_detection.interrupt_response と turn_detection.create_response を false に設定する session.update クライアントイベントを送信します。VAD の動作は維持されますが、新しい応答は自動生成されません。クライアントから response.create イベントを送ることで、明示的に応答を開始できます。

この設定は、モデレーションや入力検証、RAG を実装する際に、やり取りの遅延が多少増えても入力を制御したい場合に役立ちます。

デフォルトの会話とは別の応答の作成

デフォルトでは、セッション中に生成されたすべての応答が、そのセッションの会話状態(「デフォルトの会話」)に追加されます。ただし、デフォルトの会話のコンテキストから切り離してモデルの応答を生成したい場合や、複数の応答を並行して生成したい場合もあります。また、直近の N ターンだけを対象にするなど、応答の生成時にモデルが参照する会話アイテムを細かく制御したいこともあります。

デフォルトの会話状態に追加されない「帯域外」の応答を生成するには、response.conversation フィールドに文字列 none を設定し、response.create クライアントイベントで応答を作成します。

帯域外の応答を作成する際は、その応答に関連するサーバーイベントを識別できるようにしておくと便利です。モデルの応答に metadata を指定すると、クライアントから送ったイベントに対してどの応答が生成されているかを識別できます。

モデルの帯域外応答の作成
const prompt = `
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
`;

  const event = {
    type: "response.create",
    response: {
      // Setting to "none" indicates the response is out of band
      // and will not be added to the default conversation
      conversation: "none",

      // Set metadata to help identify responses sent back from the model
      metadata: { topic: "classification" },

      // Set any other available response fields
      output_modalities: ["text"],
      instructions: prompt,
    },
  };

  // WebRTC data channel and WebSocket both have .send()
  ws.send(JSON.stringify(event));

これで、response.done サーバーイベントをリッスンしたときに、帯域外応答の結果を識別できます。

モデルの帯域外応答の作成
function handleEvent(message) {
  const data = "data" in message ? message.data : message.toString();
  const serverEvent = JSON.parse(data);
  if (
    serverEvent.type === "response.done" &&
    serverEvent.response.metadata?.topic === "classification"
  ) {
    const { response } = serverEvent;
    if (response.status !== "completed" || response.output.length === 0) {
      throw new Error(
        "Classification response ended with status " + response.status
      );
    }
    console.log(response.output[0]);
  }
}

// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);

// Listen for server messages (WebSocket)
ws.on("message", handleEvent);

応答用のカスタムコンテキストの作成

現在のデフォルトの会話とは別に、モデルが応答の生成に使うコンテキストを組み立てることもできます。input 配列を response.create クライアントイベントに指定してください。新しい入力を指定することも、会話内の既存の入力アイテムを ID で参照することもできます。

カスタムコンテキストを使ったモデルの帯域外応答の受信
const existingItemId = "item_pizza";
ws.send(
  JSON.stringify({
    type: "conversation.item.create",
    item: {
      id: existingItemId,
      type: "message",
      role: "user",
      content: [
        { type: "input_text", text: "We are ordering pizza for dinner." },
      ],
    },
  })
);
const event = {
  type: "response.create",
  response: {
    conversation: "none",
    metadata: { topic: "pizza" },
    output_modalities: ["text"],

    // Create a custom input array for this request with whatever context
    // is appropriate
    input: [
      // potentially include existing conversation items:
      {
        type: "item_reference",
        id: existingItemId,
      },
      {
        type: "message",
        role: "user",
        content: [
          {
            type: "input_text",
            text: "Is it okay to put pineapple on pizza?",
          },
        ],
      },
    ],
  },
};

// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));

コンテキストを使わない応答の作成

他のすべての指示とコンテキストを無視して生成した応答を、デフォルトの会話に追加することもできます。その場合は input に空の配列を設定します。

コンテキストを使わずに生成したモデルの応答をデフォルトの会話に追加
const prompt = `
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
`;

  const event = {
    type: "response.create",
    response: {
      // An empty input array removes existing context
      input: [],
      output_modalities: ["text"],
      instructions: prompt,
    },
  };

  // WebRTC data channel and WebSocket both have .send()
  ws.send(JSON.stringify(event));

Function Calling

Realtime モデルは Function Calling にも対応しています。独自のコードを実行することで、モデルの機能を拡張できます。処理の流れは次のとおりです。

  1. セッションの更新時または応答の作成時に、モデルが呼び出せる関数の一覧を指定できます。
  2. 入力を処理する際、関数を呼び出す必要があるとモデルが判断すると、その呼び出しに使う引数を表すアイテムを会話に追加します。
  3. クライアントは、関数呼び出しの引数を含む会話アイテムを検出し、その引数を使って独自のコードを実行します。
  4. コードの実行が完了したら、クライアントは関数の出力を含む新しい会話アイテムを作成し、モデルに応答を要求します。

ユーザーに今日の星占いを返す関数を追加する例で、実際の流れを見てみましょう。クライアントが送信するイベントオブジェクトの形式と、それに対してサーバーが送信するイベントを示します。

呼び出し可能な関数の設定

まず、ユーザー入力に応じてモデルが呼び出せる関数を指定します。使用できる関数はセッション単位でも、個々の応答単位でも設定できます。

  • セッション単位:session.tools プロパティを session.update で設定
  • 応答単位:response.tools プロパティを response.create で設定

以下は、session.update クライアントイベントのペイロードの例です。占いたい星座を唯一の引数として受け取り、その星座の運勢を生成する関数を設定します。

session.update

{
  "type": "session.update",
  "session": {
    "tools": [
      {
        "type": "function",
        "name": "generate_horoscope",
        "description": "Give today's horoscope for an astrological sign.",
        "parameters": {
          "type": "object",
          "properties": {
            "sign": {
              "type": "string",
              "description": "The sign for the horoscope.",
              "enum": [
                "Aries",
                "Taurus",
                "Gemini",
                "Cancer",
                "Leo",
                "Virgo",
                "Libra",
                "Scorpio",
                "Sagittarius",
                "Capricorn",
                "Aquarius",
                "Pisces"
              ]
            }
          },
          "required": ["sign"]
        }
      }
    ],
    "tool_choice": "auto"
  }
}

関数とパラメータの description フィールドは、モデルが関数を呼び出すかどうかや、各パラメータに渡すデータを判断するために使われます。ユーザーが自分の星座の運勢を知りたいと入力すると、モデルは sign パラメータを指定してこの関数を呼び出します。

モデルによる関数呼び出し要求の検出

モデルは入力に応じて、最適な応答を生成するには関数の呼び出しが必要だと判断することがあります。たとえば、アプリケーションが conversation.item.create イベントで次の会話アイテムを追加し、応答を作成するとします。

{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "What is my horoscope? I am an aquarius."
      }
    ]
  }
}

続いて、response.create クライアントイベントを送信して応答を生成します。

{
  "type": "response.create"
}

この場合、モデルはすぐにテキストや音声で答える代わりに、開発者のアプリケーション内の関数に渡す引数を含む応答を生成します。response.function_call_arguments.delta サーバーイベントをリッスンすると、引数の更新をリアルタイムで受け取れます。response.done にも関数の呼び出しに必要なデータがすべて含まれています。

response.done

{
  "type": "response.done",
  "event_id": "event_AeqLA8iR6FK20L4XZs2P6",
  "response": {
    "object": "realtime.response",
    "id": "resp_AeqL8XwMUOri9OhcQJIu9",
    "status": "completed",
    "status_details": null,
    "output": [
      {
        "object": "realtime.item",
        "id": "item_AeqL8gmRWDn9bIsUM2T35",
        "type": "function_call",
        "status": "completed",
        "name": "generate_horoscope",
        "call_id": "call_sHlR7iaFwQ2YQOqm",
        "arguments": "{\"sign\":\"Aquarius\"}"
      }
    ]
  }
}

サーバーが送信した JSON の次のプロパティから、モデルが独自の関数の呼び出しを要求していることと、その内容を確認できます。

プロパティ Function Calling での役割
response.output[0].type function_call の場合、この応答には指定された関数を呼び出すための引数が含まれています。
response.output[0].name 設定済みの関数のうち、呼び出す関数の名前です。この例では generate_horoscope です。
response.output[0].arguments 関数に渡す引数を含む JSON 文字列です。この例では "{\"sign\":\"Aquarius\"}" です。
response.output[0].call_id この関数呼び出しに対してシステムが生成した ID です。関数の実行結果をモデルに返す際に、この ID が必要です。

これらの情報を使ってアプリケーションでコードを実行し、星占いの結果を生成できます。その結果をモデルに返すと、モデルはそれを使って応答を生成できます。

関数の実行結果をモデルに返す手順

関数呼び出しの引数を含む応答をモデルから受け取ったら、アプリケーションでその引数を使い、要求された処理を実行できます。外部 API の呼び出しやデータベースへのアクセスなど、必要な処理を自由に実装できます。

独自のコードの実行結果をモデルに返す準備ができたら、conversation.item.create クライアントイベントで、その結果を含む新しい会話アイテムを作成します。

{
  "type": "conversation.item.create",
  "item": {
    "type": "function_call_output",
    "call_id": "call_sHlR7iaFwQ2YQOqm",
    "output": "{\"horoscope\": \"You will soon meet a new friend.\"}"
  }
}
  • 会話アイテムの種類は function_call_output です。
  • item.call_id には、先ほどの response.done イベントで受け取った ID と同じ値を指定します。
  • item.output は、関数の実行結果を含む JSON 文字列です。

関数の実行結果を含む会話アイテムを追加したら、クライアントから再び response.create イベントを送信します。これにより、モデルは関数の実行結果を使って応答します。

{
  "type": "response.create"
}

エラー処理

error イベントは、セッション中にサーバーでエラーが発生するたびに送信されます。エラーの原因が、アプリケーションから送信したクライアントイベントにある場合もあります。

HTTP ではレスポンスがクライアントのリクエストに自動的に対応付けられますが、Realtime API では、どのクライアントイベントがサーバーのエラーを引き起こしたかを特定するために、クライアントイベントに event_id プロパティを指定する必要があります。次のコードは、クライアントがサポートされていない種類のイベントを送信しようとする例です。

const event = {
  event_id: "my_awesome_event",
  type: "scooby.dooby.doo",
};

dataChannel.send(JSON.stringify(event));

このクライアントイベントの処理に失敗すると、サーバーは次のようなエラーイベントを送信します。

{
  "type": "error",
  "event_id": "event_error_123",
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_value",
    "message": "Invalid value: 'scooby.dooby.doo'.",
    "param": "type",
    "event_id": "my_awesome_event"
  }
}

割り込みと応答の切り詰め

多くの音声アプリケーションでは、モデルが話している途中でユーザーが割り込めます。VAD が有効な場合、Realtime API はユーザーの発話を検出すると進行中の応答をキャンセルし、新しい応答を開始します。たとえば、ユーザーが「今、最後に何て言ったの?」と尋ねたときに自然に会話を続けるには、モデルがどこで遮られたかを把握できるようにする必要があります。そこで、モデルの直前の応答のうち、まだ再生されていない部分を会話から削除します。これを応答の切り詰めと呼びます。

WebRTC 接続と SIP 接続では、サーバーが出力音声バッファを管理するため、その時点で音声がどこまで再生されたかを把握しています。ユーザーが割り込むと、サーバーは未再生の音声を自動的に切り詰めます。

WebSocket 接続では、クライアントが音声の再生を管理するため、再生の停止と切り詰めもクライアント側で行う必要があります。手順は次のとおりです。

  1. input_audio_buffer.speech_started を受信したら、モデルの音声の再生を直ちに停止し、そのアイテムの再生待ちの音声をすべて破棄します。ユーザーがそのアイテムの音声を実際に聞いた時間をミリ秒単位で記録します。
  2. conversation.item.truncate イベントに、中断されたアイテムの ID と音声コンテンツのインデックスを指定し、audio_end_ms に記録した再生時間を設定して送信します。これで、未再生の部分が会話から削除されます。
  3. VAD によって進行中の応答が中断されると、サーバーはその応答をキャンセルし、response.done を送信します。このとき response.status は cancelled になります。このイベントは応答の終了を確認するためのものです。再生の停止や切り詰めイベントの送信は、このイベントを待たずに行ってください。

切り詰めイベントの例を示します。

{
  "type": "conversation.item.truncate",
  "item_id": "item_1234",
  "content_index": 0,
  "audio_end_ms": 1500
}

この例では、再生開始から 1.5 秒の位置で音声を切り詰めます。

文字起こしも同じ位置で切り詰められるでしょうか。Realtime モデルには、文字起こしと音声の位置を正確に対応付けるだけの情報がありません。そのため、conversation.item.truncate は指定した位置で音声を切り詰め、未再生部分の文字起こしを削除します。未再生の音声は会話から除かれますが、再生済みの部分だけに切り詰めた文字起こしは得られません。

プッシュトゥトーク

Realtime API はデフォルトで音声区間検出(VAD)を使用し、音声入力をきっかけにモデルが応答します。VAD を無効にし、モデルに音声入力を送るタイミングをアプリケーション側で制御すると、プッシュトゥトークも実装できます。たとえば、スペースキーを押している間だけ音声を取り込み、キーを離したら応答を開始します。この方式が特に有効なアプリもあります。ユーザー自身がやり取りのタイミングを決められ、VAD の検出ミスを避けられるうえ、VAD のタイムアウトを待たずに済むので軽快に感じられます。

プッシュトゥトークの実装方法は、WebSocket と WebRTC で少し異なります。Realtime API の WebSocket 接続では、すべてのイベントが同じチャネルを通り、送信順が保たれます。一方、WebRTC 接続では音声と制御イベントが別々のチャネルを通ります。

WebSocket

WebSocket 接続でプッシュトゥトークを実装する場合、クライアント側で音声の再生を停止し、割り込みを処理して、新しい応答を開始します。具体的な手順は次のとおりです。

  1. "turn_detection": null を設定した session.update イベントを送信し、VAD を無効にします。
  2. ボタンが押されたら、クライアントで音声の録音を開始します。
    1. モデルが応答を生成中の場合は、response.cancel イベントを送信してキャンセルします。
    2. モデルの出力音声を再生中の場合は、直ちに再生を停止し、conversation.item.truncate イベントを送信して、未再生の音声を会話から削除します。
  3. ボタンが離されたら、録音した音声を含む input_audio_buffer.append メッセージを送信し、新しい音声を入力バッファに追加します。
  4. input_audio_buffer.commit イベントを送信します。これで入力バッファに書き込んだ音声が確定し、入力の文字起こしが有効であれば開始されます。
  5. 続いて response.create イベントを送信し、応答を開始します。

WebRTC と SIP

WebRTC でプッシュトゥトークを実装する手順も同様ですが、入力音声バッファを明示的にクリアする必要があります。手順は次のとおりです。

  1. "turn_detection": null を設定した session.update イベントを送信し、VAD を無効にします。
  2. ボタンが押されたら、input_audio_buffer.clear イベントを送信し、それまでの音声入力をクリアします。
    1. モデルが応答を生成中の場合は、response.cancel イベントを送信してキャンセルします。
    2. モデルの出力音声を再生中の場合は、output_audio_buffer.clear イベントを送信して未再生の音声をクリアします。これにより、会話内の応答も切り詰められます。
  3. ボタンが離されたら、input_audio_buffer.commit イベントを送信します。これで入力バッファに書き込んだ音声が確定し、入力の文字起こしが有効であれば開始されます。
  4. 続いて response.create イベントを送信し、応答を開始します。