- Published on
- Word count
- 544 字 · 阅读约 2 分钟
成本治理:付费 API 的计费安全与双重试状态机设计
- Authors

- Name
- 眠月
成本治理:付费 API 的计费安全与双重试状态机设计
是两个独立的重试状态机,而且这种分离是有意设计。
状态机一:生成请求(_request_openai_image,L1290)
for attempt in range(1, OPENAI_IMAGE_MAX_ATTEMPTS + 1): # 3 次
POST → 按错误类型分类 → retryable?
# 退避: 5s → 15s → 30s
决策维度:错误类型决定是否可重试
- ConnectTimeout → 可重试(请求确定未送达)
- ReadTimeout/连接中断 → 不重试(未确认状态,防重复扣费)
- 401/403 → 仅多 key 时重试(轮换)
- 429/5xx → 退避重试
- 其他 4xx → 快速失败
状态机二:图片下载(_openai_image_download_bytes,L1202)
for attempt in range(1, OPENAI_IMAGE_MAX_DOWNLOAD_ATTEMPTS + 1): # 3 次
GET image_url → 200 且有内容? → 成功返回
# 固定退避: 2s
决策维度:只要没拿到 200 就一律重试,无错误分类、无 key 轮换、退避固定。
为什么必须是两个状态机
关键差异在计费语义,两阶段的"重试安全性"完全不同:
| 维度 | 生成 POST | 下载 GET |
|---|---|---|
| 是否扣费 | 每次重发都可能扣费(未确认态) | 图已付费,下载是幂等的免费操作 |
| 重试安全性 | 需按确认状态分类,保守 | 完全安全,可无脑重试 |
| 失败后果 | 重试错 → 重复扣费 | 重试错 → 只是浪费几次 GET |
| 路径关系 | 主请求 | 只在响应含 url 时才进入(b64_json 直接解码,跳过此状态机) |
两个状态机的串联关系(_parse_openai_image_response 是衔接点):
POST 生成(状态机一,3次,按计费语义分类重试)
└─ 响应含 b64_json → 直接 base64 解码(无状态机二)
└─ 响应含 url → 进入状态机二(3次,幂等重试同一 URL)
总结
这正是 review 修复后统一的计费安全原则的体现:已扣费的内容(生成结果 URL)失败就重试原地址拿回,绝不重新发起付费请求;而付费请求本身(POST)只在没有扣费确定性的前提下才允许重试。
同一个"幂等性"原则,在两个状态机上的具体策略截然相反——一个激进重试,一个保守分类。面试讲这段时,这个对比是最好的展开点。