「AutoGenのように複数エージェントを動かしたいが、処理の流れをもっと明示的に制御したい」
そんな課題を持つPython開発者・インフラエンジニアには、LangGraphが解決策になる。LangGraphはLangChainエコシステムの拡張ライブラリで、エージェントの処理をグラフ(ノードとエッジ)として表現する。ループ・分岐・状態管理が明示的に扱えるため、調査→判断→再試行といった複雑な業務フローをローカルLLMで実装しやすい。
この記事では、OllamaとLangGraphを組み合わせてステートフルAIエージェントを構築する手順を解説する。StateGraphの基本構造からConditional Edgeによる分岐・ループ、MemoryCheckpointerによる会話履歴管理まで、Ubuntu Server上でゼロから実装する。
この記事のポイント
・LangGraphのStateGraphでノードとエッジを定義し、グラフ構造のエージェントを構築できる
・add_conditional_edgesで条件分岐とループを実装し、LLMが自律的に処理を継続・終了を判断できる
・MemorySaverをCheckpointerに指定すると、スレッドIDごとに会話履歴を永続管理できる
・LangGraphはLangChainの単純チェーンでは扱いにくい「繰り返し処理が必要なタスク」に向いている
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
LangGraphとは何か、LangChainとの違いと使い分け
LangChainは「A→B→C」という直線的なチェーンでLLM処理を繋ぐライブラリだ。RAGや質問応答など、一方向に流れるパイプラインには適しているが、「LLMが結果を評価して再試行する」「条件によって処理ルートを変える」といった循環・分岐を含む処理は実装が難しい。LangGraphはこの問題をグラフ構造で解決する。処理の単位をノード(Pythonの関数)、処理の流れをエッジ(接続と条件)として定義する。ノードからノードへの移動ルートを動的に決定できるため、LLMの判断に基づいてループしたり別ルートに分岐したりする処理が自然に書ける。
LangChainとLangGraphの使い分けは以下の基準が目安になる。
・処理が一方向で完結する(RAG・要約・分類)→ LangChain LCEL
・LLMが結果を評価して再試行するか判断する → LangGraph
・複数ステップを経て状態を蓄積しながら進む → LangGraph
・複数エージェントが役割分担して協調する → LangGraph(またはCrewAI)
OllamaとLangGraphを組み合わせることで、インターネット接続不要・機密データ保護の条件でこれらのエージェントをローカル環境に構築できる。OllamaのローカルLLM構築手順はUbuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイドで解説している。
前提環境の確認とOllamaの稼働チェック
本記事の手順を進める前に、以下の環境が揃っていることを確認する。・Ubuntu Server 22.04 LTS以上(本記事の検証環境)
・Python 3.11以上(LangGraph 0.2系の動作要件)
・Ollamaがsystemdサービスとして稼働中
・モデルのpull完了(本記事ではllama3.3:70b-instruct-q4_0を使用)
1. OllamaとPythonのバージョン確認
$ curl http://localhost:11434/api/version {"version":"0.3.12"} $ python3 --version Python 3.12.3 $ ollama list NAME ID SIZE MODIFIED llama3.3:70b-instruct-q4_0 xxxxxxxxxxxx 40 GB 3 days ago
2. Python仮想環境の作成
ライブラリの競合を防ぐため、プロジェクト専用の仮想環境を用意する。$ python3 -m venv ~/langgraph-demo $ source ~/langgraph-demo/bin/activate (langgraph-demo) $
LangGraphのインストールとOllamaのセットアップ
1. 必要パッケージをインストールする
(langgraph-demo) $ pip install langgraph langchain-ollama langchain-core # インストール確認 (langgraph-demo) $ python3 -c "import langgraph; print(langgraph.__version__)" 0.2.45
2. Ollamaへの接続を確認する
(langgraph-demo) $ python3 - << 'EOF' from langchain_ollama import ChatOllama llm = ChatOllama(model="llama3.3:70b-instruct-q4_0", base_url="http://localhost:11434") response = llm.invoke("一言で答えてください: 1+1は?") print(response.content) EOF 2
StateGraphで最初のエージェントを構築する
LangGraphの中心となるクラスがStateGraphだ。グラフ全体で共有される状態(State)を型付きdictで定義し、ノードとして処理関数を登録する。1. Stateと基本ノードを定義する
以下は「ユーザーの質問を受け取ってLLMで回答する」だけの最小構成のエージェントだ。ファイル名はagent_basic.pyとして保存する。from typing import Annotated from typing_extensions import TypedDict from langchain_core.messages import HumanMessage, AIMessage from langchain_ollama import ChatOllama from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages # 1. State定義(グラフ全体で共有するデータ構造) class State(TypedDict): messages: Annotated[list, add_messages] # 2. LLMノード関数 llm = ChatOllama(model="llama3.3:70b-instruct-q4_0") def call_llm(state: State) -> dict: response = llm.invoke(state["messages"]) return {"messages": [response]} # 3. グラフ構築 graph_builder = StateGraph(State) graph_builder.add_node("llm_node", call_llm) graph_builder.add_edge(START, "llm_node") graph_builder.add_edge("llm_node", END) graph = graph_builder.compile() # 4. 実行 result = graph.invoke({"messages": [HumanMessage(content="OllamaとLangGraphの違いを30字以内で教えて")]}) print(result["messages"][-1].content)
2. 実行して動作を確認する
(langgraph-demo) $ python3 agent_basic.py OllamaはLLM実行基盤、LangGraphは処理フロー制御フレームワーク。
Conditional Edgeで条件分岐とループを実装する
LangGraphの真価は条件分岐とループだ。add_conditional_edgesを使うことで、LLMの回答の内容に応じて次に進むノードを動的に切り替えられる。典型的なパターンが「ReAct(Reasoning + Acting)」スタイルのエージェントだ。LLMが「ツールを使う」か「終了する」かを自分で判断してループする構成になる。
1. ツール呼び出し付きエージェントの構成
from typing import Annotated, Literal from typing_extensions import TypedDict from langchain_core.messages import HumanMessage, ToolMessage from langchain_core.tools import tool from langchain_ollama import ChatOllama from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages # ツール定義 @tool def get_current_date() -> str: """現在の日付を返す""" from datetime import date return str(date.today()) @tool def calculate(expression: str) -> str: """数式を計算して結果を返す。例: '2 + 3 * 4'""" try: return str(eval(expression, {"__builtins__": {}})) except Exception as e: return f"エラー: {e}" tools = [get_current_date, calculate] class State(TypedDict): messages: Annotated[list, add_messages] llm = ChatOllama(model="llama3.3:70b-instruct-q4_0").bind_tools(tools) # ノード: LLMを呼び出す def call_llm(state: State) -> dict: return {"messages": [llm.invoke(state["messages"])]} # ノード: ツールを実行する def run_tools(state: State) -> dict: tool_map = {t.name: t for t in tools} last_msg = state["messages"][-1] results = [] for call in last_msg.tool_calls: result = tool_map[call["name"]].invoke(call["args"]) results.append(ToolMessage(content=str(result), tool_call_id=call["id"])) return {"messages": results} # 分岐関数: ツールを呼ぶかENDへ進むかを判断 def should_continue(state: State) -> Literal["tools", END]: last_msg = state["messages"][-1] if hasattr(last_msg, "tool_calls") and last_msg.tool_calls: return "tools" return END # グラフ構築 graph_builder = StateGraph(State) graph_builder.add_node("agent", call_llm) graph_builder.add_node("tools", run_tools) graph_builder.add_edge(START, "agent") graph_builder.add_conditional_edges("agent", should_continue) graph_builder.add_edge("tools", "agent") graph = graph_builder.compile()
2. エージェントを実行して動作を確認する
(langgraph-demo) $ python3 - << 'EOF' result = graph.invoke({"messages": [HumanMessage(content="今日の日付を教えて。また 123 * 456 を計算して")]}) print(result["messages"][-1].content) EOF 今日の日付は 2026-10-22 です。また 123 × 456 の計算結果は 56088 です。
OllamaのFunction Calling(ツール呼び出し)の基礎についてはUbuntu ServerでローカルLLMを構築する方法の関連記事も参照すると理解が深まる。
MemorySaverで会話履歴を管理する(Checkpointer設定)
デフォルトではgraph.invoke()を呼ぶたびに状態がリセットされる。MemorySaverをCheckpointerとして設定することで、スレッドIDごとに会話履歴を保持でき、複数回のやり取りを通じてコンテキストが維持される。1. MemorySaverを使った会話継続の実装
from langgraph.checkpoint.memory import MemorySaver # MemorySaverをcheckpointerとして渡してコンパイル memory = MemorySaver() graph = graph_builder.compile(checkpointer=memory) # thread_idで会話セッションを識別する config = {"configurable": {"thread_id": "session-001"}} # 1回目の会話 result1 = graph.invoke( {"messages": [HumanMessage(content="私の名前はtanaka、覚えておいてください")]}, config=config ) print(result1["messages"][-1].content) # 2回目(同じthread_idで続きの会話) result2 = graph.invoke( {"messages": [HumanMessage(content="私の名前は何でしたか?")]}, config=config ) print(result2["messages"][-1].content)
2. 実行結果で会話継続を確認する
(langgraph-demo) $ python3 memory_agent.py 了解しました。tanakaさんですね。 あなたの名前はtanakaさんです。
チームへのローカルLLM導入の判断根拠については社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢にまとめている。
よくあるトラブルと対処法
LLMがtool_callsを返さずループが機能しない
LangGraphのツール呼び出しには、OllamaのFunction Calling対応モデルが必要だ。Llama3.3やMistralなど、ツール呼び出しをサポートするモデル以外では tool_calls が空のまま返ってくる。# モデルのツール対応確認 $ ollama show llama3.3:70b-instruct-q4_0 | grep -i "tool\|function"
RecursionError: グラフが無限ループになる
Conditional Edgeの条件関数が常に「tools」を返し続けるとグラフが無限ループになる。デフォルトでは再帰の深さが25に制限されており、超過するとRecursionErrorが発生する。再帰回数の上限を下げてデバッグするか、should_continue関数に強制終了の条件を追加する。# 再帰上限を設定して実行する result = graph.invoke( {"messages": [HumanMessage(content="テスト")]}, config={"recursion_limit": 5} )
TypeError: ChatOllama object is not subscriptable
ChatOllamaのバージョンとlangchain-ollamaのバージョン不一致で起きやすい。pip list でlangchain-ollama・langgraph・langchain-coreのバージョンを確認し、互換性のある組み合わせにアップデートする。langchain-ollama 0.3系とlanggraph 0.2系の組み合わせが現時点での安定構成だ。StateのAnnotatedが機能せずmessagesが上書きされる
TypedDictのフィールドにAnnotatedとadd_messagesを付けていない場合、メッセージリストが追記されずに上書きになる。以下の宣言が必須だ。# 正しい宣言 from langgraph.graph.message import add_messages class State(TypedDict): messages: Annotated[list, add_messages] # Annotatedとadd_messagesが必須 # 誤った宣言(上書きになる) class State(TypedDict): messages: list # Annotatedなし → NG
まとめ
OllamaとLangGraphを組み合わせてステートフルAIエージェントを構築する手順を解説した。| 機能 | 使用するクラス・関数 | 主な用途 |
|---|---|---|
| グラフ定義 | StateGraph(State) | ノードとエッジの骨格を作る |
| ノード登録 | graph_builder.add_node("名前", 関数) | 処理ステップをグラフに追加 |
| 条件分岐 | graph_builder.add_conditional_edges("ノード", 分岐関数) | LLMの出力に応じてルートを変える |
| 会話履歴管理 | MemorySaver() + compile(checkpointer=...) | thread_idで会話セッションを維持 |
| ツール実行 | llm.bind_tools(tools) + ToolMessageノード | LLMが関数を呼び出して結果を使う |
LangGraphはLangChainの単純なチェーンでは対応しにくい「繰り返し処理を含むエージェント」に適している。OllamaのローカルLLMと組み合わせることで、機密データを外部に出さずに複雑な業務フローを自動化できる。
どのローカルLLMモデルをLangGraphのエージェントに使うかについてはローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイントを参考に選定する。
OllamaとLangGraphのエージェント構築を2日間のハンズオンで体験する
StateGraphによるグラフ設計や条件分岐の実装を手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaとCeleryで非同期LLMタスクキューを構築する方法|複数ユーザーのリクエストをワーカーで並列処理してサーバー負荷を制御する手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます