pipecatでSTT・LLM・TTSを組み合わせて音声AIボットを動かしてみる

pipecatでSTT・LLM・TTSを組み合わせて音声AIボットを動かしてみる

2026.05.29

pipecat とは

pipecat は、リアルタイムの音声・映像AIエージェントを「パイプライン」として組み立てる Python フレームワークです。Daily 社が中心となって開発している OSS で、STT・LLM・TTS や各種トランスポート(WebRTC、電話、ローカル音声など)を部品として差し替えながらつなげます。

音声対話を実現するには、おおまかに次のプロセスが必要です。

音声入力 → テキスト文字起こし(STT) → 応答生成(LLM) → 音声合成(TTS) → 音声出力

近年は Gemini Live API や OpenAI Realtime API のように、この一連をまるごと引き受ける一体型のリアルタイム音声APIもあります。レイテンシーの面では一体型に軍配が上がりますが、その代わり途中のプロセスには手を入れにくくなります。pipecat のようにパイプラインとして組むと、各プロセスを好きなサービスに割り当てられます。文字起こしは Deepgram、応答生成は Gemini、音声合成は ElevenLabs、というふうにユースケースに合わせて部品を選べるわけです。今回はこの3つを組み合わせて利用してみます。

環境

今回利用する使うサービス指定と(google / deepgram / elevenlabs)と、ローカルのマイク・スピーカー用に local、発話区切りを検出する VAD 用に silero を付けます。local は内部で portaudio を使うため、macOS では先に入れておきます。

python3 -m venv .venv
source .venv/bin/activate

brew install portaudio
pip install "pipecat-ai[google,deepgram,elevenlabs,silero,local]"

キーは環境変数で渡します。実行するシェルで export しておきます。

export GEMINI_API_KEY=your-gemini-api-key
export DEEPGRAM_API_KEY=your-deepgram-api-key
export ELEVENLABS_API_KEY=your-elevenlabs-api-key

マイクに話しかけて動かす

組み立てるパイプラインは「マイク → STT → LLM → TTS → スピーカー」の一列です。STT に Deepgram、LLM に Gemini、TTS に ElevenLabs を割り当てます。一体型の API と違って文字起こしと音声合成が分かれているので、どこまでが一回の発言かを判定する VAD(発話区切りの検出)は自前で用意します。ここではローカルで動く Silero を使います。

# voice_bot.py
import asyncio
import os

from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.frames.frames import LLMRunFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineTask
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.llm_response_universal import (
    LLMContextAggregatorPair,
    LLMUserAggregatorParams,
)
from pipecat.services.deepgram.stt import DeepgramSTTService
from pipecat.services.elevenlabs.tts import ElevenLabsTTSService
from pipecat.services.google.llm import GoogleLLMService
from pipecat.transcriptions.language import Language
from pipecat.transports.local.audio import LocalAudioTransport, LocalAudioTransportParams
from pipecat.turns.user_mute import AlwaysUserMuteStrategy

async def main():

    transport = LocalAudioTransport(
        LocalAudioTransportParams(
            audio_in_enabled=True,
            audio_out_enabled=True,
            vad_analyzer=SileroVADAnalyzer(),
        )
    )

    # 音声 → テキスト
    stt = DeepgramSTTService(
        api_key=os.environ["DEEPGRAM_API_KEY"],
        settings=DeepgramSTTService.Settings(model="nova-2-general", language=Language.JA),
    )

    # テキスト → 応答テキスト
    llm = GoogleLLMService(
        api_key=os.environ["GEMINI_API_KEY"],
        settings=GoogleLLMService.Settings(
            model="gemini-3.5-flash",
            system_instruction="あなたは親しみやすい日本語の音声アシスタントです。短く話してください。",
        ),
    )

    # 応答テキスト → 音声
    tts = ElevenLabsTTSService(
        api_key=os.environ["ELEVENLABS_API_KEY"],
        settings=ElevenLabsTTSService.Settings(
            voice="EXAVITQu4vr4xnSDxMaL",  # ElevenLabs の voice IDを指定することで、好きな音声に切り替えられます
            model="eleven_flash_v2_5",
            language=Language.JA,
        ),
    )

    context = LLMContext()
    aggregator = LLMContextAggregatorPair(
        context,
        user_params=LLMUserAggregatorParams(
            user_mute_strategies=[AlwaysUserMuteStrategy()] # Bot発言中はマイク入力を無視する
        ),
    )

    pipeline = Pipeline(
        [
            transport.input(),       # マイクからの音声
            stt,                     # 音声 → テキスト
            aggregator.user(),
            llm,                     # テキスト → 応答テキスト
            tts,                     # 応答テキスト → 音声
            transport.output(),      # スピーカーへの音声
            aggregator.assistant(),
        ]
    )

    task = PipelineTask(pipeline)

    context.add_message(
        {"role": "user", "content": "日本語で簡単に自己紹介して、挨拶してください。"}
    )
    await task.queue_frames([LLMRunFrame()])

    await PipelineRunner().run(task)

if __name__ == "__main__":
    asyncio.run(main())

並びを見ると、transport.input() がマイク、transport.output() がスピーカーで、その間を sttllmtts の順に音声とテキストが通り抜けていきます。マイクから入った音声を Deepgram がテキストに起こし、そのテキストから Gemini が返事を作り、ElevenLabs がそれを音声にする。役割がサービスごとに分かれているのがそのまま並びに出ています。

シンプルに直感的にパイプラインの記述できるのがわかりやすいです。

pipeline = Pipeline(
    [
        transport.input(),       # マイクからの音声
        stt,                     # 音声 → テキスト
        aggregator.user(),
        llm,                     # テキスト → 応答テキスト
        tts,                     # 応答テキスト → 音声
        transport.output(),      # スピーカーへの音声
        aggregator.assistant(),
    ]
)

簡単に差し替えが行えるのが pipecat の良さだと思います。文字起こしの精度を上げたければ STT を別社に、声を変えたければ TTS の voice を変える、というように部品ごとに交換できます。一体型の Live API では中をいじれない部分が、ここでは設定ひとつで効きます。また、voice は ElevenLabs の声のIDで、好きな声に差し替えられます。

実行します。

python voice_bot.py

起動するとボットが自己紹介をして、あとはマイクに向かって話すと、聞き取った内容に声で返してくれます。

一つ注意として、PC のスピーカーから音を出すと、その声を自分のマイクが拾ってしまい、ボットが自分の返事に反応してオウム返しのようになることがあります。今回のようにマイクとスピーカーを直接つなぐ構成(LocalAudioTransport)にはエコーキャンセルが入っていないためで、イヤホンやヘッドホンを使えば回り込みがなくなって素直に会話できます。

まとめ

  • pipecat を使うと、音声AIボットを「マイク → STT → LLM → TTS → スピーカー」の一列のパイプラインとして組める
  • STT・LLM・TTS をサービス単位で差し替えられるのが、一体型の Live API にはない強み

非常にシンプルなコードで簡単に音声パイプラインを構築できました。柔軟にパイプラインのカスタマイズも可能なので、ぜひお試しください。

参考


生成AI活用はクラスメソッドにお任せ

過去に支援してきた生成AIの支援実績100+を元にホワイトペーパーを作成しました。御社が抱えている課題のうち、どれが解決できて、どのようなサービスが受けられるのか?4つのフェーズに分けてまとめています。どうぞお気軽にご覧ください。

生成AI資料イメージ

無料でダウンロードする

この記事をシェアする

関連記事