실시간 에이전트 구축
세션 설정
섹션 제목: “세션 설정”오디오 처리
섹션 제목: “오디오 처리”기본 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-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를 전송하세요.
outputModalities, audio.input, audio.output을 사용하는 최신 SDK 구성 형식을 권장합니다. modalities, inputAudioFormat, outputAudioFormat, inputAudioTranscription, turnDetection과 같은 이전 SDK 별칭도 이전 버전과의 호환성을 위해 계속 정규화되지만, 새 코드에서는 여기에 표시된 중첩 audio 구조를 사용해야 합니다.
gpt-realtime-2.1과 같이 추론을 지원하는 Realtime 모델에서는 세션 구성에 reasoning.effort를 설정하세요. 추론 강도를 높이면 지연 시간과 토큰 사용량이 증가할 수 있습니다. 모델이 여러 도구를 병렬로 호출할 수 있는지 제어하려면 parallelToolCalls도 설정할 수 있습니다.
음성 대 음성 세션에서는 일반적으로 오디오 출력과 트랜스크립트를 제공하는 outputModalities: ['audio']를 선택합니다. 텍스트 전용 응답이 필요한 경우에만 ['text']로 전환하세요.
새로 추가되어 RealtimeSessionConfig에 대응하는 매개변수가 없는 경우 providerData를 사용할 수 있습니다. providerData에 전달된 모든 항목은 원문 session 객체의 일부로 전달됩니다.
생성 시 설정할 수 있는 추가 RealtimeSession 옵션은 다음과 같습니다.
| 옵션 | 타입 | 용도 |
|---|---|---|
context | TContext | 세션 컨텍스트에 병합되는 추가 로컬 컨텍스트 |
historyStoreAudio | boolean | 로컬 기록 스냅샷에 오디오 데이터를 저장합니다(기본적으로 비활성화됨) |
outputGuardrails | RealtimeOutputGuardrail[] | 세션의 출력 가드레일(가드레일 참조) |
outputGuardrailSettings | { debounceTextLength?: number } | 가드레일 실행 주기입니다. 기본값은 100이며, 전체 텍스트를 사용할 수 있을 때 한 번만 실행하려면 -1을 사용합니다 |
tracingDisabled | boolean | 세션의 트레이싱 비활성화 |
groupId | string | 여러 세션 또는 백엔드 실행의 트레이스를 그룹화합니다. workflowName이 필요합니다 |
traceMetadata | Record<string, any> | 세션 트레이스에 연결할 사용자 지정 메타데이터입니다. workflowName이 필요합니다 |
workflowName | string | 트레이스 워크플로의 이해하기 쉬운 이름 |
automaticallyTriggerResponseForMcpToolCalls | boolean | MCP 도구 호출이 완료되면 모델 응답을 자동으로 트리거합니다(기본값: true) |
toolErrorFormatter | ToolErrorFormatter | 모델에 반환되는 도구 승인 거부 메시지 사용자 지정 |
toolExecution | RealtimeToolExecutionConfig | 로컬 실시간 함수 도구의 SDK 측 실행 설정입니다. 승인 대기 요청 전에 입력 가드레일을 실행하려면 preApprovalInputGuardrails: true로 설정합니다 |
connect(...) 옵션은 다음과 같습니다.
| 옵션 | 타입 | 용도 |
|---|---|---|
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를 전송하고,connect()를 완료하기 전에 해당session.updated이벤트를 기다리려고 시도합니다. 이는 instructions, tools, 모달리티가 적용되기 전에 오디오가 서버에 도달하는 것을 방지하기 위한 것입니다. 확인 이벤트가 도착하지 않으면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.1', 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로 설정하세요. 공식 음성 활동 감지 가이드와 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 playbackWebRTC와 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, 대역 외 컨텍스트 주입 또는 음성 입력과 명시적인 텍스트 설명을 함께 사용할 때 유용합니다.
이미지 입력
섹션 제목: “이미지 입력”실시간 음성 대 음성 세션에는 이미지도 포함할 수 있습니다. 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 responsesession.transport.sendEvent({ type: 'response.create', // ...});일반적인 두 가지 경우는 다음과 같습니다.
audio.input.turnDetection = null로 VAD를 완전히 비활성화하면 오디오 턴을 커밋한 다음response.create를 전송해야 합니다.- VAD를 활성화한 상태에서
turnDetection.interruptResponse = false와turnDetection.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 규칙을 따르며, 세션이 오디오 출력을 생성하기 전에만 가능합니다. 실시간 핸드오프는 주로 동일한 세션에서 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 도구
섹션 제목: “호스티드 MCP 도구”호스티드 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를 전송합니다. 그러나 브라우저 앱에서는 안전한 호스티드 MCP 초기화를 서버 측 연결 전 작업으로 취급하고, 브라우저 측 RealtimeSession 구성을 서버에서 발급한 구성과 일치시켜야 합니다.
백그라운드 결과
섹션 제목: “백그라운드 결과”도구가 실행되는 동안 에이전트는 사용자의 새 요청을 처리할 수 없습니다. 사용자 경험을 개선하는 한 가지 방법은 도구를 실행하기 전에 이를 알리거나, 도구를 실행할 시간을 확보할 수 있도록 특정 문구를 말하도록 에이전트에 지시하는 것입니다.
함수 도구가 즉시 다른 모델 응답을 트리거하지 않고 완료되어야 한다면 @openai/agents/realtime에서 backgroundResult(output)을 반환하세요. 이렇게 하면 도구 출력을 세션으로 다시 전송하면서 응답 트리거는 직접 제어할 수 있습니다.
제한 시간
섹션 제목: “제한 시간”함수 도구 제한 시간 옵션(timeoutMs, timeoutBehavior, timeoutErrorFunction)은 Realtime 세션에서도 동일하게 작동합니다. 기본값인 error_as_result에서는 제한 시간 메시지가 도구 출력으로 전송됩니다. raise_exception에서는 세션이 ToolTimeoutError와 함께 error 이벤트를 내보내며 해당 호출에 대한 도구 출력은 전송하지 않습니다.
대화 기록 접근
섹션 제목: “대화 기록 접근”에이전트가 특정 도구를 호출할 때 사용한 인수뿐만 아니라 Realtime 세션에서 추적하는 현재 대화 기록의 스냅샷에도 접근할 수 있습니다. 이는 현재 대화 상태를 기반으로 더 복잡한 작업을 수행해야 하거나 위임용 도구를 사용할 계획인 경우 유용합니다.
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 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'), );});제한 사항
섹션 제목: “제한 사항”- 현재 함수 도구 호출은 사후에 편집할 수 없습니다.
- 기록의 어시스턴트 텍스트는
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({ 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);}