
你有没有遇到过这种情况你和某个AI助手聊了半小时它把你的项目背景、忌讳、喜欢的表达风格都摸得一清二楚。第二天你重新打开对话框它像失忆了一样把你的需求从头又问一遍甚至重复推荐你已经明确否掉的方案。用户问一句“你记不记得我说过什么”——这个问题是所有从Demo走向生产级的AI Agent都要面对的灵魂拷问。这个问题的根子不在模型能力而在记忆系统缺失。模型再聪明一次对话能装的信息也有限对话一关更是片甲不留。所以当我开始从零搭建一个生产级记忆型AI Agent时第一件事不是挑模型而是设计一套能“记得住、想得起、忘得掉”的记忆系统。整个项目我选了AgentScope作为底座——它不只是一个Agent开发框架更像是一套为多Agent协作和运行时管理设计的完整环境。这篇东西不写空理论完全围绕项目实操展开为什么Agent需要记忆、记忆系统怎么设计、“记忆score时间半衰期”到底怎么落地、AgentScope怎么用、以及真正上生产时会踩到哪些文档里不会写的坑。适合正在做Agent产品、被“长期对话失忆”折磨过、或者打算用AgentScope搭业务系统的朋友。读完你至少能照着搭出一版带跨会话记忆的Agent并且知道怎么把它往生产方向迭代。1. 为什么普通Agent越用越傻记忆缺失的三个典型症状不管你是用LangChain、自研框架还是AgentScope只要没做记忆系统Agent用起来必然出现下面三个症状。先认清症状后面对症设计才有依据。1.1 会话一长关键信息被“淹没”现在的LLM上下文窗口越来越大128K、200K已经不稀奇但窗口大不等于记忆好。实际跑过的人都有体会当上下文里堆积了几万token的聊天流水账时模型对用户真正的偏好在注意力层面会被稀释。比如用户在对话第10轮说“我不用Java团队全是Go”到第80轮你再问它技术栈偏好它可能就忘了或者答得模棱两可。因为那段信息混在两百条闲聊里相对权重太低了。这不是模型笨是我们把“存储”和“记忆”混为一谈了。存储是把所有东西堆在桌面上记忆是从桌面上挑出此刻最重要的三样放到手边。不做取舍的上下文堆积本质上只是换了一种丢信息的方式——以前是关对话丢现在是开对话也丢。1.2 会话一关一切归零这个最好理解。传统ChatBot也好加了流式输出的Agent也好如果不做任何持久化新会话就是一张白纸。用户上礼拜跟你确认过的订单编号、供应商报价、项目截止时间全部消失。他们不会觉得这是技术限制只会觉得“AI真笨一点不长记性”。跨会话失忆最直接的影响是信任崩塌。用户愿意多花时间向你描述需求是因为预期你下次能基于这些信息继续服务。一旦发现你全忘了他就得从头解释这个体验坏掉的速度比任何功能短板都快。1.3 有“记忆”但不会“用”还有一种情况比没记忆更气人——有的系统确实把对话历史存进数据库了但用的时候一股脑灌进Prompt。于是你会看到一个Agent面对8000字的陈旧信息不仅没变聪明反而更爱胡说八道。因为它把“历史消息”当成了“事实知识”没有区分哪些是当时的废话、哪些是值得长期遵循的用户意图。记忆的真正定义不是“存了多少”而是“在正确的时机把正确的信息以正确的方式送到模型面前”。存储、检索、遗忘、注入这四个环节缺一不可任何一个做不好记忆系统都是摆设。2. 记忆系统设计四问存什么、怎么存、何时取、如何忘动手写代码之前先把设计问题想清楚。我建议所有做记忆型Agent的人都先回答这四问。表面上这四个问题是常识但落地时90%的方案都折在其中某一条上。2.1 存什么从对话流里提炼“值得记住的事”记忆不是日志。把每一轮用户消息原封不动存进去那不叫记忆系统叫监控存档。真正值得进入长期记忆的信息我按经验分成四类用户画像姓名、身份、团队规模、技术栈、工作节奏、沟通偏好、禁忌。这类信息稳定价值高是要长期保存的“硬通货”。关键事实已完成的事项、确认过的决策、重要事件的时间点。比如“用户已经确认Q2上线”“供应商换成了XX”。待办与承诺用户明确要求后续处理的事情以及你答应过要做的事。这类信息有时效性到期前要能主动调出来。偏好与经验用户对某个方案的态度、踩过的坑、强调过的原则。比如“用户强烈反对在生产环境直接改库”。从原始对话到结构化记忆这一步我建议用一个专门的编码Prompt让LLM做信息抽取和结构化。不要相信正则表达式和关键词自然语言里的隐含信息太多。下面是我项目里实际用的一套抽取指令效果比较稳你是记忆编码器。从下面这段对话中提取值得长期记住的信息只输出JSON数组。 每条记忆字段 - type: user_profile / fact / todo / preference - content: 一段概括性描述第三人称 - importance: 0到1之间的数字表示这条信息对后续对话的价值 - expire_at: 可选绝对时间戳表示这条信息何时失效 提取原则 1. 只提取对“未来还能复用”的信息不存储寒暄和一次性请求。 2. 用户明确表达过的偏好、禁忌、承诺优先。 3. 宁可少提取不要为了凑数而提取。加了这段Prompt之后记忆中“垃圾条目”的比例大幅下降。关键心得是编码质量直接决定整个记忆系统的天花板宁可少存不要乱存。2.2 怎么存双网络记忆模型的具体映射“双网络记忆模型”听起来玄乎说白了就是把记忆分成两个通道各干各的短期工作记忆对应当前会话内的上下文。它容量有限、速度要求高、跟着会话走。工程上我就是一个环形缓冲区保留最近N轮关键消息的摘要会话结束就归档。它负责解决“上下文淹没”问题——不是把历史全塞进去而是把历史压成几条要点放在手边。长期语义记忆对应跨会话的持久化知识。它存储在向量库结构化数据库里容量理论上无限但访问成本高。它负责解决“会话一关一切归零”的问题——用户下一次来先把和他相关的长期记忆捞出来重建对话背景。双网络不是简单的“一个Redis一个MySQL”而是两个职责完全不同的通道。短期记忆要的是“快”和“精”长期记忆要的是“全”和“稳”。两者协同的接口就是记忆管理器长期记忆负责“记得”短期记忆负责“手边就有”。2.3 如何取什么时候让记忆进入模型视野记忆不是每个token都要看而是在几个关键时机注入会话启动时把该用户的长期记忆概要拉出来作为System Prompt的种子信息。每轮用户输入后、模型推理前用当前用户消息做检索召回top-k条相关记忆拼进上下文。调用工具前如果Agent准备执行外部操作先查一下记忆里有没有相关历史。比如用户两小时前刚改过某个配置这次工具调用前就应该提醒模型“配置可能已被修改”。取回的候选集不能直接堆进去要先做一次“价值排序”。我的做法是把语义相似度和记忆价值分融合成一个排序分取前5条注入。这个“先取回再重排”的套路是记忆系统和RAG系统的通用解法。2.4 如何忘记忆score时间半衰期怎么落地记忆不是库存不能只进不出。没有遗忘机制的记忆系统迟早被噪声淹没。这里就用到了我在标题里写的那个公式记忆score时间半衰期。简单说每条记忆有三个基础属性重要度分数score、创建时间、最后访问时间。它的实时价值随时间和访问频率变化计算公式如下import time def memory_value(base_score: float, last_access_at: float, now: float, half_life: float 86400, access_bonus: float 0.0) - float: 记忆价值 基础分数 * 时间衰减 访问加成 半衰期参数 half_life 表示价值衰减到一半所需时间。 elapsed_hours (now - last_access_at) / 3600.0 decay 0.5 ** (elapsed_hours / (half_life / 3600.0)) return base_score * decay access_bonus为什么用半衰期而不是线性衰减因为人脑的记忆遗忘曲线本来就是指数型的——刚记完忘得最快然后越来越慢。工程上指数衰减还有一个好处参数直观说“半衰期30天”比说“每小时衰减系数0.997”容易让团队理解。不同类型记忆的半衰期我建议差异化设置记忆类型默认半衰期说明用户画像30天画像信息稳定性高可设更长关键事实7天项目决策类中期复用偏好经验14天需要较长期保留待办承诺按截止日期到期前价值不降到期后直接清除会话细节2小时快速过期防止污染访问加成是另一个关键用户每次提到某条记忆它的价值就该被“激活”一次最后访问时间刷新相当于复习了一遍。这个机制模拟的是“被反复确认的信息更重要”。访问加成上限建议封顶比如最多加0.3防止一条老记忆因为频繁踩中而永远有效。遗忘本身不用主动“删”。系统运行时检索阶段会把即时价值低于阈值的候选过滤掉后台再用离线任务把长期低于阈值的记录归档或删除。这样用户感知层面“忘了该忘的”存储层面也不会膨胀。3. 技术选型AgentScope凭什么当生产级Agent的底座记忆系统设计清楚了接下来是工程底座。我调研了LangChain、AutoGen、AgentScope几家最终项目落在AgentScope上。这个选择不是因为它最火而是因为它解决了我最头疼的几个生产级问题。3.1 AgentScope的项目定位和核心抽象AgentScope是阿里巴巴通义实验室开源的多智能体开发框架。它的核心抽象不算多但组合起来很强Agent智能体单元可以是一个人设角色、一个工具封装、或者一个纯逻辑节点。每个Agent自己有状态和行为循环。MessageAgent之间传递的消息对象消息有类型、内容和来源。这个设计比我见过的一些框架“裸传dict”的做法严谨得多。Pipeline/Workflow消息流转的编排机制决定消息先发给谁、如何汇聚、如何分叉。框架底层是基于Actor模型的并发机制每个Agent在自己的运行时里跑消息异步流转。这就带来一个对生产级至关重要的能力Agent可以被拆成独立的服务部署而不是只能单进程串行跑。记忆模块、检索模块、工具模块都能各自独立扩展。3.2 和LangChain、AutoGen相比差异在哪我直接列一张我选型时候的对比表都是真实体感维度LangChainAutoGenAgentScope编排方式链式为主复杂图需拼装多Agent对话驱动消息流驱动显式Pipeline生产部署偏单进程服务化要另搭偏研究和多Agent模拟有分布式运行时Agent可服务化消息机制弱类型靠约定有ConversableAgent但偏重Message一等公民字段明确记忆/检索靠外部Memory模块代码要自己串有Memory能力但要自己配可把记忆检索封装成独立Agent多Agent中台化一般一般适合作为多Agent中台底座说白了LangChain赢在组件生态但组件之间的黏合度一般AutoGen赢在对话驱动的多Agent创新但复杂业务编排和部署要自己花很多功夫AgentScope赢在架构干净、消息机制健壮、面向生产的多Agent服务化。我的项目里需要把记忆检索、持久化、RAG、工具调用都做成可独立扩缩容的模块AgentScope的消息驱动模型是最贴合这个目标的。3.3 在AgentScope里给记忆模块一个“位置”AgentScope里做记忆型Agent我建议把记忆系统也建模成一个Agent——姑且叫MemoryAgent。主Agent负责和用户对话、推理、决策MemoryAgent负责记忆的写入和读取。两者通过Message通信。这样一个设计的最大好处记忆的读写变成了消息传递而不是函数调用。主Agent想存记忆发一条消息给MemoryAgent想取记忆发一条查询消息。MemoryAgent可以单独扩展、单独部署、单独压测。将来多个业务Agent想共享一套记忆服务只需把MemoryAgent的访问地址共享出去记忆就中台化了。4. 从零搭一个“能记住用户”的Agent完整实操接下来是硬核部分。按照上面说的设计我用AgentScope搭一个带跨会话记忆的Agent代码不是玩具基本逻辑可以直接用在生产原型上。4.1 工程骨架与依赖项目结构我建议这样拆my_memory_agent/ ├── agent.py # AgentScope接入与主循环 ├── memory/ │ ├── __init__.py │ ├── models.py # 记忆数据结构 │ ├── value.py # 价值评估与半衰期衰减 │ ├── store.py # 持久化存储层(SQLite起步) │ ├── encoder.py # LLM记忆编码 │ └── manager.py # 记忆管理器对外提供读写接口 ├── prompt_templates.py # 各类Prompt模板 ├── schemas.py # 通信数据结构AgentScope Message子类 ├── config.py # 配置 └── requirements.txt依赖只需要几个核心包agentscope、openai或你用的模型SDK、sqlite3Python自带、numpy可选用于向量运算。如果后面要上向量检索再加一个chromadb或者接Milvus/Qdrant的客户端。4.2 记忆数据结构与价值评估实现记忆条目是整套系统的心脏。我定义的数据结构如下dataclass class MemoryItem: memory_id: str owner_id: str # 用户ID用于会话隔离 content: str # 记忆内容 memory_type: str # user_profile / fact / todo / preference base_score: float # 0~1初始重要度 value: float # 实时价值由value.py计算 created_at: float last_access_at: float access_count: int expire_at: float | None # 可选过期时间实时价值计算就是上一章那段代码的封装。我单独抽成模块是因为后续要反复调用而且方便做单元测试。测试用例我会覆盖分数高的记忆过期后会不如分数低的新记忆、访问会刷新价值、不同类型半衰期不同。4.3 记忆编码器从对话流中抽取结构化记忆编码器用LLM做抽取这个不用避讳。它的职责是输入用户最新一轮的消息、当前短期记忆摘要、已有长期记忆摘要输出“值得更新的记忆列表”。class MemoryEncoder: def encode(self, user_content: str, recent_summary: str, existing_summary: str) - list[dict]: prompt MEMORY_ENCODE_PROMPT.format( user_contentuser_content, recent_summaryrecent_summary, existing_summaryexisting_summary, ) raw self.llm_json(prompt) # 强制JSON输出 items parse_memory_json(raw) # 校验schema return [item for item in items if item[importance] 0.3]这里有个关键动作不是每一轮对话都跑编码器那样LLM调用成本会失控。我的策略是当用户消息满足以下任一条件时才触发编码——用户在做自我介绍、用户明确表达偏好或禁忌、用户布置了待办、用户否定了之前的某个方案或者当前会话累计超过10轮、短期摘要需要归档时做一次整体编码。4.4 把记忆管理器接入AgentScope的Agent循环重头戏在这里。AgentScope里的Agent核心方法是响应消息。我在主Agent的推理循环里显式插入记忆读取from agentscope.agent import AgentBase from agentscope.message import Msg class MemoryAgent(AgentBase): def __init__(self, name, memory_manager, llm): super().__init__(name) self.memory_manager memory_manager self.llm llm def reply(self, x: dict None) - dict: # 1. 从用户消息中解析出当前用户ID和内容 content x[content] owner_id x.get(owner_id, default) # 2. 检索相关记忆 retrieved self.memory_manager.retrieve( owner_idowner_id, querycontent, top_k5, ) # 3. 构建记忆上下文块 memory_block self._format_memory_block(retrieved) # 4. 组装Prompt messages [ {role: system, content: SYSTEM_PROMPT memory_block}, {role: user, content: content}, ] # 5. 调用模型 response self.llm(messages) # 6. 异步写入本轮值得编码的记忆 self.memory_manager.enqueue_encoding(owner_id, content) return Msg(self.name, response, assistant)retrieve和enqueue_encoding的具体实现就是记忆管理器的核心直接放出来class MemoryManager: def __init__(self, store, encoder, half_life_mapNone): self.store store self.encoder encoder self.half_life_map half_life_map or { user_profile: 30 * 86400, fact: 7 * 86400, preference: 14 * 86400, todo: 15 * 86400, } def retrieve(self, owner_id, query, top_k5): now time.time() candidates self.store.search(owner_id, query) weighted [] for item in candidates: hl self.half_life_map.get(item.memory_type, 7 * 86400) value memory_value(item.base_score, item.last_access_at, now, hl) # 融合语义相关度占大头记忆价值做加权 score item.similarity * 0.6 value * 0.4 weighted.append((score, item)) # 顺带刷新访问状态和访问次数 self.store.touch(item.memory_id, now) weighted.sort(keylambda x: x[0], reverseTrue) return [item for _, item in weighted[:top_k] if memory_value(item.base_score, item.last_access_at, now, self.half_life_map.get(item.memory_type, 86400)) 0.15]读到的那条0.15阈值很关键——低于这个值的记忆说明已经“忘得差不多了”没必要再占用上下文窗口。4.5 验证造一个“记得住”的对话场景原型跑通之后一定用下面这个三层验证法测一遍光“聊着感觉不错”不算数第一层会话内记忆验证用户在第1轮明确说“我叫林杰后端主用Go”聊到第20轮时问“我的技术栈是什么”Agent要能准确回答。这一层不过关说明短期记忆都有问题别急着往下走。第二层跨会话记忆验证结束会话重新开一个新的会话模拟同一用户ID。用户问“你记得我叫什么吗”Agent应该从长期记忆里捞出来。第三层模糊查询验证用户不用“名字”这种精确词而是说“我之前强调过不想用Java现在项目方案有没有违背这一点”——这要求记忆系统能基于语义把“用户偏好-技术栈-禁忌”这条记忆召回并注入上下文而不是靠关键词匹配。三层全过记忆Agent的核心闭环就通了。我在自己机器上跑这个验证时前两次都挂在第三层——检索召回了相关记忆但Prompt模板里给模型的位置太靠后导致模型没重视。后来把记忆块放在System Prompt靠前位置并显式标注“以下是从长期记忆中恢复的用户信息请优先遵循”效果明显改善。5. 生产级改造从“能跑”到“能扛”原型能跑通离生产级还差着十万八千里。生产级意味着数据不能丢、并发不能串、体积不会无限膨胀、出了事能追溯。这一章讲的都是我上生产时踩过真坑之后总结出来的改造点。5.1 持久化与向量检索的选型第一步记忆不能只存在内存里。我用SQLite做结构化记忆的持久化起步开启WAL模式事务性好、零运维、备份简单。生产环境数据量大了再平滑迁移到PostgreSQL。第二步为语义检索引入向量存储。最开始我用Chroma本地模式开发调试方便后来并发上来了切换到Milvus或者Qdrant。要注意的是AgentScope本身并不绑定检索组件这块是需要你自己接入的。我的存储架构是“二级存储”结构化字段owner_id、memory_type、expire_at、score走SQL负责过滤和排序。语义相似度走向量库负责“召回相关片段”。最终候选集合并再做一次价值重排。这个架构的好处是可以用SQL精确控制“我这个用户只能搜到自己的记忆”向量库只负责粗召回不需要承担权限过滤这种它不擅长的事。5.2 会话隔离与用户级权限生产环境最大的隐患不是模型不够聪明而是A用户看到了B用户的记忆。这不是危言耸听我见过一个原型系统检索条件里漏了owner_id用户A问“我之前说的那个方案”系统从全局库召回了一段其实是用户B说过的方案差点造成信息泄露。解决办法就一条铁律所有记忆读写路径必须显式携带owner_id并在SQL和向量检索两层都加过滤条件。向量检索那一层尤其容易被忽略因为向量库里存的是浮点向量你以为自己过滤了实际组装的Query里忘了带filter参数。另一个细节多租户场景下记忆模块的LLM编码和检索服务要做资源隔离至少不能因为一个租户的突发流量把别的租户的MemoryAgent打挂。基于AgentScope的Message分发机制这个问题可以靠给每个租户分配独立的MemoryAgent实例解决。5.3 记忆膨胀、冲突与重写策略记忆只进不去的系统三个月后必然被垃圾信息淹没。生产环境必须有后台任务处理四件事合并同一用户的多条相似记忆合并成一条比如用户三次表达“后端不用Java”只保留一条带重要度累加的记录。归档即时价值长期低于阈值、且超过N次没有被召回的记忆转入冷存储或者删除。冲突修复用户说“A”后来又明确说“不要A了”以更晚的、基础分更高的那条为准并把旧条目标记为过期。这里尤其要注意“用户主动否定”的语义编码器要能识别出否定语气。用户主动删除这是生产级Agent的底线能力。用户说“忘掉我之前说的事情”系统必须能真正删除对应记忆而不是假装删除。GDPR这类合规要求对个人数据删除有硬性规定记忆系统天然属于个人数据范畴不可逆删除接口必须从第一天就做好。5.4 可观测性与评估不能只靠“聊得爽”记忆系统是隐形的它好不好用直接看对话质量但对话质量是主观的。生产上必须建立可观测指标否则你根本不知道记忆系统是在帮忙还是帮倒忙记忆命中率每轮对话检索出的记忆里有多少条实际被模型采用体现在最终回答中。用LLM评估答案时看引用记忆的交叉命中率。记忆召回准确率人工抽检一批对话判断“该被回忆起来的信息是否被召回”。这是RAG里标准的recall指标。记忆写入精度定期抽检记忆库的条目看多少条是垃圾或重复。写入精度低说明编码器Prompt或触发条件有问题。会话满意度跨会话场景下用户第二次会话的完成率、撤回率、投诉率跟首次会话做对比。这块评估体系建好之后你会发现自己对记忆系统的优化有了方向而不是靠感觉调参数。6. 踩过的坑与下一步方向最后这部分是我最想写的。代码写一百遍不如一遍踩坑记忆深。这几个坑我相信每个自己做Agent记忆系统的人都会遇到。6.1 踩坑一把全部历史都塞进Prompt代价超出你想象最早一版我图省事直接把整个会话历史塞进上下文心想“反正128K窗口够大”。结果第一费用翻了快三倍第二模型在长上下文中找关键信息的表现很不稳定尤其当那个关键信息藏在中后段时。后来我做了两件事才解决一是会话历史先提炼成结构化摘要再放进去二是记忆检索只取重排后的top-k条。上下文不是仓库是工作台。放上去的每一块信息都在占模型的注意力。6.2 踩坑二半衰期参数拍脑袋定导致记忆集体“蒸发”有一阵子我把所有记忆类型的半衰期都设成24小时想着“反正每天都会聊”。结果一个周末过去了用户的画像记忆价值全部衰减到接近0周一用户回来Agent“失忆”了。用户非常愤怒说“我上周才跟你讲过我是谁”。教训是不同记忆类型必须差异化设置半衰期而且要考虑真实业务节奏。用户画像类至少30天起步项目决策类按项目周期来。另外访问加成和“回访保活机制”要配合使用——用户主动提了某条信息系统不只刷新该条记忆的访问时间还要顺带把同主题关联记忆的访问时间也刷新一点。这模拟的是“用户记起了一件事往往其他相关的事也在他脑子里活跃”。6.3 踩坑三记忆编码器输出脏数据把整个库搞臭LLM做结构化抽取偶尔会输出残缺JSON、或者把一句玩笑编码成“重要事实”。开始我没做数据校验结果库里混进不少垃圾记忆检索的时候还总能命中因为语义相关。后来我加了几个保险输出必须过Pydantic校验解析失败就重试一次再失败就丢弃。编码时要求模型给每条记忆打置信度低于0.5的直接不写库。写入走队列编码器故障不影响主Agent运行。记忆是辅助系统不能因为记忆模块挂了把主Agent拖垮。6.4 下一步记忆中台化、多Agent共享、跨会话技能迁移系统走上正轨后我打算往三个方向迭代。一是基于AgentScope的消息机制把MemoryAgent彻底做成服务化中台。多个业务Agent共用一个记忆服务按owner_id天然隔离。这是“AI Agent中台”里比较落地的一块比反复给每个Agent单独配记忆省力太多。二是跨会话技能的迁移。现在记忆里存的是“事实”下一步要存“技能”比如用户在多个项目里都用同一个代码规范我可以把“用户偏好XX规范”固化成Agent的默认行为这已经不是记忆而是基于记忆沉淀出技能。类似workbuddy这类工具已经在做跨对话Skill思路会越来越成熟。三是记忆召回重排。当前重排用的是“相关度价值”的线性公式比较粗糙。后面计划训练/微调一个轻量rerank模型或者干脆用LLM做二次重排让召回结果更贴合用户当前意图。我个人的体会是记忆系统不是Agent的一个可选组件而是决定Agent能否从“Demo玩具”进化成“生产级生产力工具”的分水岭。它本质上是一套设计哲学——你要想清楚Agent该记住什么、遗忘什么、如何在合适的时候调用。技术方案可以迭代模型可以换但“把记忆当一等公民”这个设计原则越早定下来后面省的事越多。最后送大家一个小技巧验证记忆系统别铺一大堆测试场景先跑通一个最简单的——“用户主动问你记不记得他上礼拜说过什么”。这个场景跑通记忆链路基本就通了跑不通再花哨的功能都是白搭。