docs(llm): 重试配置每层写清「什么错误会走到这层」

原来每层只有一句泛泛的说明,用户填了旋钮看不到效果时无从判断是配错了还是
这类错误压根不落在这层。现在每层固定给两句:

  触发     具体到状态码——建连是 408/429/5xx 与网络层错误,空响应是 200+
           正常 stop 却无内容块,安全窗口是拿到 200 之后的断流/overloaded/
           流内 429,熔断区分瞬时(429/5xx)与确定性(401/402/403/404),
           意图重跑是前面几层都没兜住的 model_error
  不走这层  长得像但走别处的:max_tokens 截断、额度耗尽(402)、上下文过长
           (413)、4xx 确定性拒绝

模型配置抽屉里的紧凑版只显示「触发」这一句,全局页两句都给。纯文案,无逻辑改动。
This commit is contained in:
Autumn-27 committed 2026-09-09 13:39:44 -04:00
1 parent a50bf02428
commit aa12706ed1
1 file changed
+35 -6
@@ -36,6 +36,10 @@ type LayerMeta = {
title: string;
/** 这层重试发生在哪、由谁执行 */
where: string;
/** 什么样的错误会走到这层——具体到状态码,别让人猜 */
trigger: string;
/** 长得像但【不】走这层的错误,省得填了没反应还以为是 bug */
skips?: string;
desc: string;
attemptsLabel: string;
/** 次数留空时的默认值,用于占位符 */
@@ -50,7 +54,10 @@ export const RETRY_LAYERS = {
connect: {
title: "建连重试",
where: "SDK · 拿到 200 之前",
desc: "连接重置 / 超时 / 429 / 5xx 等在流开始前的失败,原样重发请求。流一旦开始,中途断流不归这层管。",
trigger:
"连不上或还没拿到 200:连接重置 / 读写超时 / DNS 失败等网络层错误,以及 HTTP 408、429、500、502、503、504。",
skips: "其余状态码(400 / 401 / 403 / 404 / 413 / 422 等)都是确定性拒绝,重发也一样失败,直接上抛。",
desc: "原样重发同一个请求。流一旦开始(已经拿到 200),中途断开就不归这层管了。",
attemptsLabel: "重试次数",
defAttempts: 3,
defInterval: "0.5s→1s→2s 指数(封顶 8s)",
@@ -59,7 +66,10 @@ export const RETRY_LAYERS = {
empty: {
title: "空响应重试",
where: "SDK · 仅 openai 格式",
desc: "请求正常完成、但一个内容块都没有(正常 stop 且无输出)时重发整个 prompt。max_tokens 截断不算,不会重试。",
trigger:
"HTTP 200、finish_reason 是正常 stop,但整条响应一个内容块都没有——网关空帧、思考字段丢帧、采样打嗝都会长这样。",
skips: "因 max_tokens 截断而没有内容的不算(那要靠调高输出上限解决,重发只会再撞一次)。",
desc: "重发整个 prompt,所以在长上下文上比较贵,次数不宜给大。",
attemptsLabel: "重试次数",
defAttempts: 2,
defInterval: "0.5s→1s→2s 指数(封顶 8s)",
@@ -68,7 +78,11 @@ export const RETRY_LAYERS = {
stream: {
title: "同 provider 安全窗口重试",
where: "本项目 · 未交付输出前",
desc: "流中途断开、供应商过载、流内 429——只要一个 token 都还没交给调用方,就在同一个配置上重放,不会重复模型输出或工具执行。额度耗尽、上下文过长、4xx 确定性拒绝不重试。",
trigger:
"流已经建立(拿到 200)之后才出问题:连接中途断开、供应商 overloaded、流内的 429 / 5xx 错误事件——且一个 token 都还没交给调用方。",
skips:
"额度耗尽(402 / insufficient_quota,交给轮询换配置)、上下文过长(413 / context length,交给压缩)、400 / 401 / 403 / 404 / 422 确定性拒绝,都不重试。",
desc: "在同一个配置上重放同一个请求。因为还没交付任何输出,重放不会重复模型输出或工具执行。",
attemptsLabel: "重试次数",
defAttempts: 2,
defInterval: "0.5s→1s 指数(封顶 4s)",
@@ -77,7 +91,10 @@ export const RETRY_LAYERS = {
breaker: {
title: "轮询熔断",
where: "本项目 · 进程级,全局一份",
desc: "某个配置连续失败到阈值就进入冷却,冷却期内轮询直接跳过它。余额不足 / 密钥失效这类确定性失败不看阈值,第一次就熔断。",
trigger:
"瞬时失败(429、5xx、网络错误)连续累计到阈值时熔断;余额不足(402)、密钥失效(401 / 403)、模型不存在(404)这类确定性失败不看阈值,第一次就熔断。",
skips: "成功一次即清零,所以偶尔抽风的配置不会被慢慢攒到熔断。",
desc: "熔断后进入冷却,冷却期内轮询直接跳过这个配置。状态落库,重启不丢。",
attemptsLabel: "连续失败几次熔断",
defAttempts: 3,
defInterval: "1min→5min→30min 梯度",
@@ -86,7 +103,10 @@ export const RETRY_LAYERS = {
intent: {
title: "意图重跑",
where: "本项目 · 进程级,全局一份",
desc: "worker 以 model_error 收场(provider 故障,内层重试全都用尽)时,整条意图重跑。重跑期间任务被暂停 / 终止 / 进入收尾则立即让位。",
trigger:
"前面几层都没兜住:worker 以 model_error 收场——内层重试全部用尽,或者流已经开始交付输出后才断掉(那时重放不安全,只能整条重来)。",
skips: "额度耗尽已经由轮询换配置处理,不在这里重跑;任务被暂停 / 终止 / 进入收尾时立即让位,不占用退避时间。",
desc: "整条意图从头再跑一遍。它是最外层,一次重跑意味着里面几层的次数会再乘一遍。",
attemptsLabel: "重跑次数",
defAttempts: 2,
defInterval: "固定 3s",
@@ -154,7 +174,7 @@ export function RetryRuleFields({
idPrefix: string;
value: LLMRetryRule;
onChange: (r: LLMRetryRule) => void;
/** true = 配置抽屉里的紧凑版:只留标题与两个输入 */
/** true = 配置抽屉里的紧凑版:省掉展开说明,只留「什么错误会走到这层」这一句 */
compact?: boolean;
}) {
const meta = RETRY_LAYERS[layer];
@@ -166,6 +186,15 @@ export function RetryRuleFields({
<Label className="text-sm">{meta.title}</Label>
<span className="text-muted-foreground text-xs">{meta.where}</span>
</div>
{/* 哪些错误会走到这层,具体到状态码——填了旋钮却看不到效果,多半是错误压根不落在这层。 */}
<p className="text-muted-foreground text-xs">
<span className="font-medium text-foreground">触发</span>:{meta.trigger}
</p>
{!compact && meta.skips && (
<p className="text-muted-foreground text-xs">
<span className="font-medium text-foreground">不走这层</span>:{meta.skips}
</p>
)}
{!compact && <p className="text-muted-foreground text-xs">{meta.desc}</p>}
</div>
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">