AI 应用接了第二家模型供应商之后,事情就不再是"多加一个接口"那么简单了。
我们先后接了火山方舟、阿里云百炼,以及一个自有的 OpenAI 兼容网关。接下第一家的时候一切顺利;接第二家的时候,问题才开始冒出来——不是调不通,是钱会算错。
下面这 7 条,是我们踩过之后定下来的规矩。每一条背后都有代价。
先讲一个前提:为什么必须要多供应商
只接一家会有三个问题:
| 问题 | 后果 |
|---|---|
| 单点故障 | 上游挂了,你的产品就停 |
| 价格被动 | 他涨价你只能跟 |
| 能力受限 | 只有他有的模型你才能用 |
所以多供应商是必然的。而一旦多了,你就不再是"调 API 的人",你是"管钱和管状态的人"。
先分清楚四层责任
在讲那 7 条之前,先说我们最后定下来的分层。很多坑的根源是分层没想清楚。
| 层 | 负责什么 | 不负责什么 |
|---|---|---|
| 创作端 | 公开模型、能力参数、参考素材、预计积分 | 供应商名、上游模型 ID、密钥、协议字段 |
| 主站 API | 权限、能力校验、选路、预占 / 结算 / 退款 | 各家供应商的请求差异 |
| 自有网关 | 协议映射、上游提交与查询、能力清单、线路治理 | 用户、画布、积分、素材权限 |
| 官方直连 | 网关提交前故障时的受控兜底 | 已被上游接受任务的二次提交 |
注意最后两列——创作端不知道供应商是谁,主站不管各家协议差异,网关碰不到用户和钱。 这条边界一旦模糊,后面全是麻烦。
铁律一:同一任务、同一次尝试,永远用同一个执行键
AI 生成是异步的,一次任务可能要等几十秒到几分钟。这中间会发生什么?
客户端超时重试、网络抖动、服务重启、用户狂点提交按钮。
如果没有一个稳定的执行键,同一个任务就会给上游提交两次。
后果分两种:
- 两次都成功 → 用户扣两次钱,拿到两份结果
- 一次成功一次失败 → 你扣了一次钱,但上游跑了两个任务,成本算你的
执行键要在创建任务时就生成好,并且重试时复用——不是每次重试新生成一个。这一条不实现,后面每一条都没意义。
铁律二:上游返回任务 ID 之后,立刻落库
拿到任务 ID 的那一瞬间意味着什么?
钱已经花出去了。 上游已经开始跑,不管结果回不回来,成本都可能产生。
如果这时候没把 ID 存下来,服务一重启,你就:
- 不知道有这个任务在跑
- 不知道去哪里轮询结果
- 用户的钱扣了,东西拿不到
所以规则是:先持久化 ID,再回应用户。 不是"存库里异步处理",是同步写入成功才算提交成功。
铁律三:只有"拿到 ID 之前"的连接失败,才允许切备用线路
这一条最微妙,也最值钱。
失败分两种:
| 类型 | 能不能切备用线路 |
|---|---|
| 连接失败(请求根本没发出去) | ✅ 可以切 |
| 提交后超时(请求到了,响应没回来) | ❌ 绝对不能切 |
为什么第二种不能切?因为你不知道上游到底接没接。
如果接了,你再切备用线路重发一次,就是付两次钱。
所以我们的规则是:只有自有网关在获得任务 ID 之前连接失败,才允许切换官方备用线路。 一旦拿到 ID,无论后面发生什么,都不再切。
这一条的价值在于:它把"我可能付两次钱"的概率,从"每次超时都可能"降到"零"。
铁律四:拿到 ID 之后即使失败,也只能退款,不能重发
这是上一条的推论,但反直觉。
直觉上"失败了要重试"。但在付费的异步任务里,重试的代价可能是双倍成本。
所以:已经拿到任务 ID 的任务,失败就是失败——记录失败、原路退款,不创建第二个付费任务。
宁可这次体验差一点,也不能让账目变得不可解释。
铁律五:凭据必须精确匹配所有者,不能跨租户借用
如果支持用户自带密钥(BYOK),这一条是红线。
系统通常会有"个人 → 团队 → 平台"的凭据查找顺序。这里有个极其危险的默认行为:用户的个人密钥失效了,系统静默用平台额度顶上。
用户以为花的是自己的钱,实际花的是你的。
所以我们的规则是:
- 凭据只能精确匹配当前所有者
- 换线路时禁止跨凭据层切换——用户密钥失效就报错,不许偷偷换平台账号继续跑
- 平台、团队、个人三层凭据,各自独立,不互相兜底
这一条同时保护两件事:你的钱,和用户对你账目的信任。
铁律六:付款来源在创建任务时就固化
一次生成会经历:选路 → 预占积分 → 执行 → 结算或退款。
如果这中间有任何配置变了(团队预算规则调整、用户换了凭据、线路改了报价),退款的时候就会出现一个问题:这笔钱该退给谁?
所以规则是:任务创建时就把"付款来源"写死在任务记录上,退款只能回到原 Workspace 账本或原付款用户。
不固化的话,账目会随着配置漂移,最后没法对。
铁律七:浏览器永远拿不到明文密钥,创作端不展示供应商
两条:
一、密钥不进浏览器。 只要密钥出现在前端代码、网络请求或者页面里,它一定会泄露——这是时间问题,不是概率问题。
二、创作端不展示供应商信息。 用户看到"这条走的是某某供应商的某某模型",下一步就是绕开你直接去找上游。
所以创作端只给用户看三样:公开模型名、能力参数、预计积分。
除了这 7 条,还有三件事要做
1. 线路健康探测与熔断。
定时探测每条线路,连续失败到阈值就进入冷却,冷却结束自动恢复。没有这个,上游挂了你还在一直接单。
2. 统一的任务取消。
用户点取消,要做三件事:主站退款、标记任务、通知路由站——路由站再尽力调用上游取消接口。上游不支持取消时,要在日志里留下明确诊断,别让运维猜。
3. 结果状态归一化。
每家上游的任务状态命名都不一样。统一成五个状态就够:
QUEUED → RUNNING → SUCCEEDED
→ FAILED
→ CANCELED
内部逻辑只认这五个,外部差异全部在网关层吃掉。
最后
接第一家供应商的时候,你觉得这是个技术问题。
接第二家的时候你会发现——这是个账目问题。
而这 7 条的共同点只有一个:都在防止"同一笔业务被花两次钱"。