Pipecatはリアルタイム音声エージェント向けのオープンソースPythonフレームワークです。Cartesiaは公式の音声サービスとして、ストリーミング音声合成のSonicと音声認識のInkを提供しています。このガイドでは、両方を組み込んだ音声エージェントをブラウザーで動かし、Inkの発話終了予測で応答ごとに約0.5秒を短縮します。
構築に必要なのはリポジトリのクローンと2つの環境変数です。完成したエージェントは話を聞き、発話の終了を判断し、考え、声で答えます。
全体像
Pipecatの音声エージェントは、処理を順番につなぐパイプラインです。transportから音声を受け取り、文字起こししてLLMに渡し、音声に戻して出力します。
transport.input() → STT → LLM → TTS → transport.output()
会話が人間らしく聞こえるか、トランシーバーのように聞こえるかは、ユーザーが本当に話し終わったタイミングを捉えることと、返答を素早く話し始めることに左右されます。Cartesiaのサービスはこの2点を担います。CartesiaTurnsSTTServiceはストリーミングSTTモデルのInk 2を使い、ローカルで推測する代わりにサーバー側で発話の終了を判断します。CartesiaTTSServiceはSonicの生成音声を順次配信し、必要に応じて単語のタイムスタンプも返します。
Pipecatのドキュメントでは、一般的なパイプラインの往復時間は500〜800ミリ秒です。以下では、その時間を無音の待ち時間ではなくモデルの処理に使う方法を説明します。
前提条件
- Python 3.11以降。現在の要件はPipecatリポジトリのREADMEで確認できます。
- Cartesia APIキー。ダッシュボードで作成してください。
- 応答を考えるLLMのAPIキー。この例ではOpenAIの
OPENAI_API_KEYを使います。PipecatはAnthropic、Gemini、Groq、Ollamaなどにも対応しています。
対応言語には制限があります。現時点でInk 2は英語専用のため、このエージェントは英語を聞き取ります。Sonicは40以上の言語を話せるため、返答側の制約ではありません。
1. サンプルが読み込むextraを指定してインストールする
Pipecatリポジトリには、Cartesia用のサンプルexamples/voice/voice-cartesia-turns.pyがあります。
git clone https://github.com/pipecat-ai/pipecat.git
cd pipecat
uv sync --extra cartesia --extra daily --extra websocket --extra runner --extra webrtc
uv syncだけではフレームワークしかインストールされず、このサンプルに必要なextraが不足します。ファイルの先頭でDaily、FastAPI WebSocketのtransport、Pipecatのrunnerをインポートするため、-t webrtcで実行する場合もこれらが必要です。不足するとNo module named 'daily'で失敗します。実際に使用するのはcartesiaとwebrtcです。
Pipecatリポジトリ内ではなく、自分のプロジェクトを作る場合も同じextraをインストールします。
pip install "pipecat-ai[cartesia,daily,websocket,runner,webrtc]"
続いて、リポジトリのexamples/voice/voice-cartesia-turns.pyをプロジェクトにコピーします。このサンプルにはCARTESIA_API_KEYとOPENAI_API_KEYが必要です。スクリプトと同じディレクトリの.envファイルに設定してください。
CARTESIA_API_KEY=your-key-here
OPENAI_API_KEY=your-key-here
2. エージェントを実行する
uv run examples/voice/voice-cartesia-turns.py -t webrtc
runnerがローカルサーバーを起動し、URLを表示します。デフォルトはhttp://localhost:7860です。開いてマイクへのアクセスを許可し、話しかけてください。内部では次の処理が動きます。
- ブラウザーがWebRTC経由で音声を送信します。
CartesiaTurnsSTTServiceがInk 2で文字起こしします。サーバーは発話の区切りを監視し、turn.start、turn.update、turn.eager_end、turn.endイベントを送信します。- LLMが返答を書きます。
CartesiaTTSServiceがSonicで返答音声を順次配信し、transportが届いた音声を再生します。
注目するのは2番目の処理です。従来の構成では音声区間検出器を追加し、文が終わったかを推測していました。Ink 2はサーバー側でターンを検出するため、後述の処理が可能になります。
3. 声とLLMを選ぶ
サンプルには音声IDとシンプルなシステムプロンプトが設定されています。どちらも変更できます。
tts = CartesiaTTSService(
api_key=os.environ["CARTESIA_API_KEY"],
settings=CartesiaTTSService.Settings(
voice="86e30c1d-714b-4074-a1f2-1cb6b552fb49",
),
)
Playgroundで声を選び、IDを貼り付けてください。音声クローンで短いサンプルから新しい声を生成することもできます。モデルがテキストを読み上げるため、システムプロンプトも話し言葉に合うようにします。上流のサンプルでは、絵文字、箇条書き、音声では伝わらない書式を避けるようLLMに指示しています。
特定のSonicモデルを使う場合は、同じsettingsオブジェクトに指定します。現在のデフォルトはsonic-3.6です。
4. 発話終了予測で約0.5秒短縮する
文を話し終える際、Inkは発話の正式な終了より先に終わりを予測し、turn.eager_endを送信します。Pipecatではon_turn_eager_end(service, transcript)イベントとして受け取れます。すぐに返答の生成を始め、ユーザーが話し続けた場合は聞き取りを再開できます。
stt = CartesiaTurnsSTTService(
api_key=os.environ["CARTESIA_API_KEY"],
enable_eager_end_of_turn=True,
)
@stt.event_handler("on_turn_eager_end")
async def on_turn_eager_end(service, transcript):
# Ink predicted the user's turn is ending. Start the LLM now.
...
enable_eager_end_of_turn=Trueが重要です。発話終了の確定を待たず、サーバーの早期予測に反応するようPipecatに指示します。Pipecatのspeculative user aggregatorには、予測時点でLLMを起動しながら、ユーザーが話し続ける可能性に備えて聞き取りを続ける実装例があります。Cartesiaのドキュメントでは約0.5秒の短縮とされています。返答するエージェントと、順番を待つエージェントの違いになる時間です。
考えている途中で何度も割り込まれる場合は、早期予測を無効にする前にしきい値を調整してください。4つの設定とCartesiaのデフォルト値は、turn_start_threshold(0.8)、turn_eager_end_threshold(0.4)、turn_end_threshold(0.2)、turn_end_timeout_ms(5600)です。辛抱強く待つエージェントにするには、発話が終わったと判断するまで長く待たせます。割り込む前の判定を厳しくするため、eager-endのしきい値を0.3に近づけ、タイムアウトを8000に近づけます。
製品名や社内用語など、実際に話される名前はSettings(keyterm=["Pipecat", "Ink 2"])に渡すと、正しい文字起こしを促せます。
次のステップ
- 電話番号に接続する場合は、同じサンプルを
-t twilioと公開プロキシで実行し直します。パイプラインを変えずに電話で動かせます。 - パイプラインを自分で運用したくない場合は、電話、ツール、ナレッジを管理するManaged Agentsで同じエージェントを構築できます。
- ライブ会話ではなく音声ファイルが必要な場合は、同じパッケージの
CartesiaHttpTTSServiceでバッチ合成できます。
関連ドキュメント
- CartesiaのPipecat連携ドキュメント
- PipecatのCartesia STTサービスとTTSサービス
- このガイドで実行するvoice-cartesia-turnsサンプル
- 発話終了予測の一連の処理を実装したspeculative user aggregator
- 設計判断の背景にある時間配分については、音声エージェントをリアルタイムに感じさせる要素を参照してください。