Skip to content

AI Agent的Context Engineering:构建Manus的经验教训

2025/7/18 --Yichao 'Peak' Ji

Manus项目的最初阶段,我和我的团队面临一个关键决策:我们应该使用开源基础模型训练一个端到端的agentic模型,还是在前沿模型的in-context learning能力之上构建agent?

回想我在NLP领域的第一个十年,我们没有这样的选择余地。在遥远的BERT时代(是的,已经过去七年了),模型必须经过fine-tuning和评估才能迁移到新任务。这个过程每次迭代通常需要数周时间,尽管那时的模型相比今天的LLM来说非常小。对于快速发展的应用程序,特别是在PMF之前,这样缓慢的反馈循环是致命的。这是我上一个创业公司的惨痛教训,当时我从零开始为open information extraction和semantic search训练模型。然后GPT-3Flan-T5出现了,我的内部模型一夜之间变得无关紧要。讽刺的是,正是这些模型标志着in-context learning的开始——以及一条全新的前进道路。

这个来之不易的教训让选择变得明确:Manus将押注于context engineering。这使我们能够在几小时而不是几周内发布改进,并保持我们的产品与底层模型正交:如果模型进步是涨潮,我们希望Manus是船,而不是固定在海床上的柱子。

尽管如此,context engineering绝非简单直接。这是一门实验科学——我们已经重建了四次agent框架,每次都是在发现更好的context塑造方法之后。我们亲切地将这种架构搜索、prompt调整和经验猜测的手动过程称为"Stochastic Graduate Descent"。虽然不够优雅,但确实有效。

这篇文章分享了我们通过自己的"SGD"达到的局部最优解。如果你正在构建自己的AI agent,我希望这些原则能帮助你更快地收敛。

围绕KV-Cache设计

如果我必须选择一个指标,我认为KV-cache命中率是生产阶段AI agent最重要的单一指标。它直接影响延迟和成本。为了理解原因,让我们看看典型agent是如何运作的:

在收到用户输入后,agent通过一系列工具使用来完成任务。在每次迭代中,模型根据当前context从预定义的action space中选择一个action。然后在环境中执行该action(例如,Manus的虚拟机sandbox)以产生observation。action和observation被追加到context中,形成下一次迭代的输入。这个循环持续到任务完成。

可以想象,context随着每一步而增长,而输出——通常是结构化的function call——保持相对较短。这使得agent中prefilling和decoding之间的比率与chatbot相比高度倾斜。例如,在Manus中,平均输入输出token比率约为100:1。

幸运的是,具有相同前缀的context可以利用KV-cache,这大大减少了time-to-first-token (TTFT)和推理成本——无论你使用自托管模型还是调用推理API。我们说的不是小幅节省:例如,使用Claude Sonnet时,缓存的输入token成本为0.30美元/MTok,而未缓存的成本为3美元/MTok——相差10倍。

KV-Cache图表

从context engineering的角度来看,提高KV-cache命中率涉及几个关键实践:

  1. 保持prompt前缀稳定。 由于LLM的autoregressive特性,即使是单个token的差异也可能从该token开始使缓存失效。一个常见错误是在system prompt开头包含时间戳——特别是精确到秒的时间戳。当然,这让模型能告诉你当前时间,但也会破坏你的缓存命中率。

  2. 使你的context只追加。 避免修改之前的action或observation。确保你的序列化是确定性的。许多编程语言和库在序列化JSON对象时不保证稳定的键排序,这可能会悄悄破坏缓存。

  3. 在需要时明确标记缓存断点。 一些模型提供商或推理框架不支持自动增量前缀缓存,而是需要在context中手动插入缓存断点。分配这些断点时,要考虑潜在的缓存过期,至少确保断点包含system prompt的结尾。

此外,如果你使用vLLM等框架自托管模型,确保启用prefix/prompt caching,并使用session ID等技术在分布式worker之间一致地路由请求。

掩码,而非移除

随着你的agent承担更多功能,其action space自然变得更加复杂——简单来说,工具数量爆炸式增长。最近MCP的流行只是火上浇油。如果你允许用户可配置的工具,相信我:总会有人将数百个神秘工具插入你精心策划的action space。结果,模型更可能选择错误的action或采取低效路径。简而言之,你的重装agent变得更笨了。

自然的反应是设计动态action space——也许使用类似RAG的方式按需加载工具。我们在Manus中也尝试过。但我们的实验表明一个明确的规则:除非绝对必要,避免在迭代中动态添加或移除工具。主要有两个原因:

  1. 在大多数LLM中,工具定义在序列化后位于context的前面,通常在system prompt之前或之后。因此任何更改都会使所有后续action和observation的KV-cache失效。

  2. 当之前的action和observation仍然引用当前context中不再定义的工具时,模型会感到困惑。没有constrained decoding,这通常导致schema违规或幻觉action。

为了解决这个问题同时仍然改进action选择,Manus使用context感知的state machine来管理工具可用性。不是移除工具,而是在解码期间掩码token logits,以根据当前context阻止(或强制)选择某些action。

State Machine图表

实际上,大多数模型提供商和推理框架都支持某种形式的response prefill,这允许你在不修改工具定义的情况下约束action space。通常有三种function calling模式(我们将使用NousResearch的Hermes format作为例子):

Auto – 模型可以选择调用function或不调用。通过只预填充回复前缀实现:<|im_start|>assistant

Required – 模型必须调用function,但选择不受约束。通过预填充到tool call token实现:<|im_start|>assistant<tool_call>

Specified – 模型必须从特定子集调用function。通过预填充到function名称开头实现:<|im_start|>assistant<tool_call>{"name": "browser_

使用这个,我们通过直接掩码token logits来约束action选择。例如,当用户提供新输入时,Manus必须立即回复而不是采取action。我们还故意设计了具有一致前缀的action名称——例如,所有浏览器相关工具都以browser_开头,命令行工具以shell_开头。这使我们能够轻松强制agent在给定状态下只从某组工具中选择,而无需使用有状态的logits处理器。

这些设计有助于确保Manus agent循环保持稳定——即使在模型驱动的架构下。

将文件系统用作Context

现代前沿LLM现在提供128K token或更多的context window。但在现实世界的agentic场景中,这通常是不够的,有时甚至是负担。有三个常见痛点:

  1. Observation可能很大, 特别是当agent与网页或PDF等非结构化数据交互时。很容易超过context限制。

  2. 模型性能往往会下降 超过某个context长度,即使window技术上支持它。

  3. 长输入很昂贵, 即使有prefix caching。你仍然需要为传输和预填充每个token付费。

为了处理这个问题,许多agent系统实现context截断或压缩策略。但过度激进的压缩不可避免地导致信息丢失。问题是根本性的:agent本质上必须基于所有先前状态预测下一个action——而你无法可靠地预测哪个observation可能在十步后变得关键。从逻辑角度来看,任何不可逆的压缩都带有风险。

这就是为什么我们将文件系统视为Manus中的终极context:大小无限,本质上持久,并且可以被agent本身直接操作。模型学会按需写入和读取文件——将文件系统不仅用作存储,还用作结构化的外部化内存。

文件系统Context图表

我们的压缩策略总是设计为可恢复的。例如,只要保留URL,网页内容就可以从context中删除,只要其路径在sandbox中仍然可用,文档内容就可以省略。这允许Manus缩短context长度而不永久丢失信息。

在开发这个功能时,我发现自己在想象State Space Model (SSM)在agentic环境中有效工作需要什么。与Transformer不同,SSM缺乏完全attention并且在长程向后依赖方面有困难。但如果它们能掌握基于文件的内存——外部化长期状态而不是在context中保持——那么它们的速度和效率可能会解锁新一类agent。Agentic SSM可能是Neural Turing Machines的真正继承者。

通过背诵操纵Attention

如果你使用过Manus,你可能注意到了一些奇怪的事情:在处理复杂任务时,它倾向于创建一个todo.md文件——并在任务进行时逐步更新它,勾选完成的项目。

这不仅仅是可爱的行为——这是操纵attention的deliberate机制。

Todo列表示例

Manus中的典型任务平均需要大约50次tool call。这是一个长循环——由于Manus依赖LLM进行决策,它容易偏离主题或忘记早期目标,特别是在长context或复杂任务中。

通过不断重写todo列表,Manus将其目标背诵到context的末尾。这将全局计划推入模型的最近attention范围,避免"lost-in-the-middle"问题并减少目标不对齐。实际上,它使用自然语言来偏向自己对任务目标的关注——而无需特殊的架构更改。

保留错误内容

Agent会犯错误。这不是bug——这是现实。语言模型会产生幻觉,环境返回错误,外部工具行为异常,意外的边缘情况总是出现。在多步任务中,失败不是例外;它是循环的一部分。

然而,一个常见的冲动是隐藏这些错误:清理trace,重试action,或重置模型状态并依赖神奇的"temperature"。这感觉更安全,更可控。但这是有代价的:擦除失败会移除证据。没有证据,模型无法适应。

错误恢复图表

根据我们的经验,改善agent行为最有效的方法之一出奇地简单:将错误转弯留在context中。当模型看到失败的action——以及由此产生的observation或stack trace——它隐式地更新其内部信念。这将其先验从类似action转移,减少重复相同错误的机会。实际上,我们相信错误恢复是真正agentic行为最清楚的指标之一。然而,它在大多数学术工作和公共benchmark中仍然代表不足,这些通常专注于理想条件下的任务成功。

不要被Few-Shot困住

Few-shot prompting是改善LLM输出的常见技术。但在agent系统中,它可能以微妙的方式适得其反。

语言模型是优秀的模仿者;它们模仿context中的行为模式。如果你的context充满了类似的过去action-observation对,模型将倾向于遵循该模式,即使它不再是最优的。

这在涉及重复决策或action的任务中可能很危险。例如,当使用Manus帮助审查20份简历时,agent经常陷入节奏——仅仅因为这是它在context中看到的,就重复类似的action。这导致漂移、过度泛化,或有时产生幻觉。

Few-Shot模式问题

解决方案是增加多样性。Manus在action和observation中引入少量结构化变化——不同的序列化模板、替代措辞、顺序或格式中的轻微噪声。这种受控的随机性有助于打破模式并调整模型的attention。换句话说,不要让few-shot把自己困在车辙里。你的context越统一,你的agent就越脆弱。

结论

Context engineering仍然是一门新兴科学——但对于agent系统来说,它已经是必不可少的。模型可能变得更强、更快、更便宜,但再多的原始能力也无法替代对内存、环境和反馈的需求。你如何塑造context最终定义了你的agent如何行为:它运行多快,恢复多好,以及扩展多远。

在Manus,我们通过反复重写、死胡同和跨数百万用户的现实世界测试学到了这些教训。我们在这里分享的都不是普遍真理——但这些是对我们有效的模式。如果它们能帮助你避免哪怕一次痛苦的迭代,那么这篇文章就完成了它的工作。

Agentic的未来将一次一个context地构建。好好设计它们。

基于 MIT 许可证发布