음성 에이전트 구축
세션 설정
섹션 제목: “세션 설정”오디오 처리
섹션 제목: “오디오 처리”기본 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() 호출을 중단해야 합니다.
세션 구성
섹션 제목: “세션 구성”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-1.5', 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 를 보내세요.
더 새로운 SDK 구성 형태인 outputModalities, audio.input, audio.output 사용을 권장합니다. modalities, inputAudioFormat, outputAudioFormat, inputAudioTranscription, turnDetection 같은 이전 SDK 별칭도 하위 호환성을 위해 계속 정규화되지만, 새 코드에서는 여기 표시된 중첩 audio 구조를 사용해야 합니다.
speech-to-speech 세션에서는 일반적으로 outputModalities: ['audio'] 를 사용하며, 이렇게 하면 오디오 출력과 함께 전사도 제공됩니다. 텍스트 전용 응답만 원할 때는 ['text'] 로 전환하세요.
새로 추가된 매개변수로 RealtimeSessionConfig 에 대응하는 매개변수가 없다면 providerData 를 사용할 수 있습니다. providerData 에 전달한 내용은 모두 원시 session 객체의 일부로 전달됩니다.
생성 시점에 설정할 수 있는 추가 RealtimeSession 옵션:
| Option | Type | Purpose |
|---|---|---|
context | TContext | 세션 컨텍스트에 병합되는 추가 로컬 컨텍스트 |
historyStoreAudio | boolean | 로컬 히스토리 스냅샷에 오디오 데이터를 저장합니다(기본적으로 비활성화) |
outputGuardrails | RealtimeOutputGuardrail[] | 세션용 출력 가드레일(Guardrails 참고) |
outputGuardrailSettings | { debounceTextLength?: number } | 가드레일 실행 주기입니다. 기본값은 100 이며, 전체 텍스트를 사용할 수 있을 때 한 번만 실행하려면 -1 을 사용합니다 |
tracingDisabled | boolean | 세션의 트레이싱 비활성화 |
groupId | string | 세션 또는 백엔드 실행 전반에 걸쳐 trace 를 그룹화합니다. workflowName 이 필요합니다 |
traceMetadata | Record<string, any> | 세션 trace 에 첨부할 사용자 지정 메타데이터입니다. workflowName 이 필요합니다 |
workflowName | string | trace 워크플로의 친숙한 이름 |
automaticallyTriggerResponseForMcpToolCalls | boolean | MCP 도구 호출이 완료되면 모델 응답을 자동으로 트리거합니다(기본값: true) |
toolErrorFormatter | ToolErrorFormatter | 모델에 반환되는 도구 승인 거부 메시지를 사용자 지정 |
connect(...) 옵션:
| Option | Type | Purpose |
|---|---|---|
apiKey | string | (() => string | Promise<string>) | 이 연결에 사용되는 API 키(또는 지연 로더) |
model | OpenAIRealtimeModels | string | 전송 수준 옵션 타입에 존재합니다. RealtimeSession 에서는 생성자에서 모델을 설정하며, 원시 전송은 연결 시점에도 모델을 사용할 수 있습니다 |
url | string | 선택적 사용자 지정 Realtime 엔드포인트 URL |
callId | string | 기존 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 하려고 합니다. 이는 instructions, tools, modalities 가 적용되기 전에 오디오가 서버에 도달하는 상황을 피하기 위한 것입니다. 해당 확인 응답이 끝내 도착하지 않으면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-1.5', config: { audio: { input: { turnDetection: { type: 'semantic_vad', eagerness: 'medium', createResponse: true, interruptResponse: true, }, }, }, },});대표적인 두 가지 모드는 다음과 같습니다:
semantic_vad: 더 자연스러운 턴 경계를 목표로 하며, 사용자가 아직 말을 끝내지 않은 것처럼 들릴 때 조금 더 기다릴 수 있습니다server_vad: 임곗값 중심으로 동작하며threshold,prefixPaddingMs,silenceDurationMs,idleTimeoutMs같은 설정을 노출합니다
턴 경계를 직접 관리하려면 audio.input.turnDetection 을 null 로 설정하세요. 공식 voice activity detection guide 및 Realtime conversations guide 에서 기반 동작을 더 자세히 설명합니다.
인터럽션(중단 처리)
섹션 제목: “인터럽션(중단 처리)”VAD 가 활성화되어 있으면 에이전트가 말하는 도중 사용자가 끼어들어 현재 응답을 중단시킬 수 있습니다. WebSocket 전송에서 SDK 는 input_audio_buffer.speech_started 를 수신하면, 사용자가 실제로 들은 부분까지만 assistant 오디오를 잘라내고 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 WebSocketsWebRTC 와 WebSocket 모두 진행 중인 응답을 중단하지만, 저수준 메커니즘은 전송마다 다릅니다. WebRTC 는 버퍼링된 출력 오디오를 자동으로 비웁니다. WebSocket 설정에서는 여전히 로컬 재생을 직접 중지해야 하며, 해당 truncation 및 conversation 이벤트가 전송에서 돌아오면 로컬 히스토리가 업데이트됩니다.
텍스트 입력
섹션 제목: “텍스트 입력”라이브 대화에 입력한 텍스트나 추가적인 구조화된 사용자 콘텐츠를 보내려면 sendMessage() 를 사용하세요.
import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Assistant',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime-1.5',});
session.sendMessage('Hello, how are you?');이는 텍스트와 음성을 함께 사용하는 UI, 대역 외 컨텍스트 주입, 또는 말로 한 입력에 명시적인 텍스트 설명을 덧붙이는 경우에 유용합니다.
이미지 입력
섹션 제목: “이미지 입력”Realtime speech-to-speech 세션에는 이미지도 포함할 수 있습니다. SDK 에서는 addImage() 를 사용해 현재 대화에 이미지를 첨부합니다.
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Assistant',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime-1.5',});
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() 가 기본적으로 응답을 트리거합니다. 원시 전송 이벤트, push-to-talk 흐름, 또는 사용자 지정 moderation/validation 단계를 다룰 때는 수동 응답 제어가 중요합니다.
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-1.5',});
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 responsesession.transport.sendEvent({ type: 'response.create', // ...});일반적인 경우는 두 가지입니다:
audio.input.turnDetection = null로 VAD 를 완전히 비활성화하면, 오디오 턴을 commit 한 뒤response.create를 보내는 책임이 사용자에게 있습니다- VAD 를 유지하되
turnDetection.interruptResponse = false및turnDetection.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 서버 도구
섹션 제목: “호스티드 MCP 서버 도구”호스티드 MCP 서버 도구는 hostedMcpTool 로 구성할 수 있으며 원격에서 실행됩니다. MCP 도구 가용성이 바뀌면 세션은 mcp_tools_changed 를 발생시킵니다. MCP 도구 호출이 완료된 후 세션이 모델 응답을 자동으로 트리거하지 않게 하려면 automaticallyTriggerResponseForMcpToolCalls: false 로 설정하세요.
현재 필터링된 MCP 도구 목록은 session.availableMcpTools 로도 확인할 수 있습니다. 이 속성과 mcp_tools_changed 이벤트는 모두 활성 에이전트에서 활성화된 호스티드 MCP 서버만 반영하며, 에이전트 구성의 allowed_tools 필터 적용 이후 결과를 보여줍니다.
호스티드 MCP 설정은 보안이 필요한 서버 선택, 헤더, 승인 과정을 연결 전 구성으로 취급하면 이해하기 쉽습니다. RealtimeSession.connect() 가 전송을 열기 전에 SDK 는 활성 에이전트의 호스티드 MCP 도구 정의를 확인하고, 지원되는 MCP 필드를 Realtime API 로 보내는 초기 세션 구성에 포함합니다.
이 타이밍은 특히 브라우저 WebRTC 앱에서 중요합니다. ephemeral client secret 은 항상 서버에서 발급되므로, 비밀로 유지해야 하는 호스티드 MCP 자격 증명이나 사용자 지정 headers 는 초기 session 페이로드의 일부로 서버 측 POST /v1/realtime/client_secrets 요청에 첨부해야 합니다. 장기 자격 증명을 브라우저 코드에 넣고 connect() 시작 후 나중에 추가할 계획을 세우지 마세요.
Realtime API 수준에서는 이후 session.update 호출로 도구와 기타 변경 가능한 세션 필드를 여전히 바꿀 수 있고, SDK 자체도 활성 에이전트가 바뀌면 session.update 를 보냅니다. 하지만 브라우저 앱에서는 보안이 필요한 Hosted MCP 초기화를 서버 측의 연결 전 관심사로 다루고, 브라우저 측 RealtimeSession 구성을 서버가 발급한 내용과 일치시키는 것이 좋습니다.
백그라운드 결과
섹션 제목: “백그라운드 결과”도구가 실행되는 동안 에이전트는 사용자의 새 요청을 처리할 수 없습니다. 경험을 개선하는 한 가지 방법은 도구를 실행하기 직전에 안내하도록 하거나, 도구 실행 시간을 벌 수 있는 특정 문구를 말하도록 에이전트에 지시하는 것입니다.
함수 도구가 즉시 또 다른 모델 응답을 트리거하지 않고 완료되어야 한다면 @openai/agents/realtime 의 backgroundResult(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 가 출력 오디오 전사와 전사 델타를 사용하므로, 중요한 전제 조건은 별도의 텍스트 출력 modality 가 아니라 전사 가용성입니다.
제공한 가드레일은 모델 응답이 반환되는 동안 비동기적으로 실행되므로, 예를 들어 “특정 금지어를 언급함” 같은 사전 정의된 분류 트리거를 기준으로 응답을 차단할 수 있습니다.
가드레일이 발동하면 세션은 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 은 사용자 메시지, assistant 출력, 도구 호출, truncation 상태를 추적하는 로컬 history 스냅샷을 자동으로 유지합니다. 이를 UI 에 렌더링하거나, 도구 내부에서 검사하거나, 항목을 수정하거나 제거해야 할 때 업데이트할 수 있습니다.
대화가 변경되면 세션은 history_updated 를 발생시킵니다. 히스토리 변경을 요청해야 한다면 updateHistory() 를 사용하세요. 이 메서드는 전송 계층에 현재 히스토리의 diff 를 계산하고 필요한 delete/create 이벤트를 보내도록 요청하며, 대응하는 conversation 이벤트가 돌아오면 로컬 session.history 뷰가 업데이트됩니다.
import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'Assistant',});
const session = new RealtimeSession(agent, { model: 'gpt-realtime-1.5',});
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'), );});제한 사항
섹션 제목: “제한 사항”- 현재는 함수 도구 호출을 사후 수정할 수 없습니다
- 히스토리의 assistant 텍스트는
output_audio.transcript를 포함한 사용 가능한 전사에 의존합니다 - 인터럽션(중단 처리)으로 잘린 응답은 최종 전사를 유지하지 않습니다
- 입력 오디오 전사는 사용자가 말한 내용에 대한 대략적인 가이드로 보는 것이 좋으며, 모델이 오디오를 어떻게 해석했는지에 대한 정확한 복사본으로 보면 안 됩니다
도구를 통한 위임
섹션 제목: “도구를 통한 위임”
대화 히스토리와 도구 호출을 결합하면, 더 복잡한 작업을 수행하도록 대화를 다른 백엔드 에이전트에 위임한 뒤 그 결과를 사용자에게 다시 전달할 수 있습니다.
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 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.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);}