第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 加载历史状态。

相关推荐
缘友一世19 分钟前
GLM-5.3-NVFP4 部署实战系列[二]Docker 镜像选择与补丁镜像构建:纯 Python overlay,不重编一行 CUDA
python·docker·容器·vllm
海兰26 分钟前
【 Python 量化交易】第3章:金融基础概念
开发语言·python·金融
Feynman’s boom28 分钟前
PDF解析文字提取后RAG仍然检索不准
python·pdf解析·rag
心易行者44 分钟前
从零搭建完整Web应用:7步走完全流程,配合web应用托管零门槛上线
人工智能·python·ai编程
小柯南敲键盘1 小时前
跨境电商图片翻译工具,批量处理视频字幕与抠图
人工智能·python·音视频
2601_966949651 小时前
五档盘口数据对量化交易有什么价值?从信号判断到策略风险的完整分析
开发语言·人工智能·python·数据分析·量化·股票数据·quantdash
cxhello1 小时前
消费者活着、心跳正常、日志干净,但它七天没拉过一条消息
python·kafka
砚底藏山河1 小时前
【量化纯GET实战 #11】移动均线 MA:5 日 20 日 60 日怎么算怎么用
java·python·金融·maven
小吴学不废Java1 小时前
Python 基础语法
开发语言·python