콘텐츠로 이동

전송 방식

세션이 실행되는 위치와 미디어 원본 또는 이벤트를 얼마나 직접 제어해야 하는지에 따라 전송 방식을 선택합니다.

시나리오권장 전송 방식이유
브라우저 음성 대 음성 앱OpenAIRealtimeWebRTC가장 간편한 방식입니다. SDK가 마이크 캡처, 재생 및 WebRTC 연결을 관리합니다.
서버 측 Realtime 제어를 사용하는 브라우저 오디오앱 소유 WebRTC 오디오와 서버 측 RealtimeSession 조합브라우저가 오디오를 전달하는 동안 Realtime 이벤트, 도구 및 비즈니스 로직은 애플리케이션 서버에 유지됩니다.
React Native 모바일 앱네이티브 WebRTC를 기반으로 하는 앱 소유 RealtimeTransportLayerSDK는 React Native 패키지 조건을 제공하고 앱은 네이티브 WebRTC, 권한, 오디오 라우팅 및 수명 주기를 관리합니다.
서버 측 음성 루프 또는 사용자 지정 오디오 파이프라인OpenAIRealtimeWebSocket오디오 캡처와 재생을 이미 제어하고 있으며 이벤트에 직접 접근하려는 경우에 적합합니다.
SIP 또는 텔레포니 브리지OpenAIRealtimeSIPcallId를 사용하여 기존 SIP 시작 Realtime 통화에 RealtimeSession을 연결합니다.
Cloudflare Workers / workerdCloudflare 확장 전송 방식workerd는 전역 WebSocket 생성자를 사용하여 아웃바운드 WebSocket을 열 수 없습니다.
Twilio의 제공업체별 전화 흐름Twilio 확장 전송 방식Twilio 오디오 전달 및 인터럽션(중단 처리) 동작을 대신 처리합니다.

기본 브라우저 전송 방식은 WebRTC를 사용합니다. 마이크에서 오디오를 캡처하고 자동으로 재생하므로 빠른 시작에서는 임시 토큰과 session.connect(...)만으로 연결할 수 있습니다.

이 방식에서는 session.connect()가 완료되기 전에 초기 세션 구성이 session.updated로 확인될 때까지 기다리므로, 오디오 전송이 시작되기 전에 instructions와 tools가 적용됩니다. 확인이 도착하지 않는 경우를 위한 타임아웃 폴백도 있습니다.

자체 미디어 스트림이나 오디오 요소를 사용하려면 세션을 생성할 때 OpenAIRealtimeWebRTC 인스턴스를 제공합니다.

import {
RealtimeAgent,
RealtimeSession,
OpenAIRealtimeWebRTC,
} from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
async function main() {
// Keep a handle on the stream you pass in. The transport does not stop a caller-supplied
// stream on close(), so your application owns it and is responsible for ending it.
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
const transport = new OpenAIRealtimeWebRTC({
mediaStream,
audioElement: document.createElement('audio'),
});
const customSession = new RealtimeSession(agent, { transport });
// Later, once the application is actually done with the microphone, release it. Leaving this
// out keeps the microphone indicator on even after the session closes.
// mediaStream.getTracks().forEach((track) => track.stop());
}

OpenAIRealtimeWebRTC에 전달한 mediaStream의 소유권은 애플리케이션에 계속 유지됩니다. 전송 방식을 닫아도 해당 스트림의 트랙은 중지되지 않으므로 레벨 미터, 레코더 또는 재연결에 계속 사용할 수 있습니다. 애플리케이션에서 스트림 사용을 실제로 마쳤을 때 해당 트랙에서 stop()을 호출합니다. 전송 방식이 기본 마이크를 직접 열었다면 close()는 여전히 SDK 소유 트랙을 중지합니다.

더 낮은 수준에서 사용자 지정할 수 있도록 OpenAIRealtimeWebRTCchangePeerConnection도 허용합니다. 이를 통해 offer를 생성하기 전에 새로 생성된 RTCPeerConnection을 검사하거나 교체할 수 있습니다.

브라우저 측 RealtimeSession이 Realtime 이벤트를 사용하여 세션을 구성하고 제어하므로 내장 브라우저 전송 방식은 데이터 채널을 생성합니다. 애플리케이션에서 해당 이벤트와 제어를 애플리케이션 서버에 유지해야 한다면 브라우저 측 피어 연결 사용자 지정을 접근 제어 경계로 간주하지 말고 아래의 서버 측 제어 패턴을 사용합니다.

서버 측 제어를 사용하는 브라우저 오디오

섹션 제목: “서버 측 제어를 사용하는 브라우저 오디오”

일부 애플리케이션은 Realtime 이벤트, 도구 및 비즈니스 로직을 애플리케이션 서버에 유지하면서 브라우저 마이크 캡처와 오디오 재생을 사용해야 합니다. 이 아키텍처는 Realtime API의 통합 WebRTC 인터페이스서버 측 제어를 결합합니다.

  1. 브라우저는 표준 WebRTC API로 오디오 전용 피어 연결을 생성하고 SDP offer를 애플리케이션 서버로 전송합니다. 브라우저는 SDK의 내장 WebRTC 전송 방식을 사용하거나 Realtime 데이터 채널을 생성하지 않습니다.
  2. 애플리케이션 서버는 사용자를 인증하고 SDP 정책을 적용한 다음, offer와 서버 소유 세션 구성을 표준 API 키를 사용하여 /v1/realtime/calls로 전송합니다.
  3. 애플리케이션 서버는 SDP answer를 검증하고 응답의 Location 헤더에서 통화 ID를 읽습니다.
  4. 애플리케이션 서버는 WebSocket 전송 방식을 사용하는 서버 측 RealtimeSession을 생성하고 이벤트 리스너를 등록한 다음, 통화 ID를 session.connect(...)에 전달하고 초기 session.updated 이벤트를 기다립니다.
  5. 애플리케이션 서버는 SDP answer를 브라우저에 반환합니다. 브라우저는 answer를 원격 설명으로 설정하며, 오디오는 브라우저와 Realtime API 사이에서 직접 전송됩니다.
  6. 애플리케이션 서버는 Realtime 이벤트와 도구를 처리합니다. 브라우저에 상태 업데이트가 필요한 경우 애플리케이션 서버는 원문 Realtime 이벤트 스트림을 전달하는 대신 허용 목록에 포함된 애플리케이션 이벤트를 투영합니다.

오디오 전용 브라우저 피어 연결, 서버에서 적용하는 SDP 정책, 사이드밴드 RealtimeSession, 브라우저에 투영되는 이벤트 및 정리 동작을 포함한 전체 애플리케이션은 realtime-server-controlled 예제를 참고하세요.

앱 소유 전송 방식을 사용하는 React Native

섹션 제목: “앱 소유 전송 방식을 사용하는 React Native”

@openai/agents-core@openai/agents-realtime은 React Native 패키지 조건을 게시하므로 Metro는 Node.js 내장 기능 없이 이식 가능한 SDK shim을 해석할 수 있습니다. 내장 OpenAIRealtimeWebRTC는 브라우저 WebRTC 전역 객체와 DOM 오디오 요소에 의존하므로 여전히 브라우저 전송 방식입니다. React Native에서는 네이티브 WebRTC 구현을 설치하고 앱에서 관리한 다음, 사용자 지정 RealtimeTransportLayer를 통해 RealtimeSession에 연결합니다.

examples/realtime-react-native Expo 개발 빌드 예제에서는 앱 소유 react-native-webrtc 전송 방식, 마이크 권한, 오디오 라우팅 및 서버 측 임시 토큰 엔드포인트를 보여 줍니다. react-native-webrtc에는 네이티브 코드가 포함되어 있으므로 Expo Go는 지원되지 않습니다. 표준 OpenAI API 키는 서버에 보관하고 수명이 짧은 임시 클라이언트 토큰만 모바일 앱에 전달합니다.

세션을 생성할 때 transport: 'websocket' 또는 OpenAIRealtimeWebSocket 인스턴스를 전달하면 WebRTC 대신 WebSocket 연결을 사용할 수 있습니다. 서버 측 사용 사례, 텔레포니 브리지 및 사용자 지정 오디오 파이프라인에 적합합니다.

WebSocket 방식에서는 소켓이 열리고 초기 구성이 전송되면 session.connect()가 완료됩니다. 해당 session.updated 이벤트는 약간 늦게 도착할 수 있으므로 connect()가 완료되었다고 해서 해당 업데이트가 이미 에코되어 돌아왔다고 가정하지 마세요.

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
const myRecordedArrayBuffer = new ArrayBuffer(0);
const wsSession = new RealtimeSession(agent, {
transport: 'websocket',
model: 'gpt-realtime-2.1',
});
await wsSession.connect({ apiKey: process.env.OPENAI_API_KEY! });
wsSession.on('audio', (event) => {
// event.data is a chunk of PCM16 audio
});
wsSession.sendAudio(myRecordedArrayBuffer);

원문 PCM16 오디오 바이트를 처리하려면 원하는 녹음 및 재생 라이브러리를 사용합니다.

고급 연동을 위해 OpenAIRealtimeWebSocket은 자체 소켓 구현을 제공할 수 있는 createWebSocket()과 해당 사용자 지정 커넥터가 소켓을 연결 상태로 전환하는 역할을 담당할 때 사용하는 skipOpenEventListeners를 허용합니다. @openai/agents-extensions의 Cloudflare 전송 방식은 이러한 훅을 기반으로 구축되었습니다.

통화 제공업체 및 텔레포니 브리지용 SIP

섹션 제목: “통화 제공업체 및 텔레포니 브리지용 SIP”

RealtimeSession을 기존 SIP 시작 Realtime 통화에 연결하려면 OpenAIRealtimeSIP를 사용합니다. 이는 SIP를 인식하는 경량 전송 방식입니다. 오디오는 SIP 통화 자체에서 처리되며, callId를 사용하여 SDK 세션을 연결합니다.

  1. OpenAIRealtimeSIP.buildInitialConfig()로 초기 세션 구성을 생성하여 수신 통화를 수락합니다. 이렇게 하면 SIP 초대와 이후 SDK 세션이 동일한 기본값으로 시작됩니다.
  2. OpenAIRealtimeSIP 전송 방식을 사용하는 RealtimeSession을 연결하고 제공업체 웹훅에서 발급한 callId로 연결합니다.
  3. 제공업체별 미디어 전달 또는 이벤트 브리징이 필요한 경우 Twilio 확장과 같은 연동 전송 방식을 사용합니다.
import OpenAI from 'openai';
import {
OpenAIRealtimeSIP,
RealtimeAgent,
RealtimeSession,
type RealtimeSessionOptions,
} from '@openai/agents/realtime';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY!,
webhookSecret: process.env.OPENAI_WEBHOOK_SECRET!,
});
const agent = new RealtimeAgent({
name: 'Receptionist',
instructions:
'Welcome the caller, answer scheduling questions, and hand off if the caller requests a human.',
});
const sessionOptions: Partial<RealtimeSessionOptions> = {
model: 'gpt-realtime-2.1',
config: {
audio: {
input: {
turnDetection: { type: 'semantic_vad', interruptResponse: true },
},
},
},
};
export async function acceptIncomingCall(callId: string): Promise<void> {
const initialConfig = await OpenAIRealtimeSIP.buildInitialConfig(
agent,
sessionOptions,
);
await openai.realtime.calls.accept(callId, initialConfig);
}
export async function attachRealtimeSession(
callId: string,
): Promise<RealtimeSession> {
const session = new RealtimeSession(agent, {
transport: new OpenAIRealtimeSIP(),
...sessionOptions,
});
session.on('history_added', (item) => {
console.log('Realtime update:', item.type);
});
await session.connect({
apiKey: process.env.OPENAI_API_KEY!,
callId,
});
return session;
}

Cloudflare Workers 및 기타 workerd 런타임은 전역 WebSocket 생성자를 사용하여 아웃바운드 WebSocket을 열 수 없습니다. 내부적으로 fetch() 기반 업그레이드를 수행하는 확장 패키지의 Cloudflare 전송 방식을 사용합니다.

import { CloudflareRealtimeTransportLayer } from '@openai/agents-extensions';
import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'My Agent',
});
// Create a transport that connects to OpenAI Realtime via Cloudflare/workerd's fetch-based upgrade.
const cfTransport = new CloudflareRealtimeTransportLayer({
url: 'wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1',
});
const session = new RealtimeSession(agent, {
// Set your own transport.
transport: cfTransport,
});

전체 설정은 Cloudflare용 음성 에이전트를 참고하세요.

WebSocket을 직접 사용하거나 @openai/agents-extensions의 전용 Twilio 전송 방식을 사용하여 RealtimeSession을 Twilio에 연결할 수 있습니다. SDK가 Twilio Media Streams의 인터럽션(중단 처리) 타이밍과 오디오 전달을 처리하도록 하려면 전용 전송 방식이 더 적합한 기본 선택지입니다.

전체 설정은 Twilio용 음성 에이전트를 참고하세요.

다른 음성 대 음성 API 또는 자체 사용자 지정 전송 메커니즘을 사용하려면 RealtimeTransportLayer 인터페이스를 구현하고 RealtimeTransportEventTypes 이벤트를 직접 발생시킬 수 있습니다.

필요할 때 원문 Realtime 이벤트에 접근

섹션 제목: “필요할 때 원문 Realtime 이벤트에 접근”

기반 Realtime API에 더 직접적으로 접근하려면 두 가지 옵션이 있습니다.

RealtimeSession의 모든 기능을 계속 활용하면서 session.transport를 통해 전송 계층에 접근할 수 있습니다.

전송 계층은 수신하는 모든 이벤트를 * 이벤트로 발생시키며, sendEvent()를 사용하여 원문 이벤트를 전송할 수 있습니다. * 리스너는 현재 SDK 스키마가 인식하지 못하는 필드를 포함하여 원래 파싱된 JSON 페이로드의 구조화된 복제본을 수신합니다. 이름이 지정된 이벤트 리스너와 파생 이벤트 리스너는 계속해서 검증되고 정규화된 이벤트 객체를 수신합니다. 이는 session.update, response.create 또는 response.cancel과 같은 저수준 작업을 위한 우회 수단입니다.

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 response
session.transport.sendEvent({
type: 'response.create',
// ...
});

도구 자동 실행, 가드레일 또는 로컬 기록 관리가 필요하지 않다면 연결과 인터럽션(중단 처리)만 관리하는 “경량” 클라이언트로 전송 계층을 사용할 수도 있습니다.

import { OpenAIRealtimeWebRTC } from '@openai/agents/realtime';
const client = new OpenAIRealtimeWebRTC();
const audioBuffer = new ArrayBuffer(0);
await client.connect({
apiKey: '<api key>',
model: 'gpt-realtime-2.1',
initialSessionConfig: {
instructions: 'Speak like a pirate',
outputModalities: ['audio'],
audio: {
input: {
format: 'pcm16',
},
output: {
format: 'pcm16',
voice: 'ash',
},
},
},
});
// Listen for audio when you manage playback yourself
client.on('audio', (newAudio) => {});
client.sendAudio(audioBuffer);