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

開発者が PlayHT のストリーミング API を統合して対話型エージェントのリアルタイム音声応答を生成する方法

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

あなたの AI で 365 Wiki を使う

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

完全版 Skill をダウンロード

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

PlayHT のストリーミング API を対話型音声エージェントに統合することで、開発者は知覚レイテンシを極限まで抑えたリアルタイムで自然な音声応答を提供できます。自動カスタマーサポートエージェント、対話型ゲームキャラクター、音声アシスタントなどの対話システムにおいて、従来のバッチ型テキスト読み上げ(TTS)は完全な文や段落の合成を待つ間に許容できない一時停止を発生させます。WebSocket やチャンク形式の HTTP 経由で持続的なストリーミングチャネルを確立することにより、開発者は大規模言語モデル(LLM)が生成したテキストトークンを PlayHT のニューラル合成エンジンに直接パイプ処理し、即座の音声再生を実現できます。本ガイドでは、認証の設定、WebSocket 接続の管理、トークンチャンキング戦略の最適化、対話時のバージイン(割り込み)処理、および電話通信特有の音声要件への対応方法について詳述します。

リアルタイム対話型音声エージェントのアーキテクチャ

本番グレードの対話型音声エージェントは、4 つの疎結合なサブシステムで構成される継続的な全二重ループとして動作します。

  1. 音声認識(STT): リアルタイム文字起こしエンジン(Deepgram Nova-2 や Whisper Streaming など)がマイクや電話回線からユーザー音声をキャプチャし、音響フレームをテキストトークンに変換して話者の一時停止を検出します。
  2. 言語推論(LLM): 大規模言語モデル(GPT-4o、Claude 3.5 Sonnet、または Groq ホストのオープンソースモデルなど)が対話履歴と最新のユーザー入力を処理し、Server-Sent Events(SSE)経由で応答トークンを順次出力します。
  3. 音声合成(TTS): 低レイテンシ合成エンジン(PlayHT のストリーミングパイプラインなど)が受信したテキストトークンを受け取り、音韻構造を解析して、LLM が文全体の生成を完了する前に未加工の音声チャンクを合成します。
  4. トランスポートと音声再生: 音声トランスポート層(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 チャンク転送エンドポイントという、2 つの主要なストリーミングインターフェースを提供していました。

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 は、さまざまな対話ワークロードに合わせて最適化されたモデルを提供していました。

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
}

主な設定オプションは以下の通りです。

2. サーバーメッセージのライフサイクル

PlayHT WebSocket サーバーは、JSON メタデータとバイナリ音声ペイロードを交互に送信して合成の進行状況を通知しました。

  1. start イベント: 有効な合成コマンドを受信すると、サーバーは JSON メッセージを発行します。
    {"type": "start", "request_id": "req_88492048102"}
    
  2. バイナリ音声フレーム: start イベントの直後、サーバーはエンコードされた音声パケット(生の PCM スライスや MP3 フレームなど)を含むバイナリフレームを順次プッシュします。クライアントアプリケーションはこれらのバッファを音声出力ドライバまたは WebRTC メディアトラックに直接供給します。
  3. 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 は単語単位またはサブワード単位で応答を出力します。トークンを 1 単語ずつ 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

センテンスチャンキング戦略

  1. 初期バーストチャンク(3〜6 単語): バッファに 3〜6 単語が溜まるか、早い段階でカンマが現れた時点で最初のチャンクが送信されます。人間の聞き手は最初の音声が出るまでの時間(TTFA: Time-to-First-Audio)で応答性を判断するため、この小さな初期フレーズを即座に届けることで、知覚遅延を 600 ミリ秒未満に維持できます。
  2. 節と句読点の境界: それ以降のチャンクは、自然な句読点の境界(.、?、!、;、または空白が続く ,)に達するまでバッファリングされます。これにより、音響モデルが適切なピッチ輪郭と音節長を予測するのに十分な言語的コンテキストが確保されます。
  3. 最大バッファのフォールバック: 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 = "";
    }
  }
}

バージイン(割り込み)処理とインターラプション管理

人間の会話では、相手がまだ話している最中に割り込んだり、口を挟んだり、理解した旨の相槌を打ったりすることが頻繁にあります。ユーザーが話している間も一方的に話し続ける音声エージェントは、不自然でストレスを与えます。

レスポンシブなバージインを実装するには、音声活動検出(VAD)と音声再生パイプラインの連携が必要です。

  1. 継続的な音声入力: エージェントが話している間もクライアントのマイクは開いたままで監視されます(全二重動作)。
  2. 音響エコーキャンセレーション(AEC): クライアントまたは音声サーバーは、受信マイク信号からエージェント自身のスピーカー出力を差し引くハードウェアまたはソフトウェア AEC を採用する必要があります。AEC がないと、エージェントは自身が合成した音声を拾って誤った自己割り込みを引き起こします。
  3. 割り込みトリガー: VAD がエネルギーと信頼度のしきい値を超える人間の音声を検出した場合(例: Silero VAD が 150 ミリ秒間音声フレームを検出):
    • 再生停止: ローカルの音声プレーヤーは直ちに再生を停止し、キューに入っているすべての音声フレームを破棄します。
    • TTS パイプラインの中止: アプリケーションはアクティブな WebSocket 合成セッションをキャンセルするか、保留中の PlayHT ストリームを閉じて、不要な計算とネットワーク転送を停止します。
    • LLM 生成のキャンセル: AbortController によって処理中の LLM トークン生成を終了します。
    • 状態遷移: ステートマシンがリスニング状態にリセットされ、新しいユーザー音声が STT エンジンに渡されます。

電話通信とエンタープライズ音声の考慮事項

リアルタイム音声エージェントを公衆交換電話網(PSTN)—Twilio、Plivo、Vonage など—に統合するには、特定の音声フォーマットの適合作業が必要です。

パラメータ標準 Web / デスクトップ音声電話通信 (Twilio / SIP)
音声フォーマットMP3 / WAV / OGGG.711 mu-law (mulaw) / リニア PCM
サンプリングレート24,000 Hz – 48,000 Hz8,000 Hz
ビット深度16 ビット符号付き8 ビット非線形
パケットサイズ動的 / 可変ビットレート20ms チャンク(8 kHz で 160 バイト)
トランスポートWebRTC / WebSocket双方向メディアストリーム WebSocket

電話向けに PlayHT を設定する際、開発者は "output_format": "mulaw" および "sample_rate": 8000 を指定しました。これにより、アプリケーションサーバー上で高サンプリングレートの MP3 ストリームを電話形式にトランスコードする CPU 負荷が排除され、合成された音声パケットを Twilio Media Streams に直接転送できました。

最新のエージェントオーケストレーションフレームワーク

WebSocket のライフサイクル、バッファリング、バージイン管理のためのカスタムステートマシンをゼロから構築する代わりに、音声エージェントを開発するエンジニアはオープンソースの対話オーケストレーションフレームワークを広く採用しています。

プラットフォームのライフサイクルと移行パス

PlayHT は、対話型 AI 向け低レイテンシストリーミング TTS の普及において先駆的な役割を果たしました。2025 年 7 月の Meta による PlayHT チームの買収に伴い、同プラットフォームは商用ホステッドサービスを正式に終了し、すべてのパブリック API エンドポイントは 2025 年 12 月 31 日をもって恒久的にオフラインとなりました。

以前 PlayHT 上に構築されたリアルタイム対話アーキテクチャを保守またはアップグレードする開発者は、互換性のある低レイテンシアーキテクチャを提供する現行のストリーミング TTS プロバイダーに移行する必要があります。

ストリーミング合成のアーキテクチャ原則(WebSocket セッション管理、適応型トークンバッファリング、バージインの同期、音声フォーマットの最適化)を理解することで、開発者は合成プロバイダー間をスムーズに移行できる、堅牢で低レイテンシな対話型音声エージェントを設計できます。