AI记忆策略
你在设计聊天机器人时,希望本轮对话(当前会话)能让模型记住前面几轮的上下文,但又不想每次都把全部历史消息原封不动地塞进 prompt(会导致上下文长度爆炸、成本飙升、噪声干扰模型判断力下降)。
以下是目前工业界和开源社区中使用最广泛、性价比相对较高的几种本轮对话记忆方案,按推荐优先级排序(2025-2026主流做法):
| 排名 | 方案名称 | 上下文长度控制 | 信息保留质量 | 实现难度 | Token 节省 | 推荐场景 | 代表库/框架 |
|---|---|---|---|---|---|---|---|
| 1 | 滚动窗口 + 自动摘要(最推荐) | 优秀 | 良好~优秀 | 中 | 极高 | 绝大部分中长对话产品 | LangChain, LlamaIndex, Mem0 |
| 2 | 固定窗口 + 最近N轮 | 很好 | 中等 | 低 | 高 | 客服、简单问答、短期任务 | 几乎所有框架 |
| 3 | 实体/关键事实抽取 + 内存KV | 优秀 | 很好~优秀 | 高 | 极高 | 需要跨轮记住人名、数字、偏好、待办 | Mem0, LangGraph |
| 4 | 向量检索式记忆(RAG式) | 极好 | 很好(需调优) | 高 | 极高 | 非常长对话、跨会话记忆 | Pinecone + LangChain |
| 5 | 全历史(baseline) | 差 | 最好(短期) | 最低 | 无 | 上下文窗口极大或对话极短时 | — |
目前工业界最主流的实用组合方案(2025-2026)
“滚动窗口 + 动态摘要 + 少量关键事实保护”(性价比最高)
核心思想:
- 永远只给模型看最近的 K轮完整对话(比如 6–12 轮,约占窗口的 40–60%)
- 更早的对话被压缩成 1–3段摘要(放在最前面)
- 可选:把最重要的几条事实/实体/状态以结构化方式强制放在 system prompt 或开头(类似“用户档案”)
实现伪代码(最常见写法):
# 假设你使用类似 LangChain / LlamaIndex / 纯 openai api 的结构
class SmartRollingMemory:
def __init__(self, max_recent_turns=8, max_summary_tokens=400):
self.history = [] # 完整 [(role, content), ...]
self.summary = "" # 压缩后的早期对话摘要
self.key_facts = [] # 可选:抽取的关键事实列表
self.max_recent = max_recent_turns
self.max_summary_tok = max_summary_tokens
def add_message(self, role, content):
self.history.append((role, content))
def get_prompt_messages(self, system_prompt):
# 超过一定轮次才触发压缩
if len(self.history) // 2 > self.max_recent + 4:
# 找出要压缩的部分
to_summarize = self.history[:-self.max_recent * 2] # 最早的对话
new_summary = self._call_llm_to_summarize(to_summarize)
# 更新摘要(可以累加式,也可以覆盖式)
self.summary = self._merge_summaries(self.summary, new_summary)
# 可选:从要压缩的部分再抽取关键事实
self.key_facts.extend(self._extract_key_facts(to_summarize))
self.key_facts = self.key_facts[-30:] # 限制数量
# 裁剪历史,只保留最近的
self.history = self.history[-self.max_recent * 2:]
# 最终要发给模型的内容
messages = [{"role": "system", "content": system_prompt}]
# 插入用户档案 / 关键事实(非常有效)
if self.key_facts:
messages.append({"role": "system", "content": "重要记忆(不要遗忘):\n" + "\n".join(self.key_facts)})
# 插入早期对话摘要
if self.summary:
messages.append({"role": "system", "content": f"之前对话摘要:{self.summary}"})
# 插入最近的完整对话
for role, content in self.history:
messages.append({"role": role, "content": content})
return messages
def _call_llm_to_summarize(self, old_msgs) -> str:
# 用一个小模型或同一个模型做摘要,提示词很重要
prompt = f"""请用简洁的第三人称总结下面对话的主要内容、关键决定、待办事项、用户偏好等,不要遗漏重要事实,控制在300字以内:\n{msgs_to_text(old_msgs)}"""
return call_llm(prompt, max_tokens=450)
def _extract_key_facts(self, msgs):
# 可以用更强的结构化提取提示,或者用 function calling / regex
return ["用户最喜欢的颜色是深空灰", "用户有三个小孩", "项目截止日期是2026-03-15"]
更激进但效果很好的几种变体(按需选择)
- 双摘要:一个长期摘要(整段对话),一个中期摘要(最近30轮左右)
- 重要性打分:每轮结束后给消息打重要性分,低于阈值的更容易被替换/删除
- 状态快照:像游戏存档一样,每10轮强制让模型输出一次「当前已知用户画像 + 任务状态」,存下来反复使用
- Mem0 / Letta 风格:彻底把记忆变成 key-value 事实存储 + 向量召回,需要长期记忆时最强,但开发成本高
快速建议(不同场景选哪个)
| 场景 | 推荐方案 | 保留轮数建议 | 是否抽关键事实 |
|---|---|---|---|
| 普通闲聊、客服 | 固定窗口(最近8–15轮) | 8–15 | 否 |
| 连续多天的写代码/写方案 | 滚动窗口 + 自动摘要 | 6–10 | 是(强烈推荐) |
| 需要记住用户各种偏好 | 滚动窗口 + 关键事实保护 | 6–12 | 是 |
| 超长任务(几十上百轮) | 向量RAG记忆 + 摘要 | 4–8 | 是 + 向量召回 |