0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangGraph 学習記録4:LangGraphで実装するReActエージェント:シンプルで強力なAIワークフロー設計

Posted at

目次

はじめに

大規模言語モデル(LLM)を活用したAIエージェントの開発において、効率的なワークフロー設計は成功の鍵を握ります。特に「考えて行動する」というプロセスを繰り返すReAct(Reasoning and Acting)パターンは、複雑なタスクを解決するエージェントにとって重要な手法です。

しかし、従来のReActエージェントの実装は複雑で理解しづらいことがありました。LangGraph(LangChainのグラフベースフレームワーク)を使用すると、このパターンをより直感的に実装できます。

この記事では、LangGraphを使用してReActエージェントを実装する方法を、初心者にも理解しやすいように段階的に解説します。完成すると、以下のような機能を持つエージェントが構築できます:

  • ユーザーの質問に対する推論
  • 必要に応じたウェブ検索ツールの利用
  • 計算ツールの使用(例:数値の3倍計算)
  • 複数のステップにわたる推論と行動のループ処理

完成したエージェントは「サンフランシスコの天気は何度ですか?それを3倍にしてください」といった質問に答えられるようになります。

前提知識と環境設定

必要なパッケージとAPIキー

このプロジェクトを進めるには、以下のパッケージとAPIキーが必要です:

  • パッケージ:

    • langchain
    • langgraph
    • langchain-openai
    • langchain-hub
    • python-dotenv
    • black, isort(コード整形用)
  • APIキー:

    • OpenAI API キー(GPT-3.5/4へのアクセス用)
    • Tavily API キー(ウェブ検索機能用)
    • LangChain API キー(トレース機能用、任意)

Poetry による環境設定

環境を設定するために、Poetryを使用します:

# プロジェクトディレクトリを作成
mkdir langgraph-react
cd langgraph-react

# Poetry環境を初期化
poetry init

# 依存関係をインストール
poetry add langchain langgraph langchain-openai langchain-hub black isort python-dotenv

環境変数の設定:

# .envファイルを作成
touch .env

.envファイルに以下の内容を追加します:

OPENAI_API_KEY=your_openai_api_key
TAVILY_API_KEY=your_tavily_api_key
LANGCHAIN_API_KEY=your_langchain_api_key
LANGCHAIN_PROJECT=your_langchain_project

ReActパターンの理解

ReActアルゴリズムの概要

ReAct(Reasoning and Acting)は、LLMベースのエージェントが問題を解決するための効果的なパターンです。以下の2つの主要ステップを繰り返します:

  1. 推論(Reasoning): エージェントは現在の状況を分析し、次に何をすべきかを決定します
  2. 行動(Acting): 決定に基づいて行動(ツールの実行など)を行います

この反復が、エージェントの自律的な問題解決を可能にします。

従来の実装と比較したLangGraphの利点

従来のLangChainでのReActエージェント実装では、複雑なロジックを理解するのが難しいことがありました。LangGraphを使用すると以下の利点があります:

  • 視覚的な明確さ: エージェントのフローをグラフとして視覚化できる
  • モジュール性: 各ノードが明確な責任を持ち、デバッグや拡張が容易
  • 状態管理: グラフ全体で一貫した状態管理が可能
  • 柔軟性: 条件付きエッジを使って複雑な決定ロジックを実装できる

カスタムツールの実装

エージェントが使用するツールを実装します。今回は2つのツールを作成します:

Tavilyサーチツール

Tavilyサーチツールは、ウェブ検索を実行するための組み込みツールです:

from langchain_community.tools.tavily_search import TavilySearchResults

# 検索結果を1件に制限したTavilyサーチツールを作成
search_tool = TavilySearchResults(max_results=1)

カスタム「triple」関数の作成

数値を3倍にする単純なカスタムツールを作成します:

from langchain.tools import tool

@tool
def triple(number: float) -> float:
    """指定された数値を3倍にします。
    
    Args:
        number: 3倍にする数値
        
    Returns:
        入力値の3倍の数値
    """
    # 文字列として渡される可能性があるためfloatに変換
    return float(number) * 3

両方のツールをリストにまとめます:

tools = [search_tool, triple]

ReActエージェントの推論コンポーネント構築

ReActエージェントRunnableの作成

推論エンジンとして機能するReActエージェントRunnableを作成します:

from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent
from langchain_hub import pull

# LLMの初期化(GPT-3.5 Turbo)
llm = ChatOpenAI(model="gpt-3.5-turbo")

# ReActプロンプトをLangChain Hubからプル
react_prompt = pull("hwchase17/react")

# ReActエージェントRunnableを作成
react_agent = create_react_agent(llm, tools, react_prompt)

ReActプロンプトの理解

ReActプロンプトは、エージェントの思考プロセスを「Thought(思考)」、「Action(行動)」、「Action Input(行動の入力)」、「Observation(観察)」という形式に導きます。

一般的なReActプロンプトの構造:

  1. システムメッセージ: タスクと利用可能なツールの説明
  2. フォーマット指示: 思考プロセスの記述方法
  3. サンプル: 正しい使用法を示す例
  4. 現在の状態: 現在のやり取りとスクラッチパッド(中間ステップの履歴)

Runnableが返すのは次の2種類のオブジェクトのいずれかです:

  • AgentAction: 実行すべきツールとその入力パラメータ
  • AgentFinish: ユーザーに返すべき最終的な答え

LangGraphにおけるカスタムステート設計

カスタムステートの必要性

LangGraphでは、グラフのノード間で情報を共有するためのステート(状態)が必要です。ReActエージェントの場合、特定の情報を追跡する必要があります:

  1. ユーザーの入力
  2. 現在のエージェントの結果(行動または終了)
  3. これまでの中間ステップの履歴

TypedDictによるステート実装

Python 3.8以降で導入されたTypedDictを使用して、型付きの状態クラスを作成します:

from typing import Annotated, TypedDict, Union, List, Tuple
from operator import add
from langchain.agents import AgentAction, AgentFinish

class AgentState(TypedDict):
    """エージェントの状態を表すクラス"""
    input: str  # ユーザーの入力
    agent_outcome: Union[AgentAction, AgentFinish, None]  # エージェントの現在の結果
    intermediate_steps: Annotated[List[Tuple[AgentAction, str]], add]  # 中間ステップのリスト

operator.addアノテーションの使用

Annotated[..., add]構文は、LangGraphに特別な意味を持ちます:

  • add演算子は、ノードが返す新しい値を既存のリストに追加するように指示します
  • アノテーションがない場合は、値が置き換えられます

この機能により、intermediate_stepsに中間ステップを蓄積できます。これはエージェントのスクラッチパッドとして機能し、推論履歴を保持します。

グラフノードの実装

LangGraphのグラフには、少なくとも2つのノードが必要です:

agent_reasonノード

このノードは、ReActエージェントの推論部分を実行します:

def run_agent_reasoning_engine(state: AgentState):
    """エージェントの推論エンジンを実行するノード"""
    # ReActエージェントRunnableを実行
    result = react_agent.invoke(state)
    # 状態を更新して返す
    return {"agent_outcome": result}

actノード

このノードは、エージェントが選択したツールを実行します:

from langchain.tools import ToolExecutor

# ツール実行のためのツールエグゼキューターを初期化
tool_executor = ToolExecutor(tools)

def execute_tools(state: AgentState):
    """ツールを実行するノード"""
    # 実行すべきツールの情報を取得
    agent_action = state["agent_outcome"]
    # ツールを実行
    output = tool_executor.invoke(agent_action)
    # 中間ステップとして結果を記録
    return {"intermediate_steps": [(agent_action, str(output))]}

グラフの組み立て

StateGraphの作成

LangGraphのStateGraphクラスを使用して、ノードとエッジを持つグラフを作成します:

from langgraph.graph import StateGraph, END

# カスタムステートを持つStateGraphを作成
flow = StateGraph(AgentState)

# ノードを追加
flow.add_node("agent_reason", run_agent_reasoning_engine)
flow.add_node("act", execute_tools)

# エントリーポイントを設定
flow.set_entry_point("agent_reason")

条件付きエッジの実装

条件付きエッジを使用して、推論ノードの結果に基づいてフローを制御します:

def should_continue(state: AgentState):
    """次のノードを決定する条件関数"""
    # エージェントが終了を示していれば、グラフを終了
    if isinstance(state["agent_outcome"], AgentFinish):
        return END
    # そうでなければ行動ノードへ
    return "act"

# 条件付きエッジを追加
flow.add_conditional_edges("agent_reason", should_continue)

# 行動ノードから常に推論ノードへ戻るエッジを追加
flow.add_edge("act", "agent_reason")

# グラフをコンパイル
app = flow.compile()

完成したグラフの構造は以下のようになります:

エージェントのテストとデバッグ

サンプルクエリでのテスト

実装したエージェントをテストしてみましょう:

question = "サンフランシスコの天気は何度ですか?それを3倍にしてください。"
result = app.invoke({"input": question})
print(result["agent_outcome"].return_values["output"])

期待される出力例:

サンフランシスコの現在の天気は12.8°Cです。これを3倍にすると38.4°Cになります。

ステートの変化とデバッグ

エージェントの実行中に状態がどのように変化するかを確認すると、動作の理解が深まります:

  1. 初期状態:

    • input: ユーザーの質問
    • agent_outcome: None
    • intermediate_steps: []
  2. 最初の推論後:

    • agent_outcome: AgentAction(天気検索ツールを使用)
  3. ツール実行後:

    • intermediate_steps: [(検索アクション, 天気情報)]
  4. 2回目の推論後:

    • agent_outcome: AgentAction(tripleツールを使用)
  5. 2回目のツール実行後:

    • intermediate_steps: [(検索アクション, 天気情報), (tripleアクション, 計算結果)]
  6. 最終推論後:

    • agent_outcome: AgentFinish(最終回答)

このデバッグ過程では、ツールの出力や状態の変化を注意深く観察することが重要です。特にtripleツールでは、文字列から浮動小数点数への適切な変換が必要です。

まとめと発展

構築したものの概要

このチュートリアルでは、LangGraphを使用してReActエージェントを構築しました:

  • カスタム状態クラスを設計して情報の流れを管理
  • 推論と行動のノードを実装
  • 条件付きエッジを使用してフローを制御
  • 複数のツールを統合(検索と計算)

従来のLangChainでのReAct実装と比較すると、LangGraphアプローチの明確さと拡張性が際立ちます。

潜在的な改善点

このエージェントは以下のように拡張できます:

  1. より多くのツールの追加:

    • 計算ツール
    • データベースアクセス
    • APIインテグレーション
  2. エラー処理の改善:

    • ツール実行の失敗を処理するノード
    • 入力検証と前処理
  3. メモリの追加:

    • 会話履歴の保持
    • 長期記憶の統合
  4. モニタリングと分析:

    • LangSmithを使用したより詳細なトレースの追加
    • パフォーマンスメトリクスの収集
  5. ヒューマンインザループ機能:

    • 人間の介入が必要な場合に支援を求めるノード

付録:完全なコード

最終的な完全なコードは以下の通りです:

# react.py - ReActエージェントの実装
from dotenv import load_dotenv
load_dotenv()

from langchain_hub import pull
from langchain.agents import create_react_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain.prompts import PromptTemplate
from langchain.tools import tool
from langchain_openai import ChatOpenAI

@tool
def triple(number: float) -> float:
    """指定された数値を3倍にします。
    
    Args:
        number: 3倍にする数値
        
    Returns:
        入力値の3倍の数値
    """
    return float(number) * 3

# ツールの作成
tools = [
    TavilySearchResults(max_results=1),
    triple
]

# LLMの初期化
llm = ChatOpenAI(model="gpt-3.5-turbo")

# ReActプロンプトをロード
react_prompt = pull("hwchase17/react")

# ReActエージェントRunnableを作成
react_agent = create_react_agent(llm, tools, react_prompt)
# state.py - カスタムステート実装
from typing import Annotated, TypedDict, Union, List, Tuple
from operator import add
from langchain.agents import AgentAction, AgentFinish

class AgentState(TypedDict):
    """エージェントの状態を表すクラス"""
    input: str  # ユーザーの入力
    agent_outcome: Union[AgentAction, AgentFinish, None]  # エージェントの現在の結果
    intermediate_steps: Annotated[List[Tuple[AgentAction, str]], add]  # 中間ステップのリスト
# nodes.py - グラフノード実装
from dotenv import load_dotenv
load_dotenv()

from langchain.tools import ToolExecutor
from react import react_agent, tools
from state import AgentState

# ツールエグゼキューターの初期化
tool_executor = ToolExecutor(tools)

def run_agent_reasoning_engine(state: AgentState):
    """エージェントの推論エンジンを実行するノード"""
    # ReActエージェントRunnableを実行
    result = react_agent.invoke(state)
    # 状態を更新して返す
    return {"agent_outcome": result}

def execute_tools(state: AgentState):
    """ツールを実行するノード"""
    # 実行すべきツールの情報を取得
    agent_action = state["agent_outcome"]
    # ツールを実行
    output = tool_executor.invoke(agent_action)
    # 中間ステップとして結果を記録
    return {"intermediate_steps": [(agent_action, str(output))]}
# main.py - グラフ作成と実行
from dotenv import load_dotenv
load_dotenv()

from langchain.agents import AgentFinish
from langgraph.graph import StateGraph, END
from state import AgentState
from nodes import run_agent_reasoning_engine, execute_tools

# ノード名の定数
AGENT_REASON = "agent_reason"
ACT = "act"

def should_continue(state: AgentState):
    """次のノードを決定する条件関数"""
    # エージェントが終了を示していれば、グラフを終了
    if isinstance(state["agent_outcome"], AgentFinish):
        return END
    # そうでなければ行動ノードへ
    return ACT

# カスタムステートを持つStateGraphを作成
flow = StateGraph(AgentState)

# ノードを追加
flow.add_node(AGENT_REASON, run_agent_reasoning_engine)
flow.add_node(ACT, execute_tools)

# エントリーポイントを設定
flow.set_entry_point(AGENT_REASON)

# 条件付きエッジを追加
flow.add_conditional_edges(AGENT_REASON, should_continue)

# 行動ノードから常に推論ノードへ戻るエッジを追加
flow.add_edge(ACT, AGENT_REASON)

# グラフをコンパイル
app = flow.compile()

# グラフを実行する
if __name__ == "__main__":
    # グラフをメルメイド図として保存
    app.get_graph().draw_mermaid_png("graph.png")
    
    # エージェントを実行
    question = "サンフランシスコの天気は何度ですか?それを3倍にしてください。"
    result = app.invoke({"input": question})
    
    # 結果を表示
    print(result["agent_outcome"].return_values["output"])

このチュートリアルを通じて、LangGraphを使った効率的なエージェント設計の基礎を理解し、より複雑なAIアプリケーションへと発展させる準備が整いました。


理解度チェッククイズ

  1. ReActパターンの2つの主要ステップは何ですか?

    • 計画と実行
    • 分析と合成
    • 推論と行動
    • 観察と応答
  2. LangGraphでカスタムステートにAnnotated[..., add]を使用する目的は?

    • パフォーマンスを向上させるため
    • 値を上書きせずに追加するため
    • 型チェックを強化するため
    • デバッグを容易にするため
  3. ReActエージェントのグラフで条件付きエッジが必要な理由は?

    • パフォーマンスの最適化のため
    • より多くのツールを追加するため
    • エージェントが行動するか終了するかを決定するため
    • エラー処理を実装するため
  4. AgentStateクラスに含まれる3つの主要な属性は?

    • input, agent_outcome, intermediate_steps
    • input, output, memory
    • query, result, history
    • state, action, observation
  5. ReActエージェントが返す可能性のある2種類の結果は?

    • Success, Failure
    • Continue, Stop
    • AgentAction, AgentFinish
    • Query, Response

このエージェント実装を出発点として、より複雑で強力なAIシステムを開発してください。LangGraphの強みを活かし、視覚的に明確で保守しやすいエージェントフローを設計しましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?