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
というスキーマで状態を管理し、agent
とToolNode
という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
には、messages
、is_last_step
、remaining_steps
という3つの重要なフィールドがあります。
messages
とadd_messages
messages
フィールドは、ユーザーとエージェント間のやり取りの履歴を格納するリストです。このフィールドにはAnnotated[..., add_messages]
という型アノテーションが付与されています。
add_messages
は、LangGraphが提供する特別なリデューサー関数です。その役割は、2つのメッセージリストを賢くマージ(統合)することです。
- 基本的な動作: 新しいメッセージを既存のリストに追加します。
- IDが重複した場合: 同じIDを持つメッセージが存在する場合、後から渡された新しいメッセージで古いものを上書きします。これは、ツールの実行結果を後から反映させる際などに非常に便利です。
このadd_messages
の仕組みにより、各ノードは自身の処理結果(LLMの応答やツールの実行結果)を単純にmessages
に渡すだけで、LangGraphが自動的にチャット履歴を適切に更新してくれます。
is_last_step
とremaining_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]
IsLastStepManager
やRemainingStepsManager
といった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
は、このスーパー・ステップの進行状況を管理するための、いわば「グラフ計算の作業用メモ帳」です。
RemainingStepsManager
(PregelScratchpad
を参照するマネージャ)を通じて管理されています。
このオブジェクトは、現在のステップ番号 (step
) や処理全体の終了ステップ番号 (stop
) といった、グラフ実行のメタ情報を保持しています。
IsLastStepManager
やRemainingStepsManager
は、この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
つまり、AgentState
のremaining_steps
フィールドの値は、ノードが処理を進める(=スーパー・ステップが進む)たびに、PregelScratchpad
から最新の情報を取得して自動的に更新されていく、という仕組みになっています。
まとめ
本記事では、LangGraphのcreate_react_agent
について、仕組みを深掘りしました。
-
create_react_agent
は、LLMとツールを組み込んだCompiledGraph
を生成するファクトリ関数です。 - グラフの内部では、
AgentState
というスキーマに基づいて状態が管理され、agent
ノードとtools
ノードが連携して動作します。 -
AgentState
のmessages
はadd_messages
によって賢く更新され、チャット履歴の管理を容易にします。 - ループ制御に使われる
is_last_step
やremaining_steps
は、LangGraphの実行エンジンであるPregelモデルと、その進行状況を記録するPregelScratchpad
によって動的に管理されています。
create_react_agent
は、こうした強力で洗練されたLangGraphの機能をカプセル化し、開発者が本質的なロジックに集中できるようにしてくれる素晴らしいツールです。この内部構造を理解することで、より複雑なカスタムエージェントを構築する際の、強力な足がかりとなると思います。
補足
この記事の殆どはGitHub Copilotでのコードリーディングを通じてまとめました。
ほんとに便利というかなんというか・・・
Discussion