情感 AI 的对话上下文管理:滑动窗口、摘要压缩与混合策略

📅 2026/7/12 15:29:55
情感 AI 的对话上下文管理:滑动窗口、摘要压缩与混合策略
情感 AI 的对话上下文管理滑动窗口、摘要压缩与混合策略一、上下文窗口在情感对话中的特殊挑战情感陪伴 AI 对对话上下文的需求与任务型 AI 不同。用户可能在一周前提及工作压力三天后提及睡眠不好今天说今天又是那种感觉。任务型 AI 可以忽略这一跨度长达一周的上下文但情感 AI 需要将这些碎片串联成一个连贯的情绪叙事。否则回复会缺乏共情深度——AI 只能对今天做出反应无法理解又是背后的累积情绪。但技术上做不到无限记忆。GPT-4 的 128K 上下文窗口看似巨大实际填充几轮长篇对话后就会耗尽。更棘手的是长上下文中的信息密度不均用户的情绪关键信息只占全文的 5-10%其余是日常寒暄和无信息量的过渡语句。把整个对话历史塞进 Prompt 不仅浪费 Token还稀释了关键情绪的注意力权重。通过在一款情绪日记 AI 的实测数据全部历史对话送入模型时情绪识别准确率为 76%只发送经结构化的关键片段后准确率上升至 89%。二、三种上下文管理策略的协同架构单独依赖滑动窗口、摘要压缩或关键片段提取都不够。滑动窗口丢失早期信息摘要压缩丢失细节关键片段提取丢失上下文连贯性。三种策略需要协同工作graph TB DH[对话历史] -- SW[滑动窗口br/保留最近 N 轮] DH -- KE[关键片段提取br/识别情绪转折点] DH -- SUM[摘要压缩br/浓缩早期对话] SW -- MERGE[上下文组装器] KE -- MERGE SUM -- MERGE MERGE -- TOKEN[Token 预算分配器] subgraph Token分配 TOKEN -- T1[60% 滑动窗口] TOKEN -- T2[25% 关键片段] TOKEN -- T3[15% 摘要] end T1 -- FINAL[最终 Prompt] T2 -- FINAL T3 -- FINAL三种策略的分工明确滑动窗口负责近期对话的完整保留确保回复能接上当前话题关键片段负责跨时间的情绪关联将几天前的关键事件带入当前语境摘要负责远期对话的结构化压缩提供用户的长期背景信息。三、上下文组装器的工程实现from dataclasses import dataclass, field from typing import List, Optional from datetime import datetime, timedelta dataclass class DialogueTurn: 单轮对话的数据结构 role: str # user | assistant content: str timestamp: datetime # 情绪强度 0-1——由上游情绪分析模块标注 emotion_intensity: float 0.0 dataclass class ContextBudget: 上下文 Token 预算分配 total_tokens: int sliding_window_ratio: float 0.60 key_moments_ratio: float 0.25 summary_ratio: float 0.15 class ContextAssembler: 上下文组装器——混合三种策略构建最终 Prompt def __init__(self, max_tokens: int 6000): self.max_tokens max_tokens self.summarizer DialogueSummarizer() def assemble( self, history: List[DialogueTurn], current_message: str, ) - str: 组装上下文——返回结构化的 Prompt 文本 budget ContextBudget(total_tokensself.max_tokens) # 1. 滑动窗口保留最近 20 轮完整对话 recent history[-20:] if len(history) 20 else history sliding_text self._format_turns(recent) # 2. 关键片段提取找到情绪强度 0.7 的轮次 key_moments [ turn for turn in history[:-20] if turn.emotion_intensity 0.7 ] # 高情绪片段超过 5 个时只取最强的 5 个 key_moments.sort(keylambda t: t.emotion_intensity, reverseTrue) key_moments key_moments[:5] key_text self._format_turns(key_moments) # 3. 摘要压缩对 20 轮之前的对话做摘要 early_history history[:-20] if len(history) 20 else [] summary_text if early_history: summary_text self.summarizer.summarize(early_history) # 4. 按预算分配 Token sliding_tokens int(budget.total_tokens * budget.sliding_window_ratio) key_tokens int(budget.total_tokens * budget.key_moments_ratio) summary_tokens int(budget.total_tokens * budget.summary_ratio) # 截断各部分以符合 Token 预算 sliding_text self._truncate_to_tokens(sliding_text, sliding_tokens) key_text self._truncate_to_tokens(key_text, key_tokens) summary_text self._truncate_to_tokens(summary_text, summary_tokens) # 5. 组装最终 Prompt——分区块标注以便模型区分信息来源 return f## 用户背景摘要 {summary_text or 暂无历史摘要} ### 关键情绪时刻 {key_text or 暂无记录的关键时刻} ### 近期对话 {sliding_text} ### 当前消息 用户: {current_message} 请基于以上三段上下文理解用户当前情绪生成具有共情深度的回复。 def _format_turns(self, turns: List[DialogueTurn]) - str: 将对话轮次格式化为自然语言 if not turns: return lines [] for turn in turns: time_str turn.timestamp.strftime(%m-%d %H:%M) label 用户 if turn.role user else AI lines.append(f[{time_str}] {label}: {turn.content}) return \n.join(lines) def _truncate_to_tokens(self, text: str, max_tokens: int) - str: 按 Token 数截断文本——中文字符近似为 2 Token/字 # 设计意图使用简单估算而非精确 Tokenizer 减少外部依赖 # 中文每个字符约 2 Token英文每个单词约 1.3 Token char_limit max_tokens // 2 if len(text) char_limit: return text # 从前面截断——优先保留较新的内容 return text[-char_limit:]Token 分配比例的权衡60% 给滑动窗口是因为近期对话对回复质量的即时影响最大。25% 给关键片段是为了让 AI 捕捉长期情绪模式。15% 给摘要足以提供背景信息而不过度抢夺 Token 预算。四、边界场景下的策略调整对话总长度不足 20 轮所有对话都在滑动窗口内不需要摘要压缩和关键片段提取。此时应将全部 Token 分配给滑动窗口。高度重复的日常对话如果用户连续 15 轮都在发晚安早安情绪强度全部为 0。关键片段提取将返回空集此时将这部分 Token 预算分配给摘要区——虽然摘要区也很可能为空但保留的结构化 Prompt 格式有助于模型理解上下文结构。情绪记忆的衰减曲线一周前的愤怒不如一小时前的愤怒重要。可以对关键片段施加时间衰减权重让越近的情绪事件获得越高的排序权重。五、总结情感 AI 的上下文管理需要三种策略的混合编排滑动窗口负责近期的对话连贯性关键片段提取负责跨时间的情绪叙事摘要压缩负责远期的用户背景信息三者的 Token 分配比例不是固定的——根据对话长度和情绪密度动态调整。落地建议先实现滑动窗口——这是最基础的兜底方案引入情绪强度标注可在调用大模型时同时要求输出 0-1 的情绪分值当滑动窗口无法容纳全部对话时逐步加入摘要和关键片段机制定期抽检生成回复的情绪准确性反向调整 Token 分配比例。