目录
[一、为什么 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 之后,三者的协作逻辑便清晰可见:
-
State 是静态的数据载体 :它记录了 Agent 此时此刻运行到了哪一步、手里有什么数据
-
Memory 是保存机制 :它解决的是当这一轮 Agent 执行结束、进程挂起后,如何将 State 保存下来,并在用户下一次发问时原样恢复
-
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 之间持续保存和检索用户信息、知识与经验
