Claude Media
LangGraphとClaude Codeでエージェントを実装する手順 — 状態設計から永続化まで

LangGraphとClaude Codeでエージェントを実装する手順 — 状態設計から永続化まで

LangGraphにClaudeを接続し、StateGraphでツール呼び出しループを組む手順です。Claude Codeに作らせる指示文とMCP接続、永続化まで扱います。

LangGraphとは何か、Claude Codeとどう組み合わせるか

LangGraphは、状態を持つ長時間実行のエージェントを構築・実行するための、低レベルなオーケストレーションフレームワークです。決定論的な手続き型のステップと、LLMが判断するエージェント的なステップを、同じグラフの中に混在させられる点が中核の特徴です。LangChain社が開発していますが、LangChain自体を使わなくても単独で利用できます。

Claude Codeはこのグラフを書くための開発環境として使えます。LangGraphのコードそのものはPythonのライブラリなので、Claude/Anthropicの機能ではありません。Claude Codeが担うのは、StateGraphの雛形を書く、ノード関数を実装する、テストを回すといった開発作業の自動化です。LangGraphにClaudeを接続するには、langchain-anthropicパッケージを入れ、init_chat_modelかChatAnthropicにモデル名を渡し、bind_toolsでツールを結びます。コードは手順1にあります。

この記事では、公式クイックスタートに沿ってツール呼び出しループを持つエージェントを組み、状態設計・エッジ設計・永続化までを実装します。各手順のコードは、モデル呼び出しだけをスタブに差し替えて動かし、出力を載せます。環境はPython 3.12.2、langgraph 1.2.12、langchain-core 1.6.6、langchain 1.4.3です。APIキーもネットワーク越しのモデル呼び出しも使っていません。

前提条件を整える

Python環境と、Claudeの利用契約(APIキー)が必要です。パッケージは4つに分かれています。公式のインストール手順では、langgraph、langchain(Python 3.10以上)、langchain-anthropicがそれぞれ別の節に載っています。本記事のコードはfrom langchain.tools import toolを使います。from langchain.chat_models import init_chat_modelも同じlangchain側のものなので、langchainも要ります。

pip install -U langgraph langchain langchain-anthropic langchain-core

ANTHROPIC_API_KEY環境変数を設定します。Claudeコンソールでキーを発行し、シェルか.envファイルに設定してください。

export ANTHROPIC_API_KEY="sk-ant-..."

トレースを見たい場合はLangSmithの利用も選べますが、必須ではありません。ここではLangGraph単体の実装に絞ります。

手順1: モデルとツールを準備する

ChatAnthropic(またはinit_chat_model)でClaudeモデルを読み込み、@toolデコレータでツール関数を定義します。公式クイックスタートは四則演算ツールを例に使っています。

from langchain.tools import tool
from langchain.chat_models import init_chat_model
 
model = init_chat_model("claude-sonnet-4-6", temperature=0)
 
@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`."""
    return a + b
 
@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`."""
    return a * b
 
tools = [add, multiply]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)

手順2: 状態(State)スキーマを設計する

StateGraphは、ユーザー定義のStateオブジェクトでパラメータ化します。会話メッセージを扱うだけならMessagesStateを継承するのが最短です。

from langgraph.graph import MessagesState
from typing_extensions import TypedDict
 
class AgentState(MessagesState):
    llm_calls: int

ノードは辞書の一部だけを返し、グラフ側が既存の状態とマージします。マージのされ方を決めるのがreducerです。既定のreducerは新しい値で上書きしますが、Annotated[list, operator.add]のように指定すれば、リストへの追記に変わります。メッセージ履歴を上書きせず積み上げたいときは、この仕組みが必須です。MessagesStateは、add_messagesというreducerをあらかじめ組み込んだ状態スキーマです。メッセージIDが同じものは更新し、新しいものは追記します。

手順3: ノードを実装する

ノードはadd_nodeでグラフに登録する、状態を受け取り更新を返す関数です。モデルを呼ぶノードと、ツールを実行するノードの2つを用意します。

from langchain.messages import SystemMessage, ToolMessage
 
def llm_call(state: AgentState):
    return {
        "messages": [
            model_with_tools.invoke(
                [SystemMessage(content="あなたは計算を手伝うアシスタントです。")]
                + state["messages"]
            )
        ],
        "llm_calls": state.get("llm_calls", 0) + 1,
    }
 
def tool_node(state: AgentState):
    result = []
    for call in state["messages"][-1].tool_calls:
        tool = tools_by_name[call["name"]]
        observation = tool.invoke(call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=call["id"]))
    return {"messages": result}

ツール実行を自分で書く代わりに、langgraph.prebuiltのToolNodeも使えます。クラスのdocstringには、並列実行・エラーハンドリング・状態の注入・永続ストレージの扱いを担うノードと書かれています。標準的なReAct型のエージェントなら、内部でToolNodeを使うcreate_agentで足りるとも案内されています。handle_tool_errorsの既定では、モデルが渡した引数の誤りによる呼び出しエラーだけを捕捉し、ツール実行中のエラーは再送出されます。

from langgraph.prebuilt import ToolNode
 
builder.add_node("tools", ToolNode(tools))

ToolNodeはCommandを返すツールの出力も扱えます(同じdocstringの出力形式の記述)。手書きのtool_nodeで同じことをするには、Commandの受け渡しも自分で書くことになります。

2つのツール呼び出しを1回のAIメッセージに入れてToolNodeに流すと、ToolMessageがツールごとに1件ずつ、呼び出しの順に返りました。

[('HumanMessage', 'x'), ('AIMessage', ''), ('ToolMessage', '7'), ('ToolMessage', '3'), ('AIMessage', '完了')]

手順4: エッジで状態遷移を定義する

エッジはノード間の遷移を決めます。種類は4つです。

種類書き方動作
通常のエッジ書き方add_edge("node_a", "node_b")動作常に次のノードへ進む
条件付きエッジ書き方add_conditional_edges("node_a", routing_fn)動作ルーティング関数の戻り値で次のノードを決める
エントリーポイント書き方add_edge(START, "node_a")動作最初に実行するノードを指定する
条件付きエントリーポイント書き方STARTから条件付きエッジを張る動作開始ノード自体を動的に決める

ツール呼び出しループでは、モデルがツールを呼んだかどうかで次を分岐させます。

from typing import Literal
from langgraph.graph import StateGraph, START, END
 
def should_continue(state: AgentState) -> Literal["tools", END]:
    if state["messages"][-1].tool_calls:
        return "tools"
    return END
 
builder = StateGraph(AgentState)
builder.add_node("llm_call", llm_call)
builder.add_node("tools", tool_node)
builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tools", END])
builder.add_edge("tools", "llm_call")

手順5: グラフをコンパイルして実行する

compile()でグラフを実行可能なオブジェクトに変換し、invokeで1回分の実行を回します。

agent = builder.compile()
 
from langchain.messages import HumanMessage
result = agent.invoke({"messages": [HumanMessage(content="3と4を足して")]})
for m in result["messages"]:
    m.pretty_print()

3と4を足す依頼を、addツールを1回呼んで答えるスタブモデルで流した実際の結果です。

手順

スタブで流したツール呼び出しループ1周

  1. 1

    HumanMessage

    「3と4を足して」。llm_callsは未設定なので、state.get("llm_calls", 0)が0を返します。

  2. 2

    AIMessage(1回目の llm_call)

    本文は空で、tool_callsにadd(a=3, b=4)が入っています。should_continueが"tools"を返します。

  3. 3

    ToolMessage(tools ノード)

    内容は'7'です。エッジはtoolsからllm_callへ戻ります。

  4. 4

    AIMessage(2回目の llm_call)

    tool_callsが空の「7です」。ENDへ抜けます。最終状態のllm_callsは2です。

agent.get_graph().draw_mermaid()は、グラフ構造をMermaid記法のテキストで返します(draw_mermaid_png()なら画像)。条件付きエッジは点線で出ます。ノードとエッジの行は次のとおりです(先頭のconfigと末尾のclassDefは省略しています)。

graph TD;
	__start__([<p>__start__</p>]):::first
	llm_call(llm_call)
	tools(tools)
	__end__([<p>__end__</p>]):::last
	__start__ --> llm_call;
	llm_call -.-> __end__;
	llm_call -.-> tools;
	tools --> llm_call;

実行状態を永続化する

長時間動くエージェントや、中断から再開したいエージェントには、checkpointerが要ります。

from langgraph.checkpoint.memory import InMemorySaver
 
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
 
graph.invoke(
    {"messages": [HumanMessage(content="Bobです、よろしく")]},
    {"configurable": {"thread_id": "thread-1"}},
)

checkpointerとstoreは役割が異なり、compile(checkpointer=..., store=...)で両方を渡せます。

くらべる

checkpointer と store の住み分け

1スレッドの短期記憶

checkpointer

グラフ状態のスナップショットを、thread_idごとに保存します。会話の継続・人間介入・障害復旧を支えます。

スレッドをまたぐ長期記憶

store

アプリ定義のキー・バリューデータを、グラフ状態の外に保存します。ユーザーの好みや共有知識向けです。

InMemorySaver(MemorySaverも同じ)はプロセスのメモリ上に保存するだけなので、プロセスが再起動すると消えます。開発中の確認用と割り切ります。本番運用ではPostgresSaverのようなDB型の永続checkpointerに切り替えます。SqliteSaverはローカルのファイルに保存するので、開発向けです。この状態設計と永続化の考え方は、AnthropicのContext Engineering論が扱う「長時間動くエージェントに何を持たせ、何を捨てるか」という論点とも重なります。checkpointer用のテーブルとは別に、自分のドメインモデルも同じPostgreSQLへ保存したいなら、Claude CodeでPrismaスキーマを設計するの手順が参考になります。

checkpointerを組み込むと、interrupt()関数でグラフの実行を任意の地点で止められます。ノード内でinterrupt("承認しますか?")のように呼ぶと、その時点の状態が保存されて停止し、再開の指示が来るまで無期限に待ちます。再開するときはCommand(resume=...)を渡して同じthread_idで呼び直すと、その値がinterrupt()の戻り値としてノードに渡ります。再開時、ノードはinterruptを呼んだノードの先頭から実行し直すので、interruptより前のコードは再実行されます。

承認ノード1つだけのグラフで動かすと、停止時のinvokeは例外を投げず、結果の辞書に__interrupt__キーが入って返りました。

interrupt result keys: ['messages', '__interrupt__']
[Interrupt(value='承認しますか?', id='34300c8d…', response_schema=None)]
next: ('approve',)
['hi', '回答:yes']

get_state(config).nextには、再開時に実行されるノード名('approve',)が入ります。最後の行がCommand(resume="yes")で再開した後のメッセージ内容です。thread_idを渡さずに呼ぶと、ValueErrorで落ちます。メッセージは次のとおりです。

ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id

Claude Codeでの開発を効率化する具体的な方法

LangGraphの公式クイックスタートには、Claude Codeへ貼り付けて使う指示文(Prompt)があり、3種類あります。

3種類

クイックスタートの指示文

  • 四則演算エージェントを作らせる

    言語判定から依存パッケージの導入、サンプルの実装までを進める指示文です。

  • ドキュメントMCPを接続させる

    LangChain・LangGraph・LangSmithのドキュメントとAPIリファレンスを引けるようにします。

  • Agent Skillsを導入させる

    LangChain公式のスキル集を、使っているエージェントに入れます。

1つ目の指示文には、ANTHROPIC_API_KEYが未設定なら、キーを作って設定するよう利用者に頼んだうえで確認が取れるまで止まる、という指示が入っています。キーの値を勝手に作らず、コードにも書かせない設計です。手順1〜5を自分で書く前に、この指示文で動くものを1つ作ってから読み替える進め方もあります。

MCPサーバーの接続は、2つのコマンドです。

claude mcp add --transport http docs-langchain https://docs.langchain.com/mcp
claude mcp add --transport http reference-langchain https://reference.langchain.com/mcp

2つ目の指示文には、Claude Codeはclaude mcp add --transport httpを使うとあります。スコープは既定でprojectにして、--scope userは利用者が頼んだときだけにする、という指示です。一方、claude mcp add --help(v2.1.287)の--scopeの行は次のとおりです。

-s, --scope <scope>  Configuration scope (local, user, or project) (default: "local")

上の2行をそのまま打つと、既定のlocalスコープで登録されます。チームで共有する.mcp.jsonに入れたいなら--scope projectを明示します。

コーディングエージェントごとのMCP対応状況の違いはAIコーディングエージェントのMCP対応状況を比較するにまとめています。

Agent Skillsは次のコマンドで入れます。

npx skills add langchain-ai/langchain-skills --skill '*' --yes

指示文には、現在のディレクトリではなくグローバルに入れる--global付きの変形もあります。Claude Codeのプラグイン経路を好む場合は、同じリポジトリのREADMEに/plugin marketplace addから/plugin installの順で入れる案内があります。

LangGraphが向く場面・向かない場面

LangGraphはあらゆる会話ボットに必要なものではありません。場面ごとの向き不向きは次のとおりです。

場面LangGraphが向くか理由
決定論的な処理とLLM判断を1つのフローに混在させたいLangGraphが向くか◎理由StateGraphで手続き型ノードとLLM駆動ノードを同居させられる
長時間実行・中断からの再開が必要LangGraphが向くか◎理由checkpointerでスレッド単位の状態を永続化できる
人間による承認・修正を挟みたいLangGraphが向くか◎理由interrupt機能で任意の時点の状態を検査・変更できる
単発の一問一答で完結するLangGraphが向くか△理由オーケストレーション層を持ち込むほどの複雑さがない
Claude Code内の自動化だけで足りるLangGraphが向くか△理由サブエージェントとhookの組み合わせで完結するなら、足す必要がない

モデルをどう割り当てるかという設計判断は、LangGraphのノード単位でも、Claude Codeのサブエージェント単位でも同じ発想で考えられます。役割ごとに単価と精度を天秤にかける具体的な基準はClaude Codeサブエージェントのモデル配分設計で扱っています。トレースや可観測性の面では、Anthropic純正のエージェント構築手段であるAgent SDKにもOpenTelemetryで可観測化するという別のアプローチがあります。

よくあるつまずき

PostgresSaver(やAsyncPostgresSaver)はthread_idを長さ制限のあるカラムに保存するため、長すぎるとデータベースエラーになります。公式はthread_idを255文字未満に保つよう案内しています。UUIDやハッシュ値に短縮すると避けられます。

再帰上限に達するときの注意です。LangGraphはグラフの実行を「スーパーステップ」単位で数えており、v1.0.6以降は既定で1000ステップが上限です。上限に達するとGraphRecursionErrorが送出されます。invokeやstreamのconfigにrecursion_limitを渡せば調整できます。configurableの中ではなく、config直下のキーです。

終わらないツール呼び出しを返し続けるスタブで、{"recursion_limit": 6}を渡して試すと、次のメッセージで止まりました。

GraphRecursionError: Recursion limit of 6 reached without hitting a stop condition. You can increase the limit by setting the `recursion_limit` config key.

Commandでgotoを返すノードにadd_edgeも張ると、経路が二重になります。Commandが足すのは動的なエッジだけで、add_edgeの静的エッジも実行されるためです。

たとえばnode_aがCommand(goto="my_other_node")を返すとします。add_edge("node_a", "node_b")もあれば、node_bとmy_other_nodeの両方が走ります。通常のエッジと条件付きエッジの組み合わせも同じで、両方の経路が実行され得ます。ノードごとにルーティング方式を1つに絞ります。

まとめ

ツール呼び出しループの設計で効くのは、should_continueの分岐とrecursion_limitの2か所です。再開や承認が要るならcheckpointerとthread_idを、そうでなければ素のグラフで足ります。

指示文をそのまま貼る前に、チームで共有したいなら--scope projectを指示文に書き足しておきます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.langchain.com/oss/python/langgraph/overview
  2. docs.langchain.com/oss/python/langgraph/quickstart
  3. docs.langchain.com/oss/python/integrations/chat/anthropic
  4. docs.langchain.com/oss/python/langgraph/graph-api
  5. docs.langchain.com/oss/python/langgraph/persistence
  6. docs.langchain.com/oss/python/langgraph/interrupts
  7. docs.langchain.com/oss/python/langgraph/install
この記事を共有:XはてブLinkedIn