工作原理

为什么上下文一长,智能体就变笨

把「上下文腐化」讲具体。每一轮都是一次自包含的请求;请求一长、或者塞满工具往返,答案的准确率就实测下降。这一页讲清请求里装着什么、装不下时先丢什么,以及我们为什么在作答前把它重新渲染一遍,而不是让模型去总结工具结果。

周一还答得好好的智能体,周五开始说话含糊。二十条消息之前遵守的指令,它不遵守了。它调了工具、拿回了正确的数据,然后写出一个无视其中一半内容的答案。不报错,不留日志,对话看上去一切正常。这通常被叫作上下文腐化(context rot),而最常见的那个解释——「上下文窗口满了」——不是我们测出来的结论。

一轮对话,从头到尾

一轮从一次请求开始,在工具运行期间变胖,然后在作答之前被重建。中间那个状态最值得看:绝大多数智能体正是拿它来作答的。

one turn
sentsystemhistoryquestionafter the tools ransystemhistorytool callresult 38 kBtool call · resultdropanswered fromsystemrecapfacts foundquestionanswer
请求发出,工具运行、脚手架不断堆积,然后最终答由一份重新装配的干净请求生成——系统指令、一小段上文、本轮查到的事实,以及问题。

每一轮都是一次自包含的请求

模型在两轮之间不保留任何东西。对面没有会话,不记得一小时前说过什么,也没法自己去查任何没交到它手上的东西。每一轮,平台都装配出一份完整的请求整体发过去;回复只由这份请求生成,没有别的来源。

如果你有网络协议方面的直觉,这很接近一个数据报:自包含、发出去一次、带齐接收方需要的一切,并且受一个发送方必须遵守的大小限制。有两处这个类比不成立,而且两处都要紧:

  • 传输中不丢东西。 你发的全都到了。变的是模型读进去了多少——丢失发生在接收方内部,不在链路上。
  • 排布会改变答案。 路由器不会因为你重排了载荷就换个行为,模型会。同样的事实、同一次请求、两种排法,一种给出正确答案,另一种给出错的。后面大半内容都是由这个结果引出来的。

请求里装着什么

one requestsent whole, every turn
系统
智能体的人格与任务永不丢
它是谁、绝对不能做什么、这次在办的事
技能索引永不丢
每个技能一行,它才知道自己能加载什么
你的知识库永不丢
你的文档这一轮贡献出来的片段
检索到的片段第 2 批裁
搜索返回的内容,得分高的在前
客户记忆第 2 批裁
关于这个客户的长期事实
滚动摘要第 3 批裁
更早那些轮次讲了什么
客户画像第 3 批裁
偏好与长期背景
回复语言永不丢
问题之前的最后一块,才不会被前面盖过
历史
更早的轮次第 1 批裁
原文,从最旧的开始出局
用户
本轮的问题永不丢
以及随它上传的图片或文件
上下文窗口 − 预留输出 − 余量 = 一次请求可以用掉的量

顺序就是模型看到的顺序。右侧那一列是装不下时平台采用的优先级;名次是相对的,不代表固定丢掉几块。

历史以上的全部内容是同一条系统消息。这是有意的:智能体的边界、你的知识库和回复语言都不是对话,而模型会因为它们摆在哪里而区别对待。

装不下的时候,先丢什么

超出预算的请求不是从尾巴上截断,而是按优先级一层层丢,直到装得下:

  1. 最旧的历史先丢——而且被丢掉的轮次会被压进滚动摘要,不是扔掉,所以说过的东西留下来了,只是措辞没留下。
  2. 先丢分数最低的记忆,再丢分数最低的检索片段——从尾部开始丢,最相关的材料最后才走。
  3. 然后是画像,再然后是摘要。
  4. 绝不丢智能体的指令、你的知识库和问题本身。 万一光是问题就撑爆了预算,它会被截断并标明已截断——告诉模型,而不是让它去猜一句话为什么断在半个词上。

这个顺序是上下文预算里诚实的那一部分:总得有东西要走,而一个不肯说清顺序的系统,只会悄悄丢掉碰巧排在最后的那些。

装得下不等于读得进

意外正在这里。我们造了一个测试,每种条件下答题所需的事实都完整在场——没有任何东西被丢弃、被截断,哪里都不缺信息。唯一变的是请求怎么排布。任务是在几百条记录里做多条件过滤,分别打我们自己的本地模型和一个云端模型,每种条件跑若干次:

同样的事实怎么排布正确
一条干净的消息,只装相关的那几行8 / 8
完全相同的那几行,改由工具结果返回0 / 8
整份记录集,外加五份无关的工具结果0 / 6
把上面那堆累积重新渲染成一条干净消息8 / 8

两个对照排除了显而易见的解释。把那条干净请求拆成两条等长的消息,仍然 8 / 8,所以变量不是长度;把蒸馏后的那几行改用工具结果递回去,仍然 0 / 8,所以也不是「数据得离问题更近」。工具脚手架和无关材料无论摆在哪,都在抢模型的注意力。

云端模型更稳,但并非免疫:多数任务上它扛住了,可一旦请求里塞进一轮累积的工具结果,单点查找也掉到 0 / 3。

有两条限制必须说明。这是我们自己的测试、自己的任务,不是公开基准。而且它不提升模型的能力上限:同一任务加难一档后,所有排布下都是零分,因为那个模型根本做不了。重排请求找回的是排布本身害你损失的准确率,买不到模型没有的能力。

干活和作答,是两次不同的请求

这就是页面开头那个动画在讲的分界。

干活需要那份乱的请求。模型必须看见自己的工具调用和原始结果,才能决定要不要再调一个。这一段没变。

作答不需要。工具跑完之后,平台重新装配一份请求——同样的系统指令、一小段对话上文、本轮真正查到的事实,以及问题——你收到的回复由它生成。工具调用、原始载荷和中间推理都不出现在里面。

上文之所以要留,是因为真实的问题常常就是一句「是的」或者「那第二个呢」。测得最好的那种单条消息排布是单轮的,而对话不是;上文被折进同一条消息,而不是恢复成独立的几轮,实测没有代价,却让指代仍然接得上。

我们故意不做什么

对付工具输出撑爆上下文,业界常见的解法是让一个模型先把每条结果总结一遍。我们不这么做,理由就是上面那个测试:拿满分的那一档用的是原样数据重新渲染——不需要任何总结步骤就把准确率找回来了。加一个总结步骤,等于请一个模型去判断哪些事实要紧,而这套系统的整个设计原则就是不把代码能精确完成的活交给模型。一个恰好丢掉了客户所问那个数字的总结器,失败时悄无声息,看上去还像个好答案。

当事实确实装不下时,它会被截断,并且在请求里写明已截断,附带一条不许自行补全缺失部分的指令。一个说「我只看到了前四十笔订单」的智能体,比一个自信地编出其余部分的智能体值钱。

这对你意味着什么

没有任何东西需要配置。上面这些就是平台上每一个智能体的运行方式。

它在实际使用中改变的是:

  • 长对话仍然可用。 第二十轮和第一轮的装配方式一样,滑出窗口的部分以摘要形式留下来,而不是凭空消失。
  • 返回大量数据的工具不会毒化回复。 结果有封顶,而最终答由一份已经不带原始载荷的请求写出。
  • 调过工具的一轮多花一次模型调用——短回复上大约一秒。跑这一段时聊天界面会显示它在做什么,而不是干坐着。

如果你确实看到智能体无视了某个你确信给过它的东西,有用的问题不是「上下文窗口是不是太小」,而是「它当时在哪一块里,请求里还有什么和它挤在一起」。一段对话在两轮之间存在哪里回答了这一页的另一半——既然模型什么都不留,那我们这边留着什么,请求又是从什么装配出来的。有据可查的回答讲检索到什么,逐客户记忆讲记住什么,工具与 MCP讲那些让单条工具结果吃不掉整个窗口的预算。