先问你一个问题:你上一次跟某个 AI 聊完天后,下次再打开它,是不是还得从头自我介绍一遍“我是谁、我在做什么项目、我上次要的是什么”?如果你点头了,那这篇就是为你写的。
这是《走进 AI Agent》系列的第三篇。前两篇我们聊了 Agent 的基本结构、工具调用和任务拆解,但一直有个绕不开的硬伤——Agent 没有记忆。没有记忆的 Agent 就像一个每次见面都把你当陌生人的店员,你说“还是老样子”,他只会一脸茫然。这篇我想把“让 Agent 记住你”这件事彻底讲透:从记忆的分类、架构设计、代码实现,到真实项目里踩过的坑,一次性给全。
1. 没有记忆的 Agent,本质上只是个“高级玩具”
1.1 从一次让人崩溃的对话说起
我去年做一个客服类的 Agent 原型时,遇到一个特别典型的场景。用户第一轮说:“我的订单号是 20241015A,我要查物流。”Agent 正常回答。可到了第三轮,用户问:“如果今天到不了,我申请退款流程怎么走?”Agent 居然反过来问:“请问您的订单号是多少?”
这不是模型能力不行,而是Agent 根本没有跨轮次的状态保持能力。每次大模型调用都是无状态的,它处理完一个请求就把上下文忘了。你以为你在跟一个连续对话的“人”交流,实际上你是在跟一个每一次都失忆的机器交流。
没有记忆的 Agent 会导致几个很现实的问题:
- 重复提问:用户要一遍遍提供背景信息,对话效率极低。
- 无法积累偏好:Agent 记不住“这位用户喜欢简洁回复”“这位用户是后端工程师,聊技术别用太多比喻”。
- 任务断裂:多步骤任务做到一半,只要上下文窗口溢出或被截断,前面的工作全白费。
- 信任感崩塌:用户一旦发现 AI 总是“翻脸不认人”,就不太可能把复杂任务交给它。
我在网上看到很多人在热搜词里搜“Agent 记忆”“AI Agent 运行逻辑”,其实大家潜意识里已经感觉到了:记忆才是 Agent 从“玩具”走向“工具”的分水岭。
1.2 记忆不是缓存,而是“用户模型的持续构建”
很多人以为,给 Agent 加记忆就是在代码里开个数组,把历史对话存进去,下次拼到 Prompt 里。这是最粗浅的理解。
真正的记忆系统,做的是三件事:
- 记录:保存与用户交互过的关键信息,但“关键”这个词需要设计规则。
- 组织:把零散的信息结构化,比如区分“用户个人信息”“项目背景”“当前任务上下文”。
- 利用:在合适的时机把相关记忆召回、注入到模型中,让模型“想起”该想的事。
我习惯把记忆比作一个有分类归档的私人助理的记事本。记事本不是把用户说过的每句话都抄下来,而是只记重点:这位用户的习惯、偏好、正在进行的项目、上次聊到哪里。然后在用户开口的时候,助理能迅速翻到对应页面,把该带的资料放到桌面上。
这也是为什么我在系统里从来不用“把全部聊天记录塞进上下文”这种方案。上下文窗口再大,也有上限,而人类的对话信息是持续增长的。记忆系统的核心能力,不是存储,而是筛选和召回。
1.3 为 Agent 的记忆能力分个级
和很多从业者交流下来,大家普遍接受的一个分级方式是这样的:
| 级别 | 记忆能力 | 表现 |
|---|---|---|
| L0 | 无记忆 | 每次对话从零开始,像个失忆症患者 |
| L1 | 会话内记忆 | 记住当前这一轮多轮对话的内容,但关掉就忘 |
| L2 | 跨会话用户画像 | 记住用户的基础信息、偏好,例如“用户是前端工程师” |
| L3 | 跨任务情景记忆 | 记住“你上周让我调研过向量数据库”,能主动关联 |
| L4 | 主动学习型记忆 | Agent 自己判断什么值得记,自动更新记忆结构 |
目前市面上大多数开箱即用的 Agent,停留在 L0 到 L1 之间。很多接了大模型 API 的“聊天机器人”其实连 L1 都做不好,因为开发者没维护消息历史列表。而L2 到 L3,才是“让 Agent 记住你”的核心目标。
这篇文章后面讲的内容,基本围绕如何把 Agent 从 L1 推到 L3,再展望 L4 的玩法。
2. 给 Agent 装上记忆骨架:四类记忆模型
2.1 工作记忆:当前任务的“桌面草稿纸”
工作记忆对应的是 Agent 正在处理的任务状态。比如用户问“帮我写一封给客户的邮件,语气要正式一点”,那么“收件人背景”“邮件主题”“语气要求”“已经起草到第几版”这些信息,都属于工作记忆。
它的特点是:生命周期短、更新频率高、随任务结束而清空。实现上可以非常灵活,最简单的方式就是用 Python 里的一个字典,配上对话消息列表:
working_memory = { "task_type": "write_email", "customer_name": "王总", "email_topic": "项目延期说明", "tone": "formal", "draft_version": 3, }在框架层面,LangChain 里的ConversationBufferMemory、ConversationSummaryMemory,本质上都是在做工作记忆的管理。前者保留全部原始对话,后者用模型把历史总结成摘要,各有利弊。
我在实际项目里的建议是:工作记忆不要只存消息原文,要额外维护一份“任务状态字段”。因为消息原文是线性的,但任务状态是结构化的。模型每次读取工作记忆时,先看任务状态字段,能快速定位当前进度,而不是从头读一遍全部对话。
2.2 情景记忆:记住“昨天聊过的那件事”
情景记忆是跨会话的。举个例子:用户上周跟你说“我在做一个智能简历筛选工具”,这周又来问你“帮我看看这个招聘JD怎么解读”。如果 Agent 有情景记忆,它应该联想到:这位用户在做简历筛选工具,是不是想招人?或者想了解 JD 背后的筛选逻辑?
情景记忆的核心价值是连续性。用户不需要每次都重新交代背景,Agent 能把散落在多次对话里的信息串联成一条线索。
实现情景记忆,主流方案是向量数据库。把每次对话中的关键事件转化为向量,存进向量库里,每次新对话到来时做相似度检索,找到相关度高的历史事件,注入到 Prompt 中。这个我在第三章会给出完整代码。
有一个容易忽略的点:情景记忆的“粒度”很关键。如果把整段对话作为一个向量存进去,召回时往往不够精确。更好的做法是按“事件”切分,比如一次对话里用户提了三件不相干的事,那就拆成三条记忆,而不是一条混合记忆。这是很多人第一次做情景记忆时踩的坑。
2.3 语义记忆:关于你的结构化知识
语义记忆是“关于世界的知识,尤其关于用户的知识”。它不依赖某个具体事件,而是从多次交互中提炼出的稳定结论。例如:
{ "user_id": "u_123456", "name": "陈晨", "role": "后端工程师", "tech_stack": ["Go", "Python", "PostgreSQL"], "communication_style": "prefers_direct_concrete_answers", "projects": { "resume_filter": { "status": "in_progress", "goal": "基于大模型做简历初筛" } } }这类记忆适合用结构化数据库存储,比如 PostgreSQL 或 SQLite,每条记录有明确的字段。和向量检索式的情景记忆互补:语义记忆负责“静态画像”,情景记忆负责“动态经历”。
我在公司内部给团队定的规范是:凡是能从对话中稳定提取出的、对后续交互有长期影响的属性,一律进结构化记忆;凡是“某天某人说了某件事”这种事件型信息,一律进向量记忆。两条线并行,互不干扰,但可以联合召回。
2.4 程序记忆:Agent 的“肌肉记忆”
程序记忆对应的是技能。比如 Agent 学会了“当用户问天气时,调用天气 API 并加工结果再回复”,这个流程一旦固化,就不需要每次重新推理一遍。
在 AI Agent 领域,程序记忆常以Skill 或 Tool 定义的形式存在。热搜词里有“AI Agent Skill”,其实指的就是这个——把 Agent 执行某些任务的策略、步骤、模板固化下来,变成可复用的能力。
程序记忆的技术实现有两类:
- 显式定义:写死函数或 Prompt 模板,比如“查天气”“发邮件”“计算器”。
- 隐式学习:从历史成功案例中总结套路,比如用户多次让 Agent 按某个风格写周报,Agent 学会了自动套用这个风格。
目前业界用得最多的是显式定义,也就是给 Agent 配置工具清单和工具描述。隐式学习还比较早期,但已经有团队在探索用“记忆”动态生成 Skill。
给本小节画个重点:四类记忆各有各的存储介质和生命周期,别混为一谈。新人最容易犯的错误是,把什么都往向量库里塞,结果语义记忆被事件记忆冲淡,召回质量越来越差。
| 记忆类型 | 生命周期 | 存储介质 | 召回方式 |
|---|---|---|---|
| 工作记忆 | 单次任务 | 内存/字典 | 直接读取 |
| 情景记忆 | 跨会话 | 向量数据库 | 相似度检索 |
| 语义记忆 | 长期稳定 | 结构化数据库 | 精确查询 |
| 程序记忆 | 长期 | 代码/配置 | 技能触发 |
3. 从零搭建一套带记忆的 Agent:完整实操
3.1 技术选型:为什么是“向量库 + Embedding + 结构化库”
先回答一个很多人纠结的问题:为什么不能让大模型“记住”用户?直接把所有历史对话拼进上下文让模型读,不就行了吗?
理论上可以,但现实不行。第一,上下文窗口再大也是有限的,把半年对话全部塞进去,Token 成本直接爆炸。第二,信息之间相互干扰,模型会被无关历史带偏。第三,每次把全部历史重新过一遍,响应延迟会明显增加。
所以正确的做法是:对话历史只做短期保留,关键信息抽取出来进长期记忆;长期记忆分两条线,一条向量库管情景事件,一条结构化库管用户画像。
我这套方案用到的组件按照实际项目经验,是这样选的:
| 组件用途 | 推荐选项 | 选型理由 |
|---|---|---|
| Embedding 模型 | text-embedding-3-small或bge-large-zh | 中文效果好,维度适中,成本低 |
| 向量数据库 | Chroma(开发)/ Qdrant 或 Milvus(生产) | Chroma 零配置起步快,Qdrant 支持过滤和持久化更好 |
| 结构化数据库 | SQLite(开发)/ PostgreSQL(生产) | 成熟、可靠、团队都会 |
| Agent 编排框架 | 自研为主,参考 LangChain 的 Memory 设计 | 框架层依赖少一点,排障更容易 |
如果你是在 Java/Spring Boot 技术栈里做 Agent,这套设计同样成立:Embedding 调用可以封装成 HTTP 接口,向量库可以选 pgvector(PostgreSQL 插件),记忆逻辑放在 Service 层即可。热搜词里有“SpringBoot AI Agent 客户端”,本质上也逃不开这套存储和召回逻辑。
3.2 记忆的写入:哪些对话内容值得存
记忆系统设计不好,往往不是因为“存得少”,而是因为“存得太多”。我在早期版本里试过把用户的每一句话都做 Embedding 存进向量库,结果召回时全是噪音。
后来我总结了一套实用规则:不是所有对话都值得进入长期记忆,需要经过一道筛选。
筛选策略可以简单分成三层:
- 预处理过滤:把问候语、寒暄、纯语气词(“嗯嗯”“好的”“谢谢”)直接过滤掉,不进记忆。
- 关键信息提取:用大模型对当前对话做结构化总结,比如提取出“用户提到正在做 XX 项目”“用户偏好 XX 风格”。
- 显著性判断:判断信息是否具备长期价值。比如“用户今天吃了午饭”就没什么长期价值,“用户下月要上线一个电商项目”就值得记住。
典型的一段记忆写入代码大概是这样的:
from openai import OpenAI import chromadb client = OpenAI() chroma_client = chromadb.PersistentClient(path="./agent_memory") collection = chroma_client.get_or_create_collection(name="episodic_memory") def extract_memory_from_dialogue(dialogue: str) -> str: """让大模型从对话里提炼值得记住的信息""" prompt = f""" 你是信息提取器。从下面的对话中提取值得长期记住的事实。 只输出要点列表,不要输出无关内容。 如果是寒暄或日常无信息量内容,输出 NONE。 对话: {dialogue} """ response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0, ) result = response.choices[0].message.content.strip() return None if result == "NONE" else result def save_memory(user_id: str, memory_text: str, metadata: dict): """将记忆向量化并存入向量数据库""" embedding = client.embeddings.create( model="text-embedding-3-small", input=memory_text ).data[0].embedding collection.add( ids=[f"{user_id}_{uuid.uuid4().hex}"], embeddings=[embedding], documents=[memory_text], metadatas=[{**metadata, "user_id": user_id}] )注意一个点:写入时要给 metadata 打好标签。例如type: "user_preference"、type: "project"、type: "event",方便后续召回时做过滤。你不想在用户问“我的服务器架构什么样”时,召回一堆与偏好相关的记忆吧。
3.3 记忆的召回:相关度检索与 Prompt 注入
存进去不是目的,能正确召回才是。召回的基本流程是:
- 把用户当前问题做 Embedding。
- 在向量库中做相似度搜索,取出 Top-K 相关记忆。
- 加上 metadata 过滤(比如只要当前 user_id 的记忆)。
- 重排:把最相关的记忆放在 Prompt 末尾(模型对末尾信息注意力更强)。
- 组装成“记忆块”注入系统提示词。
召回代码可以这样写:
def recall_memory(user_id: str, query: str, top_k=5): query_embedding = client.embeddings.create( model="text-embedding-3-small", input=query ).data[0].embedding results = collection.query( query_embeddings=[query_embedding], n_results=top_k, where={"user_id": user_id} ) memories = [] for doc, meta in zip(results["documents"][0], results["metadatas"][0]): timestamp = meta.get("timestamp", "unknown") memories.append(f"[{timestamp}] {doc}") return "\n".join(memories) MEMORY_PROMPT_TEMPLATE = """ 以下是关于用户的一些历史记忆,请在做回答时参考这些信息: {memory_content} 如果历史记忆与当前问题无关,请忽略它们。 当前用户问题:{user_query} """ def build_prompt(user_query: str, user_id: str) -> str: memory_content = recall_memory(user_id, user_query) if memory_content: return MEMORY_PROMPT_TEMPLATE.format( memory_content=memory_content, user_query=user_query ) return user_query这里有一个很容易忽略的细节:召回时一定要先过滤 user_id,再排序,不要先全局排序再过滤。向量数据库支持先过滤再检索的模式(比如 Qdrant 的 pre-filter),这样既快又不会出现“别人的记忆串到你这里”的尴尬。我见过有团队用全局检索再在应用层过滤,数据量一上来就出各种问题。
召回阶段还有一个进阶技巧:多路召回。向量库召回一路,结构化数据库精确查询一路(比如查用户画像),两边结果合并去重后再注入。这样既能召回“似曾相识”的事件,又能拿到“确定无疑”的用户属性,效果远好于只用一路。
3.4 记忆的更新:新增、覆盖与冲突消解
写入和召回解决了“存”和“取”,但记忆不是一成不变的,会过时、会被新信息覆盖。记忆更新的三种典型操作:
- 新增:对话中出现了全新的关键信息,向量库和结构化库都新增条目。
- 覆盖:用户明确纠正了之前的说法。比如用户以前说自己用的 PostgreSQL,今天说“我们切到 MySQL 了”,旧记录应该失效。
- 降权:旧记忆不代表错误,但相关度降低。比如用户三年前做电商项目,现在做 AI Agent 项目,旧项目信息权重应下降。
覆盖在结构化库里比较好实现,直接 UPDATE 字段。但在向量库里比较麻烦,因为向量库里存储的是“历史快照”,你无法保证新信息向量和旧信息向量距离够近。
我目前落地的方案是“软失效”:给每条记忆加一个status字段,用户明确纠正后,把旧记忆的status置为inactive,召回时过滤掉。这个方法比物理删除保险,方便回溯问题,也让用户看到“阿 AI 确实记得我之前说过什么”的连续性。
metadata = { "user_id": user_id, "status": "inactive", # active / inactive "superseded_by": new_memory_id }关于冲突消解,还有一条经验:当记忆之间出现矛盾时,以用户最近的表述为准,但要保留历史痕迹。我见过最粗暴的方案是直接删旧存新,结果用户来一句“不对,我说的不是 MySQL,是 MariaDB”,旧信息已经没了,Agent 彻底懵了。保留软失效,能让你在模糊场景下有回旋余地。
4. 记忆系统落地时最常见的四个坑
4.1 向量维度不一致导致召回静默失败
这是我遇到的第一个“看上去没毛病,实际就是不出结果”的问题。开发环境用的 Embedding 模型是 A,后来上线换了 B,两个模型的向量维度不一样。向量库允许写入不同维度的向量,但检索时无法正确比较,导致召回结果非常离奇——甚至空结果。
排查链路一定要按这个顺序来:
- 打印向量库中存储的向量维度(
collection.get()里看 embedding 长度)。 - 打印当前查询向量维度。
- 如果两者不一致,不用继续查了,问题就在这。
解决办法有两个,选一个就行:
- 固定 Embedding 模型版本,线上不要随便换模型,换之前要先建新的 collection 或做全量向量迁移。
- 在写入和查询时统一做维度校验,两边的维度不一致直接抛异常提醒,而不是静默失败。
我后来在系统里加了一行校验逻辑,凡是维度不匹配就不允许写入,虽然多了一步,但避免了很多线上事故。
4.2 冷启动期:记忆系统最难受的第一个月
带记忆的 Agent 刚上线时,向量库里几乎没有关于用户的记忆,召回必然为空。这个阶段用户的实际感受是:“你告诉我你是有记忆的,但感觉你和普通聊天机器人没区别。”
冷启动问题的本质是记忆需要时间积累。作为开发者,你不能等它自然积累,得想办法“预填”:
- 接入历史数据:如果之前有用例有历史对话记录,直接批量做好清洗、抽取,灌进记忆库。哪怕数据质量参差不齐,也比空库强。
- 引导用户补充画像:在首次对话时温和地询问一些背景信息,比如“您平时主要使用哪些编程语言?”这样的问题,让语义记忆库在第一次交互时就有基本内容。
- 设置合理的记忆边界:明确告诉用户“我是从今天开始记住你的”,别让用户误以为 Agent 知道之前所有事情。这个在用户体验上反而更诚实、更可信。
我在一个项目里做了“新用户引导 + 旧数据导入”双管齐下,冷启动期从两周缩短到两天。不要觉得数据脏就懒得迁,历史数据哪怕只有 70% 的质量,也远比空库好。
4.3 数据串台:多个用户共用一套存储的灾难
如果向量库的 metadata 里有user_id字段,但你在写入和召回时忘了用它做过滤,就会出现灵异事件:A 用户问“我的项目进展如何”,Agent 回答出 B 用户的项目内容。
这种问题在大规模多用户场景下非常致命。一旦发生一次用户数据串台,信任基本归零。
排查时先看是否有过滤逻辑,再看过滤条件是否生效:
# 错误写法:没有按 user_id 过滤 results = collection.query(query_embeddings=[query_embedding], n_results=5) # 正确写法:强制按 user_id 过滤 results = collection.query( query_embeddings=[query_embedding], n_results=5, where={"user_id": user_id} )我的建议是,不只在查询层面用代码过滤,还要在存储设计上物理隔离。生产环境给每个用户建独立的 Collection(Qdrant 支持多 Collection),或者用 PostgreSQL 的行级安全策略。存储隔离比查询过滤更安全,相当于你不知道对方的保险柜密码,而不是知道密码但靠自觉不开。
4.4 记忆注入过多导致模型注意力被稀释
又一个反直觉的坑:记忆召回得越多,效果不一定越好,反而可能更差。当 5 条记忆里只有 1 条真正相关,另外 4 条只是“有点像”的时候,大模型会被无关记忆带偏,回答反而比“没有记忆”时更差。
我调试过很多次发现,问题出在“相似度阈值”设定得太低。向量召回的结果是“相似”,但“相似”不等于“当前问题需要用到”。解决办法有几个:
- 设置召回阈值:相似度分数低于某个值(比如 0.55,具体看 Embedding 模型的分布)的记忆直接丢弃。
- 加一轮重排:用更强的模型(或者简单规则)对召回结果做二次打分,只保留 Top-2 或 Top-3。
- 控制注入量:宁可少给,不要多给。我给团队的默认参数是:向量记忆 Top-3,结构化画像最多 5 个字段,总共记忆段不超过 300 Token。
# 召回时动态调整阈值 results = collection.query( query_embeddings=[query_embedding], n_results=10, # 先多召回一些 where={"user_id": user_id} ) filtered = [ (doc, meta) for doc, meta in zip(results["documents"][0], results["metadatas"][0]) if meta.get("score", 0) >= 0.55 ][:3] # 再过滤并截取这个“多召回、再过滤、严控数量”的三步法,基本解决了我遇到的 90% 记忆效果不佳问题。
5. 从“记住你”到“懂你”:记忆系统的进阶演化
5.1 时间衰减:别让陈年旧事霸占记忆高地
静态的记忆系统有个问题:3 年前的记忆和昨天的记忆有同等的召回权重。但对大多数场景来说,用户近期关注的内容远比历史内容重要。
时间衰减的实现思路是:存储时记录时间戳,召回时在相似度分数上叠加一个时间折扣系数。
import math from datetime import datetime def time_decay_score(base_score: float, timestamp: str, half_life_days: float = 30.0) -> float: days_elapsed = (datetime.now() - datetime.fromisoformat(timestamp)).days decay = 0.5 ** (days_elapsed / half_life_days) return base_score * decay半衰期 30 天意味着:30 天前的记忆权重只剩一半,60 天后只剩四分之一。这个参数需要根据业务调整:做客服的,可能只需要用户近一周的信息;做长期知识管理的 Agent,半衰期可以放宽到 90 天以上。
加了时间衰减后,一个明显的变化是:旧的、低频的记忆不会彻底消失,但也不会频繁干扰当前对话。这就是“记住但不打扰”的体验。
5.2 记忆摘要与分层归档
对话积累到一定程度,向量库里的记忆越来越多。但我发现,很多“记忆”其实是同一件事的不同变体。比如用户在前三周分别说了三个关于项目的细节,单独都存了,召回时可能返回三条碎片,但合并成一条“项目全景摘要”才真正有用。
这个需求催生了“记忆摘要层”:
- 原始记忆层:保存具体的事件细节。
- 摘要记忆层:基于一段时间内的原始记忆,由大模型生成结构化摘要。摘要不追求细节,追求全局。
- 核心画像层:从摘要中再提炼稳定属性,进结构化库。
周期性任务流程大致是:
- 每天凌晨跑一次定时任务,把当天新增的记忆按用户、按主题聚类。
- 聚类后调用大模型生成“今日摘要”。
- 将摘要合并进该主题已有的长期摘要中。
这套分层的效果是,Agent 在回答“整体上我是怎么做这个项目的”这类概括性问题时,能直接调用摘要记忆;而在回答“我上周三说的那个具体报错信息是什么”时,走原始记忆检索。两条路径各司其职。
5.3 从被动存到主动写:Agent 自己决定记住什么
前面的记忆写入流程里,筛选规则是开发者写死的。这已经是“半主动”机制——模型参与了信息提取,但触发与否由规则决定。
L4 级别的主动记忆是:Agent 在对话过程中自己判断“这段信息以后会有用”,并主动执行存储动作。这类似于人的记忆行为——你不会刻意背下朋友的生日,但你会记住他提到过最讨厌的食物。
实现上可以采用“记忆意图识别”的方式:在 Agent 的主流程里加一个旁路判断,每一轮对话结束后,用一个轻量模型判断“本轮是否出现值得长期记住的信息”,如果有,触发写入。没有的话,不浪费 Token。
我测试过的最小实现,就是加一个分类调用:
def should_save_memory(dialogue: str) -> bool: response = client.chat.completions.create( model="gpt-4o-mini", messages=[{ "role": "user", "content": f"这段对话中是否包含值得长期记住的关于用户的事实?只回答 yes 或 no。\n{dialogue}" }], temperature=0, ) return response.choices[0].message.content.strip().lower() == "yes"从效果上看,主动记忆比规则筛选更灵敏。代价是会多几百毫秒延迟和少量 Token 消耗,在非实时性要求极高的场景下完全可接受。
5.4 记忆的隐私边界与删除机制
“让 Agent 记住你”这件事,天然伴随隐私风险。我在这部分比较谨慎,因为做得不好会把整个产品带入信任危机。
设计记忆系统时,至少要回答这几个问题:
- 用户有权知道 Agent 记住了什么吗?我建议提供“记忆管理页面”,让用户能查看记忆库里的内容。
- 用户可以说“忘了这些”吗?必须提供一键清空记忆能力,而且是物理删除,不是软失效。
- 记忆的保存期限是多久?产品层面要定义清楚,比如“会话记录保留 30 天”“用户画像可长期保留”。
- 哪些敏感信息不应该进记忆?身份证号、银行卡号、密码、健康记录等,应在提取环节就拦截。不要心存侥幸,等到出了事再补救。
我在项目里专门做了一个敏感信息过滤器。大模型提取记忆后,先过一次正则+敏感词过滤,命中风险词条的直接丢弃,不进向量库。这一步不能省。
这一节其实想说的是:记忆能力强,是把双刃剑。 你用记忆给用户带来了连续体验,也要为用户承担记忆外泄的风险。安全边界设计得越早,后续返工成本越低。
写在实操之后:记忆系统的迭代方向
最后分享一点个人体会。我做记忆系统的第一版,只想着“怎么把聊天记录存下来”,结果效果一般,用户感知不到。后来我把思路转过来:记忆系统不是在“存储对话”,而是在“构建用户模型”。存储只是手段,让 Agent 在合适的时机说出“我知道你之前在搞 XX 项目,那我们接着聊”才是目的。
如果你准备从零开始做,我建议按这样的顺序推进:
- 先把工作记忆做好——确保多轮对话不丢上下文。
- 再上语义记忆——存用户画像,让 Agent 知道“你是谁”。
- 然后接情景记忆——用向量库存历史事件,让 Agent 知道“我们之间发生过什么”。
- 最后再考虑摘要、主动记忆、时间衰减这些进阶能力。
还有一个很小的技巧,我每次搭建记忆系统都会用上:每周导出一份记忆库的快照备份。记忆是用户跟你积累的资产,代码可以重新写,但记忆丢了意味着用户对你的信任也丢了。定期备份,低成本高保障。
到这一步,你的 Agent 已经能“记住你”了。下一篇,我会聊聊当 Agent 记住了很多用户之后,怎么从单用户的记忆扩展到多用户的个性化推荐和记忆共享。先写到这,有落地过程中的问题,欢迎在评论区交流。