跳转到主要内容

Agent 编排需要的是调度器,而不是框架

Kubernetes 式的控制原语——准入、配额、抢占——迁移到 agent 集群时,比任何新的 agent DSL 都更有用。

服务器机架的近景,带有发光的状�态 LED 和结构化布线,代表调度和控制平面基础设施。

为什么框架在规模化时会停滞

大多数 agent 框架建模的是单次运行:一个 LLM 循环调用工具、向记录追加内容,并在满足停止条件时终止。这种抽象对于 notebook 来说足够,但在生产环境中具有误导性。生产系统不是一次运行;而是许多并发运行在争夺同样的稀缺资源——模型速率限制、工具并发、检索带宽,以及盯着队列的运维人员的注意力。框架的职责是描述一次运行。系统的职责是决定哪些运行发生、以什么顺序发生,以及以谁为代价。

这些是调度问题,几十年前就已经为容器解决了。准入控制决定一个请求是被接受、排队还是拒绝。配额限制租户可以消耗的资源。抢占取消低优先级工作,为高优先级工作腾出空间。背压将过载向上游传播,而不是吸收它直到某个环节崩溃。这些概念没有一个是 Kubernetes 首创的,但 Kubernetes 给了它们一套工程师已经理解的词汇和 API 表面。

这种趋同在工具链中已经可见。Google 开源的 AX 编排器是一个信号:业界正在把 agent 协调当作控制平面问题,而不是提示词问题。真正有趣的工程不是用于描述 agent 的新 DSL;而是决定它们何时运行的调度器。

映射控制原语

从容器编排到 agent 编排的映射不是隐喻。它是结构性的,而且有用。

准入控制

当一次运行被提交时,调度器会预测其成本:预期 token 消耗、按类别划分的工具调用次数,以及挂钟时长。如果预测超出该租户或集群的剩余预算,运行就会被拒绝或排队。预测不需要精确;它需要保守且廉价。一个简单的机制是根据提示词大小、声明的工具集,以及每个工具类别的历史乘数,得出每次运行的成本估算。调度器将该估算与令牌桶进行比较。如果桶是空的,运行就等待。

这与 Kubernetes 准入 webhook 拒绝一个请求内存超过命名空间允许值的 pod 是同样的逻辑。区别在于资源是不可互换的:花在失败运行上的 token 就没了,而带有外部副作用的工具调用无法被撤销。

配额

Curving steel girders forming a geometric structure in modern architecture, stock photo

配额在派发之前执行,而不是之后。一个租户有三个预算:每个窗口的 token、每个窗口按类别划分的工具调用,以及并发运行数。调度器在准入时检查这三者。如果任何一个耗尽,运行就排队而不是启动。这是刻意无聊的,而且它是防止一个租户的失控循环饿死其他所有租户的最有效机制。

代价是延迟。严格的配额会让一次运行等待,即使集群还有空闲容量,因为配额是按租户而不是全局的。另一种方案——带公平份额权重的全局调度——更高效,但也更难推理。大多数团队应该从硬配额开始,只有在有数据时才放宽。

抢占

抢占是框架很少实现、而生产系统最终会需要的原语。当一个高优先级运行需要一个当前被低优先级运行持有的工具时,调度器可以取消低优先级运行并释放该工具。被取消的运行会被重新排队,而不是丢失。

抢占强大且危险。只有当被抢占的运行可以从检查点恢复而不重复副作用时,它才是安全的。这个条件并不总是满足,这就是为什么抢占应该是最后引入的原语,而不是第一个。

哪些东西无法迁移

agent 运行的三个属性打破了到 pod 的干净映射。

agent 运行不是无状态的。 pod 可以被杀死并重新调度,因为它的状态存在于卷或数据库中。agent 运行的状态存在于它的记录、工具调用历史和工作记忆中。检查点必须捕获这三者,而恢复必须对已经调用过的工具保持幂等。这是真实的工程成本,也是抢占并非免费的原因。

工具调用有外部副作用。 在工具调用中途取消运行并不会撤销该调用。一笔支付、一张工单、一封邮件、一次数据库写入——这些都会持续存在。因此,抢占Wide view of an industrial electrical substation with power lines and towers outdoorsNetwork cables data centre

可观测性必须捕获轨迹。 容器编排器关心退出码和资源使用。agent 编排器关心决策序列:调用了哪个工具、用什么参数、返回了什么,以及运行为什么走了那个分支。退出码可观测性是不够的,因为一次运行可以成功退出,却做了错误的事。调度器应该为每次运行发出结构化的轨迹记录,而该记录应该是调试的单位,而不是日志行。

一条迁移路径

团队不需要在第一天就采用完整的调度器。这条路径是渐进的、可逆的。

第一步:为每次运行发出调度记录。 在任何强制执行之前,让系统描述自己。每次运行发出包含优先级、预算估算、依赖关系和工具类别的记录。这是可观测性,不是控制,而且是安全的。它还会产生设定非任意配额所需的数据。

第二步:在抢占之前引入配额。 配额是可逆的:达到限制的租户会等待,而限制可以提高。抢占是不可逆的:被取消的运行可能已经造成了副作用。先引入配额,测量队列行为,然后才考虑对可安全取消的那部分运行进行抢占。

第三步:把调度器当作成本与策略交汇的唯一位置。 如果准入逻辑在 API 网关、配额逻辑在 worker、优先级逻辑在提示词里,就没有人能回答一次运行为什么等待。集中决策使系统可审计,使策略变更可测试。调度器成为拥有“这次运行为什么没有启动?”这个答案的组件。

诚实的权衡

调度器会增加延迟和一个新的故障域。每次运行都要付出准入检查的代价,而调度器中的一个 bug 可能让整个集群停摆。框架通过没有控制平面来避免这一点;它们把问题推给运维人员。对于小系统来说,这是一个合理的选择。当并发运行、租户或工具类别的数量使人工仲裁变得不可能时,它就不再合理。到那时,选择不是框架与调度器之间;而是显式调度器与散落在重试逻辑、速率限制处理和 on-call 判断中的意外调度器之间。

显式调度器更难构建,但更容易运维。这就是权衡。对于生产中的 agent 集群,它是正确的选择。

封面照片:panumas nikhomkhai / Pexels。

分享XLinkedIn永久链接5 分钟阅读

相关阅读

这能回答的问题

What is the difference between an agent framework and an agent scheduler?

A framework describes how a single run executes: the loop, the tools, the stop condition. A scheduler decides which runs execute, in what order, and under what resource constraints. Production systems need both, but the scheduler is the component that prevents overload.

Why is preemption harder for agents than for containers?

Containers are stateless and can be killed and rescheduled. Agent runs hold state in their transcript and tool-call history, and tool calls may have external side effects that cannot be undone. Preemption is only safe for runs whose remaining work is side-effect-free or compensable.

Should quotas or preemption be introduced first?

Quotas. They are reversible: a tenant that hits a limit waits, and the limit can be raised. Preemption is not reversible because a cancelled run may already have caused a side effect. Introduce quotas first, observe queue behaviour, then consider preemption for safe runs.

What should a scheduling record contain?

Priority, budget estimate, dependencies, and tool classes. Emitting this record before enforcing any policy gives the data needed to set non-arbitrary quotas and makes the scheduler auditable.