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

相关推荐
第九序言T7 小时前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
Java后端的Ai之路8 小时前
CAS并发控制-从CPU指令到企业级乐观锁
vue.js·人工智能·python·cas·乐观锁
qq_426003968 小时前
UI自动化高频次登录login限流解决方案
python·ui·自动化
Sakura_j_h_8 小时前
EasyEDA PCB 快速设计Skills
c++·python·github
quantdash_cc9 小时前
如何验证历史行情的交易日期是否完整覆盖研究区间?
python·数据分析·pandas·量化交易·股票数据·quantdash
I Am a robert girl9 小时前
当智能体学会“串通”:从长程交互中读懂涌现式合谋
python·多智能体系统·多智能体协作·llm agent·涌现式合谋·对齐问题·激励机制设计
云和数据.ChenGuang10 小时前
光哥说Python RAG 系统需要哪些评估指标:从检索到生成的全链路评测指南
网络·人工智能·python·算法·langchain
专注仿真10 小时前
线性回归和逻辑回归算法详解
开发语言·人工智能·python·大模型训练·控制算法
wflynn11 小时前
GitHub 今日推荐|Edge0:让大号 MoE 模型在一台 Mac 上流式跑起来
python·开源·github
2601_9679801112 小时前
python之用scapy分层解析pcap报文(Ethernet帧、IP数据包、TCP数据包、UDP数据包、Raw数据包)
python·scapy·pcap·协议分析·网络数据包