初級

ReActパターンの実装

ReasoningとActing:LLMが外部ツールと対話することを可能にする基礎的なパターン

12分で読める
LangChainOpenAI

ReActパターンの実装#

ReAct(Reasoning and Acting)は、推論の過程とアクションの実行を交互に行うことで、LLMが複雑な問題を解決できるようにする基礎的なパターンです。

ReActとは?#

ReActは以下の要素を組み合わせます:
  • 推論(Reasoning):モデルが問題を段階的に考え抜く
  • 行動(Acting):モデルが外部ツールを使用してアクションを実行する
  • 観察(Observation):モデルがツールの出力を推論に組み込む
このループは、モデルが最終的な答えに到達するまで続きます。

ReActループ#

Thought: Xについての情報を見つける必要がある
Action: search[X]
Observation: [検索結果]
Thought: 結果に基づいて、Yがわかった。Zを計算する必要がある。
Action: calculate[Zの式]
Observation: [計算結果]
Thought: 必要な情報はすべて揃った。
Action: finish[最終的な答え]

基本的な実装#

ツールのセットアップ#

python
from typing import Callable, Dict

# 利用可能なツールを定義
tools: Dict[str, Callable] = {
    "search": lambda query: search_web(query),
    "calculate": lambda expr: eval_math(expr),
    "lookup": lambda term: lookup_wikipedia(term),
}

def execute_action(action: str, input: str) -> str:
    """適切なツールを使用してアクションを実行する"""
    if action in tools:
        return tools[action](input)
    return f"Unknown action: {action}"

ReActプロンプト#

python
REACT_PROMPT = """利用可能なツールを使用して以下の質問に答えてください。

利用可能なツール:
- search[query]: ウェブで情報を検索する
- calculate[expression]: 数式を評価する
- lookup[term]: Wikipediaで用語を調べる
- finish[answer]: 最終的な答えを提供する

常に以下の形式を使用してください:
Thought: <次に何をするかについての推論>
Action: <tool_name>[<input>]

観察を受け取った後は、別のThought/Actionを続けるか、finishしてください。

質問: {question}

{history}
Thought:"""

ReActエージェント#

python
from openai import OpenAI
import re

client = OpenAI()

def parse_action(response: str) -> tuple:
    """モデルの応答からアクションと入力を解析する"""
    match = re.search(r'Action:\s*(\w+)\[(.*?)\]', response)
    if match:
        return match.group(1), match.group(2)
    return None, None

def react_agent(question: str, max_steps: int = 10) -> str:
    """完了するまでReActループを実行する"""
    history = ""

    for step in range(max_steps):
        # 次の思考とアクションを生成
        prompt = REACT_PROMPT.format(question=question, history=history)

        response = client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            stop=["Observation:"]
        )

        output = response.choices[0].message.content
        history += output + "\n"

        # アクションを解析して実行
        action, action_input = parse_action(output)

        if action == "finish":
            return action_input

        if action:
            observation = execute_action(action, action_input)
            history += f"Observation: {observation}\n"
        else:
            history += "Observation: Could not parse action. Try again.\n"

    return "Max steps reached without conclusion"

LangChainの使用#

LangChainは組み込みのReActサポートを提供します:
python
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import Tool
from langchain_openai import ChatOpenAI
from langchain import hub

# ツールを定義
tools = [
    Tool(
        name="Search",
        func=search_web,
        description="最新情報をウェブで検索する"
    ),
    Tool(
        name="Calculator",
        func=eval_math,
        description="数学計算に役立つ"
    ),
]

# hubからReActプロンプトを取得
prompt = hub.pull("hwchase17/react")

# エージェントを作成
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent = create_react_agent(llm, tools, prompt)

# エグゼキューターを作成
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=10,
    handle_parsing_errors=True
)

# 実行
result = agent_executor.invoke({"input": "What is the population of Tokyo times 2?"})
print(result["output"])

ReActの拡張#

メモリの追加#

python
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True
)

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=True
)

エラー回復#

python
def react_with_recovery(question: str) -> str:
    """エラー回復機能付きReActエージェント"""
    history = ""
    errors = 0
    max_errors = 3

    for step in range(max_steps):
        try:
            # ... 通常のReActループ
            pass
        except Exception as e:
            errors += 1
            history += f"Observation: Error occurred: {str(e)}. Trying alternative approach.\n"

            if errors >= max_errors:
                return "Too many errors, unable to complete task"

    return result

構造化出力#

python
from pydantic import BaseModel

class ReActStep(BaseModel):
    thought: str
    action: str
    action_input: str
    observation: str | None = None

class ReActTrace(BaseModel):
    question: str
    steps: list[ReActStep]
    final_answer: str

一般的なパターン#

検索 → 要約#

Thought: AIに関する最近のニュースを見つける必要がある
Action: search[latest AI news 2024]
Observation: [複数の記事...]
Thought: いくつかの記事が見つかった。主要なポイントを要約しよう。
Action: finish[Summary: 1. OpenAI released... 2. Google announced...]

計算 → 検証#

Thought: 複利を計算する必要がある
Action: calculate[1000 * (1 + 0.05) ** 10]
Observation: 1628.89
Thought: 段階的に計算して検証しよう
Action: calculate[1000 * 1.05 ** 10]
Observation: 1628.89
Thought: 両方の計算結果が一致する
Action: finish[The compound interest result is $1,628.89]

ベストプラクティス#

  1. 明確なツール説明:ツールの目的を曖昧さなくする
  2. ツール数の制限:3〜5個のツールが最適。多すぎると混乱を招く
  3. エラーの適切な処理:ループにエラー回復機能を含める
  4. 最大反復回数の設定:無限ループを防ぐ
  5. トレースのログ記録:デバッグと改善のために完全なトレースを保持する

ReActを使用するタイミング#

適しているタスク:
  • 多段階の調査タスク
  • 参照を伴う計算
  • リアルタイムデータを必要とするタスク
理想的でないタスク:
  • 単純なQ&A
  • 創造的な文章作成
  • 外部ツールを必要としないタスク