Skip to content

LangGraph 入门教程 ​

LangGraph 是由 LangChain 团队开发的一个低层级 Agent 编排框架,专为构建有状态(Stateful)、长时运行的 AI 工作流而设计。

与传统的线性 LLM 调用链不同,LangGraph 将工作流建模为有向图(Directed Graph):

  • 节点(Node):执行具体操作的函数(如调用 LLM、执行工具、处理数据)
  • 边(Edge):定义节点之间的流转路径,支持条件分支
  • 状态(State):在整个工作流中共享并传递的数据

开源地址:https://github.com/langchain-ai/langgraph。

3476ec11-af44-4b42-99a9-8942ffb9c5d3.webp

想象你正在指挥一场交响乐演出:传统的 LLM Chain 就像演奏一首从头到尾的曲子,只能顺序播放;而 LangGraph 则像一位指挥家,可以根据现场观众的反应随时调整演奏顺序,让某个乐章重复,或者跳转到特定段落。它让 AI 工作流拥有了"指挥"的智慧——能够循环、分支、回溯,真正实现复杂的自主决策。

为什么选择 LangGraph? ​

下表对比了传统 LLM Chain 与 LangGraph 的主要差异:

特性传统 LLM ChainLangGraph
工作流结构线性,单向执行图结构,支持循环
状态管理需手动管理内置状态持久化
条件路由实现复杂原生支持
人机协作需要额外开发内置支持 interrupt
多 Agent 协调实现困难一流支持
调试工具有限LangGraph Studio

适用场景 ​

  • 对话机器人:需要记忆多轮对话上下文
  • 自主 Agent:能够规划、使用工具、迭代思考
  • 多 Agent 系统:多个 AI 协同完成复杂任务
  • 审批工作流:需要人工审核的自动化流程
  • 研究助手:需要多步骤推理和信息检索

LangGraph 核心架构 StateGraph(状态图) State(共享状态)- 贯穿所有节点 START 起始节点 Node A 处理节点 Node B 决策节点 END 结束节点 Node C 备选分支 完成 需要更多处理 循环


核心概念 ​

在开始编写代码之前,先理解 LangGraph 的三大核心概念。

Graph(图) ​

Graph 是整个工作流的蓝图,定义了 Agent 的完整逻辑结构。它由节点(Nodes)和边(Edges)组成:

python
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 会传递给下一个节点。

python
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_messages 作为 reducer——新消息会追加到列表而不是覆盖。这是 LangGraph 状态管理的核心机制。

State 数据流转示意 State {msg: []} Node A 处理输入 State {msg: [A]} Node B 生成响应 State {msg: [A,B]} END 每个节点读取 State,执行操作,返回更新后的部分字段

Nodes(节点) ​

节点是普通的 Python 函数,接收当前 State,返回更新后的 State(部分字段)。

python
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 和相关依赖:

bash
# 创建虚拟环境(推荐)
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 Key ​

在项目根目录创建 .env 文件,同时配置 OpenAI 和 DeepSeek:

python
# .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 创建。

验证安装 ​

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

第一个 LangGraph 程序 ​

让我们从最简单的例子开始——一个只有两个节点的线性工作流。

实例 ​

python
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}")

运行结果:

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

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

可视化图结构 ​

在 Jupyter Notebook 中可以直接可视化图结构:

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

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

State 状态管理 ​

使用 TypedDict 定义状态 ​

python
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 定义状态(推荐用于生产) ​

python
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 提供了内置的 MessagesState,专为对话场景设计:

python
from langgraph.graph import MessagesState

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

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

Nodes 节点 ​

普通函数节点 ​

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

LLM 调用节点 ​

实例 ​

python
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]}

异步节点 ​

python
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": [...]})

使用类作为节点 ​

python
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 边与条件路由 ​

普通边 ​

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

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

条件边 ​

条件边是 LangGraph 的核心功能,根据当前 State 动态决定下一步。

python
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 意图识别 weather_agent 天气查询 code_agent 代码帮助 general_agent 通用对话 farewell "天气" "代码" "其他" "再见" END

并行执行(Fan-out) ​

python
# 从一个节点并行分叉到多个节点
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")

完整条件路由示例 ​

实例 ​

python
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 实现的快速排序算法:

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

用户: 你好,介绍一下你自己
助手: 你好!我是一个 AI 助手,很高兴为你服务。我可以帮你回答各种问题...
--------------------------------------------------

用户: 再见啦!
助手: 再见!期待下次与你交流。
--------------------------------------------------

**注意:**条件路由函数的返回值必须是已在图中注册的节点名称或 END。如果返回未注册的名称,图执行时会抛出错误。


构建对话机器人 ​

现在用所学知识构建一个支持多轮对话的机器人。这个机器人能够记住对话上下文,实现连续的交互体验。

实例 ​

python
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 编程
助手: 太好了!Python 是一门很适合初学者的编程语言。我可以帮你从基础开始:
1. 首先了解变量和数据类型
2. 学习条件语句和循环
3. 掌握函数的定义和使用
你想从哪个部分开始呢?

你: 你还记得我叫什么吗?
助手: 当然记得,你叫小明!你刚才说想学习 Python 编程,我们可以继续这个话题。

你: 退出
再见!

工具调用 - ReAct Agent ​

让 Agent 具备使用外部工具的能力是 LangGraph 最强大的特性之一。ReAct(Reason + Act)是最常见的 Agent 模式:LLM 思考 -> 选择工具 -> 执行工具 -> 观察结果 -> 继续思考。

ReAct Agent 工作流程 START Agent LLM 推理 有工具调用? Tools 执行工具 是 返回结果继续推理 END 否

定义工具 ​

首先定义 Agent 可以使用的工具:

python
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 ​

实例 ​

python
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 '(工具调用)'}")

流式输出工具调用过程 ​

python
# 流式观察 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 的内置组件。ToolNode 自动解析 LLM 返回的工具调用并执行对应的工具函数;tools_condition 是一个路由函数,检查 LLM 输出是否包含工具调用请求,有则返回 "tools",否则返回 END。


Human-in-the-Loop 人机协作 ​

LangGraph 原生支持在工作流执行过程中暂停,等待人工审核或输入。这对于需要人工确认的敏感操作非常有用。

使用 interrupt 暂停执行 ​

python
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)

完整的审批工作流 ​

实例 ​

python
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)

在边上设置断点 ​

python
# 另一种方式:在编译时指定断点
graph = builder.compile(
    checkpointer=checkpointer,
    interrupt_before=["sensitive_node"],   # 执行该节点前暂停
    # interrupt_after=["review_node"],     # 执行该节点后暂停
)

重要:checkpointer 是实现 interrupt 功能的前提条件。没有 checkpointer,图无法保存暂停时的状态,也就无法在恢复时继续执行。MemorySaver 适合开发测试,生产环境建议使用 SqliteSaver 或其他持久化存储。


持久化内存 ​

LangGraph 提供了内置的状态持久化机制,让 Agent 能够跨会话记住对话历史。

内存存储(适合开发测试) ​

python
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 持久化存储(适合本地项目) ​

bash
# 安装依赖
# 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
    )

查看对话历史 ​

python
# 获取某个 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,可以实现更强大的问题解决能力。

多 Agent 协作架构(Supervisor 模式) Supervisor 主管 - 任务分配 Research Agent 信息收集 Writing Agent 内容创作 Review Agent 质量审核 分配任务 分配任务 返回结果 返回结果 Supervisor 协调各专家 Agent,每个 Agent 完成后返回结果给 Supervisor 决定下一步

主从架构(Supervisor Pattern) ​

实例 ​

python
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

# 构建多 Agent 图

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()

# 测试多 Agent 协作

result = graph.invoke({

    "messages": [HumanMessage(content="请帮我写一篇关于 Python 装饰器的简短介绍文章")]

})

print("=== 多 Agent 协作完成 ===")

for i, msg in enumerate(result["messages"]):

    print(f"\n[{i+1}] {msg.type}: {msg.content[:150]}...")

子图(Subgraph) ​

将复杂子流程封装为子图,在主图中复用:

python
# 将复杂子流程封装为子图,在主图中复用
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 ​

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

创建项目配置文件 ​

在项目根目录创建 langgraph.json 配置文件:

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

启动开发服务器 ​

python
langgraph dev

启动后访问 http://localhost:8123 即可在浏览器中使用 LangGraph Studio。

Studio 主要功能 ​

  • 实时可视化:图形化展示节点执行过程,直观了解工作流状态
  • 状态检查:在任意节点暂停查看当前 State,方便排查问题
  • 时间旅行:回放历史执行步骤,追踪每一步的状态变化
  • 热重载:修改代码后自动更新图结构,无需重启服务

**环境要求:**LangGraph Studio 需要 Docker 环境支持。请确保已安装 Docker Desktop 并正常运行后再启动开发服务器。


最佳实践与常见问题 ​

最佳实践 ​

状态设计要点

  • 保持 State 精简,只包含必要字段
  • 为复杂字段定义明确的 reducer(如 add_messages)
  • 使用 Pydantic 模型在生产环境中验证状态类型
  • 避免在 State 中存储过大的对象,考虑使用外部存储

节点设计要点

  • 每个节点职责单一,便于测试和复用
  • 节点函数应该是幂等的(相同输入产生相同输出)
  • 避免在节点中直接修改传入的 state,而是返回新值
  • 合理使用异步节点处理 I/O 密集型操作

错误处理

python
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}"}]
        }

避免无限循环

python
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:节点返回值格式不对怎么办?

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

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

Q2:如何在节点之间传递临时数据?

将临时数据加入 State 定义,或使用下划线前缀约定为内部字段:

python
from typing import TypedDict

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

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

Q3:如何调试节点执行过程?

python
# 使用 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

推荐学习路径 ​

基础概念 简单工作流 ReAct Agent 人机协作 多 Agent LangGraph Studio

AI 思考中...

Token (词元)

Harness Engineering(驾驭工程)

基于 VitePress 构建,部署于 GitHub Pages