图像生成为什么需要独立的 Gateway 抽象:参数、重试与幂等设计

开篇:一个 generateImage(),可能不是一次供应商请求
调用方看到一次 generateImage(),供应商侧却未必只收到一次请求。AI SDK 的官方文档明确说明:当 n 超过模型单次生成上限时,SDK 会自动拆成多次并行调用;API 还提供 maxRetries,当前参考页给出的默认值是 2。一个看似原子的 SDK 调用,已经可能展开成多次有独立费用和失败状态的供应商 Attempt。
图像生成还多了尺寸、宽高比、参考图、mask、Seed、质量、风格、输出格式和二进制产物。若统一层只做字段重命名,它会在最危险的位置制造"看似兼容":参数被忽略、重试重复计费、fallback 输出不符合用户约束,取消按钮也可能只停止等待而没有停止供应商执行。
AI SDK 的 generateImage 展示了统一入口的价值,也明确保留 providerOptions、size/aspectRatio 互斥能力、seed、mask、abortSignal 和多调用拆分。官方文档一个生产级自有网关应延续这个原则:统一共同语义,同时把一次用户意图展开后的每个 Attempt 和 Artifact 暴露为可审计对象。
核心结论

- 用户 Job、供应商 Attempt 和图片 Artifact 是三个不同实体,幂等键也应分层。
- Canonical request 只应包含跨模型语义稳定的字段,特殊能力放入 provider options。
- 路由前必须做 capability negotiation,不能把不支持参数静默丢弃。
- 自动重试必须区分"确定未执行""执行状态未知"和"已完成响应丢失"。
- Cost Ledger 记录每次供应商 Attempt,而不是只记录最终展示给用户的图。
一、三层对象模型

这条链路不是一对一:一个 User Image Job 可以展开成多个 Provider Attempt;每个 Attempt 又可以产生一个或多个 Artifact。审核、转码和缩略图会继续派生新 Artifact,最终只有被选中的对象进入交付集合。
Image Job
用户意图的稳定对象:prompt、编辑输入、期望数量、能力要求、预算、保留策略、租户和业务幂等键。
Provider Attempt
一次实际供应商请求:模型、映射后参数、请求 ID、开始/结束、状态、错误、计费、fallback 原因。
Artifact
任何二进制产物:供应商输出、原图、审核图、缩略图、水印图和最终交付图。每个 Artifact 有哈希、MIME、尺寸、存储位置、来源 Attempt、派生父对象和删除状态。
将三者混成一张 generations 表,会导致重试后无法解释哪个账单对应哪个图片。
网关至少要为每次调用返回一份内部执行回执:
json
{
"job_id": "img_job_01",
"attempts": [
{
"attempt_id": "att_01",
"trigger": "initial",
"provider": "provider-a",
"credential_type": "byok",
"status": "unknown_after_send",
"provider_request_id": "req_xxx",
"estimated_cost_usd": 0.04
},
{
"attempt_id": "att_02",
"trigger": "manual_retry_after_reconciliation",
"provider": "provider-b",
"credential_type": "system",
"status": "succeeded",
"artifact_ids": ["art_01"]
}
],
"delivery_artifact_ids": ["art_02"]
}
这份回执不是为了把内部实现暴露给终端用户,而是让客服、账单、删除任务和事故复盘能够回答:发送了几次、谁实际执行、用了谁的凭证、哪些图片被保存或交付。
二、Canonical Request
ts
export type ImageJobRequest = {
userRequestId: string;
prompt: string;
count: number;
operation: 'generate' | 'edit' | 'variation';
inputArtifacts?: string[];
maskArtifact?: string;
output: ({
aspectRatio: string;
width?: never;
height?: never;
} | {
aspectRatio?: never;
width: number;
height: number;
}) & {
format?: 'png' | 'jpeg' | 'webp';
transparent?: boolean;
};
reproducibility?: {
seed?: number;
strict?: boolean;
};
policy: {
maxCostUsd: number;
allowedModels?: string[];
artifactRetention: 'ephemeral' | 'standard' | 'custom';
inferenceDataPolicy: 'required_zdr' | 'disallow_training' | 'standard';
};
providerOptions?: Record<string, unknown>;
};
字段必须分三种:
- Required semantic:供应商不支持就不能路由;
- Preferred semantic:可降级,但要返回 warning;
- Provider-specific:只对指定 adapter 有效。
例如 transparent=true 若是产品承诺,应标 required;如果只是偏好,可 fallback 到后处理抠图,但这会改变成本与质量,必须写入 plan。
三、Capability Registry
json
{
"model": "provider/model-version",
"operations": ["generate", "edit"],
"sizes": {"mode": "aspect_ratio", "values": ["1:1", "16:9", "9:16"]},
"supports_seed": false,
"supports_mask": "unknown",
"output_delivery": "inline_binary",
"inference_data_policy": {
"gateway_zdr_eligible": "unknown",
"byok_contract_status": "unknown"
},
"pricing": {
"type": "per_image",
"amount": "PRICE_FROM_VERIFIED_SOURCE",
"source": "MODEL_PAGE_OR_MODELS_API",
"as_of": "QUERY_TIME"
},
"registry_version": "2026-08-31"
}
示例字段只是网关内部格式;具体 capability 必须从官方模型文档确认。unknown 与 false 要分开:未知不能被自动当成不支持或支持。Vercel 在 2026 年 4 月新增团队级与请求级 ZDR,可用 zeroDataRetention: true 让 Gateway 只选择符合要求的 provider,并在响应 metadata 中留下过滤轨迹;这意味着应用不必继续手写每个系统凭证 route 的 ZDR 表。
但 ZDR 仍不能被压成模型布尔值。团队/请求策略、Gateway 当前 provider 资格、BYOK 合同和应用自己的 Artifact 保留策略是不同层。特别是 BYOK 协议由用户持有,Gateway 无法自动替你证明其保留条款。Capability Registry 应记录可验证的策略输入与执行回执,而不是复制一个静态 supports_zdr=true。
路由步骤:
- 校验请求;
- 根据 required capabilities 过滤;
- 根据 inference data policy、Gateway 执行回执、BYOK 合同、区域和供应商 allowlist 过滤;
- 估算成本与延迟;
- 按质量/成本/可用性排序;
- 生成参数映射计划;
- 返回 warnings;
- 创建 Attempt 并发送。
四、参数映射不能静默
| Canonical | Provider A | Provider B | 处理 |
|---|---|---|---|
aspectRatio=16:9 |
aspect_ratio |
size=1536x864 |
可映射,记录实际尺寸 |
seed=42 |
支持 | 不支持 | strict 时拒绝;非 strict 警告 |
count=4 |
单次最多 1 | 单次最多 4 | A 拆 4 次 Attempt |
format=webp |
PNG only | WebP | A 生成后转码,新 Artifact |
edit+mask |
支持 | 不支持 | 不能 fallback 到 B |
AI SDK 文档指出 n 可能被自动拆成多次请求,maxImagesPerCall 还能改变拆分方式,正说明"一个 SDK 调用"与"一个供应商请求"不同。成本和幂等必须按 Attempt 记录,并固定 SDK 与 adapter 版本,否则升级依赖也可能改变 Attempt 数量。
五、幂等分三层
Business Idempotency
用户重复点击或客户端重试,不应创建第二个 Job:
text
tenant + operation + user_request_id → image_job_id
Attempt Idempotency
若供应商支持原生 idempotency key,使用 image_job_id + attempt_index。若不支持,网关只能避免自己重复发送,无法保证网络未知状态下供应商没有执行。
Artifact Deduplication
对返回二进制计算内容哈希,避免同一响应被多次保存;但不同生成结果即使 prompt 相同也不应按请求哈希去重,因为随机性是产品行为。
六、重试状态机
text
CREATED
→ SENT
→ ACKNOWLEDGED
→ SUCCEEDED
→ ARTIFACT_STORED
失败可分:
PRE_SEND_FAILURE:确定未到供应商,可安全重试;REJECTED:参数/政策错误,不应原样重试;PROVIDER_FAILED:供应商明确失败,按策略 fallback;UNKNOWN_AFTER_SEND:已发送但无结果,可能计费和生成;RESPONSE_RECEIVED_STORE_FAILED:已有图片,不应重新生成,应重试存储。
UNKNOWN_AFTER_SEND 是关键。自动重试前应:查询供应商状态(若有)、等待宽限期、检查 provider request ID、核对成本预算,并在产品允许时要求用户确认。
这里还要区分三类"超时":客户端 abortSignal、SDK 自身重试策略,以及 Gateway/provider timeout。Vercel 当前 provider timeout 文档针对 BYOK,并以"开始响应前"的时限触发 failover;官方同时提醒部分 provider 不支持取消,超时请求仍可能计费。该文档的示例是流式文本,不能直接证明图像 provider 具有相同取消语义,但它足以证明一个通用边界:停止等待不等于远端没有执行。图像网关必须把 timeout 后状态标记为未知,除非 provider 给出确定终态。
七、Fallback 合同

Vercel AI Gateway 已支持模型级 fallback,并会按 models 数组依次尝试。官方 Changelog 进一步说明,unsupported input、context limit 和 provider outage 等错误都可能触发 fallback。对文本可用的"先成功者返回"策略,放到图像任务上仍需应用层收紧:编辑能力、参考图数量、尺寸、透明背景、合规策略和结果许可不一致时,技术成功不等于产品兼容。
因此 Fallback 计划应在发送前生成:
json
{
"primary": "model-a",
"fallbacks": [
{
"model": "model-b",
"allowed_on": ["provider_unavailable", "rate_limited"],
"not_allowed_on": ["safety_rejection", "invalid_prompt"],
"degradations": ["seed_not_supported", "max_resolution_lower"],
"requires_user_consent": false
}
]
}
安全拒绝不能自动换模型绕过。ZDR、训练限制或 BYOK 合同不满足时,也不能只因可用性进入 fallback。编辑任务、参考图和人脸一致性通常需要更严格的模型绑定。若使用 Gateway 原生 models fallback,应用应只把已经通过同一 Required Capability Contract 的模型放进数组,并从 provider metadata 回收实际 Attempt 链;否则应关闭自动模型 fallback,由自有状态机逐次发送。
八、核心数据表
image_jobs
- id、tenant、request hash、status;
- canonical request;
- requested count;
- budget、artifact retention policy、inference data policy;
- final artifact IDs;
- created/finished/cancelled。
provider_attempts
- job ID、attempt index;
- provider/model、credential type;
- mapped request;
- SDK/adapter version、provider request ID;
- status/error;
- started/ended;
- reported cost/estimated cost;
- retry/fallback reason、Gateway routing metadata。
artifacts
- source attempt、parent artifact;
- storage URI(内部引用,不给永久公开 URL);
- SHA-256、MIME、尺寸、字节;
- kind:raw/thumbnail/moderated/delivered;
- artifact retention/deletion state。
moderation_results
- input/output;
- policy/model version;
- labels/scores;
- decision;
- human review。
cost_ledger
- attempt、charge type、amount、currency;
- estimated/reported/reconciled;
- provider invoice reference;
- tenant/user/product attribution。
九、取消的真实含义

用户点击取消后:
- 如果尚未发送,终止且不计生成成本;
- 已发送但供应商不可取消,只能标记
cancel_requested,结果到达后删除或不交付; - 已生成并计费,取消不一定退款;
- 派生存储和审核任务仍需停止;
- 所有状态必须对用户透明。
不要把 UI 的"取消"写成"供应商已停止执行",除非有确认。
十、可观测性

每个 Job 至少监控:
- queue delay;
- provider latency;
- artifact storage latency;
- moderation latency;
- delivery latency;
- attempts/job;
- unknown-after-send rate;
- duplicate artifact rate;
- cost/requested image 与 cost/delivered image;
- fallback rate;
- policy rejection;
- deletion SLA。
"每张交付图片成本"比"API 单价"更接近业务现实。
风险与限制
网关能力目录会过期,供应商可能改变默认参数、ZDR 资格和审核政策。参数映射可能看似成功却产生质量退化。自建网关增加运维、合规和账单对账成本。统一抽象应保持可逃生:保存原始 provider metadata 和 adapter 版本,允许关键模型走原生路径。
本文没有运行真实图像请求,也没有验证某个 provider 对 abort、timeout、幂等键或查询任务状态的具体支持。UNKNOWN_AFTER_SEND 是保守的应用状态设计,不是对任一供应商内部实现的断言。
结论

图像网关的正确抽象不是一个万能 generate(),而是 Job、Attempt 和 Artifact 的分层状态机。共同语义可以统一,特殊能力必须显式;SDK 拆分、重试和 Gateway fallback 都要展开成 Attempt;timeout 后必须认识未知执行状态;成本按 Attempt 对账。做到这些,统一入口才不会把供应商差异变成隐形故障。
参考资料
- AI SDK Core: generateImage,Vercel AI SDK,持续更新;官方 API 参考,一手来源,访问日期:2026-08-31。
- AI SDK Core: Image Generation,Vercel AI SDK,持续更新;官方使用文档,一手来源,访问日期:2026-08-31。
- AI Gateway Image Generation Quickstart,Vercel,2026-03-12 更新;官方文档,一手来源,访问日期:2026-08-31。
- AI Gateway Model Fallbacks,Vercel,2026-01-30 更新;官方文档,一手来源,访问日期:2026-08-31。
- Model fallbacks now available,Vercel,2025-11-10;官方 Changelog,一手来源,访问日期:2026-08-31。
- AI Gateway Provider Timeouts,Vercel,2026-03-04 更新;官方文档,一手来源,访问日期:2026-08-31。
- Team-wide Zero Data Retention and prompt training controls,Vercel,2026-04-06;官方 Changelog,一手来源,访问日期:2026-08-31。
- Bring Your Own Key (BYOK),Vercel,2026-01-21 更新;官方文档,一手来源,访问日期:2026-08-31。