教程
如何通过 HiAPI 调用 Imagen 4 API:curl 与 Python 示例
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”这类词与比例参数一起发挥实际作用。
该端点的计费跟随图片生成本身——请查看网站上的价格面板了解当前费率,而不要把任何内容硬编码进你的成本模型。
处理异步任务与轮询
当生成耗时足够长而被排队时,端点在图片存在之前就会返回。你会拿回一个任务句柄,你的工作是检查它直到达到终态。即使今天的调用返回很快,也要写这套流程——容量、提示词复杂度和图片数量都会影响延迟,而为同步场景构建的客户端会在第一次遇到慢请求时崩溃。
生命周期分为三步:
- 提交。照常发送生成请求。如果响应是一个任务,提取其标识符。
- 轮询。以固定间隔用该标识符调用任务状态端点。
- 获取。一旦状态报告成功,就从结果载荷中读取图片引用。
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 客户端,并在其周围加入轮询和持久化。这条路径能让每次失败都保持范围小且可归因。