第 6 课 · 历史会越堆越长:摆在哪、砍哪些
读这一课前你需要会什么:读过第 1、3、4 课。 你需要知道:它脑子里什么都不存、所以每轮要把过去整个重发一遍(第 1、3 课), 以及那个循环会转很多圈(第 4 课)。 出现的每一个新词都会当场用大白话讲清。讲不清楚的地方就是我的问题,请直接标出来。
这一课结束时你会
- 说得出为什么「摆在哪」比「写什么」更值钱——而且知道那个差距有多大;
- 说得出为什么改一个字会让账单翻倍;
- 说得出历史堆不下时该按什么顺序砍,以及哪一种砍法是错的。
1. 先看第 4 课那个循环转久了会怎样
第 4 课那个例子只转了三圈。把问题换成「帮我比一遍这 20 个城市的天气」呢?
第 1 圈:发出去 ~200 个 token
第 2 圈:发出去 ~400 个 ← 上一圈的调用和结果都要跟着发
第 3 圈:发出去 ~600 个
……
第 20 圈:发出去 ~15000 个
每一圈发出去的东西都包含前面所有圈。 这是第 1 课那条「它脑子里什么都不存」的账单版本。
两个后果同时发生:
| 后果 | 表现 |
|---|---|
| 越来越贵、越来越慢 | 第 20 圈发出去的东西是第 1 圈的 75 倍 |
| 迟早装不下 | 它一次能看的量是有上限的 |
这一课讲怎么对付这两件事。而且这两件事的解法方向相反—— 一个要「别改」,一个要「多砍」。
2. 顶层全景:两件事,不是一件
很多人把这一课的内容笼统叫「上下文管理」,其实是两件不同的事:
┌─ 摆在哪 ──────────────────────────────────┐
│ 同样这些内容,按什么顺序排 │
│ 管 的是:它听不听话 + 账单 │
│ 规矩:稳定的排前面,变的排后面,前面别动 │
└───────────────────────────────────────────┘
┌─ 砍哪些 ──────────────────────────────────┐
│ 装不下的时候扔什么 │
│ 管的是:塞不塞得下 + 会不会失忆 │
│ 规矩:一条从不花钱到花钱的阶梯,先便宜后贵 │
└───────────────────────────────────────────┘
图说:这两件事经常打架——砍东西会动到前面,而动到前面就毁了账单。
§4.6 讲这个冲突怎么解。
先给一个词,后面全靠它:
上下文窗口 = 它一次最多能看多少个 token。
超过这个量,请求直接被拒。但没超也不等于没事——这一点 §4.4 会讲。
3. 全程例子:那 20 个城市,每一层在哪里介入
盯住那个 20 城市的例子,看一段文字从 200 涨到 15000 的路上,各层怎么管它。 下面的数字是我为了讲清楚编的量级,不是实测值。
第 1 圈:200 个 token,一切正常
┌─ 稳定的部分 ────────────────┐
│ 系统提示 ~80 │ ← 每一圈都一模一样
│ 工具定义 ~60 │ ← 每一圈都一模一样
└────────────────────────── ───┘
┌─ 变的部分 ──────────────────┐
│ 用户那句话 ~30 │
└─────────────────────────────┘
这时候要做的唯一一件事:把稳定的排前面。 为什么,§4.3 讲。
第 8 圈:约 3000 个 token,第一层开始工作
这时候历史里已经有 7 组「调用 + 结果」。 假设其中一个城市的天气接口返回了一大段原始数据:
工具:call_007 → {…这一条就有 4000 个 token…}
第一层介入:单条结果太大,落盘,只留一小段预览 + 一句「想看全的去这儿取」。
这一层的做法,有一份资料的说法最好记(依据:04-making-it-work/dexter):
把窗口当内存,把一个只能往后追加的暂存文件当磁盘。 内存必须精简、有界;但所有工具结果的完整原文都先落磁盘,一份不丢。
第 14 圈:约 8000 个 token,第二层介入
这时候没有哪一条特别大,但加起来多了。第二层是「不花模型钱」的那些:
① 把 10 圈之前那些工具结果换成一句「已清除」的占位话
② 保留最近 4 条不动
这一层的关键是它不调模型,所以不花钱、不花时间。
第 18 圈:约 13000 个 token,第三层介入
到这里前两层不够用了,才轮到花钱的那一层:调一个便宜的模型, 把旧历史摘成一段结构化的摘要。
第 20 圈:逼近上限,最后一层
假设它的窗口是 16000。这时候剩给我们的空间不多了,而且还得留给「最后这次回答」。
这一层的做法有两种,我们选后者:
| 做法 | 结果 |
|---|---|
| 硬砍最旧的几轮 | 信息真丢了 |
| 改写最后一条,逼它现在就用手头的信息交卷 | 答案不完整但完整成句,而且基于全部证据 |
停一下:刚才那四层的顺序
| 层 | 花不花模型钱 | 有没有损 |
|---|---|---|
| ① 单条结果落盘 | 不花 | 无损(原文在磁盘上) |
| ② 旧结果换占位话 | 不花 | 半损(能从磁盘找回) |
| ③ 摘要 | 花(用便宜模型) | 有损 |
| ④ 逼它交卷 | 不花 | 不损历史,但任务提前结束 |
这个顺序不是我排的,是四份资料撞出来的同一个形状,§4.5 讲。
4. 拆开看:六件事
4.1 摆在哪:同一句话换个位置,遵从率能差三倍
先看证据,再讲原理。
有一份资料记了一次实验:同一条「如果发现需要更多信息,鼓励你继续调用工具」的指令——
| 放哪儿 | 结果 |
|---|---|
| 系统提示里讲工具的那一节 | 所有模型都照做 |
| 挪到提示开头,哪怕只隔一段话 | 经常被忽略 |
遵从率从九成掉到三成(依据:04-making-it-work/onyx)。
这个数字要带一个限定:它出自那个项目的工程笔记,不是源码里的事实。 拆解文档专门标注了这一点,我们引用时保留这个限定。
另一份资料做了一组更系统的对照(依据:04-making-it-work/shen-ru-li-jie-ai-agent):
| 只改什么 | 结果 |
|---|---|
| 语气与风格(专业中立 / 夸张自信 / 轻松加表情) | 影响相对有限 |
| 打乱信息组织(内容全保留,只去掉标题层次,把有序流程拆成无序规则) | 成功率下降超过三成 |
| 移除工具说明文字(保留函数名和参数) | 调用错误率增加四成五 |
为什么打乱组织这么致命? 那份资料给的解释很具体: 规则以无序方式呈现时,它难以识别其中的优先级和依赖—— 比如「先验证身份再处理退款」这条被拆散后,它有时就跳过验证直接退款。
原理:两个效应叠出一个凹陷区
为什么位置有这么大影响? 有一份资料给了机制(依据:04-making-it-work/prompt-engineering-for-llms):
效应一:越靠近这段文字的末尾,影响越大
效应二:开头和末尾都好回忆,塞在中间的容易被略过
两个叠加 → 开头 [强] ── 前中段 [弱] ── 末尾 [强]
↑
那份资料管这块叫「凹陷区」
图说:所有模型都有这个凹陷区,深浅和位置因模型而异。
关键内容放在凹陷区之外,可有可无的背景才放中间。
那份资料明说没有完美解法,只给了两条对策: 把关键的、高质量的内容放在凹陷区之外;以及过滤内容,让整段尽量短。
第二条把这一课的两半连起来了:砍东西不只是为了塞得下,也是为了别把好东西埋进凹陷区。
三条能直接照做的
| 做法 | 出处 |
|---|---|
| 三明治:开头和结尾各说一遍你要它做什么;结尾那次是把注意力从铺垫拉回问题上 | (依据:04-making-it-work/prompt-engineering-for-llms) |
| 最后必须硬转弯:从「说明问题」转到「解决问题」,否则它会继续往下编更多背景而不作答 | (依据:04-making-it-work/prompt-engineering-for-llms) |
| 按插槽分类,不要凭感觉插 | (依据:04-making-it-work/lobehub) |
最后那条的插槽表值得抄下来:
| 塞在哪 | 后果 |
|---|---|
| 系统消息 | 全局约束,但改一个字就让整段前面失效 |
| 第一条用户消息之前 | 内容稳定的东西(知识、记忆)放这儿,前面可复用 |