📙

LangGraphのcreate_react_agentとPregelモデル

に公開

create_react_agentとは?

create_react_agentは、LangGraphが提供するprebuilt機能の一つで、ReActエージェントとして機能するCompiledGraphを返すファクトリ関数です。

CompiledGraphはLangChain Runnableオブジェクトの一種であり、他のLCELコンポーネントと同様にinvoke(呼び出し)、stream(ストリーミング)、そして他のRunnableとの合成が可能です。これにより、複雑なエージェントロジックをカプセル化し、再利用可能なコンポーネントとして扱えるようになります。

全体像:クラスと処理の流れ

クラス図で見るコンポーネントの関係

以下のクラス図は、create_react_agentが利用する主要なコンポーネントの関係性を示しています。

  • create_react_agent は、LLM(BaseLanguageModel)とツール群(BaseTool)を受け取り、CompiledGraph を生成します。
  • CompiledGraphは、内部的に状態を持つグラフ StateGraph をラップしています。
  • StateGraphは、AgentState というスキーマで状態を管理し、agentToolNode という2つの主要なノード(処理単位)を含みます。
  • ユーザーがCompiledGraphを呼び出す(invoke)と、グラフ内のノードが連携して処理を実行します。

処理シーケンス

では、実際にユーザーが「サンフランシスコの天気は?」と質問した際の、内部的な処理の流れをシーケンス図で見てみましょう。

1. 簡易シーケンス

まずは、LLMとツールの間のインタラクションに着目したシンプルな図です。

LLMエージェントは、必要に応じてツールを呼び出し、その結果を受け取って最終的な応答を生成する、というループ処理を行っていることがわかります。

2. 詳細シーケンス

次に、CompiledGraphの内部で、状態(State)がどのように更新され、ノード間をデータがどのように流れていくかを詳細に見ていきます。

この詳細なシーケンスから、CompiledGraphが単なる処理の実行者ではなく、状態を管理し、条件(should_continue)に応じて次に実行するノードを決定するコントローラーの役割を果たしていることが明確になります。

状態管理:AgentState

create_react_agentの挙動を理解する上で欠かせないのが、グラフの状態を定義するAgentStateです。これはTypedDictで定義されたPythonのクラスで、グラフ内のノード間で情報をやり取りするためのスキーマ(設計図)となります。

class AgentState(TypedDict):
    """The state of the agent."""

    # The list of messages that have been exchanged between the user and the agent.
    messages: Annotated[Sequence[BaseMessage], add_messages]
    # A flag indicating whether the recent response from the agent is the final one.
    is_last_step: IsLastStep
    # The number of steps remaining in the graph execution.
    remaining_steps: RemainingSteps

このAgentStateには、messagesis_last_stepremaining_stepsという3つの重要なフィールドがあります。

messagesadd_messages

messagesフィールドは、ユーザーとエージェント間のやり取りの履歴を格納するリストです。このフィールドにはAnnotated[..., add_messages]という型アノテーションが付与されています。

add_messagesは、LangGraphが提供する特別なリデューサー関数です。その役割は、2つのメッセージリストを賢くマージ(統合)することです。

  • 基本的な動作: 新しいメッセージを既存のリストに追加します。
  • IDが重複した場合: 同じIDを持つメッセージが存在する場合、後から渡された新しいメッセージで古いものを上書きします。これは、ツールの実行結果を後から反映させる際などに非常に便利です。

このadd_messagesの仕組みにより、各ノードは自身の処理結果(LLMの応答やツールの実行結果)を単純にmessagesに渡すだけで、LangGraphが自動的にチャット履歴を適切に更新してくれます。

is_last_stepremaining_steps

これら2つのフィールドは、グラフのループ処理を制御するために使われます。

  • is_last_step: 現在のステップが、グラフ実行の最後のステップかどうかを示す真偽値(bool)。
  • remaining_steps: グラフ実行の残りステップ数を示す整数(int)。
from typing import Annotated

from langgraph.managed.base import ManagedValue
from langgraph.types import PregelScratchpad


class IsLastStepManager(ManagedValue[bool]):
    @staticmethod
    def get(scratchpad: PregelScratchpad) -> bool:
        return scratchpad.step == scratchpad.stop - 1


IsLastStep = Annotated[bool, IsLastStepManager]


class RemainingStepsManager(ManagedValue[int]):
    @staticmethod
    def get(scratchpad: PregelScratchpad) -> int:
        return scratchpad.stop - scratchpad.step


RemainingSteps = Annotated[int, RemainingStepsManager]

IsLastStepManagerRemainingStepsManagerといったManagedValueを継承したクラスが、これらの値を管理しています。これらのマネージャは、PregelScratchpadというオブジェクトから動的に値を取得します。

一体PregelScratchpadとは何なのでしょうか?

LangGraphを支えるPregelモデル

is_last_stepのような一見複雑に見える仕組みは、LangGraphがPregelという分散グラフ処理モデルをベースにしていることに由来します。

Pregelとは?

Pregel(プレゲル)は、2010年にGoogleが発表した大規模グラフ処理のための計算モデルです。その主な特徴は以下の通りです。

  • 頂点中心(Vertex-centric)モデル: 各ノード(頂点)が自身の状態を持ち、隣接するノードとメッセージを交換しながら処理を進めます。
  • スーパー・ステップ(Superstep): 計算は「スーパー・ステップ」と呼ばれる同期的なラウンド単位で進行します。各ステップで、全ノードが並列に計算を行い、次のステップのノードへメッセージを送信します。

LangGraphのグラフ実行エンジンも、このPregelの考え方を採用しており、各ノードの実行が1つの「スーパー・ステップ」に相当します。

class CompiledStateGraph(
    Pregel[StateT, InputT, OutputT], Generic[StateT, InputT, OutputT]
):

PregelLoop

  • PregelLoopはエンジンの「心臓部」です。
  • グラフの「1ステップ(superstep)」を管理し、その中で「どのノード(タスク)を実行するか」「チャネルや状態をどう更新するか」などを制御します。
  • 並列処理やキャッシュ、チェックポイント(途中保存)、割り込み(interrupt)などもここで扱います。

PregelScratchpad:グラフ計算の"作業メモ"

PregelScratchpadは、このスーパー・ステップの進行状況を管理するための、いわば「グラフ計算の作業用メモ帳」です。

RemainingStepsManagerPregelScratchpadを参照するマネージャ)を通じて管理されています。

このオブジェクトは、現在のステップ番号 (step) や処理全体の終了ステップ番号 (stop) といった、グラフ実行のメタ情報を保持しています。

IsLastStepManagerRemainingStepsManagerは、このPregelScratchpadを直接参照することで、ループの各ステップにおいて「今が最後のステップか?」「残りステップはいくつか?」を動的に計算しているのです。

# IsLastStepManagerの実装イメージ
class IsLastStepManager(ManagedValue[bool]):
    @staticmethod
    def get(scratchpad: PregelScratchpad) -> bool:
        # 現在のステップ番号が、終了ステップ番号の1つ手前かどうかを判定
        return scratchpad.step == scratchpad.stop - 1

# RemainingStepsManagerの実装イメージ
class RemainingStepsManager(ManagedValue[int]):
    @staticmethod
    def get(scratchpad: PregelScratchpad) -> int:
        # 終了ステップと現在ステップの差から、残りステップ数を計算
        return scratchpad.stop - scratchpad.step

つまり、AgentStateremaining_stepsフィールドの値は、ノードが処理を進める(=スーパー・ステップが進む)たびに、PregelScratchpadから最新の情報を取得して自動的に更新されていく、という仕組みになっています。

まとめ

本記事では、LangGraphのcreate_react_agentについて、仕組みを深掘りしました。

  • create_react_agentは、LLMとツールを組み込んだ CompiledGraph を生成するファクトリ関数です。
  • グラフの内部では、AgentState というスキーマに基づいて状態が管理され、agent ノードと tools ノードが連携して動作します。
  • AgentStatemessagesadd_messagesによって賢く更新され、チャット履歴の管理を容易にします。
  • ループ制御に使われるis_last_stepremaining_stepsは、LangGraphの実行エンジンであるPregelモデルと、その進行状況を記録するPregelScratchpadによって動的に管理されています。

create_react_agentは、こうした強力で洗練されたLangGraphの機能をカプセル化し、開発者が本質的なロジックに集中できるようにしてくれる素晴らしいツールです。この内部構造を理解することで、より複雑なカスタムエージェントを構築する際の、強力な足がかりとなると思います。

補足

この記事の殆どはGitHub Copilotでのコードリーディングを通じてまとめました。
ほんとに便利というかなんというか・・・

Discussion