WebSockets
WebSockets
API를 골라 연결 단계와 세션 이벤트를 확인할 수 있는 음성 연결 가이드예요. GPT-Live(음성)와 Realtime API 두 가지를 다뤄요.
출처: 문서
본문
서버를 GPT-Live에 연결하기
서버가 오디오를 캡처하거나 클라이언트의 오디오 스트림을 중계할 때는 기본(primary) WebSocket을 써요. WebSocket은 양방향으로 오디오와 JSON 이벤트를 나르고, 프로젝트 API 키는 그 신뢰할 수 있는 서버에 보관해요. 브라우저·모바일 애플리케이션은 WebRTC부터 시작하세요.
이 가이드는 GPT-Live로 오디오를 스트리밍하는 내용을 다뤄요. 기존 세션을 모니터링하거나 제어하려면 Server-side controls를, 추론·도구 백엔드를 Responses API에 연결하려면 Responses WebSocket mode를 참고하세요.
인증하고 세션 시작하기
- 쿼리 파라미터 없이
wss://api.openai.com/v1/live/sessions에 연결해요.Authorization: Bearer ***로 인증하고 예시에 나온 연결 헤더를 포함해요. - 첫 메시지로
session.start를 보내요.session객체 안에 모델, 대화 지침, 오디오 형식, 음성, 위임(delegation) 구성을 넣어요. - 오디오나 애플리케이션 명령을 보내기 전에
session.started를 기다려요. 여기에는 해석된 세션 구성과 세션 ID가 들어 있어요.
아래 예시는 marin 음성, 24kHz PCM16 오디오, 웹 검색이 있는 Responses 백엔드를 써요. 대화 지침은 짧게 유지하세요. 백엔드 지침·도구·도구 권한은 Delegation and tools에서 구성해요.
SDK로 오디오 스트리밍
Node.js는 npm install openai ws로 설치하고 JavaScript 예시를 client.mjs로 저장해요. Python(macOS/Linux)은 openai[realtime]을 설치하고 client.py로 저장해요. 서버 환경에 OPENAI_API_KEY를 설정하세요. 이 예시들은 Live를 지원하는 SDK 버전이 필요해요. 예시는 표준 입력에서 24kHz raw 모노 PCM16 오디오를 읽고, 반환된 오디오를 같은 형식으로 표준 출력에 써요. 이 스트림을 자신의 앱 오디오 캡처·재생에 연결하세요. 로그와 전사 이벤트는 표준 오류로 보내 오디오 스트림을 망치지 않게 해요.
import OpenAI from "openai";
import { LiveWS } from "openai/resources/live/ws";
// stdin/stdout은 24kHz raw 모노 PCM16 오디오를 나릅니다 (WAV 아님).
process.stdin.pause();
const ws = new LiveWS(new OpenAI());
let started = false;
let closing = false;
let finalized = false;
let pendingByte = Buffer.alloc(0);
let closeTimeout;
ws.socket.on("open", () => {
ws.send({
type: "session.start",
event_id: "event_start",
session: {
model: "gpt-live-1",
instructions:
"Be concise. Delegate requests needing current information to the backend, which can search the web.",
audio: {
format: { type: "audio/pcm", rate: 24000 },
output: { voice: "marin" },
},
delegation: {
type: "responses",
responses: {
model: "gpt-5.6-luna",
tools: [{ type: "web_search" }],
tool_choice: "auto",
},
},
},
});
});
process.stdin.on("data", (chunk) => {
if (!started || closing || ws.socket.readyState !== 1) return;
const bytes = Buffer.concat([pendingByte, chunk]);
const completeLength = bytes.length - (bytes.length % 2);
pendingByte = bytes.subarray(completeLength);
if (completeLength) {
ws.send({
type: "session.input_audio.append",
audio: bytes.subarray(0, completeLength).toString("base64"),
});
}
});
// close 명령을 보내기 전에 final 이벤트 핸들러를 먼저 등록합니다.
ws.on("event", (event) => {
if (event.type === "session.started") {
started = true;
console.error("Session ready", event.session.id);
process.stdin.resume();
} else if (event.type === "session.output_audio.delta") {
process.stdout.write(Buffer.from(event.delta, "base64"));
} else if (event.type === "session.closed") {
finalized = true;
clearTimeout(closeTimeout);
process.stdin.pause();
console.error("Final session usage", event.usage);
ws.close();
} else {
// 전사 델타와 중첩 response.event usage를 포함합니다.
console.error(JSON.stringify(event));
}
});
process.on("SIGINT", () => {
if (closing) return;
if (!started || ws.socket.readyState !== 1) {
ws.socket.platformSocket.terminate();
return;
}
closing = true;
process.stdin.pause();
ws.send({ type: "session.close" });
closeTimeout = setTimeout(() => {
console.error("Incomplete finalization: session.closed was not received");
process.exitCode = 1;
ws.socket.platformSocket.terminate();
}, 15_000);
});
ws.on("error", (error) => {
console.error(error.message);
process.exitCode = 1;
});
ws.socket.on("close", () => {
clearTimeout(closeTimeout);
process.stdin.pause();
if (!finalized) {
console.error("Connection closed without final session usage");
process.exitCode = 1;
}
});
Python 버전도 같은 흐름이에요. session.start로 시작해 session.output_audio.delta에서 오디오를 디코딩해 stdout에 쓰고, session.closed에서 최종 usage를 받아 정리해요. SIGINT로 정상 종료를 요청해요.
오디오 소스와 플레이어를 연결해 node client.mjs 또는 python client.py를 실행해요. Session ready가 뜬 뒤 기록된 샘플 속도에 맞춰 연속 마이크 스트림을 공급하세요. 파일 전체를 한 번에 파이프하는 것은 라이브 마이크가 아니에요. 오디오 소스의 EOF는 대화를 끝내지 않아요. 정상 종료를 원하면 프로세스에 SIGINT를 보내요.
예시는 오디오 스트림을 연결해 줄 뿐, 캡처·버퍼링·재생·리샘플링은 여러분 앱이 처리해요. 모델 동작을 평가하기 전에 이 부분들을 자신의 기기와 네트워크로 먼저 테스트하세요.
오디오 형식 고르기
시작 시 session.audio.format을 설정해요. 하나의 형식이 입력과 출력 양쪽에 적용되고 세션 중에는 바꿀 수 없어요.
{"type":"audio/pcm","rate":24000}: 24kHz 모노 signed 16-bit little-endian PCM, 기본값{"type":"audio/pcm","rate":16000}: 16kHz 모노 signed 16-bit little-endian PCM{"type":"audio/pcmu","rate":8000}: 8kHz G.711 μ-law, 샘플당 1바이트{"type":"audio/pcma","rate":8000}: 8kHz G.711 A-law, 샘플당 1바이트
raw 바이트를 WAV 같은 컨테이너 헤더 없이 base64로 인코딩해요. PCM 청크는 완전한 16비트 샘플을 포함해야 하므로 바이트 길이가 짝수여야 해요. 예시는 다음 입력 청크로 남은 바이트를 넘겨요. 청크 경계는 그 외에는 임의적이에요: 연속적이고 순서 있는 스트림을 유지하세요. 샘플 속도가 설정된 속도와 다르면 리샘플링하세요. 형식 설정을 바꾼다고 입력 바이트가 변환되지는 않아요. G.711에 맞게 예시를 바꾸려면 PCM 특유의 2바이트 정렬 로직 없이 각 청크의 코덱 바이트를 전달하고, 출력 플레이어도 같은 코덱으로 구성하세요. 일치하는 G.711 스트림은 PCM 변환 없이 그대로 통과할 수 있어요. 전화 연결은 Telephony integrations를 참고하세요.
이벤트 보내고 받기
각 이벤트를 JSON 텍스트 메시지로 보내요. 오디오는 그 메시지 안에서 base64로 이동해요.
- 오디오 보내기:
session.input_audio.append에 raw base64 인코딩 바이트를audio에 담아 보내요. 오디오 추가에는 확인 응답이 없어요. - 오디오 받기: 각
session.output_audio.delta이벤트의delta를 디코딩해 구성된 형식으로 순서대로 재생 큐에 넣어요. - 전사 받기:
session.input_transcript.delta와session.output_transcript.delta의delta텍스트를 해당 전사에 이어 붙여요. - 백엔드 이벤트 받기: Responses 위임을 쓰면 각
response.event봉투의 중첩event를 처리해요. - 오류 처리:
error이벤트의 거부된 명령과 세션 오류를 처리해요. 있으면error.client_event_id로 명령을 식별하세요.
어플리케이션 오디오 큐로 재생을 추적하세요. GPT-Live의 기본 WebSocket은 타이밍 필드나 output-audio-done 이벤트 없이 출력 오디오를 보내요. 캡션을 구성하려면 전사 타임스탬프를, 위임 작업을 추적하려면 백엔드 이벤트를 사용하세요. GPT-Live는 오디오 스트림이 연속적으로 흐르면서 언제 듣고 말할지 스스로 관리해요. 위임된 백엔드 작업을 시작·이어가려면 response.create를 써요.
진행 중인 세션 구성
Live 모델, 초기 대화 지침, 오디오 형식, 음성, 위임 모드는 시작 시 고정돼요. 기존 위임 모드 안에서 지원되는 설정은 session.update로 바꿔요. 생략한 설정은 현재 값을 유지해요. 성공한 업데이트는 해석된 세션 구성을 담은 session.updated를 돌려줘요. session.instructions.append로 대화 지침을 더하고, session.input_audio.mute/unmute로 들어오는 오디오를 제어해요. 입력을 음소거해도 백엔드 작업이 취소되거나 생성된 음성이 멈추지는 않아요. 컨텍스트 업데이트·전사·입력 제어·사용량은 Managing sessions를 참고하세요.
세션 닫기
대화가 끝나면 session.close를 보내요. session.closed 리스너를 먼저 설치하고 그 이벤트가 올 때까지 계속 받은 뒤 연결을 해제해요. 예시는 최대 15초 기다리고, 종료 이벤트가 오지 않으면 불완전한 종료로 보고해요. session.closed의 최종 음성 사용량과 이미 받은 백엔드 사용량은 보존하세요. 음성 지속시간 업데이트는 누적 스냅숏이라 더하지 마세요. session.closed 전에 전송 실패나 타임아웃이 있으면 최종 사용량이 확인되지 않은 채 남아요. 전체 수명 주기는 Managing sessions를 참고하세요.
Realtime API: WebSocket으로 연결
WebSockets은 실시간 데이터 전송에 널리 지원되는 API라, 서버 간(server-to-server) 애플리케이션에서 OpenAI Realtime API에 연결하기 좋은 선택이에요. 브라우저·모바일 클라이언트는 WebRTC를 권장해요.
Realtime과의 서버 간 통합에서 백엔드 시스템은 WebSocket으로 Realtime API에 직접 연결해요. 토큰이 안전한 백엔드 서버에서만 유지되므로 표준 API 키로 이 연결을 인증할 수 있어요.
WebSocket으로 연결
아래는 Realtime API에 WebSocket으로 연결하는 몇 가지 예시예요. 아래 WebSocket URL 외에 OpenAI API 키를 사용한 인증 헤더도 전달해야 해요. 애플리케이션이 안전 식별자를 할당한다면, 안정적이고 개인정보를 보존하는 최종 사용자 식별자를 OpenAI-Safety-Identifier 헤더로 넘겨요.
브라우저에서 WebRTC 연결 가이드처럼 임시 API 토큰으로 WebSocket을 쓸 수도 있지만, 브라우저나 모바일 앱 같은 클라이언트에서 연결한다면 대부분 WebRTC가 더 견고한 해결책이에요.
Node.js ws 모듈:
import WebSocket from "ws";
const url = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1";
const ws = new WebSocket(url, {
headers: {
Authorization: "Bearer " + process.env.OPENAI_API_KEY,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
});
ws.on("open", function open() {
console.log("Connected to server.");
});
ws.on("message", function incoming(message) {
console.log(JSON.parse(message.toString()));
});
Python(websocket-client):
# websocket-client 라이브러리가 필요합니다: pip install websocket-client
import os
import json
import websocket
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
url = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1"
headers = [
"Authorization: *** " + OPENAI_API_KEY,
"OpenAI-Safety-Identifier: hashed-user-id",
]
ws = websocket.WebSocketApp(url, header=headers, on_open=..., on_message=...)
ws.run_forever()
Ruby(OpenAI SDK)는 client.realtime.connect(model: "gpt-realtime-2.1")으로 연결해요. 브라우저에선 표준 WebSocket 인터페이스도 쓸 수 있지만(Deno, Cloudflare Workers 같은 환경), 수명이 짧은 토큰은 앱 서버에서 가져와야 해요.
이벤트 보내고 받기
Realtime API 세션은 개발자가 내는 client-sent events와 Realtime API가 세션 수명 주기를 알려주는 server-sent events를 조합해 관리돼요.
WebSocket 위에서는 아래 Node.js 예시처럼 JSON 직렬화된 이벤트를 문자열로 보내고 받아요(다른 WebSocket 라이브러리도 같은 원리예요).
import WebSocket from "ws";
const url = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1";
const ws = new WebSocket(url, {
headers: {
Authorization: "Bearer " + process.env.OPENAI_API_KEY,
"OpenAI-Safety-Identifier": "hashed-user-id",
},
});
ws.on("open", function open() {
console.log("Connected to server.");
// 연결되면 WebSocket으로 클라이언트 이벤트를 보냅니다.
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
instructions: "Be extra nice today!",
},
})
);
});
// 서버 이벤트를 듣고 파싱합니다.
ws.on("message", function incoming(message) {
console.log(JSON.parse(message.toString()));
});
WebSocket 인터페이스는 Realtime 모델과 상호작용할 수 있는 가장 저수준 인터페이스로, 소켓 연결 위에서 base64 인코딩 오디오 청크를 직접 보내고 처리해야 해요. WebSocket으로 오디오를 주고받는 법은 Realtime conversations 가이드를 참고하세요.
더 알아보기 (Learn more)
서버 쪽에서 세션을 제어하려면 Server-side controls를, 전화 연결은 SIP 가이드를, 브라우저 연결은 WebRTC 가이드를 참고하세요.