Voice AI Quickstart

参照元: LiveKit Agents Documentation ロードマップ: 学習ロードマップ

ドキュメント: https://docs.livekit.io/agents/start/voice-ai-quickstart/ コード参照: https://github.com/livekit/agents/blob/main/examples/voice_agents/basic_agent.py スターターテンプレート: https://github.com/livekit-examples/agent-starter-python

What(何についてか)

LiveKit Agentsを使ったvoiceエージェントの最小構成と、実際に動かすまでのセットアップ手順。

Why(なぜ必要か)

「動くものを先に作る」ことで全体のアーキテクチャを肌で理解するための出発点。

How(どう動くか)

2種類のモデル構成

graph LR
    subgraph "① STT-LLM-TTS パイプライン"
        A["音声"] --> B["STT\nDeepgram Nova-3"]
        B --> C["LLM\nGPT-4.1 mini"]
        C --> D["TTS\nCartesia Sonic-3"]
        D --> E["音声"]
    end

    subgraph "② Realtime Model"
        F["音声"] --> G["OpenAI Realtime API"]
        G --> H["音声"]
    end
STT-LLM-TTSRealtime Model
制御性✅ 高い(各ステップに介入可能)❌ 低い
Transcription✅ 自然に取れる❌ 難しい
レイテンシ❌ 3段階分のオーバーヘッド✅ 速い
コスト✅ 安い❌ 高い
プロバイダ選択✅ 自由(Deepgram/ElevenLabs等)❌ 限定的

コードの構造

graph TD
    A["cli.run_app(server)"] --> B["AgentServer"]
    B --> C["@server.rtc_session()\nentrypoint(ctx)"]
    C --> D["AgentSession\nSTT+LLM+TTS+VAD+ターン検出"]
    C --> E["MyAgent\n人格・instructions・tools"]
    D --> F["session.start(agent, room)"]
    E --> F

MyAgent(人格・ツール)

  • Agent を継承して定義
  • instructions → LLMへのシステムプロンプト
  • on_enter() → セッション参加時のライフサイクルフック(ここで初回発話)
  • @function_tool → LLMが呼べるツール(Function Calling)を1アノテーションで登録

AgentServer + prewarm

  • prewarm → Jobが来る前にプロセスをウォームアップ。Silero VADのような重いモデルを事前ロードしてレイテンシを削減
  • @server.rtc_session() → WebRTCセッションが来た時に呼ばれる関数を登録

AgentSession(パイプラインの心臓部)

session = AgentSession(
    stt=inference.STT("deepgram/nova-3", language="multi"),
    llm=inference.LLM("openai/gpt-4.1-mini"),
    tts=inference.TTS("cartesia/sonic-3", voice="..."),
    turn_detection=MultilingualModel(),
    vad=ctx.proc.userdata["vad"],
    preemptive_generation=True,
    resume_false_interruption=True,
    false_interruption_timeout=1.0,
    aec_warmup_duration=3.0,
)

ターン検出の仕組み

graph LR
    A["音声入力"] --> B["VAD\n喋ってる?止まった?"]
    B --> C["STT\nテキスト化"]
    C --> D["MultilingualModel\n本当に話し終わった?"]
    D --> E["LLM回答開始"]

MultilingualModel(LivKit謹製オープンウェイト):

  • VADの「無音検知」だけでなく、テキストの意味・文脈でターン完了を判定
  • 「えーっと…」の息継ぎで割り込まなくなる
  • 多言語対応(日本語含む)
  • VADのみより精度高、STT endpointingより汎用的
手法判定根拠精度備考
VAD only無音の長さ低い息継ぎで誤検知
STT endpointing句読点・文末プロバイダ依存
MultilingualModel意味・文脈高いLivKit推奨

重要パラメータ詳細

preemptive_generation=True

  • STTのfinalトランスクリプトが出た時点でLLM推論を開始(ターン確定を待たない)
  • turn_detection(MultilingualModel)と組み合わせて初めて真価を発揮
  • コスト:通常フローは同じ。キャンセル多発時のみ微増(途中生成トークンが無駄になる)
sequenceDiagram
    participant STT
    participant TD as MultilingualModel
    participant LLM

    STT->>LLM: final transcript(即座に推論開始)
    STT->>TD: transcript
    TD->>LLM: ターン確定(or キャンセル)
    Note over LLM: 先読みで早く回答できる

resume_false_interruption=True / false_interruption_timeout=1.0

  • 背景ノイズ等でVADが誤検知 → エージェントの発話が止まる問題への対策
  • 1秒間STTでテキストが出なければ「誤検知」と判断 → 発話を再開

aec_warmup_duration=3.0

  • AEC(音響エコーキャンセル)= エージェントの声がマイクに拾われることを防ぐ機能
  • セッション開始直後はキャリブレーション中のため、3秒間は割り込み検知をブロック
  • ⚠️ v1.4.3(2026-03-01時点の最新)には存在しない。 main ブランチで追加されたばかりでまだ未リリース。v1.4.3 では使用しないこと。

3つの起動モード

モード用途
consoleターミナル内で会話。ローカルテスト向け
devLiveKit Cloudに接続、ブラウザ等からアクセス可能
start本番モード

クラスの構造的な関係

graph TD
    A["AgentServer\n常駐ワーカー"] --> B["JobProcess\nJobごとにforkされるOSプロセス"]
    B --> C["prewarm\nJob到着前にモデルをロード"]
    B --> D["@server.rtc_session()\nで登録された関数\n(名前はなんでもよい)"]
    D --> E["AgentSession\nSTT+LLM+TTSパイプライン\nRoomへ接続する"]
    E --> F["Agent\n人格・tools・ライフサイクルフック"]
クラス役割備考
JobProcessJobを隔離するOSプロセスクラッシュが他Jobに波及しない
JobContextJob実行時の情報コンテナctx.room / ctx.proc を持つ
AgentSessionSTT+LLM+TTSパイプライン、Roomに接続関数本体で自分でnewする
Agent人格・tools・ライフサイクルフックsession.start() に渡す
  • JobProcess はOSレベルの境界。Roomに参加するのはAgentSession
  • ctx.proc = JobProcess(prewarmでロードしたモデルを ctx.proc.userdata["vad"] で取り出せる)

デコレータと関数の役割分担

@server.rtc_session(agent_name="my-agent")   # ← 登録するだけ
async def my_agent(ctx: JobContext):          # ← 関数名は何でもよい("entrypoint"は慣習)
    session = AgentSession(...)               # ← AgentSessionを作るのは自分のコード
    await session.start(agent=Assistant(), room=ctx.room)
    await ctx.connect()                       # ← 明示的接続(スターターのみ)
  • デコレータ = 「Jobが来たらこの関数を呼べ」と登録するだけ。AgentSessionは作らない
  • 関数本体 = 自分でAgentSessionをnewして、session.start()で動かす
  • entrypoint = ただの慣習的な名前。my_agent でも handle_session でも動く
  • agent_name = Agent Dispatchで特定のAgentを指名する時に使う名前(省略可)
  • await ctx.connect() = スターターは明示的に呼ぶ。basic_agent.py はAgentSessionのauto-connectに任せる

Key Concepts

用語説明
VADVoice Activity Detection。音声の有無を検知するローカルMLモデル
MultilingualModelLiveKit謹製のターン検出モデル。意味・文脈でターン完了を判定
prewarmJob到着前にプロセスを起動し重いモデルを事前ロードする仕組み
preemptive_generationSTT final時点でLLM推論を先読み開始してレイテンシを削減
AECAcoustic Echo Cancellation。エージェントの声がマイクに拾われるのを防ぐ(aec_warmup_duration は v1.4.3 未実装)
false interruption背景ノイズ等でVADが誤ってユーザー発話と検知すること
JobProcessJobを隔離するOSプロセス。prewarmで事前起動
JobContextJob実行時の情報コンテナ(room / proc を持つ)
agent_namertc_session()のパラメータ。Agent Dispatchで特定Agentを指名する時に使う

一言まとめ

MyAgentが人格・ツール、AgentSessionがSTT→LLM→TTSパイプライン、AgentServerがWebRTCでRoomに繋ぐ。ターン検出はVADで音を検知し、MultilingualModelで意味的に確定させる2段構え。デコレータは「Jobが来たらこの関数を呼べ」と登録するだけで、AgentSessionを作るのは関数本体の自分のコード。