広告枠を募集中 ご相談ください

会話型ボイスボットにおける Resemble AI ストリーミング音声合成のレイテンシ最適化ガイド

365Finds Editorial
Wiki 記事 11,430 本収録

あなたの AI で 365 Wiki を使う

下のプロンプトをコピーして、普段使っている AI に貼り付け、そのまま質問してください。

完全版 Skill をダウンロード

まずはプロンプトでお試しください。よく使う場合は完全版 365Wiki Skill をインストールすると、毎回貼り付ける必要がありません。普段使う AI がなければ、Gemini や Microsoft Copilot のウェブ版でも使えます。

コンタクトセンターの自動音声応答、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 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 は 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
}

パラメータチューニングの要点


4. LLM 出力と連携する非対称テキストチャンキング戦略

LLM のストリーミング出力を TTS に橋渡しする際、チャンク(テキスト分割)の粒度選定が最大の課題となります。

  1. 単語単位の送信(過小粒度): 2〜3 単語ごとに TTS へ送信すると、音調の抑揚(イントネーション)が崩れて不自然になり、WebSocket に微小リクエストが乱発されます。
  2. 完全な文単位の送信(過大粒度): 句点(。)まで待機すると、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 の遅延が加算されてしまいます。

設計上のベストプラクティス

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 がユーザーの発話を検知した場合、即座に以下のバージイン処理を実行します。

  1. 再生バッファの即時フラッシュ: クライアントのスピーカーキューや WebRTC の送信バッファを瞬時に破棄し、50 ms 以内に発声を停止します。
  2. 古い音声フレームの破棄: 内部のターン識別子(active_turn_id)を進め、Resemble から遅れて届く古い request_id のオーディオフレームをすべてドロップします。
  3. 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. 低遅延化のためのセルフチェックリスト

本番環境へデプロイする前に、以下の最適化項目を確認してください。


参考文献