上下文窗口已从几千个 token 增长到更大,比如一百万或更多。这意味着有更多的空间容纳更多内容、指令、工具结果和文档等。那么,如果我们考虑 AI 代理并让它们访问这个巨大的上下文窗口,它们应该在长运行任务中表现得更好吧?毕竟,上下文越多,工作做得越好。然而最近一项研究考察了 18 个前沿模型,结果发现随着上下文窗口变大,它们的可靠性反而下降。研究人员给这种现象起了一个名字:语境腐烂(context rot)。窗口中的 token 越多,模型使用它们的可靠性就越低。这正是上下文工程旨在解决的问题。
上下文工程是指在代理运行的每一步决定哪些内容真正进入这个上下文窗口。现在,代理的语境有很多出错的方式,我将涵盖四种在大型上下文窗口工作中可能遇到的失效模式。而且因为我觉得自己很贴心,我也会提出修复每种问题的方法。有趣的是,这些修复措施涉及故意丢弃信息。那么代理如何通过持有更少的上下文变得更好呢?要回答这个问题,我们首先应该看看究竟是什么填满了这些巨大的上下文窗口。
假设一个 AI 代理被赋予将老化的代码库迁移到新数据库驱动器的任务,即我们正在构建一个迁移 AI 代理。在上下文窗口的开头,最可能出现在这里的是系统提示(system prompt),这是关于代理一般如何操作的固定指令。任务本身也会作为单独的提示存储在这里,可能并不大只有几行。许多代理框架还会拉入一堆项目指令,比如解释该项目如何构建的 agents.md 文件。
随后我们可能会有工具定义,即代理可以调用的每个工具的说明。从这里开始,上下文窗口变成了一个运行日志。我们将拥有消息历史(message history),这是代理采取的每一步操作。假设运行一小时后,这会变得相当庞大,包含大量步骤。最大的部分可能是我们存储在这里的工具结果(tool results),如文件内容、测试输出、错误日志和模式转储。
任务描述提示在开头只有几行,而几乎其他所有内容都是代理沿途堆积的东西。哦,在这其中还埋藏着一个注释(note)。这是代理自己写的笔记,说新驱动器会自动重试失败的连接,因此可以删除旧的重试代码。注释基本上是说:“嘿,别费心写任何重试代码了。”我为什么要提到这个?我们稍后会回到这一点。
为什么完整的上下文窗口是个问题呢?因为模型在上下文中查找信息的方式与数据库查找行不同。窗口中的每个 token 都在争夺有限的注意力,本质上是一个注意力预算(attention budget),这可以说是问题的核心。窗口可能容纳一百万个 token,但分散在这些 token 上的注意力是有限的。那么关于那些失效模式呢?让我们从第一个开始,即语境中毒(context poisoning)。
我之前隐晦地提到代理给自己写了一条关于新驱动器自动重试失败连接的笔记。事实证明,这条笔记完全错误。代理本不该这样做。它基本上浏览了驱动器的文档,误读了内容然后写下了错误的结论,这就是中毒的含义:一个错误进入了上下文窗口的某个地方,并被当作事实处理。因为对 AI 模型来说,它自己的笔记看起来就像真理。
因此文件接一个文件地删除了完全正常的重试代码。接下来是第二个失效模式:语境分心(context distraction)。这是当历史记录的 sheer size 开始引导代理时发生的情况。语言模型本质上只是一个模式续写机器(pattern continuation machine)。当上下文包含代理过去一小时的操作时,这段历史成为了最强的模式,比它在训练中获得的推理能力更强。
因此模型可能不再询问任务接下来需要什么,而是倾向于做消息历史中已经做过的事情。处于这种状态的代理可能会陷入不断重复旧工作的困境。第三个失效模式是语境混淆(context confusion),这源于工具的使用。我们的代理连接了很多东西,可能涉及版本控制、数据库控制台访问或向工单系统记录问题。
这些工具定义无论是否被实际使用都存在于窗口中,拥有更多的选择使得选择合适的工具变得更加困难。在一个基准测试中,当提供 46 个工具时模型未能完成任务;但当只提供 19 个工具时,它却通过了完全相同的任务。这就是过度蔓延(tall sprawl)在起作用。第四个失效模式是语境冲突(context clash),即窗口包含相互矛盾的信息。
看看那条含有错误信息的笔记,代理最终会看到没有发生重试操作并意识到该笔记是错误的。它可能会在消息历史中记录这一点,但问题在于我们现在窗口中有两个版本呈现相互冲突的信息。这就是上下文窗口出错的四种不同方式。那么,有什么潜在的修复方法呢?
对于中毒问题,一种修复方法是验证(validate)。在结论写入上下文窗口之前,用真实信息进行检查。一个简单的连接测试就能让代理永远不会写出那条新的重试代码笔记。对于分心问题,修复方法实际上是压缩(compact)。当历史记录变得非常长时,代理可以对其进行总结,即丢弃完整日志并从头开始基于摘要运行。
压缩是有损的(lossy),对摘要进行总结可能会像复印件的复印件一样开始漂移。考虑将尽可能多的内容移出上下文窗口并放入永久文件中,以便保留详细信息。
然后,只有在真正需要时才会将其拉入上下文窗口。好的,那什么是“上下文混淆”呢?这是一种策展问题。因此修复方法是……进行策展。代理不需要46个用于数据库迁移的工具,只需给它提供该工作所需的少数几个工具即可;如果它真的需要其他工具,它可以随时去获取。
最后是“上下文冲突”,那里的修复方法可以是修剪。当信息被取代时,直接删除旧版本,而不是让两者都留在窗口中。我们本可以完全删除这一部分;或者更进一步,开始一个新的上下文窗口,仅陈述当前已更正的工作状态,而将那些有害的信息留在后面。
如果错误的结论被保存下来,那就是“投毒”。因此你可以验证历史是否占据了主导(这是分心),如果代理拥有的工具多于任务所需,那就是混淆(因此需要策展)。而如果上下文自相矛盾,这就是冲突,修复方法是修剪。
这四种修复方法都是通过丢弃某些东西来起作用的。代理可以通过保留更少的内容来提高处理长任务的能力。现在我很想知道你们如何保持代理上下文的精简,你有自己的上下文工程修复方法吗?请在评论区告诉我!