この記事では、LangGraphを使用してPythonでエージェントワークフローを構築する方法を、単一モデル呼び出しからツールを使用し永続的な会話メモリを持つエージェントまで詳しく解説します。
はじめに
ほとんどのAIエージェント設定は単一ターンの質問応答をうまく処理します。しかし、エージェントがデータベースに問い合わせたり、以前のメッセージのコンテキストを覚えたり、モデルが何を決定したかをユーザーに明確に示す必要が生じると、問題は複雑になります。LangGraphはこれらの課題に取り組むためのクリーンな構造を提供します。
エージェントはグラフとして表現され、ノードは作業単位、エッジは実行順序を定義し、共有状態オブジェクトがメッセージ履歴全体を各ステップで運びます。モデルはノード内で実行されるため、すべての推論ステップ、ツール呼び出し、応答がグラフ状態の一部になります。これにより、実行フロー全体が可視化・検査可能になり、後続の任意のノードで利用できます。
状態、ノード、エッジの理解
すべてのLangGraphグラフは以下の3つのコンポーネントで構成されます。
- 状態(State):TypedDictで、グラフ全体の共有メモリとして機能します。各ノードは状態を読み取り、更新を書き込みます。更新しないフィールドは変更されません。
- ノード(Nodes):通常のPython関数。現在の状態を引数として受け取り、更新するフィールドの辞書を返します。
add_nodeで登録します。 - エッジ(Edges):実行順序を定義します。
add_edge(A, B)はA完了後にBを実行します。add_conditional_edgesはルーティング関数に基づいて分岐できます。
リデューサー(Reducer)を使用すると、フィールドの累積を処理できます。例えば、operator.addを使用するとリストフィールドが置き換えではなく追加されます。LangGraphのMessagesStateはadd_messagesリデューサーを使用し、メッセージの重複排除と順序付けも行います。
会話履歴の管理
MessagesStateは組み込みの状態型で、messagesフィールドを持ち、add_messagesリデューサーを使用します。ノードが新しいメッセージを返すたびに、既存のリストに追加されます。状態にカスタムフィールド(顧客IDなど)を追加することもできます。
ノード内でのモデル呼び出し
コアとなるノード関数は、現在のメッセージリストをモデルに渡し、その応答を追加します。ChatOpenAI(または任意のLangChainチャットモデル)を使用し、AIMessageを返します。システムメッセージは毎回設定でき、状態に保存されません。
ツール登録とツール呼び出しのルーティング
@toolデコレータでツールを定義し、bind_toolsでモデルにバインドします。ToolNodeがツール呼び出しを実行し、tools_conditionが条件付きエッジとして、モデルの出力にtool_callsがある場合はツールノードへ、なければ終了へルーティングします。ツールノードからモデルノードへのエッジがループを形成し、モデルがツール結果を基に最終回答を生成できるようにします。
推論ループの追跡
モデルがツールを使用する際の実際のプロセスは最終出力よりも複雑です。モデルは最初にtool_callsを持つAIMessageを生成し、ツールノードが実行されてToolMessageを返し、モデルが再度呼び出されて最終回答を生成します。これが標準のReActパターンであり、各ツール使用には2回のモデル呼び出しが必要です。
セットアップ
必要なパッケージをインストールします:pip install langgraph langchain-openai python-dotenv、.envファイルを作成してOpenAI APIキーを保存します。
この記事では、グラフをゼロから構築し、すべてのステップをカバーすることで、より複雑なエージェントワークフローの基盤を提供します。