写在前面:

        在开发企业级AI知识库问答系统时,我遇到了一个普遍但容易被忽视的问题——多轮对话的上下文记忆。传统的RAG(检索增强生成)架构往往只处理单轮问答,用户每次提问都像是“第一次见面”,导致对话缺乏连贯性。为了解决这个问题,我在项目中设计了一套基于Redis + MySQL的混合上下文存储方案。本文将分享其中的技术选型、实现细节以及踩坑经验,希望能为同样在探索RAG工程化的朋友提供一些参考。

一、短期记忆与长期记忆:Redis + MySQL 分层架构

对话上下文的存储面临一个经典矛盾:既要快,又要省;既要全,又要准。如果将所有历史对话都存入Redis,随着对话轮数增加,内存消耗会急剧膨胀;如果只依赖MySQL,每次检索都需要查询数据库,响应延迟又会显著上升。

我的解决方案是分层存储:

  • 短期记忆层(Redis):存储最近10~20轮对话,TTL设置为1小时。这覆盖了绝大多数用户连续对话的场景,保证响应速度。Redis的List或String结构都可以实现,我选择了Hash + 时间戳排序的方式,方便按顺序拉取。

  • 长期记忆层(MySQL):新增conversation_context表,存储对话摘要、元数据以及每条消息的importance_score(重要性评分)。当Redis中的上下文过期或被淘汰后,系统会从MySQL中加载摘要信息,实现“记忆唤醒”。

这种设计的核心权衡在于:用Redis的短暂存储换速度,用MySQL的结构化存储换容量。实际压测中,Redis命中率稳定在85%以上,平均响应时间控制在150ms以内。

二、对话连贯性:智能滑动窗口与顺序保证

技术实现上,最大的坑是消息乱序。在高并发场景下,用户快速连续提问,后端服务可能因为异步处理导致消息写入顺序错乱。一旦顺序颠倒,AI生成的回答就会前言不搭后语。

我的处理策略包含三个层面:

  1. 前端时间戳 + 后端序列号:每条消息入库时记录服务端统一时间戳,并在会话内自增seq_id,查询时严格按照seq_id排序。

  2. 滑动窗口管理:窗口大小不是固定的。我会根据消息的importance_score动态调整——高重要性消息(如用户明确要求“记住这个设定”)保留更多轮数,低价值消息(如“嗯”、“好的”)提前丢弃。窗口最多保留20轮,最少不低于8轮。

  3. 格式化上下文:从Redis读取消息后,统一转换为role: content格式(例如user: 李白是谁?assistant: 李白是唐代诗人…),直接拼接到Prompt中。这一步看似简单,但要注意对特殊字符(换行、引号)的转义,避免破坏Prompt结构。

三、从冗长到简洁:优化AI回答格式,提升用户体验

早期版本中,AI的回答常常带着大段的“思考过程”:

需要说明的是:用户询问了李白的身份,根据历史对话可知用户对唐代文学感兴趣,因此我应该提供简洁但准确的介绍。结论:李白是唐代诗人,被后人誉为“诗仙”。

这种回答虽然逻辑完整,但用户阅读体验极差。问题根源在于系统提示词(System Prompt)中包含了“请逐步思考”的指令,同时上下文拼接方式导致模型倾向于输出解释性内容。

我做了两处改进:

  • 重构System Prompt:明确要求“直接给出最终答案,不要输出思考过程,不要使用‘需要说明的是’、‘综上所述’等过渡词”。

  • 上下文压缩:在拼接历史对话时,去除冗余的元数据(如时间戳、消息ID),只保留纯净的role: content对。同时,对于过长的历史消息(超过200字符),调用LLM进行摘要压缩,再存入上下文。

最终效果对比如下:

四、测试验证与数据库设计:小步快跑,持续迭代

功能测试是检验方案有效性的关键。我设计了三个核心用例:

  1. 上下文连贯性:用户问“你知道李白吗?” → AI正确回答。

  2. 上下文记忆:用户接着问“他有哪些诗词?” → AI基于上一轮的“李白”给出《将进酒》《静夜思》等,而不是反问“他指谁?”

  3. 回答格式:所有回答均无思考过程,直接简洁输出。

数据库层面,除了conversation_context表,我还为message表增加了importance_score字段(默认值0,范围0~1),并创建了复合索引 (conversation_id, seq_id, importance_score)。这个分数目前由规则引擎计算(例如用户重复提问同一主题时加分,消息长度过短时减分),未来可以接入小模型自动评估。

注:相关的SQL脚本已随项目发布,感兴趣的朋友可以直接参考 GitHub仓库 中的docs/schema.sql

五、后续改进:向量化检索与流式输出

目前的方案虽然稳定运行,但仍有四个值得深挖的方向:

  1. 流式输出:当前AI回答是一次性返回完整内容,长回答时用户等待焦虑。下一步将改造为SSE或WebSocket流式传输,首字延迟可降至300ms以内。

  2. 上下文压缩:对于超过20轮的长对话,即使只保留高重要性消息,Token消耗仍然较大。计划引入记忆蒸馏技术,定期调用LLM将历史对话压缩为结构化摘要(例如“用户偏好短答案”、“对Java并发感兴趣”等)。

  3. 向量化搜索:目前加载长期记忆时,仅通过importance_score排序截取。更优雅的做法是将历史对话片段向量化存入Milvus,根据当前问题语义检索相关历史,实现“联想式记忆”。

  4. 多轮对话优化:改进摘要生成算法,从简单的拼接抽取升级为图式记忆(Graph-based Memory),捕捉对话中实体间的隐含关系。

总结

在RAG系统中引入对话上下文存储,本质上是在内存开销、响应速度、体验连贯性之间寻找平衡。通过Redis构建短期记忆、MySQL沉淀长期记忆,并辅以智能滑动窗口和重要性评分,我成功解决了“AI失忆”问题。当然,工程没有银弹,这套方案在高并发、超长对话场景下仍有优化空间。未来随着向量检索和流式技术的成熟,我相信“有记忆、懂偏好”的AI助手会离我们越来越近。

最后,所有代码均已开源在 Enterprise-AI-Copilot,欢迎交流讨论。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐