
导入先看这样一个场景前两天我用豆包查高考作文题。我说“豆包简单回答一下今年新高考二卷的语文作文要求写什么。”它给了我材料主旨和核心立意。我接着又问了一句“英语呢”就两个字但豆包准确地回答了新高考二卷的英语作文要求。它知道我在问什么——它记住了“新高考二卷”记住了“作文”。这个体验很自然自然到我们几乎不会去思考它为什么能做到。但其实让AI“记住”这件事远比看上去复杂。 而如何让RAG系统拥有像豆包那样的会话记忆能力正是这篇文章要探讨的问题。大模型的记忆真相每次请求都是失忆的很多人用过 ChatGPT、Kimi 这些产品觉得大模型天生就能记住对话内容。你跟它聊了十几轮它还记得第一轮你说了什么感觉像是在跟一个有记忆的智能体对话。但实际上大模型 API 的每次请求都是完全独立的。模型不会保存任何对话状态——它没有上一轮对话的概念没有这个用户之前问过什么的记忆甚至不知道你是谁。打个比方大模型就像一个极其聪明但患有严重失忆症的专家。每次你找他他都不记得你之前来过。你得把之前聊过的内容全部重新说一遍他才能接着聊。那最简单的记忆是怎么实现的答案很简单——把你之前所有的对话历史全部塞进 messages 数组每次请求都重新发给 API。模型看到了完整的对话历史所以显得有记忆实际上每次都是从头看一遍。POST https://api.deepseek.com/chat/completions Authorization: Bearer YOUR_API_KEY { model: deepseek-v4-flash, messages: [ {role: system, content: 你是一个高考作文辅导助手}, {role: user, content: 新高考二卷的作文要求是什么}, {role: assistant, content: 材料主旨是关于守住根本的古语...}, {role: user, content: 英语呢} // 这就是会话记忆发挥作用的地方 ], temperature: 0.3, max_tokens: 500, stream: false }但是这就会导致一些问题超出上下文窗口 有些模型的上下文窗口只有 4K 或 8K token塞不下这么多内容请求直接报错费用飙升 咱们的大模型都是按token计费的塞这么多内容考虑过钱包的感受吗所以会话记忆的核心问题不是要不要记住历史而是怎么在有限的Token预算内尽可能多地保留有用的历史信息 。会话记忆的五种策略策略一完整历史Full History原理每次调用API时把整个对话历史从第一条到最新一条全部塞进messages数组里。messages [ {role: user, content: 第一轮问题}, {role: assistant, content: 第一轮回答}, {role: user, content: 第二轮问题}, {role: assistant, content: 第二轮回答}, # ... 所有历史 {role: user, content: 第N轮问题} ]维度评价Token控制❌ 无控制随轮次线性增长信息保留✅ 完整零丢失实现复杂度⭐ 极低只是维护一个数组额外API调用无适用场景对话≤5轮的简单场景、Demo演示痛点第10轮对话时Token可能已经上万成本陡增甚至超出模型上下文窗口上限。策略二滑动窗口Sliding Window原理只保留最近K轮对话超出部分直接丢弃。# 只保留最近5轮10条消息 MAX_ROUNDS 5 messages full_history[-MAX_ROUNDS * 2:] # 每轮包含userassistant两条维度评价Token控制✅ 按轮数精确控制成本稳定信息保留⚠️ 丢失早期关键信息预算、实体、意图实现复杂度⭐ 极低切片即可额外API调用无适用场景大多数客服场景近期信息权重高痛点用户第1轮说“我的预算是5000元”第8轮问“那款红色的多少钱”——模型完全忘了5000这个关键约束因为已经被挤出去了。策略三Token截断Token Truncation原理不按轮数切而是按Token数量精确控制。历史太长时从最早的消息开始截断直到总Token数不超过阈值。维度评价Token控制✅ 按Token精确控制绝不超限信息保留⚠️ 丢失早期信息且每条消息长度不一截断粒度不可控实现复杂度⭐⭐ 中需要token计数工具如tiktoken额外API调用无适用场景消息长度差异大的场景对比滑动窗口如果用户前5轮都是一句话第6轮发了一篇2000字的长文滑动窗口按轮数切可能刚好把早期的关键信息丢掉Token截断可以按“真实占用”公平处理。策略四摘要压缩Summary Compression原理当对话超过一定轮数或Token阈值时调用LLM把旧对话压缩成一段摘要后续请求携带“历史摘要 最近几轮对话”。def compress_history(old_messages): prompt f 请将以下对话压缩为不超过200字的摘要必须保留 1. 用户提到的所有数字类信息预算、价格、数量 2. 用户提到的所有实体名称产品名、人名 3. 用户的明确意图或偏好 对话内容{old_messages} return llm(prompt) # 调用一次LLM生成摘要 # 后续请求 messages [ {role: system, content: f历史摘要{summary}}, # 最近3轮原始对话 ]维度评价Token控制✅ 大幅压缩成本可控信息保留⚠️ 保留关键信息但细节有损实现复杂度⭐⭐⭐ 高需要设计压缩Prompt、触发策略、更新机制额外API调用每次压缩1次LLM调用额外成本适用场景长对话、需要长期记忆的复杂场景痛点摘要越压越“虚”多次压缩后“5000元预算”变成“用户有预算约束”增量更新策略是覆盖还是合并策略五混合策略Hybrid Strategy原理分层管理短期保留最近N轮完整对话长期维护摘要或向量库按需组合。维度评价Token控制✅ 精确可控可配置各层占比信息保留✅ 长期摘要短期完整兼顾实现复杂度⭐⭐⭐⭐ 最高需设计协同机制额外API调用触发时1次摘要生成或每次向量检索适用场景生产级系统推荐混合策略的变体滑动窗口摘要滑动窗口向量检索三层结构System Prompt角色固定 长期摘要 短期滑动窗口一张表总结对比策略Token控制信息保留实现复杂度额外API调用一句话评价完整历史❌ 无控制✅ 完整⭐ 极低无短对话专用滑动窗口✅ 按轮数⚠️ 丢早期⭐ 极低无简单粗暴Token截断✅ 按Token⚠️ 丢早期⭐⭐ 中无公平但机械摘要压缩✅ 大幅压缩⚠️ 有损⭐⭐⭐ 高每次1次性价比之选混合策略✅ 精确可控✅ 兼顾⭐⭐⭐⭐ 最高触发时1次生产级标配会话记忆的存储方案对话历史需要一个地方存起来。不同场景下存储方案的选择也不一样。1、内存存储HashMap / ConcurrentHashMap最简单的方案用一个 Map 把每个会话的对话历史存在内存里。优点是简单快速读写都是内存操作。缺点也很明显服务重启后数据全丢只能单机使用对话多了内存占用会很大。适合开发调试和小规模部署。2、Redis 存储用 Redis 存储序列化后的消息列表天然支持过期清理。具体操作将消息列表序列化为 JSON 存入 Redis设置 30 分钟过期读取的时候就从Redis 取出并反序列化。优点分布式多个服务实例共享、高性能、自带过期机制。缺点需要序列化 / 反序列化Redis 重启也会丢数据除非开启持久化。适合生产环境。3、数据库存储用数据库表存储每条消息字段设计大概是这样优点持久化、可审计查看任意历史对话、可做数据分析统计热门问题、平均对话轮数等。缺点读写性能比内存和 Redis 低需要数据库连接管理。适合需要审计和数据分析的企业场景。4、选型建议维度内存Redis数据库读写性能极快纳秒快毫秒较慢毫秒~十毫秒持久化不支持可选AOF/RDB天然支持分布式不支持天然支持支持过期清理需自己实现原生支持TTL需定时任务实现复杂度极低低中适用场景开发调试生产环境审计 / 分析场景生产环境推荐方案Redis做主存储数据库做归档 。对话进行时消息存 Redis快速读写对话结束后异步写入 数据库持久化、审计。学习途径马丁老师