教程
如何通过 HiAPI API 调用 seedream-4.5/image-to-image:curl 与 Python
一次可运行的请求全程演示,涵盖多参考图输入、异步轮询和输出处理
此端点做什么以及何时选择它
seedream-4.5/image-to-image 会将一张或多张参考图加上文本提示词转化为一张新图像。它属于 HiAPI 上的图生图系列:你提供视觉上下文,模型根据你的指令重新诠释它。这正是它与文生图的关键区别——在文生图中,提示词是唯一输入,模型会凭空创造一切。
当你希望视觉结果仍锚定在已有素材上时,选择图生图:为现有资产重新设计风格、在保持产品形状和布局的同时改变光照或背景、在不丢失主体的前提下迭代构图,或者通过复用相同的参考图来保证一批结果的外观一致。
当你没有参考素材、只想用文字描述一个场景时,选择文生图。
此端点是异步的。你提交任务、收到一个标识符,然后轮询直到任务进入终止状态,再获取输出。请从一开始就为这个生命周期做好规划;本指南的其余部分会构建一个可运行的请求,然后将其加固。
构建一个可运行的 curl 请求
发往此端点的每个请求都具有相同的基本结构。请逐个字段处理。
端点与方法
请求以 POST 方式发往 HiAPI 基础 URL 下的图生图路由,使用模型标识符 seedream-4.5/image-to-image。模型名是请求负载中的一个值,而不是路径的一部分。
身份验证
将你的 HiAPI API 密钥作为 bearer token 放在 Authorization 头中传递。把它保存在环境变量里,这样它就不会进入 shell 历史记录或源代码管理:
export HIAPI_API_KEY="your-key-here"
最小请求负载
curl -sS -X POST "$HIAPI_BASE_URL/v1/images/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedream-4.5/image-to-image",
"prompt": "restyle this photo as a warm studio portrait",
"image": "https://example.com/reference.jpg"
}'
逐字段说明:
model— 字面标识符seedream-4.5/image-to-image。prompt— 要对参考图做什么。描述转换过程,而不仅仅是主体。image— 参考输入。对于单张图像,这是一个值;下一节将介绍多张参考图。
读取首次响应
该调用返回的是已接受的任务,而不是完成的图像。从响应体中捕获任务标识符并保留它;轮询时你会需要它。不要把 200 类状态当作“图像已就绪”。
在编写任何客户端代码之前,先原样运行这个请求一次。在 curl 中失败的请求负载在 Python 中也会因同样的原因失败,而在 shell 中调试更快。
将请求移植到 Python
Python 版本保持请求负载逐字节完全一致,只替换传输层和身份验证处理。这种分离正是关键所在:如果 curl 可以运行,Python 应该首次尝试就能运行。
import os
import requests
BASE_URL = os.environ["HIAPI_BASE_URL"]
API_KEY = os.environ["HIAPI_API_KEY"]
payload = {
"model": "seedream-4.5/image-to-image",
"prompt": "restyle this photo as a warm studio portrait",
"image": "https://example.com/reference.jpg",
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
resp = requests.post(
f"{BASE_URL}/v1/images/generations",
json=payload,
headers=headers,
timeout=30,
)
resp.raise_for_status()
job = resp.json()
print(job["id"])
以下说明可以节省后续时间:
- 将请求负载构建为 Python dict,让
json=负责序列化。手写 JSON 字符串正是引号错误的来源。 - 将身份验证构建保留在一个辅助函数中。如果头部格式发生变化,只需修改一处。
- 提交调用也始终传递显式的
timeout。提交步骤中的套接字挂起与模型缓慢无法区分。 - 立即存储返回的任务标识符,而不是从日志中重新推导。
后续章节中的轮询代码使用相同的 BASE_URL、API_KEY 和 headers,因此请将它们放在模块作用域或一个小型客户端类中。
传递多张参考图
多参考图输入是图生图请求最常出错的地方,通常是因为模型收到了三张图像,而提示词只描述了其中一张。
显式组织输入
将参考图作为一个列表放在同一个 image 字段下,按你期望的顺序排列,并让提示词指明每个位置的角色:
payload = {
"model": "seedream-4.5/image-to-image",
"prompt": (
"Use the first reference for the subject's pose, "
"the second for the background setting, "
"and the third for color grading. Keep the subject sharp."
),
"image": [
"https://example.com/pose.jpg",
"https://example.com/background.jpg",
"https://example.com/grading.jpg",
],
}
为什么标注优于位置
列表顺序是约定,但提示词才是告诉模型如何解读该顺序的关键。一个空泛的数组加上含糊的提示词会迫使模型在参考图冲突时猜测哪张优先。在提示词中指明每张图像的角色,可以将隐式的位置假设转化为显式指令。
实用规则:
- 保持参考图数量少且每张都有明确目的。多余的参考图会稀释而非丰富结果。
- 说明冲突解决方式:如果姿势参考图和背景参考图在取景上不一致,说明哪一张优先。
- 使用稳定、可公开访问的 URL,或等效的上传机制。在任务中途返回 404 的参考图会导致整个请求失败。
如果输出忽略了某张参考图,通常的解决方法不是重新调用模型,而是用更清晰的提示词句子指明该图像的角色。
处理异步任务生命周期
图像生成需要时间,因此该端点基于任务。将生命周期视为四个独立步骤,并逐一编写代码。
1. 提交
发送前面展示的 POST。在做任何其他事情之前,先持久化响应中的任务标识符。
2. 轮询
使用相同的身份验证头,向任务路由发出 GET 请求,直到状态进入终止状态:
import time
def poll_job(job_id, interval=2.0, max_interval=30.0, deadline=300.0):
start = time.monotonic()
while True:
r = requests.get(
f"{BASE_URL}/v1/images/generations/{job_id}",
headers=headers,
timeout=30,
)
r.raise_for_status()
data = r.json()
status = data["status"]
if status in ("succeeded", "failed"):
return data
if time.monotonic() - start > deadline:
raise TimeoutError(f"job {job_id} not finished within {deadline}s")
time.sleep(interval)
interval = min(interval * 2, max_interval)
3. 检测终止状态
只有两种结果会结束循环:成功或失败。其他任何情况都意味着继续等待。永远不要假设输出是即时的,也永远不要在没有实际时间截止点的情况下固定轮询次数。
4. 获取结果
在下一节中处理。
退避很重要。紧凑的 sleep(0.1) 循环会将你的速率预算花在状态检查上。从大约两秒开始并翻倍直到上限,可以在不淹没 API 的情况下保持低延迟。
检索并存储输出
当轮询返回成功状态时,响应会携带输出引用。返回的是指向生成图像的指针,不一定是字节本身。
有意地下载
data = poll_job(job_id)
url = data["output"][0]["url"] if isinstance(data["output"], list) else data["output"]
with requests.get(url, stream=True, timeout=60) as r:
r.raise_for_status()
with open(f"{job_id}.png", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
根据你从第一个真实任务中观察到的实际响应结构来调整路径,而不是假设固定的模式。
防止返工的存储规则
- 按任务标识符命名文件。它唯一、可追溯,并将产物与产生它的请求关联起来。
- 及时下载。输出链接可能有时间限制,因此在成功轮询后立即持久化,而不是缓存 URL 以备后用。
- 流式处理大文件,而不是将它们完整读入内存。
- 将提示词和参考图列表与文件一起记录。当输出出乎意料时,输入就是调试记录。
如果你需要确定性重试,存储任务标识符意味着你可以重新轮询或重新下载,而无需为第二次生成付费。
错误、重试和成本控制
大多数失败属于三类之一,每类需要不同的处理方式。
客户端错误
格式错误的请求负载、缺少 model、无法访问的参考图 URL 或错误的密钥。这些错误会立即返回,并且重试时仍会返回。修复请求;不要循环重试。在提交前验证参考图 URL 可以解析。
临时服务器和网络错误
超时、连接重置、速率限制响应。这些值得使用指数退避和抖动进行重试,并以较小的最大尝试次数为上限。重试特定步骤——提交、轮询或下载——而不是整个流水线。在成功提交后重新提交正是意外重复付费的方式。
任务级失败
任务进入失败终止状态。重试相同的任务标识符不会有帮助。明确决定是否用修改后的提示词或参考图集重新提交,并记录失败原因,以便区分输入错误和临时故障。
def with_retries(fn, attempts=4, base=1.0):
for i in range(attempts):
try:
return fn()
except requests.RequestException:
if i == attempts - 1:
raise
time.sleep(base * (2 ** i))
成本控制
计费与生成绑定,因此代价高昂的错误是重复提交。对轮询强制执行实际时间截止点,通过在轮询前存储任务标识符来保持提交幂等,并限制并发,以免重试风暴膨胀成数十个计费任务。记录每次提交及其任务标识符;该日志是你的支出审计轨迹。
将这三个关注点一次性接入客户端,同一份代码即可处理单参考图和多参考图请求,无需更改。