LangGraph入門2026|Pythonでエージェントを構築する全手順
目次
LangGraphは、LLMアプリケーションに「制御フロー」を持ち込むフレームワークだ。LangChainが個々のLLM呼び出しを抽象化するのに対し、後者は呼び出しの順序・分岐・ループ・中断と再開をグラフ構造で記述する。
一本道のチェーンでは限界が来る。「ツールの実行結果に応じて次のアクションを変えたい」「人間の承認を挟んでからメールを送りたい」「3回リトライしてダメなら別の戦略に切り替えたい」。こうした制御フローの要求に、LangChainの | パイプだけでは応えられない。
この記事では、LangGraphのインストールからStateGraphの構築、ツール呼び出しAgent、Human-in-the-Loopの実装までをPythonコード付きで進める。公式ドキュメント(v1.0系)の最新APIに準拠しているため、古い記事のコードが動かないという問題にも直面しない。
LangGraphが解決する問題 -- LangChainだけでは足りない理由
まず結論。LangGraphを選ぶべき状況は「LLMの出力に応じてフローが分岐する」ときだ。逆に、固定のプロンプト → LLM → パースという一直線の処理ならLangChainのLCELで十分。
| 判断軸 | LangChain (LCEL) | LangGraph |
|---|---|---|
| 処理フロー | 一方向のチェーン | 分岐・ループ・合流を含むグラフ |
| 状態管理 | 入出力の受け渡しのみ | グラフ全体で共有するState |
| 人間の介入 | 対応なし | interrupt / Command で一時停止・再開 |
| 状態の永続化 | 対応なし | Checkpointer (SQLite, PostgreSQL等) |
| 代表的ユースケース | RAG、要約、翻訳 | マルチステップAgent、承認フロー、自律型タスク |
LangChain社自身が「エージェントを作るならLangGraphを使え」と明言している。公式ドキュメントのAgent関連ページは2024年後半から順次LangGraphへのリダイレクトに切り替わった。もはやLangChain単体でAgentを組むコード例は公式から消えている。
ただし依存関係を誤解しやすいので補足しておく。実はLangChainなしでも動く。ChatModelやtoolデコレータを使うときにLangChainのパッケージが必要になるだけで、StateGraphやCheckpointerは単体で動く機能だ。
LangChain記事との接続
LangChainのLCEL・RAG・基本的なAgent構築についてはLangChain入門2026|RAG・Agent実装の最短ルートで扱っている。LangGraphに進む前にLCELの基本を押さえておくと理解が速い。
環境構築: 3行で動く最小セットアップ
前置きなしでいく。Python 3.11以上の仮想環境を前提とする。
pip install langgraph langchain-anthropic langchain-community
langchain-anthropicはClaudeをLLMとして使う場合のパッケージ。OpenAIを使うならlangchain-openaiに差し替える。LangGraph本体はlanggraphの1パッケージに集約されている。
APIキーの設定。
# Claude (Anthropic) の場合
export ANTHROPIC_API_KEY="sk-ant-..."
# OpenAI の場合
export OPENAI_API_KEY="sk-..."
動作確認は次のセクションのコードで兼ねる。別途「Hello World」を挟む必要はない。
State・Node・Edge -- グラフの3要素を手で組む
LangGraphのグラフは、3つの部品だけで成り立つ。
| 要素 | 役割 | 日常の比喩 |
|---|---|---|
| State | グラフ全体で共有するデータ構造 | 回覧板。各工程で書き加え、次に渡す |
| Node | Stateを受け取り、更新を返す関数 | 工場の各工程。組み立て、検査、梱包 |
| Edge | ノード間の接続(固定 or 条件分岐) | ベルトコンベア。次の工程への搬送路 |
Stateの定義。TypedDictを使う。クラスは不要。
from typing import TypedDict
class Essay(TypedDict):
topic: str
content: str | None
score: float | None
Nodeは関数。それだけだ。引数でStateを受け取り、辞書で更新分だけを返す。変更したいキーだけでいい。
def write_essay(state: Essay):
# topic を受け取って content を生成
return {"content": f"{state['topic']}に関するエッセイ本文..."}
def score_essay(state: Essay):
# content を受け取って score を付与
return {"score": 8.5}
最後にEdge。add_edgeでノード間をつなぐだけだ。STARTとENDはエントリと終了を示す特殊定数で、どのグラフにも登場する。
from langgraph.graph import StateGraph, START, END
builder = StateGraph(Essay)
builder.add_node(write_essay)
builder.add_node(score_essay)
builder.add_edge(START, "write_essay")
builder.add_edge("write_essay", "score_essay")
builder.add_edge("score_essay", END)
graph = builder.compile()
compile()を呼ぶと実行可能なグラフオブジェクトが返る。あとはgraph.invoke()に初期Stateを渡すだけ。実際にやってみる。
最初のグラフ: エッセイ採点ワークフロー
「テーマを受け取る → エッセイを書く → 採点する」。LLMは使わず、まずグラフの動きそのものを掴む。
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class Essay(TypedDict):
topic: str
content: str | None
score: float | None
def write_essay(state: Essay):
return {"content": f"{state['topic']}に関する800字のエッセイ"}
def score_essay(state: Essay):
word_count = len(state["content"] or "")
score = min(10.0, word_count / 20)
return {"score": score}
builder = StateGraph(Essay)
builder.add_node(write_essay)
builder.add_node(score_essay)
builder.add_edge(START, "write_essay")
builder.add_edge("write_essay", "score_essay")
builder.add_edge("score_essay", END)
graph = builder.compile()
# 実行
result = graph.invoke({"topic": "LangGraphの設計思想", "content": None, "score": None})
print(result)
# {'topic': 'LangGraphの設計思想', 'content': 'LangGraphの設計思想に関する800字のエッセイ', 'score': 1.05}
動いた。invoke()は同期実行で、全ノードを順に処理して最終Stateを返す。
ここで1つ気づくことがある。各ノードはStateの全体を受け取るが、返す辞書には変更するキーだけを含めればいい。write_essayはcontentだけ、score_essayはscoreだけ。フレームワークが既存のStateにマージしてくれる。
条件分岐を加えるには
add_edgeの代わりにadd_conditional_edgesを使う。ルーティング関数がStateを見て次のノード名を文字列で返す仕組みだ。「スコアが7未満なら書き直し」のようなループも、条件分岐エッジ1本で表現できる。次のセクションで実際に使う。
ツール呼び出しエージェント: ReActパターン実装
ここからが本題。LLMにツールを渡し、「呼ぶかどうかをLLM自身が判断 → 呼んだ結果をLLMに戻す → また判断」というReActループを組む。
ツール定義 → LLMバインド → 条件分岐ルーターの3ステップで組む。
from typing import Literal
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
from langgraph.graph import MessagesState, StateGraph, START, END
# --- 1. ツール定義 ---
@tool
def add(a: int, b: int) -> int:
"""2つの整数を足す"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""2つの整数を掛ける"""
return a * b
tools = [add, multiply]
tools_by_name = {t.name: t for t in tools}
# --- 2. LLMにツールをバインド ---
llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
llm_with_tools = llm.bind_tools(tools)
# --- 3. ノード定義 ---
def llm_call(state: MessagesState):
"""LLMを呼び出し、ツール呼び出しの有無を含む応答を返す"""
return {"messages": [llm_with_tools.invoke(state["messages"])]}
def tool_executor(state: MessagesState):
"""LLMが要求したツールを実行し、結果をToolMessageで返す"""
results = []
for tc in state["messages"][-1].tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
results.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
return {"messages": results}
# --- 4. 条件分岐 ---
def should_continue(state: MessagesState) -> Literal["tool_executor", "__end__"]:
last = state["messages"][-1]
return "tool_executor" if last.tool_calls else END
# --- グラフ組み立て ---
builder = StateGraph(MessagesState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_executor", tool_executor)
builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tool_executor", END])
builder.add_edge("tool_executor", "llm_call") # ツール実行後、LLMに戻る
agent = builder.compile()
# 実行
result = agent.invoke({"messages": [HumanMessage(content="3と7を足して、その結果に5を掛けて")]})
print(result["messages"][-1].content)
MessagesStateは組み込みのState型。messagesキーにadd_messagesリデューサーが設定済みで、メッセージが自動追記される。これだけで十分だ。
should_continue関数に注目してほしい。たった3行。だがこの分岐こそが、LangChainのパイプでは書けなかった制御だ。LLMの出力を見て「もう1周するかここで止めるか」を判断している。パイプ演算子の | には「条件によって接続先を変える」という概念がそもそもない。
筆者も初めてこのパターンを書いたとき、無限ループに落ちた。原因は単純で、LLMがツール呼び出しを止めないプロンプト設計だった。should_continueの条件が正しくても、LLMが毎回ツールを呼び続ければ永遠に回る。ツールのdocstringを「〜する。結果が得られたら返答せよ」と具体的に書くことで解消した。
ただしループ制御だけでは解決できない問題がある。人間が判断を挟む必要があるケースだ。
Human-in-the-Loop: 人間が承認を挟むワークフロー
日本語の入門記事でほとんど扱われていない領域がある。Human-in-the-Loop(HITL)だ。自律型エージェントを本番に載せるとき、「メール送信前に人間が確認する」「課金APIの前に承認を取る」。こうした要件は避けて通れない。
部品は3つだ。
- Checkpointer -- グラフの状態をDBに保存する。
InMemorySaver(開発用)かSqliteSaver(永続化)を選ぶ interrupt()-- ノード内で呼ぶとグラフが一時停止する。引数に渡したデータが中断理由として外部に返るCommand(resume=...)-- 一時停止したグラフを再開する。人間の判断結果を渡せる
メール送信前の承認フローを実装する。
import sqlite3
from typing import TypedDict
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
from langchain_anthropic import ChatAnthropic
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.types import Command, interrupt
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""メールを送信する(送信前に人間の承認が必要)"""
# ここでグラフが一時停止する
response = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"body": body,
"message": "このメールを送信しますか?",
})
if response.get("action") == "approve":
final_to = response.get("to", to)
final_subject = response.get("subject", subject)
return f"送信完了: {final_to} / {final_subject}"
return "送信キャンセル"
llm = ChatAnthropic(model="claude-sonnet-4-6").bind_tools([send_email])
def agent_node(state: MessagesState):
return {"messages": [llm.invoke(state["messages"])]}
def tool_node(state: MessagesState):
results = []
for tc in state["messages"][-1].tool_calls:
result = send_email.invoke(tc["args"])
results.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
return {"messages": results}
def should_continue(state: MessagesState):
last = state["messages"][-1]
return "tool_node" if getattr(last, "tool_calls", None) else END
builder = StateGraph(MessagesState)
builder.add_node("agent", agent_node)
builder.add_node("tool_node", tool_node)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "agent")
# Checkpointer付きでコンパイル(これがないとinterruptが使えない)
checkpointer = SqliteSaver(sqlite3.connect("email_approval.db"))
agent = builder.compile(checkpointer=checkpointer)
# --- 実行 ---
config = {"configurable": {"thread_id": "email-001"}}
# Step 1: エージェントが動く → send_email内のinterruptで停止
result = agent.invoke(
{"messages": [HumanMessage(content="田中さん([email protected])に会議の件でメールして")]},
config=config,
)
print("承認待ち:", result["__interrupt__"])
# Step 2: 人間が内容を確認し、承認(件名を修正して再開)
resumed = agent.invoke(
Command(resume={"action": "approve", "subject": "【4/10】定例会議のご案内"}),
config=config,
)
print(resumed["messages"][-1].content)
thread_idは手紙の宛名のようなものだ。同じthread_idで投げれば、昨日の続きとして扱われる。CheckpointerがState全体をSQLiteに書き出しているため、Pythonプロセスが死んでも関係ない。
これは地味だが強力な設計だ。interruptのタイミングでサーバーを再起動しようが、翌日に別のプロセスからCommand(resume=...)を投げても中断時点から再開する。金曜に上司の承認待ちで止めて、月曜に承認ボタンを押す。そういうフローが自然に組める。
注意: Checkpointerなしではinterruptが使えない
builder.compile()にcheckpointerを渡さずにinterrupt()を呼ぶと、RuntimeErrorが出る。開発中はInMemorySaver()を使い、本番ではSqliteSaverかPostgresSaverを選ぶ。
Graph API vs Functional API -- どちらを選ぶか
グラフを組む方法は2つある。
1つ目はここまで使ってきたStateGraphベースのGraph API。もう1つがデコレータベースのFunctional APIだ。Functional APIで先ほどのReActエージェントを書き直すとどう変わるか。
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, BaseMessage
from langchain_anthropic import ChatAnthropic
from langgraph.graph import add_messages
from langgraph.func import entrypoint, task
@tool
def add(a: int, b: int) -> int:
"""2つの整数を足す"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""2つの整数を掛ける"""
return a * b
tools = [add, multiply]
tools_by_name = {t.name: t for t in tools}
llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0).bind_tools(tools)
@task
def call_llm(messages: list[BaseMessage]):
return llm.invoke(messages)
@task
def call_tool(tool_call):
return tools_by_name[tool_call["name"]].invoke(tool_call)
@entrypoint()
def agent(messages: list[BaseMessage]):
llm_response = call_llm(messages).result()
while True:
if not llm_response.tool_calls:
break
# ツール呼び出しを並列実行
tool_futures = [call_tool(tc) for tc in llm_response.tool_calls]
tool_results = [f.result() for f in tool_futures]
messages = add_messages(messages, [llm_response, *tool_results])
llm_response = call_llm(messages).result()
return add_messages(messages, llm_response)
# 実行
result = agent.invoke([HumanMessage(content="3と7を足して、その結果に5を掛けて")])
print(result[-1].content)
StateGraphもadd_nodeもadd_edgeも登場しない。Pythonのwhileとifでフローを制御し、@taskで非同期実行の単位を切る。
自分ならどちらを選ぶか。判断基準は1つ。分岐が2つ以上あるかどうか。
| 状況 | 推奨API | 理由 |
|---|---|---|
| シンプルなReActループ | Functional API | whileループで済む。グラフ定義は冗長 |
| 複数の条件分岐・並列パス | Graph API | フロー図としての可読性が高い。可視化もしやすい |
| Human-in-the-Loop | どちらでも | interrupt / Commandは両APIで共通 |
| マルチエージェント(Supervisor構成) | Graph API | サブグラフの合成が必要になる |
内部は両API共通。同じPregelランタイム上で動き、Checkpointer・ストリーミング・LangSmith連携もすべて使える。パフォーマンス差はゼロ。選択は見通しだけの話だ。
よくある質問
Q. LangGraphは無料で使えるか?
OSS版は完全無料(MITライセンス)。LangGraph Platform(ホスティング・モニタリング)は別サービスで有料プランがある。
Q. LangChainを知らなくても使えるか?
使える。StateGraphやCheckpointerは単体で動く機能だ。ただしChatModelやtoolデコレータを使う場面ではLangChainのパッケージをインポートするため、LCEL(パイプ演算子|での連結)の基本を知っておくとスムーズ。LangChain入門記事で30分あれば押さえられる。
Q. 古い記事のコードが動かない。バージョン互換は?
LangGraphはv0.x系からv1.0でAPIの破壊的変更があった。MessageGraphは廃止されStateGraph(MessagesState)に統一。ToolInvocationはToolCallに改名。2025年10月以前の記事のコードはそのままでは動かないことが多い。この記事のコードはv1.0系(1.0.3以降)に準拠している。
Q. デバッグが難しい。ノードの実行順を確認する方法は?
stream_mode="updates"を使うとノードごとの出力を逐次確認できる。LangSmithを有効にすれば、各ノードの入出力・トークン消費・レイテンシがトレースとして記録される。無料枠でも十分使える。
Q. CrewAIやAutoGenとの違いは?
抽象度が違う。CrewAIやAutoGenは「ロール(役割)を持つエージェント間の会話」を設計する高レベルフレームワーク。LangGraphはその下のレイヤーで、ノードとエッジで自由にワークフローを組む低レベルオーケストレーター。LangGraphはノード単位でロジックを書けるため、CrewAIでは不可能な条件分岐やリトライも自在に組める。一方、3人のエージェントに順番に話させるだけならCrewAIが10行で片付く。
まとめ
- State・Node・Edgeの3要素でグラフを構築する基本パターン
- 条件分岐エッジでLLMの出力に応じたフロー制御
- ReActエージェント: ツール呼び出しループの実装
- Human-in-the-Loop: interrupt / Command / Checkpointerによる承認フロー
- Graph API vs Functional API: 分岐の数で使い分ける
LangChainで一直線の処理を書いていて「ここで分岐したい」「ここで止めたい」と感じた瞬間が、LangGraphの出番だ。should_continueの戻り値をEND固定にして動かすと、ループしない最小形が手元で確認できる。その状態から条件を1つ足すたびに挙動が変わる。それが一番早い理解ルートだ。
関連記事
-
LangChain入門2026|RAG・Agent実装の最短ルート
LCELの基本からRAG実装まで。LangGraphに進む前の土台固めに。
-
AIエージェント完全ガイド2026|ビジネス活用から導入ステップまで
エージェントの全体像とビジネス活用パターンを把握したい場合。
-
Claude Codeの使い方2026|導入・設定・Cursor比較の実践ガイド
Claude Sonnet/Opusを開発ツールとして活用する方法。
-
Python独学ロードマップ2026|132時間で基礎習得
Python自体が初めての場合のロードマップ。LangGraphはPython 3.11以上が必要。
-
AIネイティブ開発完全ガイド|5大ツール比較と実践ワークフロー
LangGraph以外のAI開発ツール群を俯瞰的に比較。
参考書籍
LangGraphの文献はまだ薄い。LangChain側から入るなら以下が現状では一番まとまっている。
- LangChain完全入門 -- 生成AIアプリケーション開発がわかる本 -- LangChainの基礎を日本語で学べる。LangGraphの前段階として。