1. 第一篇:Embedding与向量语义——大模型是怎样“理解”文字的?
  2. 第二篇:Transformer的核心思想——Attention机制直观理解
  3. 第三篇:大模型为什么会有“幻觉”——从训练方式到推理局限
  4. 第四篇:Prompt Engineering——从随意提问到工程化调用
  5. 第五篇:RAG检索增强生成——让大模型学会“开卷作答”
  6. 第六篇:大模型的“记忆”——从上下文窗口到会话管理
  7. 第七篇:大模型API调用——从Token到流式输出
  8. 第八篇:LangChain不是“套壳”——它解决了什么实际问题

前言

在前面的文章中,我们理解了RAG如何让大模型基于外部文档回答问题。但还有一个关键问题没有解决:多轮对话

你肯定见过这样的场景——用户问"Java线程池有哪些参数",AI回答后,用户追问"第二个参数怎么设置"。AI必须知道"第二个参数"指的是什么,才能正确回答。这就是上下文记忆要解决的问题。

在课程问答项目中,我用ConversationBufferMemory实现了多轮对话。但面试官可能会追问:“为什么选BufferMemory而不是SummaryMemory?如果对话持续100轮怎么办?” 本文就是帮你彻底搞懂大模型"记忆"的底层原理,让你能回答这类追问。

本文核心问题:

  1. 大模型本身有记忆吗?为什么每次对话都需要"提醒"它之前说了什么?
  2. 上下文窗口是什么?128K的窗口到底够不够用?
  3. ConversationBufferMemory的底层原理是什么?它有什么致命缺陷?
  4. ConversationSummaryMemory是怎么做摘要的?和BufferMemory比优劣在哪?
  5. 为什么你选择了BufferMemory而不是SummaryMemory?
  6. 上下文太长会出现什么问题?大模型会"遗忘"对话开头的内容吗?
  7. 如何平衡"记住更多"和"成本控制"?
  8. 除了LangChain提供的方案,还有什么更高级的记忆管理策略?

读完本文,你将对大模型的上下文管理拥有从原理到实践的完整理解。


一、大模型本身没有记忆

疑问:ChatGPT不是能记住刚才聊了什么吗?为什么说大模型没有记忆?

回答:大模型本身是"无状态"的——每次调用都是独立的。它之所以看起来"记得",是因为我们把之前的对话内容拼在了Prompt里。

1.1 无状态的大模型

第一次调用:
  Prompt: "Java线程池有哪些参数?"
  回答: "有corePoolSize、maximumPoolSize、keepAliveTime等..."

第二次调用(没有记忆):
  Prompt: "第二个参数怎么设置?"
  回答: "请问您指的是哪个参数?我不清楚您之前问了什么。"

第二次调用(有记忆):
  Prompt: "之前的问题:Java线程池有哪些参数?回答:有corePoolSize、maximumPoolSize...
           现在的问题:第二个参数怎么设置?"
  回答: "maximumPoolSize的设置需要考虑CPU核数、任务类型、队列容量..."

本质:大模型不是"记住了",而是我们把"记忆"手动拼在了Prompt里,让它重新"读"了一遍。

1.2 为什么大模型不自己记住?

大模型不存储对话状态的主要原因:

  • 架构限制:Transformer每次前向传播是无状态的,没有"状态缓存"来记住上一轮的信息
  • 商业考量:如果每个用户都占用GPU显存存储对话状态,服务提供商将无法支撑海量用户
  • 实现成本:无状态架构可以通过水平扩展支持更多用户,而有状态则需要复杂的会话管理

所以,"记忆"这个功能是由调用方在外部实现的——这就是会话管理的本质。


二、上下文窗口——大模型的"工作台"

疑问:上下文窗口是什么?为什么说它是"工作台"而不是"记忆库"?

回答:上下文窗口是大模型每次处理文本的容量上限。它更像一个"工作台"——每次调用时,我们把所有相关资料放在台面上,大模型在这个台面上工作。

2.1 上下文窗口的本质

上下文窗口 = 一次Prompt中能包含的Token总数(输入+输出)

GPT-4:128K Token ≈ 约10万汉字
GPT-3.5:4K ~ 16K Token
本地开源模型:通常 2K ~ 8K Token

Token是模型处理的最小单位:英文大约1个单词≈1-2个Token;中文一个汉字≈1.5-2个Token。128K窗口理论能装下一本中篇小说,但这个空间不是全部给"记忆"的——它还要装Prompt指令、检索到的文档、系统消息和即将生成的回答。

2.2 上下文窗口不是越大越好

窗口大小影响了多个维度:

窗口大小 优势 劣势
大窗口(128K) 能装更多上下文 成本高(按Token计费)、回答变慢、注意力稀释
小窗口(4K) 快、成本低 多轮对话容易被截断
中等(16K) 大多数场景的平衡点 极端长文档和长对话场景仍不够

2.3 注意力稀释效应

大模型对上下文窗口中的信息关注度并不均匀。通常开头和结尾的信息最受关注,中间的信息容易被忽略。

这意味着即使128K窗口能装下所有对话历史,把100轮的对话全塞进去,模型对第30-70轮的信息也可能关注不足。窗口容量是必要条件,但不是答案质量的充分条件。


三、ConversationBufferMemory——全量记忆的利与弊

疑问:BufferMemory是怎么工作的?你在课程问答项目中为什么选它?

回答:BufferMemory的逻辑很简单——把每轮对话都保存起来,下次提问时全部拼进Prompt。关键词:全量、简单、但不可持续。

3.1 底层原理

// 第一轮
用户: "Java线程池有哪些参数?"
AI: "有corePoolSize、maximumPoolSize、keepAliveTime、workQueue..."
memory → 存储一条:"用户: Java线程池有哪些参数?AI: 有corePoolSize..."

// 第二轮
用户: "第二个参数怎么设置?"

// 实际发给大模型的Prompt
"""
之前的对话:
用户: Java线程池有哪些参数?
AI: 有corePoolSize、maximumPoolSize、keepAliveTime、workQueue...

当前问题:第二个参数怎么设置?
"""
AI → 知道"第二个"指的是"maximumPoolSize"
memory → 追加存储

// 第三轮同理,之前的对话全部拼接...

3.2 为什么在课程问答项目中选它?

课程问答场景的特点:
  - 对话轮次较短(通常是3-5轮)
  - 每轮回答比较简洁(不超过300字,这是Prompt里约束好的)
  - 总Token量好控制(3-5轮对话在16K窗口内完全够用)
  - 需要精确理解上下文(技术问答中,"第二个参数"这类指代很常见)

BufferMemory在这个场景下刚好够用:
  - 实现简单,不需要额外的摘要模型
  - 信息完整,不会因为摘要丢失技术细节
  - 成本可控,短对话的Token量不会爆炸

3.3 BufferMemory的致命缺陷

对话轮次 → Token增长 → 问题和后果

第10轮:约4000 Token → 仍然正常
第50轮:约20000 Token → 开始变慢、成本上升
第100轮:约40000 Token → 注意了稀释、回答质量下降
第200轮:约80000 Token → 超出窗口被截断、丢失早期对话

根本问题:BufferMemory的Token消耗随对话轮次线性增长,而早期对话的价值随时间衰减。到某一轮后,大量的历史内容对当前问题的帮助微乎其微,反而挤占了真正有用的上下文空间。


四、ConversationSummaryMemory——以精度换空间

疑问:如果对话超过50轮,BufferMemory撑不住了,怎么办?

回答:用SummaryMemory——把长对话历史压缩成摘要,只保留要点。

4.1 底层原理

// 当对话超过一定轮次时,触发摘要
String summary = """
    用户询问了Java线程池的核心参数。AI解答了corePoolSize、
    maximumPoolSize、keepAliveTime和workQueue的含义及配置方法。
    用户追问了最大线程数与CPU核数的关系。AI建议IO密集型任务
    设置为核心数的2倍,CPU密集型任务设置为核心数+1。
    用户...
    """;

// 新一轮提问时,只把摘要+最近的几轮对话拼进Prompt
String prompt = f"""
    对话历史摘要:{summary}
    
    最近的对话:
    用户: 那么workQueue有哪几种选择?
    AI: LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue...
    
    当前问题:{question}
    """;

4.2 优劣对比

维度 BufferMemory SummaryMemory
信息完整性 完整保留 压缩,可能丢失细节
Token消耗 持续线性增长 控制在一个范围内
长对话支持 受限 理论上可以支持无限轮
实现复杂度 极低 需要额外的摘要模型调用
延迟 无额外耗时 摘要生成需要时间
回答精度 引用精确 可能因摘要偏差导致错误
成本 Token持续增加的API费用 摘要调用一次的API费用 + 被压缩后仍持续的Token费用

4.3 什么场景选SummaryMemory?

  • 用户和AI的对话很长(超过20轮)
  • 对话内容多为闲聊、咨询,而非精确的技术参数
  • 对回答的细节精度要求不是极致(摘要丢失的细节不影响用户体验)
  • 愿意接受额外的摘要生成耗时和费用

4.4 课程问答项目中为什么没选它?

课程问答场景中,每轮对话都涉及精确的技术细节——“第二个参数怎么设置"这类问题,完全依赖上一轮回答中的精确信息而非主题摘要。SummaryMemory会把"maximumPoolSize"的详细配置压缩成"讨论了最大线程数的设置”,丢失了"第二个参数"和"maximumPoolSize"之间的精确映射,后续追问就答不准了。


五、上下文太长会出现什么问题?

疑问:BufferMemory的上下文越长,除了成本高,还有什么隐藏问题?

回答:三个隐藏问题——“迷失中间”、注意力分散、成本叠加。

5.1 "迷失中间"效应

大模型对上下文开头和结尾的内容关注度较高,对中间部分关注度较低。当对话历史长达几万Token时,早期对话被压在中间区域,模型可能"读不进去"。一个在第3轮确认过的信息,到了第40轮可能被模型遗忘——不是模型本身的问题,而是上下文结构决定的注意力分布。

5.2 注意力被无关信息分散

早期对话中的信息,大部分与当前问题无关。但这堆历史持续占据着上下文空间,模型在生成回答时需要"穿过"它们才能找到后来的关键信息。信息密度被稀释了——有用的信息被大量无用信息包围,模型更容易遗漏或误读。

5.3 成本问题

GPT-4每百万Token输入约30美元。如果多轮对话每次输入8000Token,100次提问就是80万Token——约24美元。而实际对当前问题有用的信息,可能只占这8000Token中的10%。90%的费用花在了"无用的记忆搬运"上。


六、记忆管理的平衡之道

疑问:所以到底该怎么管记忆?有哪些更高级的策略?

回答:核心原则——“不该记住的果断忘,该记住的保证记住”。这不是一句口号,而是可以工程化实现的设计决策。

6.1 滑动窗口记忆

只保留最近N轮对话——旧对话直接丢弃。简单粗暴,但有效。

// 只保留最近10轮对话
int windowSize = 10;
List<Message> recentMessages = conversationHistory
    .subList(Math.max(0, history.size() - windowSize), history.size());

适用场景:对话话题会转移,旧信息对当前几乎无用的场景。

6.2 混合记忆(Buffer + Summary)

近期对话全量保留,远期对话生成摘要。

// 最近5轮:全量保留
List<Message> recent = history.subList(history.size() - 5, history.size());
// 5轮之前:摘要保存
String distant = summaryModel.summarize(history.subList(0, history.size() - 5));

String prompt = f"""
    历史对话摘要:{distant}
    最近对话:{recent}
    当前问题:{question}
    """;

这是生产环境中最常用的方案。细节保留和成本控制之间找到了一个可操作的平衡——最近几轮保持完整性支撑追问,远期只保留主题脉络释放空间。

6.3 实体记忆

只记住对话中提到的关键实体(人名、参数名、配置值),而不是整段对话。

这需要额外的命名实体识别(NER)能力,实现复杂度高于前两种方案。但在知识密集型的专业场景中(如医疗问诊、法律咨询),按实体来组织记忆可以让检索和引用更精准。


七、课程问答项目的记忆架构

疑问:在课程问答项目中,你的记忆是怎么设计的?为什么这么设计?

回答:方案选型基于场景特征——短对话、技术问答、需要精确引用。最终选择了BufferMemory + 对话轮次上限的组合。

ConversationBufferMemory memory = new ConversationBufferMemory();
memory.setMaxTokens(4000);  // 上限:约2000字的中文对话历史

ConversationalRetrievalChain chain = new ConversationalRetrievalChain(
    llm,           // 大模型
    retriever,     // RAG检索器
    memory         // 记忆
);

设置4000 Token上限的原因

  1. 课程问答通常3-5轮结束——学生问一个概念,追问一两个细节,问题就解决了。4000 Token足够覆盖这种典型场景
  2. 超过上限自动截断最早的对话——防止Token溢出被OpenAI拒绝或丢失更重要的近期对话
  3. 配合Prompt中的"回答不超过300字"约束——限制每轮回答长度,延长记忆有效轮次

学生追问"第二个参数怎么设置"时,完整链路

用户: "Java线程池有哪些参数?"
→ Chain检索课程文档 → 拼接Prompt → LLM生成回答
→ memory存储这轮对话

用户: "第二个参数怎么设置?"
→ memory取出上一轮对话 → 拼接到当前Prompt
→ LLM看到上文,理解"第二个参数" = "maximumPoolSize"
→ Chain检索"maximumPoolSize"的课程文档 → 生成回答

总结

  • 大模型本身没有记忆,每次调用都是独立的。我们通过Prompt拼接历史对话来模拟"记忆",本质是在输入端重建对话上下文
  • 上下文窗口是大模型的"工作台"——容量决定了工作台上能放多少材料。不是越大越好,还要考虑成本、速度和注意力稀释
  • ConversationBufferMemory适合短对话(<20轮)、需要精确上下文的场景,优势是信息完整,缺陷是Token线性增长不可持续
  • ConversationSummaryMemory适合长对话、非精确应用,能控制成本,但会丢失细节。"第二个参数"这类精确引用可能在摘要过程中丢失
  • 上下文过长带来的问题:迷失中间(模型忽略中间的信息)、注意力稀释(信息密度下降)、成本叠加(多少轮对话乘上每轮的Token费用)
  • 记忆管理的核心是取舍——近期全量(Buffer)+远期摘要(Summary)的混合策略是生产环境中最实用的方案
  • 课程问答项目选BufferMemory的原因:短对话、技术问答、需要精确引用。未来如果扩展为多轮的长对话系统,可以用混合记忆策略平滑升级

下一篇预告:AI理论学习(七)——大模型API调用:从Token到流式输出。拆解Token是什么、为什么按Token计费、温度参数如何控制输出、SSE流式输出的底层原理。

更多推荐