From aa12706ed1e17ded9cb5c1ec80425cb365422c20 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:39:44 -0400 Subject: [PATCH] =?UTF-8?q?docs(llm):=20=E9=87=8D=E8=AF=95=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=AF=8F=E5=B1=82=E5=86=99=E6=B8=85=E3=80=8C=E4=BB=80?= =?UTF-8?q?=E4=B9=88=E9=94=99=E8=AF=AF=E4=BC=9A=E8=B5=B0=E5=88=B0=E8=BF=99?= =?UTF-8?q?=E5=B1=82=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来每层只有一句泛泛的说明,用户填了旋钮看不到效果时无从判断是配错了还是 这类错误压根不落在这层。现在每层固定给两句: 触发 具体到状态码——建连是 408/429/5xx 与网络层错误,空响应是 200+ 正常 stop 却无内容块,安全窗口是拿到 200 之后的断流/overloaded/ 流内 429,熔断区分瞬时(429/5xx)与确定性(401/402/403/404), 意图重跑是前面几层都没兜住的 model_error 不走这层 长得像但走别处的:max_tokens 截断、额度耗尽(402)、上下文过长 (413)、4xx 确定性拒绝 模型配置抽屉里的紧凑版只显示「触发」这一句,全局页两句都给。纯文案,无逻辑改动。 --- .../(main)/system/llm/_components/retry.tsx | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/web/src/app/(main)/system/llm/_components/retry.tsx b/web/src/app/(main)/system/llm/_components/retry.tsx index 28f4048..5149031 100644 --- a/web/src/app/(main)/system/llm/_components/retry.tsx +++ b/web/src/app/(main)/system/llm/_components/retry.tsx @@ -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({ {meta.where} + {/* 哪些错误会走到这层,具体到状态码——填了旋钮却看不到效果,多半是错误压根不落在这层。 */} +

+ 触发:{meta.trigger} +

+ {!compact && meta.skips && ( +

+ 不走这层:{meta.skips} +

+ )} {!compact &&

{meta.desc}

}