LangGraph入門チュートリアル

LangGraph は LangChain チームによって開発された低レベルの Agent オーケストレーションフレームワーク、ステートフル(Stateful)で長時間実行される AI ワークフローの構築に特化して設計されています。

従来の線形LLM呼び出しチェーンとは異なり、LangGraphはワークフローを次のようにモデル化します。有向グラフ(Directed Graph):

  • ノード(Node):具体的な操作を実行する関数(LLMの呼び出し、ツールの実行、データ処理など)
  • エッジ(Edge):ノード間の遷移パスを定義し、条件分岐をサポートする
  • 状態(State):ワークフロー全体で共有・受け渡しされるデータ

オープンソースのURL:https://github.com/langchain-ai/langgraph。

交響楽の演奏を指揮しているところを想像してください。従来のLLM Chainは最初から最後まで順番に演奏するだけの曲のようなものです。一方LangGraphは指揮者のようなもので、会場の観客の反応に応じて演奏順序を自由に調整し、ある楽章を繰り返したり、特定の段落へジャンプしたりできます。これによりAIワークフローは「指揮」の知恵を持ち、ループ・分岐・バックトラックが可能になり、複雑な自律的決定を真に実現します。

なぜLangGraphを選ぶのか?

以下の表は、従来のLLM ChainとLangGraphの主な相違点を比較しています。

特性 従来のLLM Chain LangGraph
ワークフロー構造 線形、一方向実行 グラフ構造、ループ対応
状態管理 手動管理が必要 組み込みの状態永続化
条件付きルーティング 実装が複雑 ネイティブサポート
人とAIの協働 追加開発が必要 interruptの組み込みサポート
マルチAgentの調整 実装が困難 一流のサポート
デバッグツール 限定的 LangGraph Studio

適用シナリオ

  • チャットボット:複数ターンの会話コンテキストの記憶が必要
  • 自律型Agent:計画立案、ツール使用、反復的思考が可能
  • マルチAgentシステム:複数のAIが協力して複雑なタスクを完了する
  • 承認ワークフロー:人間による審査が必要な自動化プロセス
  • 研究アシスタント:多段階の推論と情報検索が必要
背景 タイトル LangGraph中核アーキテクチャ 外側の大きな枠:StateGraph StateGraph(状態グラフ) State層 - 上部を貫通 State(共有状態)- すべてのノードを貫通 ノード領域 START 開始ノード Node A 処理ノード Node B 決定ノード END 終了ノード 分岐ノード Node C 代替分岐 エッジ:START -> Node A エッジ:Node A -> Node B エッジ:Node B -> END(条件付きエッジ) 完了 エッジ:Node B -> Node C(条件付きエッジ) さらなる処理が必要 ループエッジ:Node C -> Node A ループ 矢印の定義

中核概念

コードを書き始める前に、LangGraphの3つの中核概念を理解しておきましょう。

Graph(グラフ)

Graphはワークフロー全体の青写真であり、Agentの完全な論理構造を定義します。ノード(Nodes)とエッジ(Edges)で構成されます:

StateGraph
   |-- Nodes(节点)
   |     |-- node_a
   |     |-- node_b
   |     +-- node_c
   +-- Edges(边)
         |-- START -> node_a
         |-- node_a -> node_b(条件边)
         |-- node_a -> node_c(条件边)
         +-- node_b -> END

State(状態)

Stateはグラフ全体を貫通する共有データ構造です。各ノードはStateを読み取り・更新でき、更新されたStateは次のノードに渡されます。

from typing import TypedDict, Annotated
from langgraph.graph import add_messages

class MyState(TypedDict):
    messages: Annotated[list, add_messages]  # 消息列表(自动追加)
    user_name: str                            # 用户名称
    step_count: int                           # 步骤计数
重要:Annotated[list, add_messages](この注釈は)そのフィールドがadd_messagesreducerとして使用することを示します——新しいメッセージはリストに追加され、上書きされません。これはLangGraphの状態管理の中核メカニズムです。
背景 タイトル Stateデータの流れの模式図 State初期 State {msg: []} 矢印 Node A 入力の処理 矢印 + State更新 State更新1 State {msg: [A]} 矢印 Node B 応答の生成 矢印 State更新2 State {msg: [A,B]} 矢印 END 下部の説明 各ノードはStateを読み取り、操作を実行し、更新された部分フィールドを返します 矢印の定義

Nodes(ノード)

ノードは通常のPython関数で、現在のStateを受け取り、更新されたState(部分フィールド)を返します。

def my_node(state: MyState) -> dict:
    # 读取状态
    messages = state["messages"]
    
    # 执行操作...
    result = "处理结果"
    
    # 返回更新的字段(不需要返回所有字段)
    return {"messages": [{"role": "ai", "content": result}]}

Edges(エッジ)

エッジはノード間の遷移方法を定義します:

  • 通常のエッジ:固定パス、node_a -> node_b
  • 条件付きエッジ:Stateに基づく動的ルーティング、node_a -> node_b 或 node_c
  • 開始エッジ:START -> 第一个节点
  • 終了エッジ:某节点 -> END

環境構築

依存関係のインストール

国内ミラーを使用してLangGraphと関連依存関係をインストールします:

# 创建虚拟环境(推荐)
python -m venv venv
source venv/bin/activate      # macOS/Linux
# venv\Scripts\activate       # Windows

# 安装 LangGraph 和 LangChain
pip install langgraph langchain langchain-openai python-dotenv -i https://mirrors.aliyun.com/pypi/simple/

# 可选:安装开发工具
pip install langgraph-cli jupyter -i https://mirrors.aliyun.com/pypi/simple/

APIキーの設定

プロジェクトのルートディレクトリに.envファイルを作成し、同時にOpenAIとDeepSeekを設定します:

# .env 文件内容

# OpenAI 配置(国外用户)
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://api.openai.com/v1

# DeepSeek 配置(国内用户推荐)
DEEPSEEK_API_KEY=sk-xxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-v4-pro
ヒント:この記事のサンプルは OpenAI と DeepSeek の両方をサポートしています。中国国内のユーザーには DeepSeek をお勧めします。API アドレスは次のように設定しますhttps://api.deepseek.com(/v1 なし)。LangChain が自動的にパスを連結します。DeepSeek API Key はhttps://platform.deepseek.com/api_keys作成できます。

インストールの確認

import langgraph
print(f"LangGraph 版本: {langgraph.__version__}")

最初のLangGraphプログラム

最も簡単な例から始めましょう。2つのノードだけの線形ワークフローです。

サンプル

from langgraph.graph import StateGraph, START, END
from typing import TypedDict

# Step 1: 定义 State
class SimpleState(TypedDict):
    message: str
    processed: bool

# Step 2: 定义节点函数
def greet_node(state: SimpleState) -> dict:
    """欢迎节点:生成问候语"""
    print(f"[greet_node] 收到消息: {state['message']}")
    return {"message": f"你好!{state['message']}"}

def process_node(state: SimpleState) -> dict:
    処理ノード:処理済みとしてマーク
    print(f"[process_node] 处理消息: {state['message']}")
    return {"processed": True}

# Step 3: 构建图
builder = StateGraph(SimpleState)

# 添加节点
builder.add_node("greet", greet_node)
builder.add_node("process", process_node)

# 添加边
builder.add_edge(START, "greet")
builder.add_edge("greet", "process")
builder.add_edge("process", END)

# Step 4: 编译图
graph = builder.compile()

# Step 5: 运行
result = graph.invoke({
    "message": "世界",
    "processed": False
})

print(f"\n最终结果: {result}")

実行結果:

[greet_node] 收到消息: 世界
[process_node] 处理消息: 你好!世界

最终结果: {'message': '你好!世界', 'processed': True}

グラフ構造の可視化

Jupyter Notebook ではグラフ構造を直接可視化できます:

# 在 Jupyter Notebook 中可视化
from IPython.display import Image
Image(graph.get_graph().draw_mermaid_png())

# 或者打印 Mermaid 格式
print(graph.get_graph().draw_mermaid())

State(状態管理)

TypedDictを使用して状態を定義

from typing import TypedDict, Annotated, Optional
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    # 消息历史(add_messages reducer 自动追加而非覆盖)
    messages: Annotated[list, add_messages]
    
    # 普通字段(直接覆盖)
    user_id: str
    session_id: str
    
    # 可选字段
    error: Optional[str]
    
    # 计数器(使用 operator.add 作为 reducer)
    retry_count: Annotated[int, lambda x, y: x + y]

Pydanticを使用して状態を定義(本番環境での利用を推奨)

from pydantic import BaseModel, Field
from typing import Annotated
from langgraph.graph.message import add_messages

class ProductionState(BaseModel):
    messages: Annotated[list, add_messages] = Field(default_factory=list)
    user_id: str = ""
    confidence_score: float = 0.0
    
    class Config:
        arbitrary_types_allowed = True

MessagesState(組み込みの簡易状態)

LangGraph は組み込みのMessagesStateMessagesState を提供しており、会話シナリオ向けに設計されています:

from langgraph.graph import MessagesState

# MessagesState 等价于:
# class MessagesState(TypedDict):
#     messages: Annotated[list[AnyMessage], add_messages]

# 直接使用,无需自定义
builder = StateGraph(MessagesState)

Nodes(ノード)

通常の関数ノード

def simple_node(state: AgentState) -> dict:
    # 读取状态
    last_message = state["messages"][-1]
    
    # 执行操作
    response = f"收到: {last_message.content}"
    
    # 返回部分状态更新
    return {
        "messages": [{"role": "assistant", "content": response}]
    }

LLM呼び出しノード

サンプル

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage

load_dotenv()

# 使用 DeepSeek 模型
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)

def llm_node(state: dict) -> dict:
    """LLM を呼び出すノード"""
    system_prompt = SystemMessage(content="あなたは役に立つアシスタントです。")
   
    # システムプロンプトと会話履歴を結合する
    messages = [system_prompt] + state["messages"]
   
    # 调用 LLM
    response = llm.invoke(messages)
   
    return {"messages": [response]}

非同期ノード

import asyncio

async def async_node(state: AgentState) -> dict:
    """异步节点,适合 I/O 密集型操作"""
    # 模拟异步操作(如 API 调用、数据库查询)
    await asyncio.sleep(0.1)
    
    result = await some_async_api_call(state["messages"][-1].content)
    return {"messages": [{"role": "assistant", "content": result}]}

# 使用异步图
result = await graph.ainvoke({"messages": [...]})

クラスをノードとして使用

class RouterNode:
    def __init__(self, llm, system_prompt: str):
        self.llm = llm
        self.system_prompt = system_prompt
    
    def __call__(self, state: AgentState) -> dict:
        """类实例可以作为节点使用"""
        messages = [
            SystemMessage(content=self.system_prompt),
            *state["messages"]
        ]
        response = self.llm.invoke(messages)
        return {"messages": [response]}

# 添加类节点
router = RouterNode(llm, "你是一个专业的路由助手。")
builder.add_node("router", router)

Edges(エッジ)と条件付きルーティング

通常のエッジ

# 固定路径:node_a 完成后始终执行 node_b
builder.add_edge("node_a", "node_b")

# 结束:node_a 完成后图结束
builder.add_edge("node_a", END)

条件付きエッジ

条件付きエッジは LangGraph の中心的な機能であり、現在の State に基づいて次のステップを動的に決定します。

def route_after_llm(state: AgentState) -> str:
    """
    路由函数:根据 LLM 的最新输出决定走哪条路径
    返回值必须是已注册节点名称或 END
    """
    last_message = state["messages"][-1]
    
    # 如果 LLM 请求使用工具
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    
    # 否则结束
    return END

# 添加条件边
builder.add_conditional_edges(
    "llm",              # 源节点
    route_after_llm,    # 路由函数
    {
        "tools": "tool_executor",   # 返回 "tools" 时 -> tool_executor 节点
        END: END                    # 返回 END 时 -> 结束
    }
)
背景 タイトル 条件付きルーティングの概要図 START 矢印 Router ノード Router 意図認識 条件分岐 天気 Agent weather_agent 天気検索 コード Agent code_agent コードヘルプ 汎用 Agent general_agent 汎用対話 別れ farewell 条件付きエッジ "天气" "代码" "其他" "再见" END すべての分岐は END へ 矢印の定義

並列実行(Fan-out)

# 从一个节点并行分叉到多个节点
builder.add_edge("start_node", "branch_a")
builder.add_edge("start_node", "branch_b")
builder.add_edge("start_node", "branch_c")

# 多个节点汇聚到一个节点(Fan-in)
builder.add_edge("branch_a", "merge_node")
builder.add_edge("branch_b", "merge_node")
builder.add_edge("branch_c", "merge_node")

完全な条件付きルーティングの例

サンプル

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# 初始化 LLM
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0.7
)

# 路由函数
def classify_intent(state: MessagesState) -> str:
    """ユーザーの意図に基づいて異なるAgentにルーティングする"""
    last_message = state["messages"][-1]
    content = last_message.content.lower()
   
    if "天气" in content or "温度" in content:
        return "weather_agent"
    elif "代码" in content or "编程" in content:
        return "code_agent"
    elif "再见" in content or "退出" in content:
        return "farewell"
    else:
        return "general_agent"

# 定义各个 Agent 节点
def router_node(state: MessagesState) -> dict:
    """路由节点:不做处理,只用于触发路由判断"""
    return {}

def weather_node(state: MessagesState) -> dict:
    """天气 Agent"""
    response = llm.invoke([
        SystemMessage(content="あなたは天気アシスタントです。天気に関する質問に友好のに答えてください。リアルタイムデータがない場合は、一般的なアドバイスを提供しても構いません。"),
        *state["messages"]
    ])
    return {"messages": [response]}

def code_node(state: MessagesState) -> dict:
    """代码 Agent"""
    response = llm.invoke([
        SystemMessage(content=あなたはプログラミングアシスタントであり、コード問題の解答と明確なコード例の提供を得意としています。),
        *state["messages"]
    ])
    return {"messages": [response]}

def general_node(state: MessagesState) -> dict:
    """通用 Agent"""
    response = llm.invoke([
        SystemMessage(content="あなたは親切なAIアシスタントで、さまざまな質問に答えることができます。"),
        *state["messages"]
    ])
    return {"messages": [response]}

def farewell_node(state: MessagesState) -> dict:
    """告别节点"""
    return {"messages": [{"role": "assistant", "content": さようなら!また次回お話しできることを楽しみにしています。}]}

# 构建图
builder = StateGraph(MessagesState)

# 添加节点
builder.add_node("router", router_node)
builder.add_node("weather_agent", weather_node)
builder.add_node("code_agent", code_node)
builder.add_node("general_agent", general_node)
builder.add_node("farewell", farewell_node)

# 添加边
builder.add_edge(START, "router")
builder.add_conditional_edges(
    "router",
    classify_intent,
    {
        "weather_agent": "weather_agent",
        "code_agent": "code_agent",
        "general_agent": "general_agent",
        "farewell": "farewell",
    }
)

# 所有 agent 节点处理完后结束
for node in ["weather_agent", "code_agent", "general_agent", "farewell"]:
    builder.add_edge(node, END)

# 编译图
graph = builder.compile()

# 测试不同意图
test_inputs = [
    "北京今天天气怎么样?",
    "帮我写一个 Python 快速排序",
    "你好,介绍一下你自己",
    "再见啦!"
]

for user_input in test_inputs:
    print(f"\n用户: {user_input}")
    result = graph.invoke({"messages": [HumanMessage(content=user_input)]})
    print(f"助手: {result['messages'][-1].content[:100]}...")
    print("-" * 50)

実行結果の例:

ユーザー: 北京今天天气怎么样?
アシスタント: 很抱歉,我没有实时天气数据。不过北京现在是春季,建议您出门时关注天气预报...
--------------------------------------------------

ユーザー: 帮我写一个 Python 快速排序
アシスタント: 好的,下面是一个 Python 实现的快速排序算法:

```python
def quicksort(arr):
    if len(arr) <= 1:
        return arr
    ...
--------------------------------------------------

ユーザー: 你好,介绍一下你自己
アシスタント: 你好!我是一个 AI 助手,很高兴为你服务。我可以帮你回答各种问题...
--------------------------------------------------

ユーザー: 再见啦!
アシスタント: 再见!期待下次与你交流。
--------------------------------------------------
注意:条件付きルーティング関数の戻り値は、グラフに登録済みのノード名またはENDEND である必要があります。未登録の名前を返すと、グラフの実行時にエラーが発生します。

チャットボットの構築

ここで学んだ知識を使って、マルチターン対話をサポートするボットを構築しましょう。このボットは会話のコンテキストを記憶し、連続した対話体験を実現できます。

サンプル

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# 初始化 LLM(使用 DeepSeek)
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0.7
)

SYSTEM_PROMPT = 「あなたは親切で専門のなAIアシスタントです。」
あなたの回答は:
- 简洁清晰
- 使用中文回复
- 不確かな場合は、ユーザーに積極的に質問する
"""


def chatbot_node(state: MessagesState) -> dict:
    ```コア対話ノード```
    system = SystemMessage(content=SYSTEM_PROMPT)
    messages = [system] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

# 构建图
builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot_node)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

graph = builder.compile()

# マルチターン対話関数
def chat(conversation_history: list, user_input: str) -> tuple[str, list]:
    シングルターン対話を処理し、AI応答と更新された履歴を返します
    conversation_history.append(HumanMessage(content=user_input))
    result = graph.invoke({"messages": conversation_history})
    conversation_history = result["messages"]
    ai_response = conversation_history[-1].content
    return ai_response, conversation_history

# マルチターン対話の例
history = []
while True:
    user_input = input("你: ")
    if user_input.lower() in ["退出", "exit", "quit"]:
        print("再见!")
        break
   
    response, history = chat(history, user_input)
    print(f"助手: {response}\n")

実行結果の例:

あなた: 你好,我叫小明
アシスタント: 你好,小明!很高兴认识你。有什么我可以帮助你的吗?

あなた: 我想学习 Python 编程
アシスタント: 太好了!Python 是一门很适合初学者的编程语言。我可以帮你从基础开始:
1. 首先了解变量和数据类型
2. 学习条件语句和循环
3. 掌握函数的定义和使用
你想从哪个部分开始呢?

あなた: 你还记得我叫什么吗?
アシスタント: 当然记得,你叫小明!你刚才说想学习 Python 编程,我们可以继续这个话题。

あなた: 退出
再见!

ツール呼び出し - ReAct Agent

Agent に外部ツールを使用する能力を持たせることは、LangGraph の最も強力な機能の1つです。ReAct(Reason + Act)は最も一般的な Agent パターンです:LLM が思考し -> ツールを選択し -> ツールを実行し -> 結果を観察し -> 思考を続けます。

背景 タイトル ReAct Agent のワークフロー START ノード START 矢印 START -> Agent Agent ノード Agent LLM 推論 条件判断のひし形 ツール呼び出しはありますか? 矢印 Agent -> 条件判断 Tool ノード Tools ツールの実行 矢印 条件判断 -> Tool (はい) である ループ矢印 Tool -> Agent 結果を返して推論を続行 END ノード END 矢印 条件判断 -> END (いいえ) 否 矢印の定義

ツールの定義

まず Agent が使用できるツールを定義します:

from langchain_core.tools import tool

@tool
def search_web(query: str) -> str:
    """搜索网络获取最新信息。
    
    Args:
        query: 搜索关键词
    
    Returns:
        搜索结果摘要
    """
    # 实际项目中替换为真实搜索 API
    return f"关于 '{query}' 的搜索结果:这是模拟的搜索结果..."

@tool
def calculate(expression: str) -> str:
    """计算数学表达式。
    
    Args:
        expression: 数学表达式,如 '2 + 2' 或 '100 * 0.8'
    
    Returns:
        计算结果
    """
    import ast
    import operator
    
    # 安全的运算符映射
    ops = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
    }
    
    def safe_eval(node):
        if isinstance(node, ast.Expression):
            return safe_eval(node.body)
        elif isinstance(node, ast.Constant):
            return node.value
        elif isinstance(node, ast.BinOp):
            left = safe_eval(node.left)
            right = safe_eval(node.right)
            return ops[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            operand = safe_eval(node.operand)
            return ops[type(node.op)](operand)
        else:
            raise ValueError(f"不支持的表达式类型: {type(node)}")
    
    try:
        tree = ast.parse(expression, mode='eval')
        result = safe_eval(tree)
        return f"计算结果: {expression} = {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。
    
    Args:
        city: 城市名称
    
    Returns:
        天气信息
    """
    # 实际项目中替换为真实天气 API
    return f"{city} 今日天气:晴,温度 22C,湿度 60%"

tools = [search_web, calculate, get_weather]
安全上の注意:calculate ツールは AST(抽象構文木)解析を代替として使用しますeval()。直接使用はeval()深刻なセキュリティリスクがあります。任意の Python コードを実行できるためです。本番環境では、必ず安全な式解析方式を使用してください。

ReAct Agentの構築

サンプル

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
import ast
import operator

load_dotenv()

# 定义工具
@tool
def search_web(query: str) -> str:
    """搜索网络获取最新信息。"""
    return f'{query}' の検索結果:これはシミュレーションされた検索結果です...

@tool
def calculate(expression: str) -> str:
    """计算数学表达式。"""
    ops = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
    }
   
    def safe_eval(node):
        if isinstance(node, ast.Expression):
            return safe_eval(node.body)
        elif isinstance(node, ast.Constant):
            return node.value
        elif isinstance(node, ast.BinOp):
            left = safe_eval(node.left)
            right = safe_eval(node.right)
            return ops[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            operand = safe_eval(node.operand)
            return ops[type(node.op)](operand)
        else:
            raise ValueError(f"サポートされていない式の型: {type(node)}")
   
    try:
        tree = ast.parse(expression, mode='eval')
        result = safe_eval(tree)
        return f"计算结果: {expression} = {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

@tool
def get_weather(city: str) -> str:
    """指定された都市の天気情報を取得します。"""
    return f"{city} 今日天气:晴,温度 22C,湿度 60%"

tools = [search_web, calculate, get_weather]

# 初始化 LLM 并绑定工具
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)
llm_with_tools = llm.bind_tools(tools)

def agent_node(state: MessagesState) -> dict:
    """Agent 推理节点:调用 LLM 决定下一步行动"""
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

# 构建 ReAct 图
builder = StateGraph(MessagesState)

# ノードを追加
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools))  # 組み込みの ToolNode がツール呼び出しを自動処理

# エッジを追加
builder.add_edge(START, "agent")

# 条件付きルーティング:LLM がツールを要求したらツールを実行し、それ以外は終了
builder.add_conditional_edges(
    "agent",
    tools_condition,  # 組み込みのルーティング関数
    {
        "tools": "tools",
        END: END
    }
)

# ツール実行後に agent に戻って推論を継続
builder.add_edge("tools", "agent")

graph = builder.compile()

# テスト
result = graph.invoke({
    "messages": [HumanMessage(content="今日の北京の天気は?あと 1234 * 5678 を計算して")]
})

for message in result["messages"]:
    print(f"[{message.type}]: {message.content[:200] if message.content else '(ツール呼び出し)'}")

ツール呼び出しプロセスのストリーミング出力

# 流式观察 Agent 的每一步
for chunk in graph.stream(
    {"messages": [HumanMessage(content="搜索 LangGraph 的最新特性")]},
    stream_mode="updates"
):
    for node_name, updates in chunk.items():
        print(f"\n=== 节点: {node_name} ===")
        for msg in updates.get("messages", []):
            if hasattr(msg, "tool_calls") and msg.tool_calls:
                for tc in msg.tool_calls:
                    print(f"  -> 调用工具: {tc['name']}({tc['args']})")
            else:
                print(f"  -> 输出: {msg.content[:200] if msg.content else '(无内容)'}")
ヒント:ToolNodeとtools_conditionは LangGraph の組み込みコンポーネントです。ToolNodeLLM が返したツール呼び出しを自動的に解析し、対応するツール関数を実行します。tools_conditionはルーティング関数であり、LLM の出力にツール呼び出しリクエストが含まれているかチェックし、含まれていれば "tools" を返し、それ以外は END を返します。

Human-in-the-Loop(人間とAIの協働)

LangGraph はワークフロー実行中に一時停止して、人間の承認や入力を待つことをネイティブにサポートしています。これは人間の確認が必要な機密操作に非常に役立ちます。

interruptを使用して実行を一時停止

from langgraph.types import interrupt
from langgraph.checkpoint.memory import MemorySaver

def sensitive_action_node(state: MessagesState) -> dict:
    """执行敏感操作前请求人工审批"""
    last_msg = state["messages"][-1].content
    
    # 暂停图的执行,等待人工决策
    human_decision = interrupt({
        "question": "是否批准执行以下操作?",
        "action": last_msg,
        "risk_level": "中等"
    })
    
    if human_decision == "approve":
        return {"messages": [{"role": "assistant", "content": "操作已批准并执行完毕。"}]}
    else:
        return {"messages": [{"role": "assistant", "content": "操作已取消。"}]}

# 必须使用 checkpointer 才能支持 interrupt
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

完全な承認ワークフロー

サンプル

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage

load_dotenv()

def request_node(state: MessagesState) -> dict:
    """ユーザーリクエストを受け取る"""
    return {"messages": state["messages"]}

def approval_node(state: MessagesState) -> dict:
    """承認ノード:一時停止して人間の承認を待つ"""
    last_msg = state["messages"][-1].content
   
    # 実行を一時停止し、人間の判断を待つ
    human_decision = interrupt({
        "question": "以下の操作の実行を承認しますか?",
        "action": last_msg
    })
   
    if human_decision == "approve":
        return {"messages": [{"role": "assistant", "content": f"操作が承認されました:{last_msg}"}]}
    else:
        return {"messages": [{"role": "assistant", "content": "操作は拒否されました。"}]}

def execute_node(state: MessagesState) -> dict:
    """実行ノード"""
    return {"messages": [{"role": "assistant", "content": "タスクの実行が完了しました!"}]}

# グラフを構築
builder = StateGraph(MessagesState)
builder.add_node("request", request_node)
builder.add_node("approval", approval_node)
builder.add_node("execute", execute_node)

builder.add_edge(START, "request")
builder.add_edge("request", "approval")
builder.add_edge("approval", "execute")
builder.add_edge("execute", END)

# checkpointer を使用してグラフをコンパイル
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 会話ごとに一意の thread_id を使用
config = {"configurable": {"thread_id": "approval-session-001"}}

# Step 1: グラフを起動すると、interrupt の箇所で一時停止する
print("=== Step 1: リクエストを送信 ===")
result = graph.invoke(
    {"messages": [HumanMessage(content="データベース内のすべてのテストデータを削除してください")]},
    config=config
)
print("グラフは一時停止し、承認を待っています...")

# Step 2: 人間の承認後、Command で実行を再開
print("\n"=== Step 2: 人間による承認 ===")
# 操作を承認
result = graph.invoke(
    Command(resume="approve"),
    config=config
)
print(f"最終結果: {result['messages'][-1].content}")

# 拒否する場合は、次を使用:
# graph.invoke(Command(resume="reject"), config=config)

エッジにブレークポイントを設定

# 另一种方式:在编译时指定断点
graph = builder.compile(
    checkpointer=checkpointer,
    interrupt_before=["sensitive_node"],   # 执行该节点前暂停
    # interrupt_after=["review_node"],     # 执行该节点后暂停
)
重要:checkpointerは、実現するためのinterrupt機能の前提条件です。checkpointer がないと、グラフは一時停止時の状態を保存できず、再開時に実行を継続することもできません。MemorySaver は開発テストに適しており、本番環境では SqliteSaver やその他の永続化ストレージを使用することをお勧めします。

永続メモリ

LangGraph は組み込みの状態永続化メカニズムを提供し、Agent がセッションをまたいで会話履歴を記憶できるようにします。

メモリストレージ(開発テスト向け)

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 使用 thread_id 区分不同会话
config_user_a = {"configurable": {"thread_id": "user-alice"}}
config_user_b = {"configurable": {"thread_id": "user-bob"}}

# Alice 的对话
graph.invoke({"messages": [HumanMessage(content="我叫 Alice")]}, config=config_user_a)
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_a)
# Agent 能记住:你叫 Alice

# Bob 的对话完全独立
graph.invoke({"messages": [HumanMessage(content="我叫什么名字?")]}, config=config_user_b)
# Agent 不知道 Bob 的名字(不同 thread_id)

SQLite永続ストレージ(ローカルプロジェクト向け)

# 安装依赖
# pip install langgraph-checkpoint-sqlite

from langgraph.checkpoint.sqlite import SqliteSaver

# 数据持久化到文件,程序重启后对话历史仍存在
with SqliteSaver.from_conn_string("./chat_memory.db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    
    config = {"configurable": {"thread_id": "persistent-chat"}}
    
    # 第一次运行
    graph.invoke({"messages": [HumanMessage(content="我叫张三")]}, config=config)
    
    # 程序重启后再次运行,记忆仍然存在
    result = graph.invoke(
        {"messages": [HumanMessage(content="你还记得我叫什么吗?")]},
        config=config
    )

会話履歴の表示

# 获取某个 thread 的完整状态历史
history = list(graph.get_state_history(config))

for snapshot in history:
    print(f"时间: {snapshot.created_at}")
    print(f"消息数: {len(snapshot.values['messages'])}")
    print("---")

# 获取当前状态
current_state = graph.get_state(config)
print(f"当前消息数: {len(current_state.values['messages'])}")
ヒント:thread_idはセッションの一意の識別子であり、異なるユーザーや異なる会話の状態を分離するために使用されます。同じ thread_id のすべての呼び出しは会話履歴を共有し、異なる thread_id の間は完全に独立しています。マルチユーザー環境では、通常、ユーザー ID またはセッション ID を thread_id として使用します。

マルチAgentシステム

LangGraph は、複数の専門化された Agent が連携して動作するよう調整するのが得意です。複雑なタスクを異なる専門 Agent に分割することで、より強力な問題解決能力を実現できます。

背景 タイトル マルチエージェント協調アーキテクチャ(Supervisor パターン) Supervisor ノード Supervisor スーパーバイザー - タスク割り当て Research Agent 情報収集 Writing Agent コンテンツ作成 Review Agent 品質レビュー Supervisor から各 Agent への矢印 各 Agent から Supervisor に戻る矢印(点線) 説明文 タスクを割り当て タスクを割り当て 結果を返す 結果を返す 下部の説明 Supervisor が各専門 Agent を調整し、各 Agent は完了後に結果を Supervisor に返して次のステップを決定します 矢印の定義

主従アーキテクチャ(Supervisorパターン)

サンプル

import os
from dotenv import load_dotenv
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# LLM を初期化
llm = ChatOpenAI(
    model=os.getenv('DEEPSEEK_MODEL', 'deepseek-v4-pro'),
    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),
    openai_api_base=os.getenv('DEEPSEEK_BASE_URL', 'https://api.deepseek.com'),
    temperature=0
)

# 専門 Agent を定義
def research_agent(state: MessagesState) -> dict:
    """研究 Agent:情報収集を担当"""
    system = SystemMessage(content="あなたはプロのリサーチャーです。情報の収集と整理を担当してください。重要な情報を簡潔に要約してください。")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def writing_agent(state: MessagesState) -> dict:
    """ライティング Agent:コンテンツ作成を担当"""
    system = SystemMessage(content="あなたはプロのライターです。既存の情報に基づいてコンテンツを作成してください。内容を明確で読みやすく保ってください。")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def review_agent(state: MessagesState) -> dict:
    """校閲 Agent:品質管理を担当"""
    system = SystemMessage(content="あなたはプロの編集者です。コンテンツの品質を審査し改善してください。問題点を指摘し、改善案を提示してください。")
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

# 主管 Agent がフローを決定
def supervisor_node(state: MessagesState) -> dict:
    """主管:各専門 Agent の作業を調整"""
    system = SystemMessage(content="""あなたはワークフローのスーパーバイザーです。
タスクの進行状況に応じて、次にどの Agent が処理すべきかを決定してください。
会話履歴を分析し、次のいずれかのみを返してください:RESEARCH、WRITING、REVIEW、FINISH
- RESEARCH:さらに情報を収集する必要がある
- WRITING:情報が十分であり、執筆を開始できる
REVIEW:執筆が完了し、レビューが必要
- FINISH:タスクが完了した
"""
)
    response = llm.invoke([system] + state["messages"])
    return {"messages": [response]}

def route_by_supervisor(state: MessagesState) -> str:
    """主管の決定に基づいてルーティング"""
    last_msg = state["messages"][-1].content.strip().upper()
   
    if "RESEARCH" in last_msg:
        return "research"
    elif "WRITING" in last_msg:
        return "writing"
    elif "REVIEW" in last_msg:
        return "review"
    else:
        return END

# マルチエージェントグラフを構築
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("research", research_agent)
builder.add_node("writing", writing_agent)
builder.add_node("review", review_agent)

builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_by_supervisor)

# 各専門家は完了後に主管に戻る
for agent in ["research", "writing", "review"]:
    builder.add_edge(agent, "supervisor")

graph = builder.compile()

# マルチエージェントの協調をテスト
result = graph.invoke({
    "messages": [HumanMessage(content="Python デコレータについての短い紹介記事を書いてください")]
})

print(=== マルチエージェント協調が完了しました ===)
for i, msg in enumerate(result["messages"]):
    print(f"\n[{i+1}] {msg.type}: {msg.content[:150]}...")

サブグラフ(Subgraph)

複雑なサブプロセスをサブグラフとしてカプセル化し、メイングラフで再利用します:

# 将复杂子流程封装为子图,在主图中复用
sub_builder = StateGraph(MessagesState)
sub_builder.add_node("step1", step1_node)
sub_builder.add_node("step2", step2_node)
sub_builder.add_edge(START, "step1")
sub_builder.add_edge("step1", "step2")
sub_builder.add_edge("step2", END)
sub_graph = sub_builder.compile()

# 在主图中使用子图
main_builder = StateGraph(MessagesState)
main_builder.add_node("preprocessing", preprocess_node)
main_builder.add_node("sub_workflow", sub_graph)  # 直接使用编译好的子图
main_builder.add_node("postprocessing", postprocess_node)

main_builder.add_edge(START, "preprocessing")
main_builder.add_edge("preprocessing", "sub_workflow")
main_builder.add_edge("sub_workflow", "postprocessing")
main_builder.add_edge("postprocessing", END)

main_graph = main_builder.compile()

LangGraph Studioでの可視化デバッグ

LangGraph Studio は公式提供のビジュアル開発環境であり、Agent の実行プロセスをリアルタイムで確認でき、開発とデバッグの効率を大幅に向上させます。

LangGraph CLIのインストール

pip install langgraph-cli -i https://mirrors.aliyun.com/pypi/simple/

プロジェクト設定ファイルの作成

プロジェクトのルートディレクトリに作成langgraph.json設定ファイル:

{
  "dependencies": ["."],
  "graphs": {
    "my_agent": "./my_agent.py:graph"
  },
  "env": ".env"
}

開発サーバーの起動

langgraph dev

起動後にアクセスhttp://localhost:8123ブラウザで LangGraph Studio を使用できます。

Studioの主な機能

  • リアルタイム可視化:ノードの実行プロセスをグラフィカルに表示し、ワークフローの状態を直感的に把握
  • 状態チェック:任意のノードで一時停止して現在の State を確認でき、問題の調査に便利
  • タイムトラベル:過去の実行ステップをリプレイし、各ステップの状態変化を追跡
  • ホットリロード:コードを変更するとグラフ構造が自動的に更新され、サービスの再起動は不要
環境要件:LangGraph Studio には Docker 環境が必要です。Docker Desktop がインストールされ、正常に動作していることを確認してから開発サーバーを起動してください。

ベストプラクティスとよくある質問

ベストプラクティス

状態設計のポイント

  • State を簡潔に保ち、必要なフィールドのみを含める
  • 複雑なフィールドには明確な reducer を定義する(例:add_messages)
  • 本番環境では Pydantic モデルを使用して状態タイプを検証する
  • State に過度に大きなオブジェクトを保存しないようにし、外部ストレージの使用を検討する

ノード設計のポイント

  • 各ノードは単一の責務を持たせ、テストと再利用を容易にする
  • ノード関数は冪等であるべき(同じ入力から同じ出力を生成する)
  • ノード内で渡された state を直接変更せず、新しい値を返す
  • I/O 集約型操作を処理するために非同期ノードを適切に使用する

エラーハンドリング

def robust_node(state: AgentState) -> dict:
    try:
        result = risky_operation(state)
        return {"messages": [result], "error": None}
    except Exception as e:
        return {
            "error": str(e),
            "messages": [{"role": "assistant", "content": f"操作失败: {e}"}]
        }

無限ループを避ける

def route_with_limit(state: AgentState) -> str:
    # 设置最大重试次数,防止无限循环
    if state.get("retry_count", 0) >= 3:
        return END
    
    if needs_retry(state):
        return "retry_node"
    return END

よくある質問(FAQ)

Q1:ノードの戻り値の形式が正しくない場合はどうすればよいですか?

# 错误:直接修改 state 对象
def bad_node(state):
    state["messages"].append(...)  # 不要直接修改
    return state

# 正确:返回需要更新的字段
def good_node(state):
    return {"messages": [new_message]}  # 只返回变更字段

Q2:ノード間で一時データを渡すにはどうすればよいですか?

一時データを State 定義に追加するか、アンダースコアプレフィックス規約を使用して内部フィールドとして扱います:

from typing import TypedDict

class PublicState(TypedDict):
    messages: list  # 对外暴露

class PrivateState(TypedDict):
    messages: list
    _internal_cache: dict  # 以下划线开头约定为内部使用

Q3:ノード実行プロセスをデバッグするには?

# 使用 stream 模式观察每个节点的输出
for event in graph.stream(initial_state, stream_mode="updates"):
    for node_name, state_update in event.items():
        print(f"\n[节点: {node_name}]")
        print(f"更新: {state_update}")

Q4:StateGraph と MessageGraph の違いは?

MessageGraphは初期バージョンのAPIであり、機能が比較的制限されています。現在は統一して以下を使用することを推奨しますStateGraph。これはより柔軟で、機能がより充実しています。メッセージを処理する必要がある場合は、以下を使用してくださいStateGraph(MessagesState)、または、以下を含むカスタムmessagesフィールドの State を使用してください。

注意:MessageGraphは廃止されました。統一して以下を使用してくださいStateGraph。コード内でまだ MessageGraph を使用している場合は、より良いサポートとより多くの機能を得るために、できるだけ早く StateGraph(MessagesState) へ移行することをお勧めします。

まとめ

本記事では、LangGraph フレームワークの核心概念と実践的な応用について体系的に紹介します。LangGraph はグラフ構造のワークフローオーケストレーションを通じて、開発者がループ、分岐、状態永続化の機能を備えた複雑な AI Agent を構築できるようにします。従来の線形 LLM Chain と比較して、LangGraph は多段階の推論、人機協働、マルチ Agent 連携が必要なシナリオにおいて顕著な利点があります。

概念 説明 主要コード
StateGraph 有向グラフワークフローエンジン、LangGraph の核心 StateGraph(MyState)
State ノード間で共有される状態データ構造 TypedDict + Annotated
Nodes 具体的な操作を実行する関数ノード builder.add_node()
Edges ノード間の遷移パス、条件分岐をサポート add_edge() / add_conditional_edges()
ReAct Agent 推論+行動のループパターン ToolNode + tools_condition
Human-in-Loop 人間による承認と介入メカニズム interrupt() + Command(resume=)
永続化 セッション記憶と状態保存 MemorySaver / SqliteSaver
マルチ Agent 複数専門家の協働システム Supervisor Pattern

推奨学習パス

背景 ステップ1 基本概念 矢印1 ステップ2 シンプルなワークフロー 矢印2 ステップ3 ReAct Agent 矢印3 ステップ4 人機協働 矢印4 ステップ5 マルチ Agent 矢印5 ステップ6 LangGraph Studio 矢印定義
その他の拡張