LOADING
3393 words
17 minutes
四种记忆策略(零)
2026-07-01

四种记忆策略:让 LLM 真正「记住」对话

作者手记:LLM 本质是无状态的——每次对话对它来说都是「初次见面」。要让它记住上下文,唯一的办法是把历史对话塞进 Prompt。但 Prompt 窗口是有限的,Token 成本也是有限的。于是,「记忆策略」就成了工程落地的必修课。

为什么 LLM 需要「记忆」?

先看一个真实场景:

用户:我想去北京旅游
AI: 好的,北京有很多景点推荐,比如故宫、长城...
用户:推荐一些住宿吧
AI: 请问你想去哪个城市? ← 它「失忆」了!

这就是典型的「无记忆」对话——每轮都是独立的,LLM 不知道用户之前说过什么。

要解决这个问题,我们需要做的就是:把历史对话塞进 Prompt 里。

但问题来了:

  • 上下文窗口有限(即使 128K,也装不下无限对话)
  • Token 成本高昂(每轮都带完整历史,费用爆炸)
  • 信息过载(塞太多内容,模型反而「注意力分散」)

于是,记忆策略诞生了——它要解决的核心问题是:在有限的 Token 预算内,尽可能保留最有价值的上下文。

四种主流记忆策略对比

策略核心思想优点缺点适用场景
Buffer(全量缓冲)记住所有历史对话信息完整、实现简单Token 消耗大、无法处理长对话短对话、客服场景
Window(滑动窗口)只保留最近 N 轮对话成本低、响应快会丢失早期关键信息日常聊天、短任务
Summary(摘要压缩)把历史对话压缩成摘要节省 Token、保留核心信息细节丢失、摘要可能失真长对话、多轮任务
Vector(向量检索)把历史对话存入向量库,按需检索支持无限长度、可跨会话实现复杂、检索延迟高长期记忆、个性化助手

一、Buffer Memory(全量缓冲记忆)

核心思想

把所有历史对话都塞进 Prompt,一轮都不丢。

工作流程

对话 1: 用户:你好 → AI: 你好,有什么可以帮你?
对话 2: 用户:我想查天气 → AI: 请问你在哪个城市?
对话 3: 用户:北京 → AI: 北京今天晴天...
Prompt 构建:
System: 你是一个助手
History: [对话 1, 对话 2, 对话 3]
Current: 用户:明天呢?

我的 TypeScript 实现

src/server/memory-strategies.ts
import { SystemMessage, HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
// 会话状态
interface Session {
id: string;
messages: BaseMessage[]; // 存储所有历史消息
// ...其他配置
}
// Buffer 策略:全量返回所有消息
function bufferContext(session: Session): BaseMessage[] {
return session.messages;
}

使用方式

// 添加用户消息
session.messages.push(new HumanMessage("你好"));
// 添加 AI 回复
session.messages.push(new AIMessage("你好,有什么可以帮你?"));
// 获取上下文(直接拿所有 messages)
const contextMessages = bufferContext(session);
// 调用 LLM
const response = await llm.invoke(contextMessages);

优缺点

优点缺点
✅ 实现最简单❌ Token 消耗随对话线性增长
✅ 信息完整,无丢失❌ 无法处理长对话(超出上下文窗口)
✅ 适合短任务❌ 成本高(每轮都带全量历史)

适用场景

  • 客服对话(通常 3-5 轮结束)
  • 简单问答(不需要长期记忆)
  • 原型验证(快速测试)

二、Window Memory(滑动窗口记忆)

核心思想

只保留最近 N 轮对话,超出的自动丢弃。

工作流程

窗口大小 = 3 轮(6 条消息,一问一答算一轮)
对话 1: 用户:你好 → AI: 你好 ← 这一轮会被丢弃
对话 2: 用户:查天气 → AI: 哪个城市?
对话 3: 用户:北京 → AI: 北京晴天
对话 4: 用户:明天呢? → AI: 明天也是晴天
Prompt 构建(只保留最近 3 轮):
History: [对话 2, 对话 3, 对话 4]
Current: 用户:大后天呢?

我的 TypeScript 实现

src/server/memory-strategies.ts
interface Session {
id: string;
messages: BaseMessage[];
memoryStrategy: "buffer" | "window" | "summary" | "vector";
windowSize: number; // ← 窗口大小:保留最近 N 轮
}
function windowContext(session: Session): BaseMessage[] {
// 去掉 System Message,取最近的 windowSize × 2 条消息(一轮两条)
const nonSystem = session.messages.filter(m => m._getType() !== "system");
const recent = nonSystem.slice(-session.windowSize * 2);
// 如果有 System Message,放最前面
const sys = session.messages.find(m => m._getType() === "system");
return sys ? [sys, ...recent] : recent;
}

配置方式

// 创建会话时,指定窗口大小为 3
const session: Session = {
id: "test-001",
messages: [new SystemMessage("你是一个助手")],
memoryStrategy: "window",
windowSize: 3, // 只保留最近 3 轮对话
// ...其他配置
};

优缺点

优点缺点
✅ Token 消耗可控❌ 会丢失早期关键信息
✅ 实现简单❌ 固定窗口大小,不够灵活
✅ 响应快❌ 用户说「记住我之前说的…」会失效

适用场景

  • 日常聊天(不需要长记忆)
  • 短任务(如单次查询、简单代办)
  • 成本敏感场景(控制 Token 用量)

三、Summary Memory(摘要压缩记忆)

核心思想

把历史对话压缩成一段摘要,只保留关键信息。

工作流程

原始对话(10 轮,5000 tokens):
用户:我想去北京旅游
AI: 好的,北京有很多景点...
用户:推荐几个吧
AI: 故宫、长城、颐和园...
...(省略 6 轮)
摘要压缩后(200 tokens):
「用户计划去北京旅游,已了解故宫、长城、颐和园等景点,
正在询问交通和住宿建议。」
Prompt 构建:
System: 你是一个旅游助手
Summary: [上述摘要]
Recent: [最近 3 轮对话(保留原始文本)]
Current: 用户:住宿推荐什么区域?

我的 TypeScript 实现

src/server/memory-strategies.ts
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
interface Session {
id: string;
messages: BaseMessage[];
memoryStrategy: "buffer" | "window" | "summary" | "vector";
summaryThreshold: number; // 超过 N 条消息触发压缩
recentToKeep: number; // 压缩后保留最近 M 条原文
summary?: string; // 压缩后的摘要
}
// 摘要 Prompt
const summaryPrompt = PromptTemplate.fromTemplate(`
请将以下对话历史压缩为一段简洁的摘要,保留所有关键信息(人名、偏好、决策、重要事实等)。
{previous_summary}
对话历史:
{history}
请输出压缩后的摘要(只输出摘要内容,不要加任何前缀):
`);
function formatMessages(messages: BaseMessage[]): string {
return messages.map((msg) => {
const role = msg instanceof HumanMessage ? "用户"
: msg instanceof AIMessage ? "助手" : "系统";
const content = typeof msg.content === "string" ? msg.content : String(msg.content);
return `${role}:${content}`;
}).join("\n");
}
async function summaryContext(session: Session): Promise<BaseMessage[]> {
// 不管有没有摘要,都保留最近几条的原文
const nonSystem = session.messages.filter(m => m._getType() !== "system");
const recent = nonSystem.slice(-session.recentToKeep * 2);
// 消息数量超过阈值 → 触发压缩
if (nonSystem.length >= session.summaryThreshold) {
const oldMessages = nonSystem.slice(0, -session.recentToKeep * 2);
const historyText = formatMessages(oldMessages);
// 调用 LLM 生成摘要
const chain = summaryPrompt.pipe(llm).pipe(parser);
session.summary = await chain.invoke({
previous_summary: session.summary || "",
history: historyText,
});
}
// 构建上下文:System + 摘要 + 最近几条原文
const result: BaseMessage[] = [];
const sys = session.messages.find(m => m._getType() === "system");
if (sys) result.push(sys);
if (session.summary) {
result.push(new SystemMessage(`之前的对话摘要:${session.summary}`));
}
result.push(...recent);
return result;
}

配置方式

const session: Session = {
id: "test-001",
messages: [new SystemMessage("你是一个助手")],
memoryStrategy: "summary",
summaryThreshold: 6, // 超过 6 条消息触发压缩
recentToKeep: 4, // 压缩后保留最近 4 轮原文
};

优缺点

优点缺点
✅ Token 消耗大幅降低❌ 细节丢失(摘要无法保留所有信息)
✅ 支持超长对话❌ 摘要可能失真(LLM 概括错误)
✅ 保留核心意图❌ 需要额外调用 LLM(增加成本和延迟)

适用场景

  • 长对话(10+ 轮)
  • 多轮任务(如旅行规划、项目讨论)
  • 需要跨会话记忆(把摘要持久化存储)

四、Vector Memory(向量检索记忆)

核心思想

把历史对话存入向量数据库,根据当前问题检索相关记忆。

工作流程

历史对话(100 轮) → 切分成片段 → 向量化 → 存入向量库
用户提问:「我之前说想去哪个城市来着?」
问题向量化 → 在向量库检索最相似的历史片段
找到:「用户计划去北京旅游」
把检索结果 + 当前问题 → 构建 Prompt → LLM 回答

我的 TypeScript 实现

src/server/memory-strategies.ts
import { Embeddings } from "@langchain/core/embeddings";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
interface VectorEntry {
text: string;
vector: number[];
metadata: { user: string; assistant: string };
}
interface Session {
id: string;
messages: BaseMessage[];
memoryStrategy: "buffer" | "window" | "summary" | "vector";
vectorEntries: VectorEntry[]; // 向量化的历史对话
recentToKeep: number;
}
// 纯 JS 实现的 TF-IDF Embedding(不需要 API)
class TfIdfEmbeddings extends Embeddings {
// ...省略实现(详见 RAG 那篇)
}
const embeddings = new TfIdfEmbeddings();
// 每轮对话结束,把这一轮存入向量库
export async function addToVectorStore(
session: Session,
userInput: string,
aiResponse: string,
): Promise<void> {
const text = `用户:${userInput}\n助手:${aiResponse}`;
const [vector] = await embeddings.embedDocuments([text]);
session.vectorEntries.push({
text,
vector,
metadata: { user: userInput, assistant: aiResponse },
});
}
// 余弦相似度计算
function cosineSimilarity(a: number[], b: number[]): number {
const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
return dot / (normA * normB);
}
// 构建上下文时,先检索相关历史
async function vectorContext(
session: Session,
currentInput: string,
): Promise<BaseMessage[]> {
const result: BaseMessage[] = [];
// System Message 放最前面
const sys = session.messages.find(m => m._getType() === "system");
if (sys) result.push(sys);
// 检索相关历史对话(取 Top 3)
if (session.vectorEntries.length > 0) {
const queryVector = await embeddings.embedQuery(currentInput);
const topK = session.vectorEntries
.map(entry => ({
entry,
similarity: cosineSimilarity(queryVector, entry.vector),
}))
.sort((a, b) => b.similarity - a.similarity)
.slice(0, 3)
.map(item => item.entry.text);
if (topK.length > 0) {
result.push(new SystemMessage(
`以下是从历史对话中检索到的相关内容:\n${topK.join("\n\n")}`
));
}
}
// 再加上最近 N 轮对话原文
const nonSystem = session.messages.filter(m => m._getType() !== "system");
const recent = nonSystem.slice(-session.recentToKeep * 2);
result.push(...recent);
return result;
}

使用方式

const session: Session = {
id: "test-001",
messages: [new SystemMessage("你是一个助手")],
memoryStrategy: "vector",
vectorEntries: [],
recentToKeep: 4,
};
// 对话过程中,每轮结束都把对话存入向量库
await addToVectorStore(session, "我想去北京旅游", "好的,北京有很多景点...");
await addToVectorStore(session, "推荐几个吧", "故宫、长城、颐和园...");
// 下一轮提问时,自动检索相关历史
const context = await vectorContext(session, "我之前说想去哪个城市来着?");
// 检索结果会包含「用户计划去北京旅游」相关的历史片段

优缺点

优点缺点
✅ 支持无限长度历史❌ 实现复杂(需要向量库)
✅ 跨会话记忆(持久化)❌ 检索延迟高(相比直接读内存)
✅ 按需检索(只带相关信息)❌ 检索质量依赖 Embedding 模型

适用场景

  • 个性化助手(记住用户偏好、历史决策)
  • 长期项目(跨天、跨周的持续讨论)
  • 企业知识库(结合 RAG 使用)

四种策略横向对比

维度BufferWindowSummaryVector
实现难度⭐⭐⭐⭐⭐
Token 成本
信息完整性100%部分核心信息按需检索
支持长对话⚠️
跨会话记忆✅(需持久化)
响应延迟中(需摘要)高(需检索)
我的项目代码行数3 行10 行60 行120 行

选型决策树

你的对话通常有多长?
├── 3-5 轮 → 用 Buffer(最简单)
├── 5-10 轮 → 用 Window(k=5 或 k=3)
└── 10+ 轮 → 用 Summary 或 Vector
├── 需要细节 → Vector(向量检索)
└── 只需要核心意图 → Summary(摘要压缩)

混合策略(生产环境推荐)

实际生产环境中,往往采用组合策略

方案一:Summary + Buffer(短期 + 长期)

- 最近 3 轮 → 用 Buffer(保留原始对话)
- 3 轮之前 → 用 Summary(压缩成摘要)

优点:既保留近期细节,又控制 Token 用量。

方案二:Window + Vector(滑动窗口 + 长期记忆)

- 最近 5 轮 → 用 Window(快速响应)
- 历史对话 → 存入 Vector(按需检索)

优点:平衡性能和记忆能力。

我的项目实现(统一入口)

src/server/memory-strategies.ts
export async function getContextMessages(
session: Session,
currentInput?: string,
): Promise<BaseMessage[]> {
switch (session.memoryStrategy) {
case "buffer":
return bufferContext(session);
case "window":
return windowContext(session);
case "summary":
return await summaryContext(session);
case "vector":
return await vectorContext(session, currentInput || "");
default:
return bufferContext(session);
}
}

Handler 中调用

src/server/handlers/stream.ts
// 不管用什么策略,Handler 只需要调用这一行
const contextMessages = await getContextMessages(session, content);

这就是策略模式的魅力——新增策略时,Handler 一行代码都不用改!

实战踩坑经验

坑 1:System Message 丢了怎么办?

问题:Window/Vector 策略取消息时,不小心把 System Message 过滤掉了。

解决:专门处理 System Message,永远放在最前面:

const sys = session.messages.find(m => m._getType() === "system");
if (sys) result.push(sys);

坑 2:摘要 Prompt 输出了「以下是摘要:」

问题:LLM 喜欢在输出前加解释文字。

解决:在 Prompt 末尾强制加一句:

请输出压缩后的摘要(只输出摘要内容,不要加任何前缀):

坑 3:向量检索到了不相关的内容

问题:TF-IDF 对中文语义理解有限,可能检索到不相关的历史。

解决

  • 检索 Top 5 而不是 Top 3,让 LLM 自己筛选
  • 检索结果加一个相似度阈值(低于 0.3 的直接丢弃)
  • 生产环境换成中文 Embedding 模型(如 BGE)

一句话总结

Buffer 记住所有,Window 记住最近,Summary 记住重点,Vector 记住你想问的。

根据你的场景选择合适的策略,或者组合使用——没有最好的记忆,只有最适合的记忆。

系列预告

这是 LangChain.js 工程化实战 系列的第 0 篇(基础篇),完整学习路径:

篇序主题状态
第 0 篇四种记忆策略(本文)✅ 已完成
第 1 篇RAG 检索增强生成✅ 已完成
第 2 篇Prompt Engineering✅ 已完成
第 3 篇LangChain 工程模块化✅ 已完成
第 4 篇LangGraph 多步推理✅ 已完成
第 5 篇Agent 多工具调用✅ 已完成

系列主线:如何把 LLM 从一个「聊天工具」,变成一个「可控的工程系统」。

项目源码:learn-langchain.js 核心文件:src/server/memory-strategies.ts

四种记忆策略(零)
/posts/2026-7-1/0-四种记忆策略/
Author
Atopos
Published at
2026-07-01
License
CC BY-NC-SA 4.0

Some information may be outdated