看到“AI Agent 失忆”这个话题,很多开发者第一反应是模型不够聪明,或者 Prompt 写得不到位。但如果你亲手做过一个多轮对话的 Agent,大概率会遇到这种场景:用户第一轮告诉你“我是做电商运营的,周报请用表格输出”,第二轮它就开始输出大段散文;用户刚说过“不要用 MySQL,我用的是 PostgreSQL”,下一轮它又给出 MySQL 的连接方式。
你以为是模型能力不行?往往不是。真正的问题在于:Agent 没有一套属于自己的记忆系统。
2026 年的 AI Agent 开发,纯粹的 Prompt 技巧和工具调用已经很难拉开差距。大家都会写 system prompt,都会接工具,都能跑通一个 Demo,但真正决定产品体验上限的,是记忆能不能跨轮、跨会话、跨多个 Agent 被复用。这个主题对应到很多视频教程里,就是所谓的“Agent 记忆”。我不太建议把“B站No.1”这类称呼太当真,但“失忆”确实是 Agent 从玩具走向生产力的第一道坎。
这篇文章从理论讲到实战。我会先把 Agent 记忆的分层模型讲清楚,然后带你用一个基于 SQLite 的最小记忆模块跑通整个流程,最后补充 Redis 共享记忆和向量检索的工程选型思路。全文代码基于 Python 标准库和常见的模型接口,版本信息请以你本机的实际环境为准。读完之后,你应该能回答三个问题:Agent 为什么会失忆?一套可落地的记忆系统由哪些组件构成?自己的项目应该从哪种记忆方案开始?
1. Agent 失忆问题的本质:不是模型不行,而是没有记忆系统
要解决失忆,先要承认一个事实:大模型本身没有传统意义上的“记忆”。每一次 API 调用,模型接收的是文本输入,输出的是文本结果。模型内部并没有一个数据库,去保存“用户上一轮说过什么”“用户偏好是什么”“这个任务进展到哪一步了”。
你可能觉得这是废话,但在实际开发里,很多团队都是在出了 Bug 之后才想起来查日志,看是不是消息列表没有拼对,或者 context 被截断了。这里有三类最常见的失忆原因。
第一类:对话上下文没有被完整传递。Agent 和用户聊天时,后端需要把历史消息拼到 messages 数组里。一旦数组被清空、被截断,或者遗漏了某个角色的消息,模型自然就“忘了”。
第二类:上下文窗口有限。即使你把所有历史消息都传给了模型,窗口长度也是有限制的。对话超过几千 token,早期内容就会被挤掉。用户最开始的偏好设定、任务目标,恰恰是最容易被丢弃的。
第三类:没有跨会话持久化。很多 Agent 只支持单轮请求,或者虽然支持多轮,但服务一重启就全部丢失。用户下次再来,又是陌生人的状态。
所以,“Agent 失忆”本质上不是一个模型智商问题,而是一个系统架构问题。你需要在模型之外,设计一层能够读写、更新、遗忘的记忆系统,把用户和任务的长期状态保存下来。
这也解释了另一个常见误区:有人以为“上下文越长,Agent 记忆力越强”。实际上,盲目拉长 context 只会让模型的注意力被稀释,还会让请求变慢、成本变高。正确做法是把“需要长期记住的信息”和“临时对话信息”分开管理。这就是我们常说的记忆分层。
2. 记忆的分层模型:短期记忆、长期记忆与工作记忆
AI Agent 的记忆,目前业界没有一个 100% 统一的标准术语,但大部分框架和教程都接受一个分层模型:短期记忆、长期记忆、工作记忆。另外有些教程会把它概括为“双网络记忆模型”,本质上也是短期/长期配合的方式。
先给这三个概念做一个通俗解释。
短期记忆,对应的是当前对话窗口里的上下文。它不需要额外存储,因为你只要把 messages 传给模型,模型就能够“看到”最近几轮的内容。缺点是窗口有限,超过长度就会溢出。
长期记忆,对应的是跨会话、跨任务需要持久化的信息。比如用户的姓名、职业、使用偏好、项目历史结论。它通常落到数据库、文件、Redis、向量数据库中,在需要时检索出来,重新注入到上下文里。
工作记忆,对应的是 Agent 正在执行当前任务时产生的中间状态。例如一个多步骤任务执行到第几步、刚刚调用的工具返回了什么结果、下一步要做什么。这部分不需要永久保存,但必须在任务执行期间随时可访问。
用一个表格来对比会清楚一些:
| 记忆类型 | 生命周期 | 存储位置 | 典型实现 | 失效场景 |
|---|---|---|---|---|
| 短期记忆 | 当前对话或任务期间 | messages 上下文 | 内存、对话窗口 | 会话结束、上下文被截断 |
| 长期记忆 | 跨会话、跨任务 | 外部存储 | SQLite、Redis、向量数据库 | 没有持久化或遗忘机制 |
| 工作记忆 | 单个任务执行期间 | 运行时状态 | Agent 内部状态对象 | 任务结束、进程重启 |
需要特别注意:这里说的“长期记忆”和深度学习里的 LSTM 长短期记忆网络并不是一回事。LSTM 是循环神经网络的一种结构,主要解决 RNN 处理长序列时的梯度消失问题,而 Agent 记忆是系统层面上的数据存储与检索设计。两者都可以叫“记忆”,但应用层次完全不同。如果你在搜资料时看到 LSTM 相关的内容,不要直接套到 LLM Agent 上。
从工程实现来看,短期记忆通常不需要你额外写代码,只要把 messages 列表管理好即可。真正需要设计的是长期记忆和工作记忆。大多数 Agent 项目的问题,都出在“什么信息该进入长期记忆、什么信息只需要留在短期记忆”没有想清楚。
3. 一套记忆系统需要哪些能力
理解了分层模型之后,我们再往前一步:如果让你自己设计一个记忆模块,它至少要有四种能力。
写入能力:Agent 每完成一轮对话或任务,要把值得记住的信息抽取出来并写入存储。注意不是把所有原始文本都塞进去,而是要去重、清洗、提取关键结论。
存储能力:这是最容易被想到的部分。你可以用关系型数据库、键值数据库、向量数据库,甚至 JSON 文件。存储方案会直接影响后续的检索效果和扩展性。
检索能力:这是记忆系统的核心。用户提出新问题时,Agent 要从长期记忆中找出与之相关的历史信息,再注入到 Prompt 里。检索策略决定了“记忆到底用得上用不上”。
更新与遗忘能力:记忆不是只增不改。用户的偏好会变化,旧的任务结论会过期,甚至用户会主动要求删除某些信息。一个没有遗忘机制的记忆系统,会在运行一段时间后积累大量噪声,反而降低模型表现。
很多教程只强调“把历史消息存起来”,这其实是错误的理解。如果每次对话都把全部历史消息塞进长期记忆,那和把 context 拉长没有本质区别。真正的长期记忆,应该是经过筛选、去重、索引的信息库,而不是对话流水账。
用一个类比来说:短期记忆像你手上正在读的草稿纸,工作记忆像你脑子里当前要完成的几件事,长期记忆像你存放在图书馆里的笔记。你需要写笔记,更需要知道如何快速找到对应的那一页。不要总想着把所有东西都背下来,那是低效的。
4. 环境准备与依赖安装
进入实战之前,先检查环境。本文的示例代码以 Python 为主,官方支持的版本建议使用 3.10 或更高版本。如果你用的是 3.8 或 3.9,大部分代码也能运行,但遇到类型注解或新语法时需要做小调整。
需要准备以下内容:
- Python 3.10+,已配置好 pip;
- SQLite 3,Python 3 自带 sqlite3 模块,无需额外安装;
- Redis 服务(可选,用于多 Agent 共享记忆演示);
- 一个模型服务。可以是 OpenAI 兼容接口,也可以是本地部署的模型服务。
如果只是先跑通记忆模块本身,其实不需要安装第三方包,因为 sqlite3 是 Python 标准库。但如果你要接入模型,建议安装 OpenAI 的 Python SDK,或者使用任何与 OpenAI 兼容的 request 封装。
pip install openai如果要把记忆存到 Redis,再安装 redis 客户端:
pip install redis这里不写死版本号,是因为 2026 年的 SDK 版本变化很快,你安装时直接使用当前最新稳定版即可。如果公司内部有固定的 Python 版本或依赖锁定机制,请以项目实际约束为准。
另外,如果你使用本地模型,例如通过 Ollama、vLLM 或 XInference 启动了一个 OpenAI 兼容服务,代码中只需要修改 base_url 和 model 名称。本文后续示例会提供一个模拟模型回包的版本,让你在没有模型 API 的情况下也能验证记忆逻辑是否生效。
5. 实战一:用 SQLite 做一个可持久化的记忆模块
我们从一个最小的记忆模块开始。选用 SQLite 而不是向量数据库,是为了先讲清楚记忆的读写链路,避免把问题复杂化。
5.1 记忆表设计
新建一个文件memory_store.py,内容如下:
# 文件路径:memory_store.py import sqlite3 import time class SimpleMemory: def __init__(self, db_path="agent_memory.db"): self.conn = sqlite3.connect(db_path) self._init_table() def _init_table(self): self.conn.execute(""" CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, memory_type TEXT NOT NULL DEFAULT 'long_term', created_at REAL NOT NULL, updated_at REAL NOT NULL ) """) self.conn.commit() def add_memory(self, content, memory_type="long_term"): now = time.time() self.conn.execute( "INSERT INTO memories (content, memory_type, created_at, updated_at) " "VALUES (?, ?, ?, ?)", (content, memory_type, now, now), ) self.conn.commit() def search_memory(self, keyword, limit=10): cursor = self.conn.execute( "SELECT content, memory_type, updated_at FROM memories " "WHERE content LIKE ? ORDER BY updated_at DESC LIMIT ?", (f"%{keyword}%", limit), ) return cursor.fetchall()这段代码里最关键的是 SQL 语句中的参数化查询。用户输入的内容不应该通过字符串拼接直接进入 SQL,否则会引入注入风险。使用?占位符是最基本的安全习惯。
memory_type字段用来区分长期记忆和短期记忆。在最小版本中,你可以先统一存为long_term,但建议保留这个字段,方便后续扩展。
5.2 写入与检索的基本流程
在同一个文件末尾加一段测试入口:
# 文件路径:memory_store.py if __name__ == "__main__": memory = SimpleMemory("test_memory.db") memory.add_memory("用户喜欢用 Python 写自动化脚本") results = memory.search_memory("Python") for content, memory_type, updated_at in results: print(content, memory_type)运行方式很简单:
python memory_store.py预期输出:
用户喜欢用 Python 写自动化脚本 long_term这个例子虽然简单,但已经把“写入 - 存储 - 检索”的闭环跑通了。你可能会说:这不是数据库查询吗?对,最小记忆系统本质上就是一个有读写能力的存储层。真正的难度在于,当你接入 Agent 之后,什么时候写入、检索到什么内容、怎么注入到 Prompt 里。
6. 实战二:把记忆接入 Agent 对话流程
有了记忆模块,下一步就是把记忆注入到 Agent 的对话流程中。我推荐的做法是:在每轮调用模型之前,先从长期记忆里检索与当前用户消息相关的历史内容,放进 system prompt;模型回复之后,再把本轮的要点写入长期记忆。
这样既不会把全部历史文本塞给模型,又能让模型“想起”关键信息。
6.1 模拟模型接口
为了让你在没有模型 API 的情况下也能跑通演示,我先写一个模拟call_model函数。它的逻辑很简单:如果 system prompt 里包含“张三”,就返回“我记得你叫张三”,否则返回“我没有找到相关记忆”。
# 文件路径:agent_with_memory.py from memory_store import SimpleMemory def call_model(messages): # 仅用于本地演示,生产环境请替换为真实模型接口 system_prompt = messages[0]["content"] if messages[0]["role"] == "system" else "" if "张三" in system_prompt: return "我记得你叫张三,有什么可以帮你?" return "我没有找到相关记忆。"真实项目里,你可以把这段替换成 OpenAI 兼容接口,例如:
def call_model(messages): import openai client = openai.OpenAI( base_url="http://localhost:8000/v1", api_key="EMPTY", ) response = client.chat.completions.create( model="your-model-name", messages=messages, ) return response.choices[0].message.content这里的base_url和model需要根据你自己的模型服务调整。选“OpenAI 兼容接口”的原因是,2026 年主流的本地模型服务、云端模型 API 基本都支持这套协议,接入成本最低。
6.2 Agent 记忆读写逻辑
下面是完整的 Agent 对话类:
# 文件路径:agent_with_memory.py class AgentWithMemory: def __init__(self, db_path="agent_memory.db"): self.memory = SimpleMemory(db_path) self.messages = [] def chat(self, user_input): # 1. 检索相关历史记忆 relevant = self.memory.search_memory(user_input) memory_text = "\n".join([f"- {content}" for content, _, _ in relevant]) # 2. 组装 system prompt system_prompt = f"你是 AI 助手,以下是和你相关的用户长期记忆:\n{memory_text}\n请结合记忆回答。" prompt_messages = [{"role": "system", "content": system_prompt}] + self.messages[-5:] + [ {"role": "user", "content": user_input} ] # 3. 调用模型 response = call_model(prompt_messages) # 4. 保存本轮对话到消息列表 self.messages.append({"role": "user", "content": user_input}) self.messages.append({"role": "assistant", "content": response}) # 5. 写入长期记忆 self.memory.add_memory(f"用户说:{user_input}", memory_type="long_term") return response if __name__ == "__main__": agent = AgentWithMemory("agent_demo.db") print(agent.chat("我的名字叫张三,请记住。")) print("---") print(agent.chat("我叫什么名字?"))这段代码有几个设计点需要说明。
第 1 步的search_memory用的是简单的LIKE关键词匹配。实际使用时,关键词匹配非常脆弱,因为用户表达同一个意思可能用完全不同的词语。后面我会讲到怎么升级为向量检索。
第 2 步只把最近 5 条消息传给模型,这是对短期记忆做滑动窗口。很多开源框架的默认做法类似,并不需要把所有历史消息都传进去。
第 5 步写入长期记忆时,我偷懒直接存了“用户说:xxx”。实际项目中,你应当让模型先对用户输入做一次信息抽取,只保存“用户名字是张三”“用户偏好表格输出”这样的结构化结论,而不是把原始对话直接存库。这可以避免长期记忆变成流水账。
6.3 运行验证
执行:
python agent_with_memory.py预期输出类似:
我没有找到相关记忆。 --- 我记得你叫张三,有什么可以帮你?第一轮没有找到相关记忆,因为数据库是空的。第一轮之后,“用户说:我的名字叫张三,请记住。”被写入了 SQLite(注意实际演示代码里我们存的是整句,而不是提取后的结论)。第二轮检索时,因为LIKE '%名字%'命中了这行,所以 system prompt 里包含了“张三”,模拟模型据此返回了记忆中的信息。
这个小 Demo 已经能说明整套链路:记忆写入、历史检索、Prompt 注入、模型回复。如果你把call_model换成真实模型,就得到了一个最简单的带长期记忆的 Agent。
7. 实战三:用 Redis 实现多 Agent 共享记忆
单个 Agent 有记忆之后,下一个问题很自然会出现:多个 Agent 之间怎么共享记忆?例如一个团队有负责数据分析的 Agent,有负责报告生成的 Agent,它们需要知道同一个项目的背景和结论。如果各自用本地 SQLite,信息就无法互通。
Redis 是目前最常用的共享记忆中间件。它速度快,支持键过期,适合做短期共享状态,也适合做多个 Agent 之间的消息总线。下面用一个最小示例演示怎么把记忆写入 Redis 并读取。
# 文件路径:shared_memory_demo.py import redis r = redis.Redis(host="localhost", port=6379, db=0) # Agent A 写入共享记忆 r.set("memory:user_pref", "用户喜欢简洁回答,结论优先") # Agent B 读取共享记忆 pref = r.get("memory:user_pref") print(pref.decode("utf-8") if pref else "暂无共享记忆")Redis 可以当作一个简单的键值记忆库,但如果你要按语义检索,就不能只靠 key。更合理的做法是:把 Redis 当作短期共享状态的存储,把向量数据库或者关系型数据库当作长期记忆的主存储,Redis 存任务状态、会话锁、最近 N 轮对话等热数据。
多名开发者在同一个项目里使用共享记忆时,还要注意命名空间。比如所有 key 都加上用户 ID 或团队 ID 前缀:
# 文件路径:shared_memory_demo.py user_id = "user_10001" r.set(f"memory:{user_id}:pref", "用户喜欢表格输出")这样做可以避免不同用户、不同项目之间的记忆互相污染。生产环境还要给 Redis 设置密码,并遵循最小权限原则,不要随意开放公网访问。
8. 进阶:向量检索与长期记忆的工程选型
当记忆量增长到一定规模,LIKE关键词检索就远远不够了。例如用户说“我上次问过怎么部署网关”,但记忆里只存了“Nginx 配置”,关键词匹配可能搜不出来。这时需要做语义检索。
语义检索的大致流程是:先把记忆文本做向量化处理,得到 embedding 向量;把向量存入向量数据库;查询时把用户问题也转成向量,然后找出最相近的若干条记忆。你可以把它理解成从“查关键字”升级为“查意思”。
在一个实际项目中,长期记忆表的工程选型可以这样考虑:
| 方案 | 适合场景 | 优点 | 缺点 |
|---|---|---|---|
| SQLite | 单机、少量记忆、快速验证 | 零部署、简单可靠 | 并发弱、难扩展 |
| Redis | 多 Agent 共享短期状态 | 性能高、天然支持过期 | 不适合复杂语义检索 |
| PostgreSQL + pgvector | 中大型项目、需要事务和 SQL | 兼顾关系数据和向量 | 需要额外部署扩展 |
| 专用向量数据库 | 海量语义检索 | 检索能力强、水平扩展 | 运维成本高 |
向量检索的接入代码并不复杂,大体框架是这样的:
# 文件路径:vector_memory_demo.py # 伪代码,实际 API 以你选择的向量库为准 def add_to_vector_memory(text): vector = embed_model.encode(text) vector_db.insert({"text": text, "vector": vector}) def search_vector_memory(query, top_k=5): query_vector = embed_model.encode(query) results = vector_db.search(query_vector, top_k=top_k) return [item["text"] for item in results]关键是在写入记忆时不要只存原始文本,而要存“经过提炼的结论”。如果你把几十轮对话原文都向量化存进去,检索到的很可能是大量重复和无关信息。比较推荐的做法是:每次对话结束后,让模型用一句话总结“值得长期保存的关键信息”,再存入记忆库。
这里还要特别提醒:不要一上来就上向量数据库。绝大多数项目在早期,几百条记忆用 SQLite 的LIKE检索配合规则过滤就够用了。等用户量、记忆量、语义检索需求真正起来之后,再迁移到向量方案,成本反而更低。
9. 常见问题与排查思路
实践过程中,你会遇到各种奇怪问题。我整理了一份常见的排查清单,覆盖从记忆写入到模型回复的完整链路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 第二轮对话仍然不记得用户信息 | 长期记忆没有写入,或写入失败 | 检查数据库表是否有数据 | 在 add_memory 后查询确认 |
| 记忆有数据,但模型回答时没用到 | 检索结果为空,或没有注入 Prompt | 打印 system prompt,检查是否包含记忆内容 | 优化检索关键词,或改用向量检索 |
| 对话越久,请求越慢 | messages 列表无限增长 | 检查请求 token 数量 | 对短期记忆做滑动窗口 |
| 多个 Agent 记忆互相串 | 没有使用命名空间 | 查看 Redis key/数据库记录 | 在记忆 key 中增加用户维度和业务维度 |
| 写入的记忆是重复的 | 没有去重机制 | 查看数据库中重复记录数量 | 写入前先检索相似记忆,或使用唯一约束 |
| 用户要求删除记忆,但还查得到 | 没有清理接口 | 查看删除逻辑是否真正执行 | 实现按用户维度的删除/遗忘接口 |
| 重启后记忆丢失 | 配置了内存存储,未持久化 | 检查存储类型 | SQLite/Redis/数据库持久化 |
排查时遵循一个原则:先确认存储层有数据,再确认检索层能查到数据,最后确认 Prompt 层真的把数据给到了模型。三层链路,哪一层断了都会表现为“失忆”。
10. 最佳实践与工程建议
到这里,Agent 记忆的最小闭环已经实现了。但生产环境并不是“能跑就行”,下面这些建议会直接影响长期稳定性。
第一,记忆要按用户维度隔离。无论是 SQLite 还是 Redis,数据表或 key 中都要包含 user_id / project_id。否则,把 A 用户的记忆检索给 B 用户,不仅体验错误,还可能造成隐私泄漏。这个风险在生产环境是最高优先级的问题。
第二,信息写入前必须经过提炼。不要直接把原始对话写入长期记忆,而是让模型做一次“记忆抽取”,只保存事实、偏好、结论。例如从“我叫张三,我是运维工程师,喜欢用 Python”这句话里,抽取三条结构化记忆。原始日志可以另存,但不要污染长期记忆。
第三,设计遗忘机制。用户会修改偏好,旧记忆应当被更新或过期。比较简单的策略是:每条记忆带 created_at 和 updated_at,定期清理超过 180 天且未被命中的记录;用户主动要求删除时,必须提供删除接口,并且删除后不能再被检索到。
第四,控制注入记忆的长度。检索到的记忆不是越多越好,一般建议控制在 5 到 10 条,总长度不超过几百 token。记忆过多会挤占模型对当前问题的注意力。
第五,做好观测与日志。每次记忆写入、检索命中、注入 Prompt,都应该有日志。生产环境中,如果用户投诉“Agent 忘了”,你能回溯到某轮对话到底检索到了什么,节省大量排查时间。
第六,测试要覆盖“遗忘”场景。很多团队只测“记忆有没有写入”,却不测“用户改口之后怎么办”。例如用户先说“我喜欢简洁回答”,后来说“以后请给我详细报告”,记忆系统必须能更新旧偏好,否则 Agent 会一直执行过期指令。
11. 总结与后续学习方向
Agent 记忆不是一个需要等到大模型能力进化才能解决的问题。它本质上是一个数据库加检索加 Prompt 注入的系统工程。短期记忆靠 messages 滑动窗口管理,长期记忆靠外部存储和检索,工作记忆靠 Agent 运行时状态对象。三个层次配合起来,才是完整的记忆架构。
建议你先不要急着写向量检索或多 Agent 协作,而是把本文最简单的 SQLite 记忆模块跑通,确认“写入、检索、注入”三件事都正常,再去升级。如果你已经跑通了基础版本,下一步可以深入这几个方向:一是把LIKE替换成语义向量检索;二是实现基于用户维度的记忆更新与遗忘接口;三是用 Redis 或消息中间件打通多个 Agent 的共享记忆。
真正值得警惕的是“什么都要记”的设计。记忆的价值不在于存储了多少,而在于准确、及时地想起该想起的信息。先把一条完整的记忆链路跑通,再考虑扩展,你会少踩很多坑。