실시간으로 대화 스트리밍하기

이 가이드에서는 Text to Dialogue WebSocket을 통해 Eleven v3 대화 오디오를 스트리밍하는 방법을 안내합니다.

Text to Dialogue WebSocket(/v1/text-to-dialogue/stream-input)은 대화 줄을 전송하고 base64로 인코딩된 오디오 청크를 수신하는 동안 단일 연결을 유지합니다. Eleven v3 및 Eleven v4 대화 모델 전용이며(model_id는 eleven_v3 또는 eleven_v4로 시작해야 함), 다른 모델에는 사용할 수 없습니다.

이 가이드는 Text to Dialogue WebSocket을 다룹니다. Flash, Multilingual v2 또는 기타 v3 이외의 TTS 모델은 실시간 TTS WebSocket을 사용하세요. 두 프로토콜을 나란히 비교한 요약은 Text to Speech와 Text to Dialogue WebSocket 비교를 참고하세요.

요구 사항

  • API 키가 있는 ElevenLabs 계정(인증)
  • API 키에 Text to Speech 권한 필요
  • 컴퓨터에 Python 또는 Node.js 설치

설정

pip install python-dotenv websockets

.env 파일을 만드세요.

.env
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here

보이스 라이브러리에서 음성 ID를 선택하세요. 아래 예시에서는 연결당 등록된 음성을 하나만 허용하는 eleven_v4_turbo를 사용합니다.

WebSocket 열기

model_id, output_format 등의 쿼리 파라미터와 함께 wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input에 연결하세요. API 키는 xi-api-key 헤더 또는 첫 번째 JSON 메시지로 전송할 수 있습니다(여기서는 언어 전반에서 동일한 패턴을 사용하기 위해 본문에 표시함).

import asyncio
import base64
import json
import os
from dotenv import load_dotenv
import websockets
load_dotenv()
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
VOICE_ID = "21m00Tcm4TlvDq8ikWAM"
MODEL_ID = "eleven_v4_turbo"
URI = (
"wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input"
f"?model_id={MODEL_ID}&output_format=mp3_44100_128"
)

음성 등록 및 텍스트 스트리밍

voices(필수)와 xi-api-key 헤더를 설정하지 않은 경우 xi_api_key를 포함한 첫 번째 메시지를 전송하세요. 그런 다음 inputs가 포함된 프레임을 하나 이상 전송합니다. 각 항목에는 text, voice_id, 선택 사항인 new_turn이 있습니다.

서버는 충분한 컨텍스트(약 40자 및 8단어)가 쌓일 때까지 텍스트를 버퍼링한 후 audio 청크를 전송합니다. 응답 필드는 snake_case를 사용합니다(예: is_final).

async def stream_dialogue():
async with websockets.connect(URI) as websocket:
await websocket.send(
json.dumps(
{
"voices": [VOICE_ID],
"xi_api_key": ELEVENLABS_API_KEY,
}
)
)
line = (
"This is a longer line of dialogue used to exceed the minimum buffer so the model "
"starts generating streamed audio for the registered voice. "
)
await websocket.send(
json.dumps(
{
"inputs": [
{"text": line, "voice_id": VOICE_ID, "new_turn": False},
],
}
)
)
await websocket.send(json.dumps({"close_socket": True}))
os.makedirs("output", exist_ok=True)
out_path = "output/dialogue-ws.mp3"
with open(out_path, "wb") as audio_file:
while True:
raw = await websocket.recv()
msg = json.loads(raw)
if msg.get("error"):
raise RuntimeError(msg)
if msg.get("audio"):
audio_file.write(base64.b64decode(msg["audio"]))
if msg.get("is_final"):
break
print(f"Wrote {out_path}")
asyncio.run(stream_dialogue())

close_socket은 버퍼링된 텍스트를 모두 처리하고 남은 오디오를 전송한 다음, 연결이 닫히기 전에 is_final: true가 포함된 최종 프레임을 전송합니다. 줄 사이에 연결을 유지하려면 세션이 끝날 때까지 close_socket을 생략하세요. 닫지 않고 짧은 버퍼의 오디오 생성을 강제하려면 flush를 사용하세요.

스크립트 실행

python text-to-dialogue-websocket.py

output/ 아래에 MP3 파일이 생성됩니다(파일명은 위 예시 참조).

동작 참고 사항

버퍼링

TTS WebSocket의 chunk_length_schedule과 달리, 대화 스트리밍은 첫 번째 부분 오디오를 생성하기 전에 고정된 서버 임계값(문자 수 및 단어 수)을 사용합니다. 짧은 줄을 전송할 때 지연이 발생한다면 inputs 프레임당 텍스트를 조금 더 묶어 보내거나, flush: true를 전송해 소켓을 닫지 않고 생성을 강제하세요.

턴과 음성

화자가 한 턴을 마칠 때 new_turn: true를 설정하면 운율이 깔끔하게 초기화됩니다. inputs 항목 간에 voice_id를 변경해도 새 턴이 시작됩니다. eleven_v4_turbo에서는 voices에 음성을 정확히 하나 등록해야 하며, eleven_v4는 등록된 음성을 최대 10개까지 지원합니다.

비활성 상태

서버가 20초 동안 클라이언트 메시지를 받지 못하면 연결이 종료됩니다. 오디오를 합성하지 않고 타이머를 초기화하려면 {"keep_alive": true}를 전송하세요.

동시성

열려 있는 각 연결은 열려 있는 동안 하나의 대화 세션을 유지하며, 플랜의 일반 동시성 한도와 분리된 전용 풀에서 할당됩니다. 연결을 통해 생성된 오디오는 일반 동시성에 포함되지 않습니다. Text to Dialogue 동시성을 참고하세요.

정렬

사용 가능한 경우 청크에서 alignment 객체(snake_case 타이밍 배열)를 수신하려면 쿼리 문자열에 sync_alignment=true를 추가하세요. API 레퍼런스를 참고하세요.

다음 단계