Manus 曾经发表过一篇文章《Context Engineering for AI Agents: Lessons from Building Manus》,里面介绍了一下他们在上下文工程方面的实践。 这篇文章我反反复复看过好几次,最开始看的时候会不得要领,慢慢会觉得确实写得好。我试着总结下。 Manus 在构建其 AI Agent过程中,选择了一条“上下文工程”的路径,而非训练端到端模型。这一决策基于对快速迭代、成本效率和与底层大模型解耦的综合考量。以下是 Manus 上下文工程实践的核心要点总结:
围绕 KV 缓存进行设计
在 Transformer 架构中,自注意力机制(Self-Attention)需要为每个 token 计算与其他所有 token 的注意力权重。具体来说,对于输入序列中的每个 token,模型会生成三个向量: - Query (Q):当前 token 的查询向量 - Key (K):其他 token 的键向量 - Value (V):其他 token 的值向量 在自回归生成(即逐个生成 token)过程中,比如生成第 t 个 token 时,模型已经处理了前 t−1 个 token。如果每次都重新计算所有历史 token 的 K 和 V,会造成大量重复计算。 而如果将已处理 token 对应的 K 和 V 向量缓存起来,在生成新 token 时直接复用,无需重复计算。这样,每生成一个新 token,只需计算当前 token 的 Q,并与缓存的 K、V 做注意力运算。就能将时间复杂度从 O(n²) 降至 O(n)(n 为上下文长度),显著提升推理速度。 如果每次请求都能复用之前缓存的 KV 状态(例如在多轮对话或 Agent 连续动作中),就只需计算新增部分的 Q,K/V 直接读缓存。这样就避免了对整个上下文重新编码,大幅减少 GPU 计算量和内存带宽压力。 Manus认为,这个机制对于 Agent 应用来说至关重要,或者说最重要!因为我们前面介绍过,Agent 的工作模式通常是ReAct这种”思考、行动、观察”的循环。模型思考、然后调用工具,得到观察结果,再把这些信息追加到上下文中,用于下一步决策。这种模式导致了一个特点:输入和输出的规模往往不成比例。Manus 的输入/输出比平均达到 100:1。 而如果上下文前缀发生变化(如系统提示动态插入时间戳、工具列表顺序不一致等),那么缓存将无法复用。模型必须从头计算所有 token 的 K/V,即使 99% 内容未变。 所以,为了提高KV缓存命中率,Manus总结出以下几个最佳实践: - 避免在 system prompt 中插入 当前时间:{now} 这类动态内容。 - 不要随意的总修改:提示词通过prompt模板配置,并通过git管控,变更时需要走发布流程。做强制流程管控 - JSON 键按字母排序,避免因不同框架在序列化对象时带来的字段顺序不同导致 token 序列变化。 - 上下文只 append 新动作/观察(append-only),不修改历史内容。 - 在分布式部署(如 vLLM)中保证同一会话请求路由到相同GPU节点,提升缓存复用。(Manus 通过会话 ID固定路由确保同一 Agent 会话始终使用同一缓存副本。) - 部分推理框架能自动管理 KV Cache,但有些框架或服务则需要通过插入特殊标记(如 <|cache_break|>)来手动指定缓存边界。如将其置于系统提示词末尾,以最大化缓存复用范围。同时,需要考虑系统提示词变更时的缓存重建问题。
遮蔽(Masking)而非移除工具
随着你给Agent的工具越来越多,他会经常发生选错工具的情况。尤其是MCP,通常会一次性引入一堆工具。于是很多人会选择动态调整工具集,这看似合理,但会破坏 KV 缓存并导致模型混淆。因为在大多数LLM中,工具定义在序列化后位于上下文的前部,通常在系统提示词之前或之后。因此,任何更改都将使所有后续动作和观察结果的 KV 缓存失效。 Manus的做法时是,在上下文中保留所有工具的定义,不针对这个东西做修改,避免影响KV Cache。 Manus采用了响应预填充+统一工具前缀等方案来遮蔽工具。 在让模型生成回复前,人为写入一部分固定的 token 序列作为“开头”,引导模型后续生成:
<|im_start|>user
请帮我查一下量子计算的最新进展。
<|im_end|>
<|im_start|>assistant
此时模型将从 <|im_start|>assistant 之后开始生成。基于这个机制,Manus可以控制本次回复是否一定要使用工具或者用什么工具: 预填充工具调用的起始token,这样模型必须接着填写函数名和参数,无法输出普通文本:
<|im_start|>assistant
{"name":
预填充工具名,限制模型能用的工具:
<|im_start|>assistant
{"name": "browser_
同时Manus给他的工具的设计为带有统一前缀的命名方式,如: - browser_search, browser_navigate - file_read, file_write - shell_run, shell_check 这样在模型输出browser_的时候,绝对不会选择非浏览器相关的工具了。
将文件系统作为外部上下文
尽管现代 LLM 支持超长上下文(如 128K+ tokens),但在真实代理场景中仍面临三大问题: - 观察数据过大(如网页、PDF); - 长上下文性能下降; - 成本高昂(即使缓存,仍需传输和预填充)。 Manus 的策略是,赋予 Agent 读写能力+大内容外存+上下文仅保留指针+可恢复压缩。 1、Agent 可主动创建、读取、修改沙盒中的文件(如 research.txt、page.html),就像人类使用笔记或硬盘。 2、网页 HTML、PDF 文本、日志输出等体积庞大的非结构化数据,不直接塞入上下文,而是写入文件。 3、上下文中只记录轻量引用,例如: • "已保存搜索结果到 results.html" • "参考文档:/docs/quantum.pdf" • "原始 URL: https://example.com/kv-cache" 4、信息不丢失。只要保留关键元数据(如 URL、路径),原始内容就可按需重建或重新访问。 这样就能显著的缩短上下文的长度,提升推理速度,减少成本。并且也能避免传统的压缩导致的丢失重要信息问题。
通过复述操控注意力
Manus作为一个通用智能体,他们发现,在长任务(平均 50+ 工具调用)中,模型容易遗忘初始目标(lost in the middle)。 为了解决这个问题,Manus动态维护 todo.md 文件,在Agent运行的每一个步骤之后,都来更新任务清单,并且通过在上下文末尾持续不断地复述目标,避免丢失注意力。
保留错误内容以促进学习
失败是Agent的常态。很多时候出现了失败,总是把锅甩给幻觉、甩给模型、甩给temperature,这其实是掩盖了问题,并不是解决问题。 Manus的实践原则: - 保留失败痕迹:包括错误动作、异常堆栈、无效观察等。 - 让模型隐式更新信念:通过上下文中的失败案例,降低重复错误概率。 - 视错误恢复为智能体现:真正鲁棒的Agent应能从错误中调整策略。