为什么上下文一长,智能体就变笨
把「上下文腐化」讲具体。每一轮都是一次自包含的请求;请求一长、或者塞满工具往返,答案的准确率就实测下降。这一页讲清请求里装着什么、装不下时先丢什么,以及我们为什么在作答前把它重新渲染一遍,而不是让模型去总结工具结果。
周一还答得好好的智能体,周五开始说话含糊。二十条消息之前遵守的指令,它不遵守了。它调了工具、拿回了正确的数据,然后写出一个无视其中一半内容的答案。不报错,不留日志,对话看上去一切正常。这通常被叫作上下文腐化(context rot),而最常见的那个解释——「上下文窗口满了」——不是我们测出来的结论。
一轮对话,从头到尾
一轮从一次请求开始,在工具运行期间变胖,然后在作答之前被重建。中间那个状态最值得看:绝大多数智能体正是拿它来作答的。
每一轮都是一次自包含的请求
模型在两轮之间不保留任何东西。对面没有会话,不记得一小时前说过什么,也没法自己去查任何没交到它手上的东西。每一轮,平台都装配出一份完整的请求整体发过去;回复只由这份请求生成,没有别的来源。
如果你有网络协议方面的直觉,这很接近一个数据报:自包含、发出去一次、带齐接收方需要的一切,并且受一个发送方必须遵守的大小限制。有两处这个类比不成立,而且两处都要紧:
- 传输中不丢东西。 你发的全都到了。变的是模型读进去了多少——丢失发生在接收方内部,不在链路上。
- 排布会改变答案。 路由器不会因为你重排了载荷就换个行为,模型会。同样的事实、同一次请求、两种排法,一种给出正确答案,另一种给出错的。后面大半内容都是由这个结果引出来的。
请求里装着什么
顺序就是模型看到的顺序。右侧那一列是装不下时平台采用的优先级;名次是相对的,不代表固定丢掉几块。
历史以上的全部内容是同一条系统消息。这是有意的:智能体的边界、你的知识库和回复语言都不是对话,而模型会因为它们摆在哪里而区别对待。
装不下的时候,先丢什么
超出预算的请求不是从尾巴上截断,而是按优先级一层层丢,直到装得下:
- 最旧的历史先丢——而且被丢掉的轮次会被压进滚动摘要,不是扔掉,所以说过的东西留下来了,只是措辞没留下。
- 先丢分数最低的记忆,再丢分数最低的检索片段——从尾部开始丢,最相关的材料最后才走。
- 然后是画像,再然后是摘要。
- 绝不丢智能体的指令、你的知识库和问题本身。 万一光是问题就撑爆了预算,它会被截断并标明已截断——告诉模型,而不是让它去猜一句话为什么断在半个词上。
这个顺序是上下文预算里诚实的那一部分:总得有东西要走,而一个不肯说清顺序的系统,只会悄悄丢掉碰巧排在最后的那些。
装得下不等于读得进
意外正在这里。我们造了一个测试,每种条件下答题所需的事实都完整在场——没有任何东西被丢弃、被截断,哪里都不缺信息。唯一变的是请求怎么排布。任务是在几百条记录里做多条件过滤,分别打我们自己的本地模型和一个云端模型,每种条件跑若干次:
| 同样的事实怎么排布 | 正确 |
|---|---|
| 一条干净的消息,只装相关的那几行 | 8 / 8 |
| 完全相同的那几行,改由工具结果返回 | 0 / 8 |
| 整份记录集,外加五份无关的工具结果 | 0 / 6 |
| 把上面那堆累积重新渲染成一条干净消息 | 8 / 8 |
两个对照排除了显而易见的解释。把那条干净请求拆成两条等长的消息,仍然 8 / 8,所以变量不是长度;把蒸馏后的那几行改用工具结果递回去,仍然 0 / 8,所以也不是「数据得离问题更近」。工具脚手架和无关材料无论摆在哪,都在抢模型的注意力。
云端模型更稳,但并非免疫:多数任务上它扛住了,可一旦请求里塞进一轮累积的工具结果,单点查找也掉到 0 / 3。
有两条限制必须说明。这是我们自己的测试、自己的任务,不是公开基准。而且它不提升模型的能力上限:同一任务加难一档后,所有排布下都是零分,因为那个模型根本做不了。重排请求找回的是排布本身害你损失的准确率,买不到模型没有的能力。
干活和作答,是两次不同的请求
这就是页面开头那个动画在讲的分界。
干活需要那份乱的请求。模型必须看见自己的工具调用和原始结果,才能决定要不要再调一个。这一段没变。
作答不需要。工具跑完之后,平台重新装配一份请求——同样的系统指令、一小段对话上文、本轮真正查到的事实,以及问题——你收到的回复由它生成。工具调用、原始载荷和中间推理都不出现在里面。
上文之所以要留,是因为真实的问题常常就是一句「是的」或者「那第二个呢」。测得最好的那种单条消息排布是单轮的,而对话不是;上文被折进同一条消息,而不是恢复成独立的几轮,实测没有代价,却让指代仍然接得上。
我们故意不做什么
对付工具输出撑爆上下文,业界常见的解法是让一个模型先把每条结果总结一遍。我们不这么做,理由就是上面那个测试:拿满分的那一档用的是原样数据重新渲染——不需要任何总结步骤就把准确率找回来了。加一个总结步骤,等于请一个模型去判断哪些事实要紧,而这套系统的整个设计原则就是不把代码能精确完成的活交给模型。一个恰好丢掉了客户所问那个数字的总结器,失败时悄无声息,看上去还像个好答案。
当事实确实装不下时,它会被截断,并且在请求里写明已截断,附带一条不许自行补全缺失部分的指令。一个说「我只看到了前四十笔订单」的智能体,比一个自信地编出其余部分的智能体值钱。
这对你意味着什么
没有任何东西需要配置。上面这些就是平台上每一个智能体的运行方式。
它在实际使用中改变的是:
- 长对话仍然可用。 第二十轮和第一轮的装配方式一样,滑出窗口的部分以摘要形式留下来,而不是凭空消失。
- 返回大量数据的工具不会毒化回复。 结果有封顶,而最终答由一份已经不带原始载荷的请求写出。
- 调过工具的一轮多花一次模型调用——短回复上大约一秒。跑这一段时聊天界面会显示它在做什么,而不是干坐着。
如果你确实看到智能体无视了某个你确信给过它的东西,有用的问题不是「上下文窗口是不是太小」,而是「它当时在哪一块里,请求里还有什么和它挤在一起」。一段对话在两轮之间存在哪里回答了这一页的另一半——既然模型什么都不留,那我们这边留着什么,请求又是从什么装配出来的。有据可查的回答讲检索到什么,逐客户记忆讲记住什么,工具与 MCP讲那些让单条工具结果吃不掉整个窗口的预算。