316 lines
7.3 KiB
Markdown
316 lines
7.3 KiB
Markdown
# GPT Image 2 示例文档
|
||
|
||
本文档基于用户站 `https://apicodex.xyz` 的接入方式整理,包含:
|
||
|
||
- 文生图示例
|
||
- 带参考图示例
|
||
- 支持的比例列表
|
||
- 实测返回结构
|
||
- 同步返回与异步轮询的兼容写法
|
||
|
||
## 接口地址
|
||
|
||
```text
|
||
POST https://apicodex.xyz/v1/images/generations
|
||
```
|
||
|
||
## 鉴权
|
||
|
||
请求头使用 Bearer Token:
|
||
|
||
```python
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
"Content-Type": "application/json",
|
||
}
|
||
```
|
||
|
||
## 支持的比例
|
||
|
||
当前整理到的 `size` 比例共有 13 种:
|
||
|
||
- `1:1`
|
||
- `16:9`
|
||
- `9:16`
|
||
- `4:3`
|
||
- `3:4`
|
||
- `3:2`
|
||
- `2:3`
|
||
- `5:4`
|
||
- `4:5`
|
||
- `2:1`
|
||
- `1:2`
|
||
- `21:9`
|
||
- `9:21`
|
||
|
||
说明:
|
||
|
||
- 上述 13 种比例按你提供的接口说明整理
|
||
- 本文档里实际请求验证过的是 `16:9`
|
||
|
||
建议只通过 `size` 传比例,不要在 `prompt` 里重复写比例。
|
||
|
||
## 文生图示例
|
||
|
||
```python
|
||
import requests
|
||
|
||
url = "https://apicodex.xyz/v1/images/generations"
|
||
|
||
payload = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "一只橘猫坐在窗台上看夕阳,水彩画风格",
|
||
"n": 1,
|
||
"size": "16:9",
|
||
}
|
||
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
"Content-Type": "application/json",
|
||
}
|
||
|
||
response = requests.post(url, json=payload, headers=headers, timeout=300)
|
||
print(response.json())
|
||
```
|
||
|
||
## 带参考图示例
|
||
|
||
注意:不要把参考图放进 `/v1/images/generations` 的 `image_urls` 字段。当前实测该字段会被忽略,返回 usage 中 `input_tokens_details.image_tokens` 仍为 `0`。带图请求应走:
|
||
|
||
```text
|
||
POST https://apicodex.xyz/v1/images/edits
|
||
```
|
||
|
||
### 参考图为 URL
|
||
|
||
```python
|
||
import requests
|
||
|
||
image_url = "https://example.com/photo.jpg"
|
||
image_resp = requests.get(image_url, timeout=60)
|
||
image_resp.raise_for_status()
|
||
|
||
url = "https://apicodex.xyz/v1/images/edits"
|
||
data = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "把这张照片变成水彩画风格",
|
||
"size": "1:1",
|
||
}
|
||
files = [
|
||
("image[]", ("photo.jpg", image_resp.content, image_resp.headers.get("Content-Type") or "image/jpeg")),
|
||
]
|
||
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
}
|
||
|
||
response = requests.post(url, data=data, files=files, headers=headers, timeout=300)
|
||
print(response.json())
|
||
```
|
||
|
||
### 参考图为 base64
|
||
|
||
```python
|
||
import requests
|
||
import base64
|
||
|
||
url = "https://apicodex.xyz/v1/images/edits"
|
||
|
||
data = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "把这张照片变成水彩画风格",
|
||
"size": "1:1",
|
||
}
|
||
image_bytes = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAA...")
|
||
files = [
|
||
("image[]", ("reference.png", image_bytes, "image/png")),
|
||
]
|
||
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
}
|
||
|
||
response = requests.post(url, data=data, files=files, headers=headers, timeout=300)
|
||
print(response.json())
|
||
```
|
||
|
||
### 多张参考图
|
||
|
||
```python
|
||
import requests
|
||
|
||
url = "https://apicodex.xyz/v1/images/edits"
|
||
|
||
data = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "把这两张照片融合成一张海报",
|
||
"size": "4:3",
|
||
}
|
||
files = [
|
||
("image[]", ("photo-a.jpg", open("photo-a.jpg", "rb"), "image/jpeg")),
|
||
("image[]", ("photo-b.png", open("photo-b.png", "rb"), "image/png")),
|
||
]
|
||
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
}
|
||
|
||
try:
|
||
response = requests.post(url, data=data, files=files, headers=headers, timeout=300)
|
||
print(response.json())
|
||
finally:
|
||
for _, file_tuple in files:
|
||
file_tuple[1].close()
|
||
```
|
||
|
||
## 实测结果
|
||
|
||
本仓库在 `2026-04-22`(America/Los_Angeles)对以下请求做了真实测试:
|
||
|
||
```json
|
||
{
|
||
"model": "gpt-image-2",
|
||
"prompt": "一只橘猫坐在窗台上看夕阳,水彩画风格",
|
||
"n": 1,
|
||
"size": "16:9"
|
||
}
|
||
```
|
||
|
||
实测结论:
|
||
|
||
- 请求返回 `HTTP 200`
|
||
- `size: "16:9"` 被接口接受
|
||
- 当前实测返回是“同步结果”,不是 `task_id`
|
||
- 顶层字段为 `created` 和 `data`
|
||
- `data[0]` 中包含 `b64_json` 和 `revised_prompt`
|
||
- 本次实测的 `revised_prompt` 为 `黄昏里的橘猫窗台景`
|
||
- 本次解码后的样例图尺寸为 `1196x1315`,没有严格匹配 `16:9`
|
||
|
||
实测返回结构可概括为:
|
||
|
||
```json
|
||
{
|
||
"created": 1776919131,
|
||
"data": [
|
||
{
|
||
"b64_json": "<base64 image data>",
|
||
"revised_prompt": "黄昏里的橘猫窗台景"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
本次测试产物已保存到仓库临时目录:
|
||
|
||
- `tmp/imagegen/gpt-image-2-16-9-response.json`
|
||
- `tmp/imagegen/gpt-image-2-16-9-sample.png`
|
||
|
||
## 同步返回时的取图方式
|
||
|
||
如果接口直接返回 `b64_json`,可以这样保存图片:
|
||
|
||
```python
|
||
import base64
|
||
import requests
|
||
|
||
url = "https://apicodex.xyz/v1/images/generations"
|
||
|
||
payload = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "一只橘猫坐在窗台上看夕阳,水彩画风格",
|
||
"n": 1,
|
||
"size": "16:9",
|
||
}
|
||
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
"Content-Type": "application/json",
|
||
}
|
||
|
||
response = requests.post(url, json=payload, headers=headers, timeout=300)
|
||
data = response.json()
|
||
|
||
img_b64 = data["data"][0]["b64_json"]
|
||
with open("output.png", "wb") as f:
|
||
f.write(base64.b64decode(img_b64))
|
||
```
|
||
|
||
## 异步轮询写法
|
||
|
||
你提供的说明里写的是“提交后返回 `task_id`,再轮询 `/v1/tasks/{task_id}`”。如果后续该上游切回异步模式,可以用下面这种兼容写法:
|
||
|
||
```python
|
||
import base64
|
||
import time
|
||
import requests
|
||
|
||
url = "https://apicodex.xyz/v1/images/generations"
|
||
headers = {
|
||
"Authorization": "Bearer <token>",
|
||
"Content-Type": "application/json",
|
||
}
|
||
payload = {
|
||
"model": "gpt-image-2",
|
||
"prompt": "a corgi astronaut on the moon, cinematic, 8k",
|
||
"size": "16:9",
|
||
"n": 1,
|
||
}
|
||
|
||
resp = requests.post(url, json=payload, headers=headers, timeout=300)
|
||
resp.raise_for_status()
|
||
data = resp.json()
|
||
|
||
# 情况 1:直接同步返回 base64
|
||
if isinstance(data, dict) and data.get("data") and data["data"][0].get("b64_json"):
|
||
with open("output.png", "wb") as f:
|
||
f.write(base64.b64decode(data["data"][0]["b64_json"]))
|
||
print("saved output.png")
|
||
|
||
# 情况 2:返回 task_id,需要轮询
|
||
elif isinstance(data, dict) and data.get("data", {}).get("id"):
|
||
task_id = data["data"]["id"]
|
||
task_url = f"https://apicodex.xyz/v1/tasks/{task_id}"
|
||
|
||
time.sleep(10)
|
||
while True:
|
||
task_resp = requests.get(task_url, headers=headers, timeout=60)
|
||
task_resp.raise_for_status()
|
||
task = task_resp.json()
|
||
|
||
status = task["data"]["status"]
|
||
if status == "completed":
|
||
print(task["data"]["result"]["images"][0]["url"][0])
|
||
break
|
||
if status == "failed":
|
||
raise RuntimeError(task["data"]["error"]["message"])
|
||
|
||
time.sleep(5)
|
||
|
||
else:
|
||
raise RuntimeError(f"unexpected response: {data}")
|
||
```
|
||
|
||
## 如果是异步结果,取图字段
|
||
|
||
如果接口返回的是任务模式,则最终图片 URL 取值为:
|
||
|
||
```text
|
||
data.result.images[0].url[0]
|
||
```
|
||
|
||
任务状态说明:
|
||
|
||
- `pending`: 已提交或排队中
|
||
- `processing`: 上游处理中
|
||
- `completed`: 成功
|
||
- `failed`: 失败
|
||
|
||
## 注意事项
|
||
|
||
- 单张图接口耗时可能较长,建议客户端超时至少设到 `300s`
|
||
- 本次实测中,`size: "16:9"` 虽然被接口接受,但返回图片尺寸并未严格命中 `16:9`;如果你的业务依赖固定画幅,建议在下载后自行校验并按需裁切
|
||
- 如果使用异步模式,首次轮询建议在提交后等待 `10~20` 秒
|
||
- 轮询间隔建议 `3~5` 秒,不要毫秒级高频轮询
|
||
- 建议失败时做有限次重试,尤其是 TLS 或上游网络抖动场景
|
||
- 如果服务端返回的是 `b64_json`,说明当前是同步模式,不需要再调 `/v1/tasks/{task_id}`
|