2026-10-28·by Sijie Wang#standmeet#architecture#design#events

model-call-retry

The model call gets exactly one retry: the httpx transport retries transient failures at most twice from a 400ms base, with a 10s public-tier cap.

模型调用的重试:唯一留下的同步重试

Parent: events

落地记录在 backend/internal/conversation/inference/http_retry.go、backend/internal/infra/httpx/retry_transport.go、backend/internal/infra/httpx/retry_budget.go、backend/internal/conversation/inference/errors.go。

模型调用在请求内部就是会重试的:只由 httpx transport 重试,最多 2 次。这和 retry-has-one-owner 并不矛盾:那篇的“只发一次”是禁第二层重试循环,transport 自身的重试是被允许的那一层。不要把那篇的日历例子推广过来:日历 free/busy/insert/delete 走 NoRetry,模型调用走带 transport 重试。

什么会重试

  • 最多重试 2 次(1 次初发 + 2 次重发),基础延迟 400 毫秒。
  • 重试:除调用方自身 context 取消/超时之外的传输错误;状态码 429 或 5xx 的响应。
  • 绝不重试:响应头已到、开始流式输出之后(已流出的 token 不会重读);调用自身的取消或超时。
  • 每次重试前等待:自身指数退避(400 毫秒、然后 800 毫秒)与服务商 Retry-After 头两者取大。比服务商要求的时间更早重发只会让封禁更严重,所以头更大时永远以头为准。
  • 等不起:如果等待超过本轮剩余预算,transport 立刻把响应交回去,不会把预算睡光再失败。

Public 档上限

  • Public 档一轮重试总等待上限 10 秒;超过就快速失败,返回 "The AI is busy right now — give it a minute and ask again."。Coded / BYOAI 档照旧等下去。
  • 原因:public 档跑在站长自己的免费服务商上;2026-09-25 那天每一步都返回 429 附 31–43 秒 retry-after,访客在沉默里等了好几分钟。
  • 限流的定义就是:重试用完后服务商仍返回 429,或者 retry-after 超过本轮愿意等的时长;两种都映射到同一句 busy 提示。

重试时看得见

每次退避都记一条 "llm transient failure — retrying",带 attempt、max、status、wait_ms 和 wait_from(backoff 还是 retry-after);前端 throbber 显示 "retrying"。wait_from 的作用是证明这次等待确实尊重了服务商,而不只是碰巧等够了。

不是这个机制

写路径的 2 秒等待 + 收据(async-response-contract)是另一条路:管的是写操作落进搜索索引,不是模型调用。两者不能混:模型调用超时没有收据,也没有 index_job_id。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

model-call-retry — Sijie Wang