音声エージェントの構築
セッション設定
Section titled “セッション設定”既定の OpenAIRealtimeWebRTC のような一部のトランスポート層は、音声の入出力を自動で処理します。OpenAIRealtimeWebSocket のような他のトランスポートでは、セッションの音声を自分で処理する必要があります:
import { RealtimeAgent, RealtimeSession, TransportLayerAudio,} from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'My agent' });const session = new RealtimeSession(agent);const newlyRecordedAudio = new ArrayBuffer(0);
session.on('audio', (event: TransportLayerAudio) => { // play your audio});
// send new audio to the agentsession.sendAudio(newlyRecordedAudio);基盤のトランスポートが対応している場合、session.muted は現在のミュート状態を報告し、session.mute(true | false) はマイク入力の切り替えを行います。OpenAIRealtimeWebSocket はミュートを実装していません。session.muted は null を返し、session.mute() は例外を投げるため、WebSocket 構成では自分側でキャプチャを一時停止し、マイクを再度有効にするまで sendAudio() の呼び出しを止めるべきです。
セッション構成
Section titled “セッション構成”RealtimeSession の構築時、または connect(...) を呼び出す際に追加オプションを渡して、セッションを構成できます。
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', config: { outputModalities: ['text', 'audio'], audio: { input: { format: 'pcm16', transcription: { model: 'gpt-4o-mini-transcribe', }, }, output: { format: 'pcm16', }, }, },});これらのトランスポート層は、session に一致する任意のパラメーターを渡すことができます。
新しい構成形式である outputModalities、audio.input、audio.output の使用を推奨します。従来の最上位フィールドである modalities、inputAudioFormat、outputAudioFormat、inputAudioTranscription、turnDetection は後方互換のために引き続き正規化されますが、新しいコードではここで示す入れ子の audio 構造を使用してください。
RealtimeSessionConfig に一致するパラメーターがまだない新しいパラメーターについては、providerData を使用できます。providerData に渡したものは session オブジェクトの一部としてそのまま渡されます。
構築時に設定できる追加の RealtimeSession オプション:
| オプション | 型 | 目的 |
|---|---|---|
context | TContext | セッションのコンテキストにマージされる追加のローカルコンテキスト |
historyStoreAudio | boolean | ローカル履歴スナップショットに音声データを保存(既定では無効) |
outputGuardrails | RealtimeOutputGuardrail[] | セッションの出力ガードレール(ガードレール を参照) |
outputGuardrailSettings | RealtimeOutputGuardrailSettings | ガードレールチェックの頻度と動作 |
tracingDisabled | boolean | セッションのトレーシングを無効化 |
groupId | string | セッションやバックエンド実行をまたいだトレースのグループ化 |
traceMetadata | Record<string, any> | セッショントレースに付与するカスタムメタデータ |
workflowName | string | トレースワークフローのわかりやすい名称 |
automaticallyTriggerResponseForMcpToolCalls | boolean | MCP ツール呼び出し完了時にモデル応答を自動トリガー(既定: true) |
toolErrorFormatter | ToolErrorFormatter | モデルへ返されるツール承認拒否メッセージのカスタマイズ |
connect(...) のオプション:
| オプション | 型 | 目的 |
|---|---|---|
apiKey | string | (() => string | Promise<string>) | この接続で使用する API キー(または遅延ローダー) |
model | OpenAIRealtimeModels | string | トランスポート接続の任意のモデル上書き |
url | string | 任意のカスタム Realtime エンドポイント URL |
callId | string | 既存の SIP 起点の通話/セッションにアタッチ |
エージェントの機能
Section titled “エージェントの機能”通常のエージェントと同様に、ハンドオフを使ってエージェントを複数のエージェントに分割し、それらの間をオーケストレーションすることで、エージェントの性能を向上させ、問題のスコープを適切に絞り込むことができます。
import { RealtimeAgent } from '@openai/agents/realtime';
const mathTutorAgent = new RealtimeAgent({ name: 'Math Tutor', handoffDescription: 'Specialist agent for math questions', instructions: 'You provide help with math problems. Explain your reasoning at each step and include examples',});
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.', handoffs: [mathTutorAgent],});通常のエージェントとは異なり、Realtime Agents におけるハンドオフは少し挙動が異なります。ハンドオフが実行されると、進行中のセッションは新しいエージェント構成で更新されます。このため、エージェントは進行中の会話履歴に自動でアクセスでき、入力フィルターは現在適用されません。
さらに、これによりハンドオフの一部として voice や model を変更することはできません。また、接続できるのは他の Realtime Agents のみです。別のモデル、たとえば gpt-5-mini のような推論モデルを使用する必要がある場合は、ツールによる委任 を使用できます。
通常のエージェントと同様に、Realtime Agents はアクションを実行するためにツールを呼び出せます。Realtime は function tools(ローカルで実行)と hosted MCP tools(Realtime API によりリモートで実行)をサポートします。関数ツールは、通常のエージェントで使用するのと同じ tool() ヘルパーで定義できます。
import { tool, RealtimeAgent } from '@openai/agents/realtime';import { z } from 'zod';
const getWeather = tool({ name: 'get_weather', description: 'Return the weather for a city.', parameters: z.object({ city: z.string() }), async execute({ city }) { return `The weather in ${city} is sunny.`; },});
const weatherAgent = new RealtimeAgent({ name: 'Weather assistant', instructions: 'Answer weather questions.', tools: [getWeather],});関数ツールは RealtimeSession と同じ環境で実行されます。つまり、セッションをブラウザで実行している場合、ツールもブラウザで実行されます。機微な処理が必要な場合は、ツール内からバックエンドへの HTTP リクエストを行ってください。
Hosted MCP ツールは hostedMcpTool で構成でき、リモートで実行されます。MCP ツールの可用性が変化すると、セッションは mcp_tools_changed を発火します。MCP ツール呼び出し完了後にセッションがモデル応答を自動トリガーしないようにするには、automaticallyTriggerResponseForMcpToolCalls: false を設定します。
現在のフィルタ済み MCP ツール一覧は session.availableMcpTools としても利用できます。このプロパティと mcp_tools_changed イベントは、アクティブなエージェントで有効になっている Hosted MCP サーバーに対して、エージェント構成の allowed_tools フィルター適用後の結果のみを反映します。
ツールの実行中は、エージェントは新しいユーザーからのリクエストを処理できません。体験を改善する 1 つの方法は、ツールを実行しようとしていることをエージェントにアナウンスさせたり、ツール実行のための時間を稼ぐための特定のフレーズを話すよう指示したりすることです。
関数ツールが、ただちに別のモデル応答をトリガーせずに終了すべき場合は、@openai/agents/realtime の backgroundResult(output) を返してください。これは、応答のトリガーをあなたの制御下に残したまま、ツールの出力をセッションに返します。
関数ツールのタイムアウトオプション(timeoutMs、timeoutBehavior、timeoutErrorFunction)は Realtime セッションでも同様に機能します。既定の error_as_result では、タイムアウトメッセージがツール出力として送信されます。raise_exception の場合、セッションは ToolTimeoutError とともに error イベントを発火し、その呼び出しに対してツール出力は送信しません。
会話履歴へのアクセス
Section titled “会話履歴へのアクセス”エージェントが特定のツールを呼び出した引数に加えて、Realtime Session によって追跡されている現在の会話履歴のスナップショットにもアクセスできます。これは、会話の現在の状態に基づいたより複雑なアクションを実行する必要がある場合や、ツールによる委任 を使用する予定がある場合に有用です。
import { tool, RealtimeContextData, RealtimeItem,} from '@openai/agents/realtime';import { z } from 'zod';
const parameters = z.object({ request: z.string(),});
const refundTool = tool<typeof parameters, RealtimeContextData>({ name: 'Refund Expert', description: 'Evaluate a refund', parameters, execute: async ({ request }, details) => { // The history might not be available const history: RealtimeItem[] = details?.context?.history ?? []; // making your call to process the refund request },});ツール実行前の承認
Section titled “ツール実行前の承認”ツールを needsApproval: true で定義した場合、エージェントはツールを実行する前に tool_approval_requested イベントを発火します。
このイベントをリッスンすることで、ユーザーにツール呼び出しの承認または拒否のための UI を表示できます。
import { session } from './agent';
session.on('tool_approval_requested', (_context, _agent, request) => { // show a UI to the user to approve or reject the tool call // you can use the `session.approve(...)` or `session.reject(...)` methods to approve or reject the tool call
session.approve(request.approvalItem); // or session.reject(request.rawItem);});ガードレール
Section titled “ガードレール”ガードレールは、エージェントが発話した内容が一連のルールに違反していないかを監視し、即座に応答を打ち切る手段を提供します。これらのガードレールチェックは、エージェントの応答の書き起こしに基づいて実行されるため、モデルのテキスト出力が有効である必要があります(既定で有効)。
提供したガードレールは、モデル応答が返ってくるのと同時に非同期で実行され、例えば「特定の禁止語の言及」などの事前定義された分類トリガーに基づいて応答を打ち切ることができます。
ガードレールが作動すると、セッションは guardrail_tripped イベントを発火します。このイベントは、ガードレールをトリガーした itemId を含む details オブジェクトも提供します。
import { RealtimeOutputGuardrail, RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',});
const guardrails: RealtimeOutputGuardrail[] = [ { name: 'No mention of Dom', async execute({ agentOutput }) { const domInOutput = agentOutput.includes('Dom'); return { tripwireTriggered: domInOutput, outputInfo: { domInOutput }, }; }, },];
const guardedSession = new RealtimeSession(agent, { outputGuardrails: guardrails,});既定では、ガードレールは 100 文字ごと、または応答テキストの生成が終了した時点で実行されます。テキストの発話は通常より長くかかるため、ほとんどの場合、ユーザーが聞く前にガードレールが違反を検知できます。
この動作を変更したい場合は、outputGuardrailSettings オブジェクトをセッションに渡してください。
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',});
const guardedSession = new RealtimeSession(agent, { outputGuardrails: [ /*...*/ ], outputGuardrailSettings: { debounceTextLength: 500, // run guardrail every 500 characters or set it to -1 to run it only at the end },});インタラクションフロー
Section titled “インタラクションフロー”ターン検出 / 音声活動検出
Section titled “ターン検出 / 音声活動検出”Realtime Session は、ユーザーが発話していることを自動的に検出し、組み込みの Realtime API の音声活動検出モード を使用して新しいターンをトリガーします。
音声活動検出モードは、セッション構成で audio.input.turnDetection を渡すことで変更できます。
import { RealtimeSession } from '@openai/agents/realtime';import { agent } from './agent';
const session = new RealtimeSession(agent, { model: 'gpt-realtime', config: { audio: { input: { turnDetection: { type: 'semantic_vad', eagerness: 'medium', createResponse: true, interruptResponse: true, }, }, }, },});ターン検出設定を調整すると、不要な割り込みのキャリブレーションや無音状態への対処に役立ちます。各種設定の詳細は Realtime API ドキュメント を参照してください。
組み込みの音声活動検出を使用している場合、エージェントの発話にかぶせて話すと、自動的に検出され、発話内容に基づいてコンテキストが更新されます。同時に audio_interrupted イベントも発火されます。これは、すべての音声再生を即時に停止するために使用できます(WebSocket 接続にのみ適用)。
import { session } from './agent';
session.on('audio_interrupted', () => { // handle local playback interruption});手動の割り込みを行いたい場合、例えば UI に「停止」ボタンを用意したい場合は、interrupt() を手動で呼び出せます:
import { session } from './agent';
session.interrupt();// this will still trigger the `audio_interrupted` event for you// to cut off the audio playback when using WebSocketsいずれの場合も、Realtime Session はエージェントの生成を割り込み、ユーザーに話した内容の認識を切り詰め、履歴を更新します。
エージェントへの接続に WebRTC を使用している場合は、音声出力もクリアされます。WebSocket を使用している場合は、キューに入っている再生済み音声の再生停止を自分で処理する必要があります。
テキスト入力
Section titled “テキスト入力”エージェントにテキスト入力を送信したい場合は、RealtimeSession の sendMessage メソッドを使用できます。
これは、ユーザーがエージェントと複数のモダリティでやり取りできるようにしたい場合や、会話に追加のコンテキストを提供したい場合に有用です。
import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Assistant',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime',});
session.sendMessage('Hello, how are you?');会話状態と委任
Section titled “会話状態と委任”会話履歴の管理
Section titled “会話履歴の管理”RealtimeSession は、history プロパティで会話履歴を自動的に管理します:
これを使用して、顧客向けに履歴をレンダリングしたり、追加の処理を行ったりできます。会話の進行に伴って履歴は継続的に変化するため、history_updated イベントをリッスンできます。
履歴を変更したい場合(メッセージを完全に削除、または書き起こしを更新するなど)、updateHistory メソッドを使用できます。
import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Assistant',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime',});
await session.connect({ apiKey: '<client-api-key>' });
// listening to the history_updated eventsession.on('history_updated', (history) => { // returns the full history of the session console.log(history);});
// Option 1: explicit settingsession.updateHistory([ /* specific history */]);
// Option 2: override based on current state like removing all agent messagessession.updateHistory((currentHistory) => { return currentHistory.filter( (item) => !(item.type === 'message' && item.role === 'assistant'), );});- 関数ツール呼び出しは、後から更新/変更できません
- 履歴でのテキスト出力には、書き起こしとテキストモダリティの有効化が必要です
- 割り込みにより切り詰められた応答には書き起こしがありません
ツールによる委任
Section titled “ツールによる委任”
会話履歴とツール呼び出しを組み合わせることで、会話を別のバックエンドエージェントに委任して、より複雑なアクションを実行し、その結果をユーザーに返すことができます。
import { RealtimeAgent, RealtimeContextData, tool,} from '@openai/agents/realtime';import { handleRefundRequest } from './serverAgent';import z from 'zod';
const refundSupervisorParameters = z.object({ request: z.string(),});
const refundSupervisor = tool< typeof refundSupervisorParameters, RealtimeContextData>({ name: 'escalateToRefundSupervisor', description: 'Escalate a refund request to the refund supervisor', parameters: refundSupervisorParameters, execute: async ({ request }, details) => { // This will execute on the server return handleRefundRequest(request, details?.context?.history ?? []); },});
const agent = new RealtimeAgent({ name: 'Customer Support', instructions: 'You are a customer support agent. If you receive any requests for refunds, you need to delegate to your supervisor.', tools: [refundSupervisor],});以下のコードはサーバー上で実行されます。この例では Next.js の server actions を通じて実行されます。
// This runs on the serverimport 'server-only';
import { Agent, run } from '@openai/agents';import type { RealtimeItem } from '@openai/agents/realtime';import z from 'zod';
const agent = new Agent({ name: 'Refund Expert', instructions: 'You are a refund expert. You are given a request to process a refund and you need to determine if the request is valid.', model: 'gpt-5-mini', outputType: z.object({ reasong: z.string(), refundApproved: z.boolean(), }),});
export async function handleRefundRequest( request: string, history: RealtimeItem[],) { const input = `The user has requested a refund.
The request is: ${request}
Current conversation history:${JSON.stringify(history, null, 2)}`.trim();
const result = await run(agent, input);
return JSON.stringify(result.finalOutput, null, 2);}