把 AI Agent 当成一个会忘事的对话机器人,等于把它困在当前 session。短期记忆靠 LLM 上下文窗口维持,目标是让”现在这一通对话”不丢主语;长期记忆则依赖向量库、知识图谱或关系型数据库做持久化,目标是让 Agent 跨会话、跨天、甚至跨用户记住偏好、事件和经验。一套生产级 Agent 通常把两者组合使用——短期记忆负责流畅,长期记忆负责个性化。下文把记忆的分层、典型实现、压缩策略与一段可运行代码逐项拆开。
一、Agent 记忆的分层结构
记忆不是单一存储,而是分层协作的”金字塔”。从快到慢、从临时到持久,一般分四层:
| 层级 | 生命周期 | 存储位置 | 读写速度 | 典型用途 |
|---|---|---|---|---|
| 工作记忆(Working) | 当前 turn | LLM 上下文窗口 | 极快 | 工具调用、推理中间状态 |
| 短期记忆(Short-term) | 单次会话 | 内存 / Redis | 快 | 完整对话历史、近期 tool 输出 |
| 长期记忆(Long-term) | 跨会话 | 向量库 / KV / 图 | 中 | 用户偏好、事实知识、过往事件 |
| 程序性记忆(Procedural) | 持续 | Prompt 模板 / 微调权重 | 慢 | 已固化的工具调用流程与经验 |
工作记忆随每轮推理结束即被压缩或丢弃,短期记忆随会话结束清空,长期记忆则写到外部存储。程序性记忆相对特殊——它把”成功的轨迹”沉淀为模板或 LoRA 适配器,让 Agent 不必每次从头推理。Mem0 框架对四类记忆都有统一 API:episodic(情景)、semantic(语义)、procedural(程序)、associative(关联),开发时通过 memory_type 字段即可路由。
二、短期记忆的实现要点
短期记忆的核心是”塞得进上下文窗口”和”不会把窗口塞爆”。工程上有三条主线:
- 滚动缓冲(Buffer):把全部对话原样塞进 prompt,最简单但 token 消耗最大,适合回合数 < 20 的场景;
- 滑动窗口(Sliding Window):只保留最近 N 轮,超出部分丢弃;LangChain 的
ConversationBufferWindowMemory是该思路的标准实现; - 摘要压缩(Summary):把较早的对话交给 LLM 生成 4-6 条 bullet 总结,腾出空间给新的轮次。
ConversationSummaryMemory即采用该模式。
经验上,单纯滚动缓冲遇到第 30 轮后会出现”开头用户的需求被忘掉”的现象;引入摘要后,丢失感明显降低但摘要本身的偏差会随轮次累积。组合使用(最近 10 轮原文 + 之前所有对话的摘要)是工程上的折中点。
from langchain.memory import (
ConversationBufferWindowMemory,
ConversationSummaryMemory,
)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 短期记忆:最近 10 轮原文 + 历史摘要
short_term = ConversationBufferWindowMemory(
k=10,
memory_key="chat_history",
return_messages=True,
)
summarizer = ConversationSummaryMemory(
llm=llm,
memory_key="summary",
return_messages=True,
)
# 每次新增对话后调用 save_context 即可
short_term.save_context(
{"input": "帮我把昨天写的报告找出来"},
{"output": "已定位到 reports/2025-q3.md"},
)
代码块展示的是最常见的双轨结构。ConversationBufferWindowMemory 保留最近 10 轮原始消息,ConversationSummaryMemory 把超出窗口的内容压成 4-6 条要点。两者输出的 chat_history 与 summary 字段在拼 prompt 时直接拼到 system message 之后即可。
三、长期记忆的实现路径
长期记忆的关键不是”存进去”,而是”用得上”。三类实现路线覆盖了绝大多数生产系统:
3.1 向量库路线(语义记忆)
把所有需要长期保留的文本切片后做 embedding 写入向量库,运行时按相似度检索。FAISS、Pinecone、Weaviate、Chroma、Milvus 是常见选型。优势是检索自然语言友好,代价是缺少”硬关联”——比如”用户叫张三”和”用户注册于 2024-05″之间的显式链接,向量检索需要靠 prompt 拼回。
3.2 知识图谱路线(关联记忆)
把实体、关系用三元组(subject-predicate-object)存到图数据库(Neo4j、Neptune Analytics),适合表达”用户-项目-文档”这类多跳关系。A-MEM 系统借鉴 Zettelkasten 卡片盒思路,每条记忆自动生成关键词、标签和指向其他记忆的链接,让记忆库本身随写入持续演化。
3.3 关系型 / KV 路线(情景记忆)
把对话原文按 (user_id, session_id, created_at) 索引存到 PostgreSQL 或 DynamoDB,查询时按时间窗或元数据过滤。该路线在合规审计、GDPR 删除权等场景下不可替代——向量库里的 embedding 想”忘记”某条记录极麻烦,但关系库里执行 DELETE 即可。
3.4 长期记忆的统一抽象
现代框架(Mem0、Letta、MemoryBank)会把三类存储封装成同一组 CRUD API。下面这段示例展示了如何用 Mem0 把用户偏好写进长期记忆,并按用户 ID 隔离检索:
from mem0 import Memory
# 初始化:底层用向量库 + 关系库 + KV 三件套
mem = Memory.from_config({
"vector_store": {"provider": "chroma", "config": {"path": "./mem_db"}},
"llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
})
# 写入:mem0 会自动决定该信息是 semantic / episodic / procedural
mem.add(
"我叫张三,负责后端,平时用 Python",
user_id="user_42",
)
# 检索:按 user_id 过滤,避免跨用户串味
results = mem.search("他用什么编程语言?", user_id="user_42", limit=3)
落地时常见的踩坑点:写入时不做用户隔离,会导致”用户 A 问的问题匹配到用户 B 的记忆”;检索时不做相似度阈值过滤,会让”今天天气如何”这种 query 把所有旧对话都召回来。给 search 加一个 score_threshold 是个值得养成的习惯。
四、记忆压缩与遗忘
记忆库不可能无限膨胀。两条策略几乎一定会被用到:
- 压缩(Consolidation):定期(比如每 100 条新记忆触发一次)让 LLM 把相关条目合并成更短的总结,原始条目降级为低优先级。Mem0 把这个动作叫做
consolidate_memories; - 衰减(Decay):给每条记忆打一个
importance分,长期不被访问的自动降权或删除。人类记忆曲线p(t) = 1 - exp(-r * e^{-a t})经常被作为打分参考。
五、评估与多 Agent 共享
记忆不是”装上就能用”,必须可评估。常见做法是构造一套 recall 测试集,比如”用户 100 条历史偏好 → 命中率”、”跨会话问题答案正确率”等。多 Agent 协作时还需要共享记忆层:一个 Agent 学到的”用户在 macOS 上偏好深色模式”应该被其它 Agent 直接读到,而不是各自重复问。Mem0 的 share_memory(agent_id, memory) 即用于此场景。
到这里,记忆机制从分层、读写、压缩到评估的链路就完整了。
常见问题(FAQ)
Q1:短期记忆是不是直接把全部对话塞进 prompt?
不是,超过上下文窗口必须摘要或滑动窗口,否则旧消息会被截断。
Q2:长期记忆必须用向量库吗?
不一定;事实用 KV,关系用图,事件用关系库,按数据形态选存储。
Q3:记忆越多越好吗?
不是;需要压缩和衰减,否则检索精度与延迟会双双恶化。