8月13日、Towards Data Scienceが「LangChain vs LangGraph: 4 Key Differences and When to Use Each」と題した記事を公開した。LLMアプリ開発が進むにつれ、「LangChainで作り始めたが途中でLangGraphへの移行を迫られた」という経験をする開発者は多い。その判断が後手に回る原因の多くは、両者のアーキテクチャ上の違いを最初に正しく把握していないことにある。この記事では、LangChainとLangGraphの4つの本質的な違いと、エージェント開発においてどちらを選ぶべきかについて詳しく解説されている。
LLMを使ったアプリケーション開発において、LangChainとLangGraphはよく比較される。ただし両者は競合関係ではなく、LangGraphはLangChainエコシステムの一部として、その上に構築された拡張ライブラリである。「LangChainで難しい、あるいは不可能なことをLangGraphが埋める」という位置づけだ。
どちらを選ぶかはアーキテクチャの選択に直結する。以下の4つの違いを押さえると判断しやすい。
1. パイプライン vs ループ(最も本質的な違い)
LangChainの基本抽象は一方向のパイプラインだ。コンポーネントを連鎖させてデータを前に流す。この記述スタイルはLCEL(LangChain Expression Language)として体系化されており、|演算子でチェーンを構成するのが典型的な書き方である。
chain = prompt | model | parser
output = chain.invoke(input)
分岐や並列処理、DAG構成も可能だが、「前の工程に戻る」という操作はChainの外側にPythonループを書いて実現するしかない。ループの制御はアプリケーション側の責務になる。
LangGraphはループをワークフローそのものの一部として扱う。構造はノードとエッジで構成されるグラフだ。
- ノード:特定のタスクを実行する
- 通常エッジ:固定された遷移を定義する
- 条件付きエッジ:次にどこへ行くかを動的に決定する
この構造により、以前のノードへのルーティングバックが自然に記述できる。コード生成→テスト→エラーがあれば修正に戻る、といったループを含むワークフローがLangGraphの得意領域だ。
2. ステートレス vs ステートフル
LangChainのパイプラインは状態を内部に持たない。各ステップは入力を受けて出力を返す。会話履歴・リトライ回数・バリデーションエラーといった状態の管理は開発者が別途実装する必要がある。
LangGraphは状態をグラフの一部として宣言する。TypedDictでスキーマを定義し、各ノードは必要なフィールドだけを部分更新すればよい。
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
booking_details: BookingDetails
calculated_price: NotRequired[float | None]
status: BookingStatus
booking_id: NotRequired[str | None]
たとえば料金計算ノードはcalculated_priceだけを返せばよく、LangGraphが既存の状態にマージする。
def calculate_price_node(state: AgentState) -> dict[str, Any]:
return {"calculated_price": calculate_price(state["booking_details"])}
messagesのように複数回更新されるフィールドにはリデューサー(add_messages)を指定できる。リデューサーとは、フィールドへの新しい値をどう既存値に反映するかを定義する関数のことで、LangGraphの組み込みリデューサー一覧で詳細を確認できる。リデューサーがなければ新しい値が古い値を上書きするため、チャット履歴の管理には必須の仕組みだ。
3. Human-in-the-loop
DBマイグレーションや本番デプロイなど、エージェントに自動実行させる前に人間の承認を挟みたいケースは多い。
LangChainで実装しようとすると、「チェーンを途中で止めて提案を保存→APIやジョブキューに制御を戻す→承認イベントを待つ→コンテキストを再構築して次の処理を開始」という煩雑な手順が必要になる。実質的に「ループを壊して再構築する」作業だ。
LangGraphはノード内でinterrupt()を呼ぶだけでグラフの実行を一時停止できる。停止時点の状態は自動で保存されるため、情報の損失を心配する必要がない。Human-in-the-loopの公式ガイドも参照されたい。
from langgraph.types import interrupt
def approval_node(state: State):
approved = interrupt({
"question": "Run this migration?",
"sql": state["sql"],
})
return {"approved": approved}
4. 再起動 vs 再開
チェーンの途中でエラーが発生した場合、LangChainでは最初から実行し直すのがシンプルな回復手段だ。しかし前のステップのモデル呼び出しや検索クエリをすべてやり直すのは、時間的にもコスト的にも無駄が大きい。
LangGraphにはチェックポインターという仕組みがある。実行の各ステップでグラフ状態のスナップショットを保存する永続化レイヤーだ。有効化はコンパイル時に一行追加するだけでよい。
graph.compile(checkpointer=checkpointer)
チェックポインターのバックエンド実装には用途に応じた選択肢が用意されている。開発・テスト用途にはInMemorySaver、SQLiteを使った軽量な永続化にはSqliteSaver、本番環境向けにはAsyncPostgresSaverといった実装が公式で提供されている。
失敗が起きた場合、成功済みのステップを繰り返さずに途中から再開できる。問題のあったノードの直前まで状態を巻き戻して再実行する、といった操作も可能だ。
どちらを選ぶか
記事では以下のように整理されている。
LangChainで十分なケース(予測可能で前向きのワークフロー):
- 標準的なRAGパイプライン
- シンプルなQ&Aボット
- ドキュメントの抽出・分類
- 要約処理
LangGraphを選ぶべきケース:
- コードの生成・テスト・修正を繰り返すコーディングアシスタント
- 計画と評価を繰り返すワークフロー
- 一時停止・永続化・再開が必要なアプリケーション
記事の結論はシンプルだ。「アプリケーションがパイプラインとして理解できるならLangChain、ステートフルなシステムとして理解できるならLangGraph」。LangChainで始めてLangGraphへ移行するタイミングを見誤ると、後からアーキテクチャの大幅な見直しを迫られる。設計段階でこの判断軸を持っておくことが、エージェント開発の失敗を避ける上で重要だ。
詳細はLangChain vs LangGraph: 4 Key Differences and When to Use Eachを参照していただきたい。




