`experimental_useRealtime()`
experimental_useRealtime()
실시간 프로바이더 모델과 양방향 오디오/텍스트 대화를 위한 브라우저 측 실시간 세션을 만듭니다.
이 훅은 토큰 기반 프로바이더 WebSocket, 애플리케이션 소유 WebSocket 릴레이, 그리고 모델이 지원을 선언하는 경우 선택적 WebRTC를 지원해요. 캡처와 재생 컨트롤, 턴 기반 텍스트 입력과 tool 출력을 제공해요. 턴 기반 대화 메시지는 UIMessage[] 를 사용하고, 연속 대화 자막 조각은 session 에 남아요.
import { openai } from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';
const model = openai.experimental_realtime('gpt-realtime');
function Conversation() {
const realtime = experimental_useRealtime({
model,
api: { token: '/api/realtime/setup' },
});
return <button onClick={realtime.connect}>Connect</button>;
}
AI Gateway의 경우 gateway.experimental_realtime(...) 을 모델로 전달하고 api.token 을 gateway.experimental_realtime.getToken() 을 호출하는 서버 측 설정 엔드포인트에 지정하세요.
모듈 스코프나 useMemo 를 사용해 렌더링 간에 모델과 세션 설정을 안정적으로 유지하세요. 둘 중 하나라도 교체하면 훅의 세션이 교체돼요.
출처: 문서
본문
연속 대화 (Continuous conversations)
OpenAI Live의 경우 openai.experimental_realtime('gpt-live-1') 과 애플리케이션 소유 WebSocket 릴레이를 사용하세요. 릴레이는 서버 자격 증명을 제공하고 프로바이더의 네이티브 텍스트 프레임을 전달하며, 연결을 받기 전에 클라이언트를 인증해야 해요. 프로덕션에서 wss: 를 사용하고 프로바이더 자격 증명을 서버 측에 두고 릴레이 연결에 여러분의 애플리케이션 인증을 적용하세요.
import {
openai,
type Experimental_OpenAIRealtimeModelLiveOptions,
} from '@ai-sdk/openai';
import { experimental_useRealtime } from '@ai-sdk/react';
const model = openai.experimental_realtime('gpt-live-1');
const sessionConfig = {
instructions: 'Be a concise, friendly assistant.',
providerOptions: {
openai: {
delegation: { type: 'client' },
} satisfies Experimental_OpenAIRealtimeModelLiveOptions,
},
};
function LiveConversation() {
const realtime = experimental_useRealtime({
model,
api: { websocket: 'wss://your-app.example/live' },
sessionConfig,
});
return (
<>
<button onClick={realtime.connect}>Connect microphone</button>
<button onClick={() => realtime.close()}>End conversation</button>
<p>
{realtime.status}: {realtime.session?.usage?.seconds} seconds
</p>
</>
);
}
현재 연속 브라우저 런타임은 JSON/PCM16 WebSocket 릴레이 미디어 프로파일을 지원해요. 연속 대화 의미론만으로 모든 코덱이나 transport를 보장하지는 않아요. 자체 오디오 파이프라인이 있는 애플리케이션은 다른 지원 코덱에 저수준 프로바이더를 사용할 수 있어요. OpenAI 특화 설정은 providerOptions.openai 아래에 있으며 camelCase 필드를 사용해요. 프로바이더가 이를 와이어 이름으로 변환해요.
애플리케이션 처리 클라이언트 위임 (Application-handled client delegation)
Live는 session.delegations 및 정규화된 이벤트를 통해 클라이언트 위임 메타데이터를 보고해요. 애플리케이션이 텍스트 대화, 에이전트 컨텍스트, tool 실행, 결과 검증을 소유해요. SDK는 Live용 범용 에이전트 실행기를 실행하지 않으며, Live 위임은 onToolCall 을 호출하지 않아요.
sendEvent 를 사용해 컨텍스트나 결과를 추가하세요. delegationId 를 현재 세션의 알려진 클라이언트 위임으로 설정하거나, 세션 전체 컨텍스트에는 null 로 설정하세요:
await realtime.sendEvent({
type: 'context-append',
delegationId: null,
content: 'The application has confirmed that the appointment is at 3 PM.',
providerOptions: { openai: { channel: 'commentary' } },
});
이 예제는 위임 메타데이터에서 추론한 작업 인자가 아니라 애플리케이션 제공 콘텐츠를 사용해요. OpenAI의 컨텍스트 채널은 commentary, thinking, instructions 이고, instructions 는 신뢰할 수 있는 애플리케이션 지시에 예약하세요. 성공적인 전송은 로컬 제출을 확인할 뿐, 프로바이더 수락이나 들리는 전달을 확인하지 않아요.
선택적 WebRTC
브라우저 직접 Live 오디오의 경우 api.websocket 을 api: { session: '/api/realtime-live' } 로 바꾸세요. 훅은 JSON { sdp, sessionConfig } 를 그 애플리케이션 엔드포인트에 POST하고 JSON { sdp, sessionId } 를 받을 것으로 기대해요. 서버에서는 애플리케이션 사용자를 인증하고 오퍼와 허용된 설정을 검증하세요.
애플리케이션의 세션 쿠키로 인증되는 동일 출처 브로커를 사용하세요. 내장 설정 요청은 브라우저의 기본 동일 출처 자격 증명을 사용해요. api.session 은 커스텀 인증 헤더, 커스텀 fetch, 교차 출처 자격 증명 옵션을 받지 않아요. 따라서 bearer-토큰 전용 또는 교차 출처 쿠키 브로커는 그 앞에 애플리케이션 소유 동일 출처 엔드포인트가 필요해요. 응답은 비어 있지 않은 sdp 와 sessionId 문자열(트리밍 후 검증)을 포함해야 하고, JSON 응답 본문은 1 MiB로 제한돼요.
서버 보유 프로바이더 키로 SDP를 교환하세요:
const answer = await openai
.experimental_realtime('gpt-live-1')
.doCreateWebRTCSession({
sdp,
sessionConfig: {
providerOptions: {
openai: {
delegation: { type: 'client' },
client: {
dataChannel: {
allowedClientEvents: ['session.close', 'session.thinking.append'],
allowedServerEvents: [
{ type: 'session.started' },
{ type: 'session.closed' },
{ type: 'session.usage.updated' },
{ type: 'session.delegation.created' },
{ type: 'session.input_transcript.delta' },
{ type: 'session.output_transcript.delta' },
{ type: 'session.thinking.appended' },
{ type: 'error' },
],
},
},
},
},
},
});
// Return Response.json(answer) from the application endpoint.
권한은 서버 소유예요. 브라우저 제공 위임 및 데이터 채널 정책을 오버라이드하세요. UI에 필요한 수명 주기, 자막, 위임, 명령 확인, 오류 이벤트를 허용하세요. examples/ai-e2e-next 의 실행 가능한 /realtime-live 예제는 프로토콜 음소거와 세 가지 컨텍스트 채널을 모두 추가해요.
WebRTC는 SDP를 통해 오디오를 협상하므로 고정 오디오 형식과 PCM 전용 maxPlaybackBufferSeconds 를 생략하세요. 오디오는 JSON 오디오 명령이 아니라 미디어로 전송돼요. connect({ capture: false }) 는 마이크 캡처 없이 시작하고 재사용 가능한 오디오 sender를 유지해요. resumeAudioCapture() 는 나중에 마이크를 부착할 수 있어요. 마이크 접근은 브라우저 권한과 보안 컨텍스트가 필요해요. 자동 재생은 사용자 제스처 후 resumePlayback() 이 필요할 수 있어요.
부착당 하나의 라이브 오디오 트랙이 선택되며 활성화되고 음소거되지 않은 트랙을 선호해요. isCapturing 은 음소거 및 종료 이벤트를 포함해 그 sender 트랙을 따르며, 트랙을 자동으로 전환하지 않아요. 호출자 소유 트랙은 중지되거나 비활성화되지 않고 분리돼요. 캡처 컨트롤은 sender 변경을 직렬화하고, 중지는 분리가 성공하거나 피어가 닫힌 후에만 보고돼요. 분리가 실패하면 차용한 트랙을 건드리지 않고 전송을 중지하기 위해 피어가 닫혀요. 클라이언트 위임은 두 transport 모두에서 애플리케이션 처리로 유지되고, Live 세션 업데이트는 지원되지 않는 채로 있어요.
수명 주기와 실패 처리 (Lifecycle and failure handling)
status === 'connected' 를 준비 신호로 사용하세요. connect() 는 프로바이더 준비를 기다릴 것을 약속하지 않아요. 운영상 시작 오류는 onError 와 status 를 통해 보고되며, 레거시 resolve-and-report 동작은 보존돼요.
close() 는 로컬 캡처와 제출을 중지한 다음 프로바이더가 최종 사용량을 확인하거나 마감 기한이 만료될 때까지 이벤트를 비워요. 실패한 close-명령 전송은 전송되지 않은 명령에 대한 확인을 기다리는 대신 더 짧은 수락 이벤트 드레인을 사용해요. session.finalization 을 읽으세요. 충족된 close 프로미스는 그 자체로 사용량을 확인하지 않아요. disconnect() 와 언마운트는 즉시 리소스를 해제해요.
훅 컨트롤은 렌더링과 프로바이더 이벤트에 걸쳐 안정적인 정체성을 유지해요. 유지된 컨트롤은 모델이나 엔드포인트 변경 후를 포함해 현재 커밋된 세션을 대상으로 해요. 커밋되지 않은 렌더는 활성 세션이나 그 콜백을 교체할 수 없어요. 콜백 전용 업데이트는 재연결 없이 커밋 시 적용돼요.
언마운트 후 connect, close, resumeAudioCapture, resumePlayback 은 mounted-hook 오류로 거부돼요. 다른 컨트롤은 동기 검증을 보존하고 수락된 제출에 대한 프로미스를 반환하는 sendEvent 를 포함해 그 오류를 동기적으로 던져요. 유지된 컨트롤은 언마운트된 세션을 다시 열 수 없어요.
명령 거부와 재생 실패는 복구 가능하며 건강한 프로토콜 연결을 실패로 표시하지 않아요. 복구 불가능한 transport 실패와 프로토콜 큐 오버플로는 제출과 캡처를 중지하고, 수락된 이벤트 접두어를 비우고, 정리해요. 신뢰할 수 없는 상태로 계속하면서 이벤트가 임의로 버려지지 않아요. 교체 연결에서 명령이나 부수효과 tools가 투명하게 재생되지 않아요.
연속 Live WebSocket 세션은 128 KiB 아웃바운드 와이어 프레임 한도와 다음 프레임 및 JSON/base64 인코딩 오버헤드를 포함한 128 KiB 결합 buffered-send 예산을 가져요. 크기가 큰 제어 메시지는 여러 명령으로 조용히 분할되지 않고 거부돼요. Live 컨텍스트 제출을 이 예산 내로 유지하세요. 예제 릴레이의 128 KiB maxPayload 는 프레임 한도와 일치해요. 이 바이트 상한은 사전-Live 무제한 바이트 정책을 유지하는 레거시 턴 기반 세션이나 선택적 WebRTC transport에는 적용되지 않아요.
연속 WebSocket PCM 재생 예산은 로컬 지연과 메모리를 제한해요. 오버플로 시 오래된 대기 오디오는 버려지고 재생이 일시 중지되며 onError 가 들리는 간극을 보고해요. 연결은 살아 있고, resumePlayback() 은 라이브 엣지의 새 오디오에서 재개해요. 자막과 위임된 작업의 완료는 해당 오디오가 들렸음을 증명하지 않아요.
Import
<Snippet
text={import { experimental_useRealtime } from "@ai-sdk/react"}
prompt={false}
/>
API 시그니처
매개변수 (Parameters)
<PropertiesTable
content={[
{
name: 'model',
type: 'Experimental_RealtimeModel',
description: 'The realtime model to connect to.',
},
{
name: 'api',
type: '{ token: string } | { websocket: string; protocols?: string[] } | { session: string }',
description:
'Choose one supported connection mechanism: token setup, a WebSocket relay, or a WebRTC SDP endpoint. Conversation semantics and connection capabilities are independent.',
properties: [
{
type: 'Object',
parameters: [
{
name: 'token',
type: 'string',
isOptional: true,
description:
'The setup endpoint that returns an Experimental_RealtimeSetupResponse.',
},
{
name: 'session',
type: 'string',
isOptional: true,
description:
'Application WebRTC setup endpoint. Accepts JSON { sdp, sessionConfig } and returns JSON { sdp, sessionId }.',
},
{
name: 'websocket',
type: 'string',
isOptional: true,
description:
'Application-owned raw-protocol relay URL. Use wss in production; never put provider credentials in the URL.',
},
{
name: 'protocols',
type: 'string[]',
isOptional: true,
description:
'Optional relay subprotocols, supported only with websocket.',
},
],
},
],
},
{
name: 'sessionConfig',
type: 'Partial<Experimental_RealtimeSessionConfig>',
isOptional: true,
description:
'Provider-neutral session configuration, such as instructions, voice, audio formats, input audio transcription, turn detection, tools, and providerOptions.',
},
{
name: 'sampleRate',
type: 'number',
isOptional: true,
description:
'Default audio sample rate used when inputAudioFormat.rate or outputAudioFormat.rate is not specified. Defaults to 24000.',
},
{
name: 'maxEvents',
type: 'number',
isOptional: true,
description:
'Maximum number of provider events to keep in the events array. Defaults to 500.',
},
{
name: 'startupTimeoutMs',
type: 'number',
isOptional: true,
description:
'Readiness deadline, including transport setup. Defaults to 30000.',
},
{
name: 'closeTimeoutMs',
type: 'number',
isOptional: true,
description:
'Deadline for confirmed provider finalization after graceful close. Defaults to 15000.',
},
{
name: 'rtcDisconnectTimeoutMs',
type: 'number',
isOptional: true,
description:
'WebRTC peer disconnect recovery grace period. Defaults to 5000. Failed ICE terminates immediately.',
},
{
name: 'maxPlaybackBufferSeconds',
type: 'number',
isOptional: true,
description:
'Continuous WebSocket PCM playback budget before reporting a recoverable gap and pausing playback. Defaults to 2. Unsupported with WebRTC.',
},
{
name: 'onToolCall',
type: '(options: { toolCall: { toolCallId: string; toolName: string; args: unknown } }) => unknown | Promise
반환값 (Returns)
<PropertiesTable
content={[
{
name: 'status',
type: "'disconnected' | 'connecting' | 'connected' | 'closing' | 'error'",
description: 'The current connection status.',
},
{
name: 'messages',
type: 'UIMessage[]',
description:
'Messages assembled from turn-based response text, transcript, and tool events. Continuous Live transcript fragments are exposed separately through session.transcripts.',
},
{
name: 'events',
type: 'Experimental_RealtimeServerEvent[]',
description:
'Recent normalized provider events for inspection or debug UI.',
},
{
name: 'isCapturing',
type: 'boolean',
description:
'Capture state observed through SDK controls and events on the selected audio track. External changes to borrowed tracks may require an explicit refresh; see Capture ownership.',
},
{
name: 'isPlaying',
type: 'boolean',
description: 'Whether model audio playback is active.',
},
{
name: 'session',
type: 'Experimental_RealtimeSessionState | undefined',
description:
'Session lifecycle metadata where supported: sessionId, transcript fragments, client delegation metadata, cumulative voice usage, input mute, terminationReason, and pending/confirmed/unconfirmed finalization. Transcript fragments may overlap or arrive late; they are not complete turns.',
},
{
name: 'connect',
type: '(options?: { stream?: MediaStream; capture?: boolean }) => Promise
Tool 호출 (Tool Calling)
gpt-realtime 같은 턴 기반 모델의 경우 tool 실행은 클라이언트 중심이에요. onToolCall 을 사용해 tool 호출을 처리하고 tool 출력을 반환하세요. 콜백 업데이트 전반에서 모델을 안정적으로 유지하세요:
const model = openai.experimental_realtime('gpt-realtime');
function WeatherConversation() {
const realtime = experimental_useRealtime({
model,
api: { token: '/api/realtime/setup' },
onToolCall: async ({ toolCall }) => {
if (toolCall.toolName === 'getWeather') {
const response = await fetch('/api/weather', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(toolCall.args),
});
return response.json();
}
},
});
return <button onClick={realtime.connect}>Connect</button>;
}
사용자 상호작용이 필요한 tools의 경우 onToolCall 에서 undefined 를 반환하고 나중에 addToolOutput 을 호출하세요.
턴 기반 프로바이더는 자동 연속 동작을 유지해요. Live는 아래 설명된 애플리케이션 처리 클라이언트 위임 흐름을 사용하며, 이러한 tool 콜백이나 턴 컨트롤을 사용하지 않아요.
미결 명령은 유지된 최근 ID 기록과는 별도로 제한돼요. 완료된 작업은 수명 명령 할당량을 부과하지 않아요. 새 ID를 사용하고 내구성 있는 작업 중복 제거를 애플리케이션에 유지하세요. 제한된 기록은 세션 전체 exactly-once 실행의 약속이 아니에요.
캡처 소유권 (Capture ownership)
SDK 획득 트랙은 로컬 캡처 중지나 정리 시 중지돼요. 호출자 소유 Live 트랙은 중지하거나 비활성화하지 않고 분리돼요. stopAudioCapture() 는 로컬 하드웨어 캡처를 제어하고, 프로바이더 input-audio-mute 는 원격 오디오 처리를 제어하며 그 확인을 통해 추적돼요. 이 둘은 별개 작업이에요. 프로토콜 음소거는 마이크를 해제하지 않고, 로컬 캡처 재개는 프로바이더 입력을 음소거 해제하지 않아요. resumeAudioCapture() 를 사용해 호출자 제공 스트림을 재사용하거나 재연결 없이 새 SDK 소유 마이크를 획득하세요. 애플리케이션은 자체 트랙을 끝냈을 때 중지할 책임이 여전히 있어요.
isCapturing 은 선택된 오디오 트랙의 SDK 캡처 컨트롤과 이벤트를 반영하고, 호출자 소유 미디어를 지속적으로 관찰하지 않아요. track.enabled 를 할당해도 이벤트가 발생하지 않고, track.stop() 을 호출해도 ended 이벤트가 발생하지 않아요. 차용한 트랙을 외부에서 변경한 후에는 필요에 따라 startAudioCapture(stream) 또는 resumeAudioCapture() 를 호출해 캡처 상태를 새로고침하고 재부착하세요. 트랙이 중지됐다면 라이브 오디오 트랙이 있는 스트림을 제공하세요. 중지된 트랙은 다시 시작할 수 없어요. SDK는 외부 트랙 상태를 폴링하지 않아요.
실험적 호환성 (Experimental compatibility)
이 업데이트는 명시적 릴레이 옵션을 추가하고 실험적 sendEvent 를 프로미스를 반환하도록 변경해요. 기존 토큰 설정과 인자 없는 connect 호출은 계속 지원돼요. 새 세션 상태는 프로바이더 브랜드 상태 객체가 아니라 session 을 사용해요. 일반 realtime 모델 소비자는 선택적 연결 메서드를 호출하기 전에 확인해야 해요. 모델 인터페이스와 이벤트 유니언은 실험적이며, 배타적 외부 스위치는 추가된 이벤트를 처리해야 할 수 있어요. OpenAI는 Realtime과 Live 모델 모두에 experimental_realtime 을 사용하며, 알 수 없거나 얼리 액세스 Live 모델 ID에는 프로바이더 특화 { api: 'live' } 오버라이드를 써요. 이 팩토리 옵션은 프로바이더 API를 선택하고, 훅의 api.websocket 옵션은 애플리케이션의 transport 엔드포인트를 선택해요. OpenAI Live 시작 옵션은 Experimental_OpenAIRealtimeModelLiveOptions 로 내보내져요.
서버 지원 앱 특화 tool 엔드포인트가 있는 완전한 예제는 Realtime 을 보세요.