LangGraphとは?
StateGraphの書き方・人間承認・
永続化まで完全解説【2026年】
LangChainだけでは組めない処理がある。「条件によって処理を分岐させる」「失敗したら前のステップに戻る」「実行を途中で止めて人間の承認を待つ」——この3つが必要になった瞬間が、LangGraphの出番だ。2025年10月のv1.0で永続状態管理が実装され、長時間動作するエージェントを本番で運用できる基盤が整った。本記事では最小のStateGraphから、Human-in-the-loop・状態永続化までをコード付きで解説する。
1. 結論——LangChainとどう使い分けるか
両者は競合ではなく、役割が違う。v1.0以降は「LangChain=構築、LangGraph=実行、LangSmith=運用」という3層構造に整理された。
| やりたいこと | 使うべきもの |
|---|---|
| 処理を一直線につなげる(入力→加工→出力) | LangChainのみで十分 |
| ツールを呼びながら回答を作る単純なエージェント | LangChainのcreate_agent |
| 条件によって次の処理を変えたい | LangGraph |
| 失敗したら前のステップに戻ってやり直したい | LangGraph |
| 実行を止めて人間の承認を待ちたい | LangGraph |
| 複数のエージェントを協調させたい | LangGraph |
| 長時間動作し、中断・再開が必要な処理 | LangGraph(永続状態管理) |
2. LangGraphとは何か——なぜグラフなのか
LangGraph(ランググラフ)は、AIエージェントのワークフローをグラフ構造で表現・実行・管理するOSSフレームワークだ。LangChainと同じく2025年10月22日にv1.0が正式リリースされ、PythonとTypeScriptに対応している。
なぜ「グラフ」なのか
従来のChain(チェーン)は、ベルトコンベアのように処理を一本道でつなぐ考え方だった。しかし現実のエージェント処理はこうならない。
質問を受け取る
↓
社内文書を検索する
↓
検索結果は質問に答えられる内容か? ←── 判定する
├─ Yes → 回答を生成する
└─ No → Web検索に切り替える → 再度判定へ戻る
↑ ループ
「判定して分岐する」「条件次第で前に戻る」——これを一本道のChainで書くと、途端に複雑になる。グラフ構造(ノードとエッジ)で表現すれば、この流れをそのままコードに落とせる。これがLangGraphの存在理由だ。
3. 4つの基本概念
State
グラフ全体で共有される状態。会話履歴・検索結果・判定フラグなどを保持する
Node
処理の1単位。Stateを受け取り、更新した内容を返す関数として書く
Edge
ノード間のつながり。条件に応じて行き先を変える「条件付きエッジ」もある
Checkpointer
状態の保存先。中断・再開や、人間の承認待ちを実現する土台になる
StateGraphが基本形
LangGraphには複数のグラフ型があるが、2026年時点の新規開発ではStateGraphを前提にするのが安全だ。
| グラフ型 | 用途 | 2026年の位置づけ |
|---|---|---|
| StateGraph | 状態を共有しながらノード間を遷移する | これが基本。条件分岐が少しでも想定されるならこれ |
| Graph | 共有Stateが不要な軽量フロー | 明確な入出力変換のみの軽量ワークフロー向け |
| MessageGraph | メッセージ列を扱う旧式 | 旧式。新規開発では使わない |
4. 最小のStateGraphを書く
まずは最小構成。Stateを定義し、ノードを追加し、エッジでつないでコンパイルする——これだけだ。
from langgraph.graph import StateGraph, START, END
# 1. Stateを定義する(グラフ全体で共有されるデータ)
class State(TypedDict):
question: str
answer: str
# 2. ノードを関数として書く(Stateを受け取り、更新分を返す)
def think(state: State) -> dict:
return {“answer”: f”「{state[‘question’]}」について考えました”}
def polish(state: State) -> dict:
return {“answer”: state[“answer”] + “(整形済み)”}
# 3. グラフを組み立てる
builder = StateGraph(State)
builder.add_node(“think”, think)
builder.add_node(“polish”, polish)
builder.add_edge(START, “think”)
builder.add_edge(“think”, “polish”)
builder.add_edge(“polish”, END)
graph = builder.compile()
# 4. 実行
result = graph.invoke({“question”: “LangGraphとは?”})
print(result[“answer”])
Reducerで累積させる
会話履歴のように「上書きではなく追加していきたい」項目には、Reducer(リデューサー)を使う。
import operator
class State(TypedDict):
question: str
# operator.add で、返り値がリストに追加されていく
logs: Annotated[list[str], operator.add]
def step_a(state: State) -> dict:
return {“logs”: [“step_a を実行”]}
def step_b(state: State) -> dict:
return {“logs”: [“step_b を実行”]}
# 実行後、state[“logs”] は [“step_a を実行”, “step_b を実行”] になる
5. 条件分岐を実装する
LangGraphの本領はここからだ。add_conditional_edgesで、状態に応じて次のノードを動的に決められる。
# 検索結果が十分かをLLMに判定させる想定
is_enough = len(state[“documents”]) >= 2
return {“is_relevant”: is_enough}
# 判定結果に応じて行き先を返す関数
def route(state: State) -> str:
if state[“is_relevant”]:
return “generate” # 十分 → 回答生成へ
return “web_search” # 不十分 → Web検索へ
builder.add_node(“grade”, grade)
builder.add_node(“generate”, generate)
builder.add_node(“web_search”, web_search)
# 条件付きエッジ:routeの返り値でノードを切り替える
builder.add_conditional_edges(
“grade”,
route,
{
“generate”: “generate”,
“web_search”: “web_search”,
},
)
# Web検索したら、もう一度判定に戻す(ループ)
builder.add_edge(“web_search”, “grade”)
recursion_limitの設定と、リトライ回数をStateで数える仕組みを必ず入れてほしい。API課金の考え方はAIコスト最適化完全ガイドも参照。graph.invoke(input_data, config={“recursion_limit”: 10})
6. Human-in-the-loop——AIを途中で止める
法人でAIエージェントを本番運用するなら、この機能が実質的な必須要件になる。メール送信・DB更新・外部発注のような「取り返しのつかない操作」を、AIに単独で実行させるわけにはいかない。
LangGraphではinterrupt()を使い、実行を任意の地点で停止して人間の判断を待てる。
from langgraph.checkpoint.memory import MemorySaver
def send_email(state: State) -> dict:
# 送信前に人間の承認を要求して停止する
approval = interrupt({
“action”: “メール送信”,
“to”: state[“recipient”],
“body”: state[“draft”],
})
if approval == “approve”:
# 実際の送信処理
return {“status”: “送信しました”}
return {“status”: “キャンセルしました”}
# Checkpointerがないと中断・再開できない点に注意
graph = builder.compile(checkpointer=MemorySaver())
config = {“configurable”: {“thread_id”: “user-123”}}
# 1回目の実行 → interrupt で停止する
graph.invoke({“recipient”: “client@example.com”}, config=config)
# 人間が内容を確認した後、承認して再開する
graph.invoke(Command(resume=”approve”), config=config)
thread_idが会話・タスクの識別子になる。同じthread_idで再度invokeすると、中断した地点から処理が再開される。これがLangGraphの「止めて、待って、続きから動かす」を成立させている仕組みだ。どこで止めるべきか
| 操作 | 人間承認 | 理由 |
|---|---|---|
| 社内文書の検索・要約 | 不要 | 読み取り専用で副作用がない |
| 下書きの作成 | 不要 | 送信しなければ影響がない |
| メール・チャットの送信 | 必須 | 取り消せない。誤送信の影響が大きい |
| データベースの更新・削除 | 必須 | データ破壊のリスク |
| 決済・発注・契約に関わる操作 | 必須 | 金銭的損害に直結する |
| 外部APIへの書き込み | 推奨 | 相手先システムへの影響が読めない |
7. 【v1.0の目玉】状態の永続化
v1.0で実装された永続状態管理(Durable state)により、エージェントの状態が自動保存され、再開できるようになった。長時間動作するエージェントや、中断・再開が必要なワークフローを安定して運用できる。
| Checkpointer | 保存先 | 用途 |
|---|---|---|
| MemorySaver | メモリ上 | 開発・検証用。プロセスを落とすと消える |
| SqliteSaver | SQLiteファイル | 小規模・単一サーバーでの運用 |
| PostgresSaver | PostgreSQL | 本番運用の標準。複数サーバーからアクセス可能 |
DB_URI = “postgresql://user:password@localhost:5432/langgraph”
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
checkpointer.setup() # 初回のみ:必要なテーブルを作成
graph = builder.compile(checkpointer=checkpointer)
config = {“configurable”: {“thread_id”: “session-001”}}
graph.invoke({“question”: “…”}, config=config)
# 過去の実行履歴を取得できる
for snapshot in graph.get_state_history(config):
print(snapshot.values)
8. 実践:Agentic RAGを構築する
ここまでの要素を組み合わせると、「検索結果を自己評価し、不十分なら検索し直す」RAGが作れる。通常のRAGとの違いは、AIが自分の検索結果を判定して行動を変える点だ。
from typing import Annotated
import operator
from langgraph.graph import StateGraph, START, END
class RagState(TypedDict):
question: str
documents: list
answer: str
retry_count: Annotated[int, operator.add]
def retrieve(state: RagState) -> dict:
docs = retriever.invoke(state[“question”])
return {“documents”: docs}
def grade(state: RagState) -> dict:
# LLMに「この文書で質問に答えられるか」をYes/Noで判定させる
context = “\n”.join(d.page_content for d in state[“documents”])
verdict = model.invoke(
f”以下の資料で質問に答えられますか。yes か no のみで答えてください。\n”
f”【質問】{state[‘question’]}\n【資料】{context[:2000]}”
).content.strip().lower()
return {“is_relevant”: verdict.startswith(“yes”)}
def rewrite(state: RagState) -> dict:
# 検索にヒットしやすい表現へ質問を言い換える
new_q = model.invoke(
f”次の質問を、社内文書の検索にヒットしやすい表現へ言い換えてください。”
f”言い換えた質問のみを出力してください。\n{state[‘question’]}”
).content
return {“question”: new_q, “retry_count”: 1}
def generate(state: RagState) -> dict:
context = “\n”.join(d.page_content for d in state[“documents”])
ans = model.invoke(
f”以下の資料のみを根拠に答えてください。記載がなければ「資料に記載がありません」と答えてください。\n”
f”【資料】{context}\n【質問】{state[‘question’]}”
).content
return {“answer”: ans}
def route(state: RagState) -> str:
if state.get(“is_relevant”):
return “generate”
if state.get(“retry_count”, 0) >= 3: # 無限ループ防止
return “generate”
return “rewrite”
builder = StateGraph(RagState)
builder.add_node(“retrieve”, retrieve)
builder.add_node(“grade”, grade)
builder.add_node(“rewrite”, rewrite)
builder.add_node(“generate”, generate)
builder.add_edge(START, “retrieve”)
builder.add_edge(“retrieve”, “grade”)
builder.add_conditional_edges(“grade”, route,
{“generate”: “generate”, “rewrite”: “rewrite”})
builder.add_edge(“rewrite”, “retrieve”) # 言い換えて再検索
builder.add_edge(“generate”, END)
graph = builder.compile()
retry_count >= 3で強制的にgenerateへ抜ける設計がポイントだ。「答えが見つからないときに、いつ諦めるか」を明示するのが、本番で動くエージェントと暴走するエージェントの分かれ目になる。RAGの精度をチャンク設計から詰めたい場合はAIヘルプデスクおすすめ比較|RAGとハルシネーションの正確な理解も参照してほしい。9. LangGraph Studioでデバッグする
グラフ構造は、コードだけ見ても流れが追いづらい。LangGraph Studioを使えば、ノードの遷移とStateの変化を視覚的に確認できる。
# プロジェクトルートで実行するとStudioが起動する
langgraph dev
Studio上では次のことができる。
- グラフ構造の可視化(どのノードからどこへ遷移するか)
- ステップごとのState変化の確認
- 任意のノードから実行を再開
- interruptで停止した箇所での承認・却下の操作
10. 本番運用で押さえるべき設計
① 無限ループを構造的に防ぐ
recursion_limitの設定に加え、State内にリトライ回数を持たせて上限で強制脱出させる。ループの終了条件は、LLMの判定だけに委ねてはいけない。判定が常にNoを返す入力が来た瞬間に、課金が止まらなくなる。
② Checkpointerは最初からPostgresを想定する
MemorySaverで作り込んでから本番移行時に差し替えると、thread_id設計や同時実行の考慮漏れが表面化しやすい。スケールを見込むなら早めにPostgresSaverで検証しておく。
③ 副作用のある操作は必ずinterruptで挟む
第6章の表の通り、送信・更新・削除・決済はHuman-in-the-loopの対象だ。「AIが賢くなったから承認は不要」という判断はしない。賢さと、取り返しのつかなさは別の問題である。
④ 過剰にグラフ化しない
これが最も多い失敗だ。分岐が1つもない処理をStateGraphで書くと、素直に関数を並べるより読みにくくなるだけだ。LangGraphが効くのは「分岐・ループ・中断」のいずれかが本当に必要な場合に限られる。要件がシンプルならLangChainのcreate_agentで十分である。
メリット・デメリット
- 条件分岐・ループを自然にコードで表現できる
- interrupt()で人間の承認を組み込める
- v1.0の永続状態管理で中断・再開が安定
- Studioで遷移とStateを視覚的にデバッグできる
- マルチエージェント構成を組みやすい
- OSSで無料。処理を完全に自社制御できる
- 学習コストが高い(State設計の理解が前提)
- 単純な処理には明確にオーバースペック
- 無限ループ設計を誤るとAPI料金が膨らむ
- 本番運用にはDB(Postgres)の準備が必要
- v0.x時代の情報が多く、鮮度の見極めが要る
- ノーコードで扱いたい非エンジニアには不向き
11. よくある質問
recursion_limitを設定する(デフォルトは25)、②Stateにリトライ回数を持たせ、上限に達したら強制的に終了ノードへ抜ける分岐を書く。LLMの判定結果だけをループの終了条件にするのは危険です。LangGraphの実装で難しいのは、コードよりも「どこで止めるか」「どう諦めるか」の設計です。人間承認の設計、無限ループの防止、状態永続化を含めた本番運用の設計は、LIFRELLのAIマーケティング相談をご活用ください。各種AI・SaaSツールを実際に有料契約して検証している立場から、現場で回る形に落とし込みます。
社内での生成AI定着から着手したい場合は生成AIeラーニング研修おすすめ完全ガイドもご覧ください。
