你在 AI 超级智能体项目中如何实现对话记忆持久化?
- 标签:后端、编程导航
🏛 业务架构师
我的实现是**"框架抽象 + 自研存储实现"的组合**:
- 抽象层:Spring AI 的 ChatMemory 接口,按 conversationId 存取消息列表——业务代码只跟接口交互,未来换存储不动业务;
- 存储实现:MessageChatMemoryRepository 自定义实现,消息落 MySQL(会话表 + 消息表),热会话缓存到 Redis(最近活跃会话的窗口直接内存组装,冷会话回源);
- 窗口策略:每次取最近 20 条注入上下文,超出的老消息异步生成摘要存会话元数据——用户聊了一小时,模型依然"记得"最初的目标;
- 会话生命周期:创建/续聊/归档/删除(用户删除权限级联清理),会话与消息表按用户隔离——数据资产与合规责任同在。
三个工程细节(真实坑):
- 消息角色要完整持久化(system/user/assistant/tool 消息),尤其 tool 调用记录——恢复会话时缺了工具消息,模型会"精神错乱"(上下文自洽性被破坏);
- token 计量与会话绑定:每轮记录本次消耗——按会话/用户核算成本,支撑额度系统(免费额度用完引导付费),这是 AI 产品的商业闭环基础;
- 并发续聊的一致性:同一会话并发两条消息,消息序号用版本/时间戳保序——乱序的历史会让模型人格分裂。
业务价值总结:对话记忆持久化让"聊过的不用重讲"——这既是体验差异点,也是把单次工具变成长期用户关系的数据基础。
🏗 系统架构师
我的对话记忆持久化按"热缓存 + 全量库 + 摘要档案"三层设计:
1. 存储模型:消息表(id、conversation_id、role、content、token_count、create_time),按会话 ID 查询;会话表(conversation_id、user_id、title、summary、update_time)。标题与摘要由 LLM 异步生成(首条消息后生成会话标题)。
2. 读写路径:
- 写:用户/模型消息实时落库(流式响应在结束时整条落库,或分段落),同时写 Redis List 缓存活跃会话的最近 N 条(TTL 7 天);
- 读:组装上下文时优先 Redis(活跃会话 P99 几 ms),未命中回源 DB 加载并回填缓存。
3. 上下文组装策略:system(人设 + 会话摘要)+ 最近 20 条原文 + 当前问题。超出窗口的历史定期由后台任务调用便宜模型压缩成 summary 更新到会话表——原文滚动归档,摘要常驻,token 成本随对话长度增长放缓。
4. 为什么这么设计:
- DB 全量保证不丢、可审计、跨设备恢复;Redis 保证活跃会话组装快;
- 摘要层解决"长对话 token 爆炸"——百轮对话全文可能几万 token,摘要压到几百;
- 结构化关键信息(用户确认过的参数)单独抽取存储,防止摘要丢失关键事实。
演进方向:多表分库(按 user_id 分片)、向量化历史对话支持"回忆检索"(用户提到之前的事时按语义捞相关片段)——记忆从"最近窗口"进化到"语义记忆"。
💻 全栈开发专家
我的实现是 Spring AI 的 ChatMemory 接口 + 自研 MySQL Repository + 分层压缩策略:
1. 存储层:自定义 ChatMemoryRepository
java
public class JdbcChatMemoryRepository implements ChatMemoryRepository {
public List<String> findConversationIds() { ... }
public List<Message> findByConversationId(String convId) {
// SELECT role, content FROM chat_message WHERE conversation_id=?
// ORDER BY create_time —— 按需 LIMIT 最近 N 条
}
public void saveAll(String convId, List<Message> msgs) {
// INSERT 追加,幂等(消息id唯一)
}
}消息表 (id, conversation_id, role, content, tokens, create_time),role 区分 user/assistant/tool,工具调用记录也落库方便回放。
2. 读取策略(三层):
- 近 10 轮原文全量加载;
- 更早历史由定时任务生成会话摘要(LLM 压缩成 200 token 的 summary)存
chat_conversation.summary,拼在 system prompt 里; - 会话级缓存(Caffeine,会话活跃期间免查库)。
3. 工程上处理的坑:
- 窗口截断要保头保尾:system prompt 和最近几轮必须完整,中间历史才能压;
- 消息顺序与幂等:流式响应中途断线,只落完整 assistant 消息(以
[DONE]标记为准),避免半截消息污染上下文; - 并发写:同会话快速连续提问用会话级锁串行化;
- 成本核算:每条消息存 token 数,按会话/用户统计成本。