Logo车 专
返回项目列表

会话记忆架构设计

架构记忆管理摘要压缩Token优化

实现可插拔式会话记忆组件,支持无记忆、滑动窗口、增量摘要压缩三种策略灵活切换,结合数据库持久化,在 Token 成本与上下文质量之间实现平衡,适配长短多轮问答场景

会话记忆架构设计

实现可插拔式会话记忆组件,支持无记忆、滑动窗口、增量摘要压缩三种策略灵活切换,结合数据库持久化,在 Token 成本与上下文质量之间实现平衡,适配长短多轮问答场景。


一、架构全景

会话记忆是五步编排器的第一步,在对话启动时最先执行:

用户请求
  → ChatPreparationOrchestrator.prepare()
    → Step 1: 记忆装载 ← 本章内容
    → Step 2: 问题改写
    → Step 3: 文档路由
    → Step 4: 决策路由
    → Step 5: 组装执行计划

对话结束后,异步触发摘要刷新:

对话完成/失败/停止
  → safeRefreshConversationSummary(conversationId)
    → 判断是否需要压缩
    → 增量合并摘要 → 持久化到 MySQL

二、三种记忆策略

没有显式的策略枚举或策略接口,通过 enabled 标志 + 内部状态自动切换三种模式:

策略触发条件行为
无记忆historySummary.enabled = false仅加载最近轮次原文,不生成摘要,longTermSummary 为空串
滑动窗口enabled = true 且轮次数 ≤ keepRecentTurns加载最近 N 轮原文作为上下文,不触发压缩
增量摘要压缩enabled = true 且轮次数 > keepRecentTurns窗口外的旧轮次批量送入 LLM 压缩为结构化摘要,与窗口内原文拼接

三种模式通过一个配置开关 app.chat.rag.history-summary.enabled 控制,无需改代码即可切换。


三、核心数据模型

3.1 记忆上下文输出

ConversationMemoryContext —— 记忆装载的最终产物,供后续编排步骤使用:

字段类型说明
assembledHistoryString最终组合历史(长期摘要 + 最近窗口原文)
longTermSummaryString压缩后的长期摘要文本
recentTranscriptString格式化最近轮次(用于规划/改写)
answerRecentTranscriptString仅含用户问题的最近轮次(用于回答上下文)
summaryPayloadConversationSummaryPayload完整结构化摘要 JSON
coveredExchangeIdLong摘要覆盖到的最新交换 ID
coveredExchangeCountInteger摘要覆盖的总轮次数
compressionCountInteger已执行压缩的次数
compressionAppliedboolean摘要是否处于活跃状态

3.2 结构化摘要载体

ConversationSummaryPayload —— LLM 压缩输出的 JSON 结构:

{
  "summary": "一段 180~260 字的中文摘要",
  "conversation_goal": "用户长期目标(一句话)",
  "stable_facts": ["已确认的业务事实、术语、系统名"],
  "user_preferences": ["用户偏好"],
  "resolved_points": ["已解决的结论"],
  "pending_questions": ["待跟进的问题"],
  "retrieval_hints": ["对后续检索有帮助的关键词"]
}
约束
摘要长度summaryMaxChars(默认 1400)
每个数组上限6 条
每条上限80 字符
目标上限120 字符

3.3 数据库实体

SuperAgentChatMemorySummary —— 表 super_agent_chat_memory_summary

字段类型说明
idLong (PK)UID 生成器主键
dialogue_codeString会话 ID
covered_exchange_idLong摘要覆盖到的最新交换 ID
covered_exchange_countInteger摘要覆盖的总轮次数
compression_countInteger执行压缩的次数
summary_versionInteger单调递增版本号
summary_textString人类可读摘要文本
summary_jsonString完整 ConversationSummaryPayload JSON
last_source_edit_timeDate源交换的最近编辑时间

每个会话只保留一行记录,每次压缩时覆盖更新。


四、记忆装载流程

PersistentConversationMemoryService.loadMemoryContext() 详细过程:

1. 从 ConversationArchiveStore 获取会话存档
   → 读取 dialogue 记录 + exchange 列表

2. 获取现有摘要快照
   → 查询 super_agent_chat_memory_summary(每个会话最多一行)
   → 存在 → 反序列化 summaryPayload
   → 不存在 → 空摘要

3. 判断是否需要压缩
   if enabled && stableExchanges.size() > keepRecentTurns:
     → refreshSummaryIfNecessary()
     → 批量压缩溢出轮次
     → 持久化新摘要
     → compressionApplied = true

4. 渲染最近轮次原文
   → renderRecentTranscript():用户+助手全文
   → renderAnswerRecentTranscript():仅用户问题

5. 组装最终历史
   → assembledHistory = longTermSummary + recentTranscript
   → 返回 ConversationMemoryContext

五、增量摘要压缩

5.1 触发条件

三个条件同时满足才触发压缩:

  1. historySummary.enabled = true
  2. 已完成且稳定的轮次数 > keepRecentTurns(默认 4)
  3. 稳定轮次定义:状态为 COMPLETEDquestion 非空

5.2 批量处理

过期轮次(窗口外的旧轮次)以 compressionBatchTurns(默认 6)为一组,依次送入 LLM:

现有摘要 JSON + 新一批对话原文
  → buildSummaryMergePrompt()
  → observedChatModelService.callText("summary", SYSTEM_PROMPT, userPrompt)
  → 解析 LLM 返回的 ConversationSummaryPayload JSON
  → 失败回退到 fallbackMerge()

5.3 LLM 摘要提示词

系统提示:你是企业会话长期记忆压缩助手。
你的任务是把已有长期摘要与新增对话批次合并成新的长期记忆。
你必须只保留跨轮仍然有价值的信息,例如:
1. 用户真正的目标、范围和限制
2. 已经确认的业务事实、术语、系统名、模块名
3. 已经解决的结论和仍待继续追问的问题
4. 对后续知识检索仍有帮助的关键词
不要保留寒暄、重复确认、纯过程性客套话。
最终只返回合法 JSON,不要输出 Markdown,不要附加解释。

5.4 规则兜底(fallbackMerge)

当 LLM 调用失败时,执行基于规则的提取:

字段提取策略
summary拼接已有摘要 + 批次高亮("用户关注"、"已有结论")
conversation_goal从最后一个交换中提取
pending_questions从所有交换中累积
retrieval_hints正则提取字母数字 + 中文词汇,排除噪音词("请问"、"帮我"等 11 个停用词)

六、原文渲染策略

6.1 renderRecentTranscript(规划/改写用)

格式:【最近对话原文】

规则
跳过状态RUNNING 中的交换
用户消息前缀用户:
助手消息前缀助手:(仅 COMPLETED 状态)
问题长度限制160 字符
回答长度限制320 字符
总长度上限recentTranscriptMaxChars(默认 2200)
裁剪策略从尾部截断(保留最近上下文)

6.2 renderAnswerRecentTranscript(回答上下文用)

格式:【最近相关对话】

仅包含用户问题,排除助手回答。同样受 answerHistoryMaxChars(默认 1000)预算控制。


七、Token 预算管理

系统使用字符预算(保守估计 1 Token ≈ 4 个中文字符):

预算参数默认值用途
recentTranscriptMaxChars2200最近窗口原文上限
summaryMaxChars1400长期摘要文本上限
planningHistoryMaxChars1600规划历史的组装预算
answerHistoryMaxChars1000回答上下文预算

规划历史分配策略

总预算 1600 字符按比例分配:

部分比例默认值
最近原文65%(至少 50%,最多 100%)~1040 字符
结构化摘要剩余部分~558 字符
recentPart = min(max(maxChars/2, round(maxChars * 0.65)), maxChars);
structuredPart = max(0, maxChars - recentPart.length() - 2);

Token 成本估算

ObservedChatModelService 中:

estimatedTokens = max(1, ceil(content.length() / 4.0));

模型费率:

模型输入输出
qwen-plus$0.004 / 1K Token$0.012 / 1K Token
deepseek$0.002 / 1K Token$0.008 / 1K Token

八、历史上下文的三路分发

编排器在 Step1 拿到 ConversationMemoryContext 后,将其拆分为三个独立上下文:

上下文来源用途
historySummary结构化摘要 + 最近原文送入 Step2 问题改写 + Step3 文档路由
historyPlanningContextsummaryPayload 的结构化字段(目标/事实/待跟进/检索提示)辅助规划和路由决策
answerHistoryContextanswerRecentTranscript送入 LLM 生成答案时的上下文

ConversationExecutionPlan(最终产物)携带全部三类历史:

字段类型内容
historySummaryString组合规划历史
longTermSummaryString原始压缩摘要
historyPlanningContextHistoryPlanningContext结构化规划历史
recentHistoryTranscriptString纯最近原文
answerRecentTranscriptString仅用户问题的原文
answerHistoryContextString回答上下文

九、配置参数汇总

全部位于 ChatRagProperties.HistorySummaryProperties

参数默认值说明
enabledtrue摘要压缩开关(关 = 无记忆模式)
keep-recent-turns4滑动窗口保留轮数
compression-batch-turns6每批送入 LLM 压缩的轮数
recent-transcript-max-chars2200最近原文最大字符数
summary-max-chars1400摘要文本最大字符数

相关顶层配置:

参数默认值说明
rewrite-history-turns4送入改写器的历史轮数
planning-history-max-chars1600规划历史组装预算
answer-history-max-chars1000回答历史上下文预算

十、并发安全

机制说明
ConcurrentHashMap.newKeySet()refreshingConversationIds 集合,防止同一会话并发压缩
乐观锁查询saveSummarySnapshot 写入前重查数据库,检查是否有更高版本的 coveredExchangeId
专用线程池chatMemorySummaryExecutorService:2 核心线程 + 32 容量队列 + CallerRunsPolicy

十一、生命周期管理

对话启动
  → loadMemoryContext()        // 装载记忆
  → 执行对话

对话结束
  → finishSuccessfully()       // 正常结束
  → finishWithFailure()        // 异常结束
  → stopTask()                 // 用户停止
     └─ safeRefreshConversationSummary()  // 三种路径均触发异步摘要刷新

会话重置
  → deleteConversationSummary()  // 删除摘要记录

手动操作
  → rebuildConversationSummary() // 删除后从头重新压缩(API: POST /session/summary/rebuild)

十二、完整文件清单

核心服务

文件职责
chatagent/service/ConversationMemoryService.java记忆服务接口(6 个方法)
chatagent/service/PersistentConversationMemoryService.java核心实现(841 行):装载、压缩、渲染、持久化
chatagent/service/ConversationArchiveStore.java对话存档接口(获取原始轮次)
chatagent/service/MybatisConversationArchiveStore.javaMyBatis 存档实现(617 行)
chatagent/service/ObservedChatModelService.javaLLM 调用封装 + Token 用量追踪

数据模型

文件职责
chatagent/model/memory/ConversationMemoryContext.java记忆上下文输出模型
chatagent/model/memory/ConversationSummaryPayload.java结构化摘要 JSON 模型
chatagent/model/ConversationMemorySummaryView.java摘要视图 DTO
chatagent/model/ConversationExchangeView.java交换轮次数据模型
chatagent/model/ConversationSessionView.java会话视图(含 memorySummary)
chatagent/data/SuperAgentChatMemorySummary.java摘要数据库实体
chatagent/mapper/SuperAgentChatMemorySummaryMapper.javaMyBatis-Plus Mapper

编排集成

文件职责
chatagent/rag/service/ChatPreparationOrchestrator.java五步编排器(Step1 调用记忆装载 + 三路历史分发)
chatagent/rag/service/AnswerHistoryContextAssembler.java回答历史上下文组装
chatagent/rag/model/HistoryPlanningContext.java结构化规划历史模型
chatagent/rag/model/AnswerHistoryContext.java回答历史上下文模型
chatagent/rag/model/ConversationExecutionPlan.java完整执行计划(携带全部历史字段)

配置

文件职责
chatagent/rag/config/ChatRagProperties.java记忆相关全部配置属性
chatagent/rag/config/ChatRagExecutorConfiguration.java摘要专用线程池配置
chatagent/model/trace/ConversationTraceStageCode.java追踪阶段:MEMORY

主流程

文件职责
chatagent/service/BusinessChatService.java主业务服务(启动时调用记忆装载,结束时触发摘要刷新)
chatagent/enums/ChatTurnStatus.java交换状态枚举(COMPLETED/RUNNING/FAILED/STOPPED)

十三、设计亮点总结

  1. 开关式策略切换:一个 enabled 配置即可在无记忆/滑动窗口/增量摘要三种模式间切换,无需改动代码或策略接口
  2. 增量非全量:每次只压缩窗口外的新增轮次,与已有摘要合并,避免重复处理历史轮次,性能 O(新轮次) 而非 O(总轮次)
  3. 结构化摘要载体:不是纯文本摘要,而是包含目标/事实/偏好/待跟进/检索提示的结构化 JSON,下游可精确提取所需信息
  4. 三路历史分发:同一份记忆根据用途拆分为规划历史、改写上下文、回答上下文三条通路,各自有独立预算,避免上下文污染
  5. LLM + 规则双链路:摘要压缩首选 LLM,失败时回退到基于规则的 fallbackMerge,保证压缩不会完全失败
  6. 尾部裁剪:原文超出预算时从尾部截断而非头部,确保保留最近对话(最相关),牺牲早期对话
  7. 异步无感:摘要刷新在对话结束后异步执行,不阻塞用户请求,专用线程池 2 核心 + CallerRunsPolicy 防止 OOM
  8. 乐观锁防并发:写入前重查数据库检查版本号,避免并发压缩导致摘要错乱
返回项目列表