턴 디텍션, 침묵이 아니라 발화 의도를 재는 장치
턴 디텍션은 사용자가 말을 마쳤는지 판단해 봇이 응답을 시작할 시점을 정하는 기능입니다. OpenAI Realtime API는 이를 session.audio.input.turn_detection 필드로 설정하며, 오디오 스트림의 무음 구간 길이만으로 판단하는 server_vad와 모델이 발화 완결을 판단한 시점에 청크를 끊는 semantic_vad 두 방식을 제공합니다. 기본값은 server_vad이고, 무음 임계값을 짧게 잡을수록 응답은 빨라지지만 사용자가 문장 중간에 잠시 멈추는 순간을 발화 종료로 오판할 위험이 커집니다.
바지인 한 번이 취소 신호 세 개를 요구한다
사용자가 봇이 말하는 도중 끼어들면(바지인) 멈춰야 할 대상은 하나가 아닙니다. 재생 중인 TTS 오디오 버퍼, 아직 토큰을 생성 중인 LLM 스트림, 진행 중일 수 있는 툴콜까지 세 갈래를 동시에 정지시켜야 합니다. Realtime API의 기본 동작은 VAD가 사용자 발화를 감지하면 진행 중이던 응답을 취소하고 새 응답을 시작하는 것이며, 오탐지를 줄이려면 VAD를 끄고 애플리케이션이 오디오 전송 시점을 직접 통제하는 푸시투토크 방식으로 전환할 수도 있습니다.
설계에서 운영까지: 턴 디텍션·바지인 체크리스트
이 체크리스트는 음성 콜봇뿐 아니라 텍스트 챗봇이 스트리밍 응답 중에 사용자의 새 메시지를 받는 상황에도 그대로 적용됩니다. 다만 오디오 채널에는 VAD 오탐이라는 실패축이 하나 더 붙는다는 차이가 있습니다.
목표 지표는 코드를 쓰기 전에 숫자로 고정해 둡니다. 인터럽트 감지부터 오디오 정지·LLM 스트림 취소까지 총 지연 200ms 이내, 오탐지(false barge-in)로 정상 응답이 중간에 끊기는 비율 1% 이하, 진행 중이던 툴콜 결과가 바지인 이후에도 사용자에게 노출되는 사고 0건을 출발선으로 삼을 만합니다. 콜센터형 채널과 캐주얼 챗 채널은 배경 소음 수준이 달라 같은 무음 임계값을 그대로 공유하면 한쪽에서는 반드시 오탐이 늘어납니다.
실패는 네 갈래로 반복됩니다. 첫째, 무음 임계값을 지나치게 짧게 잡아 사용자가 생각을 정리하려 잠깐 멈춘 순간을 발화 종료로 오판하는 조기 엔드포인팅. 둘째, 반대로 임계값을 늘려 오탐은 줄지만 턴테이킹 간격이 벌어져 대화가 굼떠지는 경우. 셋째, TTS 재생은 멈췄는데 LLM 스트림 취소 신호가 늦게 도착해 이미 생성된 토큰이 다음 턴에 섞여 들어가는 부분 취소. 넷째, 검색이나 예약 같은 툴콜이 바지인 시점에도 계속 실행돼 취소된 발화의 결과가 뒤늦게 화면이나 음성으로 튀어나오는 레이스 컨디션입니다.
복구 전략은 취소를 단일 신호가 아니라 하나의 트랜잭션으로 묶는 데서 시작합니다. 오디오 버퍼 flush, LLM abort, 툴콜 cancel 세 호출에 같은 상관관계 ID를 부여해 셋 중 하나라도 실패하면 세션을 안전 상태(대기 턴)로 되돌리고, 부분 생성된 응답은 저장하지 않고 폐기합니다.
배포 전 시나리오 테스트에는 잡음 섞인 배경음, DTMF 입력, "네"·"음" 같은 짧은 백채널 발화를 오탐 유발 케이스로 반드시 포함합니다. server_vad와 semantic_vad는 실제 채널 녹음 샘플로 나눠 비교해 채널별 임계값을 분리하는 편이 안전합니다.
로그에는 턴 경계 타임스탬프, 취소 트리거 출처(VAD·사용자 키 입력·타임아웃), 취소 완료까지 걸린 지연, 부분 응답 폐기 여부를 필드로 남겨야 사후에 어떤 턴에서 무엇이 어긋났는지 재구성할 수 있습니다.
매주 오탐지·지연 초과 사례를 모아 무음 임계값과 semantic_vad 전환 여부를 재조정하고, 설정값이 바뀔 때마다 변경 로그를 코드 커밋과 분리해 남겨 두면 다음 회귀가 어떤 튜닝 때문인지 하루 안에 추적할 수 있습니다.
바로 쓰는 체크리스트
바지인 대응은 이벤트 하나가 아니라 상태 머신입니다. 오디오·LLM·툴콜 세 취소를 하나의 트랜잭션으로 묶고, 채널별 무음 임계값을 분리하며, 취소 지연과 부분 응답 폐기 여부를 로그로 남기면 다음 채널을 추가할 때도 같은 체크리스트를 그대로 재사용할 수 있습니다.