powered by TechFeed
表示モード
ハウツー

15分かかっていた予約受付をAIエージェントに任せたら、フォームより賢く動いた — LangGraphでステートフルな会話を実現する方法

8月2日、Soner Yıldırımが「I Replaced a 15-Minute Booking Process with a LangGraph AI Agent」と題した記事を公開した。この記事では、LangGraphとLangfuseを組み合わせてステートフルなカスタマーサポートエージェントを構築する手順について詳しく紹介されている。

8月2日、Soner Yıldırımが「I Replaced a 15-Minute Booking Process with a LangGraph AI Agent」と題した記事を公開した。この記事では、LangGraphとLangfuseを組み合わせてステートフルなカスタマーサポートエージェントを構築する手順について詳しく紹介されている。


清掃会社に見積もりを依頼したとき、ソファのサイズや素材の確認、写真の送付、住所の提供——そのやり取りだけで15分かかった。Soner Yıldırımはこれを「AIエージェントに任せるべき問題だ」と判断し、Python・LangGraphLangChainでエージェントを実装した。ソースコードはGitHubで公開されている。

単なるチャットボットではない

記事が強調するのは、このシステムが「定型フォームの代替」ではないという点だ。

フォームベースの見積もりツールでは、全顧客が同じ質問フローをたどる必要がある。一方、AIエージェントであれば:

「2LDKの部屋を掃除してほしい。住所はここで、冷蔵庫の中も頼みたい。火曜か水曜が都合いい」

この一文に必要情報がすべて含まれていれば、エージェントは追加質問なしに価格計算へ進む。逆に情報が不足していれば、不足している項目だけを聞く。エージェントが次のアクションを自律的に決定する点が、単なるワークフローとの違いだ。

エージェントが担う処理は以下の通り:

  • 顧客との会話を通じたニーズの把握
  • サービス料金の計算と提示
  • 顧客の承諾・拒否の処理
  • 最適な予約時間枠の提案
  • 予約の確定と記録

LangGraphでステートを管理する

複数ターンにまたがる会話を扱うため、ステートフルな設計が必要になる。LangGraphはグラフ構造——処理単位である「ノード」と、ノード間の遷移を定義する「エッジ」——によって会話フローをモデル化する。各ノードは現在の状態を読み取り、部分的な更新を返す。エッジには条件付きルーティングを設定でき、状態の内容に応じて次に実行するノードを動的に切り替えられる。

状態スキーマはTypedDictで定義する:

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]
    booking_details: BookingDetails
    calculated_price: NotRequired[float | None]
    time_options: NotRequired[list[TimeOption]]
    selected_slot: NotRequired[TimeOption | None]
    status: BookingStatus
    booking_id: NotRequired[str | None]

会話から抽出した情報はBookingDetailsという構造化モデルに変換される。フィールドはすべてOptionalで、部分的な抽出にも対応できる設計だ:

class BookingDetails(BaseModel):
    service_type: ServiceType | None = Field(default=None)
    size_info: float | None = Field(default=None, gt=0)
    cleaning_depth: CleaningDepth | None = Field(default=None)
    add_ons: list[str] = Field(default_factory=list)
    address: str | None = Field(default=None)
    is_complete: bool = False
    next_question: str | None = Field(
        default=None,
        exclude=True,
        description="A concise question asking only for information still missing.",
    )

条件付きルーティングで次のノードを決定する仕組みも肝だ。情報収集ノードの後、is_completeフラグを確認して価格計算ノードへ進むか、次のメッセージを待つかを分岐させる:

def should_continue_to_pricing(
    state: AgentState,
) -> Literal["calculate_price", "end"]:
    if state["booking_details"].is_complete:
        return "calculate_price"
    return "end"

最終的にグラフはチェックポインター付きでコンパイルされ、会話の途中状態を保持できる:

graph.compile(checkpointer=checkpointer or MemorySaver())

オブザーバビリティにLangfuseを使う

本番運用で欠かせないのがオブザーバビリティ(可観測性)だ。記事ではLangfuse——LLMアプリのトレーシング・モニタリング・デバッグを目的としたプラットフォームで、OSSとしてセルフホストすることも、クラウド版(SaaS)を利用することもできる——を採用している。

設定は.envにキーを記述するだけ:

LANGFUSE_SECRET_KEY=
LANGFUSE_PUBLIC_KEY=
LANGFUSE_BASE_URL="https://cloud.langfuse.com"

Langfuseのダッシュボードでは、プロンプト・入出力・トークン使用量・コストを一覧で確認できる。予算の異常検知にも使えるため、本番環境での監視基盤として機能する。

コストについては、1回の実行でほぼゼロに近い金額と紹介されており、OpenAI APIキーがあれば手軽に検証できる。

拡張の余地

現時点はv0という位置付けで、CLIによるエンドツーエンドの動作確認が可能な状態だ。モジュール設計により、以下への拡張が想定されている:

  • Webやモバイルアプリ、WhatsAppなどのフロントエンド
  • PostgreSQL等の本番データベース
  • 従業員カレンダーや位置情報サービスとの直接連携

こうした拡張が低コストで実現できる背景には、AgentStateを中心に据えたグラフ設計がある。すべてのノードが共通の状態オブジェクトを読み書きする構造のため、新たな機能をノードとして追加するだけでよく、既存の処理フローへの影響を最小限に抑えられる。


詳細はI Replaced a 15-Minute Booking Process with a LangGraph AI Agentを参照していただきたい。