콘텐츠로 이동

음성 에이전트 구축

기본 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',
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를 보내세요.

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

gpt-realtime-2 같은 추론 기능을 지원하는 Realtime 모델의 경우 세션 구성에서 reasoning.effort를 설정하세요. 추론 노력 수준이 높을수록 지연 시간과 토큰 사용량이 증가할 수 있습니다. 모델이 여러 도구를 병렬로 호출할 수 있는지 제어하려면 parallelToolCalls도 설정할 수 있습니다.

음성-음성 세션의 일반적인 선택은 outputModalities: ['audio']이며, 오디오 출력과 전사본을 제공합니다. 텍스트 전용 응답을 원할 때만 ['text']로 전환하세요.

새로운 매개변수라서 RealtimeSessionConfig에 대응되는 매개변수가 없는 경우 providerData를 사용할 수 있습니다. providerData로 전달된 모든 값은 원문 session 객체의 일부로 전달됩니다.

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

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

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는 현재 단일 세션을 60분으로 제한합니다.
  • 입력 오디오 전사는 비동기식이므로 최신 발화의 전사본은 응답 생성이 이미 시작된 뒤에 도착할 수 있습니다.

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

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

원문 이벤트 모델이 필요하다면 이 페이지와 함께 공식 Realtime 대화 가이드를 읽어보세요.

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

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

  • semantic_vad: 더 자연스러운 턴 경계를 목표로 하며, 사용자가 아직 말을 끝내지 않은 것처럼 들릴 때 조금 더 기다릴 수 있습니다.
  • server_vad: 임계값 기반에 더 가깝고 threshold, prefixPaddingMs, silenceDurationMs, idleTimeoutMs 같은 설정을 제공합니다.

턴 경계를 직접 관리하려면 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 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-2',
});
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',
});
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',
});
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는 여전히 턴을 감지하지만 응답 생성은 사용자가 직접 수행하도록 남겨둡니다.

두 번째 패턴은 모델이 응답하기 전에 사용자 입력을 검사하거나 모더레이션하려는 경우에 유용합니다. 이는 공식 자동 응답 비활성화에 대한 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 규칙을 따릅니다. 세션이 오디오 출력을 생성하기 전에만 작동합니다. 다른 모델을 사용해야 하는 경우, 예를 들어 gpt-5.4 같은 추론 모델이나 비실시간 백엔드 에이전트에 위임해야 하는 경우에는 delegation through tools를 사용하세요.

일반 에이전트와 마찬가지로 실시간 에이전트도 작업을 수행하기 위해 도구를 호출할 수 있습니다. 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 이벤트는 모두 에이전트 구성의 allowed_tools 필터를 적용한 뒤, 활성 에이전트에서 활성화된 호스티드 MCP 서버만 반영합니다.

호스티드 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/realtime에서 backgroundResult(output)을 반환하세요. 이렇게 하면 도구 출력을 세션에 다시 보내면서 응답 트리거는 사용자가 제어할 수 있게 둡니다.

함수 도구 타임아웃 옵션(timeoutMs, timeoutBehavior, timeoutErrorFunction)은 Realtime 세션에서도 동일하게 동작합니다. 기본값인 error_as_result에서는 타임아웃 메시지가 도구 출력으로 전송됩니다. raise_exception에서는 세션이 ToolTimeoutError가 포함된 error 이벤트를 내보내며, 해당 호출에 대한 도구 출력은 전송하지 않습니다.

에이전트가 특정 도구를 호출할 때 전달한 인수 외에도, Realtime Session이 추적하는 현재 대화 기록의 스냅샷에 접근할 수 있습니다. 이는 현재 대화 상태를 바탕으로 더 복잡한 작업을 수행해야 하거나 tools for delegation을 사용할 계획일 때 유용할 수 있습니다.

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)하고 필요한 삭제/생성 이벤트를 보내도록 요청합니다. 대응되는 대화 이벤트가 돌아오면 로컬 session.history 뷰가 업데이트됩니다.

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2',
});
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);
}