【LangGraph】线程级持久化深度实战(PostgreSQL + 重放机制)

【LangGraph】新篇章:LangGraph 持久化(Persistence)*重点*

上一章-> 【LangGraph 持久化】让 AI Agent 拥有"记忆"

前言

上一章我们留了一个尾巴:InMemorySaver 方便但重启即丢,生产环境推荐用 PostgresSaver

这一章我们就围绕 PostgreSQL + 线程级持久化 展开,用一个完整的"搜索助手"案例,演示:

  1. 如何配置 PostgresSaver

  2. 如何实现多轮对话的自动记忆

  3. 如何查看和回溯历史状态

  4. 如何利用重放(Replay)机制调试 Agent

代码基于之前发布的------>【LangGraph】实战:支持搜索的智能代理系统(Agent + Tool)


一、为什么选择 PostgreSQL

  1. LangGraph 状态存储方式对比

我们可以看个表格:

存储方式 优点 缺点 适用场景
InMemorySaver 零配置、速度极快、无需依赖 重启程序丢失所有数据 本地调试、Demo 演示、单元测试
SqliteSaver 单文件、轻量、无需服务 并发能力差、不支持分布式、生产慎用 单机小工具、个人本地项目
PostgresSaver 官方原生支持、稳定可靠、分布式兼容、持久化不丢失 需要部署数据库服务 生产环境首选、正式项目、多人协作
MySQL 无官方适配 需要手动实现存储层、兼容性差、容易出问题 不推荐使用

总结一下:

  1. LangGraph 官方只完整适配 PostgreSQL ,包含状态表结构、序列化、中断恢复、查询优化

    MySQL 无官方支持,强行使用成本极高

  2. 正式开发/生产环境 → 直接用 PostgreSQL + Docker 一键部署

Docker 启动 PostgreSQL 的命令,复制到终端运行(仅供参考):

bash 复制代码
# . 运⾏ PostgreSQL 容器
# --name postgres-sql: 给容器命名
# -e POSTGRES_PASSWORD=123: 设置PostgreSQL的postgres⽤⼾密码
# -p 5432:5432: 将容器的5432端⼝映射到宿主机的5432端⼝
# -d: 后台运⾏
docker run 
--name postgres-sql 
-e POSTGRES_PASSWORD=123 
-p 5432:5432 
-d postgres

二、完整代码:支持持久化的搜索助手

以下代码是【LangGraph】实战:支持搜索的智能代理系统(Agent + Tool)的直接升级版,

只增加了 PostgresSaver 和状态查看逻辑

python 复制代码
import operator
from typing import TypedDict, Annotated

from langchain.chat_models import init_chat_model
from langchain_core.messages import AnyMessage, SystemMessage, ToolMessage, HumanMessage
from langchain_tavily import TavilySearch
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.constants import START, END
from langgraph.graph import StateGraph

# ========== 1. 初始化模型和工具 ==========
search = TavilySearch(max_results=4)
tools = [search]
model = init_chat_model("gpt-4o-mini", temperature=0)
model_with_tool = model.bind_tools(tools)

# ========== 2. 定义状态 ==========
class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]
    llm_calls: int

# ========== 3. 定义节点 ==========
def llm_call(state: MessagesState):
    """LLM 决定是否调用工具"""
    result = model_with_tool.invoke(
        [SystemMessage(content="你是一位乐于助人的助手,支持搜索工具")] + state["messages"]
    )
    return {
        "messages": [result],
        "llm_calls": state.get("llm_calls", 0) + 1
    }

tools_by_name = {tool.name: tool for tool in tools}

def tool_node(state: MessagesState):
    """执行工具调用"""
    result = []
    last_message = state["messages"][-1]
    for tool_call in last_message.tool_calls:
        tool = tools_by_name[tool_call["name"]]
        obs = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=obs, tool_call_id=tool_call["id"]))
    return {"messages": result}

def should_continue(state: MessagesState):
    """条件边:判断是否还要调用工具"""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tool_node"
    return END

# ========== 4. 构建图 ==========
builder = StateGraph(MessagesState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", tool_node)

builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "llm_call")

# ========== 5. 编译图(绑定 PostgreSQL) ==========
DB_URI = "postgresql://postgres:***@localhost:5432/postgres"

with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    # 第一次运行需要建表
    checkpointer.setup()
    
    agent = builder.compile(checkpointer=checkpointer)
    
# ========== 6. 演示多轮记忆 ==========
    config = {"configurable": {"thread_id": "user_123"}}
    
    print("=== 第一轮对话 ===")
    result1 = agent.invoke(
        {"messages": [HumanMessage(content="今天烟台天气怎么样")]},
        config=config
    )
    result1 ["messages"][-1].pretty_print()
    
    print("\n=== 第二轮对话(测试记忆) ===")
    result2 = agent.invoke(
        {"messages": [HumanMessage(content="我刚才问的什么")]},
        config=config
    )
    for i in result2 ["messages"]:
        i.pretty_print()
    
# ========== 7. 查看状态历史 ==========
    for snapshot in agent.get_state_history(config):
        print(f"checkpoint_id: {snapshot.config['configurable']['checkpoint_id']}")
        print(f"下一节点: {snapshot.next}")
        print("---")

运行结果:


三、核心知识点拆解

  1. thread_id 就是会话的身份证
    相同的 thread_id 共享同一个状态历史
    不同的 thread_id 完全隔离,互不干扰
python 复制代码
# 用户A的会话
config_a = {"configurable": {"thread_id": "a_001"}}
# 用户B的会话
config_b = {"configurable": {"thread_id": "b_001"}}

我们可以直接在postgres上观察到:


  1. get_state_history() 让你看到 Agent 的每一步
    每一次从 llm_call 到 tool_node 再回到 llm_call,都会生成一个 checkpoint
    通过遍历历史,你可以知道:

每个 checkpoint 的 ID

当时的状态(messages、llm_calls 等)

下一步要执行哪个节点(snapshot.next)

这个能力在调试 Agent 的决策逻辑时极其有用


  1. 重放(Replay):从某个 checkpoint 重新执行
    你可以把一个历史 checkpoint 拿出来,让 Agent 从那里"重新开始跑"
python 复制代码
# 获取所有历史
history = list(agent.get_state_history(config))
# 取第一个 checkpoint(最早的那个)
old_snapshot = history[-1]

# 从那个 checkpoint 重新执行
replay_result = agent.invoke(None, config=old_snapshot.config)

应用场景:

调试验证:Agent 在某一步做错了决策,重放看看哪里出了问题

人工兜底:长任务执行到一半,人工修正状态后继续


  1. 更新状态

除了查看和重放历史状态,LangGraph 还允许你直接编辑图的状态

这在调试、修正错误输入、或者人工介入时非常有用

我们通过 update_state() 方法来实现

下面我们用一个搜索助手的例子,演示如何修改用户输入并重新执行:

python 复制代码
from langchain_core.messages import HumanMessage
from langgraph.types import Overwrite

config = {"configurable": {"thread_id": "1"}}

# 第一次执行:用户问"西安天气"
result1 = agent.invoke(
    {"messages": [HumanMessage(content="今天烟台的天气如何?")]},config)

# 查看历史,找到调用 LLM 之前的那个 checkpoint
selected_state = None
for state in agent.get_state_history(config):
    print(f"checkpoint_id: {state.config['configurable']['checkpoint_id']}, "
          f"消息数: {len(state.values['messages'])}, 下一节点: {state.next}")
    if len(state.values["messages"]) == 1:  # 还未调用 LLM 的状态
        selected_state = state

# 更新用户输入:将"烟台"改为"北京"
new_config = agent.update_state(
    selected_state.config,
    {"messages": Overwrite([HumanMessage(content="今天北京的天气如何?")])}
)

# 从更新后的 checkpoint 重新执行
result2 = agent.invoke(None, config=new_config)
for message in result2['messages']:
    message.pretty_print()

四、常见问题与注意事项

  1. Q1:第一次运行报错 relation "checkpoints" does not exist

    A: 忘记调用 checkpointer.setup()

    该方法会在 PostgreSQL 中创建必要的表(checkpoints、checkpoint_writes 等)

  2. Q2:get_state_history() 返回空列表

    A: 检查两点:

    确保调用 invoke 时传入了 config(包含 thread_id)

    如果使用 PostgresSaver,确认数据库连接正常

  3. Q3:生产环境需要注意什么?

    不要用 with 语句每次重连,应该使用连接池

    定期清理过期的 checkpoint,避免表无限膨胀

    可以为 thread_id 建立索引,提升查询性能

  4. Q4:可以手动删除某个会话的状态吗?

    可以,直接操作 PostgreSQL 表:

    使用以下语句:

sql 复制代码
DELETE FROM checkpoints WHERE thread_id = 'user_001';

五、总结

这一章我们完成了从"无状态"到"有状态"的转变:

能力 实现方式

多轮记忆

python 复制代码
thread_id + PostgresSaver

状态追踪

python 复制代码
get_state_history()

断点恢复 重放(Replay)机制

线程级持久化是构建生产级 Agent 的地基

有了它,你的 AI 助手才能像真人一样记住上下文、扛得住崩溃、经得起调试


下一章我们将探讨跨会话持久化(Store)------让 Agent 在不同对话之间也能记住你的偏好和长期信息~

相关推荐
xxjj998a6 小时前
Laravel4.x:PHP开发新纪元
android·数据库
旺财矿工6 小时前
高效搭建:OpenClaw 2.6.6 Windows 11 一键安装教程
人工智能·自动化·ai自动化·openclaw·小龙虾
XD7429716366 小时前
科技晚报|2026年4月30日:财报日拉高 AI 投入,OpenAI 扩大政府与算力版图
大数据·人工智能·科技·科技新闻·科技晚报
xiangzhihong86 小时前
Claude Code 系列教程之Agent Skills
人工智能
水如烟6 小时前
孤能子视角:跨域联接之异质大模型同构验证“避坑六原则“
人工智能
skilllite作者6 小时前
LangChain-SkillLite 快速入门
网络·人工智能·安全·langchain·openclaw·agentskills
申耀的科技观察6 小时前
【观察】神州数码郭为:AI for Process不止于“AI+”,而是“AI次方”的系统性变革
人工智能
qcx236 小时前
Warp源码深度解析(三):Block-Based终端引擎——Grid模型、PTY与Shell Integration
人工智能·设计模式·架构·wrap