AI线上最贵的bug:任务重复执行两遍
生产环境中AI请求可能完成后再超时导致重复调用,作者阐述幂等性设计的重要性并给出基于 idempotency_key 的实践方案。
完整中文译文
一次失败的 AI 请求并不总是意味着工作失败了。
有时客户端在模型已经完成之后丢失了响应。有时工作线程在工具调用仍在运行时重启。有时超时触发重试,而原始请求还活着。
然后一个用户操作就变成了:
这不是模型质量问题。
这是一个幂等性问题。
重试不应该创建新任务
重试在生产级 AI 系统中是正常的。
网络会失败。提供商会变慢。工具调用会超时。队列会重新投递任务。主路由变得不确定后,回退路由可能开始。
错误在于把每次重试都当作新的工作来处理。
相反,为每个业务操作定义一个持久化任务:
一次上传的文档
一次发票提取
一次支持对话摘要
每次尝试都应该属于同一个任务。
使用幂等性密钥
为每个业务操作提供一个稳定的幂等性密钥。
{
"idempotency_key": "extract_invoice_9821",
"workflow": "invoice_extraction",
"status": "running",
"attempt_count": 2,
"model": "model-a",
"route": "fallback",
"result_reference": null
}
当相同的请求再次到达时,不要立即再次运行工作流。 首先检查任务状态。 正确的响应可能是:
- 任务已经完成,返回保存的结果
- 任务仍在运行,返回其状态
- 上一次尝试结果不确定,在回放前先调查
- 任务安全失败,创建受控重试
密钥代表业务结果,而不是一次 HTTP 请求。
在调用模型之前持久化状态
一种危险的模式是这样的:
- 调用模型
- 接收结果
- 保存任务记录
如果应用程序在步骤二和三之间崩溃,模型可能已经完成但系统没有证据。 下一次重试可能再次调用模型。
在调用模型、工具或外部 API 之前创建任务记录。
记录足够的信息来重建发生的情况:
- 幂等性密钥
- 工作流名称
- 输入引用
- 模型和配置版本
- 选择的路由
- 尝试次数
- 时间戳
- 提供商请求 ID
- 工具执行 ID
- 结果引用
- 错误类型
这将未知的重试变成可以检查的东西。
「未知」比「失败」更危险
明确的失败很容易重试。
未知的结果更难处理。
想象一个请求在应用程序发送模型调用后超时了。
- 提供商收到它了吗?
- 模型完成了吗?
- 工具执行了吗?
- 输出存储了吗?
盲目重试可能会重复工作。在回放之前,检查证据:
- 请求日志
- 提供商请求 ID
- 工具执行记录
- 回调事件
- 数据库写入
- 输出存储
- 使用记录
生产系统应该在创建更多工作之前协调不确定的工作。
回退路由仍然是同一个任务
从主模型切换到回退模型并不会创建新的业务任务。
将两次尝试保持在同一个幂等性密钥下:
Job: extract_invoice_9821
- Attempt 1: primary route timed out
- Attempt 2: fallback route completed
- Final result: stored once
这在多模型应用中很重要。
没有共享的任务记录,团队无法判断更高的成本是来自有意的回退、重试还是意外的重复。
将请求与工作分离
对于较长的工流,避免在一个用户请求中完成所有工作。
创建一个持久化任务,返回一个任务 ID,然后异步处理任务。
这样客户端可以安全地查询状态,而不会再次提交相同的任务。
这适用于:
- 长文档处理
- RAG 索引
- 批量摘要
- Agent 工作流
- 媒体生成
- 重工具自动化
超时不应该强迫用户重新开始。
测试重复路径
不要只测试快乐路径。
测试以下情况发生时会发生什么:
- 客户端在慢响应后重试
- 两个工作线程收到相同的任务
- 工作线程在执行期间重启
- 模型响应在超时后到达
- 工具成功但其回调丢失
- 回退在主路由不确定时开始
- 用户点击提交两次
成功不是「工作流最终完成了」。
成功是一次有意图业务结果。
最后的思考
重试是必要的。
重复的 AI 工作不是。
稳定的幂等性密钥、持久化的任务状态和请求级别的证据使重试更安全、更便宜、更易于调试。
VectorNode 帮助团队通过一个多模型 AI 基础设施层访问、监控和管理全球及中国前沿模型。