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

图像生成为什么需要独立的 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 暴露为可审计对象。

核心结论

  1. 用户 Job、供应商 Attempt 和图片 Artifact 是三个不同实体,幂等键也应分层。
  2. Canonical request 只应包含跨模型语义稳定的字段,特殊能力放入 provider options。
  3. 路由前必须做 capability negotiation,不能把不支持参数静默丢弃。
  4. 自动重试必须区分"确定未执行""执行状态未知"和"已完成响应丢失"。
  5. 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 必须从官方模型文档确认。unknownfalse 要分开:未知不能被自动当成不支持或支持。Vercel 在 2026 年 4 月新增团队级与请求级 ZDR,可用 zeroDataRetention: true 让 Gateway 只选择符合要求的 provider,并在响应 metadata 中留下过滤轨迹;这意味着应用不必继续手写每个系统凭证 route 的 ZDR 表。

但 ZDR 仍不能被压成模型布尔值。团队/请求策略、Gateway 当前 provider 资格、BYOK 合同和应用自己的 Artifact 保留策略是不同层。特别是 BYOK 协议由用户持有,Gateway 无法自动替你证明其保留条款。Capability Registry 应记录可验证的策略输入与执行回执,而不是复制一个静态 supports_zdr=true

路由步骤:

  1. 校验请求;
  2. 根据 required capabilities 过滤;
  3. 根据 inference data policy、Gateway 执行回执、BYOK 合同、区域和供应商 allowlist 过滤;
  4. 估算成本与延迟;
  5. 按质量/成本/可用性排序;
  6. 生成参数映射计划;
  7. 返回 warnings;
  8. 创建 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 对账。做到这些,统一入口才不会把供应商差异变成隐形故障。

参考资料

相关推荐
V哥AI增长11 分钟前
六大AI引擎引用偏好差异的技术机制与实证分析
人工智能
Mr数据杨13 分钟前
自行车需求预测实战解析 从 Kaggle 回归赛题理解时序建模
人工智能·数据分析·kaggle竞赛
tachibana213 分钟前
微调和 RAG 各自的优劣势是什么?
人工智能·ai·大模型·llm·agent
天天代码码天天17 分钟前
一个HTML,打开就能OCR
人工智能
Jialu.24 分钟前
中文 NLP 模型部署实战:FastAPI 接口 + Streamlit 看板
人工智能·python·自然语言处理·fastapi
LPCK_2026062233 分钟前
从自动化到自主化:生物制药智能体的人机边界怎么设计
大数据·人工智能·自动化
smartpi_ai34 分钟前
JL-17T 能接传感器并把数据发到小程序吗?GPIO/ADC/UART 平台可做,I2C/SPI 要二开
人工智能·小程序·语音识别
hyunbar77734 分钟前
LangChain 实战:Agnets核心组件
人工智能
Mid_search36 分钟前
高估问题、Target Network、Double DQN
人工智能·深度学习·强化学习·double dqn·bootstrapping·target network