跳转到内容
v0.9.1正式版

重试、超时与幂等

OpenAI 与 Anthropic 的官方 SDK 默认会自动重试(通常 2 次)。在直连模型服务时这没什么,在网关后面则不然:

Halro 在把请求发往上游之前就把预算预留写入账本,并且这个预留是持久的。没有有效幂等键保护时,客户端的一次重试会创建新的预留与结算。SDK 帮你「悄悄重试两次」的结果,是账上出现三笔而不是一笔。

client = OpenAI(base_url="...", api_key=..., timeout=60.0, max_retries=0)
const client = new OpenAI({ baseURL: "...", apiKey: ..., timeout: 60_000, maxRetries: 0 });

关掉之后,重试策略由你自己写 —— 下面是判断依据。

什么时候重试是安全的
情况能否重试说明
429 每源限流 / Project 限流可以读 Retry-After(可能不存在,见下),退避后重试
429 Provider 并发上限可以有可解析的 Retry-After 时遵循它,否则应用退避
429 Deployment 并发上限可以不带 Retry-After,使用有上限的应用退避
503 provider_unavailable可以当前没有健康的 Deployment
503 all_candidates_suspended按原因处理有窗口时退避;失效凭据无 Retry-After,联系管理员
503 accounting_capacity_exhausted可以遵循 Retry-After 等待已准入工作结算
上游 5xx谨慎可能已经到达 Provider,按歧义失败处理;不要假设网关已自动回退
403 token_guard_blocked不要立即重试可能是成本上限或 temporary block;按错误详情调整请求或等待/管理员处置
403 model_not_allowed / 404 model_not_found不要配置问题,找管理员
400 字段被拒不要请求本身不合法,改请求
结果不明确的失败谨慎见下一节

结果不明确的失败,网关不会替你重试

Section titled “结果不明确的失败,网关不会替你重试”

如果一次上游调用的结果是歧义的 —— 例如连接在请求已发出但响应未回来时断开 —— Halro 不会重试它,也不会把费用退掉。这是有意的:无法确定上游是否已经产生了计费事件时,账目按发生过保守处理,而不是乐观地假设它没发生。

所以这类失败给你的是一个错误,而账上可能已经有一笔。你自己重发是允许的,但要清楚那会是第二笔独立的费用。

Chat 与 Embeddings:防重复调用,不重放答案

Section titled “Chat 与 Embeddings:防重复调用,不重放答案”

v0.9.0 起,POST /v1/chat/completions(包括流式)和 POST /v1/embeddings 支持 Idempotency-Key。同一业务操作应保存并复用相同键、正文和 Run 归因;以下为首个请求示例, 重试时不要重新生成键:

Terminal window
IDEMPOTENCY_KEY="$(uuidgen)"
curl https://halro.example.com/v1/chat/completions \
-H "Authorization: Bearer $HALRO_GATEWAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{"model":"chat","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}'
Chat 与 Embeddings:防重复调用,不重放答案
重复请求状态返回处理
相同键、不同请求正文409 idempotency_conflict核对业务键,不能用换键掩盖冲突
仍在执行或结果不明确409 idempotency_in_progress有界等待;不启动另一笔调用
已完成409 idempotency_completed不会返回原答案;客户端需保存首次响应
无法安全检查或保存键503,如 resource_store_unavailable修复存储后使用原键,不按“键可用”处理

流式重复请求在打开流之前作为 HTTP 错误拒绝。此机制保证键记录有效期内至多一次上游执行, 不保证响应可重放;记录不保存请求/响应正文,24 小时后所有状态都会过期,过期后复用可能产生新调用。 键按 Project 隔离,Chat 与 Embeddings 共用 inference_call 命名空间。键必须为 1–128 个可见 ASCII 字符且不含空白;格式错误返回 400 invalid_idempotency_key。未带键的请求维持原行为。

上游已被触达的失败会消耗键;能力、Token、预算或 Attempt 预留在上游调用前拒绝的请求会释放键。 客户端不能把所有失败都当作可安全换键重试。同步 Responses 和 Messages 不提供这套推理幂等契约。 需要稍后读取答案时,选择显式启用的 background: true Responses。

创建型的资源端点接受 Idempotency-Key 请求头 —— 异步调用(POST /v1/async/invocations)、文件创建(POST /v1/files)、批处理创建(POST /v1/batches)。带上它,重复提交同一个键不会创建第二个资源。

Terminal window
curl https://halro.example.com/v1/files \
-H "Authorization: Bearer $HALRO_GATEWAY_KEY" \
-H "Halro-Route: batch-files" \
-H "Idempotency-Key: $(uuidgen)" \
-F purpose=batch -F file=@input.jsonl

同步 Responses / Messages 不走这套;Chat / Embeddings 使用上面的防重复调用契约。 background: true 的 Responses 是例外: Idempotency-Key 可选但强烈建议使用;相同 Key、正文和 X-Halro-Run-ID 归因返回同一记录, 其中任一项改变却复用 Key 都返回 409。 未受有效幂等键保护的同步调用,重发可能产生新的费用。

只在能算出一个正数等待时长时才写入。每源限流与 Project 限流会带;Deployment 并发那条不设置;上游透传的 429 只在上游自己带了可解析的头时才有。读不到就用你自己的退避策略,不要假设它一定存在。

没有放之四海的建议值 —— 它取决于你的模型和 max_tokens。两个原则:

  • 超时要长于你允许的最长生成时间,否则你会在上游正常工作时把连接掐掉,而那正好落进上一节说的「结果不明确」。
  • 流式请求的超时应当针对单个事件间隔,而不是整个响应,否则长回答必然超时。

如何把 Project/Run 预算、客户端重试和 Halro Attempt 上限组合起来,见最佳实践案例库。