错误分类与恢复
一个长时运行的 Agent 必然会遇到网络抖动、限流、模型过载、上下文过长、输出被截断、工具失败和用户中断。
恢复设计的关键不是“多重试几次”,而是先判断失败发生在哪一层,再选择不会扩大副作用的恢复方式。
四层恢复不能混为一谈
| 层级 | 解决什么 | 在同一用户回合内改变什么 |
|---|---|---|
| 请求 retry | 短暂失败、凭证刷新和可在请求层纠正的参数问题 | 保持同一模型,重建允许变化的请求参数后再次尝试 |
| 传输模式 fallback | 流式端点或流程不完整 | 保持同一模型,改用非流式传输 |
| 模型 fallback | 符合策略的连续 529/overloaded_error | 清理失败尝试,改用备用模型重新编译当前请求 |
| 语义恢复 | API 已返回有意义的“过长”、“输出截断”等结果 | 改变历史投影或输出策略后继续循环 |
如果不区分这四层,很容易用请求 retry 反复发送一个确定过长的 Prompt,或用完整回合重跑来处理一次短暂断线。
请求 retry:同一模型内的短暂失败与参数纠正
可重试范围主要包含:
- 连接错误与超时;
- 某些请求冲突、限流和服务器错误;
- 访问令牌撤销、OAuth 或云凭证过期;
- 需要刷新 API 客户端或凭证缓存的情况;
- 快速模式被限流或不受支持,以及旧式输入长度加输出上限超过窗口等可在请求层纠正的参数问题。
重试使用指数退避、上限和抖动,并尊重服务器给出的等待提示。具体次数和等待时间可被运行环境调整,不应被理解为 Agent 语义的固定常量。
后台摘要、标题和分类器不一定与前台主任务使用相同过载重试策略。对次要后台工作立即失败,可以防止过载时这些任务放大服务压力。
流式转非流式:传输恢复不等于换模型
下列情况可以触发同一模型的非流式 fallback:
- 流迭代过程抛出传输异常;
- 流很长时间没有任何新块,且条件启用的 watchdog 主动中止;
- 流结束但没有形成有效 assistant 响应;
- 中间代理不支持当前流式端点。
它的风险是已发生副作用可能重复,因此这项能力可以被显式关闭。消息层能撤销旧 assistant 尝试,却不能撤销已经发到外部系统的动作。
模型降级(fallback):只针对合资格的连续过载
模型过载不会在第一次失败时立即换模型。运行时会先应用请求级退避,只有连续出现符合资格的 529/overloaded_error、存在备用模型,且账户与模型策略允许时,才把当前 Agent 迭代转到 fallback model。模型访问失败、404 或任意“不可用”错误不能被笼统归入这条路径。
切换前还要处理当前尝试中已产生的工具块,避免旧工具结果在新模型响应中突然回流。
API 错误也是一种内部 assistant 消息
多数重试耗尽的 API 错误不会立即穿透为未分类异常,而是被转成带有类型的合成 assistant 错误,例如:
- timeout 与 connection;
- rate limit;
- prompt too long;
- PDF、图片或请求大小错误;
- tool pairing;
- 认证、模型访问、计费与权限错误。
当主循环看见最后一条是 API 错误时,不会再运行 Stop Hook,避免“API 错误 → Hook 要求继续 → 同样错误”的死循环。
恢复中的错误会先被扣留
如果 SDK 消费者一看到 error 就立即关闭,那么 prompt too long、媒体过大和 max output tokens 这些仍可恢复的中间状态,会被误认为最终失败。
因此查询循环会先扣留这些错误,在压缩、清理、升级输出上限或续写确实无法恢复后,才把原错误当作终态向外交付。
这是一个值得复用的精妙设计:内部需要看见恢复信号,外部只应看见最终成功或真正耗尽的失败。
输出被截断的恢复
当模型因输出上限停止时,条件启用的恢复顺序是:
- 当前仍使用默认上限且用户没有显式覆盖时,先扩大当前请求的输出上限;
- 仍然被截断时,把已生成内容保留在历史中,追加“从中断处继续”的 meta user 提醒;
- 续写次数达到上限后,才返回原始截断错误。
这不是重新从头生成,而是把已产生内容作为新的对话事实让模型续写。
prompt too long 与媒体过大不走同一恢复链
prompt too long 的恢复顺序大致是:
- 如果细粒度上下文折叠已有待消费结果,先排空并重试一次;
- 仍失败时,尝试条件启用的响应式压缩;
- 同一用户回合只允许一次响应式压缩尝试,避免无限压缩螺旋;
- 恢复失败时直接交付原错误,不再运行 Stop Hook。
媒体过大在相应构建能力与运行时开关同时启用时,可以走剥离媒体的响应式恢复契约;该条件能力未启用时不能假定存在这条路径。它与普通文本历史折叠是不同的恢复问题。
工具失败通常不属于 API 重试
工具失败已经是一个有语义的外部观测,通常会作为 tool_result 返给模型,让模型决定是修正参数、切换工具还是停止。
把工具失败自动隐藏并在本地重试,可能使模型看不见重要环境变化,也可能重复副作用。
恢复系统的总原则
- 传输错误在传输层处理;
- 合资格的连续模型过载在模型 fallback 层处理;
- 上下文和输出问题在主回合语义层处理;
- 工具失败作为环境事实返回模型;
- 用户中断和 Hook 阻止是控制决策,不被自动重试抵消。
这种分层让每种恢复只改变必要状态,避免一个通用“重来”按钮重复所有计算和副作用。
源码定位
- 请求重试:
src/services/api/withRetry.ts - 流/非流恢复与错误分类:
src/services/api/claude.ts、src/services/api/errors.ts - 模型 fallback 与语义恢复:
src/query.ts - 压缩恢复:
src/services/compact/ - 工具失败投影:
src/services/tools/toolExecution.ts