OpenAI Realtime API로 음성 챗봇 구현하며 겪은 WebSocket 연결 이슈
배경
회사에서 고객 상담용 음성 챗봇 프로토타입을 만들게 되었다. OpenAI의 Realtime API(베타)가 음성 입출력을 직접 처리할 수 있다는 점에서 선택했다. GPT-4o 기반이라 응답 품질도 괜찮았다.
문제 상황
초기 구현은 순조로웠으나, 실제 테스트 중 30초~1분 사이 연결이 자주 끊기는 현상이 발생했다. WebSocket 연결이 갑자기 close 이벤트를 받으며 종료되었다.
const ws = new WebSocket(
'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01',
{
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'OpenAI-Beta': 'realtime=v1'
}
}
);
ws.on('close', (code) => {
console.log(`Connection closed: ${code}`);
});
원인 분석
- Keep-alive 부재: WebSocket은 idle 상태가 길어지면 중간 프록시나 로드밸런서에서 연결을 끊을 수 있다.
- 오디오 버퍼 처리: 클라이언트에서 마이크 입력을 청크 단위로 보낼 때 버퍼 크기가 일정하지 않아 API 측에서 처리 오류가 발생했다.
해결 방법
1. Ping-Pong 메커니즘
20초마다 ping을 보내 연결을 유지했다.
let pingInterval;
ws.on('open', () => {
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 20000);
});
ws.on('close', () => {
clearInterval(pingInterval);
});
2. 오디오 버퍼 정규화
PCM16 포맷으로 고정하고, 100ms 단위로 청크를 나눠 전송했다.
const SAMPLE_RATE = 24000;
const CHUNK_SIZE = SAMPLE_RATE * 0.1 * 2; // 100ms
function sendAudioChunk(audioData) {
const event = {
type: 'input_audio_buffer.append',
audio: audioData.toString('base64')
};
ws.send(JSON.stringify(event));
}
3. 재연결 로직
연결 끊김 시 자동 재연결을 구현했다.
function connect() {
const ws = new WebSocket(/* ... */);
ws.on('close', () => {
setTimeout(() => {
console.log('Reconnecting...');
connect();
}, 3000);
});
return ws;
}
결과
위 조치 후 5분 이상 안정적으로 대화가 유지되었다. Realtime API는 아직 베타라 문서가 부족한 편이지만, 음성 latency가 낮아 실시간 상담 용도로는 충분히 활용 가능하다고 판단했다.
참고
- OpenAI Realtime API 문서
- WebSocket RFC 6455