大模型 Memory 管理:它凭什么知道你之前说过什么?

《AI 知识卡片》第 17 期 · 模型自己什么都不记得,是你每次把整段聊天重新递了一遍

  换个医生看同一个毛病,你得从头把病史再讲一遍。讲久了你会自己想办法:要么只讲最近这两周的症状,要么随身带一页纸的病历摘要,要么把几年前的检查单收进一个文件夹,医生问到哪一段你就翻哪一段。

  大模型面对的也是类似的处境,而且它比换个医生忘的更彻底------它连上一句都不记得。

模型不记事,是你替它记的

  调用大模型时,除了传入当前对话的 query,还需要递过去一个 messages 数组:先放 SystemMessage 交代角色,再放 HumanMessage。模型返回 AIMessage,如果里面带了 tool_calls,你就执行工具、追加 ToolMessage、再发起调用------直到不再返回 tool_calls。

  模型每一次都是从零开始读这个数组。能接上话,全靠历史消息又被完整地递了一遍。这套「不断往数组里 push」的做法有个必然的终点:上下文有上限。所谓大模型的 Memory 管理,本质就是这个数组的预算。

  常见大模型的上下文上限:Claude Opus 5(1M)、GPT 5.6 Sol(272k)。

三种策略:截断、总结、检索

  主流做法就三种,差别在于「旧消息怎么处置」:

策略 做法 旧消息处理
截断 保留最近 N 条,或按 token 数保留最近若干条 直接丢弃
总结 让模型给旧消息生成摘要,摘要 + 最近几条一起发 压缩成一段文字
检索 历史存进向量数据库,按当前提问召回相关的那几条 挪到库里,用得上才回来

  截断最省事,按 token 截断的核心就一个 API:

javascript 复制代码
import { HumanMessage, AIMessage, trimMessages } from "@langchain/core/messages";
import { getEncoding } from "js-tiktoken";

const enc = getEncoding("cl100k_base");
const countTokens = (msgs) => msgs.reduce((n, m) => n + enc.encode(m.content).length, 0);

const messages = [
  new HumanMessage("我想去北京玩"),
  new AIMessage("北京是个很棒的选择!你打算什么时候去?"),
  new HumanMessage("大概十一黄金周,想看天安门"),
  new AIMessage("十一期间天安门广场会有花坛布置,非常壮观。你还想去哪些地方?"),
  new HumanMessage("故宫和长城也想去,帮我推荐一下路线"),
  new AIMessage("建议第一天天安门+故宫,第二天八达岭长城,第三天颐和园+圆明园。"),
];

// 按 token 预算保留最近的消息,超出的直接丢弃
const trimmed = await trimMessages(messages, {
  maxTokens: 100,
  tokenCounter: (msgs) => countTokens(msgs), // tiktoken 精确计数
  strategy: "last",                          // 保留最近的
});
// 结果:前 3 条被丢弃,只剩最近的 3 条(96 token)

  总结需多花一次模型调用:超阈值时,把旧消息交给模型压成一句摘要,再和最近几条拼回去。大多数编码助手就是这套------上下文达上限动触发总结。

javascript 复制代码
import { SystemMessage, getBufferString } from "@langchain/core/messages";

const totalTokens = countTokens(allMessages); // 247 token,超过阈值 100

// 从后往前保留最近的消息(约 80 token),剩下的交给模型总结
const recentMessages = allMessages.slice(-2);                // 最近 1 轮
const oldMessages = allMessages.slice(0, -2);                // 前 8 条要被总结
const oldText = getBufferString(oldMessages, { humanPrefix: "用户", aiPrefix: "助手" });

const summary = (await model.invoke([
  new SystemMessage(`请用一句话总结以下对话:\n${oldText}`)
])).content;
// → "用户计划十一黄金周去北京,关注天安门、故宫、长城路线及住宿预算"

// 摘要塞进 system,和最近几条一起发
const newMessages = [
  new SystemMessage("你是一个旅行助手"),
  new SystemMessage(`之前的对话摘要:${summary}`),
  ...recentMessages,
];
// 10 条 247 token → 4 条约 150 token

  检索本质上就是 RAG,只不过检索数据换成了对话记录:每轮对话算出向量写进向量库,下次提问先检索最相关的几条历史拼进 prompt。不管多久以前聊过,都能被捞出来。

javascript 复制代码
import { OpenAIEmbeddings } from "@langchain/openai";
import { MilvusClient, MetricType } from "@zilliz/milvus2-sdk-node";

const embeddings = new OpenAIEmbeddings({ model: "text-embedding-v3", dimensions: 1024 });
const client = new MilvusClient({ address: "localhost:19530" });

// 存:每轮对话写入向量库
const content = `用户: ${humanText}\n助手: ${aiText}`;
const vector = await embeddings.embedQuery(content);
await client.insert({
  collection_name: "conversations",
  data: [{ id: `conv_${roundNum}`, vector, content, round: roundNum }],
});

// 取:按当前问题召回最相关的历史
const queryVector = await embeddings.embedQuery("北京住哪里比较方便");
const results = await client.search({
  collection_name: "conversations",
  vector: queryVector,
  limit: 2,
  metric_type: MetricType.COSINE,
  output_fields: ["content"],
});
// → 召回了"住宿建议"那轮(相似度 0.63)和"去北京玩"那轮(0.59)

// 拼:召回的历史塞进 prompt
const context = results.results.map((r) => r.content).join("\n---\n");
const response = await model.invoke([
  new SystemMessage(`以下是与用户的历史对话片段,请参考回答:\n${context}`),
  new HumanMessage("北京住哪里比较方便"),
]);
// 回答完后把这一轮也写回向量库

各有各的代价

  截断最便宜,代价是真的会丢事实------三十轮前说过的名字,扔掉就是扔掉了。

  总结不丢主线但会磨掉细节,而且不可逆:摘要生成完原始消息被清空,还得额外付一次模型调用。

  检索能翻回任意久以前的内容,但要养一套向量库和 embedding 服务,召回准不准直接决定它有没有用------捞错了两条历史,比不捞更糟。

  真实项目里常见的是总结加检索一起上,比如:每聊够二十条触发一次总结,把摘要写进向量库。这样既压住了单轮的 token,又保留了长期可回溯的线索。

一句话总结

  模型无状态,Memory 管理的好坏决定了你们对话的质量和效率。

相关推荐
许雪里11 小时前
XXL-AI v1.5.0 发布|AI应用开发平台
agent·ai编程
ZhengEnCi13 小时前
Agent 工具怎么折叠海量输出才「爽」:Trae、Claude Code 的渐进式披露设计原理
前端·agent
ZhengEnCi13 小时前
Agent 面试为什么几乎不聊高并发:从瓶颈转移到考察维度的真相
agent
天诰14 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
七夜zippoe14 小时前
Agent 优雅降级实战:模型不可用时的后备方案设计
ai·agent·模型·降级·后备方案
ikoala16 小时前
让 Codex 自己查账:一个省 token 的小技巧
前端·后端·ai编程
用户20187928316716 小时前
大模型是怎么"看"图、跑命令、调 MCP 的?我把 gemini-cli 源码扒了个底朝天
agent
ikoala16 小时前
AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md
前端·javascript·ai编程
Flynt16 小时前
一夜涨近2000星的"开源Grok Bot",我在1核2GB小机上装了一遍:壳是真的,脑子得自备
开源·agent·claude
用户9432547583716 小时前
你的 Agent 会重复扣钱吗?我给 LangGraph 加了一层"步骤日志"
agent