광고 자리 모집 중 · 제휴 문의

대화형 보이스봇을 위한 Resemble AI 스트리밍 음성 합성 레이턴시 최적화 가이드

365Finds Editorial
Wiki 문서 11,430편 수록

내 AI에서 365 Wiki 사용하기

아래 프롬프트를 복사해 평소 사용하는 AI에 붙여넣고 바로 질문하세요.

전체 Skill 다운로드

먼저 프롬프트로 체험해 보세요. 365 Wiki를 자주 쓴다면 전체 365Wiki Skill을 설치해 매번 붙여넣는 수고를 줄일 수 있습니다. 평소 쓰는 AI가 없다면 Gemini나 Microsoft Copilot 웹 버전도 사용할 수 있습니다.

고객센터 전화 상담, 인공지능 보이스 에이전트, 실시간 음성 비서와 같은 대화형 보이스봇 환경에서 응답 지연 시간(레이턴시)은 서비스의 완성도를 결정짓는 핵심 지표입니다. 일상적인 인간 대화에서 발화 교대(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 msWebSocket 기반 실시간 점진적 텍스트 전송
LLM 첫 토큰 생성(TTFT)150 ms – 350 ms저지연 추론 클러스터, 추측 디코딩, 시스템 프롬프트 경량화
텍스트 청킹 및 TTS TTFB200 ms – 350 msResemble AI WebSocket 스트리밍, 비대칭 청킹 적용
네트워크 전송 및 지터 버퍼30 ms – 80 msWebRTC 미디어 서버(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
}

핵심 파라미터 튜닝 가이드


4. LLM 연동을 위한 비대칭 텍스트 청킹 전략

LLM의 스트리밍 토큰 출력을 Resemble AI TTS로 파이프라이닝할 때 텍스트 분할 단위의 설정은 중요한 엔지니어링 과제입니다.

  1. 단어 단위 전송(너무 작은 단위): 2~3단어 단위로 TTS에 전달하면 억양과 문맥이 끊겨 기계음처럼 들리고, WebSocket에 과도한 요청이 발생합니다.
  2. 문장 전체 대기(너무 큰 단위): 문장이 마침표로 끝날 때까지 기다리면 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의 네트워크 왕복 지연이 추가되어 스트리밍의 장점이 사라집니다.

아키텍처 베스트 프랙티스

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가 봇 발화 도중 사용자 음성을 감지하면 즉시 다음 절차를 수행해야 합니다.

  1. 재생 버퍼 즉시 폐기: 클라이언트 스피커 큐와 WebRTC 오디오 트랙 버퍼를 즉시 비워 50 ms 이내에 물리적으로 음성을 음소거합니다.
  2. 이전 프레임 폐기: 내부 턴 식별자(active_turn_id)를 증가시키고, 과거 request_id로 뒤늦게 도착하는 Resemble 오디오 프레임은 버립니다.
  3. 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 기반 보이스봇을 프로덕션 환경에 배포하기 전에 다음 항목을 점검하십시오.


참고 자료