HiAPI
博客

教程

如何通过 HiAPI 调用 Imagen 4 API:curl 与 Python 示例

2026年10月31日
博客封面图:淡灰紫色块与墨黑色块以一条笔直硬边相接,交界处一道细紫缝,构图简洁平静。

Imagen 4 端点能为你提供什么

如果你已经有一个提示词,并且想在不注册计费账户、不搭建队列、也不在你从未用过的服务商那里创建存储桶的情况下拿到一张图片,那么 HiAPI 上的 Google-Imagen4 模型是一个合理的起点。你只需向单个 HTTP 端点发送一个文本提示词,用一个 API 密钥完成认证,然后就能拿到生成图片的引用(或一个可轮询的任务)。其余一切——模型托管、容量、服务商侧的重试——都被抽象掉了。

通过这个端点进行文生图调用,涵盖了你实际使用中会关心的三件事:

  • 从提示词到图片。提示词是主要输入。根据你配置请求的方式,模型会返回一张或多张图片。
  • 输出形状控制。你可以在请求时指定宽高比(方形、横向、纵向),而不是事后再裁剪。这比听起来更重要:裁剪生成好的图片会丢掉模型原本已经规划好的构图。
  • 异步执行。较长的生成会以任务形式处理。你提交、轮询状态,然后获取结果。短调用可能很快返回,但你应该把客户端写成每次调用都是异步的,因为这才是能扛住生产流量的流程。

本教程的其余部分会用三种方式构建同一个请求:一段可以直接粘贴到终端的原始 curl 调用、一个等价的 Python 客户端,然后是你在处理宽高比、轮询、持久化和错误处理时所需要的各个部分。如果你想用一行 shell 命令生成图片,这很适合。如果你需要对 inpainting 掩码或训练循环做细粒度控制,那它可能并不适合。

开始前有两个前提:你拥有 HiAPI API 密钥,并且你的代码运行环境可以发出站 HTTPS 请求。除此之外没有其他要求——SDK 安装并非强制,不过下面的 Python 示例会用到 requests。

用 curl 发起你的第一个请求

确认密钥可用最快的方式就是一次 curl 调用。该端点接收 JSON 请求体,因此你需要两个请求头:Content-Type 用于请求体,Authorization 用于密钥。请从环境变量读取密钥,而不要直接粘贴到命令里,这样它就不会留在你的 shell 历史记录中。

export HIAPI_API_KEY="your-key-here"

curl -sS -X POST "https://api.hiapi.ai/v1/images/generations" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -d '{
    "model": "google-imagen-4",
    "prompt": "a weathered lighthouse on a basalt cliff, morning fog, wide shot",
    "n": 1,
    "aspect_ratio": "16:9"
  }'

这个请求体里有几点值得注意:

  • model 选择 Google-Imagen4。即使服务有默认值,也请显式写上——这能让你一年后的代码仍然可读。
  • prompt 是唯一必需的创意输入。请像向摄影师描述镜头那样写它:主体、环境、光线、取景。
  • n 控制你要几张图片。在把响应结构调对的阶段,先从一张开始;等信任你的流水线后再提高。
  • aspect_ratio 会在后文详细说明。省略它会回退到服务默认值。

成功响应会返回一个 JSON 对象。你关心的部分是图片条目列表;每个条目都带有一个可下载的引用,或一个可以交给前端的 URL。如果你请求了多张图片,就会收到多个条目,顺序与请求一致。如果生成时间足够长而被排队,响应则会给你一个标识符和一个可轮询的状态——该流程将在下面的专门章节中描述。

如果收到 4xx,先检查两个请求头:缺少 Bearer 前缀,或密钥末尾带空白字符,是大多数首次调用失败的原因。调试时用 -i 查看状态行和错误响应体。

用 Python 发起同样的请求

在脚本里,把调用包装在一个小函数中,这样其余代码就永远不必接触原始 HTTP。该函数应接收提示词和可选参数,构建请求体,发送请求,并返回解析后的 JSON。

import os
import requests

HIAPI_BASE = "https://api.hiapi.ai/v1"


def generate_image(prompt: str, aspect_ratio: str = "1:1", n: int = 1) -> dict:
    response = requests.post(
        f"{HIAPI_BASE}/images/generations",
        headers={
            "Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "model": "google-imagen-4",
            "prompt": prompt,
            "n": n,
            "aspect_ratio": aspect_ratio,
        },
        timeout=120,
    )
    response.raise_for_status()
    return response.json()


if __name__ == "__main__":
    result = generate_image(
        "a weathered lighthouse on a basalt cliff, morning fog, wide shot",
        aspect_ratio="16:9",
    )
    print(result)

当你改编这段代码时,其中有三个设计选择值得保留:

显式超时。requests 没有默认超时,这意味着连接卡住时可能让 worker 无限期挂起。选择一个高于你最慢预期生成时间的数值,并让它显式失败。

在解析之前调用 raise_for_status()。错误响应并不总是与成功响应共享同一 schema。先检查 HTTP 状态,再碰响应体。

小而参数化的函数。当你加入轮询和下载后,这个函数仍然是提交的唯一入口,生命周期逻辑与它相邻,而不是纠缠在它内部。

解析后的结果包含与 curl 输出相同的信息。对于同步响应,遍历图片条目并取出 URL。对于异步响应,取出任务标识符并传给轮询函数。你的代码应根据收到的形状分支处理,而不是假定只有一种。

控制宽高比与输出

宽高比是你最常调整的参数,值得正确设置,而不是事后裁剪。如果你请求一张方形图片,然后从中裁出一条 16:9 的区域,你就会丢掉模型刻意构图的顶部和底部。提前请求 16:9 能让模型为那个画幅规划镜头。

在请求体中与提示词一起传入:

result = generate_image(
    "a sprawling night market, neon signs, steam from food stalls",
    aspect_ratio="16:9",
    n=2,
)

横向比例用于 hero 横幅、宽卡片的缩略图以及产品摄影;纵向比例用于移动优先卡片、海报和角色照;方形比例用于头像和裁剪不可预测的社交帖子。要根据图片最终放置的位置来决定,而不是看它在孤立状态下是否好看。

改变比例时,响应中变化的是:返回图片的像素尺寸。条目数量、顺序和下载流程保持不变,因此你的下游代码不需要根据比例分支。如果你的 UI 在图片加载前预留空间,就从响应中读取尺寸并设置到元素上,而不是硬编码尺寸。

另外两个输出调节项值得了解:

  • 图片数量(n)。每次请求更多图片意味着更多选择,但也意味着在可能写错的提示词上花费更多 token。在迭代提示词措辞时,只请求一张图片;等提示词稳定后再批量请求。
  • 用于取景的提示词措辞。比例决定画幅;提示词决定画幅内的构图。“wide shot”“close-up”和“centered”这类词与比例参数一起发挥实际作用。

该端点的计费跟随图片生成本身——请查看网站上的价格面板了解当前费率,而不要把任何内容硬编码进你的成本模型。

处理异步任务与轮询

当生成耗时足够长而被排队时,端点在图片存在之前就会返回。你会拿回一个任务句柄,你的工作是检查它直到达到终态。即使今天的调用返回很快,也要写这套流程——容量、提示词复杂度和图片数量都会影响延迟,而为同步场景构建的客户端会在第一次遇到慢请求时崩溃。

生命周期分为三步:

  1. 提交。照常发送生成请求。如果响应是一个任务,提取其标识符。
  2. 轮询。以固定间隔用该标识符调用任务状态端点。
  3. 获取。一旦状态报告成功,就从结果载荷中读取图片引用。
import time


def wait_for_job(job_id: str, interval: float = 2.0, max_attempts: int = 60) -> dict:
    for attempt in range(max_attempts):
        response = requests.get(
            f"{HIAPI_BASE}/images/jobs/{job_id}",
            headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
            timeout=30,
        )
        response.raise_for_status()
        payload = response.json()

        status = payload.get("status")
        if status == "succeeded":
            return payload
        if status in {"failed", "cancelled"}:
            raise RuntimeError(f"job {job_id} ended with status {status}")

        time.sleep(interval)

    raise TimeoutError(f"job {job_id} did not finish in time")

让这段代码在生产环境中安全的关键细节:

  • 限制尝试次数,而不是无限循环。永远不会完成的任务应该表现为可告警的超时错误,而不是永远挂起的 worker。max_attempts 乘以 interval 是你的实际耗时预算——根据你最慢的现实生成时间来设定它。
  • 负载下的退避。固定的两秒间隔对交互式使用没问题。对于批处理工作负载,应逐步退避或加入抖动,这样许多 worker 的重试就不会在同一秒同步。
  • 区分失败与未知。只有 failed 和 cancelled 是终态失败。把任何其他非成功状态视为“仍在处理”并再次轮询;为无法识别的状态添加一个立即抛出的分支,会在服务新增状态时出问题。
  • 网络错误时重试提交,而不是重试轮询。如果提交调用本身超时,重试可能产生重复任务。如果 API 支持幂等键,就给每次提交一个幂等键;否则在你这边按提示词哈希加时间戳去重。

一旦任务报告成功,就停止轮询。持久化结果并保留任务句柄用于审计,比继续询问你已经知道的状态更便宜。

下载与存储图片

生成响应给你的是引用,而不是图片字节。大多数情况下,每个图片条目都带有一个可以用普通 GET 请求获取的 URL。要把该 URL 视为临时地址:及时下载、校验它,并把字节存到你真正控制的地方。

import pathlib
import uuid


def download_images(payload: dict, out_dir: str = "images") -> list[pathlib.Path]:
    target = pathlib.Path(out_dir)
    target.mkdir(parents=True, exist_ok=True)

    saved = []
    for entry in payload.get("data", []):
        url = entry["url"]
        resp = requests.get(url, timeout=60)
        resp.raise_for_status()

        content_type = resp.headers.get("Content-Type", "")
        if not content_type.startswith("image/"):
            raise ValueError(f"unexpected content type: {content_type}")

        path = target / f"{uuid.uuid4().hex}.png"
        path.write_bytes(resp.content)
        saved.append(path)

    return saved

在把任务视为完成之前,做几项检查:

  • 内容类型与大小。在某些环境中,重定向到错误页面会返回 200 和 HTML。在写入永久存储之前,验证 Content-Type 以 image/ 开头,并且响应体大小不是微不足道的。
  • 命名。每次下载都生成新名称,而不是从提示词派生。提示词会重复、冲突,并包含文件系统拒绝的字符。
  • 把提示词与文件一起存储。把提示词、参数和任务标识符保存到你自己的数据库或旁车元数据文件中。六个月后,仅凭图片无法告诉你哪个请求产生了它。
  • 移动到永久存储,而不是复制。先写入临时位置,验证后再移动到你的存储桶或 CDN 支持的存储。永久存储中一个写了一半的文件比一个失败的任务更糟。

如果你的流水线会产生许多图片,把下载步骤与生成步骤分开。生成是昂贵且受速率限制的部分;下载重试成本低,且绝不应导致你重新生成。

常见错误与调试清单

大多数第一天的失败都落在少数几类中。按以下顺序逐项检查——每项检查都比后一项更快。

认证失败(401、403)

密钥错误、缺失或格式不正确。检查请求头值是否恰好是 Bearer 加上密钥,没有因粘贴换行而产生的换行符,并确认环境变量确实在运行代码的 shell 或进程中导出。在仪表板中轮换的密钥会立即使旧密钥失效——如果另一个服务刚刚开始失败,问问是否有人轮换了密钥。

参数被拒(400、422)

请求体与 schema 不匹配。常见原因:model 标识符拼写错误、aspect_ratio 字符串格式不被端点接受,或 n 超出支持范围。打印错误响应体——它通常会指出有问题的字段。将你的 JSON 与可用的 curl 示例对比,逐字段做 diff。

超时

两种不同的超时容易混淆。客户端超时意味着你的 HTTP 调用放弃了;任务可能仍在运行。任务永远达不到终态意味着轮询预算太小,或请求确实失败了。把客户端超时设得高于你最慢的预期生成时间,再把轮询预算设得更高。当提交期间客户端超时触发时,盲目重试可能重复工作——重新提交前检查是否已有任务。

看起来成功但实际没有的下载

200 状态却带 HTML 响应体,或零字节文件,通常意味着图片 URL 已过期,或获取时没有正确的访问权限。任务成功后立即下载,并如上所述验证内容类型和大小。

速率限制(429)

退避并重试,而不是收紧循环。带抖动的指数退避是标准答案;限制重试次数,并把持续存在的失败暴露给你的监控。

一个值得养成的调试习惯

为每个请求记录四样东西:模型标识符、去掉密钥的完整请求体、任务标识符(同步调用则为响应状态),以及耗时。当生产环境出问题时,这四个字段通常能在不到一分钟内判断问题出在你的客户端、你的参数,还是队列。

从 curl 调用开始,先拿回一张图片,然后把逻辑移入 Python 客户端,并在其周围加入轮询和持久化。这条路径能让每次失败都保持范围小且可归因。

立即开始构建持久 AI 产物

获取 API 密钥,通过一个基于任务(task)的 API 调用图像、视频和音频模型。

阅读 API 文档