고객센터 전화 상담, 인공지능 보이스 에이전트, 실시간 음성 비서와 같은 대화형 보이스봇 환경에서 응답 지연 시간(레이턴시)은 서비스의 완성도를 결정짓는 핵심 지표입니다. 일상적인 인간 대화에서 발화 교대(Turn-taking) 간격은 통상 200 ms ~ 500 ms 수준입니다. 종단 간(End-to-End) 지연 시간이 800 ms를 초과하면 대화가 끊기는 느낌이 들고, 사용자와 보이스봇이 동시에 말하는 발화 충돌(Talk-over)이 빈번하게 발생합니다.
음성 대화 파이프라인의 지연 시간은 음성 활동 감지(VAD), 음성 인식(STT), 거대 언어 모델(LLM) 추론, 음성 합성(TTS), 미디어 전송 단계에 걸쳐 누적됩니다. 특히 TTS는 LLM이 첫 번째 토큰을 생성한 직후의 중요 경로(Critical Path)에 위치하므로, 음성 합성의 첫 바이트 생성 시간(Time-to-First-Byte, TTFB)을 최소화하는 것이 1초 미만의 반응 속도를 달성하는 필수 전제 조건입니다.
Resemble AI는 실시간 대화 환경을 위해 전용 WebSocket 스트리밍 인터페이스(wss://websocket.cluster.resemble.ai/stream)를 제공합니다. 지속적 소켓 연결 유지, 원시 바이너리 PCM 전송, 비대칭 텍스트 청킹(Chunking), 즉각적인 사용자 끼어들기(Barge-in) 제어를 결합하면 음성 재생 시작 지연을 200 ms ~ 350 ms 범위로 단축할 수 있습니다.
1. 보이스봇의 엔드투엔드 레이턴시 예산
시스템 병목을 정확히 파악하기 위해 대화 턴의 일반적인 처리 소요 시간을 분석해 봅니다.
| 파이프라인 단계 | 일반적인 지연 시간 | 주요 엔지니어링 최적화 방안 |
|---|---|---|
| 음성 활동 감지(VAD) | 200 ms – 400 ms | 무음 감지 임계 시간 최적화(예: Silero VAD) |
| 음성 인식(STT) | 150 ms – 300 ms | WebSocket 기반 실시간 점진적 텍스트 전송 |
| LLM 첫 토큰 생성(TTFT) | 150 ms – 350 ms | 저지연 추론 클러스터, 추측 디코딩, 시스템 프롬프트 경량화 |
| 텍스트 청킹 및 TTS TTFB | 200 ms – 350 ms | Resemble AI WebSocket 스트리밍, 비대칭 청킹 적용 |
| 네트워크 전송 및 지터 버퍼 | 30 ms – 80 ms | WebRTC 미디어 서버(LiveKit), 동일 리전 서버 배치 |
| 종단 간 종합 지연 시간 | 730 ms – 1480 ms | 자연스러운 대화를 위한 800 ms 미만 달성에는 TTS 스트리밍 필수 |
기존의 동기식 REST API 방식은 문장이나 문단 전체의 합성이 끝날 때까지 기다린 후 오디오 파일을 반환하므로, TTS 단계에서만 1.5초 ~ 3초의 추가 지연이 발생합니다. 반면 스트리밍 합성을 사용하면 첫 번째 오디오 프레임이 생성되는 즉시 재생을 시작할 수 있습니다.
2. 음성 합성 전송 프로토콜 비교 및 선택
Resemble AI는 세 가지 합성 방식을 제공하지만, 실시간 대화형 보이스봇에 적합한 방식은 WebSocket 스트리밍뿐입니다.
동기식 REST API (POST /api/v2/projects/{project_uuid}/clips)
전체 오디오 파일을 렌더링한 후 한 번에 반환합니다. 배치 처리나 정적 안내 음성 생성에는 적합하지만 실시간 대화에는 지나치게 느립니다.
HTTP 청크 스트리밍 (POST https://f.cluster.resemble.ai/stream)
HTTP 청크 전송 인코딩을 통해 WAV 데이터를 점진적으로 수신합니다. 긴 글 낭독에는 유용하지만, 매 대화 턴마다 새로운 HTTP 연결을 생성해야 하므로 TCP 핸드셰이크와 TLS 암호화 협상 오버헤드(턴당 80 ms ~ 150 ms)가 누적됩니다.
WebSocket 실시간 스트리밍 (wss://websocket.cluster.resemble.ai/stream)
통화나 세션 내내 양방향 WebSocket 지속 연결을 유지합니다. 턴마다 연결을 맺는 비용이 전혀 없으며, 밀리초 단위로 생성되는 오디오 바이너리 프레임과 단어 단위 타임스탬프를 실시간으로 수신합니다.
플랜 및 동시성 요건: Resemble AI의 WebSocket 스트리밍 인터페이스는 Business 플랜 이상에서 지원됩니다. 기본 설정상 클러스터당 최대 20개의 동시 세션, API 키당 20개의 병렬 연결을 지원합니다.
3. 요청 페이로드 및 오디오 파라미터 최적화
WebSocket 전송 시 오디오 파라미터를 올바르게 구성하면 네트워크 전송량과 미디어 서버의 디코딩 부하를 크게 줄일 수 있습니다.
{
"voice_uuid": "YOUR_VOICE_UUID",
"project_uuid": "YOUR_PROJECT_UUID",
"data": "안녕하세요! 오늘 어떤 업무를 도와드릴까요?",
"binary_response": true,
"request_id": 1,
"output_format": "wav",
"sample_rate": 16000,
"precision": "PCM_16",
"no_audio_header": true,
"apply_custom_pronunciations": false
}
핵심 파라미터 튜닝 가이드
-
binary_response: true
기본값(false)에서는 Base64로 인코딩된 오디오 문자열이 JSON 객체로 반환됩니다. Base64 인코딩은 데이터 크기를 약 33% 증가시키며, 수신 측에서 디코딩 연산 부담을 발생시킵니다.binary_response: true를 지정하면 순수 오디오 바이너리 바이트가 직렬화 오버헤드 없이 전송됩니다. -
precision: "PCM_16"
Resemble 일부 엔드포인트는PCM_32를 기본값으로 사용합니다. 음성 통화 환경에서는 16비트 선형 PCM(PCM_16)으로도 매우 뛰어난 음질을 보장하면서 데이터 크기를 절반으로 줄일 수 있습니다. 또한 WebRTC 및 전화 교환망(SIP/RTP)의 내부 처리 포맷과 일치하므로 미디어 서버에서의 비트 심도 변환 과정이 생략됩니다. -
sample_rate: 16000(또는24000)
스튜디오 환경에서는 44.1 kHz나 48 kHz를 사용하지만, 전화 통신(Twilio, SIP 트렁크, G.711, Opus)에서는 16 kHz 광대역 오디오로 충분히 깨끗한 사람 목소리를 전달할 수 있습니다. 16 kHz는 48 kHz 대비 전송 데이터량이 3분의 1에 불과하여 네트워크 지터와 패킷 지연을 억제합니다. -
no_audio_header: true
바이너리 응답 모드에서 이 값을true로 설정하면 첫 번째 프레임의 표준 44바이트 RIFF/WAV 헤더가 제거됩니다. 수신 측에서는 별도의 헤더 파싱 없이 순수 PCM 샘플을 WebRTC AudioTrack이나 전화 RTP 패킷 생성기로 곧바로 전달할 수 있습니다. -
apply_custom_pronunciations: false
특정 전문 도메인 사전 매칭이 필수적인 경우가 아니라면false로 유지하여 사전 검색으로 인한 추가 지연을 방지합니다.
4. LLM 연동을 위한 비대칭 텍스트 청킹 전략
LLM의 스트리밍 토큰 출력을 Resemble AI TTS로 파이프라이닝할 때 텍스트 분할 단위의 설정은 중요한 엔지니어링 과제입니다.
- 단어 단위 전송(너무 작은 단위): 2~3단어 단위로 TTS에 전달하면 억양과 문맥이 끊겨 기계음처럼 들리고, WebSocket에 과도한 요청이 발생합니다.
- 문장 전체 대기(너무 큰 단위): 문장이 마침표로 끝날 때까지 기다리면 LLM이 15~30개 토큰을 생성하는 동안 800 ms ~ 1500 ms의 침묵이 발생합니다.
이 문제를 해결하기 위해 **비대칭 청킹 전략(Asymmetric Utterance Planning)**을 적용합니다.
[ 사용자 발화 종료 ]
│
▼
[ LLM 토큰 생성 시작 ]
│
├─► 청크 1 (초저지연): 첫 쉼표/대시 또는 4~8개 단어에서 즉시 분할
│ │
│ ▼
│ Resemble WebSocket으로 전송 ──► 약 300 ms 시점에 첫 음성 재생 시작
│
└─► 청크 2 이후 (자연스러운 문장): 문장 종결 부호(. ? !) 기준으로 버퍼링
│
▼
Resemble WebSocket으로 전송 ──► 청크 1 재생 도중 백그라운드에서 합성
청크 1: 즉각적인 첫 구절 전송
첫 번째 쉼표(,), 쌍점(:), 대시(—) 등 가벼운 쉼표 기호나 4~8개 단어(한국어 기준 약 15~25자)가 모이는 즉시 첫 조각을 Resemble AI로 전송합니다. 짧은 문구라도 실제 음성 재생에는 1.5초 ~ 2.5초가 소요되므로, 사용자가 첫 소리를 듣는 동안 LLM이 후속 텍스트를 생성하고 전송할 충분한 시간을 벌 수 있습니다.
청크 2 이후: 완전한 문장 단위 전송
클라이언트 버퍼에서 청크 1이 재생되는 동안, 후속 토큰들은 마침표(.), 물음표(?), 느낌표(!), 줄바꿈과 같은 명확한 문장 종결 기호까지 모아서 전송합니다. 이를 통해 Resemble Ultra 모델이 완전한 문맥을 바탕으로 자연스러운 억양과 호흡을 유지하게 됩니다.
텍스트 정제 및 마크다운 제거
LLM은 종종 굵은 글씨(**), 기울임꼴(*), 목록 기호(- ), 코드 블록 등의 마크다운 서식을 출력합니다. 이러한 기호가 TTS로 그대로 전달되면 기호 이름을 읽어버리거나 비정상적인 침묵이 발생합니다. WebSocket으로 전송하기 전에 정규식을 통해 정제해야 합니다.
import re
CLEANUP_REGEX = re.compile(r"[*_#`~]|\[.*?\]\(.*?\)|\n\s*[-*]\s*")
def sanitize_for_tts(text: str) -> str:
cleaned = CLEANUP_REGEX.sub(" ", text)
return " ".join(cleaned.split())
5. 지속 연결 관리 및 소켓 풀링
대화 턴마다 새 WebSocket 연결을 생성하면 100 ms ~ 250 ms의 네트워크 왕복 지연이 추가되어 스트리밍의 장점이 사라집니다.
아키텍처 베스트 프랙티스
- 세션 지속 연결 유지: 전화 통화나 WebRTC 세션이 유지되는 동안 동일한 WebSocket 연결을 재사용합니다.
- 웜 소켓 풀(Warm Connection Pool): 트래픽이 많은 서버 환경에서는
wss://websocket.cluster.resemble.ai/stream연결을 미리 맺어두고 새 세션이 인입될 때 대기 중인 소켓을 즉시 배정합니다. request_id기반 상태 추적: 단일 소켓에서 여러 발화를 처리할 때request_id를 1씩 증가시켜 각 요청을 구분합니다. 해당 번호의audio_end메시지가 수신되면 해당 발화의 전송이 끝난 것으로 처리합니다.
import asyncio
import json
import websockets
class ResembleAudioStreamer:
def __init__(self, api_token: str, voice_uuid: str, project_uuid: str):
self.uri = "wss://websocket.cluster.resemble.ai/stream"
self.api_token = api_token
self.voice_uuid = voice_uuid
self.project_uuid = project_uuid
self.ws = None
self.request_counter = 0
async def connect(self):
headers = {"Authorization": f"Bearer {self.api_token}"}
self.ws = await websockets.connect(self.uri, extra_headers=headers)
async def stream_utterance(self, text_chunk: str, audio_queue: asyncio.Queue):
self.request_counter += 1
req_id = self.request_counter
payload = {
"voice_uuid": self.voice_uuid,
"project_uuid": self.project_uuid,
"data": text_chunk,
"binary_response": True,
"request_id": req_id,
"output_format": "wav",
"sample_rate": 16000,
"precision": "PCM_16",
"no_audio_header": True
}
await self.ws.send(json.dumps(payload))
while True:
message = await self.ws.recv()
if isinstance(message, bytes):
# 순수 PCM_16 바이너리를 수신하여 재생 큐에 삽입
await audio_queue.put(message)
else:
data = json.loads(message)
if data.get("type") == "audio_end" and data.get("request_id") == req_id:
break
elif data.get("type") == "error":
raise RuntimeError(f"Resemble API 에러: {data.get('message')}")
6. 사용자 끼어들기(Barge-in) 및 소켓 보존
실제 대화에서는 봇이 말하는 도중 사용자가 말을 끊고 들어오는 상황이 자주 발생합니다. VAD가 봇 발화 도중 사용자 음성을 감지하면 즉시 다음 절차를 수행해야 합니다.
- 재생 버퍼 즉시 폐기: 클라이언트 스피커 큐와 WebRTC 오디오 트랙 버퍼를 즉시 비워 50 ms 이내에 물리적으로 음성을 음소거합니다.
- 이전 프레임 폐기: 내부 턴 식별자(
active_turn_id)를 증가시키고, 과거request_id로 뒤늦게 도착하는 Resemble 오디오 프레임은 버립니다. - WebSocket 연결 유지: 끼어들기가 발생했다고 소켓을 닫으면 다음 봇 응답 시 재연결 지연이 발생합니다. 소켓은 그대로 열어둔 채 다음 턴의 요청을 전송해야 합니다.
7. 오픈소스 프레임워크 연동 (LiveKit / Pipecat)
상태 머신을 직접 구축하지 않고도 검증된 보이스 에이전트 프레임워크를 통해 Resemble AI를 손쉽게 통합할 수 있습니다.
LiveKit Agents (livekit-plugins-resemble)
LiveKit은 초저지연 WebRTC 미디어 전송과 전화망(SIP) 연결을 제공합니다. 공식 플러그인을 통해 간편하게 연결할 수 있습니다.
uv add "livekit-agents[resemble]~=1.8"
from livekit.agents import AgentSession
from livekit.plugins import resemble
session = AgentSession(
tts=resemble.TTS(
voice_uuid="YOUR_VOICE_UUID",
),
# VAD, STT, LLM 연동 설정
)
LiveKit은 지터 버퍼링, WebRTC 패킷화, 끼어들기 시 즉각적인 음성 취소 처리를 프레임워크 레벨에서 자동으로 처리합니다.
Pipecat (pipecat-ai[resemble])
파이프라인 프레임 구조 기반의 Pipecat은 ResembleAITTSService를 통해 비동기 스트리밍, 립싱크를 위한 단어 단위 타임스탬프 동기화, 사용자 끼어들기 취소를 기본 지원합니다.
8. 저지연 프로덕션 배포 체크리스트
Resemble AI 기반 보이스봇을 프로덕션 환경에 배포하기 전에 다음 항목을 점검하십시오.
- 프로토콜: 동기식 REST를 배제하고
wss://websocket.cluster.resemble.ai/stream지속 연결을 사용하는가. - 오디오 파라미터:
binary_response: true,precision: "PCM_16",sample_rate: 16000,no_audio_header: true설정이 적용되었는가. - 청킹 전략: 첫 번째 절(쉼표 또는 4~8단어)을 즉시 발송하고 후속 텍스트를 문장 단위로 전송하는 비대칭 구조인가.
- 텍스트 정제: 마크다운 기호 및 특수문자를 사전에 제거하여 오독 및 불필요한 침묵을 방지했는가.
- 소켓 재사용: 사용자 끼어들기(Barge-in) 발생 시에도 소켓을 닫지 않고 재사용하는가.
- 리전 근접 배치: 보이스봇 오케스트레이션 서버를 Resemble AI 클러스터 및 LLM 엔드포인트와 물리적으로 가까운 리전에 배치했는가.