W
AI-Wiki
SOURCE

Agent上下文工程实践 摘要

文档概览

这篇材料不是泛泛而谈的“提示词技巧”,而是 Manus 团队在构建通用 Agent 过程中,对“上下文如何被组织、保留、截断、约束和重放”的一组工程原则总结。

原文先交代了一个关键路线选择:团队在项目初期曾面临两条路,一是基于开源基础模型训练端到端智能体,二是利用前沿模型的上下文学习能力,在其之上构建 Agent。Manus 最终选择后者,即押注上下文工程。

作者给出的理由非常具体:

  • 早年 NLP 系统高度依赖微调,迁移到新任务前往往必须先训练再评估,每次迭代常常需要数周。
  • 对仍处在 PMF 之前、需要快速试错的产品而言,这样的反馈周期几乎不可接受。
  • Peak 以自己上一家创业公司的经验为反例:当时从零训练开放信息抽取和语义搜索模型,但 GPT-3 与 Flan-T5 出现后,自研模型很快失去意义。
  • 因而 Manus 希望产品能力与底层模型进步“正交”:模型进步时,产品应像船一样顺势抬升,而不是像钉在海床上的柱子那样被技术路径锁死。

在这种判断下,上下文工程的价值不只是“调 prompt”,而是让产品改进能在几小时内上线,而不是等待几周的训练周期。同时这也意味着,Agent 的性能很大程度上取决于上下文结构本身,而不只取决于模型参数。

作者还强调,这套方法不是一次设计出来的。Manus 已经重构过 4 次智能体框架,每次都是因为找到了更好的上下文塑造方式。团队把这种不断手调架构、微调提示和经验试错的过程戏称为“随机梯度下降”。

关键事实

为什么押注上下文工程,而不是端到端训练

原文最重要的立场之一,是在 Agent 场景下优先选择上下文工程,而非从开源基础模型训练端到端系统。这里不是说训练永远无价值,而是说对快速变化的应用产品,尤其在产品尚未稳定前,这条路线代价过高、反馈过慢、且容易被新模型迭代吞没。

Peak 回顾了从 BERT 时代以来的开发现实:过去模型迁移到新任务时必须经过微调和评估,哪怕模型规模远小于今天的大模型,每轮迭代仍要耗费数周。这种节奏与今天 Agent 产品需要的高频实验、快速上线和快速纠错不匹配。

因此 Manus 选择把主要工程能力投入到上下文组织上。原文的判断可以概括为:

  • 上下文工程能把功能改进的发布时间缩短到数小时量级。
  • 这使产品能直接吃到底层模型升级的红利。
  • 架构上更灵活,不会被某个特定训练管线深度绑定。
  • 更适合一边构建产品、一边寻找正确交互与执行方式的阶段。

这也是全文后续所有原则的前提:既然不靠端到端训练把所有行为“烙死”在参数里,就必须把记忆、控制、纠错和稳定性都尽可能转移到上下文层面来实现。

KV 缓存命中率是生产级 Agent 的核心指标

原文明确提出:如果只能挑一个指标,生产级 AI Agent 最重要的单一指标就是 KV 缓存命中率,因为它直接决定延迟与成本。

作者解释这一点时,先描述了 Agent 的典型循环:

  • 用户发来输入。
  • 模型在预定义动作空间中选择一个动作。
  • 动作在环境中执行,例如虚拟机沙箱。
  • 环境返回观察结果。
  • 动作与观察被追加回上下文,成为下一轮决策输入。
  • 如此循环,直到任务结束。

与普通聊天机器人相比,这种循环的特点是上下文持续增长,而每一轮输出通常很短,经常只是一个结构化函数调用。因此它的预填充成本远大于解码成本。

Manus 给出的经验数字是:平均输入 token 与输出 token 比例约为 100:1。也就是说,绝大部分推理成本都花在“重新读上下文”上,而不是“生成新内容”上。

这正是 KV 缓存重要的原因。只要上下文前缀保持一致,就可以复用此前计算得到的 KV 缓存,从而显著降低首 token 延迟和推理费用。原文还给出了一组具体价格差:以 Claude Sonnet 为例,缓存输入 token 价格约为 0.30 美元/百万 token,未缓存输入 token 则约为 3 美元/百万 token,差了 10 倍。

因此,KV 缓存命中率不是一个“底层优化细节”,而是 Agent 经济性和响应性的结构性前提。

KV 缓存友好的上下文设计原则

为了提高命中率,原文给出几条非常具体的设计要求,这些要求构成了 KV缓存友好型上下文 的核心。

1. 保持提示前缀稳定

由于自回归模型对前缀高度敏感,只要某个位置出现一个 token 差异,从那个位置开始后续缓存就可能全部失效。

文中点名批评的常见错误是:在系统提示开头插入时间戳,尤其是精确到秒的时间。这样虽然让模型知道“现在几点”,但因为每次请求前缀都不同,缓存命中率会接近归零。

这条原则的含义并不只是“别放时间”,而是:所有放在上下文前部、会被每轮重复读取的内容,都应尽量静态、稳定、可复用。

2. 让上下文保持追加式,不要回改历史

原文强调应避免修改此前的动作或观察结果,并确保序列化过程是确定性的。

一个非常实际的坑是 JSON 键顺序。很多编程语言或库在序列化对象时,并不保证字段顺序稳定。如果同一语义内容每次被序列化成不同 token 序列,那么缓存会被悄悄打穿。也就是说,即使业务逻辑没变,只要表示方式有抖动,缓存收益就会下降。

因此这里的要求包括:

  • 历史轨迹尽量只追加,不重写。
  • 序列化模板固定。
  • 字段顺序稳定。
  • 同类事件尽量用相同格式表达。

3. 需要时显式设置缓存断点

原文指出,并不是所有模型提供方或推理框架都支持自动的增量前缀缓存。有些场景需要开发者手动插入缓存断点。

设置这些断点时要考虑缓存过期问题,至少要确保断点覆盖到系统提示末尾。换言之,系统提示结尾应被纳入一个稳定、可复用的缓存边界内,否则最重的前缀部分仍会被反复计算。

4. 自托管时显式开启前缀缓存并保证路由一致

如果使用 vLLM 等框架自托管模型,原文提醒要确认前缀/提示缓存功能已开启。此外,在分布式工作节点之间还要通过会话 ID 等方式保证请求路由一致,否则即使理论上前缀相同,也可能因为请求落到不同节点而无法命中已有缓存。

这说明 KV 缓存命中率不仅是 prompt 设计问题,也与推理基础设施和会话调度方式直接相关。

动作空间不要随便动态增删,优先做掩码约束

在工具系统设计上,原文反复强调“稳定动作空间”的价值。随着 Agent 能力增多、尤其在 MCP 等机制流行后,工具数量很容易膨胀。如果再允许用户自行挂载工具,动作空间很快会失控。

作者对此的判断很直接:工具越多,模型越容易选错动作或走低效路径,一个“全副武装”的 Agent 反而可能更笨。

一个看上去自然的解决方案,是做动态动作空间,例如用类似 RAG 的方式按需加载工具。Manus 也尝试过,但得出的经验规则是:除非绝对必要,不要在迭代过程中动态增删工具。

原文给出两条主要原因:

  1. 工具定义通常序列化在上下文前部,位于系统提示之前或之后。一旦这里发生变化,会导致后续所有动作与观察对应的 KV 缓存全部失效。
  2. 如果历史动作与观察仍在引用某个当前上下文中已消失的工具,模型会混乱。在没有约束解码时,容易出现模式违规甚至幻觉动作。

也就是说,动态工具加载虽然在表面上减少了“当前可见工具数”,但它牺牲了两件更关键的东西:缓存稳定性,以及历史轨迹与当前定义之间的一致性。

Manus 的约束方式:不移除工具,而是在解码阶段做 token 掩码

Manus 的做法不是把工具真的从上下文里删掉,而是保留稳定的工具集合,再由一个上下文感知状态机管理“当前哪些工具可用”。实现层面是在解码阶段屏蔽相应 token 的 logits,以阻止或强制选择某些动作。

这套思路的关键优点是:

  • 工具定义保持稳定,有利于 KV 缓存。
  • 历史动作不会失去引用对象。
  • 可根据当前状态灵活限制模型行为。
  • 约束发生在生成阶段,比事后纠错更稳。

原文进一步用函数调用格式解释了约束的几种常见模式。以 Hermes 风格为例:

  • Auto:模型可自行决定是否调用函数。实现方式是只预填充到 assistant 回复前缀。
  • Required:模型必须调用某个函数,但具体调用哪个函数不限。实现方式是预填充到工具调用标记。
  • Specified:模型必须从某个特定子集里调用函数。实现方式是把前缀直接预填充到函数名开头。