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

WebRTC

WebRTC を使って音声アプリケーションを接続します。

使用する API を選ぶと、接続手順とセッションイベントを確認できます。

ブラウザから GPT-Live への接続

ブラウザ、モバイル、サーバーの各アプリケーションで GPT-Live と音声をやり取りするには、WebRTC を推奨します。このガイドでは、ブラウザを例に接続手順を説明します。マイク入力と生成された音声は、接続時にネゴシエーションしたメディアトラックで送受信します。文字起こし、セッションの更新、タスクの委任に関する JSON イベントはデータチャネルで送受信します。

ブラウザは Session Description Protocol(SDP)のオファーを作成します。アプリケーションサーバーはプロジェクトの API キーを使って POST /v1/live/sessions にオファーを送信し、アンサーを受け取ります。キーとセッション設定は信頼できるサーバーで管理してください。

前提条件

以下を用意してください。

  • GPT-Live にアクセスできるプロジェクトの API キー
  • 使用する SDK のサンプルを実行できるサーバー環境(Node.js のサンプルには Node.js 22.6 以降が必要)
  • HTTPS または localhost でページを開き、マイクの使用を許可したブラウザ

このサンプルでは、Responses へのタスク委任に gpt-5.6-terra とホスト型ウェブ検索を使用します。バックエンドへの指示とアプリケーションのツールについては、タスクの委任とツールを参照してください。音声とバックエンドの使用量については、コストの最適化を参照してください。

接続の流れ

  1. ユーザーの操作をきっかけにマイクへのアクセス許可を求め、マイクのトラックをピア接続に追加します。
  2. SDP オファーを作成する前に、データチャネルを作成し、イベントリスナーを登録します。
  3. ローカル記述を設定し、ICE 候補の収集が完了するのを待ってから、オファーをサーバーに送信します。
  4. サーバーから、session と transport: { type: "webrtc", sdp: ... } を含む JSON を POST で OpenAI に送信します。
  5. 返された SDP アンサーをリモート記述として設定します。データチャネルで session.started を受信してから、アプリケーションのコマンドを送信します。

セッションは HTTP リクエストで開始します。データチャネルで session.start を送信しないでください。 サンプルの oai-events という文字列はデータチャネルのラベルです。

POST /v1/live/sessions で WebRTC セッションを作成すると、初期化時に音声利用時間 15 秒分の料金が請求されます。この金額はセッション開始後の利用時間に応じた料金に充当されるため、実際の利用時間に 15 秒分が加算されることはありません。料金の計算方法については、WebRTC の初期化料金を参照してください。

アプリケーションサーバーの作成

新しいディレクトリにサーバーのサンプルを保存し、サーバーの環境変数 OPENAI_API_KEY を設定します。Node.js の場合は server.mjs に保存し、openai と express を npm install openai express でインストールします。Python の場合は openai をインストールします。このサンプルは 127.0.0.1 で待ち受け、http://localhost:3000 からのセッションリクエストを受け付けます。また、実行ディレクトリから index.html を配信します。

Go の場合はサンプルを main.go として保存し、go mod init live-demo でモジュールを初期化してから、github.com/openai/openai-go/v3@v3.85.1 を go get でインストールします。Go 1.26 以降で go run . を実行します。Java の場合は Java 17 以降を使用し、Maven の依存関係に com.openai:openai-java:4.95.0 を追加して、LiveConnectionWebrtcExample クラスを実行します。Ruby 3.3 以降の場合は、openai のバージョン 0.110.0 と webrick を、それぞれ gem install openai -v 0.110.0 と gem install webrick でインストールします。サンプルを server.rb として保存し、ruby server.rb を実行します。

以下からサーバーで使用する言語を選んでください。どのサンプルもポート 3000 で index.html を配信し、同じ /api/session エンドポイントを提供します。Live に対応したバージョンの SDK を使用し、一度に実行するサンプルは 1 つだけにしてください。

import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");

app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
  response.type("html").send(await readFile(indexPath, "utf8"));
});

// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
  if (request.headers.origin !== origin) {
    response.status(403).json({ error: "Unexpected request origin" });
    return;
  }
  if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
    response.status(400).json({ error: "An SDP offer is required" });
    return;
  }
  if (!process.env.OPENAI_API_KEY) {
    response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
    return;
  }

  try {
    const result = await client.live.create({
      session: {
        model: "gpt-live-1",
        instructions:
          "Be concise. Delegate requests needing current information to the backend, which can search the web.",
        delegation: {
          type: "responses",
          responses: {
            model: "gpt-5.6-terra",
            instructions:
              "Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
            tools: [{ type: "web_search" }],
            tool_choice: "auto",
          },
        },
      },
      transport: {
        type: "webrtc",
        sdp: request.body.sdp,
      },
    });
    // Preserve the SDK's typed session ID and SDP answer.
    response.status(201).json(result);
  } catch (error) {
    if (!(error instanceof OpenAI.APIError)) throw error;
    console.error("Live session creation failed", error.status);
    response
      .status(error.status ?? 502)
      .json({ error: "Live session creation failed" });
  }
});

app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));

サーバーを他のユーザーに公開する前に、アプリケーションの認証、認可、リクエスト制限、HTTPS を使って /api/session を保護してください。このローカル用サンプルのオリジンチェックでは、ユーザーは認証されません。

ブラウザクライアントの作成

サーバーを実行するディレクトリに index.html を作成します。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>GPT-Live connection</title>
  </head>
  <body>
    <script type="module">
      // Paste the browser code below here.
    </script>
  </body>
</html>

モジュールスクリプト内に次のコードを貼り付けます。このコードは会話の開始・終了ボタンを追加し、マイク入力と音声出力を接続して、セッションイベントを処理します。/api/session はアプリケーションサーバーのルートです。

const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);

let peer;

let events;

let microphone;

let closeTimeout;
let ready = false;
let finalized = false;

function cleanup() {
  clearTimeout(closeTimeout);
  microphone?.getTracks().forEach((track) => track.stop());
  const closingEvents = events;
  events = undefined;
  closingEvents?.close();
  peer?.close();
  audio.srcObject = null;
  ready = false;
  start.disabled = false;
  stop.disabled = true;
}

start.addEventListener("click", async () => {
  start.disabled = true;
  finalized = false;
  status.textContent = "Connecting…";
  try {
    const connection = new RTCPeerConnection();
    peer = connection;
    connection.addEventListener("track", (event) => {
      audio.srcObject = new MediaStream([event.track]);
      audio.play().catch(() => {
        status.textContent =
          "Select play on the audio controls to hear the assistant.";
      });
    });
    microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
    for (const track of microphone.getAudioTracks()) {
      connection.addTrack(track, microphone);
    }

    // Create the event channel before creating the SDP offer.
    events = connection.createDataChannel("oai-events");
    events.addEventListener("message", ({ data }) => {
      const event = JSON.parse(data);
      if (event.type === "session.started") {
        ready = true;
        stop.disabled = false;
        status.textContent = "Connected: " + event.session.id;
      } else if (event.type === "session.closed") {
        finalized = true;
        console.log("Final session usage", event.usage);
        status.textContent = "Conversation ended.";
        cleanup();
      } else {
        // Save transcript and nested Responses events as needed by your app.
        console.log(event);
      }
    });
    events.addEventListener("close", (event) => {
      if (event.target !== events) return;
      if (!finalized) {
        status.textContent = "Disconnected without final session usage.";
        cleanup();
      }
    });

    const offer = await connection.createOffer();
    await connection.setLocalDescription(offer);
    if (connection.iceGatheringState !== "complete") {
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
          connection.removeEventListener("icegatheringstatechange", onState);
          reject(new Error("Timed out while gathering ICE candidates"));
        }, 10_000);
        function onState() {
          if (connection.iceGatheringState !== "complete") return;
          clearTimeout(timeout);
          connection.removeEventListener("icegatheringstatechange", onState);
          resolve(undefined);
        }
        connection.addEventListener("icegatheringstatechange", onState);
        onState();
      });
    }

    const sdp = connection.localDescription?.sdp;
    if (!sdp) throw new Error("Missing local SDP offer");
    const response = await fetch("/api/session", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ sdp }),
    });
    if (!response.ok) throw new Error(await response.text());

    const result = await response.json();
    console.log("Created session", result.session.id);
    await connection.setRemoteDescription({
      type: "answer",
      sdp: result.transport.sdp,
    });
    // The HTTP request started this session. Do not send session.start here.
  } catch (error) {
    status.textContent =
      error instanceof Error ? error.message : String(error);
    cleanup();
  }
});

stop.addEventListener("click", () => {
  if (!ready || !events || events.readyState !== "open") return;
  stop.disabled = true;
  status.textContent = "Finishing the conversation…";
  // The session.closed handler is already registered. Keep media and events
  // alive while pending work drains; only clean up after the final event.
  events.send(JSON.stringify({ type: "session.close" }));
  closeTimeout = setTimeout(() => {
    status.textContent = "Incomplete finalization: no session.closed event.";
    cleanup();
  }, 15_000);
});

選んだサーバーを起動し(node server.mjs または python server.py)、http://localhost:3000 を開いて Start conversation を選択します。ステータスが Connected に変わったら、最新情報が必要な質問をして、ホスト型ウェブ検索を試してください。ブラウザが自動再生をブロックした場合は、音声の再生コントロールを使用してください。

セッションレスポンスの読み取り

リクエストが成功すると、HTTP 201 とともに、セッション ID と SDP アンサーを含む JSON が返されます。

{
  "session": { "id": "live_123" },
  "transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}

result.session.id を読み取り、result.transport.sdp を setRemoteDescription に渡します。セッション ID の内部構造は解釈せず、プレフィックスも含めてそのまま保持してください。

接続開始時の遅延を減らす方法については、WARP を使った WebRTC 接続を参照してください。

メディアとイベントの処理

マイクの音声はメディアトラックで送信し、生成された音声もメディアトラックで受信します。WebRTC は SDP を使って音声形式をネゴシエーションするため、セッション設定では audio.format を省略してください。データチャネルで session.input_audio.append を送信しないでください。session.output_audio.delta もデータチャネルには届きません。

文字起こしの差分、セッションコマンド、ネストされた response.event メッセージにはデータチャネルを使用します。文字起こしの処理とライフサイクルイベントについては、セッションの管理を参照してください。サーバーにイベント用の独立した接続が必要な場合は、サーバー側の制御を参照してください。

会話を終了するには、session.close を送信し、session.closed が届くまでイベントの受信を続けてから、ピア接続を閉じてマイクのトラックを停止します。サンプルでは、コマンドを送信する前に最終イベントのリスナーを登録しています。最終イベントを受信する前に接続が失敗またはタイムアウトすると、最終的な使用量は確認できません。最終的な使用量の扱いについては、使用量と正常終了を参照してください。