튜토리얼
HiAPI API로 seedream-4.5/image-to-image를 호출하는 방법: curl과 Python
실행 가능한 요청으로 전체 과정을 시연하며, 다중 참조 이미지 입력, 비동기 폴링, 출력 처리를 다룹니다
이 엔드포인트가 하는 일과 선택 시점
seedream-4.5/image-to-image는 하나 이상의 참조 이미지와 텍스트 프롬프트를 결합하여 새로운 이미지를 생성합니다. 이는 HiAPI의 이미지 투 이미지 계열에 속합니다. 즉, 시각적 컨텍스트를 제공하면 모델이 지시에 따라 이를 재해석합니다. 이것이 텍스트 투 이미지와의 핵심 차이입니다. 텍스트 투 이미지에서는 프롬프트가 유일한 입력이며 모델이 모든 것을 처음부터 창조합니다.
시각적 결과가 기존 소재에 닻을 내려야 할 때 이미지 투 이미지를 선택하세요. 기존 자산의 스타일을 재설계하거나, 제품 형태와 레이아웃을 유지하면서 조명이나 배경을 바꾸거나, 주제를 잃지 않고 구도를 반복 개선하거나, 동일한 참조 이미지를 재사용하여 일련의 결과물 외관을 일관되게 유지하는 경우입니다.
참조 소재가 없고 단지 텍스트로 장면을 설명하고 싶다면 텍스트 투 이미지를 선택하세요.
이 엔드포인트는 비동기입니다. 작업을 제출하고 식별자를 받은 다음, 작업이 종료 상태에 도달할 때까지 폴링한 후 출력을 가져옵니다. 처음부터 이 생명주기를 염두에 두고 설계하세요. 이 가이드의 나머지 부분에서는 실행 가능한 요청을 구성한 다음 이를 견고하게 만듭니다.
실행 가능한 curl 요청 구성
이 엔드포인트로 보내는 모든 요청은 동일한 기본 구조를 가집니다. 필드별로 하나씩 살펴보겠습니다.
엔드포인트와 메서드
요청은 HiAPI 기본 URL 아래의 이미지 투 이미지 라우트로 POST 방식으로 전송되며, 모델 식별자 seedream-4.5/image-to-image를 사용합니다. 모델 이름은 경로의 일부가 아니라 요청 페이로드 내의 값입니다.
인증
HiAPI API 키를 bearer 토큰으로 Authorization 헤더에 담아 전달하세요. 셸 기록이나 소스 관리에 노출되지 않도록 환경 변수에 보관하세요:
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에서도 같은 이유로 실패하며, 셸에서 디버깅하는 것이 더 빠릅니다.
요청을 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) 루프는 속도 예산을 상태 확인에 낭비합니다. 약 2초에서 시작하여 상한까지 두 배씩 늘리면 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))
비용 제어
과금은 생성에 연동되므로 비용이 큰 실수는 중복 제출입니다. 폴링에 실제 시간 데드라인을 적용하고, 폴링 전에 작업 식별자를 저장하여 제출을 멱등하게 유지하며, 동시성을 제한하여 재시도 폭풍이 수십 개의 과금 작업으로 불어나지 않게 하세요. 모든 제출과 해당 작업 식별자를 기록하세요. 그 로그가 지출 감사 추적입니다.
이 세 가지 관심사를 클라이언트에 한 번만 연결하면 동일한 코드가 변경 없이 단일 참조 이미지와 다중 참조 이미지 요청을 모두 처리합니다.
관련 글
모든 아티클 보기 →-
튜토리얼
HiAPI API로 hailuo-2.3-fast/image-to-video 호출하는 방법: curl과 Python 실전
빠르고 저렴한 이미지-투-비디오 등급의 첫 실행 요청 — 모델 ID부터 MP4 다운로드까지
-
튜토리얼
HiAPI API로 hailuo-2.3/text-to-video를 호출하는 방법: curl, Python 및 실행 가능한 요청 예시
-
튜토리얼
HiAPI에서 Kandinsky 6.0 Pro 텍스트-투-비디오 사용하기: curl 및 Python 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드