第09章:上下文与记忆 (3)

基于外部存储介质的持久化器

如果将 状态检查点(checkpointer) 保存在内存, 进程结束 则状态丢失,生产环境不可接受。因此,生 产环境要用持久化的外部存储介质,如PostgreSQL。LangGraph提供的checkpointer后端列表如下

此处选择PostgreSQL作为持久化器。

1 数据库环境准备

在此之前,先准备好PostgreSQL环境,此处在云服务器的Ubuntu系统安装PostgreSQL。大家需要根据 自己云服务器位置,更改URL中的IP即可。

URL: postgresql://langchain_user:abcd1234@118.195.128.47:5432/langchain_db? sslmode=disable

说明:

上述URL中的用户名、密码、IP地址,需要根据自己的情况替换。

要在LangChain中对接PostgreSQL,还需要额外的依赖,比如:

复制代码
pip install langgraph-checkpoint-postgres

其实我们在课程开始的requirements.txt中已经提供了相关依赖,此处不必也不能重新安装,否则版本 可能冲突。

2 代码实现

复制代码
# 导入 PostgreSQL 持久化检查点,用于保存 Agent 的对话状态
from langgraph.checkpoint.postgres import PostgresSaver

# 导入创建 Agent 的方法
from langchain.agents import create_agent

# 导入用户消息类型
from langchain.messages import HumanMessage

# 导入 dotenv,用于加载 .env 文件中的环境变量
from dotenv import load_dotenv

# 导入 os,用于读取环境变量
import os


# 加载 .env 文件中的环境变量
load_dotenv(verbose=True)

# 获取 PostgreSQL 数据库连接地址
DB_URL = os.getenv("DB_URL")


# 根据数据库连接地址创建 PostgresSaver
# with 结束后会自动关闭数据库连接
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:

    # 初始化 LangGraph 用于保存 Checkpoint 的数据库表
    checkpointer.setup()

    # 创建 Agent
    # checkpointer 用于持久化 Agent 的对话状态
    agent = create_agent(
        model="deepseek:deepseek-v4-pro",
        tools=[],
        checkpointer=checkpointer,
    )

    # 配置当前会话的 thread_id
    # 相同 thread_id 的对话会共享之前保存的状态
    config = {
        "configurable": {
            "thread_id": "1"
        }
    }

    # 第一轮对话
    # Agent 会将这次对话状态保存到 PostgreSQL
    response4 = agent.invoke(
        {
            "messages": [
                HumanMessage("你好,我是小明")
            ]
        },
        config=config
    )

    # 第二轮对话
    # 因为 thread_id 相同,所以 Agent 可以读取第一轮保存的上下文
    response5 = agent.invoke(
        {
            "messages": [
                HumanMessage("你知道我是谁吗")
            ]
        },
        config=config
    )


# 遍历第二轮对话中的所有消息
for msg in response5["messages"]:

    # 以比较友好的格式打印每条消息
    msg.pretty_print()

setup() 用于初始化PostgreSQL数据库,首次运行会创建必要的表,重复执行不会重新建表,底层逻辑 是 Create IF Not Exists ,相关源码如下

复制代码
def setup(self) -> None:
"""Set up the checkpoint database asynchronously.
This method creates the necessary tables in the Postgres database if they
don't already exist and runs database migrations. It MUST be called directly
by the user the first time checkpointer is used.
"""
...

3 查看持久化数据

查看PostgreSQL数据库,可以通过命令行或图形化工具查看

复制代码
$ psql postgresql://langchain_user:abcd1234@localhost:5432/langchain_db?sslmode=disable

PostgreSQL的存储结构是 Database -> Schema -> Table

  • Database :数据库
  • Schema :分区
  • Table :表

需求1:查看所有数据库

复制代码
\l

命令行前缀 langgraph_db 标识了当前所在数据库

需求2:查看所有schema

复制代码
\dn

需求3:查看当前所处的schema

复制代码
select current_schema();

需求4:查看当前schema下的所有表

这四张表都是 setup() 函数初始化时创建的。

  • checkpoints :这是主表,存每个 thread 在某个时刻的 checkpoint 快照。
  • checkpoint_blobs :这张表专门存不适合直接内联进 checkpoints.checkpoint 的较复杂 channel 值。
  • checkpoint_writes :这张表存的是中间写入 / pending writes,不是最终完整 checkpoint。
  • checkpoint_migrations :这张表不是业务数据表,而是迁移版本表。

对比两种方式

举例1:基于内存存储

复制代码
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
from dotenv import load_dotenv



load_dotenv(verbose=True)

agent = create_agent(
    model="deepseek:deepseek-v4-pro",
    checkpointer=InMemorySaver()
)

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


print("=" * 30, "-> 第一次调用 <-", "=" * 30)

response1 = agent.invoke(
    {
        "messages": [
            HumanMessage("你好,我是谁?")
        ]
    },
    config=config
)

for msg in response1["messages"]:
    msg.pretty_print()


print("=" * 30, "-> 第二次调用 <-", "=" * 30)

response2 = agent.invoke(
    {
        "messages": [
            HumanMessage("我是老王")
        ]
    },
    config=config
)

for msg in response2["messages"]:
    msg.pretty_print()


print("=" * 30, "-> 第三次调用 <-", "=" * 30)

response3 = agent.invoke(
    {
        "messages": [
            HumanMessage("你好,我是谁?")
        ]
    },
    config=config
)

for msg in response3["messages"]:
    msg.pretty_print()

可以发现,每次执行的输出完全相同,而我们并没有更改 thread_id ,之所以看不到上次运行的状态是 因为每次运行创建新的Saver(),历史State被丢弃了。

举例2:基于外部存储器存储

复制代码
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from langgraph.checkpoint.postgres import PostgresSaver

# 导入 dotenv,用于加载 .env 文件中的环境变量
from dotenv import load_dotenv

# 导入 os,用于读取环境变量
import os


# 加载 .env 文件中的环境变量
load_dotenv(verbose=True)

# 获取 PostgreSQL 数据库连接地址
DB_URL = os.getenv("DB_URL")


# 创建 PostgreSQL Checkpointer
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:

    # 第一次使用时初始化数据库
    # 创建 Checkpoint 所需的数据表,并执行数据库迁移
    checkpointer.setup()

    # 创建 Agent,并使用 PostgreSQL 保存会话状态
    agent = create_agent(
         model="deepseek:deepseek-v4-pro",
        checkpointer=checkpointer
    )

    # 指定会话 ID
    # 相同的 thread_id 会使用同一份历史记录
    config = {
        "configurable": {
            "thread_id": "3"
        }
    }


    # ==============================
    # 第一次调用
    # ==============================
    print("=" * 30, "-> 第一次调用 <-", "=" * 30)

    response1 = agent.invoke(
        {
            "messages": [
                HumanMessage("你好,我是谁啊?")
            ]
        },
        config
    )

    for msg in response1["messages"]:
        msg.pretty_print()


    # ==============================
    # 第二次调用
    # ==============================
    print("=" * 30, "-> 第二次调用 <-", "=" * 30)

    response2 = agent.invoke(
        {
            "messages": [
                HumanMessage("我是老王~")
            ]
        },
        config
    )

    for msg in response2["messages"]:
        msg.pretty_print()


    # ==============================
    # 第三次调用
    # ==============================
    print("=" * 30, "-> 第三次调用 <-", "=" * 30)

    response3 = agent.invoke(
        {
            "messages": [
                HumanMessage("你好,我是谁??")
            ]
        },
        config
    )

    # 第三次调用时,可以看到前两次对话的历史
    for msg in response3["messages"]:
        msg.pretty_print()

根据输出判断,状态是累积的。

由此可以得出结论:即便重新创建 Saver() ,只要 thread_id 一致,历史状态就可以和当前调用串联起 来。


总结:

  1. InMemorySaver()将状态持久化到内存, 进程结束或重建Saver() 则历史状态丢失

  2. 基于外部存储介质(如PostgreSQL)的持久化器,其存储的状态不会随进程终止而丢失,只要 不 显式删除历史状态 ,即可通过 thread_id 加载历史状态。

相关推荐
龙腾-虎跃38 分钟前
AI-Vue3-python-flask-Blog 全栈博客项目深度解析:从零搭建你的 AI 博客
人工智能·python·flask
余槐i39 分钟前
无慢查询 RT 却飙升:cProfile 定位 FastAPI 事件循环阻塞
后端·python·性能优化·fastapi·asyncio
Xiu Yan1 小时前
Python 数据分析:数据分析步骤
开发语言·python·数据分析
vilya1 小时前
把 Python 塞进 APK:Chaquopy 打包实践
android·python
黑妹天下第一乖1 小时前
小智改造实战解读-用kokoro-onnx替换云端 TTS 的完整记录
大数据·开发语言·人工智能·python·交互
JWASX1 小时前
【agent 开发】agent 开发学习 - LangChain(3)
学习·langchain
范中勤1 小时前
LLM 动态加载与多用户缓存架构技术文档
redis·langchain·llm·缓存架构·多用户隔离
papaofdoudou1 小时前
从抽象群到可计算的矩阵:S₃ 的表示论与共轭视角
python·决策树·矩阵
JWASX2 小时前
【agent 开发】Agent 智能体
大数据·人工智能·python
智购科技无人售货机工厂店2 小时前
玻璃瓶饮料破损率突然上升,排查发现是取货口缓冲垫老化了~YH
java·开发语言·人工智能·python·eclipse