跳到正文

devlog/token-math/the-real-cost-of-retries-and-timeouts-in-agent-workflows-65.md

Agent 工作流里重试与超时的真实成本

一次 45 秒超时如何变成三次付费请求

我最近排查一个代码代理时,发现它看似只完成了一次“修复测试”任务,网关日志却出现了三次相同请求。原因很常见:客户端在 45 秒时放弃等待,服务端其实仍在处理;代理框架随后自动重试两次。对 Agent 而言,超时不是免费取消,而只是客户端停止等待结果。若请求已经完成输入解析、工具规划或部分输出,这些实际处理过的 token 仍可能构成成本。

先把调用收敛到一个可观察的入口。下面命令使用 Fluxo 的统一地址;它为 Claude、GPT、Gemini、DeepSeek、Kimi、GLM、Qwen、Grok 等模型提供一个密钥和一个 Base URL,并可用于任何 OpenAI SDK。

export FLUXO_BASE_URL=https://api.fluxorelay.com/v1 — export FLUXO_API_KEY=替换为你的密钥

export MODEL=Claude Fable 5.1

用 curl 复现一个可计量的 Agent 请求

不要先在完整代理里猜测。先发送一个有明确输出上限的请求,并把响应和耗时分别写入文件。这样可以区分网络慢、首 token 慢,以及模型生成慢。

time curl --max-time 45 --connect-timeout 10 --silent --show-error "$FLUXO_BASE_URL/chat/completions" -H "Authorization: Bearer $FLUXO_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: devlog-retry-001" -d '{"model":"Claude Fable 5.1","messages":[{"role":"user","content":"读取仓库结构后,给出三步测试失败排查计划。"}],"max_tokens":600}' | tee response-001.json

这里最容易被忽略的是 max_tokens。Agent 的一次失败重试往往携带完整对话历史、系统提示、工具结果和仓库摘要;输入成本通常远大于那句“请重试”。如果请求还启用了缓存,缓存读取和缓存写入也应纳入同一任务的账本,而不是只看最终回答的输出 token。

先记录请求身份,再决定是否能够重试

重试策略的第一步不是设置次数,而是给每个逻辑任务一个稳定标识。上面的 Idempotency-Key 应在同一任务的重试中保持不变;每次全新任务才生成新值。即使某个上游不能完全以该字段去重,它也能让你在客户端日志、网关记录和 trace 中聚合同一笔工作。

用下面命令连续模拟三次调用时,不要修改 devlog-retry-001;随后检查三份响应中的 usage 字段、请求标识和完成状态。

for n in 1 2 3; do curl --max-time 45 --silent --show-error "$FLUXO_BASE_URL/chat/completions" -H "Authorization: Bearer $FLUXO_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: devlog-retry-001" -d '{"model":"Claude Fable 5.1","messages":[{"role":"user","content":"读取仓库结构后,给出三步测试失败排查计划。"}],"max_tokens":600}' -o "response-$n.json"; done

如果第一次客户端超时而后端完成,第二次可能既没有必要,也不一定能避免重复计费。更可靠的处理是先查询任务状态或消费可恢复流,而不是把超时直接等同于失败。对于会写文件、执行迁移或创建工单的工具调用,还必须在工具层做幂等保护,否则成本之外还会产生重复副作用。

把超时拆成连接、首字节和总时长

单一的 45 秒总超时会掩盖问题。连接超时应较短,首字节超时用于识别排队或路由异常,总时长则应随任务预算变化。一个需要读大量文件并进行多轮工具调用的 Agent,不能沿用聊天请求的时限;反过来,简单分类任务也不该无限等待。

  • 连接阶段超过十秒时,应优先检查网络、DNS 和凭据配置。
  • 首字节阶段持续过久时,应记录模型、排队时间和请求大小。
  • 生成阶段超过预算时,应保留部分结果并停止盲目重试。
  • 工具调用失败时,应只重试可证明没有副作用的步骤。

我通常把重试限制为两次,并使用指数退避与随机抖动:第一次等待两秒,第二次等待六秒。对于 429、连接重置和短暂 5xx,这样做合理;对于 400、鉴权失败、上下文过长和工具参数校验失败,立刻重试只会重复消耗。将错误类型、输入 token、缓存读取、缓存写入、输出 token 和总耗时一起记录,才能看见真正的单位任务成本。

用任务预算而不是单次请求价格做决策

假设一次修复任务包含四轮模型调用,其中一轮因超时重试两次。表面上只是多了两次 API 请求,实际却可能重复发送同一份长上下文,并重新触发缓存读取或写入。Fluxo 按实际 token 计费,包括缓存读取与写入,不收月费,因此最实用的优化不是为了“用满套餐”而重试,而是减少无效上下文和不可恢复的重复执行。

在 Claude Code、Codex 或 Gemini CLI 中也采用同一原则:为任务保存 trace ID,缩短每轮摘要,给工具操作加幂等键,并在超时后先恢复状态。需要跨模型比较时,可以用 GPT-5.6 Sol 做同一条固定提示词的基准,但必须固定 max_tokens、上下文和重试策略,否则价格差异没有可比性。

如果你想把这些日志和统一模型入口放进现有终端工作流,可以温和地创建一个 Fluxo 账户后,用同一密钥先跑这组复现命令。用户邀请的朋友产生付费时也能获得推荐分成,但这不应替代对每次重试成本的真实测量。

缓冲区里的下一篇

一分钟注册,赠送额度足够把文中提到的模型都试一遍。 注册 →