コンテンツにスキップ

音声エージェントの構築

デフォルトの 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);

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-4o-realtime-preview-2025-06-03',
config: {
inputAudioFormat: 'pcm16',
outputAudioFormat: 'pcm16',
inputAudioTranscription: {
model: 'gpt-4o-mini-transcribe',
},
},
});

これらのトランスポートレイヤーでは、session に一致する任意のパラメーターを渡せます。

RealtimeSessionConfig にまだ存在しない新しいパラメーターについては providerData を使えます。providerData に渡した内容は session オブジェクトの一部としてそのまま送信されます。

通常のエージェントと同様に、ハンドオフを使ってエージェントを複数に分割し、それらをオーケストレーションすることでパフォーマンスを向上させたり、問題のスコープをより適切に定義できます。

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

通常のエージェントとは異なり、リアルタイムエージェントにおけるハンドオフは少し挙動が異なります。ハンドオフが実行されると、進行中のセッションは新しいエージェント設定で更新されます。そのため、エージェントは自動的に進行中の会話履歴へアクセスでき、現時点では入力フィルターは適用されません。

さらに、voicemodel はハンドオフの一部として変更できません。また、接続先は他のリアルタイムエージェントのみです。別のモデル(例: 推論用モデル o4-mini)を使用する必要がある場合は、ツールによる委任 を利用してください。

通常のエージェントと同様に、リアルタイムエージェントでもツールを呼び出してアクションを実行できます。通常のエージェントと同じ 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],
});

リアルタイムエージェントで使用できるのは関数ツールだけで、これらのツールはリアルタイムセッションと同じ場所で実行されます。つまり、ブラウザでリアルタイムセッションを実行している場合、ツールもブラウザで実行されます。よりセンシティブな処理が必要な場合は、ツール内で自分のバックエンドサーバーへ HTTP リクエストを送ることができます。

ツールが実行されている間、エージェントはユーザーからの新しいリクエストを処理できません。エクスペリエンスを向上させる 1 つの方法として、ツール実行前にエージェントにアナウンスさせたり、時間稼ぎのためのフレーズを言わせたりすると良いでしょう。

エージェントがツールを呼び出した際の引数に加え、リアルタイムセッションが追跡している現在の会話履歴のスナップショットにもアクセスできます。これは、会話の現在の状態に基づいてより複雑なアクションを実行する場合や、ツールによる委任 を計画している場合に役立ちます。

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

ツールを 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);
});

ガードレールは、エージェントが発話した内容がルールに違反していないかを監視し、違反があれば直ちにレスポンスを打ち切る方法を提供します。これらのガードレールチェックはエージェントのレスポンステキストの書き起こしに基づいて行われるため、モデルのテキスト出力が有効である必要があります(デフォルトで有効)。

指定したガードレールは、モデルレスポンスが返ってくるのと同時に非同期で実行され、例えば「特定の禁止ワードを含む」など、あらかじめ定義した分類トリガーに基づいてレスポンスを打ち切れます。

ガードレールが発動すると、セッションは 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
},
});

リアルタイムセッションは、Realtime API の音声活動検出モード を使用して、ユーザーが話しているかどうかを自動的に検出し、新しいターンをトリガーします。

turnDetection オブジェクトをセッションに渡すことで音声活動検出モードを変更できます。

import { RealtimeSession } from '@openai/agents/realtime';
import { agent } from './agent';
const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
config: {
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

いずれの方法でも、リアルタイムセッションはエージェントの生成を中断し、ユーザーに対して話した内容の知識を切り詰め、履歴を更新します。

WebRTC でエージェントに接続している場合は、音声出力もクリアされます。WebSocket を使用している場合は、キューに入った音声再生を停止する処理を自分で行う必要があります。

エージェントにテキスト入力を送信したい場合は、RealtimeSessionsendMessage メソッドを使用できます。

これは、ユーザーがエージェントと音声・テキスト両方のモダリティでやり取りできるようにしたり、会話に追加のコンテキストを提供したりする際に役立ちます。

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

RealtimeSessionhistory プロパティで会話履歴を自動管理します。

これを利用して履歴をユーザーに表示したり、追加処理を実行したりできます。会話中に履歴は常に変化するため、history_updated イベントを監視できます。

履歴を完全に削除したり、書き起こしを更新したりしたい場合は、updateHistory メソッドを利用してください。

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
});
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. 履歴内のテキスト出力には、書き起こしとテキストモダリティが有効である必要があります
  3. 割り込みにより切り詰められたレスポンスには書き起こしがありません

Delegation through tools

会話履歴とツール呼び出しを組み合わせることで、会話を別のバックエンドエージェントに委任してより複雑な処理を行い、その結果をユーザーに返すことができます。

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 のサーバーアクションを通じて行います。

// 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: 'o4-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);
}