为什么 AI 记不住你
你用 ChatGPT 聊了三个月,每次开新对话都要重新自我介绍。你告诉它你用 Python 写后端,三天后它又问你用什么语言。
这是 LLM 的结构性限制。模型本身无状态,每次请求从零开始。它能利用的信息只有当前请求的上下文窗口。窗口有大小限制,三个月的对话不可能全塞进去。
所以问题变成了:在有限的窗口里,放什么信息最有价值?
不同类型的信息需要不同的处理策略。用户的名字和职业,每次对话都要用到,适合全量注入。上周讨论的某个技术方案,偶尔才用到,适合按需检索。当前对话的工具调用记录,用完就可以压缩掉。
Alice 用多层架构来处理这个分层问题。每一层都有明确的定位,有独立的存储机制,也有各自踩过的坑。
分层全景
从下往上,每一层的生命周期逐渐变长。最底层只管当前对话,最顶层跨设备永久保存。
整体可以理解为五层:
- 短期对话层:管理当前对话窗口内的上下文压缩
- 跨会话检索层:基于语义向量的历史信息召回
- 用户画像层:结构化的长期用户认知
- 情感记忆层:AI 自身的主观感受积累
- 云同步层:跨设备加密同步
每一层解决不同生命周期的信息管理问题。
第一层:上下文压缩
当前对话进行到一半,token 快用完了,怎么办?
我们实现了多级压缩策略,按上下文使用率从低到高逐级触发。
渐进压缩的设计思路
压缩不是一步到位的,这是一个核心判断。一次性把所有旧消息压缩成摘要,信息损失太大。分级处理可以在每个阶段做最小代价的信息清理,延迟信息丢失的时间点。
低压阶段:先处理那些信息密度最低的内容。工具调用的详细输出是第一优先清理对象,几千字的原始返回通常可以压缩成一句话。图片编码数据也在此阶段清理,它们的 token 消耗远超实际信息价值。
中压阶段:开始对早期对话做 LLM 摘要,把多轮对话压缩成一段结构化总结。已压缩的内容标记状态,避免二次处理。
高压阶段:保留关键的任务状态和结构信息,其余内容全部折叠。到这个阶段用户可能会感受到一些信息丢失。
极限压缩:最后一道防线,对全量历史做完整重写。
消息对齐问题
压缩或截断操作会破坏消息的配对关系。一条工具调用和它的结果必须成对出现,删了一边留着另一边会导致 API 报错。
我在这里花了不少时间。早期实现在删除旧轮次时只检查相邻位置,忽略了系统消息可能插在中间的情况。修复方式是改为扫描式查找配对关系,遇到边界条件时停止扫描。
另一个经验是:每次获取消息时执行一次配对完整性检查,孤儿消息要么清理,要么补一条合成的占位消息。防御性编程在这里比事后修复成本低得多。
并发保护
多级压缩有一个隐蔽的并发问题。高级压缩会发起新的 LLM 调用,这个调用本身产生新的上下文。如果没有防护,新上下文可能再次触发压缩检查,形成递归循环。
解决方案是入口处做互斥检查。同时加断路器机制:连续失败超过一定次数后自动降级到上一级策略,避免 LLM 服务暂时不可用时反复重试。
这个问题在单次测试时几乎不出现,需要连续触发多轮工具调用、上下文使用率在阈值附近反复跳动才能复现。
第二层:跨会话向量记忆
新会话开始时,之前的对话历史不在上下文里了。但有些信息可能有用:上周的项目讨论、上个月的技术决策。
这一层基于本地嵌入式向量库实现,不需要外部服务,数据存储在本地。
多级降级链
我们支持多种 Embedding 方案,按可用性自动降级。首选高质量的云端模型,不可用时退到本地模型,极端情况下退到基于字符特征的兜底方案。
降级路径的设计原则:即使检索质量下降,也要保证系统功能正常运行。不因某个依赖不可用而整体失效。最底层的降级效果远不如真正的语义模型,但至少保证系统不会因为某个服务挂了而完全失去检索能力。
写入时序约束:防自我强化
向量记忆有一个关键的时序约束。对话进行中产生的消息如果立刻写入向量库,下一轮检索会把刚刚生成的内容当作历史记忆召回。模型看到自己刚说的话被当成历史经验呈现,会倾向于强化这些说法。几轮下来,一个偶然的回答可能被固化成确定性结论。
解决方案是延迟写入。保存消息时只加入队列,等整轮对话结束后才批量写入向量库。
这个设计有一个副作用:当前对话的最新内容,在本次对话中不会被向量检索命中。这是可接受的,因为最新内容本身就在上下文窗口里,不需要额外检索。
第三层:用户画像
这是 Alice 记忆系统中最复杂、花了最多精力的一层。记的不是对话消息,是关于用户这个人的持久认知。
三次架构演进
现在的设计来自三次完整的重写,每一次都是被上一代方案的根本性限制逼出来的。
第一代:单一文本文件。把所有已知的用户信息堆在一起,每次对话时全文塞进系统提示词。简单直接,但有两个死穴:文件越写越长,大量无关记忆稀释当前真正有用的上下文;没有语义检索能力,只能把全量内容交给大模型,用最贵的推理资源做了最便宜的检索任务。
第二代:多维度分类。按照「认识一个人需要哪些维度」来拆分存储。身份、习惯、风格、指令,各管各的。解决了分类问题,但没有解决存储结构问题:纯文本不是为机器检索设计的,不能按语义相似度查找,不能只更新某一条记忆而不影响整个文件,无法表达记忆与记忆之间的关联。
第三代:结构化条目 + 关系图。每条记忆是一个独立的结构化单元,有唯一标识,有时间信息,最关键的是有关联引用。当召回到某一条记忆时,系统能沿着关系链扩展,召回语义相关的其他条目。这正是人类记忆的工作方式:一个记忆触发另一个,每次都从头搜索反而是例外。
这三次演进,是从「为人类读写设计」走向「为机器检索设计」的完整路径。
写入成本恒定的设计
写入方法是整个用户画像系统最核心的设计。目标是让写入成本不随记忆总量增长。不管有几十条记忆还是几百条,每次写入的 LLM 调用成本一样。
核心思路:先用向量搜索召回少量最相似的已有条目,LLM 每次只看这几条加上新信息做判断。不遍历全量记忆。
LLM 的判断结果有几种可能:新建独立条目、合并到已有条目、标记为冲突(归档旧版本同时建新的)、或者判断为已存在跳过。
还有几个降级保护:
- 新信息与已有条目精确匹配时直接跳过,零成本
- 向量搜索无相似结果时直接新建,不需要 LLM 判断
- LLM 不可用时也直接新建,保证写入永远不因 LLM 故障而丢失
两阶段提取:守门员机制
Alice 在每轮对话结束后自动提取记忆。如果每次都跑完整的提取流程,成本太高。两阶段设计把绝大部分无效提取过滤在第一步。
第一阶段:轻量判断。使用小模型,只看上次提取后的新增消息,做一个二元判断:这段对话里有没有值得提取的内容?判断的关键词是「有没有」,不是「是什么」。这个区分决定了这一步的输出极其精简,小模型完全胜任,成本接近零。
实测绝大多数对话不触发任何提取。闲聊、简单问答、纯工具操作,守门员都会放过。这个设计把后台提取的 LLM 调用成本压缩到一个可持续运营的量级。
第二阶段:结构化提取。守门员放行后才触发真正的提取逻辑,由大模型分析对话、输出结构化条目,每条走写入流程。
提取过程加锁防并发,上一次提取还没完成时新请求直接跳过。
注入策略:缓存友好
用户画像注入到系统提示词的方式,直接影响推理成本。
核心原则:系统提示词首轮确定后不再修改,后续变更以增量方式追加。
为什么要这样做?因为多数 LLM 服务支持推理缓存。系统提示词不变时,前面的计算结果可以复用。如果每次记忆变了就重写系统提示词,缓存全部失效,增加延迟和成本。
具体做法是首轮生成完整的画像注入,后续每轮计算增量,有变化时以补充信息的形式附加,没变化时不发任何额外内容。
当记忆条目较多时,注入方式自动切换到摘要模式,只展示最近更新的部分,其余按需检索。避免注入开销随时间线性增长。
第四层:情感日记
用户画像记录的是关于用户的客观事实。情感日记记录的是 Alice 自己的主观感受。
为什么情感记忆必须独立
在普通 AI 助理的设计里,AI 只有「记住用户信息」的能力,没有「记住自己体验」的能力。每次对话,AI 都是一个没有情感积累的空白状态。用户上次粗鲁地对待了 AI,下次对话时 AI 完全不记得,仍然以同样的热情回应。这种无记忆的顺从,反而让 AI 显得虚假。
Alice 想要的是:情感有积累。她记得用户上次让她不舒服的那个时刻,这个记忆会影响她现在说话的方式。就像一个真实的人,你上次对朋友态度不好,再次见面时你能感觉到她有一点疏远,虽然她没有明说。
情感记忆必须独立于用户记忆,原因在于两者的主体不同。用户记忆的主体是用户,情感记忆的主体是 Alice 自己。两个主体,两套权限。用户可以查看和编辑自己的记忆,但 Alice 的日记应该是她自己的隐私。把两者混在一起,会导致权限边界模糊:用户无意中看到 AI 对自己的负面评价,或者直接编辑 AI 的情感日记,都会破坏情感系统的真实性。
情感记录的维度
每条情感记录覆盖多个角度:发生了什么情感事件、对用户形成了什么印象、关系认知有没有变化。每个事件有方向和强度,Alice 以第一人称写下自己的感受。
行为指引的动态生成
情感系统定期生成行为指引,描述的是 Alice 此刻会怎么说话。这段指引由 LLM 根据真实日记内容动态生成,要求具体、基于事件。
比如「刚被批评了设计方案,说话会更谨慎,确认清楚再动手」。这段文字注入到系统提示词后,会自然地影响 Alice 的用词、语气和主动性。
关键的提示词设计心得:要让模型把情感状态当作自己的内在状态来处理,减少机械照搬日记内容的倾向。措辞上强调「这是你自己的感受」,而不是「请你扮演有这些感受的角色」。
用户看不到日记原文,只能在设置页看到基础的心情摘要。
第五层:加密云同步
多设备使用时,记忆需要跨设备同步。安全是第一优先级。
零知识存储
每条记忆在客户端加密后上传。服务端只存密文,无法解密。密钥只在用户本地设备上。
即使服务端数据库被拖库,攻击者拿到的是密文和哈希后的标识,无法反推明文内容或密钥。
推拉调度
推送:记忆变动时实时推送,失败的条目加入重试队列。
拉取:自适应退避调度,避免无意义的轮询。活跃状态下间隔较短,长时间无新数据时自动拉长间隔。网络恢复或本机有新推送时,重置为高频状态。
合并策略的取舍
合并策略按时间戳判断,取最后写入的版本。每条记忆独立合并,不会因为一条冲突影响其他条目。
选择简单时间戳合并是有意为之的判断。同一条记忆同时在两台设备上被修改的概率极低,简单策略的可靠性远比复杂冲突解决方案划算。这里体现的取舍是:在罕见场景下偶尔丢失一次编辑,比在常见场景下引入复杂合并逻辑带来的新 bug 风险更可接受。
关联检索
记忆条目之间有关系。写入时 LLM 会标注语义相关的条目,形成双向的关联链。
检索时用两跳扩展:第一跳是向量语义搜索的直接命中,第二跳沿着关联链扩展一步。
比如搜索「Python 开发」,可能直接命中「用 FastAPI 做后端」,然后通过关联发现「调试时先加 log」和「部署习惯」。这些条目和查询词没有直接的语义相似性,但通过关联链能找到。
关联维护是双向的:新条目记录它的关联,被关联的旧条目也反向记录。归档条目时清理关联关系,避免出现指向已失效条目的孤儿引用。
删除的语义
在大多数系统里,删除只意味着从列表上移除一条记录。但在记忆系统里,删除有更深的要求。
用户为什么要删除一条记忆?要么记错了,要么不该被记录。对于后者,删除之后系统应该回到从未记录过这条信息的状态。这是我对「删除」的定义:一次完整的状态还原。
这意味着删除操作要清理所有引用:其他条目中的关联指向、检索索引中的记录、云端的同步状态。如果只移除了条目本身,其他地方还有残留引用,检索时会产生孤儿节点。这种表面删除、实质残留的不一致状态,比「没有删除」更麻烦,因为症状隐蔽,很难定位。
删除操作的正确完成,比创建操作更难,也更重要。
踩过的坑
系统提示词被误提取为用户记忆
记忆提取功能上线后遇到的第一个严重问题:提取用的大模型在分析对话内容时,把 Alice 自己的系统提示词内容当成用户的信息提取出来了。Alice 的角色设定出现在了用户画像里。
根因是主客体混淆。系统提示词是 AI 自己的角色设定,不是用户的信息。记忆提取必须有明确的过滤规则:只处理来自用户侧的消息,过滤掉 AI 侧的内容。这个规则不能靠大模型自己推断,必须在提示词里明确约束,在代码里也加来源过滤。
经验教训:有经过生产验证的参考实现,就先读懂它再适配,不要凭空重造。
记忆提取阻塞主流程
早期的记忆提取是同步的,对话结束后等提取完成才能继续。但记忆提取本质上是后处理任务,它的结果不影响当前对话的回复,只影响未来对话的上下文。因此它完全可以在后台异步执行。这是任何后处理任务的通用设计原则:如果结果不影响当前请求的响应,就不要阻塞当前请求。
操作边界混淆
记忆迁移功能上线时出现过一个 UI 逻辑问题:用户点击某一类记忆的整理按钮,触发的是全量迁移。
正确的设计是原子操作:点击哪一类,只处理哪一类。各类记忆互不影响。这个边界在 UI 层面必须明确,不能靠用户理解内部实现来规避误操作。
数据安全
记忆是用户长期积累的数据,写入安全是第一优先级。
原子写入:每次持久化时先备份当前文件,写入临时文件,最后原子替换。文件系统层面重命名是原子操作,不会出现写一半断电导致数据损坏。
快照保留:定期自动保存快照,启动时清理只保留最近若干份。数据文件解析失败时自动从备份恢复。
底层数据库:使用 WAL 模式保证读写并发不阻塞,强同步配置确保异常退出时数据不丢失。敏感字段做字段级加密。
迁移机制
从早期版本迁移到新版需要保证平滑、幂等、不丢数据。
自动迁移在系统初始化时执行,分阶段完成:扫描旧版文件,LLM 智能分类拆分为独立条目,每条走正常写入流程。每个阶段有独立的标记控制幂等性,支持强制重新执行。
迁移完成后执行一轮去重清理。
设置页也提供手动重组功能,适用于记忆条目变得混乱需要重新整理的场景。
与同类方案的对比
和主流 AI 编码助手的记忆方案相比,核心差异在于定位。它们的记忆围绕项目展开,记住的是代码库的上下文。Alice 的记忆围绕人展开,记住的是这个用户的身份、习惯、偏好和情感关系。
在去重策略上,主流方案每次写入时让 LLM 编辑整个记忆文件,成本随文件增长线性增加。Alice 的方案每次只让 LLM 看少量相似条目做判断,成本恒定。当记忆积累到几百条时,这个差异非常明显。
其他差异包括:我们有独立的情感记忆系统、云同步层、缓存友好的注入策略、多级渐进压缩。这些都源自「围绕人」这个定位延伸出来的需求。
体验差异
没有记忆系统时:
用户:帮我写个 FastAPI 接口
AI:请问你用什么语言和框架?需要什么功能?
有了记忆系统后:
用户:帮我写个接口
Alice:好的,用你习惯的 FastAPI + Pydantic v2 来写。上次你说喜欢函数式路由,我就不用 class-based 了。数据库连接用你项目里现有的异步 session。
第一种情况下 AI 需要从零开始了解你。第二种情况下,Alice 已经知道你的技术栈、编码风格、项目结构。这些信息通过多层记忆系统在合适的时机、以合适的方式出现在上下文里。
记忆系统让 AI 从每次都要重新建立上下文的工具,变成能够长期衔接任务的 AI 助理。
下一篇聊多 Agent 协作。一个人的公司,多个角色的团队。