LangGraph 基本用法详解:从入门到实战

一、为什么需要 LangGraph

在构建基于大语言模型(LLM)的应用时,我们常常遇到这样的场景:一个问题可能需要多次调用模型才能解决;需要根据模型的输出决定下一步做什么;需要调用外部工具(搜索、数据库查询等);某些任务需要反复执行直到满足条件。

传统的 LangChain 采用线性链式调用(Chain),其执行路径是一条直线------先做 A,再做 B,最后做 C。这种模式简单直观,但无法表达环形控制和条件分支。而真实的 Agent 运行路径是环形且带分支的:模型可能直接回答,也可能请求调用工具,工具执行完结果还要回到模型,是否结束取决于模型上一轮的输出。

LangGraph 正是为了解决这个问题而诞生的。它将 AI 应用建模为一个有向图,其中:

  • 节点(Node) 执行具体的计算或调用
  • 边(Edge) 定义节点之间的流程走向
  • 状态(State) 在节点之间传递和累积数据

一句话概括:Chain 是单行道,Graph 是立交桥。


二、核心概念

2.1 State(状态)

状态是工作流的核心,用于存储和传递信息。在 LangGraph 中,状态通常是一个 Python 字典(通过 TypedDict 定义类型)。每个节点接收当前状态作为输入,返回一个字典表示对状态的更新。

python 复制代码
from typing import TypedDict

class AgentState(TypedDict):
    messages: list          # 对话消息历史
    current_step: str       # 当前执行步骤
    result: str             # 最终结果
    error: str | None       # 错误信息

状态的设计原则:节点之间只通过状态通信,不直接调用。这保证了代码的可测试性和可维护性。

2.2 Nodes(节点)

节点是执行实际工作的函数。每个节点接收状态作为参数,返回一个字典用于更新状态。节点函数的签名如下:

python 复制代码
def my_node(state: AgentState) -> dict:
    # 1. 从状态中读取数据
    messages = state["messages"]
    
    # 2. 执行某些操作(调用LLM、调用工具等)
    result = do_something(messages)
    
    # 3. 返回增量字典,只更新需要修改的字段
    return {
        "messages": messages + [result],
        "current_step": "done"
    }

重要约定:节点函数应返回"增量字典"(只包含需要更新的字段),而不是返回整个状态对象。这样做的好处是:后续如果有并发分支、reducer、子图拼接等操作时,状态合并更加稳定可靠。

2.3 Edges(边)

边定义节点之间的连接关系,控制流程的执行顺序。LangGraph 提供两种边:

普通边(Normal Edge):无条件地从源节点跳转到目标节点。

python 复制代码
from langgraph.graph import StateGraph, START, END

workflow = StateGraph(AgentState)
workflow.add_node("step1", step1_func)
workflow.add_node("step2", step2_func)

# 普通边:step1 执行完一定去 step2
workflow.add_edge(START, "step1")
workflow.add_edge("step1", "step2")
workflow.add_edge("step2", END)

条件边(Conditional Edge):根据路由函数的返回值动态决定下一个节点。

python 复制代码
def router(state: AgentState) -> str:
    if state["current_step"] == "error":
        return "retry"
    elif state["current_step"] == "done":
        return "end"
    return "continue"

workflow.add_conditional_edges(
    "process",
    router,
    {
        "retry": "retry_node",
        "continue": "process",
        "end": END
    }
)

2.4 Checkpointer(检查点)

检查点是 LangGraph 的持久化层,它能够在图执行过程中保存状态快照。有了检查点,你可以实现:

  • 断点续跑:在特定节点暂停,人工审核后继续执行
  • 时间旅行:回到过去的某个状态,探索不同的执行路径
  • 多轮对话记忆:跨请求保留对话历史
  • 容错恢复:服务重启后从上次检查点恢复执行

三、环境安装

bash 复制代码
# 安装 LangGraph 核心包
pip install langgraph

# 如果需要与 LangChain 集成
pip install langchain langchain-openai

# 如果需要 SQLite 持久化(开发环境推荐)
pip install langgraph-checkpoint-sqlite

# 如果需要 PostgreSQL 持久化(生产环境推荐)
pip install langgraph-checkpoint-postgres

# 验证安装
python -c "import langgraph; print(langgraph.__version__)"

四、快速入门:最小可运行示例

让我们从一个最简单的示例开始------创建一个问候节点,输入名字,输出问候语。

python 复制代码
from typing import TypedDict
from langgraph.graph import StateGraph, START, END

# 1. 定义状态
class State(TypedDict):
    messages: str

# 2. 定义节点函数
def greeting_node(state: State) -> dict:
    """问候节点:拼接问候语"""
    return {"messages": "Hello! " + state["messages"]}

# 3. 构建图
graph = StateGraph(State)

# 添加节点
graph.add_node("greeting", greeting_node)

# 添加边:START -> greeting -> END
graph.add_edge(START, "greeting")
graph.add_edge("greeting", END)

# 4. 编译图
app = graph.compile()

# 5. 执行
result = app.invoke({"messages": "World"})
print(result)
# 输出: {'messages': 'Hello! World'}

这个最小示例展示了 LangGraph 应用的完整生命周期:定义状态 -> 定义节点 -> 连接边 -> 编译 -> 执行。


五、核心 API 详解

5.1 StateGraph

StateGraph 是 LangGraph 的核心类,用于构建有状态的工作流图。

python 复制代码
from langgraph.graph import StateGraph

# 创建图实例,传入状态类型
workflow = StateGraph(State)

5.2 add_node

向图中添加一个节点。节点可以是普通函数、Lambda 表达式、LangChain Runnable 对象等。

python 复制代码
# 方式1:普通函数
def agent_node(state: State) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

workflow.add_node("agent", agent_node)

# 方式2:Lambda 函数(适合简单逻辑)
workflow.add_node("simple", lambda state: {"result": "done"})

# 方式3:LangChain Runnable(LCEL 管道)
from langchain_core.runnables import RunnableLambda
chain = ChatPromptTemplate.from_template("回答: {question}") | ChatOpenAI() | StrOutputParser()
workflow.add_node("qa", chain)

# 方式4:异步节点
async def async_agent(state: State) -> dict:
    response = await llm.ainvoke(state["messages"])
    return {"messages": [response]}

workflow.add_node("async_agent", async_agent)

5.3 add_edge

添加普通边,从源节点无条件跳转到目标节点。

python 复制代码
# 线性流程:START -> step1 -> step2 -> step3 -> END
workflow.add_edge(START, "step1")
workflow.add_edge("step1", "step2")
workflow.add_edge("step2", "step3")
workflow.add_edge("step3", END)

5.4 add_conditional_edges

添加条件边,根据路由函数的返回值选择目标节点。这是实现 Agent 工具调用循环的关键。

python 复制代码
from langgraph.graph import MessagesState
from langgraph.prebuilt import ToolNode

# 定义路由函数
def should_continue(state: MessagesState) -> str:
    last_message = state["messages"][-1]
    # 模型请求了工具调用 -> 去工具节点
    # 否则 -> 结束
    if last_message.tool_calls:
        return "tools"
    return "end"

# 添加条件边
workflow.add_conditional_edges(
    "llm_call",           # 源节点
    should_continue,      # 路由函数
    {                     # 映射字典
        "tools": "tools", # 返回"tools" -> 跳转到tools节点
        "end": END        # 返回"end" -> 结束
    }
)

5.5 compile

编译图,生成可执行的应用程序。这是所有节点和边定义完成后的最后一步。

python 复制代码
# 基础编译
app = workflow.compile()

# 带检查点的编译(支持持久化)
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)

六、完整示例:带工具调用的 Agent

这是 LangGraph 最经典的使用场景------构建一个能调用工具的对话 Agent。

python 复制代码
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode

# 1. 定义工具
@tool
def search(query: str) -> str:
    """搜索互联网获取信息"""
    return f"搜索结果: 关于'{query}'的搜索结果..."

@tool
def calculator(expression: str) -> str:
    """执行数学计算"""
    result = eval(expression)
    return f"计算结果: {result}"

# 2. 定义状态
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]

# 3. 构建 Agent 循环
llm_with_tools = ChatOpenAI(model="gpt-4").bind_tools([search, calculator])

def llm_call(state: AgentState) -> dict:
    """调用 LLM"""
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: AgentState) -> str:
    """条件路由:判断是否需要调用工具"""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tools"
    return "end"

# 4. 构建图
workflow = StateGraph(AgentState)
workflow.add_node("llm", llm_call)
workflow.add_node("tools", ToolNode([search, calculator]))

# 添加边
workflow.add_edge(START, "llm")
workflow.add_conditional_edges(
    "llm",
    should_continue,
    {"tools": "tools", "end": END}
)
workflow.add_edge("tools", "llm")  # 工具执行完回到模型,形成循环

# 5. 编译并执行
agent = workflow.compile()
result = agent.invoke({"messages": [("user", "帮我计算 2+2 并搜索最新的AI新闻")]})
print(result["messages"][-1].content)

这个 Agent 的工作流程如下:

复制代码
START -> llm (有tool_calls) -> tools -> llm (无tool_calls) -> END
                          |
                          v
                      (继续循环...)

七、状态管理与 Reducer

7.1 默认覆盖语义

使用 TypedDict 时,节点返回的字段会覆盖状态中的旧值:

python 复制代码
class State(TypedDict):
    counter: int

def increment(state):
    return {"counter": state["counter"] + 1}  # 覆盖旧值

7.2 归并语义(Reducer)

对于列表类型的字段,我们通常需要追加 而非覆盖。LangGraph 提供了 Annotated 机制来声明归并行为:

python 复制代码
from typing import Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    # add_messages 是一个 reducer,自动追加新消息
    messages: Annotated[list, add_messages]
    
    # 自定义 reducer:列表追加
    history: Annotated[list, lambda x, y: x + y]
    
    # 自定义 reducer:字典合并
    config: Annotated[dict, lambda x, y: {**x, **y}]

常见的内置 reducer:

Reducer 行为 适用场景
add_messages 追加消息到列表 对话历史管理
operator.add 数值累加 计数器
lambda x,y: x+y 列表拼接 日志收集
lambda x,y: {**x,**y} 字典合并 配置更新

7.3 消息裁剪

对话历史可能无限增长,需要通过裁剪来控制状态大小:

python 复制代码
from langchain_core.messages import AnyMessage, RemoveMessage
from langgraph.graph import START

MAX_MESSAGES = 20

def trim_messages(state):
    """裁剪消息列表,只保留最近 N 条"""
    msgs = state["messages"]
    if len(msgs) <= MAX_MESSAGES:
        return {}  # 不需要裁剪
    
    # 标记要删除的消息
    to_remove = [RemoveMessage(id=m.id) for m in msgs[:-MAX_MESSAGES]]
    return {"messages": to_remove}

# 在图中添加裁剪节点
workflow.add_node("trim", trim_messages)
workflow.add_edge(START, "trim")
workflow.add_edge("trim", "agent")

八、持久化与检查点

8.1 内存检查点(开发环境)

python 复制代码
from langgraph.checkpoint.memory import MemorySaver

# 创建内存检查点存储器
checkpointer = MemorySaver()

# 编译时传入检查点
app = workflow.compile(checkpointer=checkpointer)

# 使用线程配置执行
config = {"configurable": {"thread_id": "user-123"}}
result = app.invoke({"messages": [("user", "你好")]}, config=config)

8.2 SQLite 检查点(单机部署)

bash 复制代码
pip install langgraph-checkpoint-sqlite
python 复制代码
from langgraph.checkpoint.sqlite import SqliteSaver
import sqlite3

# 方式1:文件路径
saver = SqliteSaver.from_conn_string("checkpoints.db")

# 方式2:配合 sqlite3 连接(多线程安全)
conn = sqlite3.connect("checkpoints.db", check_same_thread=False)
saver = SqliteSaver(conn)

app = workflow.compile(checkpointer=saver)

8.3 PostgreSQL 检查点(生产环境)

bash 复制代码
pip install langgraph-checkpoint-postgres
python 复制代码
from langgraph.checkpoint.postgres import PostgresSaver

DB_URI = "postgresql://user:pwd@localhost:5432/agentdb"

with PostgresSaver.from_conn_string(DB_URI) as saver:
    saver.setup()  # 首次运行需要建表
    app = workflow.compile(checkpointer=saver)

8.4 断点与恢复

检查点最强大的能力是支持断点续跑------在特定节点暂停执行,等待外部干预后继续:

python 复制代码
from langgraph.types import interrupt, Command

def sensitive_operation(state):
    """需要人工审批的节点"""
    # 暂停执行,等待外部决策
    decision = interrupt({
        "action": "refund",
        "amount": 299.0,
        "user_id": state["user_id"]
    })
    
    if decision == "approve":
        return {"result": "refund_processed"}
    return {"result": "refund_rejected"}

# 编译时指定在哪些节点前/后暂停
app = workflow.compile(
    checkpointer=checkpointer,
    interrupt_before=["sensitive_operation"]
)

# 第一次调用:执行到 interrupt 处暂停
config = {"configurable": {"thread_id": "order-888"}}
result = app.invoke(
    {"messages": [("user", "我要退款")], "user_id": "u123"},
    config=config
)
# 此时 result 中包含 __interrupt__ 信息

# 恢复执行:传入 resume 命令
result = app.invoke(
    Command(resume="approve"),
    config=config
)

8.5 状态历史与时间旅行

python 复制代码
# 获取当前状态
snapshot = app.get_state(config)
print(snapshot.values)    # 当前状态值
print(snapshot.next)      # 下一步要执行的节点

# 遍历状态历史
for snap in app.get_state_history(config):
    print(snap.config["configurable"]["checkpoint_id"], snap.next)

# 时间旅行:回到过去的某个快照
history = list(app.get_state_history(config))
old_snapshot = history[2]  # 回到第3个快照

# 从旧快照分叉出新分支
forked_config = {
    "configurable": {
        "thread_id": "user-123",
        "checkpoint_id": old_snapshot.config["configurable"]["checkpoint_id"]
    }
}
# 在新分支上继续执行,不影响原分支
result = app.invoke(
    {"messages": [("user", "换个思路重新分析")]},
    config=forked_config
)

# 手动更新状态
app.update_state(config, {"answer": "手动修正后的答案"})

九、流式传输

LangGraph 支持流式输出,可以在节点执行过程中逐步返回结果,显著提升用户体验:

python 复制代码
# 方式1:values 模式 - 返回每个节点的完整状态
for event in app.stream(input, stream_mode="values"):
    print(event)

# 方式2:updates 模式 - 返回每个节点的增量更新
for event in app.stream(input, stream_mode="updates"):
    print(event)

# 方式3:messages 模式 - 只流式返回消息内容
for event in app.stream(input, stream_mode="messages"):
    print(event)

# 方式4:custom 模式 - 使用自定义 writer
from langgraph.types import StreamWriter

def my_node(state, writer: StreamWriter):
    writer({"type": "progress", "step": "processing"})
    result = do_something(state)
    writer({"type": "result", "data": result})
    return {"result": result}

十、子图与嵌套

LangGraph 支持将子图嵌入父图,实现模块化设计:

python 复制代码
from typing import TypedDict

# 1. 定义子图状态
class SubgraphState(TypedDict):
    input_text: str
    processed_text: str

# 2. 定义子图节点
def preprocess(state: SubgraphState) -> dict:
    return {"processed_text": state["input_text"].strip().lower()}

def analyze(state: SubgraphState) -> dict:
    return {"processed_text": state["processed_text"] + " [analyzed]"}

# 3. 构建子图
subgraph = StateGraph(SubgraphState)
subgraph.add_node("preprocess", preprocess)
subgraph.add_node("analyze", analyze)
subgraph.add_edge(START, "preprocess")
subgraph.add_edge("preprocess", "analyze")
subgraph.add_edge("analyze", END)

compiled_subgraph = subgraph.compile()

# 4. 在父图中使用子图
class ParentState(TypedDict):
    text: str
    result: str

def parent_node(state: ParentState) -> dict:
    # 调用子图
    sub_result = compiled_subgraph.invoke({"input_text": state["text"]})
    return {"result": sub_result["processed_text"]}

parent_graph = StateGraph(ParentState)
parent_graph.add_node("process", parent_node)
parent_graph.add_edge(START, "process")
parent_graph.add_edge("process", END)

app = parent_graph.compile()
result = app.invoke({"text": "  Hello World  "})
print(result)
# 输出: {'result': 'hello world [analyzed]'}

子图状态传播规则:

  • 子图可以定义自己的状态字段,与父图隔离
  • 通过输入/输出映射,子图可以与父图共享特定字段
  • 检查点会自动从父图传播到子图,无需单独配置

十一、并行执行

LangGraph 支持扇出(Fan-out)和扇入(Fan-in)模式,实现多路并行执行:

python 复制代码
from typing import TypedDict, List

class ParallelState(TypedDict):
    query: str
    results: List[str]

def search_web(state: ParallelState) -> dict:
    return {"results": [f"Web搜索: {state['query']}"]}

def search_database(state: ParallelState) -> dict:
    return {"results": [f"数据库查询: {state['query']}"]}

def search_knowledge_base(state: ParallelState) -> dict:
    return {"results": [f"知识库检索: {state['query']}"]}

def merge_results(state: ParallelState) -> dict:
    # 合并所有并行结果
    all_results = state["results"]
    return {"results": all_results + ["合并完成"]}

# 构建并行图
workflow = StateGraph(ParallelState)
workflow.add_node("web", search_web)
workflow.add_node("db", search_database)
workflow.add_node("kb", search_knowledge_base)
workflow.add_node("merge", merge_results)

# 扇出:从START同时到三个搜索节点
workflow.add_edge(START, "web")
workflow.add_edge(START, "db")
workflow.add_edge(START, "kb")

# 扇入:三个搜索节点都到合并节点
workflow.add_edge("web", "merge")
workflow.add_edge("db", "merge")
workflow.add_edge("kb", "merge")

workflow.add_edge("merge", END)

app = workflow.compile()
result = app.invoke({"query": "LangGraph教程", "results": []})
print(result)

十二、最佳实践与常见陷阱

12.1 状态设计原则

  1. 最小化状态:只保留节点间通信必需的字段
  2. 不可变更新:节点返回增量字典,不要直接修改状态对象
  3. 类型安全 :始终使用 TypedDict 定义状态结构
  4. 归并语义 :列表字段使用 Annotated 声明追加行为

12.2 常见错误与排查

问题现象 可能原因 解决方案
多轮对话丢失记忆 每次 thread_id 不同 使用稳定的 thread_id(如用户ID)
服务重启后记忆丢失 使用了 InMemorySaver 切换到 SQLite 或 PostgreSQL
resume 报找不到线程 thread_id 不一致 确保两次调用使用相同的 thread_id
状态越来越大、越来越慢 消息无限累积 定期裁剪或摘要历史消息
PostgreSQL 报缺表 未调用 setup() 首次运行执行 saver.setup()
SQLite 报线程错误 多线程共享连接 设置 check_same_thread=False
节点函数返回整个 state 反模式 返回增量字典即可

12.3 性能优化建议

  1. 异步优先 :对于 I/O 密集型节点,使用 async def 定义节点
  2. 连接池:生产环境使用连接池管理数据库连接
  3. 消息裁剪:定期裁剪对话历史,控制状态大小
  4. 批量处理:利用并行扇出同时执行多个独立任务
  5. 缓存策略:对重复查询结果做缓存,减少 LLM 调用次数

12.4 调试技巧

python 复制代码
# 1. 可视化图结构
from langchain_community.graphs import NetworkxEntityGraph
graph = app.get_graph()
graph.draw_mermaid_png("workflow.png")

# 2. 查看执行历史
for event in app.stream(input, stream_mode="updates"):
    print(event)

# 3. 使用 LangSmith 追踪
import langsmith
langsmith.trace("my_agent", graph=app)

十三、总结

LangGraph 通过将 AI 应用建模为有向图,解决了传统链式调用无法表达复杂控制流的问题。其核心抽象------状态(State)、节点(Node)、边(Edge)------提供了清晰且强大的编程模型。

本文涵盖了 LangGraph 的核心用法:

  • 基础构建:StateGraph、节点、边的定义与连接
  • 条件路由:实现 Agent 工具调用循环的关键机制
  • 状态管理:Reducer 归并语义与消息裁剪策略
  • 持久化:Checkpointer 实现断点续跑与时间旅行
  • 流式传输:提升用户体验的实时输出能力
  • 子图嵌套:模块化设计复杂工作流
  • 并行执行:扇出扇入模式提升处理效率

掌握这些核心概念后,你可以构建从简单的对话机器人到复杂的多 Agent 协作系统等各种 AI 应用。建议从最小可运行示例开始,逐步添加条件和循环逻辑,最终构建出生产级别的工作流应用。

相关推荐
威哥爱编程1 小时前
Python 智能体开发实战 04:模型答得挺漂亮,程序却读不懂?让回答直接变成能入库的 JSON
python·agent
全栈陈序员1 小时前
【Python】基础语法入门(三十)—— Web 开发入门:用 Flask 搭建第一个网站
前端·python·flask
奔跑的熊猫1 小时前
VSCode + FastAPI +AI Agent+langchain第一个程序
langchain·fastapi
全栈陈序员1 小时前
【Python】基础语法入门(三十三)—— 日志系统详解:用 logging 记录程序运行
java·开发语言·python
晗夜未央1 小时前
跨仓库 Skill 换台电脑就失效?用 Git remote 校验项目
人工智能·git·python
Zack_Liu2 小时前
code样例
pytorch·python·深度学习
wusam2 小时前
基于HCL的python脚本编程(4)
网络·python·智能路由器
treasure1402 小时前
Django复习笔记(2026年10月)
后端·python·django·api·web·drf
xiaoqiangclub2 小时前
Python 的实验性 JIT 要生变?那个开关现在还该不该开
python·性能优化·jit·开发环境·cpython