LangChain 应用开发(十四):Agent 上下文与记忆机制

目录

[一、为什么 Agent 需要上下文与记忆](#一、为什么 Agent 需要上下文与记忆)

[1. 手动保存到 Memory 自动化](#1. 手动保存到 Memory 自动化)

[二、Context、State 与 Memory](#二、Context、State 与 Memory)

[1. 整体概念](#1. 整体概念)

[2. 深入理解 Agent State](#2. 深入理解 Agent State)

[3. 三者关系的解耦](#3. 三者关系的解耦)

[三、短期记忆与 Checkpointer](#三、短期记忆与 Checkpointer)

[1. 什么是短期记忆](#1. 什么是短期记忆)

[2. Checkpointer 的运作机制](#2. Checkpointer 的运作机制)

四、实现短期记忆

[1. 构建拥有短期记忆的 Agent](#1. 构建拥有短期记忆的 Agent)

[2. thread_id 路由逻辑](#2. thread_id 路由逻辑)

[3. 常见问题](#3. 常见问题)

五、使用外部存储实现持久化记忆

[1. PostgreSQL 持久化方案:PostgresSaver](#1. PostgreSQL 持久化方案:PostgresSaver)

[2. 断电重启验证记忆恢复](#2. 断电重启验证记忆恢复)

[3. 为什么不直接设计关系表保存](#3. 为什么不直接设计关系表保存)

六、内存与外部持久化对比

七、记忆治理:不是记得越多越好

[1. 消息裁剪](#1. 消息裁剪)

[2. 消息删除](#2. 消息删除)

[3. 消息摘要](#3. 消息摘要)

总结


一、为什么 Agent 需要上下文与记忆

要理解 Agent 的记忆机制,必须先揭开 LLM 无状态的本质

假设向模型发起两次独立调用:

  • 第一次调用:"我叫小明",模型回答:"你好,小明!"

  • 第二次调用:"我叫什么名字?",模型回答:"抱歉,我不知道你的名字。"

在第二次请求中,模型之所以失忆,是因为每一次 API 调用都是完全独立的 HTTP 请求。模型本身不保存任何客户端的状态信息


1. 手动保存到 Memory 自动化

在最原始的 LLM 开发模式中,维护对话记忆需要开发者手动管理消息数组:

复制代码
1. 创建 messages
2. 追加 HumanMessage("我叫小明")
3. 调用 Model 并追加 AIMessage("你好,小明!")
4. 追加 HumanMessage("我叫什么?")
5. 再次把整个 messages 丢给 Model

这种模式在面对多用户并发、长流程推导或复杂 Tool 调用的 Agent 场景时极其脆弱:数组容易爆满、程序一旦重启历史数据就会彻底丢失

现代 Agent 框架抽象出了标准的 Memory 架构

复制代码
Agent 运行循环
      │
      ▼
   Agent State (状态捕获)
      │
      ▼
  Checkpointer (自动持久化)
      │
      ▼
下一次调用 (基于 Thread ID 自动恢复历史)

通过将状态保存与恢复交给底层的 Checkpointer 机制,开发者不再需要手动追加消息流。Agent 可以在任意步骤自动挂起、保存、恢复状态,从而天然具备了跨轮次对话的短期记忆能力

二、Context、State 与 Memory

在 LangChain 和 LangGraph 的工程体系中,Context(上下文)State(状态)Memory(记忆) 是三个极易被混淆的核心概念。厘清它们之间的界限与关联,是理解 Agent 运行时架构的前提


1. 整体概念

简单来说,Agent 运行过程中所需的所有信息,可以按照生命周期与作用域划分:


2. 深入理解 Agent State

要搞懂 Memory,必须先搞懂 State。在基于图结构驱动的 Agent 中,State 就是 Agent 运行时的"工作内存"

State 通常是一个标准的字典结构,里面不仅包含对话消息列表 messages,还可以包含业务自定义的元数据:

python 复制代码
# 典型 Agent State 结构示例
AgentState = {
    "messages": [
        HumanMessage(content="帮我查询订单 12345 的状态"),
        AIMessage(content="", tool_calls=[{"name": "query_order", "args": {"id": "12345"}}])
    ],
    "user_id": "usr_9527",      # 当前操作的用户 ID
    "current_step": 2,         # 当前推导步数
    "is_vip": True             # 业务标记位
}

在 Agent 循环中,数据传递本质上就是对 State 的读取与更新过程

python 复制代码
            State (当前状态字典)
                  │
                  ▼
         Model 读取 State 消息历史
                  │
                  ▼
         Model 产生新消息 / 工具调用
                  │
                  ▼
            更新入 State
                  │
                  ▼
         Tool 读取 State 参数并执行
                  │
                  ▼
         产生 ToolMessage 并再次更新 State

3. 三者关系的解耦

理解了 State 之后,三者的协作逻辑便清晰可见:

  1. State 是静态的数据载体 :它记录了 Agent 此时此刻运行到了哪一步、手里有什么数据

  2. Memory 是保存机制 :它解决的是当这一轮 Agent 执行结束、进程挂起后,如何将 State 保存下来,并在用户下一次发问时原样恢复

  3. Context 是给模型的上下文信息 :它是从 State 和 Memory 中精炼出来、真正发送给大模型的 Prompt

核心结论State 解决 "现在手里有什么",Memory 解决 "过去发生过什么如何记下来",Context 解决 "这一次让模型看什么"

三、短期记忆与 Checkpointer

要让 Agent 拥有记忆,最直观的第一步就是实现短期记忆 。负责支撑这一能力的核心机制就是 Checkpointer(检查点保存器)


1. 什么是短期记忆

短期记忆通常围绕具体的会话线程(Thread)来隔离与保存 Agent 的 State 。它的特征在于:会话级别的独立

同一个 Agent 服务可能同时处理成千上万个用户的请求,不同用户、不同窗口之间的对话必须做到绝对隔离:

python 复制代码
Thread A (thread_id="session_001")
├── 用户:我叫张三
├── AI:你好张三,有什么我可以帮你的?
├── 用户:我今年 20 岁
└── AI:好的,记下了。

Thread B (thread_id="session_002")  ◄── (完全隔离,无法感知 Thread A)
├── 用户:东京今天天气怎么样?
└── AI:东京今天晴朗,气温 22°C...

在 Thread A 中,Agent 需要记住用户叫张三;但在 Thread B 中,Agent 绝不能把 Thread A 的信息混淆进来。这种基于 Thread 隔离、并在单次会话持续期间保留上下文的能力,就是短期记忆


2. Checkpointer 的运作机制

Checkpointer 的定位是 Agent 图状态的快照与恢复引擎。在 Agent 的每一步节点执行或工具调用结束后,Checkpointer 都会自动捕捉当前 State 的一个 "快照(Checkpoint)",并将它与特定的 thread_id 绑定存储

整个交互过程如下:

python 复制代码
【第一次调用】
用户输入 ──► Agent 执行 ──► 生成/更新 State ──► Checkpointer ──► 写入 Checkpoint

【第二次调用】
用户输入 ──► Checkpointer ──► 凭 thread_id 查找并恢复上一次的 State 
    ──► 加入新 Message ──► Agent 续写

当收到第二次请求时,系统并不会初始化一个空白的 Agent,而是由 Checkpointer 拿着请求中的 thread_id 去历史记录里检索上一次落盘的 Checkpoint,将完整的 State 重新加载到内存中。大模型拿到恢复后的上下文,就能接续上一次的话题

四、实现短期记忆

了解了 Checkpointer 的原理后,最快速上手的方式是使用预置的 InMemorySaver。它是最基础的内存级 Checkpointer,无需依赖任何外部数据库,适合用于本地开发、测试以及无需长期落盘的临时会话场景


1. 构建拥有短期记忆的 Agent

使用 InMemorySaver 为 Agent 注入记忆机制极其简单,只需三个步骤:

实例化 Checkpointer -> 挂载至 Agent -> 在调用时指定 thread_id

python 复制代码
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI

# 1. 实例化内存检查点保存器
checkpointer = InMemorySaver()

# 2. 创建 Agent 并挂载 checkpointer
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o", temperature=0),
    tools=[],
    checkpointer=checkpointer
)

# 3. 配置会话标识 Thread ID
config = {"configurable": {"thread_id": "thread_001"}}

# --- 第一次调用 ---
response1 = agent.invoke(
    {"messages": [{"role": "user", "content": "你好,我叫张三,我的职业是程序员。"}]},
    config=config
)
print("AI 回复 1:", response1["messages"][-1].content)

# --- 第二次调用(使用相同的 thread_id)---
response2 = agent.invoke(
    {"messages": [{"role": "user", "content": "还记得我叫什么以及我的工作是什么吗?"}]},
    config=config
)
print("AI 回复 2:", response2["messages"][-1].content)

输出示例:


2. thread_id 路由逻辑

在上述代码中,区分不同会话的核心是 config 字典中的 thread_id

InMemorySaver 在底层构建了一个基于 Key-Value 的哈希映射表。每一个 thread_id 都代表着一条独立的内存分支:

python 复制代码
        InMemorySaver (内存存储容器)
                    │
       ┌────────────┼────────────┐
       ↓            ↓            ↓
   thread_001   thread_002   thread_003
       │            │            │
   State 副本   State 副本   State 副本
 (张三/程序员)  (李四/设计师) (王五/产品经理)

当不同的用户发起请求时,只需要传入不同的 thread_id(例如绑定用户的 Session ID),InMemorySaver 就会自动加载对应分支的 State,完全不用担心多个用户之间的对话会互相干扰


3. 常见问题

在实际开发中使用 InMemorySaver 时经常会遇到以下问题:

Q1:InMemorySaver 会丢失数据吗?

。InMemorySaver 的数据完全存放在 Python 进程的工作内存中

  • 进程运行期间:数据有效,可随时读取与更新

  • 程序崩溃或重启 :随着 Python 进程结束,内存中的所有 Checkpoint 数据将彻底清空且不可恢复

    因此,它仅适合单元测试、演示 Demo 以及不要求持久化的短期交互场景

Q2:内存会无限增长吗?

。随着交互的持续,内存压力来自于三个维度:

用户总数 × Thread 数量 × 单会话 Message 长度

如果服务长时间不重启,且没有对历史 Message 进行清理,InMemorySaver 最终可能导致进程 OOM。这也是为什么生产环境必须引入外部数据库持久化记忆治理 的原因

Q3:如何手动清空或重置某个会话的记忆?

在业务上,清除记忆并不是要求大模型忘记,而是在存储层删除或重置指定 thread_id 绑定的 State

可以通过 Checkpointer 提供的接口修改或覆盖状态。最彻底的方式是更新对应的 thread_id 配置,将其 messages 列表重置为空,或者在业务发起新会话时直接为其生成一个新的 thread_id,旧的 Thread 数据便不再被读取

五、使用外部存储实现持久化记忆

InMemorySaver 虽然使用便捷,但在生产环境中存在致命缺陷:应用进程一旦重启或关机,所有对话与状态将彻底消失。为了保证服务在高可用部署、版本发布或宕机恢复后依然能保持连续的记忆,必须引入外部持久化组件


1. PostgreSQL 持久化方案:PostgresSaver

LangGraph 官方提供了基于 PostgreSQL 的数据库检查点保存器 PostgresSaver。它会将每次图节点变更后的完整 State 自动序列化存入 PostgreSQL 中

环境依赖准备

在使用之前,需要安装对应的 Checkpointer 扩展与 psycopg 连接驱动:

python 复制代码
pip install -U langgraph-checkpoint-postgres "psycopg[binary]"

第一次连接全新的数据库时,必须调用 checkpointer.setup() 以自动创建数据库底层的 checkpoints、checkpoint_blobs 和 checkpoint_writes 等核心表结构:

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.postgres import PostgresSaver

DB_URI = "postgresql://postgres:password@localhost:5432/database_name?sslmode=disable"

# 1. 使用 PostgresSaver 连接数据库
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
    
    # 第一次使用时调用 setup() 自动初始化数据库表结构 (后续启动可跳过)
    checkpointer.setup()
    
    # 2. 将持性的 checkpointer 挂载至 Agent
    agent = create_agent(
        model=model,
        tools=[],
        checkpointer=checkpointer
    )

2. 断电重启验证记忆恢复

为了直观验证外部持久化的效果,我们可以模拟一次真实的程序宕机重启过程

第一步:运行程序,向 Agent 写入信息并关闭进程

python 复制代码
# 脚本 1:运行并写入数据
import sys

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

# 发起第一次对话
response = agent.invoke(
    {"messages": [{"role": "user", "content": "你好,我叫张三,我的幸运数字是 7。"}]},
    config=config
)
print("第一轮回复:", response["messages"][-1].content)

# 强制终止 Python 进程(模拟服务器宕机或重启)
print("--- 模拟进程崩溃退出 ---")
sys.exit(0)

第二步:重新启动一个全新进程并提问

在重新启动的脚本中,内存已经被完全重置,没有任何历史变量。但只要传入相同的 thread_id:

python 复制代码
# 脚本 2:重新启动全新的 Python 进程,从数据库读取记忆
config = {"configurable": {"thread_id": "user_session_888"}}

# 直接发起第二次提问,不附带任何前情提要
response = agent.invoke(
    {"messages": [{"role": "user", "content": "还记得我叫什么吗?我的幸运数字是多少?"}]},
    config=config
)

print("重启后回复:", response["messages"][-1].content)

当新进程发起请求时,PostgresSaver 根据 user_session_888 从数据库检索出上一次持久化的完整 Checkpoint 快照,成功将状态恢复至 Agent 工作内存中

输出示例:


3. 为什么不直接设计关系表保存

为什么不直接在 MySQL 或 PostgreSQL 里建一张会话表,手动 INSERT 和 SELECT 聊天记录,再传给 LLM 呢?

传统的聊天消息表设计往往如下:

sql 复制代码
CREATE TABLE conversation (
    id UUID PRIMARY KEY,
    thread_id VARCHAR(50),
    role VARCHAR(10),
    content TEXT,
    created_at TIMESTAMP
);

这种 "手写对话表" 方案在面对现代 Agent 架构时会迅速遇到瓶颈,两者的设计初衷完全不同:

  • 自定义关系表只存了 "聊天文本":你只存下了 Human 和 AI 说话的字面文字

  • Checkpointer 保存的是完整的 Agent 图执行状态

    • 工具调用中间态:包括 Pending Tool Calls、Tool Execution Results 及其底层上下文序列化数据

    • 业务自定义 State:如 user_id、表单填写进度、当前推导步骤索引 current_step 等非文本变量

    • 节点分支与版本控制:记录了当前处于 Agent 图的哪一个 Node,中断(Interrupt)、人工审批(HITL)与历史回滚

直接使用官方提供的 PostgresSaver,能够确保状态的保存与恢复和 Agent 内部图路由引擎的运行机制 100% 同步,避免开发者重写一套易出错的底座框架

六、内存与外部持久化对比

在掌握了 InMemorySaver 与 PostgresSaver 的特性后,我们可以从存储位置、数据安全性、配置成本以及适用场景等维度对两者进行直观对比:

InMemorySaver PostgreSQL 持久化
保存位置 应用进程内存 外部关系型数据库
程序重启 数据完全丢失 数据永久保留
配置难度 零配置,开箱即用 需准备数据库环境与连接池
读写性能 极快(纯内存读写) 存在数据库 I/O 与网络开销
多实例部署 无法共享状态 天然支持多服务节点共享与同步
适用场景 学习测试、本地 Demo、临时会话 生产环境、正式线上服务、长期会话

在实际选型时,无需把问题复杂化,直接遵循以下简单原则即可:

  • 学习测试 / 本地开发 / Demo 演示 ──► InMemorySaver (开发效率优先)

  • 正式上线 / 生产服务 / 复杂业务 ──► PostgreSQL 等外部持久化 Checkpointer (数据安全优先)

七、记忆治理:不是记得越多越好

在刚接触 Agent 记忆机制时容易陷入一个误区:既然建立了存储,就应该把发生过的所有消息毫无保留地全盘保存,并在每次调用时全部塞给大模型

这种记忆策略在对话轮次较少时没有任何问题,但随着对话持续深入,隐患会疯狂放大。包括 API 成本飞涨,响应延迟大幅飙升,注意力稀释等

因此,构建生产级 Agent 的核心竞争力,不在于能存多少,而在于记忆治理------即通过上下文工程,控制进入大模型的 Token 载荷


1. 消息裁剪

消息裁剪 是成本最低、效果最立竿见影的治理手段。它的核心思想是基于策略仅保留最近的 N 条消息或 Token 额度,抛弃很久以前的历史

python 复制代码
【原始消息队列】
M1(User) ──► M2(AI) ──► M3(User) ──► M4(AI) ──► M5(User) ──► M6(AI)

                           ▼ 执行裁剪 (保留最近 4 条)

【实际投喂给 LLM 的上下文】
                        M3(User) ──► M4(AI) ──► M5(User) ──► M6(AI)

在 LangChain 中,提供了内置的 trim_messages 工具函数,允许你按消息数量、Token 总数灵活截断:

python 复制代码
from langchain_core.messages import trim_messages

# 动态裁剪消息列表,限定最大使用 2000 个 Token
trimmed_messages = trim_messages(
    messages=state["messages"],
    max_tokens=2000,
    strategy="last",            # 保留最新的消息
    token_counter=model,        # 使用指定 LLM 的 Token 计算器
    include_system=True,        # 始终强制保留开头的 SystemMessage
    start_on="human"            # 确保裁断后的消息开端是用户提问,维持对话结构合规
)

消息裁剪非常适合对于 "即时上下文依赖高、旧历史无参考价值" 的客服咨询或短任务 Agent


2. 消息删除

某些情况下,仅靠截断末尾是不够的。比如工具调用阶段产生的中间结果,或者执行失败的报错信息,在 Agent 完成任务后就变成了毫无价值的脏数据

消息删除允许开发者主动选择并清理指定的消息节点

python 复制代码
[HumanMessage] ──► [AIMessage(ToolCall)] ──► [ToolMessage(巨幅日志)] ──► [AIMessage(结论)]
                                                    │
                                            【触发主动清理】
                                                    ▼
[HumanMessage] ─────────────────────────────────────────────────────────► [AIMessage(结论)]

在 LangGraph 架构中,删除消息非常简单,只需要向 State 抛出一个带特定 ID 的 RemoveMessage 标识即可:

python 复制代码
from langchain_core.messages import RemoveMessage

# 删除 State 中指定的冗余 ToolMessage
def cleanup_middleware(state):
    messages = state["messages"]
    # 筛选出长度过长的历史 Tool 节点并移除
    remove_ops = [
        RemoveMessage(id=m.id) for m in messages 
        if m.type == "tool" and len(m.content) > 1000
    ]
    return {"messages": remove_ops}

将消息删除逻辑与上一篇讲解的 Middleware(中间件) 结合,即可实现在每次 LLM 推导完成后,自动清扫大体积中间产物的隐式治理流程


3. 消息摘要

如果既希望保留很久以前的关键背景信息,又不希望消耗大量 Token,消息摘要是最优解

它的本质是利用一个轻量大模型,将过去几十轮的繁复对话压缩成一段简短的前情提要,并挂载到系统提示词中:

python 复制代码
【过去 50 轮长对话】 ──► [ LLM 摘要压缩 ] ──► System: "用户是张三,正在办理退款..."
                                                             │
                                                             ▼
                                                    [ 最近 5 轮对话 ]

通过消息摘要,我们终于可以将前面提到的概念理清 Agent 架构中最经典的一句话:

保存的 Memory(记忆)与发送给 Model 的 Context(上下文),不一定是同一回事

  • 数据库里存的 Memory:可以是完整、未经修改的 100 轮原始聊天记录(用于审计、人工复盘、客诉追踪)

  • 发送给 Model 的 Context:经过了摘要与裁剪,只包含了 【前情摘要】 + 【最近 10 轮对话】

Memory 关注 "全量存下了什么",而 Context 关注 "本次精简投喂了什么"

总结

本章开始学习 LangChain Agent 的 上下文与短期记忆机制。我们首先梳理了 Context、State 与 Memory 之间的关系,并理解了 Agent 的记忆本质上是对运行状态的保存、恢复与再次利用

随后,我们通过 InMemorySaver 实现了基于内存的短期记忆,并进一步使用 PostgreSQL 等外部存储实现持久化,理解了 Checkpointer、thread_id 与 Agent State 在多轮会话中的协作机制

最后,我们学习了消息裁剪、消息删除和摘要等记忆治理策略。记忆并不是保存得越多越好,更重要的是合理区分:

Memory 决定保存什么,Context 决定本次让模型看到什么

下一篇将继续学习长期记忆,看看如何让 Agent 跨越单个会话,在不同 Thread 之间持续保存和检索用户信息、知识与经验

相关推荐
飞哥数智坊27 分钟前
直播半小时,我聊了聊 Agent 最常见的 3 个疑问
人工智能
circuitsosk30 分钟前
任务规划器的三种范式对比:ReAct、Plan-and-Execute 与 Tree-of-Thought 在真实业务中的取舍
前端·javascript·python·react.js·llm·ai agent
shehuiyuelaiyuehao42 分钟前
算法31,前缀和,可被k整除的子数组
数据结构·python·算法
ggb喔1 小时前
AI 原生攻防时代:2026 年渗透测试与逆向开发的范式重构
人工智能·重构
宸津-代码粉碎机1 小时前
AI攻防战升级!基于Spring AI构建Java应用自动免疫安全体系
java·大数据·开发语言·人工智能·python·安全·spring
测试19981 小时前
如何用appium搭建Android自动化测试框架?
自动化测试·软件测试·python·测试工具·职场和发展·appium·测试用例
程序员大雄学编程1 小时前
微积分46. 无穷级数四大核心性质:从理论到实践的全方位解析
python·学习·微积分·学习工具
克里斯蒂亚诺更新2 小时前
机器学习库sklearn的主要任务
人工智能·机器学习·sklearn
先跑起来再说2 小时前
Qavor:一个能跑、能观测、能扩展的开源 AI Agent 工作台
人工智能·开源