リアルタイムトランスポート
セッションを実行する場所と、元のメディアやイベントをどの程度制御する必要があるかに基づいて、トランスポートを選択します。
| シナリオ | 推奨トランスポート | 理由 |
|---|---|---|
| ブラウザーの音声対音声アプリ | OpenAIRealtimeWebRTC | 最も簡単な方法です。SDK がマイク入力、再生、WebRTC 接続を管理します。 |
| サーバー側で Realtime を制御するブラウザー音声 | アプリ所有の WebRTC 音声とサーバー側の RealtimeSession | ブラウザーで音声を伝送しながら、Realtime イベント、ツール、ビジネスロジックをアプリケーションサーバー上に維持します。 |
| React Native モバイルアプリ | ネイティブ WebRTC を基盤とするアプリ所有の RealtimeTransportLayer | SDK が React Native のパッケージ条件を提供し、アプリがネイティブ WebRTC、権限、音声ルーティング、ライフサイクルを管理します。 |
| サーバー側の音声ループまたはカスタム音声パイプライン | OpenAIRealtimeWebSocket | 音声のキャプチャと再生をすでに制御しており、イベントへ直接アクセスしたい場合に適しています。 |
| SIP またはテレフォニーブリッジ | OpenAIRealtimeSIP | callId を使用して、RealtimeSession を既存の SIP 起点の Realtime 通話に接続します。 |
| Cloudflare Workers / workerd | Cloudflare 拡張トランスポート | workerd では、グローバルな WebSocket コンストラクターを使用して外向きの WebSocket 接続を開けません。 |
| Twilio 上のプロバイダー固有の電話フロー | Twilio 拡張トランスポート | Twilio の音声転送と割り込み処理を管理します。 |
デフォルトのトランスポートレイヤー
Section titled “デフォルトのトランスポートレイヤー”ブラウザーのデフォルト選択肢としての WebRTC
Section titled “ブラウザーのデフォルト選択肢としての WebRTC”デフォルトのブラウザートランスポートは WebRTC を使用します。音声はマイクからキャプチャされて自動的に再生されるため、クイックスタートでは一時トークンと session.connect(...) だけで接続できます。
この方法では、session.connect() は解決する前に、初期セッション設定が session.updated によって確認応答されるまで待機しようとします。そのため、音声の伝送が始まる前に指示とツールが適用されます。確認応答が届かなかった場合に備えて、タイムアウトによるフォールバックも用意されています。
独自のメディアストリームまたは音声要素を使用するには、セッションの作成時に OpenAIRealtimeWebRTC インスタンスを指定します。
import { RealtimeAgent, RealtimeSession, OpenAIRealtimeWebRTC,} from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',});
async function main() { // Keep a handle on the stream you pass in. The transport does not stop a caller-supplied // stream on close(), so your application owns it and is responsible for ending it. const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, });
const transport = new OpenAIRealtimeWebRTC({ mediaStream, audioElement: document.createElement('audio'), });
const customSession = new RealtimeSession(agent, { transport });
// Later, once the application is actually done with the microphone, release it. Leaving this // out keeps the microphone indicator on even after the session closes. // mediaStream.getTracks().forEach((track) => track.stop());}OpenAIRealtimeWebRTC に渡した mediaStream の所有権はアプリケーションに残ります。トランスポートを閉じても、そのストリームのトラックは停止しないため、レベルメーター、レコーダー、再接続に引き続き使用できます。アプリケーションでストリームの使用を完全に終えた時点で、そのトラックの stop() を呼び出してください。トランスポート自体がデフォルトのマイクを開いた場合は、close() によって SDK 所有のトラックが停止します。
より低レベルのカスタマイズのために、OpenAIRealtimeWebRTC は changePeerConnection も受け付けます。これにより、オファーが生成される前に、新しく作成された RTCPeerConnection を検査または置換できます。
組み込みのブラウザートランスポートは、ブラウザー側の RealtimeSession が Realtime イベントを使用してセッションを設定および制御するため、データチャネルを作成します。アプリケーションでこれらのイベントと制御をアプリケーションサーバー上に維持する必要がある場合は、ブラウザー側のピア接続のカスタマイズをアクセス制御境界として扱うのではなく、以下のサーバー側制御パターンを使用してください。
サーバー側で制御するブラウザー音声
Section titled “サーバー側で制御するブラウザー音声”一部のアプリケーションでは、Realtime イベント、ツール、ビジネスロジックをアプリケーションサーバー上に維持しながら、ブラウザーのマイク入力と音声再生を使用する必要があります。このアーキテクチャでは、Realtime API の統合 WebRTC インターフェースとサーバー側制御を組み合わせます。
- ブラウザーは標準の WebRTC API を使用して音声専用のピア接続を作成し、その SDP オファーをアプリケーションサーバーへ送信します。ブラウザーは SDK の組み込み WebRTC トランスポートを使用せず、Realtime データチャネルも作成しません。
- アプリケーションサーバーはユーザーを認証し、SDP ポリシーを適用して、オファーとサーバー所有のセッション設定を標準の API キーを使用して
/v1/realtime/callsへ送信します。 - アプリケーションサーバーは SDP アンサーを検証し、レスポンスの
Locationヘッダーから通話 ID を読み取ります。 - アプリケーションサーバーは WebSocket トランスポートを使用するサーバー側の
RealtimeSessionを作成し、そのイベントリスナーを登録して、通話 ID をsession.connect(...)に渡し、最初のsession.updatedイベントを待機します。 - アプリケーションサーバーは SDP アンサーをブラウザーへ返します。ブラウザーはアンサーをリモート記述として設定し、音声はブラウザーと Realtime API の間で直接伝送されます。
- アプリケーションサーバーは Realtime イベントとツールを処理します。ブラウザーでステータス更新が必要な場合、アプリケーションサーバーは元の Realtime イベントストリームを転送するのではなく、許可リストに登録されたアプリケーションイベントをブラウザー向けに投影します。
音声専用のブラウザーピア接続、サーバーで適用される SDP ポリシー、サイドバンドの RealtimeSession、ブラウザー向けに投影されるイベント、クリーンアップ動作を備えた完全なアプリケーションについては、realtime-server-controlled のコード例を参照してください。
アプリ所有のトランスポートを使用する React Native
Section titled “アプリ所有のトランスポートを使用する React Native”@openai/agents-core と @openai/agents-realtime は React Native のパッケージ条件を公開しているため、Metro は Node.js の組み込み機能を使用せずに、ポータブルな SDK シムを解決できます。組み込みの OpenAIRealtimeWebRTC はブラウザーの WebRTC グローバルと DOM 音声要素に依存するため、引き続きブラウザー向けトランスポートです。React Native では、ネイティブ WebRTC 実装をインストールしてアプリで管理し、カスタム RealtimeTransportLayer を介して RealtimeSession に接続します。
examples/realtime-react-native の Expo 開発ビルドのコード例では、アプリ所有の react-native-webrtc トランスポート、マイク権限、音声ルーティング、サーバー側の一時トークンエンドポイントを示しています。react-native-webrtc にはネイティブコードが含まれるため、Expo Go はサポートされていません。標準の OpenAI API キーはサーバー上に保持し、有効期間の短い一時クライアントトークンのみをモバイルアプリに渡してください。
サーバーのデフォルト選択肢としての WebSocket
Section titled “サーバーのデフォルト選択肢としての WebSocket”WebRTC の代わりに WebSocket 接続を使用するには、セッションの作成時に transport: 'websocket' または OpenAIRealtimeWebSocket のインスタンスを渡します。これは、サーバー側のユースケース、テレフォニーブリッジ、カスタム音声パイプラインに適しています。
WebSocket を使用する場合、ソケットが開いて初期設定が送信されると session.connect() が解決します。対応する session.updated イベントは少し遅れて届く場合があるため、connect() の解決時点でその更新がすでに返されたとは想定しないでください。
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',});
const myRecordedArrayBuffer = new ArrayBuffer(0);
const wsSession = new RealtimeSession(agent, { transport: 'websocket', model: 'gpt-realtime-2.1',});await wsSession.connect({ apiKey: process.env.OPENAI_API_KEY! });
wsSession.on('audio', (event) => { // event.data is a chunk of PCM16 audio});
wsSession.sendAudio(myRecordedArrayBuffer);任意の録音および再生ライブラリを使用して、元の PCM16 音声バイトを処理します。
高度な連携では、OpenAIRealtimeWebSocket の createWebSocket() に独自のソケット実装を指定できます。また、カスタムコネクターがソケットを接続済み状態へ遷移させる役割を担う場合は、skipOpenEventListeners を使用できます。@openai/agents-extensions の Cloudflare トランスポートは、これらのフックを基盤としています。
通話プロバイダーとテレフォニーブリッジ向けの SIP
Section titled “通話プロバイダーとテレフォニーブリッジ向けの SIP”RealtimeSession を既存の SIP 起点の Realtime 通話に接続する場合は、OpenAIRealtimeSIP を使用します。これは SIP に対応した軽量なトランスポートです。音声は SIP 通話自体によって処理され、callId を使用して SDK セッションを接続します。
OpenAIRealtimeSIP.buildInitialConfig()で初期セッション設定を生成し、着信通話を受け入れます。これにより、SIP の招待と後続の SDK セッションが同じデフォルト設定から開始されます。OpenAIRealtimeSIPトランスポートを使用するRealtimeSessionを接続し、プロバイダーの Webhook で発行されたcallIdを使用して接続します。- プロバイダー固有のメディア転送またはイベントブリッジが必要な場合は、Twilio 拡張などの連携トランスポートを使用します。
import OpenAI from 'openai';import { OpenAIRealtimeSIP, RealtimeAgent, RealtimeSession, type RealtimeSessionOptions,} from '@openai/agents/realtime';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, webhookSecret: process.env.OPENAI_WEBHOOK_SECRET!,});
const agent = new RealtimeAgent({ name: 'Receptionist', instructions: 'Welcome the caller, answer scheduling questions, and hand off if the caller requests a human.',});
const sessionOptions: Partial<RealtimeSessionOptions> = { model: 'gpt-realtime-2.1', config: { audio: { input: { turnDetection: { type: 'semantic_vad', interruptResponse: true }, }, }, },};
export async function acceptIncomingCall(callId: string): Promise<void> { const initialConfig = await OpenAIRealtimeSIP.buildInitialConfig( agent, sessionOptions, ); await openai.realtime.calls.accept(callId, initialConfig);}
export async function attachRealtimeSession( callId: string,): Promise<RealtimeSession> { const session = new RealtimeSession(agent, { transport: new OpenAIRealtimeSIP(), ...sessionOptions, });
session.on('history_added', (item) => { console.log('Realtime update:', item.type); });
await session.connect({ apiKey: process.env.OPENAI_API_KEY!, callId, });
return session;}Cloudflare Workers と workerd
Section titled “Cloudflare Workers と workerd”Cloudflare Workers およびその他の workerd ランタイムでは、グローバルな WebSocket コンストラクターを使用して外向きの WebSocket 接続を開けません。拡張パッケージの Cloudflare トランスポートを使用してください。このトランスポートは、内部で fetch() ベースのアップグレードを実行します。
import { CloudflareRealtimeTransportLayer } from '@openai/agents-extensions';import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'My Agent',});
// Create a transport that connects to OpenAI Realtime via Cloudflare/workerd's fetch-based upgrade.const cfTransport = new CloudflareRealtimeTransportLayer({ url: 'wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1',});
const session = new RealtimeSession(agent, { // Set your own transport. transport: cfTransport,});完全な設定については、Cloudflare 上の音声エージェントを参照してください。
Twilio の電話通話
Section titled “Twilio の電話通話”元の WebSocket または @openai/agents-extensions の専用 Twilio トランスポートを使用して、RealtimeSession を Twilio に接続できます。Twilio Media Streams の割り込みタイミングと音声転送を SDK で処理する場合は、専用トランスポートがより適切なデフォルトです。
完全な設定については、Twilio 上の音声エージェントを参照してください。
独自トランスポートの導入
Section titled “独自トランスポートの導入”別の音声対音声 API または独自のカスタムトランスポート機構を使用する場合は、RealtimeTransportLayer インターフェースを実装し、RealtimeTransportEventTypes イベントを自分で送出できます。
必要に応じた元の Realtime イベントへのアクセス
Section titled “必要に応じた元の Realtime イベントへのアクセス”基盤となる Realtime API により直接的にアクセスするには、2 つの方法があります。
オプション 1 - トランスポートレイヤーへのアクセス
Section titled “オプション 1 - トランスポートレイヤーへのアクセス”RealtimeSession のすべての機能を引き続き利用する場合は、session.transport を介してトランスポートレイヤーにアクセスできます。
トランスポートレイヤーは受信したすべてのイベントを * イベントとして送出し、sendEvent() を使用して元のイベントを送信できます。* リスナーは、現在の SDK スキーマが認識していないフィールドを含む、解析済みの元の JSON ペイロードの構造化クローンを受け取ります。名前付きイベントリスナーと派生イベントリスナーは、引き続き検証および正規化されたイベントオブジェクトを受け取ります。これは、session.update、response.create、response.cancel などの低レベル操作に使用するエスケープハッチです。
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime-2.1',});
session.transport.on('*', (event) => { // Event received from the underlying Realtime transport});
// Send any valid client event, for example, to trigger a new responsesession.transport.sendEvent({ type: 'response.create', // ...});オプション 2 - トランスポートレイヤーのみの使用
Section titled “オプション 2 - トランスポートレイヤーのみの使用”ツールの自動実行、ガードレール、ローカル履歴管理が不要な場合は、接続と割り込みのみを管理する「軽量な」クライアントとしてトランスポートレイヤーを使用することもできます。
import { OpenAIRealtimeWebRTC } from '@openai/agents/realtime';
const client = new OpenAIRealtimeWebRTC();const audioBuffer = new ArrayBuffer(0);
await client.connect({ apiKey: '<api key>', model: 'gpt-realtime-2.1', initialSessionConfig: { instructions: 'Speak like a pirate', outputModalities: ['audio'], audio: { input: { format: 'pcm16', }, output: { format: 'pcm16', voice: 'ash', }, }, },});
// Listen for audio when you manage playback yourselfclient.on('audio', (newAudio) => {});
client.sendAudio(audioBuffer);