
OpenAI Realtime APIで音声エージェントを構築:2026年版7ステップ実装ガイド
私たちのテストエージェントは、発信者が話し終えてから1.1秒後に最初の言葉を発してTwilioの応答を行いました。これはgpt-realtime-2とsemantic_vadを使用し、40回の通話で測定したp50(中央値)の往復時間です。魔法ではありません。OpenAI Realtime APIは1つのソケット内で音声対音声(speech-to-speech)処理を行うため、STT → LLM → TTSというリレー処理をスキップでき、約600msのオーバーヘッドを削減できます。しかし、デフォルト設定だけでは1秒以内には到達しません。以下は、コードとレイテンシ表とともに私たちが本番環境で導入した7ステップの構築プロセスです。
これは概念解説ではなく、実践的な構築チュートリアルです。まず階層的な概要を知りたい場合は、AI音声エージェントとは何かを読んでから戻ってきてください。以下の内容はすべて、OpenAI APIキーとNode実行環境を持っていることを前提としています。
主なポイント:
- gpt-realtime-2は1つのソケットで音声対音声処理を行い、STT/LLM/TTSのリレーが不要で約600msを節約。
- **一時鍵(ephemeral keys)**はサーバー側で発行し、標準APIキーをブラウザに渡さないこと。
- Twilioのメディアストリームは8kHz μ-lawなので、Realtime API用に24kHz PCM16へリサンプリングする。
- 私たちの測定では、往復時間がp50で1.1秒 / p95で1.9秒。割り込み(barge-in)は
response.cancelで処理。
7ステップで構築するもの
このチュートリアルでは、1.5秒以内に返信し、会話中に実際の関数を呼び出し、発信者による割り込みを許可するOpenAI Realtime API音声エージェントを構築します。フローはシンプルです。発信者が電話番号にかけると、音声がサーバーにストリーミングされ、サーバーは単一のソケット経由でそれをgpt-realtime-2にブリッジします。モデルは発話を行い、ツール呼び出しを実行でき、音声は再びストリーミングされて戻ります。
以下がその道筋です。ユースケースに合わせて任意のステップで止めることができます。
- 一時鍵を発行する(サーバールート)
- セッションを開き、設定する
- 関数呼び出しを追加する
- Twilioで電話番号にブリッジする
- 割り込み(barge-in)と中断を処理する
- レイテンシをサブ秒級に調整する
- デプロイと強化
オーディオを運ぶトランスポートは3種類あり、選択はオーディオのソースによって異なります。ブラウザが直接キャプチャする場合(WebRTC)、サーバーがすでに生ストリームを持っている場合(WebSocket)、または電話網が配信する場合(SIP)です。ここではTwilioブリッジにWebSocketを使用し、他のものが適合する箇所ではそれらについても言及します。
ステップ1:一時鍵を発行する(省略できないルート)
標準的なOpenAI APIキーをブラウザやクライアントデバイスに公開してはいけません。Realtime APIはまさにこのために短命な**一時鍵(ephemeral keys)**を発行します。サーバーは実際のキーを使用してPOST /v1/realtime/client_secretsを呼び出し、約1分で期限切れになるトークンをクライアントに渡し、クライアントはそのトークンを使用して接続します。
以下は、一時鍵を発行する最小限のExpressルートです。
// server.js
import express from "express";
const app = express();
app.get("/session", async (req, res) => {
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: { type: "realtime", model: "gpt-realtime-2" },
}),
});
const data = await r.json();
res.json({ client_secret: data.value, expires_at: data.expires_at });
});
app.listen(3000);ブラウザは/sessionを取得し、短命なシークレットを読み取り、それを使用してRealtime接続を開きます。エージェントがサーバー専用(ステップ4のTwilioの場合)である場合は、クライアントへの受け渡しをスキップし、バックエンドから標準キーを直接使用してソケットを開くことができます。一時鍵のフローは、信頼できないクライアントを保護するために存在します。
ステップ2:セッションを開き、gpt-realtime-2を設定する
接続を開き、モデル、オーディオ形式、音声、ターン検出を設定するsession.updateを送信します。OpenAIのドキュメントでは、ツールのロジックにより高い精度が必要でない限り、reasoning.effortをlowに設定して開始することを推奨しています。高いeffortはレイテンシのコストとなるためです。オーディオは双方向で24kHz PCM16として動作します。
ws.send(JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2",
output_modalities: ["audio"],
audio: {
input: { format: "pcm16", sample_rate: 24000 },
output: { format: "pcm16", sample_rate: 24000, voice: "marin" },
},
instructions: "You are a reservations agent for a restaurant. Be brief.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));そのソケットをどのトランスポートで包むかは、オーディオソースによって異なります。
| トランスポート | 使用場面 | オーディオソース |
|---|---|---|
| WebRTC | ブラウザまたはモバイルアプリがマイクを直接キャプチャする場合 | クライアントデバイス |
| WebSocket | サーバーがすでに生のオーディオストリームを保持している場合 | サーバーパイプライン |
| SIP | OpenAIに電話回線の処理を行わせたい場合 | PSTN / 電話通信 |
完全なセッションフィールドリストとGA機能セットについては、OpenAI Realtime APIドキュメントが正典です。ステップ4でTwilioから生のオーディオを受け取るため、ここではWebSocketを使用します。
ステップ3:関数呼び出しを追加する(エージェントに実際に行動させるため)
行動できない音声エージェントは、単なるナレーションに過ぎません。関数呼び出しにより、gpt-realtime-2は会話の途中で一時停止し、コードに何かを実行させ、その結果を持って会話を続けることができます。セッションでツールを宣言し、モデルが必要としたときにfunction_call_arguments.doneイベントを発行します。あなたは作業を実行し、出力を送り返します。
ツールを宣言し、イベントを処理します。
// in session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Book a table for a given party size and time.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "ISO 8601 datetime" },
},
required: ["party_size", "time"],
},
}]
// handling the call:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // your real logic
ws.send(JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
ws.send(JSON.stringify({ type: "response.create" })); // let it speak the result
}ツールが暗黙的に決して発火しない最も一般的な理由:function_call_arguments.doneをリッスンしておらず、その後response.createを送信していないことです。モデルは呼び出しを生成しましたが、あなたが無視したため、発信者は無音状態を聞くことになります。
エージェントが多くのツールを扱う場合、OpenAI Agents SDKがこの部分の計算式を変えました。2026年4月15日の刷新により、Model Context Protocol (MCP) がファーストクラスとなり、サブエージェントへのハンドオフがランタイムプリミティブとなりました。そのため、すべてのツールを1つのプロンプトに詰め込むのではなく、ルーターエージェントが予約を予約サブエージェントに、請求に関する質問を別のエージェントに引き渡すことができます。Agents SDK音声クイックスタートは、同じRealtimeセッションをRealtimeAgentで包み、独自のオーケストレーションループを書かずにハンドオフを提供します。
ステップ4:電話番号にブリッジする(Twilio)
実際の通話に応答するには、電話通信プロバイダーをソケットにブリッジする必要があります。Twilioを使用する場合、着信をTwilioのTwiML <Connect><Stream>に向け、サーバーへのWebSocketを開き、TwilioとRealtime APIの間でオーディオフレームをリレーします。SIPは代替手段です。OpenAI RealtimeはSIPを直接受け入れるため、オーディオに触れる必要がない場合、メディアリレーを完全に削除できます。
ストリームを開始するTwiML:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>ここで注意すべき落とし穴があります。これを逃すと1日無駄になります。Twilioのメディアストリームは8kHz μ-lawですが、Realtime APIは24kHz PCM16を要求します。双方向でリサンプリングしないと、音が歪んだり、チップマンクのような高音になったりします。
// inbound: Twilio (8kHz μ-law base64) -> Realtime (24kHz PCM16)
const pcm16 = upsample(muLawDecode(Buffer.from(msg.media.payload, "base64")), 8000, 24000);
realtime.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: pcm16.toString("base64"),
}));
// outbound: Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));完全なフレーム形式はTwilio Media Streamsドキュメントに記載されています。リサンプリングは軽量に保ってください。ここで重いライブラリを使用すると、すべてのフレームで支払うことになるレイテンシが増加します。
ステップ5:割り込み(Barge-in)と中断を処理する
本番環境のエージェントは、発信者が被せて話すことを許可します。**割り込み(Barge-in)**とは、エージェントが発話中に発信者が話し始めたことを検出し、エージェントをきれいに切断することを意味します。Realtime APIはresponse.cancelでこれを処理します。再生中にターン検出が発話開始を報告した場合、アクティブなレスポンスをキャンセルし、発信者に向けてすでにバッファリングしたオーディオをフラッシュします。
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // drop queued playback
}ターン検出には2つのモードがあり、選択が重要です。server_vadは生の無音閾値でトリガーされ、自然な休止時に発信者を遮断しやすい傾向があります。semantic_vadは、モデルが発信者が実際に思考を終えたと思うまで待機するため、考え込んでいる間の誤った割り込みが大幅に減少します。電話通話では、人間的に感じられるのはsemantic VADです。
ステップ6:レイテンシをサブ秒級に調整する
ここがデモを製品に変える場所です。理論的な予算ではなく、私たち自身の構築からの数値を示します。2026年5月、OpenAIリージョン近くにコロケーションされた小さなサーバー1台で、同じレストランエージェントを40回のテスト通話で実行し、ターン検出と推論設定のみを入れ替えました。
| 構成 | p50 往復時間 | p95 | 備考 |
|---|---|---|---|
| server_vad, reasoning low | ~1.4秒 | ~2.3秒 | 休止時の誤った割り込みが多い |
| semantic_vad, reasoning low | ~1.1秒 | ~1.9秒 | 私たちの本番環境デフォルト |
| semantic_vad, reasoning medium | ~1.8秒 | ~3.1秒 | ツールの精度は向上するが遅い |
実際に効果があったレバーは、影響度の順に以下の通りです。
- 特定のツールが本当に精度を必要としない限り、
reasoning.effortをlowに保つ。mediumにするとp50がほぼ倍増しました。 - リアルタイムより速くオーディオをプッシュしない。
input_audio_buffer.appendを洪水のように送信するとバッファがオーバーランし、ドリフトが発生します。フレームを壁時計時間に合わせます。 - ソケットを温めておく。通話ごとに接続をコールドオープンすると、ハンドシェイクが最初の言葉のレイテンシに加算されます。通話量が許す範囲で接続をプールします。
- 効率的にリサンプリングする。ホットパスでの単純なリサンプラーは、私たちにとって1ターンあたり約80ms追加されました。
稼働後、1分あたりのコストはいくらでしょうか?Bring-your-own-keyの計算は別に行いました。ここで再導出するのではなく、BYOK音声エージェントの1分あたりのコストをご覧ください。
ステップ7:本番環境向けにデプロイおよび強化する
「自分のラップトップでは動いた」と「1日500回の通話に耐える」の間には、いくつかのよく知られた失敗が存在します。以下は、実際にRealtimeエージェントを壊す間違いから抽出した強化チェックリストです。
| 落とし穴 | 症状 | 修正 |
|---|---|---|
| サンプルレートが間違っている | 歪んだ音 / チップマンク音声 | 双方向で24kHz PCM16 |
function_call_arguments.doneを無視している | ツールが発火しない | リッスンしてresponse.createを送信 |
| リアルタイムより速くオーディオをプッシュしている | バッファオーバーラン、ドリフト | フレームをリアルタイムに合わせる |
| 再接続ロジックがない | ソケットの不調で通話が切断される | 自動再接続 + セッションの再開 |
response.doneの処理がない | ターンが重複する | 次のターンをresponse.doneで制御 |
実際のトラフィックのためにもう2点。長時間の通話では、コンテキストが drift しないように数ターンごとにセッションを回転または再シードしてください。20分の通話は、モデルがつまずき始める状態を蓄積します。また、すべてのツール呼び出しとその引数、結果をログに残してください。発信者が「エージェントが間違った時間を予約した」と言った場合、トランスクリプトだけではモデルが間違っていたのかコードが間違っていたのか判断できません。
ステップ3でAgents SDKルートを採用する場合、新しいコンテナサンドボックスはツールコードを隔離して実行します。これは、ツールがAPIだけでなくファイルシステムやシェルに触れる場合に重要になります。
代わりに管理型プラットフォームを購入すべき場合
Realtime API上で直接構築することは最大の制御と最低の分単価コストを提供しますが、再接続ロジック、電話ブリッジ、コンプライアンス、観測可能性——ステップ4から7までのすべての地味な部分——をあなたが所有することになります。今週中に電話エージェントを稼働させたいが、メディアリレーを維持したくない場合、管理型プラットフォームの方が迅速な選択です。
私たちは同じエージェントを主要3社で構築し、正直に比較しました:Retell、Vapi、またはBland。どちら側の立場にいるかまだ迷っている場合は、エンジニアリング時間をコミットする前に完全な構築vs購入の意思決定フレームワークを確認してください。
チームがカスタムRealtime構築の制御を望むが、人員を割けない場合、それが私たちが行う仕事です。電話ブリッジから上記のレイテンシ調整まで、本番環境向け音声エージェント開発をサポートします。検討中であれば、あなたのユースケースを拝見させていただきます。
著者について — Mert Batur GurbuzはTechsy.ioの共同創設者であり、同社のチームはB2Bクライアント向けにAIエージェント、自動化システム、音声/SDRパイプラインを提供しています。バーミンガム大学で学び、Techsyチームが本番環境で実際に使用しているLLMツールスタックについて執筆しています。LinkedIn
よくある質問
OpenAI Realtime API音声エージェントのレイテンシは?
semantic_vadと低い推論努力値を使用したgpt-realtime-2での私たちの構築では、40回のテスト通話で測定した往復レイテンシはp50で1.1秒、p95で1.9秒でした。1つのソケットでの音声対音声処理はSTT/LLM/TTSのリレーを回避するため、これがサブ秒級の応答を可能にする要因です。
音声エージェントにはWebRTC、WebSocket、SIPのどれが必要ですか?
ブラウザまたはモバイルアプリがマイクを直接キャプチャする場合はWebRTCを、サーバーがすでに生のオーディオストリームを保持している場合(Twilioブリッジの場合)はWebSocketを、独自のメディアリレーなしでOpenAIに電話回線の処理を行わせたい場合はSIPを使用してください。ほとんどの電話エージェントはWebSocketまたはSIPを使用します。
OpenAI Realtime APIをTwilioに接続するには?
着信Twilio通話を、サーバーへのWebSocketを開くTwiML <Connect><Stream>に向け、TwilioとRealtimeソケットの間でオーディオをリレーします。双方向でTwilioの8kHz μ-lawをAPIの24kHz PCM16にリサンプリングしないと、音声は歪んで出力されます。
Realtime APIでの関数呼び出しはどのように動作しますか?
セッション構成でツールを宣言します。モデルが必要とすると、function_call_arguments.doneイベントを発行します。作業を実行し、結果をfunction_call_output会話アイテムとして送り返し、その後エージェントが結果を発話できるようにresponse.createを送信します。この最後のステップを忘れることが、ツールが「暗黙的に」失敗する理由です。
Realtime APIでの割り込み(barge-in)はどのように処理しますか?
再生中にターン検出がinput_audio_buffer.speech_startedを報告した場合、response.cancelを送信してアクティブなレスポンスを停止し、発信者向けのキューイングされた出力オーディオをクリアします。自然な休止が文中の誤った割り込みを引き起こさないように、semantic_vadと組み合わせます。
OpenAI Realtime APIが使用するオーディオサンプルレートは?
Realtime APIは双方向で24kHz PCM16オーディオを使用します。Twilioなどの電話通信プロバイダーは8kHz μ-lawを提供するため、電話ブリッジは入力時にアップサンプリングし、出力時にダウンサンプリングする必要があります。サンプルレートの不一致は、歪んだ音声の最も一般的な原因です。
Realtime APIで音声エージェントを実行するコストは?
コストはgpt-realtime-2のオーディオ入出力分数によって駆動され、bring-your-own-keyの経済性は管理型の分単位プラットフォームとは大きく異なります。ここで推定するのではなく、音声エージェント価格の内訳で完全な計算を行っています。
Realtime API上に構築すべきか、Retell、Vapi、またはBlandを使用すべきか?
最大の制御と最低の分単価コストを望み、再接続、電話通信、コンプライアンスを自身で所有できる場合は直接構築してください。ローンチまでの速度が重要な場合は管理型プラットフォームを購入してください。トレードオフについては、Retell vs Vapi vs Bland比較および構築vs購入フレームワークで説明しています。
2026年4月のOpenAI Agents SDKアップデートは音声エージェントに何をもたらしましたか?
2026年4月15日の刷新により、Model Context Protocolがファーストクラスとなり、ツールコード用のコンテナサンドボックスが追加され、サブエージェントへのハンドオフがランタイムプリミティブとなりました。音声エージェントにとって、これはルーターエージェントがすべてのツールを1つのプロンプトに詰め込むのではなく、専門的なサブエージェントにハンドオフできることを意味します。