1. 为什么Agent需要“记住你”这件事
赶在写这篇之前,我刚把手头一个客服对话Agent的上下文方案重做了一遍。之前那个版本有个很典型的问题:用户昨天刚问过“你们家有没有支持POE供电的8口交换机”,今天换个方式问“那个能走网线的交换机会员价多少”,Agent就完全懵了,不仅答非所问,还把用户当新访客又重新推销了一遍基础款。问题不复杂,就是Agent没有记忆。
在之前的文章里我们聊过Agent的基本运行逻辑和工具调用,但说实话,如果Agent上线后连用户是谁、聊过什么、做过什么决策都记不住,那它的体验跟一个每次都重新自我介绍的客服没什么区别。记忆功能是AI Agent从“能用”走向“好用”的关键分水岭,它决定了多轮对话的连贯性、个性化推荐的准确度,以及复杂任务(比如跨多天的方案跟进)能否跑得通。
这篇博文适合谁看?如果你已经在用LangChain、Cohere、OpenAI、LlamaIndex这类框架搭过Agent,但一直觉得对话“智商在线但不贴心”;如果你正在做一个需要沉淀用户画像、历史决策、偏好信息的AI产品;又或者你只是对“怎么让AI记住我”这件事感到好奇,这篇文章都能给你一条清晰的落地方案。
我先摆一段实操里最典型的场景,方便你对齐这篇文章要解决的问题:
用户第一轮:“帮我推荐一款适合200平米办公室的无线AP。”
Agent:推荐了型号、数量、部署建议。
用户隔天再来:“上次那套方案你帮我算一下带46个终端会卡吗?”
如果Agent没有记忆,第二个问题它根本不知道“上次那套方案”指什么。有记忆的Agent会从存储里召回上次对话的实体信息——产品型号、办公室面积、终端数量,然后基于这些内容直接计算。这才是“记住你”的真实含义。
2. 记忆机制设计的核心思路与选型考量
2.1 先搞清楚“记忆”在Agent里到底是什么
在涉足具体实现之前,得先重新审视“记忆”这个词。做Agent的记忆跟人类记忆有一点点相似,但又不完全相同。从工程角度拆解,Agent的记忆至少可以分成四层:
第一层:会话级工作记忆。就是当前这一轮对话窗口里的所有内容,包括用户输入、模型输出、工具调用的中间结果。它通常存在上下文字段里,随请求一起发给大模型。它的特点是读取快、寿命短,只要会话结束或窗口超长,就会被截断或丢弃。大多数人刚开始做Agent的时候,用的就是这一层记忆——严格来说,这只能叫“上下文”,谈不上“记忆”。
第二层:会话间短期记忆。用户在短时间内(比如几小时内)多次访问,Agent能记住他上次问过什么、做到哪一步。通常通过把历史消息摘要或关键实体写入数据库,在下一轮对话开始时主动加载。这层记忆解决的是“用户换了会话,但Agent不能失忆”的问题。
第三层:长期用户画像记忆。这层对应的是“记住你”的升级版——记住用户的身份、偏好、历史决策、禁忌,甚至用户的情绪倾向和沟通习惯。比如“这个用户是运维工程师,负责300人公司的网络,预算敏感,喜欢华为设备”,这些信息不依赖某一次对话,而是跨时间的稳定沉淀。
第四层:全局世界知识记忆。Agent对整个业务领域、产品参数、行业知识的结构化存储。它不完全针对某个用户,但决定了Agent的回答是否专业。
做记忆系统时,最容易犯的错误是试图用一套存储解决所有层级的记忆。我早期就是这样——把所有历史对话都塞进向量数据库,每次请求都做相似度检索,然后拼到上下文里发给模型。结果对话一长,相关和不相关的内容全被召回,模型被大量低相关的“记忆”干扰,回答质量反而下降。
2.2 记忆方案选型:哪种存储适合哪类需求
用最简单的话来区分:短期记忆可以用KV缓存或关系库临时表,长期记忆的核心是向量数据库,用户画像建议用图数据库或文档型数据库。但这不是绝对的,我按实际场景把常见选型列了个表:
| 记忆类型 | 存储选型 | 核心理由 |
|---|---|---|
| 会话工作记忆 | 内存 / Redis | 读写延迟低,TTL自动过期,适合高频访问 |
| 会话间短期记忆 | Redis + 摘要存储 | 快速加载最近N轮要点,不用全量灌上下文 |
| 长期事实/偏好 | 向量数据库(如Milvus、Qdrant、pgvector) | 支持语义检索,自然语言直接召回 |
| 实体关系/用户画像 | 图数据库(如Neo4j)或PostgreSQL JSONB | 适合描述“用户—偏好—历史决策”的多跳关系 |
| 业务知识/产品参数 | 文档库 + 向量索引 | 结构化知识与非结构化描述结合 |
如果你是个人开发者或者小团队,刚开始做记忆功能,我建议别一上来就上Milvus集群。先用PostgreSQL+pgvector,一张表存会话摘要,一张表存用户画像,再给关键实体建向量索引,完全够支撑几十万用户的记忆场景。等并发量真的大上来了,再迁移到独立向量数据库也来得及。
很多人问为什么不用Redis一把梭?Redis确实快,但它不是为语义检索设计的。当你需要“找出和‘上次聊过的预算方案相关的历史记录’”时,用Redis的模糊匹配几乎是不可行的,而向量检索只需要一句话的自然语言表达就能搞定。
2.3 为什么“记忆”不只是存储,还有遗忘和抽取
做记忆系统时还有一个常被忽略的关键点:存储只是记忆的一半,另一半是写入策略和遗忘策略。如果什么东西都往长期记忆里塞,Agent会变得越来越“啰嗦”——因为检索结果里充斥着无关紧要的细节,模型被迫在噪音中找信号。
我自己的经验是,记忆写入层一定要做“提取-结构化-压缩”三步处理,而不是把原始对话直接存进向量库。具体来说:
- 提取:从每轮对话中抽出关键实体(用户名字、公司、产品型号、预算、时间点)
- 结构化:把这些实体整理成固定的Schema,比如
[用户ID, 实体类型, 实体值, 关联对话ID, 时间戳] - 压缩:对完整对话做摘要,只保存摘要级别的文本进长期库
这样后续检索时,召回的是“这个人对预算敏感、上次选型倾向华为”,而不是一整段三十分钟的聊天记录。模型拿到的是提炼过的记忆,而不是原始日志。
3. 记忆系统核心实操:从接入到落库全流程
3.1 第一步:给Agent加一个记忆模块的数据结构
既然要动代码,先把底层的存储结构设计好。我这里用一个兼容LangChain和LlamaIndex的实践方案做讲解,但是我们可以直接用OpenAI的Assistants API模拟一个轻量级的记忆接口。对于没有用框架的读者,也可以照着这个思路自己写。
第一步,先定义记忆的数据模型。建议用类似下面的数据结构:
from pydantic import BaseModel from typing import Optional, List from datetime import datetime class MemoryItem(BaseModel): user_id: str memory_type: str # "short_term" | "long_term" | "profile" content: str entities: List[str] = [] importance: float # 0.0 ~ 1.0 created_at: datetime last_accessed_at: Optional[datetime] = None access_count: int = 0这里面的importance字段是我从很多次踩坑后加上的。它体现了记忆的权重——重要的信息(比如“用户是负责采购的决策人”)权重高,不重要的信息(比如“用户夸过一句界面好看”)权重低。检索时可以用它做排序过滤,重要性高的记忆优先被送入上下文。初始写入时,可以让大模型自己给信息的重要性打分,也可以基于规则(比如是否包含金额、是否包含决策词)。
3.2 第二步:对话记忆的写入与更新
当用户和Agent进行多轮对话时,我们不可能在每轮对话后都把全部内容写入长期记忆(那样会产生海量冗余数据)。更合理的策略是设置一个写入触发点:
- 当前轮次对话包含决策性内容(下单、选型、同意某个方案)
- 用户明确提到重要信息(“我是做运维的”“预算不能超过五万”)
- 每个会话结束时,做一次整体摘要
下面是一个简化的写入逻辑,配合LangChain使用:
from langchain.memory import ConversationSummaryBufferMemory from langchain_openai import ChatOpenAI # 设置一个带摘要的缓冲记忆 llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) memory = ConversationSummaryBufferMemory( llm=llm, max_token_limit=2000, # 超过这个 token 数,自动触发摘要 return_messages=True, memory_key="chat_history" ) # 模拟一次带工具调用的对话 from langchain.schema import HumanMessage, AIMessage memory.chat_memory.add_message(HumanMessage(content="我想给办公室做一个无线覆盖,大概200平米,预算1万以内")) memory.chat_memory.add_message(AIMessage(content="好的,200平米建议使用2个吸顶式AP,预算内可以覆盖,需要我提供具体型号吗?")) memory.chat_memory.add_message(HumanMessage(content="可以,我倾向于华为的设备")) # 让LLM从上面的对话中抽取长期记忆 extract_prompt = "从以下对话中抽取需要长期记住的信息,要求输出JSON格式,包含实体、属性、重要性评分" # 实际工程中会封装一个函数做抽取这段代码的关键点在于max_token_limit=2000。当对话累积的token超过这个阈值时,LangChain会自动用LLM生成一个压缩摘要,替换掉最老的原始消息。这样短期记忆不会无限膨胀,但又不会丢失核心信息。至于长期记忆的写入,通常是在这个摘要生成后,再让模型从摘要里抽取实体和偏好,写进向量库或画像库。
3.3 第三步:记忆的检索与上下文集装
有了存储,下一步就是如何把记忆送进模型的上下文。这一步有几条核心原则:
- 不是所有记忆都要加载,只加载与当前问题相关的部分
- 按时间衰减过滤:太久远且访问次数极少的信息可以忽略
- 按重要性截断:宁可少给,不要给不相关的干扰信息
我用pgvector做一个简单的相似度检索示例:
-- 假设走过的表结构已经带上了 embedding 列 SELECT content, importance, created_at, embedding <=> $query_embedding AS distance FROM memory_entries WHERE user_id = $user_id AND memory_type = 'long_term' ORDER BY CASE WHEN importance > 0.8 THEN 0 ELSE 1 END, embedding <=> $query_embedding LIMIT 5;这个SQL有意思的地方在于排序逻辑:先按重要性分组,再按语义距离排序。这样即使某个不重要的历史信息和当前问题语义上很接近,也不会挤掉重要性更高的记忆。实测下来,这个策略能明显提升生成结果的相关性,尤其是面对长尾问题时。
召回后,再把这些记忆拼装成系统提示词里的一个段落,比如:
以下是关于用户的历史记忆(按重要性排序): 1. 用户是XX公司的网络运维工程师,负责约200人办公室的网络设备选型(重要度0.95) 2. 用户对华为设备有明显偏好(重要度0.88) 3. 用户预算敏感,上次提到预算上限为1万元(重要度0.80)模型看到这些信息后,再处理“上次那套方案帮我算一下带46个终端会不会卡”这样的问题时,表现会完全不同——它知道“上次那套方案”是华为的AP,知道要结合预算评估,而不是从零开始提问。
3.4 第四步:用户画像的构建与更新
前面那几层记忆是所有Agent产品的基础,但“记住你”要做到极致,还需要一个更细致的用户画像层。我的实现方式是这样的:预设一套用户画像Schema,包含静态属性(姓名、角色、公司)、动态偏好(设备倾向、预算区间、沟通风格)、历史决策记录(买过什么、拒绝过什么、为什么拒绝)。
每一轮对话结束后,我会让模型做一个增量更新,而不是全量重建画像。看一个具体的实现示例:
class UserProfile(BaseModel): user_id: str static_attributes: dict = {} # 静态属性 preference_tags: dict = {} # 偏好标签及其权重 decision_history: list = [] # 历史决策记录 negative_preferences: list = [] # 明确拒绝过的东西 def update_user_profile(user_id: str, conversation_text: str): prompt = f""" 你是用户画像分析师。根据以下对话,更新用户的画像信息。 只提取对话中有明确依据的信息,不要推测。不要编造。 输出JSON格式,字段如下: - static_attributes: {"company": "...", "role": "...", "name": "..."} - preference_tags: {"华为": 0.9, "预算敏感": 0.8} - decision_history: [{...}] - negative_preferences: [...] 对话内容: {conversation_text} """ # 调用LLM解析... # 然后将返回的JSON与数据库中的已有画像进行合并这里有一个特别容易被忽视的细节:增量更新时不要直接覆盖旧值。比如用户第一次说“预算1万以内”,第二次说“预算可以放宽到两万”,这时画像里的预算信息应该更新为最新值,但要保留历史轨迹,这样才能理解用户的决策变化过程。我用的是“版本化画像”的思路——每个字段保存最近N次取值,并附带时间戳,这样Agent既能看出用户的稳定偏好,也能感知偏好变化。
4. 实操经验:调试记忆Agent最容易踩的五个坑
4.1 记忆污染:召回了一堆不该召回的内容
这是我在项目里踩过最深的坑。向量检索是按语义相似度找内容的,它不管“这条信息是不是适合这轮对话”。比如用户问“你们的AP支持802.11ax吗”,结果向量检索召回了历史里“用户上次问过802.11ac和802.11ax的区别”这一记录,看起来相关,但那条记忆里的产品信息和当前问题指向不同型号,模型被干扰之后反而给了错误答案。
我的解法是:检索前先做一次意图粗分类。如果当前用户问题是“产品参数”,就只检索长期知识库和当前型号相关的记忆,不检索用户画像;如果问题是“推荐方案”,才结合用户偏好和历史决策。这个意图闸门让召回质量提升了非常多。
4.2 上下文膨胀:记忆越多,模型越“失焦”
大模型的注意力窗口虽然越来越长,但太长的上下文会导致“lost in the middle”问题——模型对上下文中间部分的信息记忆特别差。如果把一堆记忆塞在中间,模型实际上“看到了”但没有“用到”。
解决这个问题,我是用了一个“记忆分级加载”的策略:系统提示词附近的记忆权重最高,放最重要、最相关的长期画像;对话历史放在中段,压缩成摘要;当前用户的输入永远放在最末尾。实测下来,同样的记忆内容,调整位置之后,回答准确率能提升约15%。
4.3 实时性和异步写入的矛盾
做记忆系统时会发现,如果等LLM生成完回答再写记忆,整个请求链路会长很多。而且LLM抽取需要时间,用户等不起。后来我改成了异步写入:先返回Agent的回答给用户,同时在后台任务里异步抽取记忆并落库。这样在用户感知上响应更快,而且如果抽取失败,也不会影响主流程。但要注意一个问题:异步写入要加锁和去重,避免同一个信息被重复写入多次。我在Redis里做了一个简单指纹去重,用对话ID加实体哈希作为key,写入前先检查。
4.4 记忆过期与遗忘策略没有定义
如果只做写入不做过期,长期记忆库会越来越脏。比如用户半年前说过“预算2万以内”,半年后可能已经完全不符合实际。如果Agent还拿这个旧画像决策,那反而是负资产。我建议像操作系统管理缓存一样管理记忆:低重要性、长时间未访问的记忆逐步降级;确认过时或有冲突的信息优先删除;定期做一轮“记忆回顾”,让LLM对一堆旧记忆做清洗和合并。
4.5 多用户隔离没做好
这是涉及隐私和安全的高危问题。如果你的Agent是给多个用户服务的,记忆检索时一定要在所有查询中都加上user_id的条件过滤。我见过有团队初期没加这个过滤,结果用户A的问询历史被用户B的对话检索到了,这在生产环境是严重事故。任何记忆检索的SQL或向量查询,都应该把用户隔离作为默认的第一条件。
5. 面向2026年:AI Agent记忆功能还能怎么做深
5.1 记忆从“被动存储”走向“主动理解”
现在的记忆系统基本都是被动的——用户说什么,我们存什么,再在合适的时机取回。但更进一步的记忆应该具备“主动推断”的能力。比如用户没有明确说“我是管理采购的”,但从他多次询问价格、要对比表格、顾虑审批周期这些信息,Agent应该主动推断出他可能是采购决策人或影响者,并把这条推断加入画像。当然,这里要有边界:推断结果不能直接当事实用,而是作为一个“待验证标签”,随着更多证据积累再提高置信度。
5.2 多模态记忆会成为新的差异化点
现在的记忆大多存的是文本。但用户跟Agent交互时的表情、语气、停留时长,甚至浏览记录,都可以成为记忆的一部分。比如用户在看到某一款产品介绍时停留了特别久,这可能是一个强偏好信号;用户连续两次追问某功能,说明这是他在意的点。这类隐式信号虽然没有被用户说出口,但对个性化推荐非常有用。
5.3 记忆的“可解释化”与用户可控
未来的Agent记忆应该做到让用户知道“你记住了什么”。比如很多产品会在设置页展示“Agent对我的了解”,用户可以选择删除某条记忆、关闭某类记忆。这不只是合规需求,也是信任建立的关键。我甚至建议在对话里做到一种轻量级的“记忆透明化”:当Agent的回答明显是基于某条历史记忆时,可以标注一下“这是根据您上次提到的XX信息”。
5.4 跨Agent的记忆共享与迁移
现在的记忆系统大多绑定在单一Agent上。未来可能会出现一种场景:用户有一个通用偏好库,不同的垂直Agent(客服Agent、运维Agent、选型Agent)都遵循同一个标准接口来读写这个偏好库。这样用户在任何一个Agent那里沉淀的信息,都能被另一个Agent继承,真正做到“一次了解,处处贴心”。当然这里数据安全和隐私授权的挑战很大,需要从架构层面设计好权限模型。
最后说一点个人体会。做Agent记忆功能,技术选型从来不是最难的,最难的是想清楚“哪些记忆值得存、哪些该忘、怎么在不打扰用户的前提下让用户感觉被记住”。我在实际操作中试过三次大的方案调整,最终效果最好的反而是“轻存储、重抽取、精召回”这条路线——不需要把每一句话都存下来,但要让存下来的每条记忆都足够精准。如果你刚开始做这个功能,我建议先跑通一个最小闭环:一次会话的记忆写入、一次跨会话的召回、一次基于记忆生成的个性化回答。把这个闭环跑通了,再去想多模态、去中心化这些更远的事情也不迟。