一、短期记忆与长期记忆的区别
Agent 的记忆系统通常可以从使用范围和生命周期的角度划分为短期记忆与长期记忆。
| 对比维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 作用范围 | 当前会话或任务 | 跨会话复用 |
| 主要内容 | 对话历史、任务状态、工具结果 | 用户偏好、历史经验、长期知识 |
| 生命周期 | 通常随会话或任务生命周期管理 | 按业务需求长期保存 |
| 典型实现 | LangGraph Checkpointer、Redis | 数据库、向量数据库、LangGraph Store |
| 核心目标 | 保持当前任务的上下文连续性 | 积累可复用的信息 |
例如,在一个图片换色 Agent 中:
- 短期记忆:当前处理的图片、用户选择的墙面、目标颜色、家具保持不变的约束。
- 长期记忆:用户长期偏好暖色系、习惯使用某种色彩空间、历史项目中的常用配色方案。
两者可以协同工作,但不应该将所有对话内容都直接转化为长期记忆。
二、短期记忆应该保存哪些内容?
在实际开发中,建议将短期记忆划分为两个核心部分。
3.1 对话历史(Message History)
对话历史记录用户与 Agent 的交互,包括:
- 用户输入消息。
- Agent 生成的回复。
- Agent 发起的工具调用。
- 工具返回的执行结果。
在 LangGraph 中,可以通过 messages 字段维护消息列表,并使用 add_messages 进行消息合并。
python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
add_messages 不只是普通的列表拼接,它会按照 LangGraph 的消息合并规则处理消息,支持根据消息 ID 更新已有消息。
需要注意,对话历史保存完整,并不意味着每轮都应该将全部历史发送给 LLM。随着对话增长,还需要进行上下文裁剪或摘要。
3.2 任务状态(Task State)
对于具备工具调用能力的 Agent,仅保存对话历史往往不够。
假设 Agent 正在执行图片局部换色任务,如果每次都让模型从完整对话中重新提取图片 ID、目标区域和颜色参数,不仅增加模型调用成本,也容易出现参数遗漏或理解偏差。
因此,可以通过结构化状态保存关键业务信息。
python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class ColorAgentState(TypedDict):
messages: Annotated[list, add_messages]
task_type: str
image_id: str
target_region: str
target_color: str
constraints: list[str]
task_status: str
状态示例:
python
state = {
"messages": [],
"task_type": "recolor",
"image_id": "img_123",
"target_region": "wall",
"target_color": "#F5EBDD",
"constraints": ["家具保持不变"],
"task_status": "processing",
}
以上代码展示了状态结构,实际使用时需要根据用户输入和工具执行结果更新对应字段,并在创建新会话时初始化状态。
这种设计将自然语言交互与结构化任务执行相结合,使 Agent 能够更可靠地处理连续任务。
三、LangGraph 如何实现短期记忆?
LangGraph 提供了基于图执行状态的持久化机制,可以通过 Checkpointer 保存和恢复某个会话的状态。
理解这一机制,需要掌握三个核心概念。
4.1 State:定义 Agent 的状态
State 用于描述 Agent 在执行过程中可以读取和更新的数据。
例如:
python
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
task_status: str
当节点执行时,可以读取当前状态并返回需要更新的字段。LangGraph 会根据字段对应的更新规则合并状态。
4.2 Thread ID:标识会话
LangGraph 使用 thread_id 标识一个执行线程所对应的状态。
例如:
python
config = {
"configurable": {
"thread_id": "conversation_001"
}
}
当同一用户继续当前会话时,后续调用应使用相同的 thread_id,从而读取对应的历史状态。
如果使用新的 thread_id,则会访问另一个独立的会话状态。
在 Web 应用中,可以由后端维护 user_id、conversation_id 与 thread_id 的映射关系,并进行权限校验,不能直接信任客户端传入的任意会话 ID。
4.3 Checkpointer:保存和恢复状态
Checkpointer 负责在图执行过程中保存状态快照,并在后续调用中恢复对应状态。
常见选择包括:
InMemorySaver:适合本地开发、测试和学习。- PostgreSQL Checkpointer:适合需要可靠持久化和状态恢复的场景。
- Redis Checkpointer:适合希望利用 Redis 基础设施管理会话状态的场景,具体能力取决于所选实现。
需要注意,Checkpointer 保存的是图执行状态,不只是对话文本。
4.4 完整代码示例
下面使用 InMemorySaver 演示最小化的多轮会话记忆。
python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import InMemorySaver
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
def reply(state: AgentState):
# 示例节点:实际项目中可以替换为 LLM 调用
return {
"messages": [
{
"role": "assistant",
"content": "已收到,我会结合当前会话继续处理。"
}
]
}
builder = StateGraph(AgentState)
builder.add_node("reply", reply)
builder.add_edge(START, "reply")
builder.add_edge("reply", END)
graph = builder.compile(
checkpointer=InMemorySaver()
)
config = {
"configurable": {
"thread_id": "conversation_001"
}
}
result = graph.invoke(
{
"messages": [
{
"role": "user",
"content": "帮我把墙面改成暖白色"
}
]
},
config=config,
)
print(result["messages"])
这段代码主要用于展示 Checkpointer 的使用方式。由于 reply 是固定回复节点,并未真正调用 LLM,因此它不是完整的对话 Agent。
实际项目中,第二次调用可以继续使用同一个 thread_id,并传入新的用户消息。LangGraph 会从 Checkpointer 中恢复该线程的状态。
需要特别注意: InMemorySaver 仅将状态保存在当前进程的内存中。应用重启后,不应指望它恢复之前的会话;生产环境需要根据可靠性和部署需求选择合适的持久化 Checkpointer。
四、短期记忆的工程化设计
在生产环境中,仅仅接入 Checkpointer 还不够,还需要解决以下问题。
5.1 上下文窗口管理
LLM 的上下文窗口有限。如果将所有历史消息都发送给模型,会增加 Token 消耗、推理延迟,并可能引入大量无关信息。
常见的处理策略包括:
- 最近消息保留:保留最近若干轮对话。
- 历史摘要:将较早的对话压缩为摘要。
- 工具结果裁剪:避免将大型工具输出完整地重复放入上下文。
- 关键状态独立保存:将当前任务参数、用户约束和执行进度放在结构化 State 中。
其中,任务参数不应该仅依赖历史摘要保存。例如,图片 ID、用户明确指定的颜色和不可修改的区域,通常需要保留在结构化状态中。
裁剪历史时还需要注意工具调用与工具返回消息的关联,避免破坏模型要求的消息顺序和调用上下文。
5.2 会话隔离
多用户场景下,短期记忆必须严格隔离。
建议建立以下映射关系:
user_id
|
v
conversation_id
|
v
thread_id
|
v
LangGraph Checkpoint
其中:
user_id用于标识用户。conversation_id用于标识业务会话。thread_id用于定位 LangGraph 执行状态。
实际系统可以让 conversation_id 与 thread_id 一一对应,也可以使用其他映射策略。关键是确保每次访问会话状态前都完成用户身份和访问权限校验。
5.3 状态恢复与工具执行
对于需要调用外部 API 的 Agent,状态恢复并不等于业务操作自动恢复。
例如,图片换色任务可能已经调用 Python 图像处理服务,但在保存结果前服务发生异常。如果 Agent 恢复后直接重新执行工具,可能导致重复操作。
因此,应结合以下机制设计:
- 任务执行状态记录。
- 工具调用幂等性。
- 异常重试与超时处理。
- 工具执行结果持久化。
- 必要时使用业务任务 ID 关联图执行状态。
Checkpointer 负责图状态的保存与恢复,而外部工具的事务、幂等和副作用需要由业务层负责。
5.4 记忆的生命周期
短期记忆也需要明确的保留和清理策略。
例如:
- 对话消息按照业务规定的保留期限管理。
- 已完成任务可以保留必要的执行记录。
- 临时图片文件可以设置独立的过期策略。
- 对于敏感数据,应明确访问权限、删除机制和日志脱敏规则。
不要因为数据存储在 Checkpointer 中,就默认它会自动过期或自动清理。
五、Redis 和 PostgreSQL 应该如何选择?
短期记忆并不要求一定使用某种特定数据库。应根据项目已有基础设施、持久化需求和运维能力进行选择。
| 方案 | 适用场景 | 主要考虑 |
|---|---|---|
| InMemorySaver | 本地开发、功能验证 | 简单,但不适合依赖跨重启恢复的生产场景 |
| Redis Checkpointer | 已有 Redis,希望低延迟访问会话状态 | 需确认具体实现、持久化配置及清理策略 |
| PostgreSQL Checkpointer | 需要可靠持久化与状态恢复 | 需要管理数据库连接、Checkpoint 表和历史清理 |
Redis 和 PostgreSQL 是存储后端,而不是短期记忆本身。
真正的短期记忆机制包括状态定义、会话隔离、上下文更新、状态持久化和恢复策略。数据库负责提供相应的存储能力。
另外,LangGraph 的 Checkpointer 与 Store 职责不同:
- Checkpointer:面向某个 thread 的执行状态,支持当前会话的短期记忆。
- Store:用于跨 thread 保存和检索长期记忆。
不应把两者混为一谈。
六、推荐的整体架构
对于一个具备工具调用、RAG 和多轮任务处理能力的 Python Agent,可以采用以下分层结构:
用户请求
|
v
会话管理层
|
|-- user_id
|-- conversation_id
|-- thread_id
|
v
Agent 状态管理层
|
|-- messages:对话历史
|-- task_state:当前任务状态
|-- tool_results:工具结果或引用
|
v
LangGraph 工作流
|
|-- 意图识别
|-- 上下文构建
|-- LLM 推理
|-- Tool Calling
|-- 状态更新
|
v
Checkpointer
|
v
Redis / PostgreSQL
长期记忆可以作为独立模块,通过 Store 或业务数据库进行管理,在需要时检索用户偏好、历史知识等信息,再将相关结果注入当前推理上下文。
这种架构将会话管理、任务状态、执行流程和存储后端进行了解耦,便于后续扩展。
七、总结
Python Agent 的短期记忆不只是保存历史聊天记录,而是围绕当前会话维护完整的上下文和执行状态。
在 LangGraph 中,可以使用 State 描述当前状态,使用 thread_id 标识会话,并通过 Checkpointer 保存和恢复图执行状态。再结合上下文裁剪、任务状态管理、会话隔离和生命周期控制,就可以构建较为完整的短期记忆体系。
在工程实践中,可以遵循以下原则:
- 对话历史与结构化任务状态分开管理。
- 使用稳定的会话标识实现状态隔离。
- 使用 Checkpointer 实现状态持久化与恢复。
- 使用裁剪和摘要控制上下文长度。
- 对外部工具的执行副作用单独设计幂等与恢复机制。
- 将跨会话的用户偏好和长期知识交由长期记忆系统管理。
核心思想:短期记忆解决的是 Agent 在当前会话中如何持续理解上下文、维护任务状态并延续执行,而不仅仅是让模型记住之前说过的话。