コンテンツにスキップ

リアルタイムエージェントの構築

デフォルトの 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 agent
session.sendAudio(newlyRecordedAudio);

基盤となるトランスポートが対応している場合、session.muted は現在のミュート状態を返し、session.mute(true | false) はマイクのキャプチャを切り替えます。OpenAIRealtimeWebSocket はミュートを実装していません。session.mutednull を返し、session.mute() は例外をスローします。そのため、WebSocket の構成では、クライアント側でキャプチャを一時停止し、マイクを再び有効にするまで sendAudio() の呼び出しを停止してください。

RealtimeSession の作成時に、通常は model オプションと config オブジェクトを通じてセッション自体を設定します。connect(...) は、任意のセッションフィールドではなく、認証情報、エンドポイント URL、SIP 通話の接続など、接続時の事項に使用します。

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',
config: {
outputModalities: ['audio'],
reasoning: {
effort: 'low',
},
parallelToolCalls: true,
audio: {
input: {
format: 'pcm16',
transcription: {
model: 'gpt-4o-mini-transcribe',
},
},
output: {
format: 'pcm16',
},
},
},
});

内部では、SDK がこの設定を Realtime の session.update 形式に正規化します。RealtimeSessionConfig に対応するプロパティがない元のセッションフィールドが必要な場合は、providerData を使用するか、session.transport.sendEvent(...) を通じて元の session.update を送信してください。

outputModalitiesaudio.inputaudio.output を使用する新しい SDK 設定形式を推奨します。modalitiesinputAudioFormatoutputAudioFormatinputAudioTranscriptionturnDetection などの古い SDK エイリアスも後方互換性のために引き続き正規化されますが、新しいコードでは、ここに示すネストされた audio 構造を使用してください。

gpt-realtime-2.1 など、推論に対応した Realtime モデルでは、セッション設定で reasoning.effort を指定します。推論の労力を高くすると、レイテンシーとトークン使用量が増える可能性があります。また、モデルが複数のツールを並列に呼び出せるかどうかを制御する場合は、parallelToolCalls を指定できます。

音声対音声セッションでは、通常は outputModalities: ['audio'] を選択します。これにより、音声出力と文字起こしが得られます。テキストのみの応答が必要な場合に限り、['text'] に切り替えてください。

新しいパラメーターで、RealtimeSessionConfig に対応するパラメーターがない場合は、providerData を使用できます。providerData に渡した内容はすべて、元の session オブジェクトの一部として転送されます。

構築時に設定できる追加の RealtimeSession オプションは次のとおりです。

オプション目的
contextTContextセッションコンテキストにマージされる追加のローカルコンテキスト。
historyStoreAudiobooleanローカルの履歴スナップショットに音声データを保存します(デフォルトでは無効)。
outputGuardrailsRealtimeOutputGuardrail[]セッションの出力ガードレール(ガードレールを参照)。
outputGuardrailSettings{ debounceTextLength?: number }ガードレールの実行頻度。デフォルトは 100 です。完全なテキストが利用可能になった時点でのみ実行するには、-1 を使用します。
tracingDisabledbooleanセッションのトレーシングを無効にします。
groupIdstringセッションまたはバックエンド実行間でトレースをグループ化します。workflowName が必要です。
traceMetadataRecord<string, any>セッショントレースに付加するカスタムメタデータ。workflowName が必要です。
workflowNamestringトレースワークフローのわかりやすい名前。
automaticallyTriggerResponseForMcpToolCallsbooleanMCP ツールの呼び出し完了時にモデル応答を自動的にトリガーします(デフォルト:true)。
toolErrorFormatterToolErrorFormatterモデルへ返すツール承認拒否メッセージをカスタマイズします。
toolExecutionRealtimeToolExecutionConfigローカルのリアルタイム関数ツールに対する SDK 側の実行設定。保留中の承認リクエストより前に入力ガードレールを実行するには、preApprovalInputGuardrails: true を指定します。

connect(...) のオプションは次のとおりです。

オプション目的
apiKeystring | (() => string | Promise<string>)この接続で使用する API キー(または遅延ローダー)。
modelOpenAIRealtimeModels | stringトランスポートレベルのオプション型に含まれます。RealtimeSession では、コンストラクターでモデルを設定します。元のトランスポートでは接続時にモデルを指定することもできます。
urlstring任意のカスタム Realtime エンドポイント URL。
callIdstring既存の SIP 起点の通話またはセッションに接続します。

RealtimeSession は、長時間維持される Realtime 接続上で動作します。会話履歴のローカルコピーを保持し、トランスポートイベントをリッスンし、ツールと出力ガードレールを実行し、アクティブなエージェント設定をトランスポートと同期します。

基盤となる API の動作も重要です。

  • 接続に成功すると session.created イベントから始まり、その後の設定変更によって session.updated が生成されます。
  • ほとんどのセッションプロパティは後から変更できますが、会話の途中で model を変更することはできません。voice を変更できるのは、セッションが音声出力を生成する前だけです。また、Realtime API では有効化後にトレーシングを変更できないため、トレーシングは事前に決定してください。
  • 現在、Realtime API では 1 つのセッションが 60 分に制限されています。
  • 入力音声の文字起こしは非同期で行われるため、最新の発話の文字起こしは、応答生成がすでに開始された後に届く場合があります。

SDK レイヤーでは、await session.connect() は「会話を開始できる程度にトランスポートの準備が整った」ことを意味しますが、正確なタイミングはトランスポートによって異なります。

  • デフォルトのブラウザ用 WebRTC トランスポートでは、データチャネルが開くとすぐに SDK が最初の session.update を送信し、対応する session.updated イベントを待ってから connect() を完了しようとします。これは、instructions、ツール、モダリティが適用される前に音声がサーバーへ到達することを防ぐためです。その確認応答が届かない場合、connect() は短いタイムアウト後に完了します。
  • デフォルトのサーバー側 WebSocket トランスポートでは、ソケットが開き、初期設定が送信された時点で connect() が完了します。そのため、対応する session.updated イベントは、connect() がすでに完了した後に届く場合があります。

元のイベントモデルが必要な場合は、このページとあわせて公式の Realtime 会話ガイドを参照してください。

ターン検出と音声アクティビティ検出

Section titled “ターン検出と音声アクティビティ検出”

デフォルトでは、Realtime セッションは組み込みの音声アクティビティ検出(VAD)を使用します。これにより API は、ユーザーが発話を開始または終了したタイミングと、応答を作成するタイミングを判断できます。SDK では、これを audio.input.turnDetection で設定します。

import { RealtimeSession } from '@openai/agents/realtime';
import { agent } from './agent';
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
config: {
audio: {
input: {
turnDetection: {
type: 'semantic_vad',
eagerness: 'medium',
createResponse: true,
interruptResponse: true,
},
},
},
},
});

一般的なモードは次の 2 つです。

  • semantic_vad は、より自然なターン境界を目指すモードで、ユーザーがまだ話し終えていないように聞こえる場合は少し長く待機できます。
  • server_vad は、しきい値を重視するモードで、thresholdprefixPaddingMssilenceDurationMsidleTimeoutMs などの設定を利用できます。

ターン境界を自分で管理する場合は、audio.input.turnDetectionnull に設定します。基盤となる動作について詳しくは、公式の音声アクティビティ検出ガイドRealtime 会話ガイドを参照してください。

VAD が有効な場合、エージェントの発話中にユーザーが話すと、現在の応答を中断できます。WebSocket トランスポートでは、SDK が input_audio_buffer.speech_started をリッスンし、アシスタント音声をユーザーが実際に聞いた位置まで切り詰め、audio_interrupted イベントを発行します。このイベントは、WebSocket の構成で再生を自分で管理する場合に特に便利です。

import { session } from './agent';
session.on('audio_interrupted', () => {
// handle local playback interruption
});

手動の停止ボタンを提供する場合は、interrupt() を直接呼び出します。

import { session } from './agent';
session.interrupt();
// This still triggers `audio_interrupted` so your UI can stop playback

WebRTC と WebSocket はどちらも進行中の応答を停止しますが、低レベルの仕組みはトランスポートによって異なります。WebRTC はバッファリングされた出力音声を自動的に消去します。WebSocket の構成ではローカル再生を自分で停止する必要があり、対応する切り詰めイベントと会話イベントがトランスポートから返されると、ローカル履歴が更新されます。

ライブ会話に入力したテキストや追加の構造化されたユーザーコンテンツを送信する場合は、sendMessage() を使用します。

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
session.sendMessage('Hello, how are you?');

これは、テキストと音声を組み合わせた UI、帯域外でのコンテキスト注入、または音声入力と明示的なテキストによる補足の組み合わせに便利です。

Realtime の音声対音声セッションには、画像も含められます。SDK では、addImage() を使用して現在の会話に画像を添付します。

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
const imageDataUrl = 'data:image/png;base64,...';
session.addImage(imageDataUrl, { triggerResponse: false });
session.sendMessage('Describe what is in this image.');

triggerResponse: false を渡すと、モデルに応答を求める前に、画像を後続のテキストまたは音声ターンとまとめられます。これは公式の Realtime 会話における画像入力のガイダンスに沿った動作です。

上位の SDK レイヤーでは、デフォルトで sendMessage()addImage() が応答をトリガーします。元のトランスポートイベント、プッシュトゥトークのフロー、カスタムのモデレーションまたは検証ステップを扱う場合は、手動による応答制御が重要です。

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 response
session.transport.sendEvent({
type: 'response.create',
// ...
});

一般的なケースは次の 2 つです。

  1. audio.input.turnDetection = null で VAD を完全に無効にした場合は、自分で音声ターンをコミットしてから response.create を送信します。
  2. VAD を有効にしたまま turnDetection.interruptResponse = falseturnDetection.createResponse = false を設定すると、API は引き続きターンを検出しますが、応答の作成はユーザー側に委ねられます。

2 番目のパターンは、モデルが応答する前にユーザー入力を検査またはモデレーションする場合に便利です。これは、公式の Realtime 会話における自動応答の無効化に関するガイダンスに沿っています。

通常のエージェントと同様に、ハンドオフを使用してエージェントを複数のエージェントに分割し、それらを連携させることで、パフォーマンスを向上させ、問題の範囲をより適切に限定できます。

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 API のルールが適用されます。つまり、セッションが音声出力を生成する前にのみ変更できます。リアルタイムのハンドオフは、主に同じセッション上で RealtimeAgent の設定を切り替えるためのものです。gpt-5.4 のような推論モデルなど、別のモデルを使用する必要がある場合や、リアルタイムではないバックエンドエージェントへ委譲する場合は、ツールを通じた委譲を使用してください。

通常のエージェントと同様に、リアルタイムエージェントはツールを呼び出してアクションを実行できます。Realtime は、関数ツール(ローカルで実行)と ホステッド MCP ツール(Realtime API によってリモートで実行)に対応しています。通常のエージェントと同じ tool() ヘルパーを使用して関数ツールを定義できます。

Responses 専用のツールオプションは、リアルタイムエージェントには引き継がれません。'programmatic' を含む関数ツールの outputSchema および allowedCallers の値は拒否されます。ホステッド MCP ツールのプログラムによる呼び出し元も同様です。Realtime セッションでは、直接呼び出し可能な関数ツールまたはホステッド MCP ツールを使用するか、Programmatic Tool Calling が必要な処理を Responses エージェントへ委譲してください。

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 と同じ環境で実行されます。つまり、セッションをブラウザで実行している場合、ツールもブラウザで実行されます。機密性の高いアクションを実行する必要がある場合は、ツール内からバックエンドを呼び出し、特権を必要とする処理をサーバー側で実行してください。

これにより、ブラウザ側のツールを、サーバー側ロジックへの軽量なバックチャネルとして使用できます。たとえば、examples/realtime-next では、ブラウザに refundBackchannel ツールを定義しています。このツールは、リクエストと現在の会話履歴をサーバー上の handleRefundRequest(...) に転送します。サーバーでは別の Runner が異なるエージェントまたはモデルを使用して返金を評価し、その実行結果を音声セッションへ返せます。

ホステッド MCP ツールは hostedMcpTool で設定でき、リモートで実行されます。MCP ツールの利用可否が変わると、セッションは mcp_tools_changed を発行します。MCP ツールの呼び出し完了後にセッションがモデル応答を自動的にトリガーしないようにするには、automaticallyTriggerResponseForMcpToolCalls: false を設定します。

現在フィルタリングされている MCP ツールの一覧は、session.availableMcpTools でも利用できます。このプロパティと mcp_tools_changed イベントのどちらも、エージェント設定の allowed_tools フィルターを適用した後の、アクティブなエージェントで有効になっているホステッド MCP サーバーのみを反映します。

安全なサーバーの選択、ヘッダー、承認を接続前の設定として扱うと、ホステッド MCP のセットアップを理解しやすくなります。RealtimeSession.connect() がトランスポートを開く前に、SDK はアクティブなエージェントのホステッド MCP ツール定義を解決し、対応している MCP フィールドを Realtime API へ送信する初期セッション設定に含めます。

このタイミングは、ブラウザの WebRTC アプリで特に重要です。一時的なクライアントシークレットは必ずサーバー上で発行されるため、秘匿する必要があるホステッド MCP の認証情報またはカスタム headers は、初期 session ペイロードの一部として、サーバー側の POST /v1/realtime/client_secrets リクエストに含めてください。有効期間の長い認証情報をブラウザコードに含め、connect() の開始後に追加することは避けてください。

Realtime API レベルでは、後続の session.update 呼び出しによって、ツールやその他の変更可能なセッションフィールドを引き続き変更できます。また、アクティブなエージェントが変更されると、SDK 自体も session.update を送信します。ただしブラウザアプリでは、安全な Hosted MCP の初期化をサーバー側かつ接続前の処理として扱い、ブラウザ側の RealtimeSession 設定をサーバーで発行した内容と一致させてください。

ツールの実行中、エージェントはユーザーからの新しいリクエストを処理できません。エクスペリエンスを改善する方法の 1 つは、ツールを実行しようとしていることを事前に伝えるようエージェントへ指示するか、ツールの実行時間を確保するための特定のフレーズを発話させることです。

関数ツールの完了直後に別のモデル応答をトリガーしない場合は、@openai/agents/realtimebackgroundResult(output) を返します。これにより、応答のトリガーをユーザー側で制御したまま、ツールの出力がセッションへ返されます。

関数ツールのタイムアウトオプション(timeoutMstimeoutBehaviortimeoutErrorFunction)は、Realtime セッションでも同じように機能します。デフォルトの error_as_result では、タイムアウトメッセージがツール出力として送信されます。raise_exception では、セッションが ToolTimeoutError を含む error イベントを発行し、その呼び出しに対するツール出力は送信しません。

エージェントが特定のツールを呼び出したときの引数に加えて、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 ?? [];
// Call your backend to process the refund request
},
});

needsApproval: true を指定してツールを定義すると、エージェントはツールを実行する前に tool_approval_requested イベントを発行します。

このイベントをリッスンすることで、ツール呼び出しを承認または拒否するための UI をユーザーに表示できます。

await session.approve(request.approvalItem) または await session.reject(request.approvalItem) でリクエストを解決します。関数ツールでは、{ alwaysApprove: true } または { alwaysReject: true } を渡すことで、セッションの残りの期間中、繰り返される呼び出しに同じ判断を再利用できます。また、session.reject(request.approvalItem, { message: '...' }) を使用すると、その特定の呼び出しに対するカスタム拒否メッセージをモデルへ返せます。ホステッド MCP の承認では、継続的な承認または拒否はサポートされていません。代わりに、ホステッド MCP の allowedTools 設定でツールを制限してください。

呼び出しごとの拒否 message を渡さない場合、セッションは toolErrorFormatter が設定されていればそれを使用し、その後 SDK のデフォルト拒否テキストへフォールバックします。

デフォルトでは、関数ツールの入力ガードレールは承認後、ツールの実行直前に実行されます。new RealtimeSession(...)toolExecution: { preApprovalInputGuardrails: true } を渡すと、セッションが tool_approval_requested を発行する前にも、ローカル関数ツールの入力ガードレールが実行されます。ガードレールが拒否した場合、拒否メッセージがツール出力として返され、承認イベントはスキップされます。ガードレールが呼び出しを許可した場合も承認イベントは発行され、実行前に session.approve(...) の後でもう一度ガードレールが実行されます。

import { session } from './agent';
session.on('tool_approval_requested', (_context, _agent, request) => {
// Show a UI to let the user approve or reject the tool call
// Then resolve the request with `session.approve(...)` or `session.reject(...)`
session.approve(request.approvalItem);
});

ガードレールを使用すると、エージェントの発話が一連のルールに違反していないかを監視し、応答を直ちに停止できます。これらのチェックは、エージェントの応答の出力ストリームに対して実行されます。テキストのみのセッションでは、SDK は出力テキストの差分を評価します。音声セッションでは、出力音声の文字起こしと文字起こしの差分を使用するため、重要な前提条件は、個別のテキスト出力モダリティではなく文字起こしが利用可能であることです。

指定したガードレールは、モデル応答が返される間に非同期で実行されます。これにより、たとえば「特定の禁止語に言及している」といった、事前定義された分類トリガーに基づいて応答を停止できます。

ガードレールが作動すると、セッションは 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 オブジェクトをセッションに渡せます。

応答の最後に、完全に生成された文字起こしを一度だけ評価する場合は、debounceTextLength: -1 を設定します。

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
},
});

RealtimeSession は、ユーザーメッセージ、アシスタントの出力、ツール呼び出し、切り詰め状態を追跡するローカルの history スナップショットを自動的に維持します。この履歴を UI に表示したり、ツール内で確認したり、項目の修正または削除が必要な場合に更新したりできます。

会話が変化すると、セッションは history_updated を発行します。履歴の変更をリクエストする必要がある場合は、updateHistory() を使用します。このメソッドは、現在の履歴との差分を計算して必要な削除または作成イベントを送信するようトランスポートへ要求します。対応する会話イベントがトランスポートから返されると、ローカルの session.history ビューが更新されます。

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
await session.connect({ apiKey: '<client-api-key>' });
// listening to the history_updated event
session.on('history_updated', (history) => {
// returns the full history of the session
console.log(history);
});
// Option 1: explicit setting
session.updateHistory([
/* specific history */
]);
// Option 2: override based on current state like removing all agent messages
session.updateHistory((currentHistory) => {
return currentHistory.filter(
(item) => !(item.type === 'message' && item.role === 'assistant'),
);
});
  1. 現在、関数ツールの呼び出しを後から編集することはできません。
  2. 履歴内のアシスタントテキストは、output_audio.transcript を含む利用可能な文字起こしに依存します。
  3. 割り込みによって切り詰められた応答には、最終的な文字起こしが保持されません。
  4. 入力音声の文字起こしは、モデルが音声をどのように解釈したかの正確なコピーではなく、ユーザーが発話した内容のおおまかな目安として扱うことを推奨します。

ツールを通じた委譲

会話履歴とツール呼び出しを組み合わせることで、会話を別のバックエンドエージェントへ委譲して、より複雑なアクションを実行し、その実行結果をユーザーへ返せます。

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 Action を介して実行されます。

// This runs on the server
import '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.4',
outputType: z.object({
reason: 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);
}