LangGraph エージェント実装ガイド
LangGraph による Tool-based エージェント・マルチエージェントシステムの実装ガイド。 StateGraph の基礎から ReAct エージェント、Router/Supervisor パターンまでを網羅する。
目次
- エージェント vs アジェンティックワークフロー
- StateGraph の基礎
- Tool Calling の仕組み
- エージェントグラフの組み立て
- ReAct エージェント(プリビルドコンポーネント)
- マルチエージェント: Router パターン
- マルチエージェント: Supervisor パターン
- LangSmith によるトレーシング
- アーキテクチャ判断テーブル
1. エージェント vs アジェンティックワークフロー
| 特徴 | アジェンティックワークフロー | エージェント |
|---|---|---|
| 実行フロー | 決定論的(事前定義された分岐) | 動的(LLMが実行時に判断) |
| ツール選択 | ハードコード | LLMが自律的に選択 |
| 適用場面 | 処理手順が明確な場合 | 未知のシナリオに対応が必要な場合 |
| 実装 | StateGraph + 条件付きエッジ | create_react_agent or 自作グラフ |
使い分けの原則:
- どのツールをいつ呼ぶか事前に決められない → エージェント
- 処理の順序と分岐が決まっている → アジェンティックワークフロー
2. StateGraph の基礎
状態定義(TypedDict)
from typing import Annotated, Sequence
from typing_extensions import TypedDict
from langchain_core.messages import BaseMessage
import operator
# シンプルなエージェント状態(メッセージのみ)
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
Annotated[Sequence[BaseMessage], operator.add] の意味:
- 各ノードが返す
messagesは既存のリストに追加される(置換ではない)
グラフ構造の基本
from langgraph.graph import StateGraph, END, START
# グラフ構築
builder = StateGraph(AgentState)
# ノード追加
builder.add_node("node_a", node_a_function)
builder.add_node("node_b", node_b_function)
# エッジ追加
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
# エントリーポイント設定
builder.set_entry_point("node_a")
# または: builder.add_edge(START, "node_a")
# グラフをコンパイル
app = builder.compile()
# 実行
result = app.invoke({"messages": [HumanMessage(content="質問")]})
条件付きエッジ
def router_function(state: AgentState) -> str:
"""次のノードを決定する関数"""
last_msg = state["messages"][-1]
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
return "tools" # ツール呼び出しがある場合
return "end" # ない場合は終了
# 条件付きエッジ: ルーター関数の返り値でルーティング
builder.add_conditional_edges(
"llm_node",
router_function,
{
"tools": "tools_node",
"end": END
}
)
3. Tool Calling の仕組み
Function Calling から Tool Calling へ
LLMの進化により、ツール呼び出しプロトコルが標準化された。
LangChainは bind_tools() でOpenAI等のプロトコルを抽象化する。
@tool デコレーター による Tool 定義
from langchain_core.tools import tool
@tool
def search_travel_info(query: str) -> str:
"""旅行先に関する情報をWikivoyageコンテンツから検索する。"""
docs = retriever.invoke(query)
top = docs[:4] if isinstance(docs, list) else docs
return "\n---\n".join(d.page_content for d in top)
@tool(description="指定した都市の天気予報を返す。")
def weather_forecast(town: str) -> dict:
"""Get weather forecast for a town."""
forecast = WeatherForecastService.get_forecast(town)
if forecast is None:
return {"error": f"'{town}'の天気データがありません。"}
return forecast
効果的なツール説明の要件:
- いつ使うべきかを明確に記述
- 入力パラメーターと期待される出力を説明
- 類似ツールとの区別を明示
LLM へのツール登録
from langchain_openai import ChatOpenAI
llm_model = ChatOpenAI(
model="gpt-4o",
use_responses_api=True # Responses API を使用(推奨)
)
TOOLS = [search_travel_info, weather_forecast]
# ツールをLLMにバインド
llm_with_tools = llm_model.bind_tools(TOOLS)
ToolNode(プリビルドのツール実行ノード)
from langgraph.prebuilt import ToolNode
# プリビルドのToolNode(手動実装不要)
tools_execution_node = ToolNode(TOOLS)
内部では:
- LLMの最新メッセージから
tool_callsを抽出 - ツール名と引数でツールを呼び出し
- 結果を
ToolMessageとしてメッセージリストに追加
4. エージェントグラフの組み立て
LLM ノードの実装
from langchain_core.messages import SystemMessage
def llm_node(state: AgentState):
"""LLMがツール呼び出しか回答かを判断するノード"""
current_messages = state["messages"]
# システムメッセージでLLMの行動を制御(重要)
system_message = SystemMessage(content="""
あなたは旅行情報と天気予報を提供するアシスタントです。
情報取得には必ず提供されたツールを使用してください
(町名の特定を含む)。
""")
messages_with_system = list(current_messages) + [system_message]
response = llm_with_tools.invoke(messages_with_system)
return {"messages": [response]}
単一ツールエージェントのグラフ
from langgraph.prebuilt import tools_condition
# グラフ構築
builder = StateGraph(AgentState)
builder.add_node("llm_node", llm_node)
builder.add_node("tools", ToolNode(TOOLS))
# 条件付きエッジ(ツール呼び出しがあれば tools ノードへ、なければ終了)
builder.add_conditional_edges("llm_node", tools_condition)
# ツール実行後は LLM ノードに戻る
builder.add_edge("tools", "llm_node")
# エントリーポイント
builder.set_entry_point("llm_node")
# コンパイル
travel_agent = builder.compile()
エージェントグラフの実行フロー
ユーザー質問
↓
[llm_node]: LLMが質問を分析
│
├─ ツール呼び出しあり → [tools]: ToolNodeがツールを実行
│ ↓
│ [llm_node]: 結果を受け取り再推論
│ │
└─ ツール呼び出しなし → [END]: 最終回答を返す
チャットループ(REPL)の実装
from langchain_core.messages import HumanMessage
def chat_loop():
print("アシスタント('exit'で終了)")
while True:
user_input = input("You: ").strip()
if user_input.lower() in {"exit", "quit"}:
break
state = {"messages": [HumanMessage(content=user_input)]}
result = travel_agent.invoke(state)
last_msg = result["messages"][-1]
print(f"Assistant: {last_msg.content}\n")
if __name__ == "__main__":
chat_loop()
5. ReAct エージェント(プリビルドコンポーネント)
create_react_agent による簡易実装
グラフの手動構築を省略し、ReAct パターンを簡単に実装できる。
from langgraph.prebuilt import create_react_agent
from langgraph.managed.is_last_step import RemainingSteps
# AgentState に RemainingSteps を追加(prebuilt 使用時に推奨)
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
remaining_steps: RemainingSteps
# ReAct エージェントを作成(グラフ構築・ツールバインドが不要)
travel_agent = create_react_agent(
model=llm_model,
tools=TOOLS,
state_schema=AgentState,
prompt="""
あなたは旅行情報と天気予報を提供するアシスタントです。
情報取得には必ず提供されたツールを使用してください(町名の特定を含む)。
"""
)
手動実装 vs prebuilt の比較:
| 項目 | 手動実装 | create_react_agent |
|---|---|---|
| コード量 | 多い(グラフ構築必要) | 少ない |
| 可視性 | 高い(デバッグしやすい) | LangSmithで補完 |
| カスタマイズ性 | 高い | 制限あり |
| 推奨用途 | 独自ロジックが必要な場合 | 標準的なReActパターン |
6. マルチエージェント: Router パターン
概要
複数の専門エージェントを1つのルーターで管理する「シングルパスルーティング」。 各クエリは1つの専門エージェントにのみ転送される(「片道切符」)。
ユーザー質問 → Router Agent → 旅行情報エージェント OR 宿泊予約エージェント → 終了
Router Agent の実装
from enum import Enum
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, END
from langgraph.types import Command
class AgentType(str, Enum):
travel_info = "travel_info_agent"
accommodation = "accommodation_booking_agent"
class AgentTypeOutput(BaseModel):
agent: AgentType = Field(description="どのエージェントが処理すべきか?")
# 構造化出力でルーティング判断を強制(文字列パース不要)
llm_router = llm_model.with_structured_output(AgentTypeOutput)
ROUTER_PROMPT = """
あなたはルーターです。ユーザーのメッセージを以下のいずれかに分類してください:
- travel_info_agent: 旅行先、観光スポット、一般旅行情報に関する質問
- accommodation_booking_agent: ホテル、B&B、空室確認、価格に関する質問
ユーザーのメッセージに基づいて適切なエージェントを返してください。
"""
def router_agent_node(state: AgentState) -> Command[AgentType]:
"""ルーターノード: クエリをどのエージェントに転送するか決定"""
messages = state["messages"]
last_msg = messages[-1] if messages else None
if isinstance(last_msg, HumanMessage):
router_messages = [
SystemMessage(content=ROUTER_PROMPT),
HumanMessage(content=last_msg.content)
]
response = llm_router.invoke(router_messages)
agent_name = response.agent.value
return Command(update=state, goto=agent_name)
# デフォルト: 旅行情報エージェントへ
return Command(update=state, goto=AgentType.travel_info.value)
Router 多エージェントグラフの組み立て
from langgraph.graph import StateGraph, END
# グラフ構築
graph = StateGraph(AgentState)
graph.add_node("router_agent", router_agent_node)
graph.add_node("travel_info_agent", travel_info_agent)
graph.add_node("accommodation_booking_agent", accommodation_booking_agent)
# 専門エージェントから終了へのエッジ
graph.add_edge("travel_info_agent", END)
graph.add_edge("accommodation_booking_agent", END)
# エントリーポイントをルーターに設定
graph.set_entry_point("router_agent")
# コンパイル
travel_assistant = graph.compile()
注意: ルーター → 専門エージェントのエッジは Command オブジェクトで動的に決定されるため、明示的なエッジ定義は不要。
7. マルチエージェント: Supervisor パターン
概要
Supervisor が複数の専門エージェントを「ツール」として管理するパターン(「往復切符」)。 複数エージェントを組み合わせた複雑なリクエストを処理できる。
ユーザー複合質問
↓
Supervisor Agent
├─ → 旅行情報エージェント(検索)→ Supervisor(結果受け取り)
└─ → 宿泊予約エージェント(検索)→ Supervisor(統合) → 終了
Router vs Supervisor の違い:
| 観点 | Router | Supervisor |
|---|---|---|
| 呼び出し | 1回のみ(片道切符) | 複数回可能(往復切符) |
| 複合質問 | 対応不可 | 対応可能 |
| 実装複雑度 | 低 | 高 |
| 使用LLM | 軽量で可 | 高性能モデル推奨 |
Supervisor の実装
from langgraph_supervisor.supervisor import create_supervisor
from langchain_openai import ChatOpenAI
# 専門エージェント定義(必ず名前を付ける)
travel_info_agent = create_react_agent(
model=llm_model,
tools=TRAVEL_TOOLS,
state_schema=AgentState,
name="travel_info_agent",
prompt="旅行情報を検索して提供するアシスタントです。"
)
accommodation_agent = create_react_agent(
model=llm_model,
tools=BOOKING_TOOLS,
state_schema=AgentState,
name="accommodation_booking_agent",
prompt="ホテルとB&Bの空室情報を確認するアシスタントです。"
)
# Supervisor の作成(より高性能なモデルを使用)
travel_assistant = create_supervisor(
agents=[travel_info_agent, accommodation_agent],
model=ChatOpenAI(model="gpt-4o", use_responses_api=True),
supervisor_name="travel_assistant",
prompt="""
あなたは旅行情報エージェントと宿泊予約エージェントを管理するスーパーバイザーです。
複雑なリクエストには複数のエージェントを組み合わせて回答してください。
"""
).compile()
Supervisor の実行フロー例
複合質問:「天気の良い海辺の町を探して、その町のダブルルームの空室と価格も教えて」
Supervisor
└→ transfer_to_travel_info_agent(天気確認)
travel_info_agent
└→ search_travel_info(海辺の町を検索)
└→ weather_forecast(各町の天気確認)
└→ transfer_to_accommodation_booking_agent(空室確認)
accommodation_booking_agent
└→ check_bnb_availability(B&B空室確認)
└→ sql_db_query(ホテル空室確認)
└→ 最終回答統合
8. LangSmith によるトレーシング
設定
.env ファイルに追加:
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="<your-api-key>"
LANGSMITH_PROJECT="my-agent-project"
load_dotenv() で読み込み後、自動でトレーシングが有効化される。
トレースで確認できる情報
- LLMノードの入出力メッセージ
- どのツールを選択したか(ツール名・引数)
- ツールの実行結果
- 各ステップのレイテンシ・トークン数
- エージェント間のハンドオフ(Supervisorパターン)
デバッグのベストプラクティス
- ブレークポイントデバッグ: ツール関数・LLMノード・ToolsExecutionNodeにブレークポイントを設定して状態を確認
- messages の検査: 各ノードで
state["messages"]を確認し、LLMの推論過程を追う - tool_calls フィールド: LLMが返す
AIMessage.tool_callsでツール選択意図を確認 - LangSmith トレース: 複雑なマルチエージェントの実行フローはLangSmithで可視化
9. アーキテクチャ判断テーブル
エージェントアーキテクチャの選択
| 要件 | 推奨アーキテクチャ | 理由 |
|---|---|---|
| 単純な質問、1種類のツール | 単一エージェント(手動グラフ) | 実装が簡単、フロー把握しやすい |
| 複数ツール、ツール連鎖あり | ReAct エージェント(prebuilt) | LLMが自動でツール選択・連鎖 |
| 明確に分離できる2種類のドメイン | Router パターン | 各クエリを専門エージェントに転送 |
| 複合クエリ(複数ドメイン跨ぎ) | Supervisor パターン | 複数エージェントの協調が必要 |
| カスタムロジック・複雑な状態管理 | 手動 StateGraph | 最大の制御性・カスタマイズ性 |
ツール定義のベストプラクティス
| チェック項目 | 悪い例 | 良い例 |
|---|---|---|
| ツール名 | func1 | search_hotel_availability |
| description | なし | 「指定した都市のホテル空室と価格を検索する。」 |
| 戻り値の型 | str | dict or List[Dict] |
| エラーハンドリング | なし | {"error": "理由"} を返す |
ルーティングプロンプト設計
ルーターとSupervisorのプロンプトには以下を含める:
ROUTER_PROMPT = """
エージェント分類基準:
- travel_info_agent:
- 旅行先、観光スポット、アクティビティの質問
- 「〜について教えて」「〜でおすすめは?」
- 天気情報(旅行コンテキスト)
- accommodation_booking_agent:
- ホテル・B&Bの空室確認
- 宿泊料金の確認
- 「〜に泊まれますか?」「〜の値段は?」
エッジケース(分類が曖昧な場合):
- デフォルトは travel_info_agent
- 宿泊関連のキーワード(hotel, B&B, room, availability, price)があれば accommodation
"""
エージェント間のデータ受け渡し
# 悪い例: 自然言語でエージェント間通信
"ニューキーの天気は晴れで気温26度です。次にそこのホテルを探してください。"
# 良い例: 構造化データ(JSON)で受け渡し
{
"town": "Newquay",
"weather": "sunny",
"temperature": 26,
"action": "search_accommodation"
}
環境セットアップ
# 基本パッケージ
pip install langchain langgraph langchain-openai langchain-chroma
# Supervisor パターン使用時
pip install langgraph-supervisor
# トレーシング
pip install langsmith
# .env ファイル設定
OPENAI_API_KEY=<your-key>
LANGSMITH_API_KEY=<your-key>
LANGSMITH_TRACING=true
関連ファイル:
- → RAGパイプライン実装は
RAG-PIPELINE.md参照 - → MCPサーバー統合は
MCP-INTEGRATION.md参照 - → メモリ・ガードレール・本番化は
PRODUCTION.md参照 - → LangChain LCEL・チェーン基礎は
LANGCHAIN-CORE.md参照