すべての記事
OpenAIRealtime APIWebSocketAI

OpenAI Realtime APIをトンネル経由でローカルテスト

OpenAI Realtime APIはWebSocketまたはWebRTCでクライアントを接続します。サーバー側の接続なら、インターネットがあればどのマシンからでもOpenAIに直接アクセスできます。クライアント側の接続 — これでまさに作りたいものの大半です — では、ブラウザに安全なoriginが必要で、バックエンドがエフェメラルトークンを発行する必要があります。どちらもトンネル経由で動きます。知っておくべき詳細がいくつかあります。

2つの接続方法、トンネルする2つの理由

Realtime APIはサーバー側接続(バックエンドがOpenAIへWebSocketを開く)とクライアント側接続(バックエンドが発行したエフェメラルトークンを使い、ブラウザがWebRTCピア接続またはWebSocketを開く)をサポートします。サーバー側の経路はトンネル不要。クライアント側はほぼ必ず必要です。

クライアント側にHTTPSが必要な理由:

  • ブラウザのマイク権限は安全なoriginを要求します。
  • WebRTCのgetUserMediaとICE処理はHTTPSで最もうまく動きます。
  • (あれば)Service Workerに必要です。
  • エフェメラルトークンを得るためにバックエンドの/sessionエンドポイントを呼ぶなら、フロントがHTTPSならそのバックエンドもHTTPSでなければなりません。

1つ目はブラウザフラグで回避できます。やめましょう。トンネルを使って現実世界で生きましょう。

開発時のRealtimeアプリの構造

最小限のrealtimeアプリは3つの部分から成ります。

  1. マイク入力を捕捉しオーディオ出力を再生するフロントエンド。
  2. 本物のAPIキーでOpenAIの/v1/realtime/sessionsエンドポイントを叩き、エフェメラルトークンを発行するバックエンドのルート。
  3. エフェメラルトークンを使った、ブラウザからOpenAIへの直接のWebRTCまたはWebSocket接続。

トンネルはフロントエンド+バックエンド(多くの構成では同一ドメイン)を公開します。実際のrealtimeトラフィックはトンネルを通らずブラウザ-OpenAI間を流れます — その接続はHTTPS/WSSで直接です。

PortPreviewでのクイックセットアップ

  1. dev サーバー(Next.js、Viteなど)をポート3000または5173で起動します。
  2. npx portpreview 3000を実行します。
  3. ブラウザでHTTPS URLを開きます。マイク権限のプロンプトは通常どおり動きます。
  4. トークン発行エンドポイントを叩き、エフェメラルトークンを取得し、realtime接続を開きます。
  5. 話すと、ストリーミングされた応答が返ります。

Viteを使うなら、Vite + トンネルallowedHostsとHMR設定が必要です。Next.jsでどこかにedge runtimeがあるなら、Node cryptoを必要とするルートのruntime設定に注意してください。

トンネル越しのWebSocketアップグレード

realtime接続はWebSocket(またはほぼトンネルを迂回するWebRTC)です。使うトンネルはHTTPのUpgradeリクエストを正しく転送する必要があります。ほとんどは転送します — PortPreview、Cloudflare quick tunnels、ngrokはどれもWebSocketアップグレードを処理します。転送しないと、接続はブラウザで静かに失敗し、汎用的なWebSocket closed before connectedメッセージだけで有用な詳細はありません。

不安ならwscatでアップグレードをテストしましょう。wss://your-tunnel.portpreview.dev/anythingに接続し、ハンドシェイクを観察します。

役立つ開発のコツ

トークン発行リクエストを調べる

フロントエンドはOpenAIを呼ぶバックエンドのルートを呼びます。ここで最も多いdevバグは、/v1/realtime/sessionsへの設定ミスのリクエストです — モデル名が違う、voiceが違う、modalitiesが違う。トンネルはフロント→バックエンドのリクエストは捕捉しますが、バックエンド→OpenAIのホップは捕捉しません。そのリクエストはサーバー側でログするか、両層をローカルで動かしてトンネル経由でプロキシしましょう。

オーディオ形式の不一致

Realtime APIはデフォルトで設定可能なサンプルレートのPCM16を使います。別の形式のオーディオを流し込むと、モデルは拒否するか幻覚を起こします。ブラウザのMediaRecorderはたいていOpusかWebMを返します — 変換を処理するか、それを代わりにやってくれるWebRTCの経路を使う必要があります。

長いセッション中のトークン失効

エフェメラルトークンは短命です。トークンのTTLより長いセッションをテストするなら、スケジュールに沿って更新します。Realtime SDKにはそのためのヘルパーがあります。自前でやるならsession.expiredイベントを監視し、古いトークンが死ぬ前に新しいものを発行します。

本番ではどう見えるか

開発から本番への唯一の実質的な変更はURLです。トンネルのホスト名を本物のドメインに置き換えます。トークン発行エンドポイントはバックエンドに残ります。ブラウザからOpenAIへのWebRTC/WebSocket接続は同じコードパスです。これを考えすぎてrealtimeトラフィックをバックエンド経由でプロキシしようとするチームを見てきました — やめましょう。レイテンシが増え、OpenAIのSDKが前提とするモデルを壊します。

Anthropicの並行的な提供(ストリーミングWebSocketよりtool useループに近い)については、Anthropicのtool useとWebhookをローカルでを参照。WebSocketアップグレードをデフォルトで処理するトンネルのために、PortPreviewのウェイトリストに登録してください。

よくある質問

OpenAI Realtime APIをlocalhostでテストできますか?
サーバー側接続はトンネル不要です。ブラウザからのクライアント側接続は、マイク権限とエフェメラルトークンのエンドポイントのためにHTTPSが必要です。トンネルでdevサーバーをHTTPSで公開し、ブラウザをエフェメラルトークンで直接OpenAIに接続してください。
Realtime APIのためにトンネルはWebSocketをサポートする必要がありますか?
WebSocketトランスポートを使うなら、はい — トンネルはHTTPアップグレードリクエストを転送する必要があります。多くの最新トンネルは対応しますが、幻の接続バグを追う前にwscatでテストする価値があります。WebRTCはrealtimeトラフィック自体についてはトンネルを迂回します。
なぜブラウザのrealtime接続にエフェメラルトークンが必要なのですか?
完全なAPIキーをブラウザに置くのはセキュリティの大惨事です。バックエンドが1つのrealtimeセッションにスコープされた短命のエフェメラルトークンを発行します。ブラウザはそのトークンで、本物のAPIキーを一切見ることなく直接OpenAIに接続します。