AI 智能体记忆不是把聊天记录堆在上下文里那么简单。没有记忆的智能体,每轮对话都是「失忆重启」,无法追问、无法沉淀、也无法跨会话延续任务。本文从工程视角拆解智能体的三层记忆架构——工作记忆、短期记忆、长期记忆——并用向量库落地一套可写入、可检索、可压缩、可评测的长期记忆系统,让 Agent 真正「记得住、用得上、不膨胀」。
如果把记忆和上下文混为一谈,工程上一定会走弯路。大模型上下文工程解决的是「单次推理里塞什么」,而记忆解决的是「跨多次、跨多天,Agent 凭什么比上次更聪明」。两者互补,但实现路径完全不同。
一、为什么智能体必须自己管理记忆
LLM 本身是无状态的:每次推理只看到当前这一次传入的 token。上下文窗口无论多大(128K、200K、1M),关掉再打开,模型对之前发生的事一无所知。要让 Agent 具备「连续性」,记忆必须由应用层显式地存、取、管——模型不负责记,应用负责。
没有记忆的 Agent 有三个硬伤:① 无法多轮追问(上一轮说的约束忘了);② 无法从历史交互中学习用户偏好;③ 无法在长任务里累积中间结论。这也是为什么 LangChain Agent 这类框架都要内置 Memory 抽象——它本质上是把「状态」从模型外部接管过来。
二、三层记忆架构:工作 / 短期 / 长期
成熟的智能体记忆通常分三层,对应不同的容量、时效和存储介质:
| 层级 | 容量 | 时效 | 典型存储 | 职责 |
|---|---|---|---|---|
| 工作记忆 | 小(当前轮) | 毫秒级 | 运行时变量 / 消息列表 | 本轮推理可用的全部上下文 |
| 短期记忆 | 中(最近 N 轮) | 会话内 | 内存 / Redis | 维持单次会话的连贯对话 |
| 长期记忆 | 大(跨会话) | 永久 | 向量库 + 关系表 | 沉淀用户画像、历史结论、知识 |
工作记忆就是当前 prompt 里的全部内容;短期记忆是「这一通对话」的滑动窗口;长期记忆才是真正让 Agent 越用越聪明的部分,也是本文重点。
三、长期记忆的存储选型
长期记忆既要「语义检索」(「上次客户投诉过什么」),又要「精确查询」(「用户手机号」)。单一存储很难兼顾,常见组合是「向量库 + 结构化表」:
| 存储 | 适合存什么 | 检索方式 | 代表实现 |
|---|---|---|---|
| 向量库 | 对话片段、经验、非结构化结论 | 相似度 / 语义检索 | Chroma、PGVector、Qdrant |
| 关系表 / KV | 用户画像、偏好、实体属性 | 主键 / 字段精确查 | MySQL、Redis、PostgreSQL |
| 对象存储 | 长文档原文、附件 | 路径引用 | S3 / 本地盘 |
向量检索部分和 Chroma 向量库、Embedding 向量检索 的原理完全一致:把文本编码成向量,再按相似度召回最相关片段。区别只是这里召回的是「记忆」。
四、记忆的写入与检索流程
一个最小可用的长期记忆管理器,核心是两个动作:写(把一条经验向量化入库)和读(把相关记忆召回拼进 prompt)。下面是一段可直接跑的骨架:
import uuid, datetime
from embeddings import embed # 返回 768 维向量
from vectordb import query, insert
class MemoryStore:
def write(self, text, scope, meta=None):
"""把一条记忆写入长期存储。
scope 用于隔离不同用户/会话,避免串档。"""
vec = embed(text)
doc = {
"id": str(uuid.uuid4()),
"text": text,
"scope": scope, # 例如 user:1024
"meta": meta or {},
"created_at": datetime.datetime.utcnow().isoformat(),
}
insert(collection="memories", vector=vec, document=doc)
return doc["id"]
def recall(self, query_text, scope, top_k=5):
"""召回与当前问题最相关的历史记忆。"""
vec = embed(query_text)
hits = query(collection="memories", vector=vec,
filter={"scope": scope}, top_k=top_k)
return [h["text"] for h in hits]
关键点在于 scope 隔离:每个用户、每个会话的记忆必须分桶,否则 A 客户的隐私会出现在 B 客户的回答里——这是生产事故,不是优化项。
五、记忆压缩与去重:别让记忆膨胀
长期记忆如果不加治理,会无限增长:存储成本上升、检索噪声变大、prompt 被无关记忆塞爆。必须定期「压缩」——把零散片段聚合成高层摘要。常见做法是用模型自身做摘要:
# 把同一主题的 N 条旧记忆,压缩成一条语义摘要后回写
SUMMARY_PROMPT = """以下是某用户近 30 天的多条历史交互片段,
请提炼为一条不超过 120 字的长期记忆,保留:稳定偏好、反复出现的问题、已确认的结论。
只输出摘要,不要解释。
片段:
{fragments}"""
def compress(memories, scope):
fragments = "\n".join(m["text"] for m in memories)
summary = llm(SUMMARY_PROMPT.format(fragments=fragments))
# 删除旧碎片,写入摘要,避免重复占用
for m in memories: delete(m["id"])
return write(summary, scope, meta={"type": "summary"})
去重同样重要:语义相近的记忆要合并,而不是各存一份。可以借用 结构化输出 把记忆打上标签,再按标签做聚合,降低冗余。
六、从 episodic 到 semantic:让记忆沉淀为知识
认知科学把记忆分为 episodic(事件记忆,记得「上周三发生了什么」)和 semantic(语义记忆,记得「这个客户的通用偏好」)。智能体记忆的成熟标志,就是从 episodic 向 semantic 演进:
原始对话是 episodic,经过压缩、归纳后才变成 semantic。semantic 记忆更泛化、更省空间、更抗噪声,是 Agent「越用越懂你」的来源。工程上可以用一个标记字段区分两者,检索时优先返回 semantic,episodic 作为补充证据。
七、实战:一个带记忆的客服 Agent
把上面的零件拼起来,一个最小客服 Agent 的推理循环长这样:
def answer(user_id, question):
# 1. 召回长期记忆
mem = store.recall(question, scope=f"user:{user_id}", top_k=5)
# 2. 召回业务知识(RAG)
kb = rag_search(question) # 见 RAG 进阶
# 3. 组装 prompt
prompt = build_prompt(question, memories=mem, knowledge=kb)
reply = llm(prompt)
# 4. 把本轮有价值的结论写回长期记忆
if is_worth_remembering(question, reply):
store.write(f"Q: {question}\nA: {reply}", scope=f"user:{user_id}")
return reply
注意第四步的「是否值得记」闸门:不是每句话都入库,否则噪声会淹没信号。可以简单用「用户表达了偏好 / 解决了具体问题 / 修正了之前的回答」作为写入触发条件。这个 Agent 的编排框架可参考 MCP 与 A2A 协作 的调用规范。
八、可观测与评测:记忆到底有没有用
记忆系统上线后必须能量化效果,否则只是多了个存储。建议埋三条指标:① 记忆命中率(多少问题召回了相关记忆);② 复用率(被召回的记忆最终进入答案的比例);③ 任务完成率在有/无记忆下的对比。观测可以直接接 Langfuse 这类链路追踪,把每次回忆和写入都打点。更系统的能力评测方法见 AI Agent 评测基准。
九、最常见的五个反模式
| 反模式 | 表现 | 后果 |
|---|---|---|
| 不隔离 scope | 所有用户记忆混在一个桶 | 串档泄露隐私,严重事故 |
| 全量回灌上下文 | 每次把全部记忆塞进 prompt | token 爆炸、噪声淹没信号 |
| 从不压缩 | 记忆只增不删 | 存储与检索成本线性上涨 |
| 记了不校验 | 模型生成的「记忆」含幻觉 | 错误信念被长期固化为「事实」 |
| 无评测 | 记忆系统上线即弃管 | 无法证明收益,易被砍 |
十、落地检查清单
如果你打算给自己的 Agent 加记忆,按这个顺序走:① 先定 scope 隔离规则(用户/会话分桶);② 选「向量库 + 结构化表」双存储;③ 实现 write / recall 两个最小接口;④ 加「值得记」闸门与定期压缩;⑤ 接可观测,跑有/无记忆的对照评测。记忆不是炫技,它的唯一价值是让 Agent 在第二次见面时,比第一次更懂你。




