图片生成 API 集成:OpenAI 兼容模式
通过 OpenAI 兼容端点集成图片生成与编辑,安全限制 n 和尺寸,并用 gpt-image-2 正确持久化临时媒体。
图片生成 API 集成,是指产品代码调用稳定的图片端点——通常是 POST /v1/images/generations,并经常包括 POST /v1/images/edits——同时把模型选择、凭证、配额与媒体存储从各个客户端中抽离。在 OpenAI 兼容网关中,应用发送 prompt,以及可选的 model、n、size、quality 和 GPT Image 字段(如 output_format),然后解码 base64 载荷;只有仍支持旧格式的模型才处理 URL 响应。本文基于本仓库已经暴露的网关路由,给出接近生产形态的集成路径,不虚构供应商价格或质量评分。
GPT Image 的最小正确请求形状
使用一个经过认证的 base URL、一把由网关签发的 Bearer token,以及当前 OpenAI GPT Image 契约。官方文档将 gpt-image-2 列为当前主力图片模型;GPT Image 生成返回 data[].b64_json。response_format 参数仅适用于旧版 DALL·E 模型,不适用于 GPT Image。
curl -sS https://YOUR_GATEWAY/v1/images/generations \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "A product photo of a ceramic mug on a wooden table, soft window light",
"n": 1,
"size": "1024x1024",
"output_format": "png"
}' | jq -r '.data[0].b64_json' | base64 --decode > mug.png
应把响应视为临时媒体加计费元数据,而不是持久产品资产。如果产品需要在供应商临时 URL 过期后继续使用图片——适用于旧模型或非 OpenAI 适配器——应立即下载到自有对象存储,或者使用按用户所有权授权的个人图片工作台持久化路径。
始终在当前 OpenAI 图片生成指南 与 Images API 参考 中重新检查模型专属字段。Google 在 ai.google.dev 单独记录 Gemini 原生图片生成。网关不会取代这些文档,它只统一访问方式。
为什么通过网关集成,而不是维护多个 SDK
产品团队在图片集成上失败,通常是运维问题,而不是模型质量问题:
- 每个服务保存不同的供应商密钥
- prompt 实验绕过预算控制
- 把临时 CDN URL 存进数据库,随后链接失效
- 图片
n或分辨率在未校验时放大成本 - 个人沙箱与团队项目共用一把密钥,导致费用归属失真
多供应商 LLM 网关 与 OpenAI 兼容 API 网关 中介绍的模式,可以保持客户端稳定,同时让运维人员把稳定别名映射到通道、实施配额并写入使用日志。对于媒体,同一控制面还必须回答:“之后谁可以下载这项资产?”
关键端点与请求字段
生成
POST /v1/images/generations 是文生图入口。本仓库图片请求 DTO 中常见的字段包括:
| 字段 | 在 GPT Image / 网关中的作用 |
|---|---|
model | 优先使用稳定别名 gpt-image-2(或你的映射别名);旧 gpt-image-1* ID 已进入 OpenAI 向 gpt-image-2 迁移的弃用路径 |
prompt | 必填文本指令 |
n | 可选数量;指针类型保留 JSON 是否显式出现,但本网关会在计费前把 n=0(或缺省)归一化为 1 |
size | 如 1024x1024、1536x1024、1024x1536 的分辨率字符串;GPT Image 2 还记录了更广泛的 WIDTHxHEIGHT 规则 |
quality | 模型支持时使用的供应商专属质量 token |
output_format | GPT Image 系列支持的 png / jpeg / webp |
output_compression | 对支持压缩的格式设置压缩参数 |
response_format | 仅旧版 DALL·E 支持(url 或 b64_json);GPT Image 不支持,并始终返回 base64 |
background、moderation、stream | 适配器支持时可用的模型系列扩展 |
user | 用于滥用追踪的最终用户标签 |
不要假设每个字段在每个模型上都有效。应根据环境实际启用的模型和供应商当前文档进行验证。
编辑
POST /v1/images/edits 是编辑/inpaint 表面。集成通常通过 multipart form 发送源图片、可选 mask 和 prompt。产品代码应:
- 把用户上传接收到有大小上限的临时存储
- 调用网关前统一 MIME type
- 与生成请求保持相同鉴权和计费所有者
- 绝不在应用日志中记录原始图片字节
如果产品只需要“用更严格的 prompt 重新生成”,只接生成端点可能就够了。只有用户必须保留构图或品牌资产时再加入编辑能力。
计费安全属于集成的一部分
图片生成是乘数密集型 API。恶意或有 bug 的客户端若把 n 设置成荒谬数值,一次 HTTP 请求就可能演变为记账事故。在本代码库中:
- 请求校验会在配额计算前拒绝超过
MaxImageN(128)的n n缺失或为0时归一化为1(“保留指针是否出现”不等于“执行零张图片”)- 预扣与结算路径仍必须拒绝负数或饱和后的费用
- 质量/尺寸比例只有在校验后才能成为乘数
产品规则:
- UI 中把
n默认设为1 - 不要在没有小于平台上限的产品级 clamp 时接受用户输入的
n - 多变体生成前显示预估成本
- 如果预扣失败,UX 应直接失败;不要“乐观排队”并从另一个钱包扣费
TypeScript:生成、解码并持久化
type ImageGenResponse = {
data: Array<{ b64_json?: string; url?: string; revised_prompt?: string }>;
};
async function generatePng(prompt: string, model = "gpt-image-2") {
const res = await fetch(`${process.env.GATEWAY_BASE}/v1/images/generations`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GATEWAY_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
prompt,
n: 1,
size: "1024x1024",
output_format: "png",
}),
});
if (!res.ok) throw new Error(await res.text());
const body = (await res.json()) as ImageGenResponse;
const b64 = body.data?.[0]?.b64_json;
if (!b64) {
// Legacy/provider-adapter path only: some non-GPT models may still return url.
if (body.data?.[0]?.url) {
return { kind: "url" as const, url: body.data[0].url, revised: body.data[0].revised_prompt };
}
throw new Error("no image payload");
}
const bytes = Buffer.from(b64, "base64");
// upload bytes to object storage under the billing owner's key prefix
return { kind: "bytes" as const, bytes, revised: body.data[0].revised_prompt };
}
Python:使用同一契约并显式解码
import base64
import os
from pathlib import Path
import requests
def generate_and_store(prompt: str, model: str = "gpt-image-2") -> Path:
r = requests.post(
f"{os.environ['GATEWAY_BASE']}/v1/images/generations",
headers={"Authorization": f"Bearer {os.environ['GATEWAY_KEY']}"},
json={
"model": model,
"prompt": prompt,
"n": 1,
"size": "1024x1024",
"output_format": "webp",
"output_compression": 80,
},
timeout=120,
)
r.raise_for_status()
b64 = r.json()["data"][0]["b64_json"]
out = Path("/tmp/out.webp")
out.write_bytes(base64.b64decode(b64))
return out
当你控制存储并希望只请求一次时,优先使用 b64_json,这是 GPT Image 默认格式。只有在描述旧模型或供应商适配器行为时才提到 url。绝不要假设 URL 永久有效。
个人图片工作台与团队视频工作台
本仓库提供的图片工作台属于个人 / session / token 作用域:
- UI:图片工作台
/api/user/image-workbench/*下的用户 API,包括预设、模板、运行、资产、生成与编辑/api/image-workbench/*下用于生成/编辑/下载的 API-key 变体/api/image-workbench/admin/*下的管理员模板工具- 对象键和历史记录使用用户所有权
当前代码树中不存在团队作用域的 /api/teams/:team_id/image-workbench 路由。除非你的 fork 已添加,否则不要宣称支持团队图片历史或项目作用域图片资产。
相比之下,视频工作台 确实包含 /api/teams/:team_id/video-workbench 下的团队/项目路由。如果产品经理要求“像视频一样共享图片历史”,那是一个功能缺口,不是当前图片工作台已经具备的能力。原始 /v1/images/* 的多租户计费仍跟随令牌的计费所有者;参见 多租户 LLM 网关架构。
当运维人员或设计师需要交互式历史和预设时使用工作台;当后端服务以编程方式生成资产时使用原始 /v1/images/*。两条路径应共用相同的模型别名与使用日志字段,便于后续归因。
媒体生命周期清单
- 提交:使用有作用域的网关密钥,而不是供应商密钥。
- 校验:发送前校验
n(记住这里的0会变成1)、尺寸和 prompt 长度。 - 记录:保存 request ID / usage log ID,供支持排查。
- 解码:解码
b64_json(或只获取一次旧版 URL),并按留存策略持久化对象存储键。 - 授权:后续下载按所有者鉴权;图片工作台资产归用户,你自行存储的 API 任务归计费所有者。
- 过期或重审:产品策略要求时让资产过期或重新审核。
- 观测:通过监控栈按模型追踪失败率与成本。
如果跳过第 4 步,产品 bug 会在数周后表现为缩略图损坏,而不是 API 错误。
Prompt 与产品工程注意事项
- 在应用层分离系统意图(品牌规则)与用户 prompt;Images API 通常只有一个
prompt字符串,所以需要谨慎组合。 - 保存最终发送的 prompt,而不仅是 UI 草稿,因为供应商可能返回
revised_prompt。 - 限制每个用户的并发图片任务;重试很容易意外 fan-out 图片端点。
- 对 HTTP 429/5xx 使用有界重试,并在技术栈支持时使用幂等客户端键(参见 LLM API 重试与幂等)。
- 多模型实验在确认尺寸/质量支持后只修改
model别名,不要 fork 整个客户端。 - 默认示例优先使用
gpt-image-2;如果通道仍暴露旧别名,应显式映射。
常见集成失败
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
| “成功”后没有图片 | 在 GPT Image 响应中查找 url | 读取并解码 b64_json |
| 配额突然大量消耗 | n 无上限或重试风暴 | Clamp n,对重试去重 |
期望 n=0 表示“试运行” | 网关把 0 归一化为 1 | 不要发送零,直接不调用 |
| 个人账户可用,团队密钥失败 | 密钥缺少项目授权或模型分组错误 | 修复团队令牌授权;不要假设图片工作台是团队作用域 |
| 编辑端点返回 400 | multipart 字段名错误或 mask 尺寸错误 | 对齐供应商/网关 form 契约 |
| 找不到模型 | 通道未映射别名 | 在通道配置中将稳定别名映射到上游 ID |
相关阅读
来源
- OpenAI 图片生成指南
- OpenAI Images API 参考
- OpenAI 弃用公告(旧 GPT Image →
gpt-image-2) - Google Gemini 图片生成
- OWASP Top 10 for LLM Applications
失败问题解答
我应该用三个 SDK 分别调用 OpenAI、Gemini 和其他供应商吗?
如果产品只需要一种客户端契约,就不应这样做。生成和编辑优先走 OpenAI 兼容网关路径;只有网关尚未映射的功能才保留原生 SDK。
response_format=url 适合写入生产数据库吗?
它不是 GPT Image 的正确默认值,因为 GPT Image 不支持该参数并始终返回 base64。对于仍返回 URL 的旧模型,只把 URL 当作临时指针,并在用于长期产品页面前重新托管。
n 的安全默认值是多少?
生产 UX 从 1 开始。只有产品明确需要多个变体时才提高,并把产品硬上限设在平台上限之下(本代码库为 128)。不要发送 n=0 并期待 no-op。
图片请求需要 streaming 吗?
通常不需要。只有适配器和 UI 确实能从局部预览中获益时才启用 streaming;否则保持简单的 request/response 路径。
团队如何在不泄漏资产的情况下共享图片历史?
当前网关的图片工作台历史属于个人。共享图库需要在你自行保存的字节之上构建应用层分享;也可以仅用团队项目密钥为 /v1/images/* 计费,同时对持久化对象实现自己的 ACL。
首次生产接线
把一个后端任务接到 POST /v1/images/generations,使用 model=gpt-image-2、n=1、output_format=png,完成 base64 解码并立即上传对象存储。确认使用日志显示预期计费所有者;只有在通道配置中验证尺寸/质量兼容性后,再加入第二个模型别名。