跳到主要内容

代理墙钟预算:一场与工具超时机制的赛跑

· 阅读需 12 分钟
Tian Pan
Software Engineer

有一种 Agent 漏洞,当你孤立地观察任何单个组件时,它都不会出现。模型没问题,工具没问题,重试策略也没问题。纸面上的超时值甚至可以说很慷慨。然而,一个通常在 8 秒内完成的工具,却总是在一个已经在 7.9 秒时将其宣告为失败的 Agent 面前折戟。Agent 围绕一个从未发生过的“错误”重新规划,并启动了第二次调用,而第一次调用的结果即将与其发生碰撞。

漏洞不在任何一个框框里。它存在于两个没人同意应该同步的时钟之间的缝隙中。

这是 Agent 工程领域中相当于分布式系统经典问题——协作节点之间的时钟漂移 (clock drift)——只不过这两个节点位于同一个进程中,且漂移不是以 NTP 偏差的毫秒为单位衡量的。它是以每一方决定称为“t-zero”的事件来衡量的。Agent 的预算往往从 LLM 发出第一个 token 开始计时。工具的预算往往从工具进程实际收到调用的那一刻开始计时。在这两个时刻之间,隔着整个 prefill 阶段、整个流式传输管道、整个工具路由器 (tool-router) 跳转,以及工具工作线程 (tool worker) 前的任何排队。这两个秒表之间没有任何时间是共享的。它们都认为自己在为同一件事计时。

两个没人同意应该同步的时钟

对 Agent 步骤的传统看法就像时间轴上的一个条形图:提示词输入,模型思考,工具运行,结果返回。对超时的传统看法是,你在该条形图的某个地方画一条垂直线,并称之为截止日期。

现实情况是至少有四个时钟在并发运行,且几乎从未同步:

  • Agent 框架 (harness) 时钟,通常在请求发送给模型时开始计算预算。
  • 模型的有效时钟,框架通常只有在第一个流式 token (TTFT) 出现时才能感知到。在 2026 年,对于对话式生成,首字时间被报告在数百毫秒到几秒不等,对于长上下文可能更长。这整个间隔可能算作,也可能不算作你的“Agent 预算”,具体取决于你使用的库。
  • 工具客户端时钟,从框架发出工具调用开始,到工具返回结束。大多数框架将其公开为每个工具的超时。
  • 工具服务端时钟,仅在工具进程实际收到请求时开始。它前面的任何东西——队列、MCP 路由器、具有自身空闲超时的反向代理——都会增加工具进程看不见且无法计入自身的延迟。

在表现良好的 RPC 栈中,这些时钟通过截止日期传播 (deadline propagation) 来协调。调用方计算一个绝对截止日期,将其作为墙钟瞬间 (而非持续时间) 附加到请求中,每个下游跳转都会继承它。经典的 gRPC 公式对此非常明确:截止日期是以绝对时间戳表示的“从现在起 5 秒内”,并且上下文会通过每个子调用进行传播,以便整个子树在同一时刻终止。像 userver 这样的框架也描述了同样的想法——链接截止日期,这样缓慢的上游就不会消耗掉下游仍期望拥有的预算。

在实践中,Agent 栈并不这样做。Agent 的预算是在框架进程中计算的持续时间。工具的预算是在工具进程中计算的另一个独立的持续时间。没有共享的截止日期。没有传播。

因此,当模型的第一个 token 终于到达时,框架时钟已经过去了 1100 毫秒。当工具调用经过 MCP 服务器路由时,又消失了 300 毫秒。当工具工作线程出队请求并开始其自身的 8 秒预算时,框架已经在针对一个 1.4 秒前就开始的 8 秒预算进行计时。从框架的角度看,工具还有 6.6 秒。从工具的角度看,它有 8 秒。数字看起来相等,实则不然。

为什么这看起来像是一个 Agent 拒绝使用的成功工具调用

病理性的情况并不是 Agent 在工具启动前就放弃了。那种情况非常明显——你会看到一个被取消的调用和一个愤怒的日志。病理性的情况是 Agent 在 7.9 秒时放弃,工具在 8.0 秒时完成,且双方都在各自的追踪 (trace) 中写入了结构化的成功日志。

Agent 的追踪显示:启动调用,等待,超时在 7.9 秒过期,重新规划。从 Agent 的角度来看,工具失败了。它进入了一个恢复分支——选择一个不同的工具,向用户询问澄清问题,或者最糟糕的是,重试相同的调用。

工具'的追踪显示:收到调用,执行,在 8.0 秒返回并带有干净的 200 状态码。从工具的角度来看,一切正常。它甚至还因为这项工作向用户收了费。

这种竞争在下游表现为三种不同的症状,它们看起来都像是不同的漏洞:

  1. 一个没有任何东西在等待的“成功”工具结果。Agent 的协程已被取消。结果落入了一个关闭的通道或孤儿 future 中。一些框架将其记录为“tool_use without matching tool_result”——如果双方对发生的事情达成一致,这种形式本应是不可能的。
  2. 一个自信地报告“工具失败了,我将尝试不同的方法”的模型,而一个完全正确的答案正坐在工具的响应队列中。
  3. 重复调用,因为 Agent 的重新规划选择了具有相同参数的相同工具——而现在工具工作线程正在做两次相同的工作,第二次调用正与第一个过时的结果赛跑,进入一个不知道该信任哪一个的状态机。
加载中…
References:Let's stay in touch and Follow me for more thoughts and updates