做 Agent 项目绕不开一个问题:大模型本身没有记忆,每次调用都是无状态的。所谓”记忆”,本质上是把历史信息塞进下一次请求的 prompt 里。这篇文章从原理出发,整理三种主流实现方案。
核心问题
大模型每次调用都是独立的,它不知道你上一轮说了什么。”记忆”的实现原理是这样的:
1 2 3 4 5
| 第1轮:[system] + [user: 你好] 第2轮:[system] + [user: 你好] + [assistant: 你好!] + [user: 我叫古恩豪] 第3轮:[system] + [前两轮历史] + [user: 我叫什么?] ↑ 这就是"记忆"——把历史塞进 prompt
|
问题是 history 会无限增长,迟早超出 context window(Claude 200k token,GPT-4 128k token)。所以需要压缩策略。
方案一:滑动窗口
最简单的方案,只保留最近 N 轮,超出就丢掉最早的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class ConversationAgent: def __init__(self, max_turns=10): self.history = [] self.max_turns = max_turns def chat(self, user_input): self.history.append({"role": "user", "content": user_input}) if len(self.history) > self.max_turns * 2: self.history = self.history[-self.max_turns * 2:] response = llm.call(messages=[ {"role": "system", "content": "你是一个助手"}, *self.history ]) self.history.append({"role": "assistant", "content": response}) return response
|
优点: 实现极简,延迟低,token 消耗可控
缺点: 早期信息完全丢失,用户说”你之前提到的那个方案”就找不到了
适合:客服、简单问答,对话之间关联性弱的场景。
方案二:摘要压缩
超过阈值时,把旧对话喂给 LLM 让它总结,摘要替代原始内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class ConversationAgent: def __init__(self, max_turns=20): self.history = [] self.summary = "" self.max_turns = max_turns def _compress(self): to_compress = self.history[:self.max_turns // 2] self.history = self.history[self.max_turns // 2:] prompt = f""" 已有摘要:{self.summary}
新增对话: {self._format(to_compress)}
请将以上内容合并更新为简洁摘要,保留关键信息(用户说了什么、决定了什么、重要的上下文): """ self.summary = llm.call(prompt) def chat(self, user_input): self.history.append({"role": "user", "content": user_input}) if len(self.history) > self.max_turns: self._compress() messages = [{"role": "system", "content": "你是一个助手"}] if self.summary: messages.append({ "role": "system", "content": f"[对话历史摘要]\n{self.summary}" }) messages.extend(self.history) response = llm.call(messages=messages) self.history.append({"role": "assistant", "content": response}) return response
|
优点: 信息损失可控,关键内容保留
缺点: 摘要本身消耗 token,有额外延迟;摘要质量依赖 LLM
这是 Claude Code 本身用的方案——你现在看到的对话 Summary 就是这个机制。
方案三:分层记忆
三层结构,各司其职:
1 2 3 4 5
| 短期记忆(最近5轮) → 直接放进 prompt,保证对话连贯性 ↓ 超出阈值 中期记忆(摘要) → LLM 压缩,作为 system message ↓ 全量存储 长期记忆(向量库) → 历史向量化,按当前问题检索相关片段
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| from sentence_transformers import SentenceTransformer import numpy as np
class AgentWithMemory: def __init__(self): self.short_term = [] self.summary = "" self.long_term = [] self.embedder = SentenceTransformer("all-MiniLM-L6-v2") def _add_to_long_term(self, turn): text = f"用户:{turn['user']}\n助手:{turn['assistant']}" vec = self.embedder.encode(text) self.long_term.append({"text": text, "vec": vec}) def _retrieve(self, query, top_k=2): if not self.long_term: return [] q_vec = self.embedder.encode(query) scores = [np.dot(q_vec, item["vec"]) for item in self.long_term] top = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k] return [self.long_term[i]["text"] for i in top] def chat(self, user_input): relevant = self._retrieve(user_input) messages = [{"role": "system", "content": "你是一个助手"}] if self.summary: messages.append({"role": "system", "content": f"[历史摘要] {self.summary}"}) if relevant: messages.append({"role": "system", "content": "[相关历史]\n" + "\n---\n".join(relevant)}) messages.extend(self.short_term) messages.append({"role": "user", "content": user_input}) response = llm.call(messages=messages) self.short_term.append({"role": "user", "content": user_input}) self.short_term.append({"role": "assistant", "content": response}) if len(self.short_term) > 10: old_user = self.short_term.pop(0) old_asst = self.short_term.pop(0) self._add_to_long_term({ "user": old_user["content"], "assistant": old_asst["content"] }) return response
|
优点: 信息损失最小,既保连贯性又能召回远期细节
缺点: 实现复杂,需要维护向量库,每次请求多一次检索
三种方案对比
| 方案 |
实现复杂度 |
信息损失 |
延迟 |
适合场景 |
| 滑动窗口 |
极简 |
高(早期全丢) |
最低 |
简单问答、客服 |
| 摘要压缩 |
中等 |
中(关键信息保留) |
中等 |
通用 Agent |
| 分层记忆 |
复杂 |
低 |
较高 |
长期陪伴、个人助手 |
实际项目里三层通常叠加用:短期用滑动窗口保连贯性,中期用摘要保关键信息,长期用向量库按需召回。
面试怎么答
面试官问”你的 Agent 怎么处理长对话”,不要只说”我用了分层记忆”,要说清楚为什么选这个方案:
- 对话 Agent(问答场景):摘要压缩够用,分层记忆成本高但收益有限
- 运维 Agent(每次告警是独立会话):滑动窗口就够,不需要跨会话记忆
- 个人助手(需要记住用户偏好):分层记忆,向量库存用户画像
选型理由比实现细节更能体现思考深度。