AgentSession — Auto-Retry(自动重试)
文件位置:
F:\Pi\packages\coding-agent\src\core\agent-session.ts依赖的配置类型:
F:\Pi\packages\coding-agent\src\core\settings-manager.ts—RetrySettings、getRetrySettings()UI 事件消费:
F:\Pi\packages\coding-agent\src\modes/interactive/interactive-mode.ts—auto_retry_start/auto_retry_end事件处理编写目的:详细拆解 AgentSession 中 Auto-Retry(自动重试)机制的完整实现,包括
agent-session.ts中的编排方法、settings-manager.ts中的配置管理,以及interactive-mode.ts中的 UI 消费端。
一、Auto-Retry 概述
Auto-Retry(自动重试)是 AgentSession 中用于自动恢复 LLM 请求失败的机制。当 LLM 提供商返回临时性错误(过载、限流、网络波动)时,Pi 不会直接放弃,而是自动等待一段时间后重试。
设计目标
- 零干扰恢复:对于临时性的服务端错误,用户无需手动操作,系统自动重试
- 指数退避:重试间隔逐次翻倍,避免过载时雪上加霜
- 非重试错误不重试:配额耗尽、上下文溢出等不可恢复错误直接跳过
- 用户可控:用户可在重试倒计时期间按 Escape 取消,并可配置是否启用
什么错误会被重试
| 错误类型 | 重试? | 示例 |
|---|---|---|
| 服务端过载 | 是 | overloaded, service unavailable, 503 |
| 限流 | 是 | rate limit, 429, too many requests |
| 网络瞬断 | 是 | connection refused, connection lost, fetch failed |
| 超时 | 是 | timed out, timeout, stream ended before message_stop |
| 服务器内部错误 | 是 | 500, 502, 503, 504, server error |
| WebSocket 异常 | 是 | websocket closed, other side closed |
| 配额耗尽 | 否 | quota exceeded, insufficient_quota, available balance |
| 上下文溢出 | 否 | 由 Compaction 处理,不是重试的职责 |
| 语法/参数错误 | 否 | 重试也没用 |
默认配置
| |
二、AgentSession 中的 Auto-Retry 方法
2.1 _isRetryableError(message) — 判断是否可重试
| |
位置:agent-session.ts 第 2495 行
功能:判断一个 assistant 消息的 stopReason === "error" 是否属于可重试的错误。
执行流程:
1. stopReason !== "error" 或没有 errorMessage → 不重试
2. 上下文溢出(isContextOverflow)→ 不重试(交给 Compaction)
3. 不可重试的配额限制 → 不重试(见 _isNonRetryableProviderLimitError)
4. 正则匹配以下错误类型 → 重试
正则匹配的可重试错误模式:
overloaded
provider.?returned.?error
rate.?limit | too many requests | 429
500 | 502 | 503 | 504
service.?unavailable | server.?error | internal.?error
network.?error | connection.?error | connection.?refused | connection.?lost
websocket.?closed | websocket.?error | other side closed
fetch failed
upstream.?connect | reset before headers | socket hang up
ended without | stream ended before message_stop
http2 request did not get a response
timed? out | timeout | terminated
retry delay
可重试错误完整清单
下表列举 _isRetryableError() 正则匹配的所有错误模式,每条给出触发示例和重试意义:
| 类别 | 匹配模式 | 典型错误示例 | 重试原因 |
|---|---|---|---|
| 服务端过载 | overloaded | "overloaded_error: server is overloaded" | 服务端负载高,等待几秒后可能恢复 |
| 服务端错误 | provider.?returned.?error | "provider returned error: internal error" | 通用的上游服务端异常,通常是临时的 |
| 限流 | rate.?limit | "rate limit exceeded, retry after 30s" | 短时间请求过多,等待后配额重置 |
| 限流 | too many requests | "too many requests, please slow down" | 同上,不同格式 |
| 限流 | 429 | "429 Too Many Requests" | HTTP 429 状态码,标准限流响应 |
| 服务端错误 | 500 | "500 Internal Server Error" | HTTP 500,服务端临时不可用 |
| 服务端错误 | 502 | "502 Bad Gateway" | 网关/代理层临时故障 |
| 服务端错误 | 503 | "503 Service Unavailable" | 服务正在重启或维护 |
| 服务端错误 | 504 | "504 Gateway Timeout" | 上游服务响应超时 |
| 服务不可用 | service.?unavailable | "service temporarily unavailable" | 服务端临时不可用(同 503 但文本格式) |
| 服务端错误 | server.?error | "server error occurred, please retry" | 通用服务器错误 |
| 服务端错误 | internal.?error | "internal error encountered" | 内部错误,通常是瞬时的 |
| 网络错误 | network.?error | "network error: connect ETIMEDOUT" | 网络层故障,瞬时可恢复 |
| 连接错误 | connection.?error | "connection error: socket hang up" | 连接异常断开 |
| 连接被拒 | connection.?refused | "connect ECONNREFUSED" | 目标端口未监听,服务正在重启时常见 |
| 连接丢失 | connection.?lost | "connection lost while reading response" | 长连接意外中断 |
| WebSocket | websocket.?closed | "websocket connection closed unexpectedly" | 流式连接非正常关闭 |
| WebSocket | websocket.?error | "websocket error: unexpected frame" | WebSocket 协议层错误 |
| 连接异常 | other side closed | "other side closed the connection" | 对端主动关闭连接 |
| HTTP 层 | fetch failed | "fetch failed: aborted" | 请求在传输层失败 |
| HTTP 层 | upstream.?connect | "upstream connect error or disconnect/reset before headers" | 反向代理层连接上游失败 |
| HTTP 层 | reset before headers | "stream reset before headers received" | HTTP/2 流在收到响应头前被重置 |
| HTTP 层 | socket hang up | "socket hang up" | Node.js HTTP 请求的经典超时错误 |
| 流式错误 | ended without | "response ended without completing" | LLM 流式响应未完成就结束 |
| 流式错误 | stream ended before message_stop | "stream ended before message_stop" | SSE 流在收到停止标记前中断 |
| HTTP/2 | http2 request did not get a response | "http2: request did not get a response" | HTTP/2 连接复用中的响应丢失 |
| 超时 | timed? out | "connection timed out" | 请求在超时周期内未完成 |
| 超时 | timeout | "stream timeout after 30000ms" | 流式读取超时 |
| 终止 | terminated | "request terminated by server" | 服务端主动终止请求 |
| 退避 | retry delay | "retry delay not yet elapsed" | SDK 内部建议等待后重试 |
不可重试错误完整清单
下表列举 _isNonRetryableProviderLimitError() 和 _isRetryableError() 明确排除的所有错误模式:
| 排除方式 | 匹配模式 | 典型错误示例 | 不重试原因 |
|---|---|---|---|
_isNonRetryableProviderLimitError | GoUsageLimitError | "GoUsageLimitError: rate limit exceeded" | 免费套餐额度已用尽,充值前无法恢复 |
_isNonRetryableProviderLimitError | FreeUsageLimitError | "FreeUsageLimitError: free tier limit reached" | 同上 |
_isNonRetryableProviderLimitError | Monthly usage limit reached | "Monthly usage limit reached, upgrade to continue" | 月度额度耗尽 |
_isNonRetryableProviderLimitError | available balance | "insufficient available balance" | 账户余额不足 |
_isNonRetryableProviderLimitError | insufficient_quota | "insufficient_quota: you have exceeded your quota" | API 配额用完 |
_isNonRetryableProviderLimitError | out of budget | "out of budget, please upgrade your plan" | 预算超限 |
_isNonRetryableProviderLimitError | quota exceeded | "quota exceeded for this API key" | 同上,不同格式 |
_isNonRetryableProviderLimitError | billing | "billing error: account past due" | 计费账号异常 |
_isRetryableError 前置排除 | isContextOverflow | "context tokens exceeded limit" | 由 Compaction 机制处理,重试同样会溢出 |
_isRetryableError 前置排除 | stopReason !== "error" | (正常回复或中止回复) | 非错误消息不需要重试 |
_isRetryableError 前置排除 | !message.errorMessage | (无错误信息的 error) | 缺乏可判断的错误信息 |
设计意图:这些错误都是临时性的。服务端过载时等待几秒可能就恢复了;网络抖动是偶发的;限流等待退避后可能解除。相比直接失败,重试的收益远大于成本。
2.2 _isNonRetryableProviderLimitError(errorMessage) — 不可重试的配额限制
| |
位置:agent-session.ts 第 2488 行
功能:判断错误消息是否属于"配额耗尽"类型的不可重试错误。
匹配的错误模式:
GoUsageLimitError
FreeUsageLimitError
Monthly usage limit reached
available balance
insufficient_quota
out of budget
quota exceeded
billing
设计意图:配额耗尽不是临时性问题,单纯等待不会恢复,重试只会浪费 token 和 API 费用。必须由用户通过
/login充值或切换模型解决。
2.3 _prepareRetry(message) — 准备重试
| |
位置:agent-session.ts 第 2514 行
功能:执行重试前的准备工作,包括递增计数器、指数退避等待、移除错误消息。返回 true 表示调用者应继续 agent 循环(即执行重试),false 表示已达最大重试次数或已取消。
完整执行流程:
_prepareRetry(msg)
│
├─ settings.enabled === false ────→ return false
│
├─ this._retryAttempt++
│
├─ this._retryAttempt > maxRetries ──→ this._retryAttempt--, return false
│ (保留已完成的次数,用于发出 finalError 事件)
│
├─ 计算延迟:
│ delayMs = baseDelayMs * 2 ^ (attempt - 1)
│ = 2000ms → 4000ms → 8000ms (默认配置、3次重试)
│
├─ 发出 auto_retry_start 事件(含 attempt、maxAttempts、delayMs、errorMessage)
│
├─ 从 agent.state.messages 中移除错误消息
│ (保留在 session 文件中用于历史,但 agent 状态中清除,避免重试后消息重复)
│
├─ 创建 retryAbortController
│
├─ await sleep(delayMs, signal) ── 被中止 ──→ 发出 auto_retry_end 事件
│ _retryAttempt = 0
│ return false
│
└─ return true(调用者应继续 agent 循环)
指数退避公式:
| 重试次数 | 等待时间 |
|---|---|
| 第 1 次 | baseDelayMs * 2^0 = 2000ms = 2s |
| 第 2 次 | baseDelayMs * 2^1 = 4000ms = 4s |
| 第 3 次 | baseDelayMs * 2^2 = 8000ms = 8s |
设计意图:指数退避是分布式系统中处理临时故障的经典策略。第一次重试等 2 秒(服务端可能立即恢复),第二次等 4 秒(需要更多恢复时间),第三次等 8 秒。逐次翻倍避免在服务端压力大时进一步加剧负载。
2.4 abortRetry() — 取消重试
| |
位置:agent-session.ts 第 2549 行
功能:中止正在进行的重试等待。由 UI(用户按 Escape)或 dispose()/abort() 等清理方法调用。
| |
2.5 isRetrying / autoRetryEnabled / setAutoRetryEnabled() — 状态查询与开关
| |
位置:agent-session.ts 第 2553-2590 行
功能:
| 方法 | 返回 | 说明 |
|---|---|---|
isRetrying | boolean | 当前是否有重试正在进行(_retryAbortController 非空) |
autoRetryEnabled | boolean | 委托给 settingsManager.getRetryEnabled(),返回配置中的启用状态 |
setAutoRetryEnabled(enabled) | void | 调用 settingsManager.setRetryEnabled(enabled) 持久化设置 |
2.6 _willRetryAfterAgentEnd(event) — Agent 结束时判断是否要重试
| |
位置:agent-session.ts 第 562 行
功能:在 agent_end 事件处理中,判断是否应该在 agent 循环结束后启动重试。这个判断是只读的,真正的重试启动在 _handlePostAgentRun() 中。
| |
返回值用于:agent_end 事件的 willRetry 字段,让 UI 端(以及 extension)可以知道 agent 结束是因为失败后将重试,还是真正的结束。
三、Auto-Retry 在 Workflow 中的实例实现
3.1 完整调用链
用户输入 prompt
│
▼
prompt() → _runAgentPrompt()
│
▼
agent.prompt(messages)
│
▼
[LLM 请求失败,返回错误消息]
│
▼
_handleAgentEvent()
→ message_end 事件
→ 记录 _lastAssistantMessage
→ 如果 stopReason !== "error",重置 retryAttempt
│
▼
agent_end 事件
→ _willRetryAfterAgentEnd() 计算 willRetry 字段(用于 UI 显示)
│
▼
_runAgentPrompt() 的 finally 块执行完毕
│
▼
_handlePostAgentRun()
→ 检查 _lastAssistantMessage
→ _isRetryableError() → true
→ _prepareRetry()
→ 递增计数器
→ 发出 auto_retry_start 事件
→ 从 agent.state 中移除错误消息
→ 等待指数退避(可被 abort 取消)
→ return true(告诉循环调用 agent.continue())
│
▼
agent.continue()
→ 重新发送 LLM 请求(重试)
│
├─ 成功 → 正常处理回复,发出 auto_retry_end(success=true)
│
└─ 再次失败 → 再次进入 _handlePostAgentRun()
→ 重试次数已达上限
→ 发出 auto_retry_end(success=false, finalError)
→ 返回 false,循环结束
3.2 _handlePostAgentRun() 中的重试挂载点
| |
优先级顺序:
后处理链优先级:
1. Auto-Retry → 2. Compaction → 3. Queue drain
重试的优先级高于 Compaction,因为重试是轻量级操作(只是重新发请求),而 Compaction 是重量级操作(调用 LLM 生成摘要)。如果一个错误是可重试的,先重试,只有重试全部失败后才考虑其他路径。
3.3 成功回复后重置计数器
| |
设计意图:一个 Assistant 消息回复可能会产生多次 LLM 调用(steer/followUp 队列)。计数器在成功回复后立即重置,防止跨多个 LLM 调用累积计数。
四、Interactive Mode 中的 UI 处理
4.1 auto_retry_start 事件
位置:interactive-mode.ts 第 2999 行
| |
UI 效果:用户看到状态栏显示 Retrying (1/3) in 2s... (Esc to cancel),倒计时每秒更新。按 Escape 可立即取消重试。
4.2 auto_retry_end 事件
| |
UI 效果:
| 结果 | 表现 |
|---|---|
| 重试成功 | 倒计时消失,用户看到正常回复内容,无异常提示 |
| 重试全部失败 | 显示红色错误信息,如 “Retry failed after 3 attempts: overloaded” |
| 用户取消 | 显示 “Aborted after 1 retry attempt” |
五、配置管理
5.1 RetrySettings 接口
位置:settings-manager.ts 第 21 行
| |
5.2 getRetrySettings() 实现
| |
注意:
getRetrySettings()返回的是简化版本,仅包含 AgentSession 自身需要的字段。ProviderRetrySettings(含timeoutMs、maxRetryDelayMs)是 SDK 层面的重试配置,不在 AgentSession 的 Auto-Retry 中使用。
六、与其他机制的交互
6.1 与 Compaction 的关系
| 机制 | 处理场景 | 优先级 |
|---|---|---|
| Auto-Retry | 临时性服务端错误(过载、限流、网络波动) | 高 — 先尝试重试 |
| Compaction | 上下文溢出(context overflow) | 低 — 重试不处理时才检查 |
在 _handlePostAgentRun() 中,重试检查在 Compaction 之前。这意味着:
- 如果一条错误消息既是可重试的,又触发了上下文溢出 → 先重试
- 但
_isRetryableError()中明确排除了isContextOverflow→ 所以上下文溢出错误会直接跳到 Compaction
6.2 与 Dispose/Abort 的关系
| |
设计意图:在 session 清理或手动 abort 时,确保重试也被一并中止,避免重试在后台继续运行。
6.3 与 Disconnect/Reconnect 的关系
| |
当需要断开 agent 事件订阅(如 compact() 期间),重试状态(_retryAttempt、_retryAbortController)不受影响,因为这些状态存储在 AgentSession 实例中,而非 agent 事件流中。
七、设计总结
Auto-Retry 是 Pi 中最小但最有效的容错机制:
| 方面 | 设计 |
|---|---|
| 触发条件 | LLM 请求返回 stopReason: "error",且错误匹配可重试模式 |
| 不可重试 | 配额耗尽、上下文溢出、语法错误 |
| 等待策略 | 指数退避:baseDelayMs * 2^(attempt-1) |
| 默认配置 | 3 次重试,2s/4s/8s 间隔 |
| 可取消 | 用户按 Escape → abortRetry() |
| UI 反馈 | 倒计时指示器 + 可取消提示 |
| 生命周期 | 成功回复后立即重置计数器 |
| 优先级 | 高于 Compaction,低于正常回复处理 |
一句话总结:Auto-Retry 是 Pi 在面对 LLM 临时性故障时的自动恢复机制——使用指数退避等待、在用户可见的倒计时内自动重试,成功后对用户完全透明,失败时提供明确的错误信息。
附录:错误匹配实现的三层架构
Auto-Retry 的错误匹配逻辑并非集中在一个文件中,而是分布在三个不同的层次,各自服务于不同的目的:
┌──────────────────────────────────────────────────────────────────┐
│ 应用层 — AgentSession Auto-Retry │
│ F:\Pi\packages\coding-agent\src\core\agent-session.ts │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ _isRetryableError() 通用正则匹配 │ │
│ │ _isNonRetryableProviderLimitError() │ │
│ └────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────┤
│ AI 层 — Provider 溢出检测 │
│ F:\Pi\packages\ai\src\utils\overflow.ts │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ isContextOverflow() 按 Provider 分类的溢出模式 │ │
│ │ OVERFLOW_PATTERNS[19] 19 个 Provider 特定正则 │ │
│ └────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────┤
│ SDK 层 — OpenAI Codex 请求重试 │
│ F:\Pi\packages\ai\src\api\openai-codex-responses.ts │
│ ┌────────────────────────────────────────────────────────────┐ │
| │ isRetryableError(status, text) 基于 HTTP 状态码的重试 │ │
| │ isTerminalRateLimitError() 429 + 配额用尽 = 不重试 │ │
| │ getRetryAfterDelayMs() 读取 Retry-After 响应头 │ │
| └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
第一层:应用层 — AgentSession(通用模式匹配)
文件:F:\Pi\packages\coding-agent\src\core\agent-session.ts 第 2482-2540 行
这一层的两个函数是完全通用的正则匹配,不区分具体 Provider。所有模式均为硬编码在 agent-session.ts 中的正则表达式:
_isNonRetryableProviderLimitError(errorMessage)
| |
这些模式覆盖了主流 LLM 提供商的配额耗尽信息:
- Anthropic:
GoUsageLimitError(免费额度用完)、FreeUsageLimitError - OpenAI:
insufficient_quota、quota exceeded、billing - Google:
quota exceeded、out of budget - 通用:
Monthly usage limit reached、available balance
_isRetryableError(message)
| |
这一层的特点是不关心具体 Provider,只做通用判断。唯一的 Provider 相关知识来自 isContextOverflow() 的调用——但那是委托给下层的。
第二层:AI 层 — Provider 溢出检测(按 Provider 分类)
文件:F:\Pi\packages\ai\src\utils\overflow.ts
这一层是 Auto-Retry 中唯一真正按 Provider 分类的过滤逻辑。isContextOverflow() 维护了两组正则数组:
OVERFLOW_PATTERNS — 19 个覆盖主流 Provider 的正则
| 序号 | Provider | 正则 | 示例错误 |
|---|---|---|---|
| 1 | Anthropic | prompt is too long | "prompt is too long: 213462 tokens > 200000 maximum" |
| 2 | Anthropic | request_too_large | "413 Request exceeds the maximum size" |
| 3 | Amazon Bedrock | input is too long for requested model | "input is too long for requested model" |
| 4 | OpenAI | exceeds the context window | "Your input exceeds the context window of this model" |
| 5 | OpenAI/LiteLLM | exceeds.*maximum context length | "exceeds the model's maximum context length of 131072 tokens" |
| 6 | Google Gemini | input token count.*exceeds the maximum | "The input token count exceeds the maximum number of tokens" |
| 7 | xAI Grok | maximum prompt length is | "maximum prompt length is 131072 but request contains 537812 tokens" |
| 8 | Groq | reduce the length of the messages | "Please reduce the length of the messages" |
| 9 | OpenRouter | maximum context length is | "maximum context length is 200000 tokens" |
| 10 | OpenRouter/Poolside | exceeds.*maximum allowed input length | "Input length X exceeds the maximum allowed input length of Y" |
| 11 | Together AI | input.*tokens.*longer than.*context length | "The input (X tokens) is longer than the model's context length" |
| 12 | GitHub Copilot | exceeds the limit of | "prompt token count of X exceeds the limit of Y" |
| 13 | llama.cpp | exceeds the available context size | "the request exceeds the available context size" |
| 14 | LM Studio | greater than the context length | "tokens to keep is greater than the context length" |
| 15 | MiniMax | context window exceeds limit | "context window exceeds limit" |
| 16 | Kimi For Coding | exceeded model token limit | "exceeded model token limit: X (requested: Y)" |
| 17 | Mistral | too large for model with.*context length | "Prompt contains X tokens ... too large for model with Y context length" |
| 18 | z.ai | model_context_window_exceeded | z.ai 将溢出暴露为 finish_reason |
| 19 | Ollama | prompt too long; exceeded.*context length | "prompt too long; exceeded max context length by X tokens" |
| 20 | — | context_length_exceeded | 通用回退模式 |
| 21 | — | too many tokens | 通用回退模式 |
| 22 | — | token limit exceeded | 通用回退模式 |
| 23 | Cerebras | `^4(?:00 | 13).*\(no body\)` |
NON_OVERFLOW_PATTERNS — 防止误杀的反向排除
这些模式虽然可能匹配 OVERFLOW_PATTERNS,但实际上是非溢出错误(如限流),需要排除:
| 模式 | 来源 Provider | 原因 |
|---|---|---|
Throttling error: / Service unavailable: | AWS Bedrock | Bedrock 的限流信息格式为 “Throttling error: Too many tokens…",会误触 too many tokens 模式 |
rate limit | 通用 | 限流信息有时包含 token 相关的描述 |
too many requests | 通用 | HTTP 429 中的通用描述 |
三种溢出检测模式
isContextOverflow() 实现了三种不同的溢出检测逻辑,分别应对不同 Provider 的行为:
| 检测模式 | 适用 Provider | 触发条件 |
|---|---|---|
| Error-based | Anthropic, OpenAI, Google, xAI, Groq, Mistral 等 | stopReason === "error" + 匹配 OVERFLOW_PATTERNS |
| Silent overflow | z.ai | stopReason === "stop" 但 input + cacheRead > contextWindow |
| Length-stop | Xiaomi MiMo | stopReason === "length" + output === 0 + input + cacheRead >= contextWindow * 0.99 |
文件源码引用:overflow.ts 第 1-178 行为正则定义与注释(每个 Provider 的典型错误消息都写在注释中),第 180-227 行为
isContextOverflow()实现,第 229-234 行为测试用导出函数。
第三层:SDK 层 — OpenAI Codex 请求重试(HTTP 状态码)
文件:F:\Pi\packages\ai\src\api\openai-codex-responses.ts 第 113-142 行
这一层与 Auto-Retry 机制正交。它是 OpenAI Codex Responses API 的 SDK 实现内部的请求重试逻辑,处理的是 HTTP 层面的瞬态错误:
isRetryableError(status, errorText)
| |
与 AgentSession Auto-Retry 的区别
| 维度 | AgentSession _isRetryableError | SDK isRetryableError |
|---|---|---|
| 层面 | 应用层—LLM 消息层错误 | SDK 层—HTTP 请求层错误 |
| 输入 | AssistantMessage(含 stopReason) | number status + string errorText |
| 重试机制 | 移除 agent state 中的错误消息,调用 agent.continue() | 循环 fetch,递增指数退避 |
| 退避计算 | baseDelayMs * 2^(attempt-1) | 优先读 Retry-After 响应头,否则 BASE_DELAY_MS * 2^attempt |
| 覆盖 Provider | 通用(不区分) | 仅 OpenAI Codex Responses API |
是否使用 isContextOverflow | 是(在 _isRetryableError 中调用) | 否 |
两者的关系是串联的:
- SDK 层首先在 HTTP 层面处理网络抖动和限流(可能重试 2-3 次)
- 如果 SDK 层重试全部失败,错误最终会以
AssistantMessage.stopReason === "error"的形式到达 AgentSession - AgentSession 的
_handlePostAgentRun()检查是否可重试,启动应用层的指数退避重试
架构总结:为什么这样分层
| 层级 | 解决的问题 | 为什么不合并 |
|---|---|---|
| 应用层(通用正则) | 判断一条 LLM 返回的消息是否值得重试 | 这是语义级判断,与底层传输协议无关。所有 Provider 的临时性错误最终都表现为相似的关键词 |
| AI 层(Provider 分类) | 如果 SDK 层重试全部失败,错误最终会以 AssistantMessage.stopReason === \"error\" 的形式到达 AgentSession\n3. AgentSession 的 _handlePostAgentRun() 检查是否可重试,启动应用层的指数退避重试\n\n—\n\n### 架构总结:为什么这样分层\n\n | 层级 |