アプリケーションで使用する API を選択します。認証、セッション作成、イベントの仕様は API ごとに異なります。
サーバーからの GPT-Live セッション制御
サーバーで会話イベントの受信、非公開ツールの実行、会話の更新が必要な場合は、アプリケーションサーバーを既存の GPT-Live の WebRTC または SIP セッションに接続します。この 2 つ目の接続を サイドバンド WebSocketと呼びます。両方の接続が 1 つのセッションを共有し、メインの音声は WebRTC または SIP が伝送します。
サイドバンドはイベントとコマンドを伝送します。ツールの実行、認可チェック、ビジネスルールはアプリケーション側で実装します。API キーとツールの認証情報はサーバー側で保持してください。
サイドバンドの必要性の判断
ブラウザアプリケーションでは、字幕とローカル UI の更新に WebRTC データチャネルを使用します。ガードレールのチェック、感情分析、先行的なツール呼び出しなど、文字起こしの処理をサーバーで実行する場合はサイドバンドを使用します。ブラウザの音声伝送には引き続き WebRTC を使いながら、サーバーでイベントを受信し、同じセッションを直接制御できます。例については、文字起こしの断片への対応を参照してください。
バックエンドがすでにメインの WebSocket 接続を管理している場合は、その接続でセッションのイベントを受信し、コマンドを送信できます。
Responses への委任はサイドバンドがなくても機能します。ブラウザはデータチャネルから関数呼び出しイベントを認証済みのバックエンドに転送し、実行を任せることができます。OpenAI がホストするツールは委任先のバックエンドで実行されるため、アプリケーション側にツールの実行機構は不要です。
既存セッションへの接続
-
バックエンドで制御するセッションの ID を保存します。WebRTC の場合は、
POST /v1/live/sessionsに対する JSON レスポンスのsession.idを使用します。SIP の場合は、まず着信を受け入れ、その Webhook のdata.session_idを使用します。この ID は、アプリケーションのユーザーと会話の記録に紐づけて保持してください。 -
保存した ID を変更せずに次の URL に埋め込み、サーバーから WebSocket 接続を開きます。セッションを作成または受け入れたプロジェクトの認証情報を使い、
Authorization: Bearer $OPENAI_API_KEYで認証します。セッション作成時に必要な接続ヘッダーも同じように含めてください。wss://api.openai.com/v1/live/sessions/{session_id}/attach -
接続したソケットでイベントを受信し、コマンドを送信します。セッションはすでに実行中なので、
session.startを再送信しないでください。
セッション ID は、内部構造を解釈せずに扱ってください。プレフィックスを保持し、アプリケーションにアクセスが許可されているセッションに対してのみ使用します。ID は Realtime の Location ヘッダーや call_id URL パラメーターではなく、Live の JSON レスポンスから読み取ってください。
イベントの監視とコマンドの送信
| タスク | イベントまたはコマンド |
|---|---|
| 会話の追跡 | ユーザーとアシスタントの文字起こしの差分、委任イベント、ネストされた Responses イベントを受信します。 |
| バックエンド構成の更新 | session.update を使用して、現在の委任モードで変更がサポートされている設定を更新します。フロントエンドのモデルや音声構成など、起動時の設定は固定されたままです。 |
| コンテキストの提供 | 指示には session.instructions.append、発話を伴わないコンテキストには session.thinking.append、発話可能な更新情報には session.commentary.append を使用します。 |
| ツール結果の返却 | Responses への委任では、response.item.create を送信した後に response.create を送信して、バックエンドの処理を続行します。 |
| マイク入力の制御 | session.input_audio.mute と session.input_audio.unmute を使用します。入力をミュートしても、アシスタントの出力は停止しません。 |
| セッションの終了 | 切断する前に、session.close を送信し、session.closed を受信します。 |
コマンドには、メインの接続と同じ検証ルールと委任ルールが適用されます。コンテキストを追加する際、セッション全般のコンテキストには delegation_id: null を使用します。null 以外の ID は、既存のクライアント委任を識別する必要があります。構成、関数の実行、コンテキスト追加の例については、委任とツールを参照してください。
ブラウザのセッションでは、マイク入力とスピーカー出力に、ネゴシエーション済みの WebRTC メディアトラックを引き続き使用します。サイドバンドは会話イベントと制御に使用します。文字起こしイベントやコマンドの確認応答があっても、音声が再生されたことや、ユーザーがその音声を聞いたことの証明にはなりません。
複製された音声の受信
メインの接続がライブメディアを伝送する一方、サイドバンドも接続後の入力音声と出力音声のコピーを受信します。
| イベント | 音声フィールド | 時刻情報 |
|---|---|---|
session.input_audio.append | audio | タイムスタンプはありません。 |
session.output_audio.delta | delta | start_ms と end_ms は、セッションのタイムライン上での出力の範囲を示します。 |
どちらのペイロードも、メインのトランスポートの音声形式にかかわらず、base64 エンコードされた 24 kHz のモノラル生 PCM16LE データです。どちらのイベントにも event_id はありません。複製された入力には、入力ミュートが適用される前に受信した音声が含まれますが、モデルがそのサンプルを処理したことを示すものではありません。複製された出力の範囲には、フレームの欠落による空白が生じる場合があり、通話者が音声を聞いた時刻を示すものでもありません。
これらはサーバーイベントであり、サイドバンドで音声を送信できるという意味ではありません。マイク音声はメインのトランスポートで送信し、追加接続したソケットでは session.input_audio.append を送信しないでください。
アクションごとの担当の一本化
各アクションをブラウザとバックエンドのどちらが処理するかを決めます。両方の接続が関数呼び出しイベントを受信しても、関数は 1 回だけ実行します。コンテキストの更新とバックエンド処理の続行リクエストにも、同じ担当ルールを適用してください。
文字起こしとツールの状態はアプリケーションに保存します。バックエンドで会話を最初から監視する必要がある場合は早期に接続し、接続前に収集した履歴も保持してください。接続すれば過去の文字起こしやツール結果を復元できるとは考えないでください。
サイドバンドを使用するだけでは、セッションのイベントがブラウザから見えなくなるわけではありません。機密性の高いツールの認証情報と認可の判断はバックエンドに留め、会話に必要なコンテキストだけを返してください。
会話へのガードレールの適用
サーバーの接続を使用して会話を監視し、リクエストがアプリケーションのポリシーに沿っているかをチェックして、問題を検出したら介入します。サイドバンドを通じて、サーバーはセッションのイベントとコマンドにアクセスできます。チェックの実行と結果に基づく制御はアプリケーションが担います。サーバーがすでにメインの WebSocket 接続を管理している場合も、同じワークフローを適用できます。
会話と並行したチェックの実行
ガードレールは、文字起こしの断片を受信するたびに処理する仕組みの活用例の 1 つです。同じストリームを使い、チェックと並行して先行的な情報検索を開始したり、UI を更新したりすることもできます。
- 文字起こしを監視します。
session.input_transcript.deltaの断片を蓄積し、ユーザーのリクエストにジェイルブレイクの試み、機密情報、ポリシー違反がないかをチェックします。session.output_transcript.deltaを使用して、アシスタントの発話に根拠のない主張やアプリケーションの範囲外の応答がないかをチェックします。各チェックは、評価対象の文字起こしとアプリケーションのリクエストに紐づけて保持してください。 - チェックを並行して実行します。 高速で軽量なモデルを使えば、会話を続けながらリクエストを評価できます。
{"triggered": true}など、アプリケーションが処理に利用できる小さな構造化された結果を返します。承認が必要なアクションは、チェックに合格するまでブロックしたままにしてください。タイムアウトやチェックの失敗を承認として扱ってはいけません。 - 該当するアクションをブロックします。 チェックで問題を検出したら、アプリケーションの状態にそのリクエストがブロックされたことを記録します。すでにキューに入っている処理も含め、ツールの実行や変更の確定の前に、その状態を確認してください。音声で拒否を伝えるだけでは、ツールの実行を防げません。
- 関連する処理を停止します。 バックエンドがキャンセルをサポートしている場合は、アプリケーションが管理するジョブをキャンセルし、ブロック済みのリクエストや新しいリクエストに置き換えられたリクエストから遅れて届いた結果を破棄します。Responses への委任では、該当するカスタム関数の実行を停止し、ブロックした処理を続行するための
response.createを送信しないでください。これによって、ホスト側ですでに実行中のレスポンスがキャンセルされたり、フロントエンドの発話が停止したりするわけではありません。 - 判断を記録し、会話を軌道修正します。 該当するリクエストと委任の ID とともに判断をログに記録し、修正指示を送信します。
guardrail.triggeredのようなイベント名は、アプリケーションのテレメトリに属するものであり、GPT-Live API のイベントではありません。
断片の収集については文字起こしの差分を、バックエンドの結果を現在のタスクに沿ったものに保つ方法については委任とツールを参照してください。
会話の軌道修正
ガードレールに沿って会話を制御するには、session.instructions.append を使用します。進行中の発話を中断し、新しい指示を適用できます。たとえば、アプリケーションがリクエストをブロックした後に、次の内容を送信します。
export function sendUpdate(connection) {
connection.send({
type: "session.instructions.append",
event_id: "guardrail_block_17",
delegation_id: null,
content:
"Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.",
});
}指示には、アプリケーション側で作成した内容を使用してください。信頼できないユーザーテキストを指示としてコピーしないでください。このセッション全体に対する修正には delegation_id: null を使用し、content は 500 トークン以内に収めてください。
client_event_id を使って、session.instructions.appended を送信したコマンドと対応付けます。確認応答は、コンテキストの挿入が完了したと推定される時点より後に届きますが、アシスタントの発話やキュー内の音声の再生が停止したことを保証するものではありません。修正指示を送っても、ユーザーがすでに聞いた音声を取り消すことはできません。
特定の文言を読み上げる開示にも、指示を使用します。例と再生時の考慮事項については、開示事項の読み上げを参照してください。
必要に応じた再生制御
まず、修正指示とアクションのブロックをテストします。アプリケーションでモデルの音声もブロックする必要がある場合は、クライアントまたはメディアリレーで出力を制御します。出力を一時的にミュートするか破棄し、ローカルのキュー内の音声を破棄してから修正指示を送信し、アプリケーションの復旧ポリシーに従って再生を再開します。再開する前に、古い音声を消去してください。サイドバンドだけではメディア経路を制御できません。また、指示の確認応答は、再生を再開してよいという合図ではありません。
session.input_audio.mute は、発信者のマイク入力を制御します。モデルの出力をミュートしたり、委任した処理をキャンセルしたりするものではありません。
GPT-Live は、発話しながら文字起こしの断片をストリーミングします。ユーザーが音声を聞く前にチェックを完了する必要がある場合、アプリケーション側で音声をバッファリングし、承認してから再生する必要があります。その分、遅延が増えます。また、音声の再生を抑止すると、モデルの会話コンテキストがユーザーの聞いた内容より先に進んでしまう場合があるため、会話がどのように再開されるかをテストしてください。
介入のテスト
許可されるリクエストとブロックされるリクエスト、誤検知、チェックの遅延や失敗、発話中の介入の発動、ツール実行中の介入の発動、キャンセルした処理から遅れて届く結果をテストします。アクションのブロック、アプリケーションの状態、修正指示に基づく発話、実際の再生をそれぞれ個別に検証してください。出力を制御する場合は、キュー内の音声と復旧もテストに含めます。タスクの成功と音声での応答時間を比較するには、音声エージェント評価の Cookbookを使用してください。
適切な終了処理
バックエンドがツールの実行や最終的な使用量の収集を担っている間は、イベントの受信を続けます。session.close を送信する前に session.closed のハンドラーを登録し、保留中の処理がすべて完了するまで WebRTC 接続、データチャネル、サイドバンドを開いたままにしてください。クリーンアップの前に、セッションの最終的な使用量と、Responses イベントで受信したバックエンドの使用量をすべて保存します。最後のイベントが届く前に接続が失敗した場合は、終了処理を未完了として記録してください。終了の手順については、セッションの管理を参照してください。
Realtime API では、クライアントから WebRTC または SIP 経由で API サーバーに直接接続できます。ただし、多くの場合、ツールの使用やその他のビジネスロジックはアプリケーションサーバーに配置し、非公開かつクライアントに依存しない状態にしておくことが望ましいでしょう。
「サイドバンド」制御チャネル経由で接続すると、ツールの使用、ビジネスロジック、その他の詳細をサーバー側で安全に管理できます。現在、SIP と WebRTC の両方の接続でサイドバンドを利用できます。
サイドバンド接続を使うと、同じ Realtime セッションに対して、ユーザーのクライアントからの接続とアプリケーションサーバーからの接続の 2 つが同時にアクティブになります。サーバー側の接続では、セッションの監視、指示の更新、ツール呼び出しへの応答ができます。
WebRTC の場合
- ピア接続を確立する際には、Realtime API から SDP 応答を取得し、それを使って接続を設定します。WebRTC ガイドのサンプルコードを使用した場合、次のようになります。
const baseUrl = "https://api.openai.com/v1/realtime/calls";
const sdpResponse = await fetch(baseUrl, {
method: "POST",
body: offer.sdp,
headers: {
Authorization: `Bearer ${EPHEMERAL_KEY}`,
"Content-Type": "application/sdp",
},
});- 取得した応答には、一意の通話 ID を持つ
Locationヘッダーが含まれます。サーバーでは、この ID を使って同じ Realtime セッションへの WebSocket 接続を確立できます。
// Location: /v1/realtime/calls/rtc_123456
const location = sdpResponse.headers.get("Location");
const callId = location?.split("/").pop();
console.log(callId);- その後、サーバーでは、以下のようにその通話 ID を URL
wss://api.openai.com/v1/realtime?call_id=rtc_xxxxxに指定して、通常の Realtime API の WebSocket 接続と同じようにイベントのリッスンとセッションの設定を行えます。
import WebSocket from "ws";
const callId = "rtc_u1_9c6574da8b8a41a18da9308f4ad974ce";
// Connect to a WebSocket for the in-progress call
const url = "wss://api.openai.com/v1/realtime?call_id=" + callId;
const ws = new WebSocket(url, {
headers: {
Authorization: "Bearer " + process.env.OPENAI_API_KEY,
},
});
ws.on("open", function open() {
console.log("Connected to server.");
// Send client events over the WebSocket once connected
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
instructions: "Be extra nice today!",
},
})
);
});
// Listen for and parse server events
ws.on("message", function incoming(message) {
console.log(JSON.parse(message.toString()));
});このように、クライアント側でこれらのアクションを設定する必要なく、サーバー側でツールの追加、セッションの監視、ビジネスロジックの実行ができます。
SIP の場合
- ユーザーが SIP を使い、電話で OpenAI に接続します。
- OpenAI は、アプリケーションサーバーの Webhook URL に Webhook を送信し、セッションの状態をアプリに通知します。Webhook は次のような形式です。
POST https://my_website.com/webhook_endpoint
user-agent: OpenAI/1.0 (+https://platform.openai.com/docs/webhooks)
content-type: application/json
webhook-id: wh_685342e6c53c8190a1be43f081506c52 # unique id for idempotency
webhook-timestamp: 1750287078 # timestamp of delivery attempt
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= # signature to verify authenticity from OpenAI
{
"object": "event",
"id": "evt_685343a1381c819085d44c354e1b330e",
"type": "realtime.call.incoming",
"created_at": 1750287018, // Unix timestamp
"data": {
"call_id": "some_unique_id",
"sip_headers": [
{ "name": "From", "value": "sip:+142555512112@sip.example.com" },
{ "name": "To", "value": "sip:+18005551212@sip.example.com" },
{ "name": "Call-ID", "value": "03782086-4ce9-44bf-8b0d-4e303d2cc590"}
]
}
}
- アプリケーションサーバーは、Webhook で提供された
call_idの値を使い、wss://api.openai.com/v1/realtime?call_id={callId}のような URL で Realtime API への WebSocket 接続を開きます。この WebSocket 接続は、SIP 通話が続いている間、維持されます。
その後は、WebSocket 接続でセッションを開始した場合と同じように、この WebSocket 接続でイベントを送受信して通話を制御できます。具体的には、通話の監視、指示の動的な更新、ツール呼び出しへの応答などができます。