콘텐츠로 이동

음성 에이전트 구축

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

내부적으로 SDK는 이 구성을 Realtime session.update 형태로 정규화합니다. RealtimeSessionConfig에 대응 속성이 없는 원시 세션 필드가 필요하면 providerData를 사용하거나 session.transport.sendEvent(...)를 통해 원시 session.update를 전송하세요.

outputModalities, audio.input, audio.output를 사용하는 최신 SDK 구성 형태를 권장합니다. modalities, inputAudioFormat, outputAudioFormat, inputAudioTranscription, turnDetection 같은 이전 SDK 별칭은 하위 호환성을 위해 여전히 정규화되지만, 새 코드는 여기의 중첩 audio 구조를 사용해야 합니다.

음성-대-음성 세션에서는 보통 outputModalities: ['audio']를 선택하며, 이는 오디오 출력과 전사를 제공합니다. 텍스트 전용 응답이 필요할 때만 ['text']로 전환하세요.

새로운 매개변수인데 RealtimeSessionConfig에 대응 매개변수가 없다면 providerData를 사용할 수 있습니다. providerData에 전달된 항목은 원시 session 객체의 일부로 전달됩니다.

생성 시 설정할 수 있는 추가 RealtimeSession 옵션:

OptionTypePurpose
contextTContext세션 컨텍스트에 병합되는 추가 로컬 컨텍스트
historyStoreAudioboolean로컬 히스토리 스냅샷에 오디오 데이터 저장(기본 비활성화)
outputGuardrailsRealtimeOutputGuardrail[]세션용 출력 가드레일(Guardrails 참고)
outputGuardrailSettings{ debounceTextLength?: number }가드레일 주기. 기본값은 100; 전체 텍스트가 준비된 뒤 한 번만 실행하려면 -1 사용
tracingDisabledboolean세션의 트레이싱 비활성화
groupIdstring세션 또는 백엔드 실행 간 트레이스 그룹화. workflowName 필요
traceMetadataRecord<string, any>세션 트레이스에 첨부할 사용자 정의 메타데이터. workflowName 필요
workflowNamestring트레이스 워크플로의 친숙한 이름
automaticallyTriggerResponseForMcpToolCallsbooleanMCP 도구 호출 완료 시 모델 응답 자동 트리거(기본값: true)
toolErrorFormatterToolErrorFormatter모델에 반환되는 도구 승인 거부 메시지 사용자 지정

connect(...) 옵션:

OptionTypePurpose
apiKeystring | (() => string | Promise<string>)이 연결에 사용할 API 키(또는 지연 로더)
modelOpenAIRealtimeModels | string전송 계층 옵션 타입에 존재합니다. RealtimeSession에서는 생성자에서 모델을 설정하고, 원시 전송은 connect 시점에도 모델을 사용할 수 있습니다
urlstring선택적 사용자 지정 Realtime 엔드포인트 URL
callIdstring기존 SIP 시작 통화/세션에 연결

RealtimeSession은 장기 유지 Realtime 연결 위에서 동작합니다. 대화 히스토리의 로컬 사본을 유지하고, 전송 이벤트를 수신하며, 도구와 출력 가드레일을 실행하고, 활성 에이전트 구성을 전송 계층과 동기화합니다.

기반 API 동작도 여전히 중요합니다:

  • 연결이 성공하면 session.created 이벤트로 시작하고, 이후 구성 변경 시 session.updated가 발생합니다
  • 대부분의 세션 속성은 시간에 따라 변경할 수 있지만, model은 대화 중간에 변경할 수 없고 voice는 세션이 오디오 출력을 생성하기 전에만 변경할 수 있으며, Realtime API는 활성화 후 트레이싱 수정을 허용하지 않으므로 트레이싱은 초기에 결정해야 합니다
  • Realtime API는 현재 단일 세션을 60분으로 제한합니다
  • 입력 오디오 전사는 비동기이므로, 최신 발화의 전사가 응답 생성이 시작된 뒤에 도착할 수 있습니다

SDK 계층에서 await session.connect()는 “전송이 대화를 시작할 만큼 준비되었다”는 뜻이지만, 정확한 시점은 전송 방식에 따라 다릅니다:

  • 기본 브라우저 WebRTC 전송에서는 데이터 채널이 열리자마자 SDK가 초기 session.update를 전송하고, 가능하면 대응하는 session.updated 이벤트를 기다린 뒤 connect()를 resolve하려고 시도합니다. 이는 지시사항, 도구, 모달리티가 적용되기 전에 오디오가 서버에 도달하는 것을 방지하기 위한 것입니다. 해당 확인이 오지 않으면 connect()는 짧은 타임아웃 후 resolve로 폴백합니다
  • 기본 서버 측 WebSocket 전송에서는 소켓이 열리고 초기 구성이 전송되면 connect()가 resolve됩니다. 따라서 대응 session.updated 이벤트는 connect()가 이미 resolve된 뒤 도착할 수 있습니다

원시 이벤트 모델이 필요하면 이 페이지와 함께 공식 Realtime conversations guide를 확인하세요.

기본적으로 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',
config: {
audio: {
input: {
turnDetection: {
type: 'semantic_vad',
eagerness: 'medium',
createResponse: true,
interruptResponse: true,
},
},
},
},
});

일반적인 두 가지 모드는 다음과 같습니다:

  • semantic_vad: 더 자연스러운 턴 경계를 목표로 하며 사용자가 아직 끝나지 않은 것처럼 들리면 조금 더 기다릴 수 있습니다
  • server_vad: 임계값 기반 성격이 더 강하며 threshold, prefixPaddingMs, silenceDurationMs, idleTimeoutMs 같은 설정을 노출합니다

턴 경계를 직접 관리하려면 audio.input.turnDetectionnull로 설정하세요. 공식 voice activity detection guideRealtime conversations guide에서 기반 동작을 더 자세히 설명합니다.

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 will still trigger the `audio_interrupted` event for you
// to cut off the audio playback when using WebSockets

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',
});
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',
});
const imageDataUrl = 'data:image/png;base64,...';
session.addImage(imageDataUrl, { triggerResponse: false });
session.sendMessage('Describe what is in this image.');

triggerResponse: false를 전달하면 모델에 응답을 요청하기 전에 이미지를 이후 텍스트 또는 오디오 턴과 일괄 처리할 수 있습니다. 이는 공식 Realtime conversations image input guidance와 일치합니다.

상위 SDK 계층에서는 sendMessage()addImage()가 기본적으로 응답을 트리거합니다. 원시 전송 이벤트, 푸시-투-토크 흐름, 또는 사용자 지정 moderation/검증 단계를 사용할 때는 수동 응답 제어가 중요합니다.

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',
});
session.transport.on('*', (event) => {
// JSON parsed version of the event received on the connection
});
// Send any valid event as JSON. For example triggering a new response
session.transport.sendEvent({
type: 'response.create',
// ...
});

일반적인 경우는 두 가지입니다:

  1. audio.input.turnDetection = null로 VAD를 완전히 비활성화하면, 오디오 턴을 커밋한 다음 response.create를 전송하는 책임이 사용자에게 있습니다
  2. VAD를 유지하되 turnDetection.interruptResponse = falseturnDetection.createResponse = false로 설정하면, API는 여전히 턴을 감지하지만 응답 생성은 사용자에게 맡깁니다

두 번째 패턴은 모델 응답 전에 사용자 입력을 검사하거나 moderation하고 싶을 때 유용합니다. 이는 공식 Realtime conversations guidance on disabling automatic responses와 일치합니다.

일반 에이전트와 유사하게 핸드오프를 사용해 에이전트를 여러 에이전트로 분리하고 이들 사이를 오케스트레이션하여 성능을 개선하고 문제 범위를 더 잘 한정할 수 있습니다.

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의 핸드오프는 약간 다르게 동작합니다. 핸드오프가 수행되면 진행 중인 세션이 새 에이전트 구성으로 업데이트됩니다. 따라서 새 에이전트는 진행 중인 대화 히스토리에 자동으로 접근할 수 있고, 현재 입력 필터는 적용되지 않습니다.

세션이 라이브로 유지되므로 핸드오프 중에는 해당 세션의 모델이 바뀌지 않습니다. 음성 변경은 기반 Realtime API 규칙을 따르며 세션이 오디오 출력을 생성하기 전에만 동작합니다. Realtime 핸드오프는 주로 동일 세션에서 RealtimeAgent 구성 간 전환을 위한 것이며, 예를 들어 gpt-5.4 같은 추론 모델처럼 다른 모델을 사용하거나 비실시간 백엔드 에이전트로 위임해야 하면 도구를 통한 위임을 사용하세요.

일반 에이전트와 마찬가지로 Realtime Agents도 작업 수행을 위해 도구를 호출할 수 있습니다. Realtime은 함수 도구(로컬 실행)와 호스티드 MCP 서버 도구(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과 동일한 환경에서 실행됩니다. 즉, 세션을 브라우저에서 실행하면 도구도 브라우저에서 실행됩니다. 민감한 작업이 필요하면 도구 내부에서 백엔드를 호출하고 서버가 권한 작업을 수행하도록 하세요.

이렇게 하면 브라우저 측 도구가 서버 측 로직으로 가는 얇은 백채널로 동작할 수 있습니다. 예를 들어 examples/realtime-next는 브라우저에서 refundBackchannel 도구를 정의해 요청과 현재 대화 히스토리를 서버의 handleRefundRequest(...)로 전달하고, 그곳에서 별도의 Runner가 다른 에이전트나 모델을 사용해 환불을 평가한 뒤 결과를 음성 세션으로 반환할 수 있습니다.

호스티드 MCP 서버 도구는 hostedMcpTool로 구성할 수 있으며 원격에서 실행됩니다. MCP 도구 가용성이 바뀌면 세션은 mcp_tools_changed를 발생시킵니다. MCP 도구 호출 완료 후 세션이 모델 응답을 자동 트리거하지 않게 하려면 automaticallyTriggerResponseForMcpToolCalls: false를 설정하세요.

현재 필터링된 MCP 도구 목록은 session.availableMcpTools로도 확인할 수 있습니다. 이 속성과 mcp_tools_changed 이벤트는 모두 활성 에이전트에서 활성화된 호스티드 MCP 서버만 반영하며, 에이전트 구성의 allowed_tools 필터 적용 후 결과입니다.

호스티드 MCP 설정은 보안 서버 선택, 헤더, 승인 정보를 연결 전 구성으로 다룰 때 가장 이해하기 쉽습니다. RealtimeSession.connect()가 전송을 열기 전에 SDK는 활성 에이전트의 호스티드 MCP 도구 정의를 해석하고, Realtime API로 보내는 초기 세션 구성에 지원되는 MCP 필드를 포함합니다.

이 타이밍은 브라우저 WebRTC 앱에서 특히 중요합니다. 임시 클라이언트 시크릿은 항상 서버에서 발급되므로, 비밀로 유지해야 하는 호스티드 MCP 자격 증명이나 사용자 지정 headers는 초기 session 페이로드의 일부로 서버 측 POST /v1/realtime/client_secrets 요청에 첨부해야 합니다. 장기 자격 증명을 브라우저 코드에 두고 connect() 시작 후 나중에 추가할 계획을 세우지 마세요.

Realtime API 수준에서는 이후 session.update 호출로 도구 및 다른 변경 가능한 세션 필드를 계속 바꿀 수 있고, SDK 자체도 활성 에이전트가 바뀔 때 session.update를 전송합니다. 다만 브라우저 앱에서는 보안 호스티드 MCP 초기화를 서버 측 연결 전 관심사로 취급하고, 브라우저 측 RealtimeSession 구성을 서버가 발급한 내용과 일치시키세요.

도구가 실행되는 동안 에이전트는 사용자의 새 요청을 처리할 수 없습니다. 경험을 개선하는 한 가지 방법은 도구 실행 직전임을 알리거나, 도구 실행 시간을 벌기 위한 특정 문구를 말하도록 에이전트에 지시하는 것입니다.

함수 도구가 즉시 또 다른 모델 응답을 트리거하지 않고 끝나야 한다면 @openai/agents/realtimebackgroundResult(output)를 반환하세요. 이렇게 하면 도구 출력이 세션으로 돌아가되, 응답 트리거는 사용자가 제어할 수 있습니다.

함수 도구 타임아웃 옵션(timeoutMs, timeoutBehavior, timeoutErrorFunction)은 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 ?? [];
// making your call 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 기본 거부 텍스트를 사용합니다.

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.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()를 사용하세요. 현재 히스토리와의 diff를 전송 계층에 요청해 필요한 delete/create 이벤트를 보내며, 대응 대화 이벤트가 돌아오면 로컬 session.history 뷰가 업데이트됩니다.

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 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({
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);
}