GPT-Live 실시간 음성
GPT-Live WebSocket으로 전이중 음성 상호작용을 수행하고 세션 이벤트를 처리합니다.
GPT-Live는 지속 WebSocket으로 전이중 음성 상호작용을 제공하며 동일한 세션에서 입력 오디오 전송, 음성 및 전사 결과 수신, 세션 구성 업데이트 및 다중 턴 문맥 유지를 할 수 있습니다. 현재 기록된 모델은 gpt-live-1이며 실제 사용 가능 범위는 현재 계정과 모델 카탈로그를 기준으로 합니다.
wss://api.agtcloud.cn/v1/live/sessions인증 및 연결
서버 애플리케이션은 WebSocket 핸드셰이크 Header에서 Bearer API 키를 사용합니다. 모델은 쿼리 매개변수에 넣지 않고 연결 후 첫 session.start 이벤트에서 구성합니다.
| 항목 | 값 |
|---|---|
| 연결 주소 | wss://api.agtcloud.cn/v1/live/sessions |
| Authorization | Bearer your-api-key |
| 모델 위치 | session.start.session.model |
| 입력 오디오 | Base64로 인코딩한 원시 PCM 오디오 청크 |
장기 API 키는 신뢰할 수 있는 서버에만 보관합니다. 이 문서는 해당 WebSocket 라우트만 다루며 WebRTC, SIP, sideband, 세션 fork 또는 녹음 다운로드는 포함하지 않습니다.
세션 흐름
- Bearer API 키로 WebSocket 연결을 설정합니다.
session.start를 보내 모델, 지침, 오디오 및 위임 방식을 구성합니다.session.started를 받은 후 오디오 또는 애플리케이션 명령 전송을 시작합니다.- 입력 전사, 출력 전사, 출력 오디오 및 사용량 이벤트를 지속적으로 처리합니다.
- 오디오 생성을 중지한 뒤
session.close를 보냅니다. - 이벤트를 계속 수신하고
session.closed의 최종 사용량으로 마무리합니다.
발화 턴 종료가 세션 종료를 뜻하지는 않습니다. 다중 턴 대화는 동일한 연결을 유지하고 업무 로직에서 전체 세션을 종료할 준비가 되었을 때만 session.close를 보냅니다.
시작 매개변수
| 필드 | 유형 | 설명 |
|---|---|---|
type | string | 첫 프레임은 항상 session.start입니다 |
event_id | string | 오류를 연결하는 선택적 클라이언트 이벤트 식별자 |
session.model | string | 필수, GPT-Live 모델 ID |
session.instructions | string | 선택적 음성 어시스턴트 지침 |
session.audio.format | object | 오디오 형식입니다. 예: audio/pcm, 24 kHz |
session.audio.output.voice | string | 출력 음색입니다. 예: marin. 선택 가능한 값은 모델에 따라 달라집니다 |
session.delegation | object | 위임 방식으로 client 또는 responses를 사용합니다 |
연결 시작 후 session.update로 지침 등 변경이 허용된 세션 구성을 업데이트하고 session.updated 확인을 기다릴 수 있습니다. 동일한 연결에서 session.start를 다시 보내거나 업데이트 이벤트로 최상위 음성 모델을 전환하지 않습니다.
오디오 준비 및 호출 예시
예시는 PCM signed 16-bit little-endian, 24 kHz, 모노 오디오를 사용하며 WAV 파일 헤더를 포함하지 않습니다. 100 ms마다 4800바이트이며 전송할 때 각 오디오 청크를 Base64로 인코딩합니다.
# 입력 오디오 준비
ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio.pcm
# 저장된 출력 오디오 재생
ffplay -f s16le -ar 24000 -ac 1 reply.pcmNode.js 예시는 ws가 필요합니다. Python 예시는 세션 시작과 종료만 확인하며 오디오는 보내지 않습니다.
npm install ws
python -m pip install websocket-clientimport fs from 'node:fs';
import WebSocket from 'ws';
const key = process.env.API_KEY;
const file = process.argv[2];
if (!key || !file) throw new Error('API_KEY와 PCM 파일 경로가 필요합니다');
const pcm = fs.readFileSync(file);
if (!pcm.length || pcm.length % 2) {
throw new Error('PCM은 비어 있지 않고 16비트 샘플 단위로 정렬되어야 합니다');
}
const ws = new WebSocket('wss://api.agtcloud.cn/v1/live/sessions', {
headers: { Authorization: 'Bearer ' + key },
handshakeTimeout: 10000,
});
const audioOut = fs.createWriteStream('reply.pcm');
let started = false;
let closing = false;
let finalized = false;
let sender;
let drain;
let offset = 0;
let inputFinishedAt = 0;
let lastOutputAt = 0;
function send(event) {
ws.send(JSON.stringify(event));
}
function closeSession(reason) {
if (closing) return;
closing = true;
clearInterval(sender);
console.error('종료 요청:', reason);
if (started && ws.readyState === WebSocket.OPEN) {
send({ type: 'session.close' });
drain = setTimeout(() => ws.terminate(), 15000);
} else {
ws.terminate();
}
}
const limit = setTimeout(() => closeSession('예시 제한: 60초'), 60000);
ws.on('open', () => send({
type: 'session.start',
event_id: 'start_001',
session: {
model: 'gpt-live-1',
instructions: '중국어로 간단히 답변하고 사용자가 먼저 말할 때까지 기다려 주세요.',
audio: {
format: { type: 'audio/pcm', rate: 24000 },
output: { voice: 'marin' },
},
delegation: { type: 'client' },
},
}));
ws.on('message', (raw) => {
let event;
try {
event = JSON.parse(raw.toString());
} catch {
closeSession('유효하지 않은 JSON');
return;
}
if (event.type !== 'session.output_audio.delta') {
console.log(raw.toString());
}
if (event.type === 'session.started' && !started) {
started = true;
sender = setInterval(() => {
if (closing || ws.readyState !== WebSocket.OPEN) return;
if (offset >= pcm.length) {
inputFinishedAt ||= Date.now();
if (
lastOutputAt &&
Date.now() - Math.max(lastOutputAt, inputFinishedAt) >= 3000
) {
closeSession('출력 완료');
return;
}
send({
type: 'session.input_audio.append',
audio: Buffer.alloc(4800).toString('base64'),
});
return;
}
const chunk = pcm.subarray(offset, offset + 4800);
offset += chunk.length;
send({
type: 'session.input_audio.append',
audio: chunk.toString('base64'),
});
}, 100);
}
if (event.type === 'session.output_audio.delta') {
lastOutputAt = Date.now();
audioOut.write(Buffer.from(event.delta, 'base64'));
}
if (event.type === 'session.output_transcript.delta') {
lastOutputAt = Date.now();
}
if (event.type === 'session.closed') {
finalized = true;
ws.close(1000);
}
if (event.type === 'error') closeSession('서비스 오류');
});
ws.on('error', (error) => {
console.error(error.message);
closeSession('연결 오류');
});
ws.on('close', (code, reason) => {
clearTimeout(limit);
clearTimeout(drain);
clearInterval(sender);
audioOut.end();
console.error({ code, reason: reason.toString(), finalized });
if (!finalized || code !== 1000) process.exitCode = 1;
});
process.on('SIGINT', () => closeSession('사용자가 중지했습니다'));이벤트 Reference
이 인터페이스는 session.input_audio.append로 Base64 오디오를 보내며 Muse의 바이너리 오디오 프레임 및 endStream이나 OpenAI Realtime의 input_audio_buffer.* 이벤트를 사용하지 않습니다.
session.start
GPT-Live 세션을 시작하고 모델, 지침, 오디오 및 위임 구성을 제출합니다.
| 필드 | 유형 | 필수 | 설명 | 예시 값 |
|---|---|---|---|---|
type | string | 예 | 항상 session.start입니다. | session.start |
event_id | string | 아니요 | 클라이언트 이벤트 식별자입니다. | start_001 |
session.model | string | 예 | GPT-Live 모델 ID입니다. | gpt-live-1 |
session.instructions | string | 아니요 | 음성 어시스턴트의 대화 지침입니다. | 중국어로 간단히 답변해 주세요. |
session.audio.format.type | string | 아니요 | 오디오 형식 유형입니다. | audio/pcm |
session.audio.format.rate | integer | 아니요 | 오디오 샘플링 주파수입니다. | 24000 |
session.audio.output.voice | string | 아니요 | 출력 음색입니다. | marin |
session.delegation.type | string | 예 | 위임 방식입니다. | client |
{
"type": "session.start",
"event_id": "start_001",
"session": {
"model": "gpt-live-1",
"instructions": "중국어로 간단히 답변해 주세요.",
"audio": {
"format": {
"type": "audio/pcm",
"rate": 24000
},
"output": {
"voice": "marin"
}
},
"delegation": {
"type": "client"
}
}
}사용량 및 과금
session.usage.updated는 현재 세션의 누적값을 반환하며 단일 턴의 새 사용량을 뜻하지 않습니다. 클라이언트는 여러 단계의 값을 합산하면 안 됩니다. 정상 종료 시 session.closed.usage.seconds를 최종 세션 길이로 사용합니다.
비용에는 음성 세션 길이, 백그라운드 모델 사용량 및 도구 호출이 포함될 수 있습니다. 구체적인 가격과 현재 모델 사용 가능 범위는 모델 목록을 기준으로 합니다.
위임 방식
client: 애플리케이션이 위임할 작업을 직접 처리하며 기본 음성 세션을 먼저 검증할 때 적합합니다.responses: 서버가 구성된 Responses 모델과 도구를 실행합니다. 대상 모델, 도구 및 권한은 현재 사이트에서 사용할 수 있어야 합니다.
responses를 사용하면 백그라운드 작업의 모델 및 도구 비용이 별도로 발생할 수 있습니다. 예시의 MODEL_ID를 항상 사용할 수 있는 고정 모델로 간주하지 않습니다.
오류 처리
| 증상 | 확인 및 처리 |
|---|---|
| 핸드셰이크가 401 반환 | Bearer API 키, 모델 권한 및 현재 사이트 주소를 확인합니다 |
| 경로가 404 반환 | 최종 연결 경로가 /v1/live/sessions이며 /v1이 중복 연결되지 않았는지 확인합니다 |
| 첫 프레임 오류 또는 시작 시간 초과 | 연결 후 먼저 session.start를 보내고 session.started를 받은 뒤 오디오를 보냅니다 |
| 소리가 없거나 왜곡됨 | PCM 비트 깊이, 바이트 순서, 샘플링 주파수, 채널 및 WAV 파일 헤더를 확인합니다 |
session.closed를 받지 못함 | 마지막 누적 사용량, 오류 이벤트 및 종료 이유를 저장하며 전체 오디오를 자동으로 다시 보내지 않습니다 |
| 백그라운드 위임 거부 | Responses 모델, 도구, 권한 및 현재 사이트의 지원 범위를 확인합니다 |