コンタクトセンターの自動音声応答、AI電話窓口、リアルタイム音声アシスタントなどの対話型ボイスボットにおいて、応答遅延(レイテンシ)は会話の自然さを左右する最も重要な要素です。人間同士の自然な会話では、発話の交代(ターンテーキング)にかかる時間は通常 200 ms 〜 500 ms 程度です。エンドツーエンドの応答遅延が 800 ms を超えると、会話にぎこちない沈黙が生じ、ユーザーとボットの発話が衝突する「かぶり(Talk-over)」が発生しやすくなります。
音声ボットのパイプラインでは、発話区間検出(VAD)、音声認識(STT)、大規模言語モデル(LLM)推論、音声合成(TTS)、メディア配信という複数のフェーズで遅延が累積します。特に TTS は LLM が最初のトークンを出力した直後のクリティカルパスに位置するため、TTS の初回オーディオ生成遅延(Time-to-First-Byte, TTFB)を極小化することが、サブセコンド(1秒未満)の応答性を達成する鍵となります。
Resemble AI は、リアルタイム対話向けの WebSocket ストリーミング API(wss://websocket.cluster.resemble.ai/stream)を提供しています。常時接続ソケットの再利用、バイナリ PCM 転送、非対称テキストチャンキング、バージイン(割り込み)制御を組み合わせることで、音声合成の再生開始遅延を 200 ms 〜 350 ms に抑えることが可能です。
1. 音声ボットにおけるレイテンシバジェットの分解
最適化すべきボトルネックを特定するため、音声ボットの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 は 3 つの音声合成方式を提供していますが、会話型ボイスボットには WebSocket 方式が最適です。
同期 REST API(POST /api/v2/projects/{project_uuid}/clips)
音声ファイル全体をレンダリングした後にレスポンスを返します。バッチ処理や静的なアナウンス音声には適していますが、リアルタイム対話には遅延が大きすぎます。
HTTP チャンクストリーミング(POST https://f.cluster.resemble.ai/stream)
HTTP Chunked 転送により WAV データを順次受信します。長文の読み上げには適していますが、対話のターンごとに毎回新しい HTTP 接続を開くため、TCP ハンドシェイクと TLS ネゴシエーションのオーバーヘッド(1ターンあたり 80 ms 〜 150 ms)が毎回発生します。
WebSocket リアルタイムストリーミング(wss://websocket.cluster.resemble.ai/stream)
持続的な双方向 WebSocket コネクションを維持します。通話中にコネクションを常時オープンにしておくことで接続確立のオーバーヘッドをゼロに抑え、ミリ秒単位で生成される音声フレームと単語レベルのタイムスタンプを順次受信できます。
プランと同時実行数の要件: Resemble AI の WebSocket ストリーミング API は 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)では、JSON 形式で Base64 エンコードされた音声文字列が返されます。Base64 はデータサイズを約 33% 肥大化させ、送受信双方で CPU によるエンコード・デコード処理が発生します。binary_response: trueを指定して生のバイナリオーディオを受信することで、帯域と処理負荷を最小化できます。 -
precision: "PCM_16"
Resemble は一部のエンドポイントでPCM_32をデフォルトとしていますが、音声通話用途では 16 ビットリニア PCM(PCM_16)で十分な音質が得られます。データ量は 32 ビット比で半減し、WebRTC や電話メディアゲートウェイの内部形式と一致するため、ビット深度変換のオーバーヘッドが解消されます。 -
sample_rate: 16000(または24000)
音楽スタジオ向けには 44.1 kHz や 48 kHz が使われますが、電話回線(Twilio、SIP、G.711、Opus)や WebRTC ボイスボットでは 16 kHz(広帯域通話品質)で極めて明瞭な人間の声が伝達されます。48 kHz に比べてデータ転送量が 3 分の 1 に削減され、パケット遅延を抑制できます。 -
no_audio_header: true
バイナリ応答モード時、このパラメータを有効にすると先頭フレームの 44 バイト RIFF/WAV ヘッダーが除外され、純粋な PCM 生データのみが連続して届きます。WebRTC AudioTrack や RTP パケッタイザへヘッダー解析なしで直接流し込めます。 -
apply_custom_pronunciations: false
業界専門用語などのカスタム発音辞書マッチングが必須でない限り、合成ごとの辞書検索オーバーヘッドを避けるためfalseに設定します。
4. LLM 出力と連携する非対称テキストチャンキング戦略
LLM のストリーミング出力を TTS に橋渡しする際、チャンク(テキスト分割)の粒度選定が最大の課題となります。
- 単語単位の送信(過小粒度): 2〜3 単語ごとに TTS へ送信すると、音調の抑揚(イントネーション)が崩れて不自然になり、WebSocket に微小リクエストが乱発されます。
- 完全な文単位の送信(過大粒度): 句点(。)まで待機すると、LLM の生成待ちで 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 秒かかるため、ユーザーが最初の声を聞いている間に後続テキストの生成と合成を完了させることができます。
第 2 チャンク以降:文全体の構文単位で合成
第 1 チャンクがクライアント側で再生されている間に、後続トークンを完全な文末記号(。、!、?、改行)まで蓄積して送信します。これにより、Resemble Ultra の自然な抑揚、息遣い、感情表現を損なうことなく再生をシームレスに維持できます。
テキストの正規化と Markdown 除去
LLM はしばしば太字(**)、箇条書き(- )、バッククォートなどの Markdown 記号を出力します。これらをそのまま 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 の遅延が加算されてしまいます。
設計上のベストプラクティス
- セッション単位での接続維持: 通話やセッションが終了するまで WebSocket を切断せずに維持します。
- ウォーム接続プール: 高負荷なサーバーでは
wss://websocket.cluster.resemble.ai/streamへの接続を事前に確立(プール)しておき、着信時に空きソケットを即座に割り当てます。 request_idによる状態管理: 同一接続上で発話を連続処理する際、インクリメントされるrequest_idで合成ジョブを識別します。対応する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 Error: {data.get('message')}")
6. バージイン(割り込み)制御とソケット維持
実際の会話では、ボットが話している最中にユーザーが発話を被せることが日常的に発生します。再生中に VAD がユーザーの発話を検知した場合、即座に以下のバージイン処理を実行します。
- 再生バッファの即時フラッシュ: クライアントのスピーカーキューや WebRTC の送信バッファを瞬時に破棄し、50 ms 以内に発声を停止します。
- 古い音声フレームの破棄: 内部のターン識別子(
active_turn_id)を進め、Resemble から遅れて届く古いrequest_idのオーディオフレームをすべてドロップします。 - WebSocket を切断しない: 割り込みが発生したからといって WebSocket を切断してはいけません。再接続による遅延ペナルティを回避するため、ソケットは維持したまま次のターンの合成リクエストを投入します。
7. 実装フレームワークとの統合(LiveKit / Pipecat)
基盤部分を自作する代わりに、主要なリアルタイム音声エージェントフレームワークを活用することで安定したシステムを構築できます。
LiveKit Agents(livekit-plugins-resemble)
WebRTC ベースで極めて低遅延な通話基盤を提供する LiveKit は、公式プラグインで Resemble AI をサポートしています。
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 の設定
)
ジッターバッファ、WebRTC パケット化、割り込み時の自動音声破棄が標準で実装されています。
Pipecat(pipecat-ai[resemble])
Pipecat のフレームパイプライン構造では、ResembleAITTSService が非同期ストリーミング、リップシンク用の単語タイムスタンプ、割り込みキャンセル処理を標準提供しています。
8. 低遅延化のためのセルフチェックリスト
本番環境へデプロイする前に、以下の最適化項目を確認してください。
- プロトコル: 同期 REST を排除し、
wss://websocket.cluster.resemble.ai/streamを採用しているか。 - 音声パラメータ:
binary_response: true、precision: "PCM_16"、sample_rate: 16000、no_audio_header: trueが指定されているか。 - チャンキング設計: 最初の節(読点または 4〜8 単語)を先行送信し、後続文を文末記号単位で送信する非対称構成になっているか。
- テキストクレンジング: Markdown や特殊記号を事前にサニタイズしているか。
- ソケット再利用: ユーザーの割り込み(バージイン)時にも WebSocket を切断せず、使い回しているか。
- リージョン近接配置: ボイスボットの実行サーバーと Resemble AI クラスタおよび LLM エンドポイントを地理的に近いリージョンに配置しているか。