대화형 음성 에이전트에 PlayHT의 스트리밍 API를 연동하면 개발자는 인지 대기 시간을 최소화하면서 실시간에 가까운 자연스러운 음성 응답을 제공할 수 있습니다. 고객 지원 봇, 인터랙티브 게임 캐릭터, 음성 비서와 같은 대화형 시스템에서 기존의 배치 방식 텍스트 음성 변환(TTS)은 완전한 문장이나 단락이 합성될 때까지 기다려야 하므로 허용하기 어려운 지연을 초래합니다. WebSocket 또는 청크 방식의 HTTP를 통해 영구 스트리밍 채널을 구축함으로써, 개발자는 대형 언어 모델(LLM)이 생성한 점진적 텍스트 토큰을 PlayHT의 신경망 합성 엔진에 직접 파이프라인으로 전달하여 즉각적인 오디오 재생을 실현할 수 있습니다. 본 가이드에서는 인증 구성, WebSocket 연결 관리, 토큰 청킹 전략 최적화, 대화 중 끼어들기(barge-in) 처리, 그리고 텔레포니 환경의 특수 오디오 요구 사항 충족 방법을 상세히 설명합니다.
실시간 대화형 음성 에이전트 아키텍처
프로덕션 수준의 대화형 음성 에이전트는 서로 분리된 4개의 하위 시스템으로 구성된 연속 전이중(full-duplex) 루프로 동작합니다.
- 음성 텍스트 변환(STT): 실시간 전사 엔진(예: Deepgram Nova-2 또는 Whisper Streaming)이 마이크나 전화선에서 사용자 음성을 캡처하고, 음향 프레임을 텍스트 토큰으로 변환하며, 화자가 말을 멈추는 시점을 감지합니다.
- 언어 추론(LLM): 대형 언어 모델(예: GPT-4o, Claude 3.5 Sonnet 또는 Groq 호스팅 오픈소스 모델)이 대화 기록과 최신 사용자 질의를 처리하여 서버 전송 이벤트(SSE)를 통해 순차적으로 응답 토큰을 출력합니다.
- 텍스트 음성 변환(TTS): 저지연 합성 엔진(예: PlayHT 스트리밍 파이프라인)이 들어오는 텍스트 토큰을 수신하고, 음성학적 구조를 파싱하며, LLM이 전체 문장 생성을 마치기 전에 원시 오디오 청크를 합성합니다.
- 전송 및 오디오 재생: 오디오 전송 계층(예: WebRTC, WebSocket 또는 Twilio Media Streams)이 합성된 오디오 패킷을 클라이언트 플레이어나 전화 핸드셋으로 스트리밍하여 즉시 재생합니다.
배치 TTS 아키텍처에서는 LLM이 먼저 완전한 문장(보통 800~2,000밀리초 소요)을 생성해야 하며, 이후 TTS 엔진이 재생을 시작하기 전에 오디오 파일을 렌더링하는 데 500~1,500밀리초가 추가로 필요합니다. 이 1.5~3.5초의 누적 지연은 자연스러운 대화의 몰입감을 해칩니다. 반면 스트리밍 파이프라인은 LLM 생성과 음성 합성을 중첩시킵니다. 음성 엔진은 뒤따르는 어구가 생성되는 동안 처음 몇 단어 합성을 시작하므로 종단 간 대기 시간을 400~800밀리초로 대폭 단축합니다.
PlayHT 스트리밍 인터페이스 및 인증
PlayHT는 초저지연 및 상태 유지 대화 세션을 위해 설계된 양방향 WebSocket API와 더 단순한 단방향 스트리밍을 위한 HTTP 청크 전송 엔드포인트라는 두 가지 주요 스트리밍 인터페이스를 제공했습니다.
1. 인증 및 WebSocket URL 획득
URL 쿼리 문자열로 자격 증명을 받는 공개 WebSocket 엔드포인트와 달리, PlayHT는 소켓을 열기 전에 보안 REST 핸드셰이크로 인증 과정을 분리했습니다. 이를 통해 전송 로그나 프록시 추적에 민감한 API 키가 노출되는 것을 방지했습니다.
인증을 위해 개발자는 인증 게이트웨이로 인증된 HTTP POST 요청을 전송했습니다.
POST https://api.play.ht/api/v4/websocket-auth
Authorization: Bearer <YOUR_API_KEY>
X-User-Id: <YOUR_USER_ID>
Content-Type: application/json
서버는 자격 증명을 검증하고 사용 가능한 합성 엔진에 매핑된 임시 서명된 WebSocket 엔드포인트가 포함된 JSON 페이로드를 반환했습니다.
{
"websocket_urls": {
"PlayDialog": "wss://api.play.ht/v1/websocket/playdialog?auth=<SIGNED_TOKEN>",
"Play3.0-mini": "wss://api.play.ht/v1/websocket/play3.0-mini?auth=<SIGNED_TOKEN>"
}
}
서명된 URL에는 수명이 짧은 암호화 토큰이 포함되어 있어, 클라이언트 애플리케이션이 마스터 자격 증명을 재전송하지 않고도 보안 WebSocket 연결을 수립할 수 있었습니다.
2. 지원되는 스트리밍 모델
PlayHT는 다양한 대화 워크로드에 최적화된 차별화된 모델을 유지했습니다.
- PlayDialog: 대화형 멀티턴 대화에 맞춤 설계되었습니다. 이전 문맥을 평가하여 호흡, 반응형 억양, 공감하는 음조 변화 및 대화 템포와 같은 자연스러운 대화 요소를 구현합니다.
- Play 3.0 Mini: 극한의 속도와 높은 처리량을 위해 구축되었습니다. 첫 번째 바이트 응답 시간(TTFB)이 약 190밀리초로, 지연 시간에 민감한 음성 봇, 아웃바운드 텔레포니 다이얼러 및 빠른 인터랙티브 에이전트의 표준 선택지로 활용되었습니다.
WebSocket 스트리밍 프로토콜 및 메시지 흐름
할당된 WebSocket URL에 연결되면 통신은 정형화된 JSON-over-WebSocket 프로토콜을 따릅니다. 연결 수명 주기는 세션 시작, 증분 텍스트 전송, 바이너리 오디오 수신 및 스트림 완료로 구성됩니다.
1. 페이로드 매개변수
음성 생성을 시작하기 위해 클라이언트는 합성 매개변수가 포함된 JSON 프레임을 전송합니다.
{
"text": "Hello! I can help you check your account balance today.",
"voice": "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/narrator/manifest.json",
"output_format": "mp3",
"sample_rate": 24000,
"speed": 1.0,
"temperature": 0.7
}
주요 구성 옵션은 다음과 같습니다.
text: 음성으로 합성할 텍스트 내용입니다. 스트리밍 시 이 필드는 개별 절 또는 문장 세그먼트로 제공될 수 있습니다.voice: 고유 음성 식별자로, PlayHT 라이브러리의 기본 제공 음성을 참조하거나 인스턴트/고충실도 복제 음성을 가리키는 S3 매니페스트 URI를 지정합니다.output_format: 대상 오디오 인코딩입니다.mp3,wav,mulaw(전화 통신용 8비트 CCITT mu-law),flac,ogg형식을 지원했습니다.sample_rate: 출력 오디오 샘플링 주파수로, 8,000Hz(전화 표준)부터 48,000Hz(스튜디오 음질)까지 지원합니다.speed: 재생 속도 조절 인자로, 일반적으로 0.8에서 1.5 사이로 설정됩니다.temperature: 운율적 표현력과 변화도를 제어합니다. 낮은 값(0.4~0.6)은 일관되고 중립적인 음성을 생성하며, 높은 값(0.7~0.9)은 역동적인 억양과 표현력 있는 리듬을 부여합니다.
2. 서버 메시지 수명 주기
PlayHT WebSocket 서버는 JSON 메타데이터와 바이너리 오디오 페이로드를 번갈아 전송하여 합성 진행 상황을 알렸습니다.
start이벤트: 유효한 합성 명령을 수신하면 서버는 JSON 메시지를 전송합니다.{"type": "start", "request_id": "req_88492048102"}- 바이너리 오디오 프레임: 시작 이벤트 직후 서버는 인코딩된 오디오 패킷(원시 PCM 슬라이스 또는 MP3 프레임 등)이 포함된 순차적 바이너리 프레임을 푸시합니다. 클라이언트 애플리케이션은 이 버퍼를 오디오 출력 드라이버나 WebRTC 미디어 트랙에 직접 전달합니다.
end이벤트: 텍스트 시퀀스의 합성이 완전히 끝나면 서버는 최종 JSON 프레임을 전송합니다.{"type": "end", "request_id": "req_88492048102"}
구현 예제
1. WebSocket을 사용한 Python 연동
다음 Python 스크립트는 인증을 수행하고, 스트리밍 URL을 가져오며, 합성된 오디오 청크를 로컬 플레이어나 오디오 싱크로 스트리밍하는 방법을 보여줍니다.
import asyncio
import json
import websockets
import httpx
USER_ID = "YOUR_USER_ID"
API_KEY = "YOUR_API_KEY"
VOICE_ID = "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/narrator/manifest.json"
async def get_websocket_url():
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.play.ht/api/v4/websocket-auth",
headers={
"Authorization": f"Bearer {API_KEY}",
"X-User-Id": USER_ID,
"Content-Type": "application/json"
}
)
response.raise_for_status()
data = response.json()
return data["websocket_urls"]["PlayDialog"]
async def stream_speech(text: str, audio_queue: asyncio.Queue):
ws_url = await get_websocket_url()
async with websockets.connect(ws_url) as ws:
payload = {
"text": text,
"voice": VOICE_ID,
"output_format": "mp3",
"sample_rate": 24000,
"temperature": 0.7
}
await ws.send(json.dumps(payload))
async for message in ws:
if isinstance(message, bytes):
# Put binary audio chunk into playback queue
await audio_queue.put(message)
else:
data = json.loads(message)
if data.get("type") == "end":
await audio_queue.put(None) # Signal playback completion
break
async def main():
audio_queue = asyncio.Queue()
await stream_speech("Connecting to your server now. How may I direct your call?", audio_queue)
if __name__ == "__main__":
asyncio.run(main())
2. 공식 SDK를 사용한 Node.js 연동
PlayHT는 WebSocket 핸드셰이크와 연결 재사용을 추상화한 공식 클라이언트 라이브러리(npm의 playht)도 제공했습니다.
import * as PlayHT from "playht";
import { Writable } from "node:stream";
PlayHT.init({
userId: process.env.PLAYHT_USER_ID,
apiKey: process.env.PLAYHT_API_KEY,
});
async function synthesizeTextStream(textPrompt, audioDestinationStream) {
const stream = await PlayHT.stream(textPrompt, {
voiceEngine: "PlayDialog",
voiceId: "s3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b0ef-dd630f59414e/narrator/manifest.json",
outputFormat: "mp3",
sampleRate: 24000,
});
stream.on("data", (chunk) => {
audioDestinationStream.write(chunk);
});
stream.on("end", () => {
audioDestinationStream.end();
});
stream.on("error", (err) => {
console.error("PlayHT streaming error:", err);
});
}
LLM 토큰 스트림과 오디오 합성의 연계
대화형 에이전트를 구축할 때의 핵심 과제는 LLM의 토큰 출력을 TTS 입력에 연결하는 것입니다. LLM은 단어 또는 서브워드 단위로 응답을 출력합니다. 토큰을 한 단어씩 TTS 엔진에 입력하면 발음, 억양 및 음성 연음이 손상됩니다. 반대로 LLM이 전체 단락 생성을 마칠 때까지 기다리면 심각한 대기 시간이 발생합니다.
개발자는 적응형 문장 버퍼링 및 청킹을 통해 이 문제를 해결합니다.
[LLM Token Stream]
│
▼
[Token Buffer & Boundary Detector]
- Chunk 1: First 4-6 words (Quick start) ──► PlayHT Stream ──► Instant Audio (TTFB < 500ms)
- Chunk 2: First complete clause ──► PlayHT Stream ──► Seamless Playback
- Chunk 3: Next full sentence ──► PlayHT Stream ──► Continuous Queue
문장 청킹 전략
- 초기 버스트 청크(3~6개 단어): 버퍼에 3~6개 단어가 모이거나 초기에 쉼표가 감지되면 즉시 첫 번째 청크를 전송합니다. 사용자는 첫 오디오 청취 시간(TTFA)을 기준으로 응답성을 체감하므로, 이 작은 초기 어구를 즉시 전달하면 인지 대기 시간을 600밀리초 미만으로 유지할 수 있습니다.
- 절 및 구두점 경계: 후속 청크는 자연스러운 구두점 경계(
.,?,!,;또는 공백이 뒤따르는,)를 만날 때까지 버퍼링됩니다. 이는 음향 모델이 적절한 음조 윤곽과 음절 길이를 예측하는 데 충분한 언어적 문맥을 제공합니다. - 최대 버퍼 안전 한계치: LLM이 구두점 없이 긴 문장을 생성하는 경우, 오디오 버퍼 고갈을 방지하기 위해 15~20단어 후 적응형 버퍼를 강제로 비웁니다.
// Example: Minimal regex-based token buffer for streaming TTS
class TextStreamBuffer {
constructor(onChunkReady) {
this.buffer = "";
this.isFirstChunk = true;
this.onChunkReady = onChunkReady;
this.punctuationRegex = /([.?!;:]\s+|,[\s]+)/;
}
addToken(token) {
this.buffer += token;
if (this.isFirstChunk) {
const words = this.buffer.trim().split(/\s+/);
if (words.length >= 4 || this.punctuationRegex.test(this.buffer)) {
this.flush();
this.isFirstChunk = false;
}
return;
}
const match = this.buffer.match(this.punctuationRegex);
if (match) {
const splitIndex = match.index + match[0].length;
const readyChunk = this.buffer.slice(0, splitIndex).trim();
this.buffer = this.buffer.slice(splitIndex);
if (readyChunk) {
this.onChunkReady(readyChunk);
}
}
}
flush() {
const content = this.buffer.trim();
if (content) {
this.onChunkReady(content);
this.buffer = "";
}
}
}
끼어들기(Barge-In) 및 대화 중단 관리
사람 간의 대화에서는 상대방이 말하는 도중에도 자주 끼어들거나, 맞장구를 치거나, 이해했음을 나타냅니다. 사용자가 말하고 있음에도 일방적으로 말을 이어가는 음성 에이전트는 부자연스럽고 불편함을 줍니다.
신속한 끼어들기를 구현하려면 음성 활동 감지(VAD)와 오디오 재생 파이프라인의 긴밀한 조정이 필요합니다.
- 지속적인 오디오 수음: 에이전트가 발화하는 동안에도 클라이언트 마이크는 열린 상태로 모니터링됩니다(전이중 동작).
- 음향 음향 에코 캔슬링(AEC): 클라이언트나 오디오 서버는 하드웨어 또는 소프트웨어 AEC를 적용하여 수신 마이크 신호에서 에이전트의 스피커 출력을 차감해야 합니다. AEC가 없으면 에이전트가 자신이 합성한 음성을 듣고 잘못된 자체 중단을 유발하게 됩니다.
- 중단 트리거: VAD가 에너지 및 신뢰도 임계값을 초과하는 인간의 음성을 감지하면(예: Silero VAD가 150밀리초 동안 음성 프레임을 감지):
- 재생 중지: 로컬 오디오 플레이어는 즉시 재생을 중지하고 대기 중인 모든 오디오 프레임을 폐기합니다.
- TTS 파이프라인 중단: 애플리케이션은 활성 WebSocket 합성 세션을 취소하거나 대기 중인 PlayHT 스트림을 닫아 불필요한 연산과 네트워크 전송을 중단합니다.
- LLM 생성 취소:
AbortController가 진행 중인 LLM 토큰 생성을 종료합니다. - 상태 전이: 상태 머신이 듣기 상태로 리셋되고 새로운 사용자 음성을 STT 엔진으로 전달합니다.
텔레포니 및 엔터프라이즈 오디오 고려 사항
실시간 음성 에이전트를 공중 교환 전화망(PSTN)—Twilio, Plivo, Vonage 등—에 통합하려면 특수한 오디오 형식 변환이 필요합니다.
| 매개변수 | 표준 웹 / 데스크톱 오디오 | 텔레포니 (Twilio / SIP) |
|---|---|---|
| 오디오 형식 | MP3 / WAV / OGG | G.711 mu-law (mulaw) / 선형 PCM |
| 샘플링 레이트 | 24,000 Hz – 48,000 Hz | 8,000 Hz |
| 비트 심도 | 16비트 부호 있음 | 8비트 비선형 |
| 패킷 크기 | 동적 / 가변 비트레이트 | 20ms 청크 (8 kHz에서 160바이트) |
| 전송 | WebRTC / WebSocket | 양방향 Media Stream WebSocket |
전화 통신을 위해 PlayHT를 구성할 때 개발자는 "output_format": "mulaw" 및 "sample_rate": 8000을 지정했습니다. 이를 통해 애플리케이션 서버에서 고샘플링 레이트 MP3 스트림을 전화 통신 형식으로 트랜스코딩하는 CPU 오버헤드를 제거하고, 합성된 오디오 패킷을 Twilio Media Streams로 직접 전달할 수 있었습니다.
최신 에이전트 오케스트레이션 프레임워크
WebSocket 수명 주기, 버퍼링, 끼어들기 관리를 위한 맞춤형 상태 머신을 처음부터 작성하는 대신, 음성 에이전트를 개발하는 엔지니어들은 오픈소스 대화 오케스트레이션 프레임워크를 널리 채택하고 있습니다.
- LiveKit Agents: WebRTC 전송, 네이티브 VAD, 상태 유지 룸 관리, 주요 LLM, STT 제공자 및 스트리밍 TTS 엔진을 위한 사전 구축된 연동을 제공하는 인프라 프레임워크입니다.
- Pipecat: 실시간 멀티모달 및 음성 에이전트에 특화된 Python 프레임워크입니다. Pipecat은 대화 파이프라인을 모듈식 노드(VAD -> STT -> Context -> LLM -> TTS -> Audio Output)로 구성하며 중단 로직과 프레임 직렬화기를 기본 내장하고 있습니다.
플랫폼 수명 주기 및 마이그레이션 경로
PlayHT는 대화형 AI를 위한 저지연 스트리밍 TTS의 대중화에 중추적인 역할을 했습니다. 2025년 7월 Meta가 PlayHT 팀을 인수한 이후, 해당 플랫폼은 상용 호스팅 서비스를 공식 종료했으며 모든 공개 API 엔드포인트는 2025년 12월 31일부로 영구 중단되었습니다.
기존에 PlayHT를 기반으로 구축된 실시간 대화 아키텍처를 유지 관리하거나 업그레이드하는 개발자는 호환 가능한 저지연 아키텍처를 제공하는 활성 스트리밍 TTS 제공업체로 마이그레이션해야 합니다.
- ElevenLabs: 양방향 청크 텍스트 입력과 PCM/mu-law 출력을 지원하는 저지연 WebSocket 스트리밍 엔드포인트(
/v1/text-to-speech/{voice_id}/stream-input)를 제공합니다. - Cartesia (Sonic): 실시간 대화형 에이전트 전용으로 설계된 네이티브 WebSocket 인터페이스를 바탕으로 초저지연 음성 생성(TTFA 150밀리초 미만)을 지원합니다.
- Deepgram (Aura): Deepgram의 STT 엔진과 동일한 플랫폼 및 네트워크 인프라에 통합된 고속 스트리밍 TTS를 제공하여 서비스 간 네트워크 홉을 단축합니다.
- OpenAI Realtime API: 전사, 추론 및 합성을 단일 멀티모달 모델로 결합한 직접 음성 대 음성 WebSocket/WebRTC 프로토콜을 사용하여 중간 STT 및 TTS 핸드오프를 완전히 제거합니다.
- Inworld AI: 실시간 인터랙티브 대화 및 고객 서비스 워크플로에 특화된 캐릭터 및 대화형 에이전트 오케스트레이션 엔진을 제공합니다.
WebSocket 세션 관리, 적응형 토큰 버퍼링, 끼어들기 동기화, 오디오 포맷팅과 같은 스트리밍 합성의 아키텍처 원리를 이해함으로써, 개발자는 다양한 합성 제공업체 간을 유연하게 전환할 수 있는 견고한 저지연 대화형 음성 에이전트를 설계할 수 있습니다.