Front-desk sample

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

追加参照元:

Front-desk sample は、音声で予約受付を行う LiveKit Agent の実戦寄りサンプルである。単なる function tool のデモではなく、音声会話、ツール実行、会話内サブワークフロー、外部カレンダー連携、セッション状態管理、セッション終了後の自動評価までを一通り備えている。

このサンプルを読む上で最も重要なのは、責務が複数の層に分離されている点である。frontdesk_agent(ctx) は起動オーケストレーション、AgentSession は実行基盤、FrontDeskAgent は会話ロジック、Calendar は外部依存境界、on_session_end は評価と改善ループを担う。音声エージェントを本番運用に近い形で組む時の骨格として理解できる。

graph TD
    A["JobContext"] --> B["frontdesk_agent(ctx)"]
    B --> C["AgentSession[Userdata]"]
    C --> D["FrontDeskAgent"]
    C --> E["STT / LLM / TTS / VAD / Turn Detection"]
    C --> F["Userdata"]
    F --> G["Calendar Protocol"]
    G --> H["FakeCalendar"]
    G --> I["CalComCalendar"]
    D --> J["list_available_slots"]
    D --> K["schedule_appointment"]
    D --> L["beta.workflows.GetEmailTask"]
    B --> M["on_session_end"]
    M --> N["JudgeGroup"]
    M --> O["tagger.success/fail"]

What

このサンプルの目的は、ユーザーとの音声対話を通じてアポイント予約を完了することである。ユーザーは空き時間を尋ね、候補から一つを選び、メールアドレスを伝え、予約を確定する。Agent はその流れを会話で主導し、必要な場面でツールを呼び出す。

FrontDeskAgentinstructions によって音声会話向けの挙動を細かく制御している。例えば、挨拶だけで終わらず予約へ前進すること、時間表現は自然言語寄りにすること、候補は数件ずつ提示して反応を待つこと、空き枠が失われた場合は穏やかに代替案を出すことなどが定義されている。これはテキストチャットではなく音声UXを前提にしたプロンプト設計である。

Why

このサンプルが価値を持つのは、予約業務という具体的なユースケースに対して、LiveKit Agents の複数機能がどのように組み合わされるかを実コードで示しているからである。

第一に、AgentSessionAgent の責務が分離されている。AgentSession は STT / LLM / TTS / VAD / turn detection を束ねる実行基盤であり、FrontDeskAgent は会話ロジックとツール定義に集中する。これにより会話ロジックがインフラ設定と混線しない。

第二に、userdata によってアプリケーション状態が明示的に管理されている。カレンダー実装、予約済み時刻、空き枠競合回数などは LLM の記憶に頼らず、型付きデータとして保持される。これは業務フローの再現性とデバッグ容易性を高める。

第三に、カレンダー依存が Protocol によって抽象化されている。FakeCalendar はローカル開発とデモ用、CalComCalendar は本番用の Cal.com API 連携であり、上位層は共通インターフェースしか知らない。外部API都合をアプリケーション層へ漏らさない設計になっている。

第四に、ToolError と独自ドメイン例外の使い分けが明確である。SlotUnavailableError はカレンダー層の業務エラーを表し、それをエージェント層で ToolError に変換することで、内部クラッシュではなく「会話で回復可能な失敗」としてLLMへ伝える。

第五に、JudgeGrouptagger.success/fail により、セッション終了後の評価とビジネス成否判定が組み込まれている。会話品質と業務成功を別々に測ることで、運用改善の土台が作られている。

How

起動フロー

@server.rtc_session で登録された frontdesk_agent(ctx) がRTCセッション開始時のエントリーポイントになる。ここで room に接続し、環境変数 CAL_API_KEY の有無によって FakeCalendarCalComCalendar を選び、AgentSession[Userdata] を構築する。

sequenceDiagram
    participant User
    participant Room
    participant Session
    participant Agent
    participant Calendar
    participant Eval

    User->>Room: connect and speak
    Room->>Session: audio stream
    Session->>Agent: transcribed turn + context
    Agent->>User: greet and guide to booking
    User->>Agent: ask availability
    Agent->>Calendar: list_available_slots()
    Calendar-->>Agent: AvailableSlot list
    Agent->>User: offer a few spoken options
    User->>Agent: select a slot
    Agent->>Agent: GetEmailTask(chat_ctx)
    Agent->>Calendar: schedule_appointment()
    Calendar-->>Agent: success or unavailable
    Agent->>User: confirm or offer alternatives
    Session->>Eval: session end
    Eval->>Eval: JudgeGroup evaluation

AgentSession には stt=inference.STT("deepgram/nova-3")llm=inference.LLM("google/gemini-2.5-flash")tts=inference.TTS("cartesia/sonic-3", voice=...)turn_detection=MultilingualModel()vad=silero.VAD.load() が注入されている。VAD は音声区間の有無を判定し、turn detector はユーザーが話し終えたかを判断する。max_tool_steps=1 により、1ターン中のツール呼び出し回数を制限している。

会話ロジック

FrontDeskAgent.__init__ では self.tzself._slots_map を初期化する。_slots_mapslot_id -> AvailableSlot の辞書であり、list_available_slots() が候補を列挙した後、schedule_appointment() が選択された枠を復元するためのセッション内キャッシュとして機能する。

on_enter() では self.session.say() を使って最初の発話を行い、受動的に待つのではなく予約フローへ入る。

list_available_slots

この tool は空き枠を取得してプレーンテキストで返すと同時に、_slots_map を埋める。range 引数は Literal で制限されており、会話文脈から LLM が暗黙に選ぶ想定である。返り値をJSONではなくプレーンテキストにしているのは、サンプルとして LLM がそのまま読み上げやすい形を優先しているためだが、保守性の観点では JSON の方が優位という判断もあり得る。

候補ごとに relative time を付けている点も重要である。絶対日時だけではなく tomorrowin 1 week のような表現を加えることで、音声会話で理解しやすくしている。

schedule_appointment

この tool は slot_id_slots_map から引き当て、beta.workflows.GetEmailTask(chat_ctx=self.chat_ctx) によって会話履歴からメールアドレスを抽出し、カレンダーへ予約を依頼する。

beta.workflows.GetEmailTask はサンプル内実装ではなく、LiveKit Agents のベータ機能として提供される補助タスクである。本体Agentとは別に、会話履歴から必要情報を抽出する小さな内部ワークフローとして動く。

ctx.speech_handle.interrupted により途中中断を確認し、予約処理に入った後は ctx.disallow_interruptions() で割り込みを禁止する。これにより中途半端な状態遷移を防ぐ。業務上の失敗は ToolError として返し、ユーザーに代替候補を提示可能な会話継続性を確保する。

Calendar 抽象化

calendar_api.py では CalendarProtocol として定義されている。これは ABC のような明示継承ではなく、必要なメソッド形を満たしていれば同じ型として扱う構造的部分型である。DI境界を軽く保てるため、このサンプルには適している。

AvailableSlotstart_timeduration_min を持ち、unique_hash で短い外部向け識別子を生成する。この識別子生成には hashlib.blake2s が使われている。用途は暗号強度よりも、安定して短いIDを素早く作ることにある。

FakeCalendar は次の90日分の平日9:00-17:00からランダムに空き枠を生成し、ローカル検証用の偽実装として機能する。

CalComCalendar は Cal.com API への本番接続を担う。initialize() ではユーザー情報を取得し、event type を探し、無ければ作成する。event type は「どの種類の予約枠か」を表す予約テンプレートIDであり、空き枠検索と予約作成の両方で基準になる。schedule_appointment()list_available_slots() は Cal.com のレスポンスを AvailableSlotSlotUnavailableError に変換し、上位層に vendor 固有仕様を漏らさない。

また http_context.http_session() を優先利用している点も重要である。これは LiveKit ランタイムが管理する共有HTTPセッションを取得する仕組みであり、接続プールとライフサイクル管理をフレームワーク側に寄せる意図がある。利用できない文脈では aiohttp.ClientSession() にフォールバックする。

評価フロー

on_session_end(ctx) では ctx.make_session_report() によって会話レポートを生成し、短すぎる会話はスキップした上で JudgeGroup を用いて評価する。accuracy_judgetool_use_judgesafety_judgecoherence_judge などは LiveKit が提供する組み込み評価器であり、実際の採点には指定した評価用LLMが使われる。

その後、userdata.booked_times の有無によって ctx.tagger.success() または ctx.tagger.fail() を呼び、会話品質と業務成功を別軸で記録する。これは運用改善において極めて重要な分離である。

Key Concepts

用語説明
Agent会話ロジック本体。instructions、on_enter、toolsを持つ
AgentSessionSTT/LLM/TTS/VAD/turn detection と userdata を束ねる実行基盤
Userdataセッション状態の保持領域。LLM記憶に頼らないアプリ状態
JobContextジョブ全体の実行文脈。room、tagger、session report等を持つ
RunContexttool実行時の局所文脈。userdata、speech_handle、interrupt制御などを持つ
ToolError会話で回復可能なツール失敗をLLMに伝える例外
Protocol構造的部分型。必要メソッドを持てば同じ型として扱う
Event TypeCal.comにおける予約テンプレートID。空き枠検索と予約作成の基準
JudgeGroup複数judgeをまとめて評価する枠組み
beta.workflows.GetEmailTask会話履歴からメールアドレスを抽出するLiveKit組み込みベータworkflow

一言まとめ

Front-desk sample は、音声予約エージェントを実務寄りに構成する際の責務分離パターンを凝縮したサンプルである。会話ロジック、実行基盤、状態管理、外部依存、評価の各層をどう切り分けるかを学ぶ教材として価値が高い。一方で、返り値のJSON化やメンバ変数の明示宣言など、実務コードとして改善したくなるポイントも見えるため、サンプルと本番設計の差分を考える題材としても優れている。