第7章 LangGraph入門
更新日:2025年12月16日
1. LangGraphの概要
LangGraphは、LLMアプリケーションを有向グラフとして構築するためのフレームワークである。ノード(処理)とエッジ(遷移)を定義することで、複雑なワークフローやエージェントを表現できる。2024年初頭にリリースされ、LangChainエコシステムの中核コンポーネントとなっている。
1.1 グラフベースアーキテクチャ
LangGraphでは、アプリケーションを「状態」「ノード」「エッジ」の3要素で表現する。
Table 1. LangGraphの基本要素
| 要素 | 説明 | 役割 |
|---|---|---|
| State | グラフ全体で共有されるデータ | 会話履歴、中間結果の保持 |
| Node | 処理を行う関数 | LLM呼び出し、ツール実行 |
| Edge | ノード間の遷移 | 処理の流れを定義 |
Fig 1. グラフベースのエージェント構造
┌──────────┐
│ START │
└────┬─────┘
│
┌────▼─────┐
│ agent │ ←── LLMが判断
└────┬─────┘
│
┌─────┴─────┐
│ │
┌────▼────┐ ┌────▼────┐
│ tools │ │ END │
└────┬────┘ └─────────┘
│
└──────────→ agent へ戻る
1.2 vs LangChain Agent
従来のLangChain Agent(AgentExecutor)と比較して、LangGraphには以下の利点がある。
Table 2. LangChain Agent vs LangGraph
| 観点 | LangChain Agent | LangGraph |
|---|---|---|
| 制御フロー | 暗黙的(ループ) | 明示的(グラフ定義) |
| 状態管理 | 限定的 | 柔軟なState定義 |
| 人間介入 | 困難 | 組み込みサポート |
| 永続化 | 追加実装必要 | チェックポイント機能 |
| デバッグ | 困難 | 可視化・ステップ実行 |
LangGraphは、エージェントの動作をより細かく制御したい場合、複雑なワークフローを構築したい場合、本番環境での運用を見据えている場合に特に有効である。
2. 状態管理
LangGraphの中核機能は、グラフ全体で共有される状態(State)の管理である。各ノードは状態を読み取り、更新することで、処理間でデータを受け渡す。
2.1 StateGraph
StateGraphは、状態付きグラフを構築するためのクラスである。TypedDictまたはPydanticモデルで状態の型を定義する。
Fig 2. StateGraphの基本構造
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
from operator import add
# 状態の定義
class AgentState(TypedDict):
messages: Annotated[list, add] # メッセージリスト(追加で更新)
next_action: str # 次のアクション
# グラフの作成
graph = StateGraph(AgentState)
# ノードの追加
graph.add_node("agent", agent_node)
graph.add_node("tools", tools_node)
# エッジの追加
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", route_function)
graph.add_edge("tools", "agent")
# グラフのコンパイル
app = graph.compile()
2.2 State定義
状態の各フィールドには、更新方法を指定するReducerを設定できる。Annotatedと関数を組み合わせて定義する。
Table 3. 主要なReducerパターン
| Reducer | 動作 | 用途 |
|---|---|---|
| なし(デフォルト) | 上書き | 単一値の更新 |
| operator.add | リストに追加 | メッセージ履歴 |
| カスタム関数 | 任意のロジック | 複雑な更新処理 |
Fig 3. Reducerの動作例
from operator import add
from typing import Annotated
class State(TypedDict):
# 上書き更新
current_step: str
# リストへの追加
messages: Annotated[list, add]
# カスタムReducer(最大値を保持)
max_score: Annotated[float, lambda old, new: max(old, new)]
# ノードからの更新
def my_node(state: State) -> dict:
return {
"current_step": "step2", # 上書き
"messages": [new_message], # 既存リストに追加
"max_score": 0.95 # 現在値と比較して大きい方を保持
}
3. ノードとエッジ
ノードは状態を受け取り、更新を返す関数である。エッジはノード間の遷移を定義し、条件分岐も可能である。
Fig 4. ノードの実装例
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
model = ChatOpenAI(model="gpt-4o")
def agent_node(state: AgentState) -> dict:
"""エージェントノード: LLMを呼び出して次のアクションを決定"""
messages = state["messages"]
response = model.invoke(messages)
return {
"messages": [response],
"next_action": determine_action(response)
}
def tools_node(state: AgentState) -> dict:
"""ツールノード: ツールを実行して結果を返す"""
last_message = state["messages"][-1]
tool_calls = last_message.tool_calls
results = []
for call in tool_calls:
result = execute_tool(call)
results.append(result)
return {"messages": results}
Fig 5. 条件付きエッジの実装
def route_function(state: AgentState) -> str:
"""次に遷移するノードを決定"""
last_message = state["messages"][-1]
# ツール呼び出しがある場合
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
# 終了条件
return END
# 条件付きエッジの追加
graph.add_conditional_edges(
"agent", # 元のノード
route_function, # ルーティング関数
{
"tools": "tools", # "tools" → toolsノードへ
END: END # END → 終了
}
)
4. 実践:エージェント構築
実際にLangGraphを使用してReActエージェントを構築する例を示す。
Fig 6. 完全なReActエージェントの実装
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from typing import TypedDict, Annotated
from operator import add
# 状態の定義
class AgentState(TypedDict):
messages: Annotated[list, add]
# ツールの定義
@tool
def search(query: str) -> str:
"""Web検索を実行"""
return f"'{query}'の検索結果: ..."
@tool
def calculator(expression: str) -> str:
"""数式を計算"""
return str(eval(expression))
tools = [search, calculator]
# モデルの設定(ツールをバインド)
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)
# ノードの定義
def agent(state: AgentState):
response = model.invoke(state["messages"])
return {"messages": [response]}
tool_node = ToolNode(tools)
# ルーティング関数
def should_continue(state: AgentState):
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return END
# グラフの構築
graph = StateGraph(AgentState)
graph.add_node("agent", agent)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue)
graph.add_edge("tools", "agent")
# コンパイル
app = graph.compile()
# 実行
result = app.invoke({
"messages": [HumanMessage(content="2024年の東京オリンピックについて調べて")]
})
print(result["messages"][-1].content)
Fig 7. ストリーミング実行
# ストリーミングで各ステップを確認
for event in app.stream({"messages": [HumanMessage(content="質問")]}):
for node_name, output in event.items():
print(f"--- {node_name} ---")
print(output)
Fig 8. チェックポイントによる永続化
from langgraph.checkpoint.sqlite import SqliteSaver
# チェックポイントの設定
with SqliteSaver.from_conn_string(":memory:") as checkpointer:
app = graph.compile(checkpointer=checkpointer)
# スレッドIDを指定して実行
config = {"configurable": {"thread_id": "user-123"}}
# 最初の実行
result1 = app.invoke({"messages": [HumanMessage(content="こんにちは")]}, config)
# 同じスレッドで継続(履歴が保持される)
result2 = app.invoke({"messages": [HumanMessage(content="続きを教えて")]}, config)
チェックポイント機能を使用することで、会話の状態を永続化し、中断・再開が可能になる。本番環境では、PostgreSQLやRedisなどのバックエンドを使用する。
本コンテンツは2025年12月時点の情報に基づいて作成されています。LangGraphは活発に開発が進んでおり、APIが変更される可能性があります。最新情報は公式ドキュメント(https://langchain-ai.github.io/langgraph/)をご確認ください。