当 AI 应用开始“记住事情”,架构需要变化什么?
最近和几个做 AI 应用的朋友聊技术方案,聊来聊去都会撞上同一个话题:应用开始有记忆了。不是那种会话级的临时缓存,而是真正的、跨越多轮对话、跨越多天的记忆。这个需求一出现,原本跑得好好的架构就开始别扭。有的团队选择硬塞进 Redis,有的把记忆全量丢给向量库,有的直接在业务代码里维护一个全局变量——各有各的临时方案,但没人觉得踏实。
我自己的体会是,当 AI 应用从“无状态调用大模型接口”进化到“有状态地使用记忆”,架构层面的改动不是加个缓存那么简单。这背后涉及数据流方向、服务拆分逻辑、检索策略、甚至容错机制的整套调整。今天把这几个月折腾出来的经验整理一下,重点聊聊当 AI 应用开始“记住事情”,我们的架构到底要变化什么、为什么必须这么变,以及落地时会踩到哪些坑。
1. 先说清楚:“记忆”在技术层面到底指什么
聊架构之前,得先把概念对齐。很多人一听到“AI 应用有记忆”,脑子里浮现的还是人脑那样模糊的“记得以前聊过什么”。但从工程实现角度看,记忆是一个非常具体的、由不同存储和检索策略组合出来的能力。若这部分定义含糊,后面讨论架构必然一团乱。
1.1 三种记忆类型,背后是三种截然不同的技术需求
我在实际项目里会把 AI 应用的记忆拆成三类,类别不同,对架构的要求也完全不同。
第一类是短期会话记忆,指当前会话内、围绕本次任务产生的上下文信息。本质上就是 Prompt 里拼的那一大段历史消息。实现方式很直观,把聊天记录按时间顺序存下来,调用模型时按窗口截取最近 N 轮。以前很多应用直接存 Redis,设置一个过期时间,简单粗暴。但随着上下文窗口扩大、单轮携带的信息量暴涨,Redis 里的大 value 读写压力会很尴尬,而且这里还涉及 token 成本控制,不是单纯存储问题。
第二类是长期事实记忆,指跨会话保存的用户偏好、个人信息、项目背景等。比如用户上次提到“我儿子今年上小学三年级”,过了两周再次使用应用时,AI 应该还记得这个信息。实现上通常是两条线并行:一条是结构化数据,比如用户画像表、标签表,适合精确查询和规则过滤;另一条是向量化存储,把语义信息 embedding 后存进向量库,靠相似度检索命中。很多团队只做第二条线,结果项目一复杂就发现纯向量检索不够准,还得退回结构化存储做约束。
第三类是工作记忆,指 Agent 在执行多步骤任务时临时产生的、需要在多个工具调用之间传递的状态。比如 Agent 先查天气接口,再根据天气结果安排行程,这个“天气结果”就是工作记忆。这部分通常不需要持久化,但随着任务链拉长、并行分支增多,工作记忆的管理直接在代码里维护会导致状态散布在多个函数间,很难追踪。
1.2 为什么“有记忆”会让架构从舒适区里走出来
传统 Web 架构和微服务架构的核心假设是无状态。服务实例可以随意扩缩容、重启、负载均衡,因为每个请求都自带完整上下文——要么从请求体里拿参数,要么从数据库里查数据,服务本身不保存任何业务状态。这个假设让整个分布式体系变得简单可靠:任何节点挂了,换一台接着跑,数据不丢,状态不坏。
但 AI 应用一旦引入记忆,这个假设就被打破了。记忆天然是状态,而且是有依赖关系的状态。上一次对话的内容会影响这一次的回复,用户三天前的偏好会影响今天的推荐。若架构不主动设计状态的管理方式,就会出现:请求被负载均衡转发到不同实例后,实例 A 记得的东西实例 B 不知道,AI 表现像失忆了一样,体验断崖式下降。
一句话总结:记忆的引入,让 AI 应用从“无状态计算”被迫走向“有状态服务”。这个转变正是架构必须变化的根本原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 记忆对传统架构的三大冲击点
既然核心矛盾是有状态化了,那就要看传统架构具体在哪些环节承压。我梳理下来,冲击集中在服务层、数据层、以及调用链三个位置,每个位置都有典型症状。
2.1 服务层:无状态设计的墙开始处处透风
先看服务层。经典的微服务架构里,业务服务被设计成无状态,方便横向扩展。用户状态放 Session 中心或者 Token 里,订单状态放订单服务里,各司其职,互不干扰。
当 AI 应用开始有记忆,最直接的方案是给服务加一个“记忆中间件”,比如集成 Redis 或向量库。听起来简单,但实际改造时就会碰到问题:AI 应用的记忆不只是数据存取,还涉及记忆的写入时机、更新策略、版本管理。比如用户说了一句“我不喜欢吃辣”,这个信息什么时候写入长期记忆?是每轮对话都写入,还是等用户重复确认后才写入?写入时是追加一条新的,还是覆盖旧的?一旦需要动态决策,服务就不再是纯 IO 逻辑了,它变成了有决策逻辑的状态机。那些坚持无状态设计的服务层,在这里会非常别扭。
更麻烦的是,Agent 场景下的记忆经常不是单个服务内部状态,而是跨越多个服务的数据。比如一个旅游规划 Agent,需要同时读取用户偏好服务里的口味数据、行程服务里的历史安排、天气服务里的实时信息,再组合成一次回复。若记忆散落在各个服务里而没有统一的读取入口,组合成本会非常高,延迟也会成倍增加。
2.2 数据层:传统关系型数据库不再是万能解
再看数据层。很多团队早期做 AI 应用,第一反应是把记忆存进 MySQL,毕竟关系型数据库是万金油,什么都能存。但实际跑起来就发现不对劲。
记忆数据有几个特点传统数据库不太擅长应对。一是写入频率高但单条数据小——每轮对话都可能产生记忆写入,量大但都是短文本,传统库的写入压力主要在内核层面还好,但结合向量索引一起维护就比较头疼了。二是查询方式特殊——长期记忆检索很多时候不是“查某个 ID 的记录”,而是“找出和当前语境最相似的记录”,这需要向量相似度检索,MySQL 原生不擅长,社区方案多半是引入额外的向量索引插件或独立向量库。三是记忆之间存在关系——用户偏好可能关联某个项目、某个时间段,纯向量库表达不了这层关系,得靠额外的属性字段做过滤。
很多团队的做法是分两套存:关系型库存结构化信息,向量库存语义信息,查询时两边并行检索再合并结果。这个模式本身没问题,但它意味着数据层的拓扑变复杂了,事务边界不好划,一致性问题就跟着来了。写入向量库成功了但写 MySQL 失败了,记忆就不完整;两边同时写入但检索时结果矛盾了,AI 的表现就会诡异。
2.3 调用链:检索、排序、过滤变成了新瓶颈
第三个冲击点是调用链。传统 AI 应用的链路很简单:接收用户请求 → 拼 Prompt → 调模型 → 返回结果。记忆引入后,链路变成了:接收用户请求 → 检索记忆 → 排序和过滤 → 拼 Prompt → 调模型 → 返回结果,并且返回结果后可能还要触发记忆写入。
中间多了“检索记忆”这一大步,很多人一开始不重视,直接在主线程里同步跑一遍向量检索,结果延迟飚到用户无法接受。实测下来,向量检索本身不慢,但加上前置的权限过滤、记忆去重、与当前会话内容的相关性排序,整套流程耗时可能是模型调用的一半甚至更多。而且这部分逻辑嵌入在主链路里,一旦检索服务抖动,整个 AI 应用跟着抖,影响面被放大了。
这三个冲击点叠在一起,结论就很明显:需要重新设计一套能承载“记忆生命周期”的架构,而不是简单加几个中间件了事。
3. 架构调整的整体思路:给记忆一个明确的“位置”
搞清楚了痛点,下一步聊方案。调整思路用一句话概括:把记忆从服务的“附属品”变成架构的“一等公民”。记忆不应该是业务代码里顺手操作的变量,它应该有独立的存储、独立的生命周期管理、独立的访问接口,甚至独立的容量规划。
3.1 从“无状态服务 + 外部存储”升级为“记忆感知的分层架构”
具体来说,我会把系统拆解成几层来设计。
最底层是记忆存储层,负责不同类型记忆的持久化。短期会话记忆用高吞吐的 KV 存储,长期事实记忆拆成结构化数据库和向量库两条线,工作记忆则用临时存储。这一层的核心设计原则是“每个记忆类型都有最适合的存储引擎”,不要试图用一个库包打天下。
中间是记忆管理层,负责记忆的写入、更新、过期、关联和版本控制。这一层是新增出来的,传统架构里没有对应物。记忆不是简单 append 数据,它需要语义去重、冲突消解、时效性判断。比如用户先说了“我住在北京”,一周后又说“我最近搬到上海了”,管理层要能判断后者覆盖前者,而不是两条记录共存互相打架。
最上层是记忆访问层,给业务服务提供一个统一的 API,屏蔽底层存储差异。业务方调用 getMemory(userId, query) 就行,不需要关心这个请求是走数据库还是向量库。同时访问层负责权限控制和用量统计,防止记忆被越权访问。
这三层合在一起,相当于在业务逻辑和存储之间加了一道“记忆虚拟化层”,业务代码还是写业务,记忆的读写细节被隔离了。
3.2 为什么不能让业务服务直接读写存储
有人可能会问,搞这么一层抽象,是不是过度设计?直接让业务服务读写 Redis 和向量库,代码写起来还更短。我的答案是:短期内直接读写确实快,但长期一定会出问题,原因有三。
第一是类型切换的成本。今天你用的是 Redis 做向量检索,明天业务量大了想换一个带持久化能力的专用向量库,若所有业务代码都直接操作 Redis 客户端,换库意味着每个调用点都要改。有了访问层,只需要改这一层内部的实现。
第二是跨服务共享记忆的需求。AI 应用很少只有一个服务,通常是多个服务协作。用户偏好被推荐服务读取,被对话服务读取,被 Agent 调度服务读取。若每个服务都自己写一套记忆访问逻辑,维护效率太低,也容易出现各自为政的脏数据。统一入口是刚需。
第三是安全合规的硬约束。记忆里包含用户个人信息,访问必须可审计、可控制。访问层做成唯一入口之后,权限策略只需要在这一层实现一次,所有业务服务统一受控。如果各自直连存储,要确保每个服务都没漏洞,那工作量就上天了。
3.3 记忆读写路径的分离设计
另一个关键思路是读写路径分离。这里的分离不是指读写分离库,而是在架构上把“记忆如何写入”和“记忆如何被读取”设计成两条独立的处理流。
写入路径通常是异步的。用户和 AI 对话结束后,后台任务把对话中值得记住的信息抽取、清洗、写入存储。为什么不实时写?因为记忆抽取本身需要调用模型做信息提取,如果每轮对话都同步等模型返回再写入,用户感知的响应时间会明显变长。异步话术之后,主链路只做轻量级的必要写入(比如会话记录直接落库),抽取和结构化放到队列里慢慢处理。
读取路径则对延迟非常敏感。用户在提问,AI 需要在几百毫秒内从记忆库里捞出相关信息拼进 Prompt。这就要求读取路径上的每个环节都要性能可控:先做粗筛,再做精排,权限检查和过滤尽量下沉到存储层完成,减少不必要的数据传输。
两个路径独立设计之后,还有一个好处:可以分别做容量规划。写入高峰和读取高峰未必重合,异步化之后写路径可以削峰填谷,读路径可以按峰值预留资源。整体资源利用率比同步大锅饭高不少。
4. 实操落地:一份记忆感知架构的改造笔记
思路说得再多,最后都要落进代码和配置里。这里分享一个实际改造案例。这个项目原本是一个基于大模型的客服助手,无状态部署在 Kubernetes 上,对话记录存 Redis,过期时间 24 小时。客户提出新需求:用户再次访问时,AI 要记得上次聊过的内容,最好还能记住用户的基本偏好。下面是完整的改造过程。
4.1 改造第一步:理清记忆类型和生命周期
动手之前先梳理需求,明确系统里到底有哪些记忆。
- 会话记忆:最近 10 轮对话内容,生存周期为 24 小时,过期自动清理。
- 用户画像记忆:用户主动告知的偏好信息,如称呼、偏好语言风格、常问主题,长期保存。
- 业务上下文记忆:用户上次未完成的事项,比如“上次帮你生成的三篇文案还在草稿箱里”,保存 30 天。
梳理完之后建了一张表,把记忆类型、存储位置、生命周期、写入触发方式都定清楚。这一步看似事务性工作,但非常关键——很多团队跳过这步直接写代码,结果记忆类型和生命周期混在代码里,后面越搅越乱。
表格设计如下:
| 记忆类型 | 存储引擎 | 生命周期 | 写入触发方式 | 读取触发方式 |
|---|---|---|---|---|
| 会话记忆 | Redis(KV) | 24小时 | 每轮对话结束同步写入 | 每轮对话开始时读取 |
| 用户画像 | MySQL + 向量库 | 长期 | 后台异步抽取 | 每轮对话开始时查询 |
| 业务上下文 | MySQL | 30天 | 任务状态变更时写入 | 新任务开始时读取 |
4.2 改造第二步:搭建统一的记忆访问服务
第二步是写一个独立的记忆服务,把上面三类记忆的操作封装成 API。这个服务可以独立部署,也可以作为代码库被其他服务引用。第一次做建议独立部署,隔离性最好,排查问题也方便。
核心 API 设计如下:
saveSession(userId, sessionId, messages):保存会话记录,内部按窗口大小截断。getRecentSession(userId, limit):获取最近 N 轮会话。savePreference(userId, preference):保存一条用户偏好。searchMemory(userId, query, topK):混合检索用户相关的长期记忆,内部同时跑 MySQL 精确匹配和向量相似度检索,合并结果后统一返回。deleteExpiredMemory(userId, type):清理过期记忆。
这里最复杂的 API 就是 searchMemory。因为它要处理时序问题:用户记忆可能是多轮对话积累下来的,检索结果还要按时间衰减和相关性做综合排序。我的实现思路是召回阶段:先从向量库按相似度召回 Top 50 条,再从结构化库按标签精确匹配召回 Top 20 条,然后合并在服务端做过滤、去重、按时间和相关性加权重新排序,最后取 Top 10 返回。这个流程看起来繁琐,但实测效果远好于单路召回。
4.3 改造第三步:主链路集成与 Prompt 组装
服务层改造完后,接着改对话主链路。原来代码长这样:
python复制def handle_message(user_id, user_message):
history = get_redis_history(user_id)
prompt = build_prompt(history, user_message)
response = call_llm(prompt)
save_redis_history(user_id, user_message, response)
return response
改造后长这样:
python复制def handle_message(user_id, user_message):
# 并行读取会话记忆与长期记忆,减少主链路耗时
recent_session, long_term_memory = asyncio.gather(
memory_service.get_recent_session(user_id, limit=10),
memory_service.search_memory(user_id, user_message, top_k=10)
)
prompt = build_prompt(recent_session, long_term_memory, user_message)
response = call_llm(prompt)
# 会话记录同步写,用户画像异步抽取
asyncio.create_task(memory_service.save_session(user_id, user_message, response))
asyncio.create_task(background_extract_preference(user_id, user_message, response))
return response
改动核心有两个:一是读取路径上并行调用,会话记录和长期记忆同时查,主链路延迟不会叠加;二是写入路径异步化,会话保存和偏好抽取都不阻塞用户响应。这里用 asyncio.create_task 做异步任务调度的方式比较简单,生产环境建议替换成消息队列,防止任务丢失。
build_prompt 函数里也很讲究,记忆不是直接拼接到 Prompt 末尾,而是按角色和相关性组织。基本规则是:长期记忆放在系统提示词之后,作为“关于用户的事实背景”;会话记忆放在用户消息之前,保持对话连贯性。这样模型能清楚区分“这是用户的历史事实”和“这是最近的对话内容”,回复质量会高不少。
4.4 改造第四步:异步抽取管道的实现细节
最后看一下后台抽取的实现。这个模块把用户消息和 AI 回复发送给一个抽取专用模型,让它输出结构化偏好信息。我用的是简单的提示词加 JSON 输出框架,但有几个细节值得注意。
一是抽取频率要限流。不是每轮对话都需要抽取,如果连续几轮都是“嗯”“好的”这类对话,强行抽取只会浪费模型调用费。我会在前端判断信息增益,用户消息长度小于 10 个字符且与上下文高相似的,直接跳过抽取。
二是冲突处理策略。抽取出来的偏好信息如果和库里的旧信息矛盾,需要策略决定覆盖还是保留。我的规则是双条件判断:新信息明确包含否定词(比如“我不再喜欢”),且时间上是最近对话产生的,则执行覆盖;否则追加为一条新记录,并在检索时通过时间衰减让新信息权重更高。
三是可观测性。每个抽取任务要记录抽取模型返回的结构化结果、置信度、冲突处理结果,便于后面复盘。一旦发现抽取质量下滑,可以及时调整提示词或切换模型版本。这个模块看着简单,但它会长期运行在后台,不可观测等于埋雷。
5. 常见问题与排查技巧实录
这几个月改造下来,踩了不少坑,挑几个有代表性的问题分享出来,希望能帮大家少走弯路。
5.1 记忆串号:最隐蔽、最危险的问题
第一个坑是记忆串号。系统上线后有一阵子经常收到用户投诉,说 AI 莫名其妙提到一些他从来没聊过的话题。排查后发现是用户 ID 键冲突——测试环境的数据污染了生产环境的缓存键,加上代码里在多处地方硬编码了用户 ID 的拼接逻辑,有一处没加命名空间前缀,导致测试数据被当成生产数据读出来了。
排查这个问题的过程非常痛苦,因为记忆数据不像业务流水有明确的事务关联,出错时症状又特别“智能”——AI 会说一些看似合理但完全不属于当前用户的内容,用户第一反应是“你们 AI 疯了”,不会想到是记忆串了。后来我写了一个诊断脚本,随机抽取一批用户 ID,检查他们记忆库里的数据是否存在异常交叉引用,才定位到问题。
从此之后,我把记忆服务的键设计规范列为代码评审必查项。统一要求:mem:{userId}:{type}:{bizId},所有拼接必须走封装好的方法,不让业务代码直接操作字符串。这个教训值一个事故报告的份量。
5.2 检索召回太少:向量检索的 TopK 陷阱
第二个常见问题是检索召回量太少导致 AI“失忆”。一开始我图省事,向量检索 TopK 设成 3,想着用户画像嘛,命中几条最相关的就够了。上线后发现用户连续问几个不同领域的问题时,AI 经常对更早的偏好“失忆”——因为 TopK 太小,只有最近的那几条偏好被捞出来了。
后来我把召回策略改成两阶段:召回阶段 TopK 设大一点(比如 50 条),服务端做重排序后再截断到 10 条。重排序逻辑里加上了多样性约束:来自不同主题边的候选各保留至少一条,防止偏好单一化。这个改动见效非常明显,用户明显感觉 AI“懂得更多了”。
另外要注意向量库的索引参数。有些开源向量库默认的距离度量是内积,如果你的 embedding 模型是余弦相似度训练的,需要显式指定度量方式,否则召回相关性会飘忽不定。
5.3 写入延迟引起的主链路抖动
第三个坑是异步抽取写得不够“异步”。第一版抽取管道是在同一个进程里直接调用模型接口,用了线程池,表面上不阻塞主请求,但线程池满时,模型接口的耗时仍然会反压到主服务,导致响应时间飙高。故障表现是用户侧偶发的长时间等待,非常难追。
排查手段是看线程池的活跃线程数和等待队列长度,发现高峰期线程池打满,大量任务在排队,并且排队时间传导到了主链路。修法是改造成独立任务队列,进程内队列换成了 Redis Stream,消费者单独部署成独立服务,队列积压时只影响抽取时效,不再影响对话主链路。
这个坑提醒我一个原则:异步化不是用了 async 关键字就算完事,要看任务是否真正从执行路径上解耦了。只要共享同一个进程资源,就没做到真正的隔离。
5.4 记忆数据膨胀导致成本失控
最后是成本问题。记忆抽取每轮对话都要调用抽取模型,抽取模型虽然参数不大,但日活用户一上来,调用量还是很可观的。刚开始没做预算控制,月底账单出来吓了一跳。
现在我的做法是为抽取服务设置最大并发数和每日调用预算,超过预算自动降级为只保存原始会话文本,不抽取结构化偏好。同时对话记录按 30 天做冷热分层,超过 30 天的数据转存到低成本存储,不再参与实时检索。这套策略跑下来成本可控在一开始的四分之一左右,用户的记忆体验没有明显下降。
6. 架构演进路径:从单体改造到全套 Agent 基础设施
前面聊的是单个应用的改造方案,但行业里很多团队已经在往更远处走了。AI 应用的记忆能力越来越强,一些基于 Agent 的应用已经出现了一些全新的基础设施需求。
6.1 从“记忆服务”到“记忆网络”
单一应用有单一应用的记忆架构,但当一个企业里有多个 AI 应用协同工作时,问题就复杂了。比如 CRM 系统里的 AI 助手、客服系统里的 AI 客服、营销系统里的 AI 写手,它们各自有各自的记忆,但服务的是同一个用户。
继续各存各的,就会出现人格分裂:用户在 CRM 里说过“我是做跨境电商的”,到了客服系统里 AI 依然问“您是做什么行业的”。用户视角下,他在和同一个品牌对话,但企业视角下,是三个互不相通的 AI 在接力。
解法是把记忆存储从应用级提升到组织级,做一个跨应用的统一记忆网络。所有应用通过同一个记忆访问层读写用户记忆,记忆数据按领域和用途分层,不同应用按权限读取。这个架构听起来很美,实现难度也很大,尤其是权限管理和数据归属的设计,牵扯到部门墙和合规边界,不是纯技术能解决的。但方向上已经有团队在探索了,我判断这是下一阶段的竞争焦点。
6.2 记忆版本化与回溯能力
另一个值得关注的方向是记忆的版本化。目前的实现基本是“新信息覆盖旧信息”,覆盖之后旧信息就丢了。但用户偏好是会变的,而且变化可能需要回溯。
设想一个场景:用户上个月说“我最近在控制咖啡因摄入”,这周又说“我重新开始喝黑咖啡了”。新的记忆覆盖旧的,AI 开始推荐咖啡。但系统需要知道“用户曾经过敏/戒断过”,才能在这个月给出“逐步恢复,不建议过量”的贴心提醒。这需要的是记忆的完整历史,而不是最新快照。
我在架构里预留了记忆变更日志,每次偏好覆盖都记录前后值和时间戳。短期看这个日志只是用来排查问题,长期看它就是构建“理解用户成长轨迹”能力的数据底座。建议大家在设计存储时不要把表结构写死,一定要留出变更加载的空间。
6.3 可解释性与人工审核机制
最后聊聊可解释性。记忆是黑盒,AI 模型也是黑盒,两个黑盒叠在一起,出问题时非常可怕。用户投诉“AI 胡说八道”,你无法判断是模型幻觉、记忆检索错误、还是记忆本身写脏了。
运行一段时间后,我的经验是必须建立记忆的人肉巡检机制。具体做法是:用一套独立的模型定期抽样检查用户记忆库中的数据是否与原始对话一致,标注疑似错误样本,推送给运营人员复核。听起来费人力,但在金融、医疗等敏感行业,这个环节不能省。记忆数据的准确性直接影响后续所有 AI 交互的质量,这个基础打不牢,上层什么花活都是白搭。
这个方向往后走,可能会出现专门做“记忆审计”的工具和服务。现阶段我们靠的是抽查和规则,但需求已经很明确了。
最后再说一点个人体会。AI 应用从“对话即用即走”走向“越用越懂你”,这条路的本质是把 AI 从一个无状态的计算工具变成一个持续学习和适应用户的伙伴。这个过程里,架构师的角色也在变——不再是简单地堆积中间件,而是要像设计用户体验一样设计整套记忆流转机制。每次架构调整,都应该先问自己:用户感受到的“被记住”是不是真实的、可靠的、可追溯的?答案如果是肯定的,这套架构就是有生命力的。
