ReActエージェント実装ガイド
ゼロからReAct(Reasoning + Acting)パターンのエージェントを構築する
ReActフレームワークとは
ReAct (Reasoning and Acting) は2023年にYaoらが発表したフレームワーク。LLMに「思考」と「行動」を交互に行わせることで、複雑なタスクを段階的に解決する。
Thought
現状を分析し、次のアクションを計画
Action
ツールを実行して情報を取得
Observation
結果を観察し、次のループへ
従来の方法との違い
従来: Chain-of-Thought (CoT)
- - 思考のみ、行動なし
- - 外部情報にアクセス不可
- - ハルシネーションが起きやすい
- - 静的な推論のみ
ReAct: 思考 + 行動
- - 思考と行動を交互に実行
- - ツールで外部情報を取得
- - 事実確認が可能
- - 動的に計画を修正
完全なReActエージェント実装
以下は、Web検索と計算ができるReActエージェントの完全な実装です。
ReActエージェント完全実装
python
import os
import re
import json
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY"),
)
# ================================
# ツール定義
# ================================
class Tools:
@staticmethod
def search(query: str) -> str:
"""Web検索をシミュレート(実際はTavily APIなどを使用)"""
return f"検索結果: '{query}' に関する情報が見つかりました。"
@staticmethod
def calculate(expression: str) -> str:
"""数式を計算"""
try:
result = eval(expression)
return f"計算結果: {expression} = {result}"
except Exception as e:
return f"計算エラー: {e}"
@staticmethod
def get_weather(city: str) -> str:
"""天気情報を取得(シミュレート)"""
return f"{city}の天気: 晴れ、気温22度、湿度45%"
AVAILABLE_TOOLS = {
"search": Tools.search,
"calculate": Tools.calculate,
"get_weather": Tools.get_weather,
}
# ================================
# ReActエージェント
# ================================
class ReActAgent:
def __init__(self, model: str = "anthropic/claude-sonnet-4", max_iterations: int = 10):
self.model = model
self.max_iterations = max_iterations
def parse_action(self, text: str) -> tuple[str, str] | None:
"""Action: tool_name(argument) を解析"""
pattern = r"Action:\s*(\w+)\((.+?)\)"
match = re.search(pattern, text)
if match:
return match.group(1), match.group(2).strip('"\'')
return None
def execute_action(self, tool_name: str, argument: str) -> str:
"""ツールを実行"""
if tool_name in AVAILABLE_TOOLS:
try:
return AVAILABLE_TOOLS[tool_name](argument)
except Exception as e:
return f"ツール実行エラー: {e}"
return f"未知のツール: {tool_name}"
def run(self, user_query: str) -> str:
"""エージェントを実行"""
messages = [
{"role": "system", "content": REACT_SYSTEM_PROMPT},
{"role": "user", "content": user_query}
]
for iteration in range(self.max_iterations):
response = client.chat.completions.create(
model=self.model,
messages=messages,
temperature=0
)
assistant_message = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_message})
if "Final Answer:" in assistant_message:
return assistant_message.split("Final Answer:")[-1].strip()
action = self.parse_action(assistant_message)
if action:
tool_name, argument = action
observation = self.execute_action(tool_name, argument)
messages.append({"role": "user", "content": f"Observation: {observation}"})
return "最大イテレーション数に達しました。"
# 使用例
agent = ReActAgent()
result = agent.run("東京の今日の天気を教えてください")
print(result)実行例
「日本のGDP 4.2兆ドル、人口1.24億人で一人当たりGDPを計算して」と依頼した場合:
エージェントの実行ログ
text
--- イテレーション 1 ---
エージェント:
Thought: ユーザーは一人当たりGDPを計算したいようです。
GDPが4.2兆ドル(4,200,000,000,000ドル)で、人口が1.24億人(124,000,000人)なので、
一人当たりGDP = GDP / 人口 で計算できます。
Action: calculate(4200000000000 / 124000000)
観察: 計算結果: 4200000000000 / 124000000 = 33870.96774193548
--- イテレーション 2 ---
エージェント:
Thought: 計算結果が出ました。一人当たりGDPは約33,871ドルです。
これをわかりやすく整形して回答します。
Final Answer: 2024年の日本の一人当たりGDPは約33,871ドル(約3.4万ドル)です。発展: 複数ツールの連携
ReActの真価は、複数のツールを連携させて複雑なタスクを解決できることです。
高度なツール定義
python
# 追加ツールの例
class AdvancedTools:
@staticmethod
def read_file(path: str) -> str:
"""ファイルを読み込む"""
with open(path, 'r') as f:
return f.read()
@staticmethod
def write_file(path: str, content: str) -> str:
"""ファイルに書き込む"""
with open(path, 'w') as f:
f.write(content)
return f"ファイル {path} に書き込みました"
@staticmethod
def run_code(code: str) -> str:
"""Pythonコードを実行"""
import subprocess
result = subprocess.run(
["python", "-c", code],
capture_output=True,
text=True,
timeout=10
)
return result.stdout or result.stderr
@staticmethod
def call_api(url: str) -> str:
"""外部APIを呼び出す"""
import requests
response = requests.get(url)
return response.text[:1000]
# 実際の検索ツール (Tavily API使用例)
def tavily_search(query: str) -> str:
import requests
response = requests.post(
"https://api.tavily.com/search",
json={
"api_key": os.getenv("TAVILY_API_KEY"),
"query": query,
"max_results": 3
}
)
results = response.json()
return "\n".join([r["content"][:200] for r in results.get("results", [])])発展: Reflexionパターン
ReflexionはReActを拡張し、自己評価と反省のステップを追加。過去の失敗から学習し、次回の試行で改善できる。
Reflexionパターンの実装
python
class ReflexionAgent(ReActAgent):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.memory = [] # 過去の反省を保存
def reflect(self, task: str, result: str, success: bool) -> str:
"""結果を振り返り、改善点を抽出"""
reflection_prompt = f"""
タスク: {task}
結果: {result}
成功: {"はい" if success else "いいえ"}
この試行から学べることは何ですか?次回の改善点を3つ挙げてください。
"""
response = client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": reflection_prompt}]
)
reflection = response.choices[0].message.content
self.memory.append(reflection)
return reflection
def run_with_reflection(self, user_query: str, max_retries: int = 3) -> str:
"""反省を活用してリトライ"""
for attempt in range(max_retries):
context = ""
if self.memory:
context = "過去の反省:\n" + "\n".join(self.memory[-3:])
result = self.run(f"{context}\n\n{user_query}")
success = "エラー" not in result and "わかりません" not in result
if success:
return result
self.reflect(user_query, result, success)
return resultベストプラクティス
1. 明確なツール説明
各ツールの目的、引数、戻り値を明確に記述。LLMが適切なツールを選択できるようにする。
2. 適切なイテレーション制限
無限ループを防ぐためmax_iterationsを設定。タスクの複雑さに応じて5-15程度が適切。
3. エラーハンドリング
ツール実行エラーを適切にキャッチし、LLMに報告。リカバリー可能な形式で返す。
4. 低いtemperature設定
エージェントの動作を決定論的にするため、temperature=0または0.1を推奨。