在 Manus
项目的初期,团队面临一个关键抉择:是基于开源模型训练一个端到端的代理模型,还是利用前沿模型强大的“上下文学习”能力来构建代理?
时间倒回十年,在自然语言处理领域,开发者甚至没有选择的余地。在那个属于 BERT
的时代,任何模型都必须为新任务进行数周的微调和评估。对于一个追求快速迭代、尚未找到产品市场契合点(PMF)的应用而言,这种缓慢的反馈循环是致命的。这是从上一次创业中得到的惨痛教训,当时为开放信息提取和语义搜索从零开始训练模型,但随着 GPT-3
和 Flan-T5
的出现,那些自研模型几乎在一夜之间被淘汰。讽刺的是,正是这些新模型开启了上下文学习的时代,也指明了一条全新的道路。
这个教训让 Manus
的选择变得异常清晰:把赌注押在上下文工程上。这使得产品改进的周期从数周缩短到几小时,并让产品本身与底层大模型的发展保持“正交关系”——如果模型的进步是上涨的潮水,Manus
要做的是水面上的船,而不是被固定在海床上的柱子。
然而,上下文工程远非一帆风顺。它更像一门实验科学。Manus
的代理框架已经彻底重建了四次,每一次都是因为团队发现了塑造上下文的更优方法。这个混合了架构搜索、提示词调试和经验猜测的手动过程,被团队戏称为“随机研究生下降”(Stochastic Graduate Descent),虽不优雅,但卓有成效。
这篇文章将分享团队通过“SGD”所达到的局部最优解。如果你也在构建自己的 AI 代理,希望这些原则能帮助你更快地收敛。
围绕 KV 缓存进行设计
如果只能选择一个指标,那么 KV
缓存命中率无疑是衡量生产级 AI 代理最重要的指标,因为它直接影响延迟和成本。要理解这一点,首先需要了解一个典型代理的运作方式。
收到用户输入后,代理会通过一系列工具调用来完成任务。在每次迭代中,模型根据当前上下文从预定义的动作空间中选择一个行为,这个行为在环境(如 Manus
的虚拟机沙箱)中执行后产生一个观察结果。随后,该行为和观察结果被附加到上下文中,成为下一次迭代的输入。这个循环会一直持续到任务完成。
显而易见,上下文在每一步都会增长,而输出——通常是一个结构化的函数调用——却相对较短。这导致代理的输入与输出Token比例严重失衡,与普通聊天机器人形成鲜明对比。在 Manus
中,该比例平均约为 100:1。
幸运的是,拥有相同前缀的上下文可以利用 KV
缓存机制。KV
缓存通过存储已计算过的键值对(Key-Value pairs),大幅降低再次处理相同前缀时的计算开销,从而显著减少首个 Token 生成时间(TTFT)和推理成本。无论你使用自托管模型还是调用推理 API,这都能带来巨大的成本节约。以 Claude Sonnet
为例,缓存过的输入 Token 成本为 0.30 美元/百万 Token,而未缓存的成本则为 3 美元/百万 Token,两者相差整整十倍。
从上下文工程的角度来看,提高 KV
缓存命中率需要遵循几个关键实践:
1. 保持提示词前缀的稳定性。 由于大语言模型的自回归特性,哪怕只有一个 Token 的差异,也会导致该 Token 之后的所有缓存失效。一个常见的错误是在系统提示的开头包含精确到秒的时间戳。虽然这能让模型告诉你当前时间,但它也彻底摧毁了缓存命中率。
2. 使上下文“只增不减”。 避免修改之前的行为或观察结果。同时,确保序列化过程是确定性的。许多编程语言和库在序列化 JSON 对象时,不保证键的顺序稳定,这会悄无声息地破坏缓存。
3. 在必要时明确标记缓存断点。 一些模型供应商或推理框架不支持自动增量前缀缓存,需要手动在上下文中插入缓存断点。设置断点时,要考虑到缓存可能的过期时间,并至少确保断点包含系统提示的末尾。
此外,如果使用 vLLM
等框架自托管模型,请确保启用了前缀/提示缓存功能,并使用会话 ID 等技术将请求在分布式工作节点间进行一致性路由。
用掩码代替移除
随着代理能力越来越强,其行为空间自然也变得愈发复杂,直白地说,就是工具的数量会爆炸式增长。近期流行的模型上下文协议(MCP)更是火上浇油。一旦允许用户配置工具,总会有人将数百个稀奇古怪的工具塞进你精心策划的行为空间。结果是,模型更容易选错工具或采取低效路径,一个全副武装的代理反而变得更“笨”了。
一个直接的反应是设计一个动态的行为空间,比如使用类似 RAG
(检索增强生成)的技术按需加载工具。Manus
也尝试过这种方法,但实验得出的结论很明确:除非绝对必要,否则应避免在迭代中途动态增删工具。原因有二:
1. 工具定义通常位于上下文前部。 在大多数大语言模型中,工具定义经过序列化后,会紧随系统提示之后。因此,任何改动都会导致后续所有行为和观察结果的 KV
缓存失效。
2. 模型会产生困惑。 如果之前的行为和观察结果引用了当前上下文中已不存在的工具,模型会感到困惑。在没有约束解码的情况下,这常常导致格式错误或模型幻觉出不存在的工具调用。
为了解决这个问题,Manus
并未移除工具,而是使用一个上下文感知的状态机来管理工具的可用性。它在解码阶段通过掩盖(mask)特定 Token 的 logits,来阻止或强制模型根据当前状态选择某些动作。
在实践中,多数模型供应商和推理框架都支持某种形式的响应预填充,这允许在不修改工具定义的情况下约束行为空间。以 NousResearch
的 Hermes
格式为例,通常有三种函数调用模式:
- 自动(Auto): 模型可以自行决定是否调用函数。通过预填充
<|im_start|>assistant
实现。 - 必需(Required): 模型必须调用一个函数,但具体调用哪个不受限制。通过预填充
<|im_start|>assistant<tool_call>
实现。 - 指定(Specified): 模型必须调用指定子集中的某个函数。通过预填充到函数名的开头实现,例如
<|im_start|>assistant<tool_call>{"name": “browser_
。
利用这种机制,可以直接通过掩盖 Token logits 来约束动作选择。例如,当用户提供新输入时,Manus
必须立即回复而不是执行动作。团队还刻意设计了具有一致性前缀的动作名称,例如所有浏览器工具都以 browser_
开头,命令行工具以 shell_
开头。这使得在特定状态下,无需使用有状态的 logits 处理器,就能轻松地强制代理只从某个工具组中进行选择。
这些设计确保了即使在模型驱动的架构下,Manus
代理的循环依然保持稳定。
将文件系统作为上下文
现代前沿大语言模型提供了 128K甚至更长的上下文窗口,但在真实的代理应用场景中,这往往还不够,有时甚至成为一种负担。主要有三个痛点:
1. 观察结果可能非常庞大。 当代理与网页、PDF 等非结构化数据交互时,很容易超出上下文长度限制。
2. 模型性能会随上下文长度增加而下降。 即使技术上支持,模型的表现在超过某个长度后也容易退化。
3. 长输入成本高昂。 即便有前缀缓存,开发者仍需为传输和预填充每个 Token 付费。
为了应对这些问题,许多代理系统采用了上下文截断或压缩策略。但过于激进的压缩不可避免地导致信息丢失。这是一个根本性问题:代理的天性是根据所有先前的状态来预测下一步行动,而你无法可靠地预测十步之前的哪个观察结果会在之后变得至关重要。从逻辑上讲,任何不可逆的压缩都伴随着风险。
因此,Manus
将文件系统视为终极上下文:它容量无限、本质上持久,并且能被代理直接操作。模型学会了按需读写文件,将文件系统不仅用作存储,更用作结构化的外部记忆。
Manus
的压缩策略始终被设计为可恢复的。例如,只要保留了 URL,网页内容就可以从上下文中丢弃;只要沙箱中还存在文件路径,文档内容就可以被省略。这使得 Manus
可以在不永久丢失信息的前提下,有效缩减上下文长度。
在开发此功能时,一个有趣的想法是:状态空间模型(SSM)如何在代理环境中高效工作?与 Transformer
不同,SSM 缺乏全局注意力,难以处理长距离的回溯依赖。但如果它们能掌握基于文件的记忆——将长期状态外部化,而不是保存在上下文中——那么它们的速度和效率可能会开启一类全新的代理。到那时,代理化的 SSM 或许会成为神经图灵机(Neural Turing Machines)真正的继承者。
通过“复述”来操控注意力
如果你使用过 Manus
,可能会注意到一个有趣的现象:在处理复杂任务时,它倾向于创建一个 todo.md
文件,并随着任务进展逐步更新、勾选已完成的项目。
这并非简单的拟人化行为,而是一种精心设计的注意力操控机制。
在 Manus
中,一个典型任务平均需要约 50 次工具调用,这是一个相当长的循环。由于 Manus
依赖大语言模型进行决策,它很容易在长上下文或复杂任务中偏离主题或忘记早期目标。
通过不断重写待办事项列表,Manus
相当于在上下文的末尾“复述”其核心目标。这将全局计划推入模型近期的注意力范围内,有效避免了“迷失在中间”(lost-in-the-middle)的问题,减少了目标漂移。实际上,这是在用自然语言引导模型自身的注意力偏向任务目标,而无需任何特殊的架构改动。
保留失败的记录
代理会犯错。这不是一个缺陷,而是一个事实。语言模型会产生幻觉,环境会返回错误,外部工具会出故障,各种意想不到的边缘情况层出不穷。在多步骤任务中,失败不是例外,而是循环的一部分。
然而,开发者的一种常见冲动是隐藏这些错误:清理痕迹、重试动作,或者重置模型状态,然后寄望于神奇的“温度”(temperature)参数来解决问题。这感觉上更安全、更可控,但代价是:抹去失败,也就抹去了证据。没有证据,模型就无法适应和学习。
根据 Manus
的经验,提升代理行为最有效的方法之一,简单到令人意外:将错误的尝试保留在上下文中。当模型看到一个失败的动作及其导致的观察结果或堆栈跟踪时,它会隐式地更新其内部“信念”,从而降低其对相似行为的偏好,减少重复犯错的概率。
事实上,从错误中恢复的能力,是衡量真正代理智能的最清晰指标之一。然而,在大多数学术研究和公开基准测试中,这一点仍然被严重低估,因为它们往往只关注理想条件下的任务成功率。
警惕“少样本”陷阱
“少样本提示”(Few-shot prompting)是提升大语言模型输出质量的常用技巧,但在代理系统中,它可能会以微妙的方式适得其反。
语言模型是出色的模仿者,它们会模仿上下文中的行为模式。如果你的上下文中充满了相似的“行为-观察”对,模型就会倾向于遵循这种模式,即使它已不再是最佳选择。
这在涉及重复决策或动作的任务中可能非常危险。例如,当使用 Manus
批量审阅 20 份简历时,代理常常会陷入一种节奏——仅仅因为在上下文中看到了类似的操作,就不停地重复它们。这会导致行为漂移、过度泛化,有时甚至是幻觉。
解决方法是增加多样性。Manus
在行为和观察结果中引入了少量结构化的变体,例如使用不同的序列化模板、替代性的措辞、顺序或格式上的微小噪音。这种受控的随机性有助于打破固化模式,调整模型的注意力。
换言之,不要让自己陷入“少样本”的路径依赖。上下文越是整齐划一,代理的行为就越是脆弱。
上下文工程仍然是一门新兴科学,但对于代理系统而言,它已是成功的基石。模型也许会变得更强、更快、更便宜,但任何原始能力的提升,都无法取代对记忆、环境和反馈的精心设计。你如何塑造上下文,最终决定了你的代理如何行动:它的运行速度、恢复能力以及扩展的边界。代理的未来,将由一个又一个精心设计的上下文构筑而成。