ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

智能体记忆系统设计:分层模型与工程落地

智能体记忆系统设计:分层模型与工程落地

开头

做了一段时间大模型应用开发,我发现一个特别有意思的现象:同样是调用同一个模型,有的产品用下来像冷冰冰的问答机,有的却像相处很久的老朋友。差别往往不在模型本身,而在产品有没有给这个模型配上“记忆”。最近我们团队在做一款名叫“小度想想”的智能体产品,核心目标就是让智能体记住每个用户的偏好、习惯和对话上下文,真正实现千人千面的个性化体验。这篇文章把我们在设计记忆系统时踩过的坑、理清的思路、落地的实现方式整理出来,希望能给正在做智能体、AI助手或个性化对话系统的朋友一些参考。记忆的分层、写入策略、召回调优、隐私边界这些我们都会展开聊,内容偏工程落地,不堆概念,看完可以直接照着搭一版。

1. 为什么智能体需要记忆:产品痛点与设计原点

1.1 “失忆”的智能体有多让人抓狂

先讲个真实场景。早期我们上线了一版没有记忆能力的对话Demo,用户每天进来都是陌生感拉满。有用户连续一周跟智能体说“我胆固醇偏高,早餐不要推荐油腻的”,智能体每次都像第一次听到一样,下一条推荐还是煎饼果子配油条。用户后来在反馈里写了一句很扎心的话:“它还不如一个记事本。”这句话直接推动我们把记忆系统提上了排期。

失忆带来的问题不只是体验差,还会让用户对产品的信任感快速流失。试想,用户花了十分钟告诉智能体自己的作息、饮食禁忌、健身目标,转头再问一句“我今天适合练什么”,智能体完全接不上,那一刻用户就会觉得之前的信息全白给了。更现实的是,很多智能体产品的核心价值并不是单轮问答的正确率,而是长期陪伴中形成的连贯服务。没有记忆,就等于每次对话都是初次见面,产品永远停留在工具层面,做不出情感黏性和个性化价值。

1.2 从“会聊天”到“懂你”:记忆的分层模型

想清楚记忆要解决什么问题后,我们一开始也犯过“什么都想存”的毛病。对话记录、用户点击、停留时长、页面浏览全部塞进一张表里,结果又乱又没法用。后来参考认知科学里关于人类记忆的分层方式,把智能体记忆拆成四类:短时工作记忆、长期情景记忆、语义知识记忆和用户画像记忆。

短时工作记忆对应当前会话里的上下文,比如用户前一句提到“周末去杭州”,后一句问“帮我查高铁”,智能体得知道“杭州”就是它要查的目的地。长期情景记忆则是跨会话的关键事件,比如“上个月用户说过在准备省考”“去年年底换了新工作”。语义知识记忆更偏通用常识,比如城市地图、菜系分类、履历常识,这部分可以直接从外部知识库挂载,不一定要从对话里学。用户画像记忆是最高抽象层,把用户明确表达或行为推断出的偏好沉淀成标签,比如“偏爱清淡”“工作时间上午九点到下午六点”“家里有一只五岁的柯基”。

这四类记忆不是并列关系,而是层层汇总的关系。会话上下文经过提炼变成情景记忆,情景记忆再经过归纳和校验沉淀为用户画像。分清楚层级之后,后面设置存储策略和更新逻辑都会省力很多。

1.3 千人千面不是玄学:记忆如何支撑个性化

“千人千面”这个词在产品文档里出现频率很高,但落到智能体身上,本质就一句话:让系统在面对不同用户时,能够调取不同的背景信息,生成不同的回答策略和表达风格。举个例子,同一个“晚上吃什么”的问题,面对一位高血糖用户和一位正在减肥的健身用户,智能体给出的答案完全不同。前者侧重推荐的含糖量提示,后者侧重蛋白质和热量控制。如果不用记忆做区分,模型只能给出一份模板化的“均衡饮食建议”,看起来没错,但谁都觉得没说透。

记忆支撑个性化还有一层递进:它让模型越用越懂用户的表达习惯。有人喜欢简洁答案,有人希望先解释原理再给结论,有人习惯用短句提问。这些风格特征完全可以被记忆系统捕获,然后在生成提示词里作为显性指令传给模型。我们实测下来,加了风格记忆之后,用户的对话满意度评分提升非常明显,比单纯调模型参数还管用。这也是为什么我一直认为,大模型应用拼到最后,拼的不是模型本身,而是模型外面的这套数据与记忆架构。

2. 记忆系统整体架构与技术选型

2.1 四层架构:采集、存储、召回、应用

记忆系统在我们项目里并不是一个单独的服务,而是嵌在智能体主流程中的中间层。整体分成四层:采集层、存储层、召回层和应用层。

采集层负责从交互过程中提取原始线索,包括用户输入、用户反馈、主动点击行为、系统触发的埋点等。这里有一个容易被忽略的点:不是所有对话内容都值得进存储层,采集层需要先做一轮预过滤,把噪音话题、闲聊寒暄、重复试探性内容筛掉。存储层负责把采集到的线索变成结构化记录,写入对应的存储介质。召回层是记忆系统的核心,它的任务是当用户发起新一轮对话时,从海量记忆里快速找到与当前问题相关的片段。应用层再把召回结果转成提示词中的上下文块,交给大模型生成最终回答。

四层之间的接口要尽量保持简单,我们一开始设计时还加了消息队列做异步解耦,后面发现对话场景下的记忆写入延迟其实没那么敏感,反而是同步写入更容易排查问题。最后简化成了“采集层写一份、存储层存一份、召回层查一份、应用层拼一份”的直线流水,代码清晰很多。这个经历也让我意识到,架构是为场景服务的,不要为了所谓的解耦而过度设计。

2.2 技术选型:向量库、缓存库和关系库怎么搭配

存储层的选型是记忆系统设计里最花时间的环节。我们考虑过全用向量数据库,也考虑过全用关系型数据库,最后定下来的是“关系库 + 向量库 + 缓存库”的组合方案。

关系型数据库用来存用户画像这类强结构数据。它字段明确、支持复杂条件查询、事务能力强,标签体系变动的时候也好做迁移。向量数据库用来存文本类的记忆片段,比如用户说过的一段话、事件摘要、偏好描述,这类信息没有固定结构,只能靠语义相似度来检索。缓存库用来存当前会话内的短时上下文,需要低延迟读写,所以对速度要求最高,一般用键值型存储就能满足。三者的边界其实和前面说的记忆分层是对应的:画像层偏结构化,情景层偏向量化,会话层偏缓存化。

选型时还有个容易被忽视的细节:向量数据库的索引类型和距离度量方式。如果记忆片段数量不大,几万条以内,用暴力检索都能满足需求;一旦上了百万级,就得考虑HNSW这类近似最近邻索引,同时要根据文本长度选择合适的归一化方式。我们早期在评估阶段用默认配置跑了一版,结果中文长文本的召回结果非常不稳定,后来发现距离度量选成了不适合文本向量的方式,换成适用的度量之后,效果立刻改善。这个坑后面细说。

2.3 记忆类型与生命周期管理

不同记忆类型的生命周期差异很大,不能一视同仁。会话内的短时记忆只在当前对话中有效,对话结束就可以清理,最多保留到用户关闭页面后十分钟。用户画像层的记忆则需要长期保存,通常情况下只增不删,除非用户主动修改或要求清除。情景记忆和语义记忆处于中间状态,需要做时间衰减和定期归档。

生命周期管理的核心是防止两件事:一是记忆无限膨胀,导致召回时噪声变大;二是陈旧记忆误导模型,比如用户两年前喜欢某个品牌,现在可能早就换口味了。我们的处理方式是给每条记忆打上时间戳和置信度字段,召回时按时间衰减系数做加权;同时后台跑定期任务,把超过半年的低置信度记忆降级,超过一年且从未被命中的记忆直接归档。表格里可以看到我们对四类记忆的生命周期约定:

记忆类型存储介质默认保存时长更新策略召回优先级
短时工作记忆缓存库会话结束 + 10分钟每轮对话全量覆盖最高
长期情景记忆向量库180天事件抽取后追加中
语义知识记忆向量库/知识挂载持久定期同步外部知识低
用户画像记忆关系库持久显式确认后更新高

这套生命周期管理规则上线之后,最直观的变化是召回精度的稳定性变好了。之前出现过用户上一秒说“想去重庆”,下一秒智能体突然引用他半年前说过的“不喜欢火锅”,严重割裂。加了时间加权后,近期的记忆天然占更大权重,这种跨时空打架的情况明显减少。

3. 核心细节解析:从用户画像到记忆槽位设计

3.1 用户画像字段设计与标签体系

用户画像不是简单堆字段,而是要具备可扩展性。我们把画像表设计成三块:基础属性、动态偏好、行为统计。基础属性包括称呼、所在城市、年龄范围等,这类信息通常由用户主动提供或对话中显式说出。动态偏好包括饮食口味、沟通风格、兴趣话题、常用时间段等,这类信息会随着对话持续更新。行为统计则记录统计类的指标,比如最近七天对话次数、平均对话轮数、主动点击次数。

画像字段里我们特别设计了一个“标签来源”字段,记录每个标签是被用户明确说出来的,还是被系统推断出来的。比如用户直接说“我不吃香菜”,这就是显式标签,置信度直接拉满;如果是通过用户多次提到“香菜好难吃”推断出来的,就是隐式标签,置信度低一些。这个设计非常关键,因为在后续的记忆更新冲突中,我们要靠置信度来决定听谁的。JSON示例可以这样表示:

{ "user_id": "u_10001", "base_info": { "nickname": "A同学", "city": "杭州", "age_range": "25-30" }, "preferences": [ { "tag": "饮食偏好:清淡", "source": "explicit", "confidence": 0.95, "updated_at": "2025-06-01 20:13:00" }, { "tag": "沟通风格:喜欢短答案", "source": "inferred", "confidence": 0.72, "updated_at": "2025-05-30 09:12:00" } ], "behavior_stats": { "recent_days_active": 12, "avg_dialogue_rounds": 6.5, "preferred_hour": "22:00-23:00" } }

3.2 记忆写入策略:什么时候记、怎么记、记多少

记忆写入是整个系统里最需要克制的地方。我们的经验是:宁缺毋滥。写入过多低质量的记忆,会让系统变得“自作聪明”,甚至产生错误的个性化。写入时机主要看三类信号。第一类是用户显式表达,比如“我喜欢”“我不吃”“我住在”这类明确句,这是最高优级的写入信号。第二类是重复行为,同一个偏好在多轮对话或多次会话中反复出现,系统可以自动打上隐式标签。第三类是外部行为反馈,比如用户对某类推荐频繁点击,或者对某类回答频繁点踩,这些行为也能反映偏好。

记多少的问题同样值得琢磨。我们尝试过把用户原话完整存进向量库,后来发现效率极低,且原话里的情绪化表达会干扰语义检索。最终方案是让大模型先做一次结构化抽取,每条记忆只保留“主体 + 属性 + 偏好对象 + 时间上下文”的格式,比如“用户不喜欢吃香菜”而不是“我记得用户上次吃饭的时候说香菜好难吃”。这样做至少带来两个好处:一是记忆片段更干净,向量检索的语义距离更准确;二是后续做用户可读的记忆管理界面时,可以直接把这些结构化描述展示给用户,体验更友好。

提示:记忆写入要做幂等控制,同一个标签如果已经被写入,再次写入时不要生成新记录,而是更新原记录的置信度和时间戳,否则画像表很快就会堆满重复数据。

3.3 记忆召回:相似度计算与阈值调优

召回模块直接决定了记忆能不能被“用起来”。我们采用的是多路召回策略,每一路单独输出一批候选,再做融合排序。第一路是根据用户当前问题文本向量与向量库中的记忆片段算相似度,找语义上最接近的记忆。第二路是直接拉取当前用户的画像标签,把置信度高的画像记录全量取出。第三路是查最近几轮会话的短时上下文。第四路是可选的,根据当前场景和时间段做规则召回,比如用户在晚间提问,就把“晚间提问偏好”这一维度的记忆优先提出来。

多路召回的难点在融合排序。不同路的打分口径不一样,不能直接相加。我们的做法是给每一路设一个基础权重,再用时间衰减因子加权。时间衰减采用指数衰减公式:最终权重 = 基础权重 × exp(-λ × 天数间隔)。λ取值我们调到0.02,表示一条记忆经过35天权重会衰减到原来的一半。这个参数可以根据产品节奏调整,如果用户活跃周期是周级,λ可以适当调大。

阈值也不是拍脑袋定的。我们先拿了一批人工标注的测试数据,跑索引方式下的相似度分布,发现相关记忆的相似度集中在0.78以上,不相关记忆大多在0.65以下。所以初始阈值设为0.76,然后再根据线上召回日志微调。调阈值的核心指标是“召回命中率”,也就是召回来的记忆里有多少条真正对回答有帮助。如果命中率低于40%,说明阈值偏低或排序有问题,需要收紧。

3.4 个性化生成:提示词组装与冲突消解

召回的最终目的是给大模型提供一份干净的“记忆简报”。我们在提示词组装上迭代过很多版本,总结出一个比较稳的思路:system提示词中先固定角色人设,然后放“用户画像”,接着放“近期重要记忆”,最后放“当前会话上下文”。画像是高置信度标签,语气肯定;近期记忆用“据用户最近提到”这类弱表述,避免模型把过期信息当硬事实。示例模板大致如下:

【角色设定】 你是“小度想想”,一位温暖、细心的个人智能助手。 【用户画像】 用户偏好清淡饮食;喜欢200字以内的短答案;常在工作日晚十点后提问。 【近期记忆】 用户在本周二提到周末想爬山;用户在上次对话中反馈不喜欢氛围嘈杂的餐厅。 【当前上下文】 用户现在问:这周六有什么合适的户外活动推荐?

注意这里的措辞控制。如果记忆置信度偏低,我们在生成提示词时会把“用户偏好清淡饮食”改成“用户近期似乎比较关注清淡饮食”,给模型留出怀疑和追问的空间。这个细节很重要,它避免了模型过度自信地依据错误记忆生成答案。

冲突消解是另一个大坑。最典型的情况是用户今天说“我要开始吃辣了”,但历史画像里还写着“不吃辣”。我们的策略是:当用户在当前对话中给出了与画像相反的信息时,以当前对话为准,同时把画像标签的置信度降低,并且更新为“近期偏好可能改变”。但更新不是立刻覆盖,而是进入一个“待确认”状态,只有当下一次对话再次验证后,才算彻底覆盖。这样设计是为了防止用户只是随口说了句违背习惯的话,系统就永久修改画像,反而矫枉过正。

4. 实操过程与核心环节实现

4.1 环境准备与工程目录设计

这套记忆系统我们用的是Python实现,依赖并不多。下面是基础的环境清单,主要用到关系型数据库做画像存储、向量索引库做记忆片段检索,以及一个嵌入式向量构建模块。如果你想快速复现,建议先把基础环境跑通:

pip install numpy sqlalchemy redis

工程目录我们按功能模块拆分,核心就三个文件:画像存储模块、向量召回模块、记忆组装模块。后期维护起来非常方便,每个新同学接手后基本一天能看懂结构。目录设计如下:

memory_system/ ├── profile_store.py # 用户画像读写与冲突更新 ├── vector_memory.py # 记忆向量化、写入、召回 ├── memory_composer.py # 提示词组装与记忆简报生成 ├── tests/ # 单元测试与回归用例 └── config.py # 阈值、衰减系数等全局配置

4.2 画像存储与记忆管理核心代码实现

先说画像存储模块。这一层最核心的是写入时要判断该更新还是该新建,我们用一个简单的按标签名查重再更新的逻辑实现。核心代码如下:

class ProfileStore: def __init__(self, db_connection): self.conn = db_connection def upsert_tag(self, user_id, tag, source, confidence): sql = "SELECT id, confidence FROM user_tags WHERE user_id=? AND tag=?" row = self.conn.execute(sql, (user_id, tag)).fetchone() if row: new_conf = max(confidence, row["confidence"] * 0.9) update_sql = ("UPDATE user_tags SET confidence=?, updated_at=CURRENT_TIMESTAMP " "WHERE id=?") self.conn.execute(update_sql, (new_conf, row["id"])) else: insert_sql = ("INSERT INTO user_tags(user_id, tag, source, confidence, created_at) " "VALUES(?, ?, ?, ?, CURRENT_TIMESTAMP)") self.conn.execute(insert_sql, (user_id, tag, source, confidence)) self.conn.commit()

这个实现里有一个小细节:对于已存在的标签,新置信度不是直接替换,而是取原先置信度的九折和当前置信度之间的较大值。这么写是为了防止某次误判导致标签被瞬间拉得过高,给系统留一点“犹豫空间”。

记忆向量模块就更直接了。写入时把文本丢给Embedding模型转成向量,随向量一起存上原文、类型和时间戳;召回时用当前query向量去索引里做相似度搜索。核心代码片段如下:

class VectorMemory: def __init__(self, index, embed_func, top_k=5): self.index = index self.embed = embed_func self.top_k = top_k def add_memory(self, memory_id, text, memory_type): vec = self.embed(text) metadata = {"id": memory_id, "text": text, "type": memory_type} self.index.upsert(memory_id, vec, metadata) def recall(self, query_text, user_id=None, top_k=None): k = top_k or self.top_k query_vec = self.embed(query_text) hits = self.index.query(query_vec, k=k) return [{"text": h.metadata["text"], "score": h.score, "type": h.metadata["type"]} for h in hits]

这里我刻意没有绑定具体向量库的API,因为不同方案接口差异不大。实际工程里你需要把index.upsert和index.query替换成所用库对应的方法即可。

4.3 完整示例:模拟一次“千人千面”的对话

做一个完整的走查。假设用户A同学第一次使用“小度想想”,在周二晚上说了句“周末想去人少一点的山里走走”。采集层识别到“人少”“山里”“周末”三个关键偏好,写入情景记忆,同时把“偏好安静的自然环境”作为隐式画像标签,置信度设为0.6。

第二天用户再问:“这周六有什么推荐的地方吗?”系统进入召回流程:

第一步,把用户问题向量化,在向量库里做语义检索,召回“周末想去人少一点的山里走走”这条记忆,相似度约0.83,超过了我们设定的0.76阈值。 第二步,从画像表拉取用户标签,看到“偏好安静的自然环境”和“喜欢短答案”。 第三步,组装记忆简报,得到类似下面的文本:

用户画像:喜欢安静的自然环境,答案偏好简洁。 近期记忆:用户提到周末想去人少一点的山里走走。 生成要求:请围绕江浙沪周边、人少、适合徒步的景点给出推荐,控制在150字以内。

模型依据这段简报生成答案,给出的推荐自然就避开了热门景区,而倾向那些徒步路线相对冷门、风景更自然的地点。用户如果觉得推荐满意,点了赞,这个点赞行为又会作为信号反馈给采集层,再次提高“偏好安静的自然环境”这个标签的置信度。这条闭环跑通之后,产品就具备了一个最基础的“越用越懂你”能力。

4.4 效果评估:别只盯着模型指标

记忆系统上线后怎么评估效果,我们分成了离线指标和在线指标两部分。离线阶段跑了三组测试:召回命中率、记忆标签准确率、上下文冲突率。召回命中率看的是召回片段和当前问题是否相关,标签准确率靠人工抽检来打标,上下文冲突率是看提示词里是否出现互相矛盾的信息。这三项在开发阶段用回归测试集持续盯着就够用。

在线阶段则更关注业务指标。我们重点看了四个:用户次日留存、平均对话轮数、回答采纳率、差评率。记忆系统的上线会让对话轮数和使用时长明显上升,如果一段时间内这些指标没有变化,说明记忆没有形成正向体验。还有一类比较隐蔽的评估方式是“可解释性回访”,随机抽选用户,对照他们的记忆画像让用户自己判断画像准不准。我们做了一轮访谈,发现用户对于“智能体记得我上次说过什么”的正面感知特别强烈,哪怕只对了一条,也会提升对整个产品的信任。这比任何算法指标都更能说明问题。

5. 常见问题与排查技巧实录

5.1 记忆污染:聊错一次永久带偏

记忆污染是我们上线后遇到最头疼的问题。典型症状是用户某次闲聊时随口说了句气话“我再也不喝咖啡了”,系统立刻把“不喝咖啡”写入画像,之后半个月的推荐全都避开咖啡,用户无奈地说“我不是那个意思啊”。

排查下来发现根因是写入策略太激进,把低置信度的情绪化表达直接当成了偏好。解决方案从三个层面入手。第一层,在采集层加情绪识别,带有明显负面情绪的句子不直接入画像,只进情景记忆存档。第二层,对隐式标签设置“冷静期”,新标签先以低置信度写入,至少需要在三天内的两次对话中重复出现,才会升级为正式标签。第三层,提供用户可见的“记忆管理面板”,让用户能直接对每条记忆做删除和修改。这个过程在智能体产品里不是额外功能,而是必须项,因为自动记忆一定会犯错,要给用户一个纠错的出口。

5.2 上下文爆炸:长对话下的记忆裁剪

记忆系统上线后,另一个高频问题是上下文长度。用户的记忆越积越多,提示词里的记忆简报越拼越长,最终吃掉大量上下文配额,甚至影响模型响应速度。我们遇到一个极端案例:某位高频用户对话三个月后,画像标签和记忆片段加起来接近8000字,每次请求的延迟都比其他人高出近一倍。

解决思路是给记忆简报设“总字数值”,通常控制在600字以内。超出的部分通过优先级截断:先保留高置信度画像,再保留近期记忆,最后才放中长期的情景记忆。与此同时,后台做一个记忆摘要任务,把三个月前的事件摘要合并压缩成一条全局摘要,只保留“发生了什么、结果如何、涉及什么偏好”三要素。这个处理在实践里非常管用,记忆简报的平均长度从1500字降到了500字左右,模型生成质量和响应速度双双提升,属于少见的“减法优化”。

5.3 冷启动与召回不准:数据稀疏怎么办

新用户没有任何画像和情景记忆时,记忆系统基本是空转的,这个问题叫冷启动。我们尝试的方案有两个。第一是在第一次交互时主动提问采集信息,但不是生硬地让用户填问卷,而是把问题融进对话里,比如用户问天气时顺手问一句“您在南方还是北方?换季的时候我会留意提醒您”。第二是为新用户加载一套“通用常识包”,把城市信息、季节信息、常见场景模板挂载到记忆上下文里,虽然不是个性化内容,但至少能让首轮回答不那么空洞。

召回不准的问题更多出现在向量索引的调参不当。我们踩过的典型坑是文本长度差异太大造成的匹配偏差。长文本向量和短文本向量如果不做长度截断或归一化,语义距离的计算结果会偏向长度相近的文本,而不是含义相近的文本。解决方法是写入前把所有记忆片段做长度归一化,统一截成256字符以内的摘要,再进行向量化。这个改动看似不起眼,却让召回命中率提升了差不多十个百分点。

5.4 隐私与合规底线:哪些记忆不能碰

记忆系统天然会触碰个人信息,这是逃不开的课题。我们的原则是:能少存就少存,能脱敏就脱敏,能给用户控制权就给控制权。身份证号、银行卡、详细住址这类强敏感信息,一律不写入记忆,甚至在采集层就要做识别拦截。健康信息、政务信息等敏感类别,只在用户主动提供且明确授权时才允许进入情景记忆,并且使用时只保留必要描述,不保留原始隐私细节。

我们还在产品里加入了“记忆透明度”设计。用户在对话中随时可以问智能体“你记得关于我的哪些信息”,系统会把已存储的记忆清单以通俗语言展示出来。用户也可以一键清空某类记忆或全部记忆。这个设计虽然不能带来直接的业务指标,但对用户信任度和合规风险控制的价值非常大。很多从业者会忽略这一点,我强烈建议把记忆可视化放到需求优先级前列。一个连自己记住了什么都不愿意展示给用户的智能体,很难真正获得用户信赖。

结尾

最后再分享一点个人体会。在做“小度想想”这套记忆系统之前,我一直以为最难的是算法和存储架构,真正落地之后才发现,难点其实是“克制”。克制写入、克制召回数量、克制对用户隐私的过度挖掘,每一条克制都在为更好的长期体验让路。我们曾经因为召回命中率不够高而盲目加规则,结果系统变得格外“自以为是”,后来砍掉一半规则,效果反而好了。所以给正在做类似项目的朋友一个建议:先搭一个最简单的“画像 + 情景记忆”的闭环,跑通核心体验,再逐步加复杂策略。另外一个小技巧:给每一类记忆都留一个“用户可解释入口”,用户能看见的系统才更可信。这个领域还在快速演进,今天单条记忆的存取逻辑,说不定未来就会变成智能体之间协作的通用语言。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进