首页 / 资讯中心 / 文章详情

Redis 接入 AI:大模型记忆层的架构设计与落地实践

Redis 接入 AI:大模型记忆层的架构设计与落地实践 ★ FEATURED ARTICLE
看到「Redis 正式接入 AI」这个标题估计很多人第一反应是搞缓存的老兵怎么跟大模型扯上关系了其实这个组合不是标题党它踩中的是当下所有大语言模型应用都绕不开的短板——AI 的「记忆问题」。大模型本身是相当「健忘」的而我们天天说 Redis 快、Redis 稳却很少意识到记忆这件事恰恰需要快读快写、需要自动遗忘、需要语义查找这几样恰好都是 Redis 的看家本领。这段时间我给几个大模型应用项目做架构改造核心工作之一就是给它们接上 Redis 作为记忆层。说实话把 Redis 用在 AI 场景里并不仅仅是加个向量索引那么肤浅它需要你重新理解「记忆」到底分几层、什么该存什么该忘、查询怎么做才不会崩。这篇文章就围绕「为什么 Redis 能解决 AI 记忆问题」以及「怎么落地一个能用的记忆层」来写适合正在做大模型应用、客服机器人、个人知识助手的工程师参考。1. 先搞清楚问题大模型的「记忆」为什么是硬伤1.1 大模型的健忘是设计使然大语言模型的本质是一个「无状态函数」给它一段输入它预测下一段输出仅此而已。它不记得你昨天问过什么不记得上个用户叫什么名字更不会主动记住「你说过你孩子在读初中」。如果你想让大模型表现得像有记忆唯一直接的办法就是把历史内容全部塞进每一次请求的上下文里。但这个做法有三个硬伤。第一上下文窗口是有限的。虽然现在模型动辄几十万 token 的窗口但你把整段历史对话、用户档案、业务数据全塞进去很快就把窗口挤爆。越长的上下文模型对中间信息的关注度越容易下降反而影响回答质量。第二成本会快速失控。大模型 API 基本按 token 计费每次请求把几万 token 的历史反复回传等于每聊一句都在为历史内容买单。我见过一个客服机器人项目改造前一个月仅上下文重复计费就占了总费用的四成。第三跨会话能力基本为零。用户今天问「发票怎么开」明天回来问「我昨天那个发票问题处理到哪了」。如果系统不额外存储模型只能一脸茫然。所以AI 的「记忆问题」本质上是个工程问题不是模型问题。你需要在模型之外给它配一个比上下文窗口更灵活的「外置记忆」。1.2 为什么偏偏是 Redis 来收拾这个局面解决记忆问题理论上什么数据库都能做但 AI 应用对记忆层的读写要求非常具体。记忆层要扛得住高频读写。用户和机器人聊一句可能要读三次记忆、写两次记忆QPS 一下就上来了。传统磁盘数据库在这种放大效应下很容易把延迟拉高。而 Redis 是内存数据库读写延迟是微秒到毫秒级这个底子决定了它能站在请求路径上而不拖后腿。记忆层要支持「自动遗忘」。人脑会忘事AI 的记忆层也必须能忘。Redis 的 TTL 过期机制天然就是「遗忘」的调度器。给短期记忆设置一个过期时间到点自动消失不用你写定时任务去清数据。记忆层要能「按语义查找」。用户不会每次都说出完全相同的关键词。今天说「发票」明天说「上次开发票那个事」系统得能找到同一件事。Redis 生态里的向量检索能力解决的就是这个——把文本变成向量用余弦相似度找「意思相近」的记忆。所以Redis 能接住 AI 记忆这个场景不是因为某一个功能强而是因为它把「快读快写」「自动过期」「持久化」「向量检索」这四块拼图凑齐了。打个不严谨的比方给 AI 配记忆Redis 既当随手能翻的便利贴又当长期存档的档案柜还自带一套按「大概意思」检索的索引卡。便利贴会过期档案柜不丢数据索引卡能模糊查找这正好覆盖了记忆系统的核心需求。2. AI 记忆体系的三大分层与选型逻辑2.1 第一层工作记忆管住当前会话工作记忆对应的是「这次对话里发生了什么」。它最明显的特征是短、快、不需要永久保留。我之前做智能客服项目时给工作记忆定的规则很简单每个会话用 Redis 的 List 结构存最近二十轮对话并设置 30 到 60 分钟的 TTL。用户在这个窗口内继续提问机器人能顺着上下文往下聊超过窗口没说话就从零开始相当于「把上一个话题忘掉」。用 List 存对话轮次非常顺手伪代码大致是这样import redis r redis.Redis(host127.0.0.1, port6379, decode_responsesTrue) def push_chat(user_id: str, role: str, content: str, ttl: int 3600): key fsession:{user_id} r.rpush(key, f{role}:{content}) # 只保留最近20条防止单个会话无限增长 r.ltrim(key, -20, -1) # 重置过期时间用户持续活跃就不会丢 r.expire(key, ttl)这段代码里有两个容易被忽略的细节。ltrim(key, -20, -1)是保尾去头只留最新二十轮避免长期会话把内存吃光。expire每次写入都重置一次只要用户还在持续对话这个会话就不会半路消失用户一旦离开TTL 自然让它过期这就是「自动遗忘」。顺带一提如果对话里带有图片、文件引用不要硬塞进 List。把内容指针或消息 ID 存进去实际内容单独放对象存储Redis 里只保留轻量引用否则 List 会越来越臃肿。2.2 第二层情景记忆跨会话的事实档案如果说工作记忆是临时草稿纸情景记忆就是正式的档案柜。它用来回答这一类问题「这个用户上个月买过什么」「他最喜欢的付款方式是什么」「他之前投诉过什么」。情景记忆必须持久化不能随便 TTL。Redis 的持久化机制在这里派上用场RDB 做定期快照AOF 做追加日志两者配合可以在进程重启后把记忆恢复回来。数据模型上我推荐优先按「业务事实」存储而不是按「原始聊天记录」存储。比较常用的做法是给每个用户维护一组结构化事实用 Hash 或者 RedisJSON 保存def save_user_fact(user_id: str, fact_type: str, fact_value: str): key fmem:fact:{user_id} r.hset(key, mapping{ fact_type: fact_value, }) def get_user_fact(user_id: str, fact_type: str): return r.hget(fmem:fact:{user_id}, fact_type)这里的关键点是不要相信模型能自己记住事实而是把「从对话里抽事实」变成一个独立流程。用户说「我上次投诉过物流太慢」系统在后台把「物流投诉: 处理中」这个结构写进 Hash下次用户再来问直接把这个事实取出来拼进提示词模型自然回答得准确。情景记忆的写入时机也值得讲。不要在用户请求的同步路径上做复杂的事实抽取那样会增加首字延迟。更稳妥的做法是把原始对话先推入 Redis Stream由后台异步任务消费调用大模型做摘要和事实抽取再回写情景记忆。用户的聊天响应走快路径记忆加工走慢路径两边互不拖累。2.3 第三层语义记忆用向量记住「意思」前两层解决的是「字面记忆」但很多场景需要「意思记忆」。例如用户第一次说「发票开错了」第二次说「上次的票要重开」这两句话没有共同关键词系统要能发现它们其实是同一件事。办法就是把文本映射成向量然后做相似度检索。Redis 生态里承担这部分的是向量索引能力。你可以把一条条事实先转成 embedding再存进 Redis 的 Hash并建立向量索引。查询时把用户当前问题转成向量在索引里做 KNN 搜索找到语义最接近的若干条记忆。语义记忆的定位和使用频率我用一张表来梳理记忆层级典型内容存放结构生命周期检索方式工作记忆当前会话最近对话List / Stream短TTL 控制按时间顺序读取情景记忆用户偏好、订单、投诉事实Hash / RedisJSON长持久化按字段精确读取语义记忆历史事件的向量表示Hash 向量索引长持久化向量相似度检索三层记忆不是各自孤立的。实际运行时工作记忆提供即时上下文情景记忆提供精准事实语义记忆负责「想起来还有这么回事」。三层配合基本能覆盖一个助手的全部记忆需求。3. 手把手落地把 Redis 改造成 AI 记忆层3.1 先规划 key 的命名空间很多人在 Redis 里写业务数据比较随意结果数据一多连自己都分不清哪块是哪块。做记忆层key 命名一定要在一开始定好规范。我的习惯是mem:记忆类型:业务域:用户ID。例如session:10001 当前会话的工作记忆 mem:fact:10001 用户事实档案 mem:summary:10001 历史摘要 mem:event:10001:1690000000 某条情景事件用户 ID 统一放在中间位置方便按前缀批量清理也方便后面做多租户隔离。如果你用的是 Redis Cluster还能靠 key 里的用户 ID 做哈希标签保证同一个用户的记忆落在同一个分片避免跨节点查询。3.2 建立向量索引接着搭建语义记忆的底座。假设我们统一用 1536 维的 embedding 模型把用户 ID 设为 TAG 字段文本内容设为 TEXT 字段embedding 设为 VECTOR 字段。命令类似FT.CREATE idx_mem_fact ON HASH PREFIX 1 mem:fact \ SCHEMA \ user_id TAG \ text TEXT \ embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE这段命令的意思对前缀为mem:fact:的 Hash 数据自动建立索引其中user_id用于精确过滤text用于普通关键词搜索embedding用 HNSW 算法建向量索引距离度量选余弦相似度。HNSW 是当前最常用的向量索引算法召回率和查询性能平衡得比较好。它有两个参数需要关注M控制每个节点的最大连接数越大越精准但内存和构建时间也越高EF_CONSTRUCTION控制建索引时的搜索广度越大索引质量越高。起步可以设M16、EF_CONSTRUCTION200数据量上来后再调。3.3 写入记忆与语义召回索引建好后写入一条事实记忆变得很直接。这里有个坑我一开始踩过向量是二进制数据如果用decode_responsesTrue的连接去写很容易出现类型问题。建议单独准备一个二进制连接专门处理向量字段。import redis import uuid import numpy as np # 普通文本操作走 decode 连接 r redis.Redis(host127.0.0.1, port6379, decode_responsesTrue) # 向量二进制操作建议单独用非 decode 连接 vconn redis.Redis(host127.0.0.1, port6379, decode_responsesFalse) # 这里是你实际用的 embedding 服务的封装 def get_embedding(text: str) - bytes: vec MY_EMBEDDING_SERVICE.embed(text) return np.array(vec, dtypenp.float32).tobytes() def save_fact(user_id: str, fact_text: str): fact_id uuid.uuid4().hex[:8] key fmem:fact:{user_id}:{fact_id} vconn.hset(key, mapping{ user_id: user_id, text: fact_text, embedding: get_embedding(fact_text), })查询的时候把用户当前问题转成向量然后做 KNN 搜索。记得一定要带上user_id的 TAG 过滤否则会跨用户召回我在初版系统里就吃过这个亏。def recall_fact(user_id: str, query: str, k: int 5): query_vec get_embedding(query) res r.ft(idx_mem_fact).search( fuser_id:{{{user_id}}}[KNN {k} embedding $qvec AS score], query_params{qvec: query_vec}, dialect4, ) facts [] for doc in res.docs: # RediSearch 返回的 score 是距离COSINE 下 score 越小越相似 similarity 1 - float(doc.score) facts.append({text: doc.text, similarity: similarity}) return facts有个细节容易看懵RediSearch 里 VECTOR 查询返回的score是距离不是相似度。余弦距离越小越相似当两条文本完全一样时距离为 0相似度为 1。所以代码里要换算成1 - score不然你会看到一个「相似度越低越好」的诡异结果。3.4 把检索到的记忆拼进提示词记忆层的数据结构再好最终也要喂给大模型才有效。我的做法是每次用户提问时并行从三层记忆里取数据然后组装成结构化的「记忆上下文」再加到系统提示词里。def build_memory_context(user_id: str, question: str): recent get_recent_sessions(user_id, 6) facts recall_fact(user_id, question, k3) summary get_most_recent_summary(user_id) context [] if summary: context.append(f[历史摘要] {summary}) if facts: for f in facts: context.append(f[相关记忆] {f[text]}) if recent: context.append(f[最近对话] {recent}) return \n.join(context)注意召回结果的拼接顺序。摘要放最前面因为它概括性最强相关记忆紧跟其后最近对话放最后因为它和当前问题的时间距离最近模型处理长文本时对后部信息的权重更高。这个顺序调整我在测试中明显感觉到回答相关性有提升。调用大模型时把记忆上下文拼进 system prompt你是一个智能客服助手。以下是关于当前用户的记忆信息请结合这些信息回答问题 [历史摘要] ... [相关记忆] ... [最近对话] ... 用户当前问题...这套流程跑通以后模型在回答「上次那个发票问题」这类跨会话问题时就不再是瞎猜了而是真正「想起了」之前的经历。4. 记忆治理别让「记住一切」变成灾难4.1 记忆不是原始日志最容易犯的错误是把所有原始对话一股脑写进记忆层。这样做有三个后果内存快速增长、检索噪音变大、token 成本失控。用户说过一万句话真正值得长期记住的可能就二十句。记忆层的价值在于提炼不在于存档。我在实际项目里把记忆加工链路设计成三步原始保留、摘要提炼、事实抽取。原始保留只做短期就是前面说的工作记忆TTL 一小时。摘要提炼是后台任务每累计十轮对话就调一次大模型生成一段 200 字以内的对话摘要覆盖写入mem:summary:user_id。事实抽取是从每轮对话里识别「用户偏好」「待办事项」「历史结论」这类值得长期保留的信息写入情景记忆。4.2 设计一套「遗忘策略」AI 记忆也要讲取舍不是越多越好。情景记忆虽然没有 TTL但应该设置容量上限和重要度分级。我给每个用户的事实档案设置上限为 50 条超过上限时按「重要度 最近访问时间」做综合评分分低的淘汰。这套逻辑本质上就是把 LRU 和人工优先级结合起来。常用遗忘策略可以这么设计工作记忆TTL 自动过期默认 30 到 60 分钟会话摘要保留最近 10 到 20 条摘要更早的摘要转成年度级总摘要事实档案单用户不超过 50 条按重要度和最近访问淘汰语义记忆每条向量记录保留来源信息来源摘要被删除时同步清理这背后有一个思路AI 助手不是你的私人档案管理员它更像是你的贴身助理。助理会记住重要的事也会合理地忘记无关紧要的事。4.3 成本与性能的权衡接入向量检索后内存压力是绕不开的话题。1536 维的 float32 向量单条大约占 6 KB一万条就是 60 MB百万条就是 6 GB。这是纯向量的开销还没算原始文本和索引结构。如果公司已经部署了 Redis且记忆量在千万条以内通常可以接受如果目标是上亿条向量还是建议考虑独立向量数据库。批量写入也能省不少时间。embedding 的到来是有网络开销的逐条调用接口很慢。我在项目里把历史记忆的向量化做成批量任务每批 100 条一次请求拿到所有向量然后批量写入 Redis。实测下来十万条历史的向量化时间从数小时压缩到二十分钟以内。缓存层面的优化更重要。用户的高频问题往往是同一批比如「怎么退款」「物流到哪了」。如果每次都用向量去 Kafka 式地检索再触发 embedding延迟和费用都不划算。更好的做法是加一层语义缓存把「问题和答案」也向量化存进 Redis新问题进来先算一次语义相似度命中高度相似的历史问题就直接返回缓存答案不再调大模型。这既是记忆也是省钱利器。5. 实测与踩坑一个客服机器人的记忆改造复盘5.1 改造前后的直观对比我用一个模拟客服机器人项目做过对比实验。改造前用户跨会话提问的重复率非常高很多用户会因为「你刚才不是说了吗」而流失。改造后机器人接入 Redis 记忆层三层记忆全部启用。运行两周后几个关键数据还挺能说明问题指标改造前改造后用户重复提问率约 23%约 7%首字回复延迟约 1.1 秒约 300 毫秒语义缓存命中率0%约 35%人工转接率约 18%约 10%首字延迟下降主要来自语义缓存高频问题直接命中缓存不需要等待模型推理。重复提问率下降则要感谢语义记忆它能跨会话把用户之前的问题捞出来。5.2 我在这套系统里真实踩过的坑再顺利的方案落地也免不了踩坑。我把自己在这套记忆层上遇到的问题整理成了速查表现象大概率原因解决办法FT.SEARCH报维度错误embedding 维度与索引 DIM 不一致检查模型输出维度和FT.CREATE对齐向量检索结果为空索引字段类型与写入类型不匹配确认 Hash 里字段名和 SCHEMA 完全一致跨用户召回混乱查询时没带 TAG 过滤KNN 查询用user_id:{...}前置过滤检索结果相似度异常把距离当相似度用用1 - score换算余弦相似度内存持续上涨TTL 没生效或长期记忆无上限给短期记忆设 TTL给档案设容量上限重启后记忆丢失持久化策略配置不当开启 AOF everysec并保留 RDB 快照维度错误是最常见的。我当时换过一次 embedding 模型从 768 维换成 1536 维但旧索引的 DIM 没改查询时直接报错。解决方法不是删除重建那么简单——要确认旧数据已经不再依赖旧索引再删旧索引、建新索引、批量重算向量。另一个值得提醒的是maxmemory-policy。如果 Redis 实例设置了noeviction记忆层数据写满后会直接写入失败对线上系统是致命的。我在测试环境就遇到过向量索引把内存占满业务写入全部报错。后来把记忆所在的实例单独拆分设置allkeys-lru同时给短期记忆和长期记忆使用不同的 key 前缀方便单独管理内存配额。5.3 两个很有价值的改进第一个是混合检索。纯向量检索在「语义相近但专有名词必须精确」的场景下不够稳。比如用户问「YF-200 订单退款」向量可能把它匹配到「其他订单退款」但订单号应该是精确匹配。我的方案是专有名词先做精确查询命中直接返回精确查询没命中再走向量语义召回。两者互补准确率明显提升。第二个是记忆回写闭环。之前的流程是「读记忆、回答问题」但回答完之后这段新对话并没有回流到记忆层。我加了一个后台任务每轮对话结束后把这轮对话推入 Stream异步抽取事实、更新摘要、写向量。这样记忆会随着对话持续生长而不是永远停留在第一次建档的状态。6. 一些个人体会跑了几个项目的记忆层改造后我最大的感受是Redis 解决 AI 记忆问题真正值钱的地方不在「向量排名有多精准」而在它所处的位置——请求链路的正中间能用极低的延迟把记忆喂给大模型。这一点很多独立的向量数据库反而不容易做到因为它们往往需要额外的网络跳转和数据同步。如果你也想给大模型应用加记忆我建议从语义缓存这个最小切口开始。它不需要复杂的记忆分层只需要把「问题向量 答案」存进 Redis再做一次相似度判断。投资小、见效快还能直接降低大模型调用的 API 费用。等跑通语义缓存再循序渐进地引入情景记忆和摘要体系会比一上来就搞全套三层记忆稳妥得多。另外始终记住一件事记忆不是越多越好。好的记忆层要学会「在恰当的时候想起在恰当的时候忘记」。Redis 的 TTL 给了我们「忘记」的手段但要不要忘、什么时候忘终究是设计上的取舍。我见过不少团队把记忆系统做成垃圾收集站最后模型被大量低质量记忆干扰反而不如不记。最后分享一个小技巧生产环境一定要把记忆系统做成可观测的。给每次记忆读取、缓存命中、向量召回打上日志出事时你能快速判断是记忆层的哪一环出了问题。我现在的习惯是任何「模型回答变差」的问题先看记忆召回日志再模型调优。多数情况下问题出在记忆没取对而不是模型不会答。
阅读完成 · 觉得有帮助?
咨询建站