HTTP 重试与错误分类(对标 Spring AI RetryUtils)
可重试(transient):
不可重试(non-transient):
(require '[im.ttalk.agent.provider.http.retry :as retry])
;; 包裹一个返回 http response map 的函数 (retry/with-retry #(http/post url :headers h :body b) {:max-retries 3})
;; 关闭重试(max-retries 0)即等价于直接调用
HTTP 重试与错误分类(对标 Spring AI RetryUtils)
========================================
设计目标
========================================
- 错误二分类:transient(可重试)vs non-transient(不可重试),对标
Spring AI 的 TransientAiException / NonTransientAiException。
- 指数退避 + 满抖动(full jitter),避免重试风暴。
- 尊重服务端的 Retry-After 响应头(429 / 503 常见)。
- opt-in:仅当调用方在 config 里传入 :retry 时启用,不改变现有默认行为。
========================================
错误分类规则
========================================
可重试(transient):
- 网络层错误(连接失败、超时)—— HTTP 客户端返回 {:error ...}
- HTTP 408 / 409 / 425 / 429 / 500 / 502 / 503 / 504 / 529
不可重试(non-transient):
- 其余 4xx(400 参数错误、401 鉴权、403、404 等)—— 重试无意义
========================================
基本用法
========================================
(require '[im.ttalk.agent.provider.http.retry :as retry])
;; 包裹一个返回 http response map 的函数
(retry/with-retry
#(http/post url :headers h :body b)
{:max-retries 3})
;; 关闭重试(max-retries 0)即等价于直接调用
(compute-backoff attempt opts)(compute-backoff attempt opts rand-fn)计算第 attempt 次重试的退避毫秒(含满抖动)——就是 im.ttalk.agent.retry/compute-backoff
(ChatModel 层重试用的同一份;两份曾逐字重复,2026-11-09 收成一份,designs/http-module.md §8)。
attempt 从 0 开始。基础退避 = min(base * multiplier^attempt, max-delay), 再在 [0, 基础退避] 上取满抖动(full jitter)。
参数:
计算第 attempt 次重试的退避毫秒(含满抖动)——就是 `im.ttalk.agent.retry/compute-backoff`
(ChatModel 层重试用的同一份;两份曾逐字重复,2026-11-09 收成一份,designs/http-module.md §8)。
attempt 从 0 开始。基础退避 = min(base * multiplier^attempt, max-delay),
再在 [0, 基础退避] 上取满抖动(full jitter)。
参数:
- attempt: 第几次重试(0-based)
- opts: {:base-delay :multiplier :max-delay}
- rand-fn: 取 [0,1) 随机数的函数,便于测试注入(缺省 rand)默认重试配置
默认重试配置 - :max-retries 最大重试次数(不含首次调用) - :base-delay 首次退避基准(毫秒) - :multiplier 退避倍率 - :max-delay 单次退避上限(毫秒) - :respect-retry-after? 是否优先采用响应头 Retry-After
(maybe-with-retry config request-fn)根据 config :retry 决定是否启用重试(opt-in 入口)
参数:
返回: http response map
根据 config :retry 决定是否启用重试(opt-in 入口) 参数: - config: provider config,读取其 :retry - 为 nil/false -> 直接调用 request-fn(零开销) - 为 true -> 用默认配置重试 - 为 map -> 作为重试配置 - request-fn: 无参函数,返回 http response map 返回: http response map
(parse-retry-after headers)(parse-retry-after headers now-ms)从响应头解析服务端要求的等待,返回毫秒数或 nil(不判上限,上限在 with-retry 里按 :max-delay 截)。
解析在 im.ttalk.http.date/retry-after-ms:retry-after-ms 头(毫秒)优先,其次 Retry-After——
秒数(可带小数)或 HTTP 日期(IMF-fixdate / RFC 850 / asctime,距今毫秒,已过 ⇒ 0)。
头名大小写无关;值是 List<String> / 向量时取第一个——java.net.http 给的正是 List,此前直接 (str v) 得到
"[120]",Retry-After 在真实请求上从未生效(designs/provider-reliability.md §1.3)。
参数:
从响应头解析服务端要求的等待,返回毫秒数或 nil(不判上限,上限在 `with-retry` 里按 :max-delay 截)。 解析在 `im.ttalk.http.date/retry-after-ms`:`retry-after-ms` 头(毫秒)优先,其次 `Retry-After`—— 秒数(可带小数)或 HTTP 日期(IMF-fixdate / RFC 850 / asctime,距今毫秒,已过 ⇒ 0)。 头名大小写无关;值是 `List<String>` / 向量时取第一个——java.net.http 给的正是 List,此前直接 `(str v)` 得到 "[120]",Retry-After 在真实请求上**从未生效**(designs/provider-reliability.md §1.3)。 参数: - headers: 响应头 map - now-ms: 当前时间戳(毫秒),便于测试注入;缺省取系统时间
(retryable-ex? e)异常是否可重试
连接/超时类异常视为可重试;其余默认不可重试。
异常是否可重试 连接/超时类异常视为可重试;其余默认不可重试。
(transient-response? resp)http response map 是否代表一个可重试的失败
参数:
返回: boolean
规则:
http response map 是否代表一个可重试的失败
参数:
- resp: {:status int :error any :success? bool ...}
返回: boolean
规则:
- 网络层错误 (:error 非空) -> 可重试
- 状态码命中 transient-status-codes -> 可重试
- 其余(含成功响应、不可重试 4xx)-> 不重试(transient-status? status)状态码是否可重试
示例: (transient-status? 429) ; => true (transient-status? 400) ; => false
状态码是否可重试 示例: (transient-status? 429) ; => true (transient-status? 400) ; => false
(with-retry request-fn opts)带指数退避的重试包装
参数:
返回: 最终 http response map(成功,或重试耗尽后的最后一次响应)
说明:
带指数退避的重试包装
参数:
- request-fn: 无参函数,返回 http response map({:status :error :success? ...})
- opts: 重试配置(合并 default-retry-opts)
- :max-retries 最大重试次数
- :base-delay 退避基准毫秒
- :multiplier 退避倍率
- :max-delay 退避上限毫秒
- :respect-retry-after? 是否优先用 Retry-After 头
- :retry-on 判定是否需要重试的谓词 (fn [resp] bool),
缺省 transient-response?
- :rand-fn 随机数函数(测试用)
- :on-retry 每次重试前回调 (fn [{:keys [attempt delay-ms response]}])
返回: 最终 http response map(成功,或重试耗尽后的最后一次响应)
说明:
- 仅对同步、整体可重试的请求使用(如非流式 LLM 调用、或流式建链阶段)。
- max-retries 为 0 时等价于直接调用 request-fn。cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |