Amit Shekhar
2026-07-21
R
原文
---
title: "上下文压缩是如何工作的?"
author: "Amit Shekhar"
source_url: "https://outcomeschool.com/blog/how-does-context-compaction-work"
published_at: "2026-07-21T00:00:00.000Z"
fetched_at: "2026-07-21T14:51:39Z"
updated_at: "2026-07-22T12:37:13Z"
language: "zh"
review_status: "reviewed"
---
# 上下文压缩是如何工作的?

本文将介绍大语言模型中的上下文压缩,以及它是怎样工作的。我们还会看看:长对话为什么会占满上下文窗口,怎样把对话中的旧消息概括成更短的摘要,同时保留重点,以及实际的 AI agent 会在哪里用到上下文压缩。
本文将介绍以下内容:
- 什么是大语言模型
- 什么是上下文窗口
- 什么是上下文
- 长对话带来的问题
- 最容易想到的办法,以及它为什么行不通
- 什么是上下文压缩
- 如何通过摘要实现上下文压缩
- 分步演示
- 用代码实现上下文压缩
- 实际 AI agent 中的上下文压缩
- 为什么上下文压缩很重要
我是 **Amit Shekhar**,[Outcome School](https://outcomeschool.com) 的创始人。我教过并指导过许多开发者,他们凭自己的努力拿到了高薪技术工作。我也帮助多家科技公司解决过各自的问题,并开发了多个被知名公司采用的开源库。我热衷于通过开源项目、博客和视频分享知识。
我在 Outcome School 教授 [AI 和机器学习](https://outcomeschool.com/program/ai-and-machine-learning)。
我们开始吧。
### 什么是大语言模型
在介绍上下文压缩之前,我们需要先了解什么是大语言模型。
**大语言模型(Large Language Model,LLM)** 是一种读取文本并预测下一个词的模型。
简单来说,它是一台很会猜词的机器。我们给它一些词,它会猜下一个词是什么;然后把猜出的词加到文本里,再继续猜。这个过程不断重复,每次生成一个词。
假设我们输入“I want to learn”(我想学习……)。模型猜测下一个词是“machine”(机器),于是文本变成“I want to learn machine”。模型再把整段文字读一遍,继续猜下一个词。长回答就是这样一个词接一个词生成的。
**注意:** 模型实际处理的是一种叫作 **token** 的文本小单元。一个 token 可以是完整单词、单词的一部分,甚至单个字母。为了便于理解,我们可以暂时把 token 看作一个词。
大语言模型就是这样工作的。下面来了解上下文窗口。
### 什么是上下文窗口
模型无法读取无限多的文本。它一次能容纳的词数有上限。
**上下文窗口**是模型在同一时刻最多能够保留的 token 数量。
简单来说,上下文窗口就是模型短期记忆的容量。如果上限是 4000 个 token,模型最多只能在上下文中保留最近的 4000 个 token,再往前的内容就放不下了。
可以把上下文窗口想成一块小白板。白板上能写下的内容有限,一旦写满,就必须先擦掉一些内容,才能再写新的东西。
所以,上下文窗口的大小是固定的。我们接下来要解决的问题,根源就在这里。下面看看,这个窗口里实际装了些什么。
### 什么是上下文
我们一次发送给模型的全部内容,统称为 **上下文**。
简单来说,上下文就是模型回答问题前需要读取的全部文本。对当前任务来说,它就是模型的工作记忆。
也就是说,上下文是模型此刻需要查看的所有文字。在聊天中,它包括系统指令、之前的每条消息,以及我们刚刚提出的新问题。
假设我们正在和聊天机器人交谈。上下文就是截至目前的完整对话记录。模型每次回复前都会重新读取这份记录,因此它才能记得我们之前说过什么。
这里有一个重点:上下文装在上下文窗口里。窗口是盒子,上下文就是我们放进盒子里的东西。
决定上下文里放什么、怎样管理这些内容,本身就是一门学问。我们另有一篇文章深入介绍[上下文工程](https://outcomeschool.com/blog/context-engineering)。
以上就是上下文的工作方式。下面看看,为什么我们需要上下文压缩。
### 长对话带来的问题
假设我们正在构建一个聊天机器人或 [AI 编码助手](https://outcomeschool.com/blog/how-does-claude-code-work),并希望连续使用它几个小时。对话会越来越长,我们发出的每条消息和模型给出的每个回答都会加入上下文。
问题在于,装上下文的盒子大小固定,对话却会不断增长。盒子迟早会被装满。
下图展示了盒子是怎样一点点装满的:
```text
Context window (a fixed box that holds 8 messages)
Early in chat: [ msg1 msg2 ........................ ] plenty of room
Later in chat: [ msg1 msg2 msg3 msg4 msg5 msg6 .... ] filling up
Very long chat: [ msg1 msg2 msg3 msg4 msg5 ... msg8 ] FULL, no room
New message msg9 arrives -> it does not fit. Now what?
```
可以看到,对话越长,盒子里的内容就越多。如果新消息到来时盒子已经满了,就必须先删掉一些旧内容,才能把新消息放进去。
上下文溢出会带来三个问题。第一,模型放不下新消息,于是会报错或截断旧文本。第二,上下文中的内容越多,模型运行得越慢、成本也越高,因为它每一轮都要读取更多 token。第三,过多的旧文本可能干扰模型,让它抓不住当前真正重要的内容。
所以问题是:怎样才能让对话持续几个小时,又不让这个盒子溢出?
### 最容易想到的办法,以及它为什么行不通
最容易想到的办法很简单:盒子满了之后,丢掉最旧的消息,只保留最近的消息。这叫作 **滑动窗口(sliding window)**。
简单来说,滑动窗口只保留最近的一批消息。随着窗口向前移动,旧消息会从另一端掉出去,就像坐在行驶的火车上,看着旧景色不断从窗外退去。
来看一个例子。假设盒子只能容纳 4 条消息,对话内容如下:
```text
msg1 msg2 msg3 msg4 msg5 msg6
```
这里共有六条消息,但盒子只能容纳四条。msg5 到来时,我们丢掉 msg1;msg6 到来时,再丢掉 msg2。这样一来,盒子里始终只有最近 4 条消息。
听上去很完美,但问题在于,旧消息里往往保存着我们仍然需要的信息。
假设 msg1 是“My name is Amit and I am building an Android app in Kotlin.”(我叫 Amit,正在用 Kotlin 开发一款 Android 应用。)几小时后,我们问:“我用的是什么编程语言?”如果 msg1 已经被删掉,模型就无从得知答案。这个信息已经丢失,模型要么猜错,要么说不知道。
这种方法的问题在于,丢掉旧消息,也会丢掉其中的重要信息。我们失去的不只是文字,还有记忆。下面看看另一种办法如何解决这个问题。
如果你想深入学习上下文工程、agent 的记忆与架构,并从零构建一个 AI 编码 agent,可以了解 Outcome School 的 [AI 和机器学习课程](https://outcomeschool.com/program/ai-and-machine-learning)。
### 什么是上下文压缩
这时,上下文压缩就派上用场了。
**上下文压缩**会把之前的对话浓缩成一份简短摘要,让重要信息保留下来,也让盒子里重新有了空间。
简单来说,我们不直接删除旧消息,而是把它们压缩成一份简短摘要,用它替换大段旧文本。盒子里的内容变少了,重要信息却保留下来。
为了便于理解,我们把这个术语拆开来看。
**上下文压缩 = 上下文 + 压缩**
“上下文(Context)”是模型需要读取的全部文本,“压缩(Compaction)”就是把这些文本变得更短。因此,上下文压缩就是在保留重点的前提下,减少模型需要读取的内容。
假设我们用 20 页日记记录了一天的经历。到了第二天,我们不再需要全部 20 页,只需写一份半页长的摘要,留下重要事件,再丢掉原来的 20 页。我们会失去一些细节,但仍然记得事情的来龙去脉。这半页摘要就是压缩后的上下文。
可以把它想成收拾行李箱:
| 收拾行李箱 | 上下文压缩 |
| --- | --- |
| 行李箱 | 上下文窗口(大小固定) |
| 所有衣物 | 完整对话记录 |
| 衣物多得装不下 | 上下文窗口逐渐装满 |
| 折叠衣物并用真空袋压缩 | 为旧消息生成摘要 |
| 收拾整齐的行李箱 | 压缩后的上下文 |
| 丢掉垃圾、留下衣物 | 丢掉无关内容、保留重要信息 |
可以看到,行李箱装满时,我们不会把衣服扔掉,而是把它们叠好、压紧,腾出空间。上下文压缩对文字做的也是同一件事。
这就是本文的核心。接下来看看,上下文压缩具体是怎样实现的。
### 如何通过摘要实现上下文压缩
我们通过 **生成摘要** 来缩短旧文本。
简单来说,就是让模型自己读一遍旧消息,把其中的重点整理成一份简短摘要。
巧妙之处在于,负责回答问题的那个大语言模型,本身也很会做摘要。所以,这两项工作可以交给同一个模型。我们只要告诉它:“阅读这段旧对话,写一份保留重要信息的简短摘要。”它就会返回一份很短的摘要。
假设旧消息共有 3000 个 token。我们把它们交给模型,请模型生成摘要。模型返回一份只有 200 个 token 的摘要,其中仍然保留着用户姓名、项目、所用的编程语言,以及此前作出的决定。
接下来,我们删掉原来那 3000 个 token 的旧消息,只留下这份 200 个 token 的摘要。这样就腾出了 2800 个 token 的空间,同时保留了重要信息。整个方法就是这么简单。
可以用下面的示意图表示这一流程:
```text
Old messages The model Short summary
(3000 tokens) (summarizer) (200 tokens)
+-------------+ +-----------+
| msg1 msg2 | --> +-----------+ --> | key facts |
| msg3 ... old| | read and | | name |
| raw history | | summarize | | project |
+-------------+ +-----------+ +-----------+
Throw away 3000 raw tokens, keep the 200-token note.
```
可以看到,旧消息送进模型后,出来的是一份很短的摘要。也就是说,负责回答问题的模型,也能替我们把旧内容压缩成一份摘要。
**顺便提醒一句**
无论你从事哪个技术领域,都应该熟悉下面这些主题:
- LLM
- RAG
- MCP
- Agent
- 微调
- 量化
我们用一个视频把这些内容串到了一起:
[AI 工程详解:LLM、RAG、MCP、Agent、微调与量化](https://www.youtube.com/watch?v=lnfWvX66FUk)
不用现在停下来去看视频——先收藏,等有空再看。以后你会感谢现在的自己。
现在回到正题。
还有一点:这个过程可以反复执行。下次盒子再装满时,我们把上次压缩之后积累的那些消息,连同已有摘要一起交给模型,再生成一份新的、甚至更短的摘要。以后每次盒子装满,都可以重复这个过程。
所以,生成摘要是实现压缩的手段,缩短后的上下文才是结果。下面用一组具体数字看看这个过程。
### 分步演示
通过例子最容易理解。下面用一个小例子,看看这些数字是怎样变化的。
假设上下文窗口可以容纳 1000 个 token。我们设置一条规则:上下文超过 800 个 token 时,就执行压缩。
**第 1 步:** 对话不断增长。经过多轮交互,旧消息占用 700 个 token,最近的消息占用 150 个 token,总计 850 个 token。上下文已经超过 800 个 token 的阈值,因此需要开始压缩。
**第 2 步:** 我们拿出占用 700 个 token 的旧消息,让模型为它们生成摘要。模型读完这些消息后,返回一份只有 120 个 token 的摘要,其中保留了用户姓名、项目目标和关键决定。
**第 3 步:** 我们重新组成上下文:删掉占用 700 个 token 的旧消息,换成 120 个 token 的摘要;最近的消息占用 150 个 token,内容较新、细节也完整,因此原样保留。
新的上下文如下所示:
```text
Before compaction (850 tokens, over the limit):
[ old messages: 700 ] + [ recent messages: 150 ] -> 850 (too big)
After compaction (270 tokens, plenty of room):
[ summary: 120 ] + [ recent messages: 150 ] -> 270 (lots of space)
We freed up 580 tokens and kept every important fact.
```
可以看到,上下文从 850 个 token 减少到 270 个 token,腾出了 580 个 token 的空间。最近的消息保留了全部细节,旧消息中的重要信息则被浓缩进摘要。
**第 4 步:** 对话继续进行。模型现在读取这份摘要和最近的消息。它仍然知道我们的姓名和项目,因此能够正确回答。以后盒子再次装满时,只需再压缩一次。
数字就是这样变化的。下面看代码。
### 用代码实现上下文压缩
先来看最简单但存在缺陷的滑动窗口版本:
```python
def naive_sliding_window(messages, max_messages):
# keep only the most recent messages
return messages[-max_messages:]
```
这里,我们只保留最后 `max_messages` 个元素。这样会彻底丢掉旧消息,其中包含的信息也会随之消失。这个版本会忘掉之前的信息。
下面是改进后的代码。它会压缩旧内容,而不是直接删除:
```python
def compact_context(messages, recent_count, summarize):
# split the history into old and recent parts
old = messages[:-recent_count]
recent = messages[-recent_count:]
# turn the old messages into one short summary
summary = summarize(old)
# rebuild: short summary first, then the recent messages
return [summary] + recent
```
这段代码做了三件事:
- 把对话记录拆成 `old` 和 `recent` 两部分:最后 `recent_count` 条消息放进 `recent`,其余消息放进 `old`。
- 调用 `summarize(old)`,让模型把 `old` 中的消息压缩成一份简短摘要。
- 重新组成上下文:先放简短的 `summary`,再接上保留完整内容的 `recent`。
旧消息中的信息保存在摘要里,最近的消息则保留完整细节。上下文又缩短了,对话可以继续,问题也就解决了。
**注意:** 实际使用时,我们不会在每条消息后都执行压缩。只有当上下文占用达到设定阈值(例如窗口容量的 80%)时,才会执行压缩。这样可以降低成本。
以上就是这段代码的工作方式。下面看看实际的 AI 工具如何使用上下文压缩。
### 实际 AI agent 中的上下文压缩
上下文压缩并不只是一个设想。如今的 [AI agent](https://outcomeschool.com/blog/ai-agent) 和编码助手已经大量采用这种方法。
简单来说,AI agent 是一种利用大语言模型连续完成多项操作的程序,例如读取文件、运行命令和修复代码。每一步都会向上下文加入更多文字。任务持续得越久,上下文就会变得越庞大。
假设一款编码助手已经工作了一小时。它读取了几十个文件,也运行了许多命令。此时,未经压缩的历史记录已经非常庞大,盒子几乎装满。如果不进行上下文压缩,助手就会忘记任务一开始的目标。
因此,助手会在后台执行上下文压缩。它把前面执行过的步骤整理成一份简短记录,例如:“目标:修复登录 bug。已经修改 `auth.py` 和 `login.py`。测试均已通过。”接下来,它只需把这份记录和最新的执行步骤放进上下文,就能继续工作。
还不止如此。好的压缩机制会完整保留几类内容,不会用摘要替换它们:最初的系统指令、用户的主要目标,以及最新消息的全部细节。只有中间那段杂乱的内容会被压缩。
因此,现代 agent 会把上下文压缩当作一项预先设计的功能,而不是事后补救措施。它们保护最重要的部分,压缩其余内容。这样,agent 才能在长任务中连续工作几个小时,又始终不忘任务主线。
如果你想深入学习上下文工程、agent 的记忆与架构,并从零构建一个 AI 编码 agent,可以了解 Outcome School 的 [AI 和机器学习课程](https://outcomeschool.com/program/ai-and-machine-learning)。
### 为什么上下文压缩很重要
至此,我们已经完整了解了上下文压缩。最后看看它为什么重要。
- 它让聊天机器人和 agent 连续运行几个小时,而不会超出上下文窗口。
- 它能保留重要信息,只丢掉无关内容。
- 模型每一轮需要读取的 token 更少,因此成本不会太高,速度也不会太慢。
- 即使记忆容量有限,它也能让对话持续顺畅地进行下去。
这样一来,即使可用的记忆空间很小而且固定,我们仍能维持长对话,同时不忘记重要内容。
准备 AI 工程面试,可以参考:[AI 工程面试题](https://github.com/amitshekhariitbhu/ai-engineering-interview-questions)
本文到这里就结束了。
谢谢。
**Amit Shekhar**,[Outcome School](https://outcomeschool.com) 创始人