【langgraph 从入门到精通graphApi 篇】Checkpoint 持久化与状态管理

文章目录

    • [第 5 章:Checkpoint 持久化与状态管理](#第 5 章:Checkpoint 持久化与状态管理)
      • [5.1 本章目标](#5.1 本章目标)
      • [5.2 核心概念](#5.2 核心概念)
        • 持久化三要素
        • [Checkpoint 生命周期](#Checkpoint 生命周期)
        • [Thread 隔离模型](#Thread 隔离模型)
        • [三种 Checkpointer 对比](#三种 Checkpointer 对比)
      • [5.3 实战](#5.3 实战)
        • [实战 1:多会话隔离](#实战 1:多会话隔离)
        • [实战 2:断点续传](#实战 2:断点续传)
        • [实战 3:状态历史回溯](#实战 3:状态历史回溯)
        • [实战 4:update_state 手动修改状态](#实战 4:update_state 手动修改状态)
      • [5.4 API 速查](#5.4 API 速查)
      • [5.5 错误与避坑指南](#5.5 错误与避坑指南)
        • [坑 1:忘记传入 config](#坑 1:忘记传入 config)
        • [坑 2:混淆 config 和 input](#坑 2:混淆 config 和 input)
        • [坑 3:以为 update_state 是覆盖](#坑 3:以为 update_state 是覆盖)
        • [坑 4:生产环境用 MemorySaver](#坑 4:生产环境用 MemorySaver)
      • [5.6 最佳实践总结](#5.6 最佳实践总结)

第 5 章:Checkpoint 持久化与状态管理

5.1 本章目标

学完本章你将能够:

  1. 理解 LangGraph 持久化模型(Checkpoint、Thread、Super-step)
  2. 掌握 MemorySaver / SqliteSaver / PostgresSaver 的选择和使用
  3. 学会 get_state / update_state / get_state_history 的用法
  4. 实现多会话隔离和状态回溯

5.2 核心概念

持久化三要素
概念 说明 比喻
Checkpoint 每个 super-step 后自动保存的状态快照 游戏存档------每一步操作后自动保存
Thread 通过 thread_id 标识的独立对话线程 游戏存档槽------不同存档互不干扰
Super-step 图的一次完整"滴答"(该步内所有并行节点执行完毕) 游戏中的一回合
Checkpoint 生命周期

#mermaid-svg-QSoW6OTMiMyAXmMe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QSoW6OTMiMyAXmMe .error-icon{fill:#552222;}#mermaid-svg-QSoW6OTMiMyAXmMe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QSoW6OTMiMyAXmMe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .marker.cross{stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QSoW6OTMiMyAXmMe p{margin:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label text{fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label span{color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label span p{background-color:transparent;}#mermaid-svg-QSoW6OTMiMyAXmMe .label text,#mermaid-svg-QSoW6OTMiMyAXmMe span{fill:#333;color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .node rect,#mermaid-svg-QSoW6OTMiMyAXmMe .node circle,#mermaid-svg-QSoW6OTMiMyAXmMe .node ellipse,#mermaid-svg-QSoW6OTMiMyAXmMe .node polygon,#mermaid-svg-QSoW6OTMiMyAXmMe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .rough-node .label text,#mermaid-svg-QSoW6OTMiMyAXmMe .node .label text,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label,#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label{text-anchor:middle;}#mermaid-svg-QSoW6OTMiMyAXmMe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .rough-node .label,#mermaid-svg-QSoW6OTMiMyAXmMe .node .label,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label,#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label{text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .node.clickable{cursor:pointer;}#mermaid-svg-QSoW6OTMiMyAXmMe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .arrowheadPath{fill:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QSoW6OTMiMyAXmMe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster text{fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster span{color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QSoW6OTMiMyAXmMe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe rect.text{fill:none;stroke-width:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape p,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label rect,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QSoW6OTMiMyAXmMe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QSoW6OTMiMyAXmMe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可回溯
可回溯
可回溯
START
Step 1: agent 节点执行
Checkpoint 1

保存 State 快照
Step 2: tools 节点执行
Checkpoint 2

保存 State 快照
Step 3: agent 节点执行
Checkpoint 3

保存 State 快照
END

Thread 隔离模型

#mermaid-svg-fo7FLyfNxZjijxGj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fo7FLyfNxZjijxGj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fo7FLyfNxZjijxGj .error-icon{fill:#552222;}#mermaid-svg-fo7FLyfNxZjijxGj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fo7FLyfNxZjijxGj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .marker.cross{stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fo7FLyfNxZjijxGj p{margin:0;}#mermaid-svg-fo7FLyfNxZjijxGj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label text{fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label span{color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label span p{background-color:transparent;}#mermaid-svg-fo7FLyfNxZjijxGj .label text,#mermaid-svg-fo7FLyfNxZjijxGj span{fill:#333;color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .node rect,#mermaid-svg-fo7FLyfNxZjijxGj .node circle,#mermaid-svg-fo7FLyfNxZjijxGj .node ellipse,#mermaid-svg-fo7FLyfNxZjijxGj .node polygon,#mermaid-svg-fo7FLyfNxZjijxGj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .rough-node .label text,#mermaid-svg-fo7FLyfNxZjijxGj .node .label text,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label,#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label{text-anchor:middle;}#mermaid-svg-fo7FLyfNxZjijxGj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .rough-node .label,#mermaid-svg-fo7FLyfNxZjijxGj .node .label,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label,#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label{text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .node.clickable{cursor:pointer;}#mermaid-svg-fo7FLyfNxZjijxGj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .arrowheadPath{fill:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fo7FLyfNxZjijxGj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fo7FLyfNxZjijxGj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster text{fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster span{color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fo7FLyfNxZjijxGj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj rect.text{fill:none;stroke-width:0;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape p,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label rect,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fo7FLyfNxZjijxGj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fo7FLyfNxZjijxGj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Checkpointer
Thread: user-002
Thread: user-001
thread_id=user-001
thread_id=user-002
消息1
消息2
消息3
消息1
消息2
MemorySaver

或 SqliteSaver

或 PostgresSaver

三种 Checkpointer 对比
实现 数据存储 适用场景 优缺点
MemorySaver 内存 开发/测试 快、简单,但重启丢失
SqliteSaver SQLite 文件 本地开发/小规模部署 持久化、无需外部服务
PostgresSaver PostgreSQL 生产环境 高可用、支持并发、持久化

5.3 实战

实战 1:多会话隔离
python 复制代码
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage


class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    user_name: str


def remember_name(state: State) -> dict:
    """从消息中提取用户名"""
    last_msg = state["messages"][-1]
    if "叫" in last_msg.content:
        name = last_msg.content.split("叫")[-1].strip("。! ")
        return {"user_name": name, "messages": [AIMessage(content=f"好的,我记住了,你叫{name}!")]}
    return {"messages": [AIMessage(content="你好!")]}


# 创建 checkpointer
checkpointer = MemorySaver()

builder = StateGraph(State)
builder.add_node("remember", remember_name)
builder.add_edge(START, "remember")
builder.add_edge("remember", END)
graph = builder.compile(checkpointer=checkpointer)


# ============================================
# 两个独立的会话
# ============================================
# 会话 1:小明
config1 = {"configurable": {"thread_id": "user-001"}}
result1 = graph.invoke(
    {"messages": [HumanMessage(content="我叫小明")], "user_name": ""},
    config=config1,
)
print(f"会话1: {result1['user_name']}")  # 小明

# 会话 2:小红
config2 = {"configurable": {"thread_id": "user-002"}}
result2 = graph.invoke(
    {"messages": [HumanMessage(content="我叫小红")], "user_name": ""},
    config=config2,
)
print(f"会话2: {result2['user_name']}")  # 小红

# 验证隔离:回到会话1,状态仍然是小明
snapshot = graph.get_state(config1)
print(f"会话1 状态: {snapshot.values['user_name']}")  # 小明(不受会话2影响)
实战 2:断点续传

说明:下面的示例展示了正常执行流程。真正的断点续传场景是:执行到中途程序崩溃/服务重启,下一次调用时从上次 checkpoint 自动恢复。只要配置了 checkpointer,这个恢复过程是自动的,不需要额外代码。

python 复制代码
from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver


class State(TypedDict):
    step: int
    data: Annotated[list[str], operator.add]


def step_one(state: State) -> dict:
    print(f"  执行步骤 1...")
    return {"step": 1, "data": ["步骤1完成"]}


def step_two(state: State) -> dict:
    print(f"  执行步骤 2...")
    return {"step": 2, "data": ["步骤2完成"]}


def step_three(state: State) -> dict:
    print(f"  执行步骤 3...")
    return {"step": 3, "data": ["步骤3完成"]}


checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("step1", step_one)
builder.add_node("step2", step_two)
builder.add_node("step3", step_three)
builder.add_edge(START, "step1")
builder.add_edge("step1", "step2")
builder.add_edge("step2", "step3")
builder.add_edge("step3", END)
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "thread-resume"}}

# 执行(如果在某步中断,下次调用会从上次中断处继续)
result = graph.invoke({"step": 0, "data": []}, config=config)
print(f"最终 step: {result['step']}, data: {result['data']}")

# 查看状态快照
snapshot = graph.get_state(config)
print(f"当前状态: step={snapshot.values['step']}, next={snapshot.next}")  # next=() 表示已完成
实战 3:状态历史回溯
python 复制代码
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage


class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]


def chat(state: State) -> dict:
    last_msg = state["messages"][-1]
    return {"messages": [AIMessage(content=f"回复: {last_msg.content}")]}


checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("chat", chat)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "history-demo"}}

# 多轮对话
for i, msg in enumerate(["你好", "今天天气不错", "再见"]):
    graph.invoke({"messages": [HumanMessage(content=msg)]}, config=config)

# 查看所有历史状态
print("=== 对话历史 ===")
history = list(graph.get_state_history(config))
for i, snapshot in enumerate(reversed(history)):
    msgs = snapshot.values.get("messages", [])
    print(f"  Checkpoint {i}: {len(msgs)} 条消息, step={snapshot.metadata.get('step')}")

# 回溯到第 2 个 checkpoint
if len(history) >= 2:
    checkpoint_2 = history[-2]  # 倒数第 2 个
    cp_id = checkpoint_2.config["configurable"]["checkpoint_id"]
    replay_config = {"configurable": {"thread_id": "history-demo", "checkpoint_id": cp_id}}
    replay_state = graph.get_state(replay_config)
    print(f"\n回溯到 checkpoint 2:")
    for msg in replay_state.values.get("messages", []):
        print(f"  [{msg.type}] {msg.content}")
实战 4:update_state 手动修改状态
python 复制代码
# ... 接上面代码 ...

# 手动更新状态:在 thread 中插入一条修正消息
graph.update_state(
    config=config,
    values={"messages": [AIMessage(content="【已修正】之前的回复有误,正确回复是...")]},
    as_node="chat",  # 标记为来自 chat 节点
)

# 验证
snapshot = graph.get_state(config)
print(f"修正后消息数: {len(snapshot.values['messages'])}")

5.4 API 速查

API 完整签名 入参说明 返回值 说明
MemorySaver() MemorySaver() Checkpointer 内存存储(开发用)
SqliteSaver.from_conn_string(path) from_conn_string(path: str) path: SQLite 文件路径 Checkpointer SQLite 持久化
PostgresSaver(conn) PostgresSaver(conn) conn: psycopg2 连接 Checkpointer Postgres 持久化
compile(checkpointer=...) compile(checkpointer: Checkpointer) checkpointer: 检查点保存器 CompiledGraph 编译时注入持久化
.get_state(config) get_state(config: dict) config: {"configurable": {"thread_id": "..."}} StateSnapshot 获取最新状态
.get_state_history(config) get_state_history(config: dict) config: 同上 Iterator[StateSnapshot] 获取历史快照
.update_state(config, values) update_state(config, values, as_node) config: 配置; values: 更新值; as_node: 来源节点名(可选) dict 手动更新状态
config 参数 {"configurable": {"thread_id": str}} thread_id: 线程唯一标识 配置字典 所有方法都需要传入

5.5 错误与避坑指南

坑 1:忘记传入 config
python 复制代码
# ❌ 错误写法
graph.invoke({"messages": [...]})  # 没有 config!
# → 使用默认 thread_id,所有调用共享同一个线程

# ✅ 正确写法
config = {"configurable": {"thread_id": "user-123"}}
graph.invoke({"messages": [...]}, config=config)
坑 2:混淆 config 和 input
python 复制代码
# ❌ 错误写法
# 把 thread_id 放在 input 里
graph.invoke({"thread_id": "user-123", "messages": [...]})

# ✅ 正确写法
# config 和 input 是分开的!
config = {"configurable": {"thread_id": "user-123"}}
input_data = {"messages": [...]}
graph.invoke(input_data, config=config)
坑 3:以为 update_state 是覆盖
python 复制代码
# ❌ 误解
graph.update_state(config, {"messages": [new_msg]})
# 以为会用 new_msg 替换所有 messages
# 实际:add_messages Reducer 将 new_msg 追加到现有消息

# ✅ 正确理解
# update_state 的值会通过 Reducer 合并,并非直接覆盖
# 如果 State 字段有 Reducer(如 add_messages),会按 Reducer 逻辑合并
坑 4:生产环境用 MemorySaver
python 复制代码
# ❌ 错误:生产环境
checkpointer = MemorySaver()  # 服务重启后所有对话状态丢失!

# ✅ 正确:生产环境
from langgraph.checkpoint.postgres import PostgresSaver
import psycopg2

conn = psycopg2.connect(os.environ["DATABASE_URL"])
checkpointer = PostgresSaver(conn)
checkpointer.setup()  # 初始化表结构

5.6 最佳实践总结

  1. 开发用 MemorySaver,生产用 PostgresSaver:根据环境选择合适的持久化方案
  2. 每个用户/会话使用唯一 thread_id :如 f"user-{user_id}"f"session-{uuid.uuid4()}"
  3. 定期清理旧 checkpoint:避免存储无限膨胀(生产环境可设置 TTL)
  4. 使用 get_state_history 实现"撤销"功能:用户可以回到之前的对话状态
  5. update_state 用于人工修正:当 AI 输出有误时,可以手动插入修正消息
相关推荐
JoyCong199815 小时前
打破远程协助的安全信任困局,ToDesk AI审计功能自动操作留痕
网络·人工智能·科技·安全·电脑·远程工作
罗西的思考15 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (9)--- Reward Judging
人工智能·算法·机器学习
小保CPP15 小时前
OCR C++ Tesseract按行识别字符
c++·人工智能·ocr·模式识别·光学字符识别
小弥儿15 小时前
GitHub今日热榜 | 2026-07-19
人工智能·学习·github·知识图谱
犀利豆15 小时前
写 Mermaid 总在查语法?我做了个用一句话生成图的小工具 - text2mermaid
人工智能
墨舟的AI笔记15 小时前
端侧推理后端:ONNX Runtime 与跨平台执行提供方
人工智能
大模型码小白16 小时前
JAVA 集合框架进阶:List 与 Set 的深度解析与实战
java·开发语言·人工智能·windows·语言模型·list·ai编程
IT_陈寒16 小时前
为什么我的JavaScript异步代码总是不按顺序执行?
前端·人工智能·后端
石头dhf17 小时前
Claude code执行过程
人工智能