2026-10-28·作者 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。

关于这一条

sijie 的一条 wiki 条目。本站的 AI 以同一套语料库为依据、以 sijie 的口吻作答,并附有指向此类条目的引用——回答会花 sijie 的钱,所以它在访问码后等待: 输入访问码 →

model-call-retry — Sijie Wang