Memory管理三大策略
大模型是无状态的,每次调用独立,不记得之前聊过什么。Memory 管理就是让它"记住"。三种策略:截断(按条数或 token 数去掉旧消息)、总结(用 LLM 生成摘要替代旧消息)、检索(存入 Milvus 向量数据库,用 RAG 检索相关历史对话)。存储层有 InMemoryChatMessageHistory(短时记忆)和 FileSystemChatMessageHistory(长时记忆/持久化)。Cursor 和 Claude Code 就是用总结策略——达到 token 限制自动触发总结。之前的 memory API 已废弃,现在直接自己实现。
# Memory 管理的三大策略:截断、总结、检索
大模型是无状态的,每次调用独立,不知道之前问了什么、回答了什么。你感觉它能接着聊,是因为已经做了 Memory 管理。
# 存储层:消息存在哪
# 短时记忆(内存)
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
const history = new InMemoryChatMessageHistory();
// 添加消息
await history.addMessage(new HumanMessage('你好'));
await history.addMessage(new AIMessage('你好!有什么可以帮助你的?'));
// 读取消息
const messages = await history.getMessages();
# 长时记忆(文件持久化)
import { FileSystemChatMessageHistory } from '@langchain/community/stores/message/file_system';
const history = new FileSystemChatMessageHistory({
filePath: './chat_history.json',
sessionId: 'user_session_001',
});
// 同样的 addMessage / getMessages API
// 关闭程序后重新打开,历史消息还在
类比:Cursor 就是把对话持久化了,随时可以找到之前的聊天继续聊。
# 策略一:截断
超出限制直接去掉旧消息,保留最近的。
# 按消息条数截断
const maxMessages = 4;
const allMessages = await history.getMessages();
const trimmedMessages = allMessages.slice(-maxMessages); // 保留最近 4 条
# 按 token 数量截断
import { trimMessages } from '@langchain/core/messages';
import { getEncoding } from 'js-tiktoken';
const enc = getEncoding('cl100k_base');
function countTokens(messages) {
let total = 0;
for (const msg of messages) {
total += enc.encode(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)).length;
}
return total;
}
const trimmedMessages = await trimMessages(allMessages, {
maxTokens: 100,
tokenCounter: async (msgs) => countTokens(msgs, enc),
strategy: 'last', // 保留最近的消息
});
# 策略二:总结
用 LLM 把旧消息总结成摘要,只保留摘要 + 最近几条消息。
import { getBufferString } from '@langchain/core/messages';
async function summarizeHistory(messages) {
const conversationText = getBufferString(messages, {
humanPrefix: '用户',
aiPrefix: '助手',
});
const summaryPrompt = `请总结以下对话的核心内容,保留重要信息:\n${conversationText}\n\n总结:`;
const response = await model.invoke([new SystemMessage(summaryPrompt)]);
return response.content;
}
// 触发总结逻辑
const allMessages = await history.getMessages();
if (allMessages.length >= maxMessages) {
const keepRecent = 2;
const recentMessages = allMessages.slice(-keepRecent);
const messagesToSummarize = allMessages.slice(0, -keepRecent);
// 用 LLM 总结旧消息
const summary = await summarizeHistory(messagesToSummarize);
// 清空历史,只保留总结 + 最近消息
await history.clear();
// 可以把 summary 作为 SystemMessage 加入
for (const msg of recentMessages) {
await history.addMessage(msg);
}
}
Cursor 和 Claude Code 就是这样:达到 token 限制自动触发总结。Claude Code 还可以 /compact 手动总结。
# 策略三:检索(RAG)
把对话存入 Milvus 向量数据库,用 RAG 检索相关历史对话。
# 存储对话到 Milvus
const conversations = [
{ id: 'conv_001', content: '用户: 我叫赵六\n助手: 你好赵六!', round: 1 },
{ id: 'conv_002', content: '用户: 我在研究机器学习\n助手: 机器学习很有意思!', round: 2 },
// ...
];
const conversationData = await Promise.all(
conversations.map(async (conv) => ({
...conv,
vector: await getEmbedding(conv.content),
timestamp: new Date().toISOString(),
}))
);
await client.insert({ collection_name: 'conversations', data: conversationData });
# 检索相关历史对话
async function retrieveRelevantConversations(query, k = 2) {
const queryVector = await getEmbedding(query);
const searchResult = await client.search({
collection_name: 'conversations',
vector: queryVector,
limit: k,
metric_type: MetricType.COSINE,
output_fields: ['id', 'content', 'round', 'timestamp'],
});
return searchResult.results;
}
# RAG 流程
// 1. 检索相关历史
const retrievedConversations = await retrieveRelevantConversations(input);
// 2. 构建上下文
const relevantHistory = retrievedConversations
.map((conv, idx) => `[历史对话 ${idx + 1}]\n轮次: ${conv.round}\n${conv.content}`)
.join('\n\n━━━━━\n\n');
// 3. 用检索到的历史作为上下文调用 LLM
const response = await model.invoke([
new HumanMessage(`相关历史对话:\n${relevantHistory}\n\n用户问题: ${input}`)
]);
// 4. 把当前对话也存入 Milvus(供未来检索)
await client.insert({
collection_name: 'conversations',
data: [{ id: `conv_${Date.now()}`, vector: convVector, content: conversationText }],
});
不管之前多久聊过什么,都能通过语义检索找到相关对话继续聊。
# 三种策略对比
截断:简单粗暴,直接删旧消息 → 丢失历史信息
总结:LLM 生成摘要,保留核心信息 → 需要额外 LLM 调用
检索:向量数据库语义检索 → 最灵活,但需要 Milvus
# 要点
- 大模型是无状态的 — Memory 管理是让它"记住"的前提
- 短时记忆(内存)vs 长时记忆(持久化) — InMemoryChatMessageHistory vs FileSystemChatMessageHistory
- 截断 — 按条数
slice(-n)或按 tokentrimMessages()去掉旧消息 - 总结 — 用 LLM 生成对话摘要,Cursor / Claude Code 都用这个策略
- 检索 — 存入 Milvus,用 RAG 语义检索历史对话,最灵活
- 之前的 memory API 已废弃 — 现在直接自己实现,逻辑更清晰
- 可以组合使用 — 比如总结 + 检索:存总结到 Milvus,检索时拿到的是摘要
编辑 (opens new window)
上次更新: 2026/06/17, 14:57:34