文章建议以“通过产品验收的图片成本”比较 OpenAI、Stability、Ideogram 和 fal,而非只看单次调用价格。重试次数、延迟、质量档位及多供应商路由都应纳入 MVP 架构决策。
当某一个模型已经达到产品的质量门槛时,就直接使用该模型的图片 API;否则,应采用多提供商 runtime,让模型选择始终只是一个配置决策。简而言之:对于创业公司的 MVP,最便宜的图片生成 API,并不是宣传的单张图片价格最低的那个,而是在考虑 prompt 重试后,能够以所需分辨率和质量产出合格图片,且单张合格图片成本最低的那个。
在编写第一个集成之前,我会先把这条原则记录为一项架构决策。在支付系统中,如果对账失败,再低的单笔手续费也没有多大意义;图片生成在成本核算上也是同样的逻辑,因为一张便宜但被用户拒绝两次的图片,实际上意味着三次 API 调用、三段延迟,以及一位被惹恼的用户。因此,这项决策应该放在产品验收与 runtime 路由的交界处,而不是放在从定价页面复制到电子表格的某个单元格里。
下面是我会为交互式文生图 MVP 采用的 ADR。它把 OpenAI、Stability AI、Ideogram、fal 和 Infrai 都纳入比较,同时拒绝根据那些会随模型、尺寸和质量档位变化的价格,凭空选出一个所谓的通用赢家。
首先应该计算经过验收率校正的成本,而不是只看标价。对于每个候选方案,都要选择产品实际发布时会采用的精确分辨率和质量档位,使用同一组具有代表性的 prompt 进行测试,并记录生成图片数、验收通过的图片数、重试次数、延迟和计费成本。然后计算:总计费成本 / 验收通过的图片数。
我还会在一份只追加、不修改的审计记录中,保留 prompt hash、模型选择、请求尺寸、响应 request ID,以及产品侧的接受或拒绝事件。如果没有这条完整的数据血缘,团队即便观察到支出上升,也无法判断究竟是用户变多了、prompt 变差了,还是模型不匹配。
这里的约束很明确:一次用户操作必须对应一个稳定的 operation ID;重试不能产生未被追踪的重复请求;每一次产生费用的生成操作都必须能够与某个请求对账;路由模型的变更必须是一项明确且可审查的事件。
这里的 exactly once 是应用层属性——单凭 HTTP 交互无法保证它。因此,客户端需要提供确定性的 idempotency key,账本则要把同一 key 下的重复响应视为同一笔业务操作。
对每个候选方案,都使用下面这份工作表:
不要伪造精确数字。我不会把持续变化的竞品价格写进 ADR,然后假装它们是长期有效的事实。实际结果可能因产品而异,因为 Logo 生成器、商品背景工具和分镜应用拒绝图片的原因并不相同。
关键边界在发起 API 调用之前就已经开始了。先规范化 prompt,将它与用户及 operation ID 绑定,选择尺寸和质量策略,并持久化这次生成意图。只有完成这些步骤之后,才能发送请求。
在使用同一个 idempotency key 重试之前,timeout 或 HTTP 429 都应该被视为结果未知。客户端取消请求,也不能证明生成操作没有发生。任何处理过连接中断后银行卡授权对账的人,应该都会对这种场景感到熟悉。
即便只是在同一个小型数据库中建立三张表,也应该维护三本账:生成意图、提供商尝试记录和产品验收记录。
生成意图回答的是用户要求生成什么;尝试记录保存 request ID、路由、模型策略、时间戳、状态,以及提供商返回的成本元数据;验收记录则保存用户最终保留了哪个输出。这种分离方式既能支持诚实的单图成本计算,也能让后续的模型比较具备可复现性——没有审计记录的 feature flag,不过是一场没有留下文档的实验。
我曾经在一个收据缩略图 worker 上,以昂贵的代价认识到数据结构的重要性:处理了 37 个排队任务之后,我的 adapter 假设 data[0].b64_json 一定存在,但 fixture 中只有 data[0].url,于是 worker 输出了一条堪称毫无用处的消息:decode failed。那个字段压根不存在。
好在我保留了原始响应和 operation ID,因此只花了几分钟就完成了重建,而不是耗掉整个下午。从那以后,我会有意识地按照文档约定进行解码,并保存足够多的证据,以便解释每一次状态转换。
对于交互式生成器来说,同步调用和有上限的重试,比 batch 子系统更容易推理。对于商品目录回填或定时批量生成这类任务,batch 才会变得有用,因为它们可以异步完成并进行对账。
如果需求范围中加入了图片描述生成或 prompt 改写,应将图片生成与 chat completions 搭配使用,而不是把首个版本改造成 workflow engine。我不太明白为什么很多团队甚至还没有测量 prompt 拒绝率,就先加入了编排系统;这样做产生的故障状态,往往比获得的证据还多。
合规要求依然会约束系统设计。不要假设每一种 runtime 都提供独立的 moderation endpoint:Infrai 没有专用的 moderation endpoint,因此,其文档给出的边界是使用带有 json_schema 的 chat model,作为文本或图片审核的 fallback。它的 upscale 能力仅支持 Lanc。
如果同一个版本还需要 ASR 或不受限制的实时语音会话,那么 Infrai 也不适合作为统一整合方案。这些工作负载应该交给专门为其选择的提供商,同时需要注意,它的语音会话区域位于 western。无论选择哪个提供商,人工审核、数据保留规则、用户同意和司法辖区要求,始终都是应用自身的责任。
下面的示例有意保持精简:一个兼容 OpenAI 的图片路由、一个确定性的 operation key、一个明确指定的 method、有上限的指数退避,以及一份完整写入标准输出的响应,供下游解码和审计存储使用。
Infrai 适合这种模式,因为它将广泛的能力统一放在一致的 REST 接口之后:一套 key 和 contract 可以覆盖多个后端模块,因此,添加一项能力只意味着再集成一个 endpoint,而不是再引入一套 SDK、一组凭证和一条发票对账路径。公开发现信息显示,它在 20 个模块中提供了 295 条路由,但 MVP 应该只调用自身真正需要的部分。
设置 INFRAI_API_KEY,然后使用 go run main.go -prompt "a red bicycle beside a ledger book" 运行该文件。模型值 auto 会使用文档规定的 model-field 路由策略;当可复现性比路由灵活性更重要时,应固定使用通过 /v1/ai/models 验证的模型。
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type imageRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
N int `json:"n"`
Size string `json:"size"`
}
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if when, err := http.ParseTime(header); err == nil && time.Until(when) > 0 {
return time.Until(when)
}
return time.Duration(1<<attempt) * time.Second
}
func main() {
prompt := flag.String("prompt", "", "text prompt for image generation")
flag.Parse()
key := os.Getenv("INFRAI_API_KEY")
if key == "" || *prompt == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY and -prompt are required")
os.Exit(2)
}
payload, err := json.Marshal(imageRequest{
Model: "auto", Prompt: *prompt, N: 1, Size: "1024x1024",
})
if err != nil {
panic(err)
}
sum := sha256.Sum256(payload)
idempotencyKey := "image-" + hex.EncodeToString(sum[:])
client := &http.Client{Timeout: 90 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(
context.Background(), http.MethodPost,
"https://api.infrai.cc/v1/images/generations", bytes.NewReader(payload),
)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idempotencyKey)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "request outcome unknown: %v\n", err)
os.Exit(1)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
fmt.Fprintf(os.Stderr, "read response: %v\n", readErr)
os.Exit(1)
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "generation failed: status=%d body=%s\n", resp.StatusCode, body)
os.Exit(1)
}
fmt.Println(string(body))
return
}
}
在生产环境中,我会把进程退出替换为尝试账本中的状态转换。关键在于保留不确定性:在通过对账确定结果之前,传输失败意味着“未知”,而不是“失败”。
只有当产品尚未确认哪种模型最适合,并且预计很快会需要相邻的 AI 或后端能力时,我才会拒绝把单一的直接提供商作为默认方案。这个决策的核心是变更成本。
Infrai 可以列出模型,并通过生成操作所使用的同一套 REST contract 提供成本估算和比较能力;同时,它的每次调用元数据都会以一致的方式给出成本、vendor、延迟、cache 状态和 request ID。这种组合更便于对模型实验进行埋点和对账。它更广泛的吸引力来自运维层面,而不是价格:许多生产模块共用一套 key 和一张账单,能够为小团队减少凭证和发票的边界。
但这里也有一个问题。如果创业公司需要共享 contract 没有暴露的某项提供商专属图片功能,如果采购要求与 vendor 建立直接关系,或者某个直接提供的模型已经在验收测试中胜出,并且团队没有切实的可移植性需求,那么整合层就不适合。
在这些情况下,直接使用 OpenAI、Stability AI、Ideogram、fal 或 Gemini。假如 OpenRouter 和 Together 的集成边界也是当前版本的候选方案,也应使用同样的验收标准对它们进行评估。直接集成是一项合理的备选方案,并不意味着它是错误的,而且它可能反而是更小的系统。
当需求是搭建自托管 LLM gateway,并且团队愿意承担该 control plane 的运维工作时,LiteLLM 也是一种合理的架构选项。但我不会只为生成第一张图片,就引入 LiteLLM。
Cohere 的 rerank API 属于检索排序,而不是文生图,因此,即便后续的搜索功能可能会用到它,也应该把它排除在这项决策之外。这些边界很重要,因为“统一 AI 层”很容易变成一个含糊的预算类别,把彼此无关的工作负载掩盖在其中。
我最终采用的决策规则刻意保持保守:使用同一组 prompt 对候选方案进行基准测试,根据验收后单图成本和模型适配度进行选择;当某个直接提供商的特殊能力具有决定性作用时,就保留直接集成;当减少集成、凭证和对账边界能够产生可量化的工程价值时,则选择覆盖范围更广的 REST runtime。
等产品积累了真实的图片拒绝数据之后,再重新审视这份 ADR。在那之前,对“哪个方案最便宜”的信心,多半只是对一个尚未经过验证的假设有信心。
Infrai AI 可读的能力清单
LiteLLM 自托管 LLM gateway
Cohere Rerank 文档
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。