# 图片生成

创建图片，处理直接返回图片或异步任务两种响应，了解输入参数和计价限制。

## 同步与异步响应形状 [#同步与异步响应形状]

图片生成使用专用接口，不使用聊天 `messages`。响应有两种已支持的形状，不能无条件读取任务 `id`：同步响应直接在 `data[]` 中返回带 `url` 的图片，此时没有任务可轮询；异步响应返回任务 `id` 和 `status`（通常为 `PENDING`），应用才需要查询到 `SUCCEEDED` 后读取图片 URL。两种形状都没有可用图片 URL，且也没有异步任务的 `id` 与 `status` 时，应报错而不是猜测结果。

本文用 GFW 模型 ID `qwen-image-plus` 演示请求格式。使用前在 [模型库](/models) 确认它仍可用、通道接受所需尺寸。示例响应不是实际生成结果。

## 端点与鉴权 [#端点与鉴权]

| 操作 | 端点 |
| --- | --- |
| 创建任务 | `POST https://api.gfw.net/v1/images/generations` |
| 查询任务 | `GET https://api.gfw.net/v1/images/generations/{id}` |
| 鉴权 | 以上请求均使用 `Authorization: Bearer $GFW_API_KEY` |
| 获取图片 | 完成后读取 `data[].url`，向返回 URL 发起 GET |

虽然创建路径与 OpenAI 图片接口相似，GFW 可能返回立即可用的 `data[].url`，也可能返回异步任务。cURL/Python 示例都会先检查 `data[]` 中是否有图片 URL；有 URL 就直接输出，不发起轮询；否则才读取 `id`/`status` 并轮询。不要假设 SDK 的 `images.generate()` 永远只返回其中一种形状，初次接入建议按本页使用 HTTP 请求处理响应。

## 请求参数 [#请求参数]

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `model` | string | 是 | GFW 图片模型 ID |
| `prompt` | string | 是 | 非空生成提示 |
| `n` | integer | 否 | 请求数量，省略或非正值在入口按 1 处理；可生成数量仍受模型限制 |
| `size` | string | 否 | 模型接受的尺寸字符串，例如 `1024x1024`；可选尺寸不是跨模型统一集合 |
| `images` | array of strings | 否 | 参考图输入，仅在支持相应图生图能力的模型与通道上使用 |
| 其他字段 | 依模型而定 | 否 | 通用请求体可转发，但不保证 `quality`、`response_format` 等参数在每种通道有效 |

当前通用入口按 `size` 最大边长匹配图片价格档位。尺寸可能同时影响模型可用性与费用，不能只为降低费用随意填一个不被模型支持的值。图生图字符串应使用什么 URL 或编码形式，需确认该模型通道的规则；不要套用对话视觉输入的通用限制。

## 完整创建与轮询示例 [#完整创建与轮询示例]

设置 `GFW_API_KEY`。cURL 示例依赖本地 Python 解析返回的 JSON；Python 示例依赖 `requests` 包。两种示例都会先处理同步 `data[].url`，只有异步响应才提交一次轮询，轮询等待上限为 300 秒。



**cURL**


```bash
set -eu
: "${GFW_API_KEY:?请先设置 GFW_API_KEY}"

created=$(curl --fail-with-body --silent --show-error --max-time 60 \
  https://api.gfw.net/v1/images/generations \
  -H "Authorization: Bearer ${GFW_API_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{"model":"qwen-image-plus","prompt":"白色马克杯放在木桌上，柔和自然光，简洁构图。","n":1,"size":"1024x1024"}')

direct_urls=$(printf '%s' "$created" | python -c 'import json,sys; d=json.load(sys.stdin); data=d.get("data",[]) if isinstance(d,dict) else []; print("\n".join(item["url"] for item in data if isinstance(item,dict) and item.get("url")))')
if [ -n "$direct_urls" ]; then
  printf '%s\n' "$direct_urls"
  exit 0
fi

task_id=$(printf '%s' "$created" | python -c 'import json,sys; d=json.load(sys.stdin); print(d.get("id","") if isinstance(d,dict) else "")')
task_status=$(printf '%s' "$created" | python -c 'import json,sys; d=json.load(sys.stdin); print(d.get("status","") if isinstance(d,dict) else "")')
if [ -z "$task_id" ] || [ -z "$task_status" ]; then
  printf '响应既没有 data[].url，也没有异步任务 id/status\n' >&2
  exit 1
fi
printf '任务 ID：%s（状态：%s）\n' "$task_id" "$task_status"
started=$SECONDS
while (( SECONDS - started < 300 )); do
  result=$(curl --fail-with-body --silent --show-error --max-time 30 \
    "https://api.gfw.net/v1/images/generations/${task_id}" \
    -H "Authorization: Bearer ${GFW_API_KEY}")
  status=$(printf '%s' "$result" | python -c 'import json,sys; print(json.load(sys.stdin).get("status",""))')
  case "$status" in
    SUCCEEDED)
      printf '%s' "$result" | python -c 'import json,sys; d=json.load(sys.stdin); urls=[x["url"] for x in d.get("data",[]) if x.get("url")]; assert urls,"完成但未返回图片 URL"; print("\n".join(urls))'
      exit 0 ;;
    FAILED)
      printf '图片任务失败：%s\n' "$result" >&2
      exit 1 ;;
  esac
  sleep 3
done
printf '轮询超时，请保存任务 ID，稍后查询；不要重复提交。\n' >&2
exit 1
```



**Python**


```python
import os
import time
import requests

BASE = "https://api.gfw.net/v1"
headers = {"Authorization": f"Bearer {os.environ['GFW_API_KEY']}"}

with requests.Session() as session:
    created_response = session.post(
        f"{BASE}/images/generations",
        headers=headers,
        json={
            "model": "qwen-image-plus",
            "prompt": "白色马克杯放在木桌上，柔和自然光，简洁构图。",
            "n": 1,
            "size": "1024x1024",
        },
        timeout=60,
    )
    created_response.raise_for_status()
    created = created_response.json()
    data = created.get("data", []) if isinstance(created, dict) else []
    direct_urls = [item["url"] for item in data if isinstance(item, dict) and item.get("url")]
    if direct_urls:
        for url in direct_urls:
            print(url)
    else:
        task_id = created.get("id") if isinstance(created, dict) else None
        task_status = str(created.get("status", "")).upper() if isinstance(created, dict) else ""
        if not task_id or not task_status:
            raise RuntimeError("响应既没有 data[].url，也没有异步任务 id/status")
        print("task_id:", task_id, "status:", task_status)
        deadline = time.monotonic() + 300
        while True:
            remaining = deadline - time.monotonic()
            if remaining <= 0:
                raise TimeoutError(f"轮询超时，保留任务 ID 后续查询：{task_id}")
            response = session.get(
                f"{BASE}/images/generations/{task_id}",
                headers=headers,
                timeout=min(30, remaining),
            )
            response.raise_for_status()
            task = response.json()
            status = str(task.get("status", "")).upper()
            if status == "FAILED":
                raise RuntimeError(f"图片任务失败：{task_id}，详情：{task.get('error', '未提供')}")
            if status == "SUCCEEDED":
                urls = [item["url"] for item in task.get("data", []) if isinstance(item, dict) and item.get("url")]
                if not urls:
                    raise RuntimeError("任务完成但未返回图片 URL")
                # 使用完成响应中的 URL，不拼接媒体路径或使用创建响应猜测结果。
                for url in urls:
                    print(url)
                break
            time.sleep(min(3, max(0, deadline - time.monotonic())))
```




Shell 示例使用 Bash 的 `SECONDS` 与算术条件，请用 Bash 执行。300 秒是示例轮询预算，不是生成完成承诺；可根据应用总时限调整。HTTP 单次超时与整体截止时间是不同限制，失败或超时后不要自动重新创建任务。

## 任务响应字段 [#任务响应字段]

同步响应结构示意（直接返回图片，不含任务 `id`）：

```json
{
  "object": "image.generation",
  "data": [{"url": "https://api.gfw.net/media/images/example.png"}]
}
```

异步创建响应结构示意：

```json
{"id":"img_example","object":"image.generation","status":"PENDING"}
```

异步完成后的查询响应结构示意，URL 只是格式示例，不是实际文件：

```json
{
  "id": "img_example",
  "object": "image.generation",
  "model": "qwen-image-plus",
  "status": "SUCCEEDED",
  "data": [{"url": "https://api.gfw.net/media/images/example.png"}],
  "image_urls": ["https://example.invalid/upstream-image.png"]
}
```

| 字段 | 处理建议 |
| --- | --- |
| `id` | 仅异步响应提供；保存并用它查询现有任务，不要根据 ID 格式猜测后端通道 |
| `status` | 仅异步响应提供；`PENDING` / 运行中状态表示继续等待；`SUCCEEDED` 与 `FAILED` 为终态 |
| `data[].url` | 优先用于获取图片；可能是平台缓存 URL，也可能是上游 URL |
| `image_urls` | 补充结果字段，可能包含原始 URL 或路径，不优先于 `data[].url` |
| `data[].b64_json` | 某些兼容上游可能提供的 Base64 图片字段；只有模型契约明确返回时才解码，不能把它臆造为本入口的响应 |

通用后台处理器可将上游 Base64 结果保存为文件后返回 URL，因此不能要求响应一定有 `b64_json`。本入口已核对的可用图片形状是 `data[].url`；若实际响应只有未在模型契约中声明的 Base64 字段，应按不支持处理。异步任务失败未必提供详细错误文字，应用应至少保存任务 ID 与最后状态。

## 文件、计费与相关文档 [#文件计费与相关文档]

完成后及时将图片下载到你的受控存储，不能把任务 URL 当作永久资产地址。下载媒体 URL 时不要向未知上游域名附带 GFW API Key；对链接和下载大小进行必要限制。

图片按张数及模型尺寸档位等规则计价，不按聊天输出 Token 定价。创建成功只表示任务已受理，不表示图片已成功或账单已结算。费用以 [计费与充值](/docs/billing) 和 [用量与调用日志](/docs/usage-logs) 为准。

相关文档：[视频生成](/docs/video-generation)、[模型与能力](/docs/models)、[错误码与重试](/docs/errors)。
