重试、超时与幂等
先把 SDK 的自动重试关掉
Section titled “先把 SDK 的自动重试关掉”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 });关掉之后,重试策略由你自己写 —— 下面是判断依据。
什么时候重试是安全的
Section titled “什么时候重试是安全的”| 情况 | 能否重试 | 说明 |
|---|---|---|
| 429 每源限流 / Project 限流 | 可以 | 读 Retry-After(可能不存在,见下),退避后重试 |
| 429 并发上限 | 可以 | 同上 |
503 provider_unavailable | 可以 | 当前没有健康的 Deployment |
| 上游 5xx | 网关已替你试过 | Halro 内部的回退是有界的;返回给你时说明它已经放弃 |
403 token_guard_blocked | 不要 | 价格超出策略上限,重试多少次都是同一个结果 |
403 model_not_allowed / 404 model_not_found | 不要 | 配置问题,找管理员 |
| 400 字段被拒 | 不要 | 请求本身不合法,改请求 |
| 结果不明确的失败 | 谨慎 | 见下一节 |
结果不明确的失败,网关不会替你重试
Section titled “结果不明确的失败,网关不会替你重试”如果一次上游调用的结果是歧义的 —— 例如连接在请求已发出但响应未回来时断开 —— Halro 不会重试它,也不会把费用退掉。这是有意的:无法确定上游是否已经产生了计费事件时,账目按发生过保守处理,而不是乐观地假设它没发生。
所以这类失败给你的是一个错误,而账上可能已经有一笔。你自己重发是允许的,但要清楚那会是第二笔独立的费用。
资源类端点:用 Idempotency-Key
Section titled “资源类端点:用 Idempotency-Key”创建型的资源端点接受 Idempotency-Key 请求头 —— 异步调用(POST /v1/async/invocations)、文件创建(POST /v1/files)、批处理创建(POST /v1/batches)。带上它,重复提交同一个键不会创建第二个资源。
curl https://halro.example.com/v1/files \ -H "Authorization: Bearer $HALRO_GATEWAY_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F purpose=batch -F file=@input.jsonl推理端点(chat / responses / messages)不走这套。 那里没有幂等键可用,所以上一节那条规则更重要:结果不明确时,重发就是一笔新的费用。
Retry-After 不是每次都有
Section titled “Retry-After 不是每次都有”只在能算出一个正数等待时长时才写入。每源限流与 Project 限流会带;Deployment 并发那条不设置;上游透传的 429 只在上游自己带了可解析的头时才有。读不到就用你自己的退避策略,不要假设它一定存在。
没有放之四海的建议值 —— 它取决于你的模型和 max_tokens。两个原则:
- 超时要长于你允许的最长生成时间,否则你会在上游正常工作时把连接掐掉,而那正好落进上一节说的「结果不明确」。
- 流式请求的超时应当针对单个事件间隔,而不是整个响应,否则长回答必然超时。