基础

LLM 智能体的上下文窗口:里面装了什么,智能体为何会忘记规则

更新于: · Skillaria.Top

LLM 上下文窗口是模型在每次生成回复时能看到的全部文本:系统提示词、指令文件、对话历史、已读取的文件以及工具输出。窗口有一个以 token 计的硬性上限,但注意力质量远在达到上限之前就会下降,因此在长会话中智能体会开始丢失规则。短小的指令、上下文压缩、带有计划文件的新会话,以及把杂乱的工作交给子智能体,都有助于缓解这一问题。

什么是 LLM 的上下文窗口

LLM 的上下文窗口是模型在生成下一条回复时收到的全部输入文本。模型并没有人类意义上的记忆:它当下“知道”的关于您任务的一切,都在这个窗口中。不在窗口里的东西,对模型来说就不存在。

窗口大小以 token 计量,token 是词的片段。对于中文和俄文文本,一个 token 通常比英文短,因此同样的指令在中文或俄文中往往消耗更多的 token。如今的模型窗口已达数十万 token,看起来足以容纳一切。但实际上,智能体的可用空间会比预期更快耗尽,而它的表现则在更早之前就开始下降。

智能体的上下文窗口里装了什么

编程智能体有三个信息来源:描述任务的系统提示词、它读取过的文件内容,以及它调用过的工具的输出。这三者都位于同一个窗口中,彼此争夺空间。

内容何时进入通常占用多少
智能体的系统提示词与工具描述每个会话开始时固定开销,与您关系不大
CLAUDE.md、AGENTS.md 等指令文件会话开始时,整体载入从几百到几万 token,取决于您
您的消息和智能体的回复整个对话期间不断累积每说一句都会增长
已读取的文件智能体打开它们时大文件一次就是数千 token
命令输出:测试、日志、构建、grep每次调用之后往往是最大、也最无用的部分

关键在于:这些内容不会自行消失。二十步之前需要的失败测试日志,仍然留在窗口里,占用当前步骤的注意力。长会话就像一堆不断增长的草垛,要找到那根针越来越难。

为什么智能体在长会话中会忘记规则

常见的抱怨是:会话开始时,智能体认真遵守 CLAUDE.md 中的规则,但工作一小时后就开始违反它们。它并不是字面意义上“忘记”了——规则的文字仍然在窗口里。问题在于模型如何在长文本中分配注意力。

Lost in the middle(迷失在中间)

Liu 等人在 2023 年的研究表明:与中间部分相比,语言模型更擅长利用长上下文开头和结尾的信息。指令文件在会话开始时载入,但随着对话推进,上下和下方都积累了越来越多的文本。原本显眼的规则,逐渐落到模型“看得最差”的区域。

规则在窗口中的占比下降

在一个全新的会话里,指令占了上下文的相当大一部分。经过一百次工具调用之后,它们只是日志、差异和已读代码之中的一小段。信号没有变,但周围的噪音增加了几十倍。

新的例子压过旧的规则

模型很依赖对话中最近发生的事。如果智能体几次绕过了某条规则,并且没有受到惩罚,那么这种模式在历史中的分量就会超过开头某一行指令的分量。

Context rot:窗口尚未满时质量已经下降

Context rot(上下文腐化)指的是:随着窗口被填满,模型回答的质量会下降,即使距离技术上限还很远。模型形式上能接受 20 万 token,但在 15 万 token 时的表现比 2 万 token 时更差:它更容易混淆细节,重复已被否决的方案,忘记指令。

会话“腐化”的迹象包括:

  • 智能体提出了一个您和它半小时前一起否决的方案;
  • 混淆了已经讨论过的文件名和函数名;
  • 不再运行它在开始时自己运行过的检查;
  • 回答越来越长、越来越含糊,并出现多余的“顺便也修了……”。

试图靠增加一条规则来解决这个问题是没有用的:新规则同样会淹没在这些噪音中。必须减少噪音。

上下文压缩

压缩(compaction)是对会话历史的压缩:模型写出一份对已发生事情的摘要,之后对话便以这份摘要代替完整历史继续进行。窗口腾出了空间,工作的要点看似得以保留。

在 Claude Code 中,可以使用命令 /compact,并附上压缩时应关注的提示。当窗口接近上限时,Claude Code 也会自动进行压缩。命令 /clear 会完全清空历史,从零开始;/context 则显示窗口的占用情况。

/context
/compact 保留:任务目标、已做出的决定及其原因、修改过的文件列表、尚需检查的内容
/clear

压缩的危险之处

摘要总会丢失细节。被否决的方案及其原因、需求的确切表述、您顺带约定的细微之处,都可能没有被写进摘要。压缩之后,智能体面对的是对自己会话的别人写的笔记。因此,重要的内容最好不要只放在对话历史中,而应放在能够经受任何压缩的文件里。

压缩还是新会话

情况应该怎么做
任务差不多完成,只需要再腾一点空间带着保留提示进行压缩
一个任务已结束,开始另一个新会话,旧历史只会造成干扰
会话“腐化”,智能体在原地打转把状态写入计划文件,然后开启新会话
需要长时间研究代码或日志把研究工作交给子智能体

如何节省上下文窗口

节省上下文不是为了省 token 的钱,而是为了工作质量。窗口里的噪音越少,留给任务和规则的注意力就越多。

1. 短小的指令路由文件

入口文件 CLAUDE.md 或 AGENTS.md 在每个会话中都会被整体读取。一个 600 行的文件大约占用 1 到 2 万 token 的固定开销。文件里只保留检查命令、硬性限制和项目地图,各主题的细节则放在单独的文档中,由智能体在需要时再打开。

2. 按需加载的知识

在 Claude Code skills 中,上下文里首先只有技能的简短描述,只有当任务需要该技能时才会载入完整说明。这是保存不常用流程(部署、迁移、发布)的好办法,不必在每个会话中为它们付费。

3. 用子智能体处理杂乱的工作

子智能体在自己的上下文窗口中工作,只把结果返回给主会话。搜索大型代码库、分析长日志、研究陌生的模块——这些都可以交给子智能体,换回的只是几段结论,而不是数千行原始输出。

4. 安静的工具输出

命令输出是窗口的头号消耗者。应将检查命令配置为成功时只打印一行,失败时只输出关键信息:失败的测试及其消息,而不是完整的构建日志。

# 嘈杂:每次运行都有几千行进入窗口
pytest -v

# 安静:成功时一行,失败时只显示失败的测试
pytest -q --tb=short

# 日志不要整份输出,只取末尾和错误
tail -n 50 app.log | grep -i error

5. 一个任务,一个会话

不要让同一个会话贯穿三个互不相关的任务。第一个任务的历史对第三个毫无用处,却占着位置,还会迷惑智能体。完成之后,记录结果,再打开新会话。

6. 状态放在文件里,而不是历史中

凡是需要在压缩和新会话之后保留的内容,都应写入仓库:目标、已做决定及其原因、被否决的方案、已完成的工作及验证方式、下一步。例如 PLAN.md 和 PROGRESS.md。这样,新会话从一份精确的文档开始,而不是靠回忆,您也可以放心地清空历史。

# PROGRESS.md
## 目标
将折扣计算改为以整数分为单位

## 已完成(已验证)
- app/payments/discount.py:float → int,make test 通过
- 迁移 041 已添加,本地已应用

## 决定
- 舍入方式为银行家舍入,与支付服务商一致

## 已否决
- 在模型中使用 Decimal:会破坏 webhook 的序列化

## 下一步
更新下单的端到端场景,并运行 make e2e

关键规则不能只放在窗口里

即使上下文组织得再好,也无法保证规则被遵守:窗口中的文字只是对模型的影响,而不是可以执行的禁令。如果某种违规是不可接受的,这条规则就必须通过机制来检查。在 Claude Code 中,为此可以使用 hooks——在智能体事件上运行、并能阻止操作的命令——以及禁止特定工具或命令的权限设置。代码检查工具、测试和 CI 独立于智能体窗口中正在发生的事情运行。

一条良好的 harness 原则是:凡是能够自动检查的,就自动检查;上下文中只保留需要判断力的内容。

小结

LLM 的上下文窗口是智能体唯一能看到您任务的地方,而它被填满的速度比想象中快:指令、历史、已读文件和命令输出都会一直累积到会话结束。质量在达到技术上限之前就会下降,长上下文中间的规则会被遗忘,而压缩则以丢失细节为代价换取空间。应当保持指令简短,按需加载不常用的知识,把杂乱的工作交给子智能体,把状态存放在文件中,并用 hooks 和检查来保证关键规则。关于智能体上下文的管理,我们在 harness engineering 课程中结合实践进行了讲解。

常见问题

Claude 的上下文窗口有多少 token?

取决于模型和套餐,常见的参考值约为 20 万 token,某些模型和配置更大。具体数值请查看 Anthropic 对相应模型的文档。

使用更大窗口的模型能解决忘记规则的问题吗?

只能部分解决:空间更大了,但 lost in the middle 和 context rot 的效应依然存在。大窗口无法替代简短的指令和干净的会话。

智能体违反规则时,是否需要在消息中重复该规则?

一次性的提醒有帮助,因为模型对上下文末尾的新内容注意力更好。但如果需要不断提醒,就应该用检查或 hook 来保证这条规则。

如何知道 Claude Code 的上下文窗口被什么占满了?

/context 命令会显示系统提示词、工具、记忆文件和消息历史各占多少空间。这是快速查看臃肿的 CLAUDE.md 或冗长历史是否在占用窗口的方法。

压缩时最先丢失的是什么?

只存在于对话中的约定:被否决的方案、需求的细化、细微的注意事项。写在仓库文件中的内容,智能体随时可以重新读取。