Stitch SDK错误处理完全指南:StitchError错误码清单与指数退避自动重试原理
Stitch SDK错误处理完全指南:StitchError错误码清单与指数退避自动重试原理
Stitch SDK(@google/stitch-sdk)是一个能通过文本提示词生成 UI 屏幕、并程序化提取其 HTML 与截图的开发工具包。当调用 Stitch API 失败时,SDK 不会抛出五花八门的原生异常,而是统一抛出一个结构化的 StitchError,并内置"指数退避 + 全抖动"的自动重试机制。这篇指南面向新手,带你一次性搞清楚:9 个错误码分别是什么意思、哪些错误会自动重试、退避延迟是怎么算出来的。
1️⃣ 为什么所有错误都长一个样?
传统写法里,网络超时、404、限流、认证失败各自是不同的异常类型,你只能靠 catch 后字符串匹配来区分。Stitch SDK 的做法更干净:
无论底层失败来自 HTTP 状态码、MCP 工具错误文本还是网络中断,最终都会归一化为一个
StitchError。
StitchError 继承自标准 Error(可以 instanceof 判断),并携带一组"可决策"的字段:
| 字段 | 含义 |
|---|---|
code |
错误码,如 RATE_LIMITED(9 选 1,见下文清单) |
message |
人类可读的错误描述 |
recoverable |
布尔值:这个错误值不值得重试? 新手最需要看的就是它 |
status |
来自 HTTP 响应时的状态码(如 429、404) |
toolName |
失败时正在调用的 MCP 工具名(如 get_screen) |
retryAfter |
服务端通过 Retry-After 请求头要求的等待毫秒数 |
定义见 errors.ts,其中 StitchErrorCode 就是一个穷举的枚举,StitchError.fromUnknown() 还能把任意"野生"异常兜底转成带 UNKNOWN_ERROR 码的规范错误。
2️⃣ StitchError 错误码清单:9 种情况一次看懂
错误码在 spec/errors.ts 中以 Zod 枚举形式定义,全量清单如下:
| 错误码 | 典型来源 | recoverable |
你会怎么办 |
|---|---|---|---|
AUTH_FAILED |
HTTP 401,API Key / Token 无效 | ❌ | 检查 STITCH_API_KEY 或 OAuth 配置 |
NOT_FOUND |
HTTP 404,项目或屏幕不存在 | ❌ | 核对资源 ID |
PERMISSION_DENIED |
HTTP 403,Key 无权访问该项目 | ❌ | 确认 Key 的归属项目 |
RATE_LIMITED |
HTTP 429,触发限流 | ✅ | SDK 对只读操作已自动重试 |
SERVICE_UNAVAILABLE |
HTTP 502 / 503 / 504,服务端故障 | ✅ | 稍后再试,SDK 已自动退避 |
NETWORK_ERROR |
连接被重置、超时、拒绝连接等 | ✅ | 检查网络,SDK 会重连并自动重试 |
VALIDATION_ERROR |
参数不合法(如非法 baseUrl) |
❌ | 修正代码中的输入 |
CLIENT_CLOSED |
客户端已 close() 后仍在使用 |
❌ | 新建一个 StitchToolClient 实例 |
UNKNOWN_ERROR |
无法归类的兜底错误 | ❌ | 查看 message 详情 |
其中 recoverable 的判定逻辑只认三种:限流、服务不可用、网络故障(error-mapping.ts 中的 isRecoverable)。也就是说,SDK 用这个字段明确告诉你:"重试有意义的是这 3 类,其余的重试也是徒劳。"
3️⃣ 错误分类原理:HTTP 状态码永远优先
那么一个失败到底被判成哪个码?分类函数 classifyError 遵循两条规则:
规则一:有 HTTP 状态码时,状态码说了算。
429 → RATE_LIMITED;502/503/504 → SERVICE_UNAVAILABLE;404 → NOT_FOUND;403 → PERMISSION_DENIED;401 → AUTH_FAILED;其他 5xx(如 500)→ UNKNOWN_ERROR。哪怕响应体里写着 "rate limit",403 依然判为 PERMISSION_DENIED——状态码永远压过文本。
规则二:没有状态码时(MCP 工具只返回错误文本),按关键词匹配。
SDK 用了一组带单词边界的正则来识别文本,这是很讲究的工程设计:
\brate limit|\b429\b能匹配 "rate limited / rate limiting",但不会误伤 "crate limit";\b404\b只认独立的 404,所以 "1404 items" 不会被误判成 404;\bauthorized\b式的边界确保 "author"、"authorize" 永远不会被错分成AUTH_FAILED。
这些"防误判"细节都有对应测试覆盖,见 error-mapping.test.ts。
此外,MCP 传输层抛出的裸 HTTP 错误(比如网关直接返回 429)会先经过 normalizeTransportError 归一化:它按状态码分类、解析 Retry-After 头,补上 status 与 toolName 后再交给重试逻辑;fetch failed、ECONNRESET 等网络特征则被归为 NETWORK_ERROR。
4️⃣ 指数退避自动重试:只重试"读",绝不重试"写"
这是 SDK 最有价值的设计。自动重试藏在 callTool 的重试循环 中,先回答三个关键问题:
Q1:哪些工具会被自动重试?
只有幂等的只读操作——名字以 get_ / list_ 开头的工具(如 list_projects、get_screen)。生成类 / 修改类工具(generate_*、edit_*、create_*)永远不会自动重试:重试一次生成,等于白白多烧几分钟算力和配额,还可能在服务端留下孤儿屏幕(client.ts 中的设计注释)。测试也锁死了这一条:generate_screen_from_text 遇到 429 立即抛错,只调用 1 次(retry.test.ts)。
Q2:哪些错误码会触发重试?
仅 RATE_LIMITED、SERVICE_UNAVAILABLE、NETWORK_ERROR 三种。NOT_FOUND 重试一万次也还是 404,所以一次都不重试。
Q3:等待多久再试?—— 指数退避 + 全抖动公式
delay = min(maxMs, baseMs × 2^attempt) × random(0..1)
实现见 computeBackoffMs。以默认配置(baseMs=250、maxMs=4000)为例,理论上界是:
| 第几次重试 (attempt) | 延迟上界 |
|---|---|
| 1 | 250ms |
| 2 | 500ms |
| 3 | 1000ms |
| 4 | 2000ms |
| 5 及以上 | 4000ms(被 maxMs 封顶) |
每次实际等待还会再乘一个 0~1 的随机数(全抖动),避免大量客户端在同一毫秒集体重连、对服务端形成"重试风暴"。如果响应带有 Retry-After 头(支持秒数或 HTTP 日期,见 parseRetryAfter),SDK 取 max(退避值, retryAfter),尊重服务端的最低等待要求。
还有一个贴心的细节:一旦遇到 NETWORK_ERROR,SDK 会把内部连接标记为断开(client.ts),下一轮循环自动重连后再发请求——你无需自己处理 socket 重建。
5️⃣ 重试参数怎么配?
重试策略通过 StitchToolClient 的 retry 配置项控制,Schema 定义在 spec/client.ts:
| 参数 | 默认值 | 说明 |
|---|---|---|
attempts |
3 |
总尝试次数(含首次调用),上限 10 |
baseMs |
250 |
指数退避的基准延迟(毫秒) |
maxMs |
4000 |
单次退避延迟的上限(毫秒) |
retry: false |
— | 一键关闭自动重试 |
注意:attempts: 3 意味着"失败 1 次后最多再试 2 次",3 次都失败才把 StitchError 抛给你(retry.test.ts)。REST 直连路径 httpPost(用于批量上传等写操作)出于同样的"不重试写"原则,明确不做自动重试。
6️⃣ 实战:捕获并正确响应 StitchError
官方 README 给出的推荐写法(README.md Error Handling):
import { stitch, StitchError } from "@google/stitch-sdk";
try {
const project = stitch.project("bad-id");
await project.screens();
} catch (error) {
if (error instanceof StitchError) {
console.error(error.code); // "NOT_FOUND"
console.error(error.message); // 人类可读描述
console.error(error.recoverable); // false → 别重试,检查 ID
}
}
给新手的三条落地建议:
- 先判断
instanceof StitchError,再读code做分支,不要解析message文本; recoverable === true但 SDK 已经重试过仍失败时,再决定要不要做应用级重试(比如生成类工具被限流,只能等冷却后手动发起);AUTH_FAILED出现 401 时,优先检查环境变量STITCH_API_KEY是否已加载——SDK 对baseUrl等非法配置也会以VALIDATION_ERROR提前暴露,别和认证失败混淆。
更多行为细节(429 网关错误归一化、503 + Retry-After 等)全部有单元测试锁定,欢迎阅读 retry.test.ts 与 error-mapping.test.ts 作为"活文档"。
📌 小结
- Stitch SDK 用统一
StitchError+ 9 个穷举错误码,取代了对散乱异常的字符串猜测; - 分类遵循"HTTP 状态码优先、单词边界关键词兜底",杜绝 "1404" 误判 404 这类乌龙;
- 自动重试只覆盖
get_*/list_*只读操作,用min(maxMs, baseMs×2^n)×rand的指数退避 + 全抖动,并尊重Retry-After; - 生成类操作永不自动重试——保护你的配额与数据一致性;
- 代码与测试入口:spec/errors.ts、spec/error-mapping.ts、client.ts、spec/client.ts。