02. 最初の ReAct エージェント¶
ゴール¶
StateGraphの基本構造を理解する- ツール呼び出しループ(ReAct)を 自分の手で 組む
create_react_agentヘルパーを使うのは後回し。中身を理解してから使う
LangGraph の世界観¶
LangGraph は agent = 有限状態機械(FSM) という見方をする。
- State: 共有メモリ。messages、scratchpad、ユーザー入力など
- Node: state を受け取り、新しい state を返す関数
- Edge: node 間の遷移。条件付き edge で分岐可能
- Graph: node と edge をまとめたもの。コンパイルすると Runnable になる
┌──────────┐
→ │ agent │ ───→ tool call あり ──→ ┌─────┐
│ (LLM) │ │tools│
└──────────┘ ←── tool result ─────── └─────┘
│
└── tool call なし → END
ハンズオン1: 最小 ReAct¶
src/agent.py:
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@tool
def get_weather(city: str) -> str:
"""指定された都市の天気を返す(モック)"""
return f"{city}の天気: 晴れ、気温22度"
@tool
def calculator(expression: str) -> str:
"""Python の eval で式を評価する(学習用、本番では使わない)"""
try:
return str(eval(expression, {"__builtins__": {}}, {}))
except Exception as e:
return f"error: {e}"
tools = [get_weather, calculator]
llm = ChatAnthropic(model="claude-sonnet-4-5", temperature=0).bind_tools(tools)
def agent_node(state: State) -> dict:
"""LLM を呼んで次のアクションを決める"""
response = llm.invoke(state["messages"])
return {"messages": [response]}
def tool_node(state: State) -> dict:
"""直前の AIMessage に含まれる tool_call をすべて実行"""
last_msg = state["messages"][-1]
results = []
for call in last_msg.tool_calls:
tool_fn = {t.name: t for t in tools}[call["name"]]
result = tool_fn.invoke(call["args"])
from langchain_core.messages import ToolMessage
results.append(ToolMessage(content=str(result), tool_call_id=call["id"]))
return {"messages": results}
def should_continue(state: State) -> str:
"""tool_call があれば tools へ、なければ END へ"""
last_msg = state["messages"][-1]
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
return "tools"
return END
builder = StateGraph(State)
builder.add_node("agent", agent_node)
builder.add_node("tools", tool_node)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent")
graph = builder.compile()
実行:
# src/run.py
from agent import graph
from dotenv import load_dotenv
load_dotenv()
result = graph.invoke({
"messages": [("user", "東京の天気を教えて、それから 23 * 17 を計算して")]
})
for msg in result["messages"]:
msg.pretty_print()
何が起きているか¶
- user message →
agentnode - LLM が
get_weather(city="東京")を tool_call として返す should_continueがtoolsを返す →tool_nodeで実行- ToolMessage が messages に追加され、また
agentnode へ - LLM が今度は
calculator(expression="23 * 17")を要求 - 結果を見た LLM が tool_call なしの応答を返す →
END
これが ReAct ループの正体。while llm.has_tool_call(): execute() を state machine で表現しているだけ。
ハンズオン2: create_react_agent で同じものを作る¶
これだけ。中身は上で書いたものとほぼ同じ。ヘルパーの中身を理解した上で使う。
グラフ可視化¶
Mermaid 形式の図が出る。Obsidian に貼って構造を眼で覚える。
Try(手を動かす)¶
- tool を1つ追加する(例:
search_web(query)を DuckDuckGo API で) agent_nodeの中で system message を差し込む(「あなたは丁寧な日本語アシスタント」)graph.stream(...)で途中経過を出力してみる(step ごとに何が起きるか観察)- tool 実行で例外が出たらどうなるか試す(now: 落ちる。次章で対処)
学んだこと¶
StateGraphは state を中心に node を繋ぐ FSM- ReAct ループは「LLM → tool → LLM → tool → ... → END」を条件付き edge で表現
create_react_agentはこの典型パターンのヘルパー- グラフは Mermaid で可視化できる
次は 03_state_and_checkpoint。プロセスを落としても続きから再開できる永続化を扱う。
作成: 2026-05-16 / 最終更新: 2026-05-16