AI agent开发——LangGraph接入持久化

从"历史记忆"到 PostgresStore:一次长期记忆系统的生产化改造

本文整理自一次完整的 LangGraph 长期记忆实践:先理解记忆系统要解决什么问题,再通过一个教学示例踩坑,最后把它改造成基于 PostgresStore 的低耦合生产方案。

一、记忆系统到底在做什么?

很多人会把"记忆系统"简单理解成:用户下次提问时,把以前的历史对话拿出来回答。

这个理解方向是对的,但更准确的说法是:记忆系统服务于"下一次提问时,让智能体拥有该有的上下文",它通常分两层:

  1. 会话级记忆

    当前这一轮任务里的聊天历史。例如用户连续问"帮我查 A 公司""那 B 呢?",智能体需要记得上一轮在查公司,才能理解"那 B 呢"的含义。

  2. 长期记忆

    跨会话、跨任务仍然保留的重要信息。例如用户上月说"我是高风险偏好客户",今天新开一个会话问"帮我配置一笔资产",智能体应该把上次的偏好调出来,而不是当作陌生人。

在 LangGraph 中:

  • checkpointer 负责会话级状态恢复;
  • store 负责跨会话长期记忆;
  • 两者职责分开,才是比较清晰的记忆架构。

二、教学示例里踩过的一个坑

最初运行 LangGraph 长期记忆示例时,出现了一个很典型的报错:

text 复制代码
ValidationError: 2 validation errors for AIMessage
content.str
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]

根因是代码里写了:

python 复制代码
AIMessage(Content=f"收到!{memories_text}")

Content 首字母大写,被 pydantic 当成未知字段忽略,导致 content 保持 None,最终触发校验错误。

修复很简单:

python 复制代码
AIMessage(content=f"收到!{memories_text}")

继续运行后又发现,示例里还有几个逻辑问题会影响实际演示:

  • State.messages 的类型写成了 list[str],实际应该使用 list[BaseMessage]
  • 记忆文本拼接逻辑有问题,会把字符串当成字符列表拼接;
  • 存入 Store 时保存的是消息对象,而不是可直接读取的文本。

修正后,示例的输出就符合预期了:

text 复制代码
AI: 收到!我没有关于你的记忆。
AI:收到! - 我叫小明,记住这个

第一次对话记住"我叫小明",第二次换新 thread、但同一个用户时,能正确读回记忆。

三、这个示例能直接上生产吗?

设计思路很实用,但这版实现只能算教学原型,不能直接上生产。

主要差距:

  1. InMemoryStoreMemorySaver 只存在于进程内存,服务重启后数据全部丢失。
  2. 记忆写入靠"记住""我叫"这种关键词判断,太脆弱。
  3. 每次把所有记忆无差别塞进提示词,会随着时间变长、变贵、产生干扰。
  4. 缺少记忆更新、合并、过期和删除机制。
  5. 简单的递增 key 在并发写入时也不够安全。

所以生产化要解决的核心问题是:把内存存储换成持久化存储,同时让记忆的读写与主流程保持低耦合。

四、基于 PostgresStore 的生产化改造

这次改造选择了 LangGraph 官方的 Postgres 持久化方案:

  • AsyncPostgresSaver:保存会话检查点;
  • AsyncPostgresStore:保存跨会话长期记忆。

4.1 架构分层

新增了一个独立的 app/memory/ 模块,和原有主智能体保持低耦合:

text 复制代码
app/memory/
├── persistence.py   # Postgres/内存回退的生命周期管理
├── service.py       # 记忆读写、检索、格式化
├── tools.py         # Agent 可调用的记忆工具
└── router.py        # HTTP 管理接口

主智能体只做三件事:

  1. 启动时从记忆服务读取当前用户的长期记忆;
  2. 把记忆以纯文本形式注入用户消息;
  3. 提供 remember_memory / recall_memory / forget_memory 工具,让模型自己决定何时保存和回忆。

4.2 持久化层

persistence.py 的核心思路是:优先使用 Postgres,未配置或初始化失败时自动回退到内存模式,保证项目仍能运行。

python 复制代码
async def initialize(self) -> bool:
    if not self._enabled:
        return False

    conn_string = os.getenv("POSTGRES_CONN_STRING", "").strip()
    if not conn_string:
        return False

    pool = AsyncConnectionPool(
        conn_string,
        min_size=_env_int("POSTGRES_POOL_MIN_SIZE", 1),
        max_size=_env_int("POSTGRES_POOL_MAX_SIZE", 10),
        kwargs={
            "autocommit": True,
            "prepare_threshold": 0,
            "row_factory": dict_row,
        },
        open=False,
    )
    await pool.open()

    checkpointer = AsyncPostgresSaver(pool)
    store = AsyncPostgresStore(pool)
    await checkpointer.setup()
    await store.setup()
    ...

setup() 会自动创建 LangGraph 需要的表,例如:

  • 检查点相关:checkpointscheckpoint_writescheckpoint_blobs
  • 长期记忆相关:storestore_vectorsstore_migrations

4.3 业务服务层

service.py 负责记忆的保存、查询、删除和上下文格式化,例如:

python 复制代码
async def save_memory(self, user_id, content, category="general") -> str:
    ...
    await persistence.store.aput(
        _namespace(user_id),
        key,
        {
            "content": content,
            "category": category,
            "source": "agent",
        },
    )
    return f"已保存长期记忆:{content}"

每个用户的记忆使用独立 namespace:

python 复制代码
("finance_search_agents", user_id, "memories")

这样不同用户之间天然隔离,不会串记忆。

4.4 Agent 记忆工具

给 DeepAgents 增加了三个工具:

  • remember_memory:保存长期记忆;
  • recall_memory:查询历史长期记忆;
  • forget_memory:删除过时记忆。
python 复制代码
@tool
async def remember_memory(content: str, category: str = "general") -> str:
    if not memory_service.enabled:
        return "记忆系统未启用。"
    user_id = get_user_context()
    if not user_id:
        return "当前没有可用的用户上下文,无法保存长期记忆。"
    return await memory_service.save_memory(user_id, content, category)

同时在系统提示词里补充了记忆使用说明:用户告知身份、偏好、风险等级、客户编号等信息时,先保存;用户询问历史信息时,先回忆。

4.5 HTTP 管理接口

除了 Agent 自动读写,还提供了人工管理接口:

接口 作用
GET /api/memory/ 查看当前用户全部长期记忆
POST /api/memory/ 手动新增一条长期记忆
DELETE /api/memory/{key} 删除指定长期记忆

这些接口同样按登录用户隔离,适合做后台管理、调试,或给用户提供"管理我的记忆"页面。

五、Docker Compose 部署 Postgres

为了让本地环境可以一键启动,在 infra/docker-compose.yaml 中新增了 Postgres 服务,并让 Postgres 和 MySQL 处于同一个 Docker 网络,宿主机访问地址保持一致。

yaml 复制代码
services:
  postgres:
    image: postgres:16-alpine
    container_name: deepsearch-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-deepsearch}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-deepsearch@123}
      POSTGRES_DB: ${POSTGRES_DB:-deepsearch_db}
      TZ: Asia/Shanghai
    ports:
      - "${POSTGRES_PORT:-5433}:5432"
    volumes:
      - deepsearch_postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 10s
    networks:
      - deepsearch-net

对应的 .env 配置:

bash 复制代码
POSTGRES_CONN_STRING=postgresql://deepsearch:deepsearch@123@localhost:5433/deepsearch_db
POSTGRES_HOST=localhost
POSTGRES_PORT=5433
POSTGRES_USER=deepsearch
POSTGRES_PASSWORD=deepsearch@123
POSTGRES_DB=deepsearch_db
POSTGRES_POOL_MIN_SIZE=1
POSTGRES_POOL_MAX_SIZE=10
MEMORY_ENABLED=true
MEMORY_LIMIT=20
MEMORY_RECALL_LIMIT=30

启动命令:

bash 复制代码
docker compose -f infra/docker-compose.yaml up -d

六、关于初始化,需要区分两件事

Postgres 容器创建时,官方镜像只负责初始化 PostgreSQL 实例本身:

  • 创建用户 deepsearch
  • 创建数据库 deepsearch_db

此时 LangGraph 的表不会出现,所以直接看数据库可能觉得"没有初始化"。

后端启动时 ,FastAPI 生命周期会调用 persistence.initialize(),再执行:

  • AsyncPostgresSaver.setup()
  • AsyncPostgresStore.setup()

这时才会自动创建 checkpointscheckpoint_writesstore 等表。

所以正确流程是:

  1. 启动 Postgres 容器;
  2. 启动后端服务;
  3. 后端自动完成 LangGraph 表结构初始化。

验证命令:

bash 复制代码
docker compose -f infra/docker-compose.yaml exec postgres psql -U deepsearch -d deepsearch_db -c '\dt'

七、问答速览

1. 记忆系统是不是用于用户提问下一个问题时,提取以前的历史记忆来回答问题?

是的,但更准确地说,记忆系统分为会话级记忆和长期记忆。会话级记忆负责当前任务上下文,长期记忆负责跨会话保留重要信息,并在下一次提问时按需读取。

2. 这个教学代码在实际项目中实用吗?

思路实用,但原版不能直接上生产。主要问题是内存存储、关键词触发、无检索排序、无更新删除机制。

3. 记忆 API 接口有什么用?

GET /api/memory/ 查看记忆,POST /api/memory/ 新增记忆,DELETE /api/memory/{key} 删除记忆。它们用于在对话之外人工管理长期记忆。

4. 创建 Postgres 后是不是没有初始化 deepsearch?

Postgres 容器创建时只初始化用户和数据库;LangGraph 的业务表要等后端启动后由 setup() 自动创建。

八、总结

长期记忆系统最核心的设计不是"记住所有东西",而是:

  • 区分会话上下文和长期记忆;
  • 用持久化存储保证重启不丢;
  • 按用户隔离数据;
  • 按需检索而不是全量灌入;
  • 提供保存、回忆、删除的完整闭环。

PostgresStore 方案可以让 LangGraph 的 checkpointerstore 都落到同一个 PostgreSQL 中,同时通过独立模块接入主流程,既满足生产需求,也保持项目结构清晰。

补充:当前环境没有真实 Postgres 时,系统会自动走内存回退模式,便于开发调试;接入真实 Postgres 后,首次启动后端即会自动完成建表。

相关推荐
民乐团扒谱机1 小时前
【读论文】2005 一种旋律转录的分类方法A classification approach to melody transcription[C]
人工智能·分类·数据挖掘
程序员-李俞1 小时前
Coze 工作流调用异步 HTTP API 完整教程:任务 ID、循环轮询、状态判断与结果 URL 提取
网络·人工智能·网络协议·http·aigc·ai编程·ai写作
柠檬07111 小时前
图像亮暗不均匀,如何去除背景或者怎么处理可以让图像暗的地方亮一些,亮的地方暗一些
人工智能·opencv·计算机视觉
啦啦啦啦啦zzzz1 小时前
升序链表的定时器
linux·服务器·网络·数据结构·c++·链表
咕泡科技1 小时前
AI 前沿速递:OpenAI 开源 Codex 重构编程生态,人形机器人破纪录、迈入消费量产时代!
人工智能·机器人·开源·wrc2026·人形机器人运动会·启元机器人·辉羲智能
熊猫_豆豆1 小时前
黑洞的粒子产生(霍金1975年论文)第二部分
人工智能·数学·算法·机器学习·量子力学·大学物理·黑洞
mwmbfh1 小时前
【CentOS7环境下Redis 6.2.14 源码编译部署说明】
linux·运维·数据库·redis
jay神1 小时前
本科深度学习需要从零开始训练模型吗?
人工智能·深度学习·算法·机器学习·计算机视觉