튜토리얼
AI 이미지 프롬프트 확장기 구축: ideogram-v4와 flux-2를 위한 LLM 프롬프트 확장
짧은 프롬프트로는 평범한 이미지만 생성됩니다. "coffee shop logo"는 모델에 거의 아무 정보도 주지 않습니다—렌더링할 이름도, 스타일도, 구도도, 색상도 없습니다. 현대 이미지 파이프라인은 프롬프트 확장으로 이 문제를 해결합니다: 프롬프트가 이미지 API로 들어가기 전에 저렴한 LLM 호출 한 번으로 다섯 단어 프롬프트를 풍부하고 모델 특성에 맞는 설명으로 바꾸는 것입니다. 일부 플랫폼은 이제 특정 모델에 확장기를 내장하고 있는데, 이는 이 전처리 단계가 얼마나 보편화되었는지를 보여줍니다.
이 튜토리얼에서는 이 단계를 직접 구축합니다. LLM이 사용자의 짧은 프롬프트를 풍부하게 만들고, hiapi의 통합 작업 API를 통해 결과를 ideogram-v4 또는 flux-2로 보내는 작은 Python 파이프라인입니다. 모델별로 확장 규칙을 제어할 수 있습니다—이는 중요한데, Ideogram 프롬프트를 돋보이게 하는 요소(큰따옴표로 감싼 정확한 텍스트)와 FLUX.2 프롬프트를 돋보이게 하는 요소(장면, 조명, 카메라)가 서로 다르기 때문입니다.
사전 준비: hiapi API key(콘솔에서 생성), Python 3.10+ 및 requests(pip install requests), 그리고 확장 단계에 사용할 chat-completions 호환 LLM 엔드포인트(작은 모델/저렴한 모델로 충분합니다—확장기는 매번 약 100단어만 작성합니다).
왜 프롬프트를 확장해야 할까?
hiapi에서 두 모델은 확장 스위치를 내장하고 있습니다: qwen-image-2.0에는 prompt_extend가, flux-1.1-pro에는 prompt_upsampling이 있습니다. 이를 켜면 플랫폼이 서버 측에서 프롬프트를 풍부하게 만듭니다.
그러나 ideogram-v4와 flux-2/text-to-image—각각 타이포그래피와 프롬프트 준수 측면에서 가장 강력한 두 모델—에는 내장 확장기가 없습니다. 이들의 입력 스키마는 프롬프트 문자열과 생성 매개변수만 받고 그 외에는 아무것도 받지 않습니다. 이 모델들에서 더 풍부한 프롬프트를 얻으려면 확장 단계를 직접 가져와야 합니다. 장점은: 직접 만든 확장기는 모델 인식, 브랜드 인식이 가능하고 디버깅도 됩니다. 어떤 문자열이 전송되는지 정확히 볼 수 있습니다.
확장기: 모델 제품군당 하나의 시스템 프롬프트
핵심 기술은 문자열 하나를 받아 문자열 하나를 내보내는 함수입니다. 아래는 서비스 제공자와 무관한 LLM 호출입니다—LLM_BASE_URL / LLM_API_KEY / LLM_MODEL을 이미 실행 중인 임의의 chat-completions 호환 엔드포인트로 지정하세요:
import os
import requests
LLM_BASE_URL = os.environ["LLM_BASE_URL"] # e.g. https://your-llm-endpoint/v1
LLM_API_KEY = os.environ["LLM_API_KEY"]
LLM_MODEL = os.environ["LLM_MODEL"]
EXPANDER_PROMPTS = {
# ideogram-v4 is a typography specialist: exact strings to render go in
# double quotes, and layout/style language matters more than camera language.
"ideogram-v4": (
"You expand short image prompts for a typography-focused image model. "
"Rules: keep the user's intent exactly; if the image should contain text, "
"put the exact words in double quotes; describe layout (centered, badge, "
"poster), lettering style, palette, and background; flat/vector terms are "
"good; no camera or lens language; output 60-100 words, one paragraph, "
"no preamble."
),
# flux-2 has strong prompt adherence for scenes: subject, setting, lighting,
# mood, camera. It has no negative prompt, so state what you want, not what
# you don't.
"flux-2/text-to-image": (
"You expand short image prompts for a high-fidelity text-to-image model. "
"Rules: keep the user's intent exactly; add subject detail, setting, "
"lighting, mood, composition and camera hints; state desired qualities "
"positively (the model has no negative prompt); output 80-120 words, one "
"paragraph, no preamble."
),
}
def expand_prompt(short_prompt: str, target_model: str) -> str:
"""LLM pass: short user prompt -> enriched, model-appropriate prompt."""
r = requests.post(
f"{LLM_BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {LLM_API_KEY}"},
json={
"model": LLM_MODEL,
"messages": [
{"role": "system", "content": EXPANDER_PROMPTS[target_model]},
{"role": "user", "content": short_prompt},
],
"temperature": 0.7,
},
timeout=60,
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"].strip()
파이프라인에는 str -> str만 필요하므로 이를 임의의 확장기로 교체할 수 있습니다—더 큰 LLM, 파인튜닝된 모델, 또는 고정 브랜드 캠페인을 위한 결정론적 템플릿까지도 가능합니다.
전후 비교
입력: coffee shop logo
ideogram-v4용으로 확장한 후:
"Driftwood Coffee"라는 이름의 스페셜티 커피숍을 위한 미니멀한 원형 배지 로고, "DRIFTWOOD COFFEE" 텍스트가 깔끔한 대문자 세리프 서체로 단순한 라인 커피컵을 둘러싸고 있으며, 컵 위에는 세 줄의 증기선이 있습니다. 따뜻한 크림색 배경, 진한 에스프레소 브라운의 텍스트와 라인, 플랫 벡터 스타일, 높은 대비, 중앙 정렬 구도, 사방에 충분한 여백.
flux-2용으로 확장한 후:
골든 아워의 아늑한 스페셜티 커피숍 내부, 넓은 전면 창으로 들어오는 따뜻한 햇살이 호두나무 바 위에 내리쬐고, 한 바리스타가 도자기 컵에 라떼 아트를 만들고 있으며, 뒤쪽 선반에는 호박색 유리병과 삼베 커피 원두 자루가 가득하고, 부드러운 증기가 피어오르며, 얕은 심도, 35mm 사진 질감, 따뜻하고 매력적인 색조, 초점은 컵에 선명하게 맞춰짐.
동일한 다섯 글자의 의도에서 두 개의 완전히 다르고—각각 정확히 들어맞는—프롬프트가 나왔습니다. Ideogram 버전은 렌더링할 정확한 텍스트를 큰따옴표로 표시하고(이 모델의 전문성), FLUX.2 버전은 장면과 빛에 집중합니다(이 모델의 전문성).
전체 파이프라인: 확장, 작업 생성, 폴링, 다운로드
모든 hiapi 생성 모델은 하나의 비동기 엔드포인트로 호출됩니다: POST /v1/tasks로 작업을 생성하고, GET /v1/tasks/<id>로 작업을 폴링하며, 완료된 산출물은 data.output[0].url에 나타납니다.
import json
import os
import time
import requests
HIAPI_KEY = os.environ["HIAPI_API_KEY"]
TASKS = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {HIAPI_KEY}", "Content-Type": "application/json"}
def create_task(model: str, input_obj: dict) -> str:
r = requests.post(TASKS, headers=HEADERS,
json={"model": model, "input": input_obj}, timeout=60)
data = r.json()
task_id = (data.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"create task failed: {json.dumps(data)[:300]}")
return task_id
def wait_task(task_id: str, timeout_s: int = 300, poll_s: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{TASKS}/{task_id}",
headers={"Authorization": f"Bearer {HIAPI_KEY}"}, timeout=30)
task = r.json().get("data") or {}
if task.get("status") == "success":
return task
if task.get("status") == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} still running after {timeout_s}s")
def generate(short_prompt: str, target_model: str) -> str:
expanded = expand_prompt(short_prompt, target_model)
print(f"expanded prompt -> {expanded}\n")
if target_model == "ideogram-v4":
input_obj = {
"prompt": expanded,
"rendering_speed": "BALANCED", # required: TURBO | BALANCED | QUALITY
"aspect_ratio": "1:1", # 1:1, 16:9, 4:3, 9:16, 3:4
}
else: # flux-2/text-to-image
input_obj = {
"prompt": expanded,
"aspect_ratio": "3:2", # 1:1, 16:9, 3:2, 2:3, 4:5, 5:4, 9:16, 3:4, 4:3, custom
"resolution": "1 MP", # '0.5 MP' | '1 MP' | '2 MP' | '4 MP'
}
task_id = create_task(target_model, input_obj)
task = wait_task(task_id)
url = task["output"][0]["url"]
# Output URLs expire — download immediately, don't hot-link them.
img = requests.get(url, timeout=120).content
fname = f"{target_model.replace('/', '-')}.png"
with open(fname, "wb") as f:
f.write(img)
return fname
if __name__ == "__main__":
print(generate("coffee shop logo", "ideogram-v4"))
print(generate("coffee shop logo", "flux-2/text-to-image"))
실행:
export HIAPI_API_KEY="sk-..."
export LLM_BASE_URL="https://your-llm-endpoint/v1"
export LLM_API_KEY="..."
export LLM_MODEL="your-small-model"
python pipeline.py
입력 스키마에 주의—모델마다 다름
작업 API는 모델에 따라 input을 엄격히 검증하며, 알 수 없는 필드는 무시되지 않고 거부됩니다. ideogram-v4에 인식하지 못하는 필드를 보내면:
{
"code": 400,
"error_code": "INVALID_REQUEST",
"message": "invalid input: <root>: additional properties 'size' not allowed"
}
이 파이프라인에서 주목할 세 가지 차이점:
rendering_speed는 ideogram-v4에서 필수입니다(TURBO/BALANCED/QUALITY—등급별로 가격이 다릅니다). flux-2에는 이 필드가 없습니다.- 종횡비 메뉴가 일치하지 않습니다. ideogram-v4는 약 1MP의 다섯 가지 프리셋(
1:1,16:9,4:3,9:16,3:4)을 제공합니다; flux-2는 더 넓은 집합과 더불어custom및 명시적width/height(256–2048, 16의 배수)를 제공합니다. 애플리케이션에서 사용자가 종횡비를 선택하게 한다면, 디스패치 전에 모델별로 매핑해야 합니다. - flux-2는 메가픽셀 등급으로 크기를 정합니다(
resolution:'0.5 MP'에서'4 MP'까지), 그리고 네거티브 프롬프트가 없습니다—이는 확장기가 항상 긍정적 표현을 사용해야 하는 또 다른 이유입니다.
프로덕션 환경 강화
고동시성에서는 폴링보다 콜백을 선호하세요. 작업 생성 시 callback 블록을 추가하면 hiapi가 종료 상태에서 POST로 알려주므로 폴링할 필요가 없습니다:
payload = {
"model": "flux-2/text-to-image",
"input": input_obj,
"callback": {"url": "https://your-domain.com/hiapi/callback", "when": "final"},
}
폴링은 로컬 스크립트, cron 작업, 저동시성에 더 간단하고 적합합니다; 콜백은 장시간 생성에서 요청 낭비를 줄이고 각 작업당 정확히 한 번 알림을 줍니다. 일반적인 프로덕션 패턴은 콜백을 주 채널로, 느린 대사 폴링을 백업으로 사용하는 것입니다.
재시도를 멱등하게 만드세요. 작업 생성 시 Idempotency-Key 헤더를 보냅니다(최대 255바이트, 24시간 보존). 같은 key로 재시도하면 중복 작업을 생성—및 청구—하는 대신 원래 taskId를 반환합니다(응답 헤더에 Idempotent-Replay: true). 다른 요청 본문으로 key를 재사용하면 422 IDEMPOTENCY_KEY_MISMATCH를 반환합니다; 재시도가 진행 중인 원래 요청과 경쟁하면 409와 Retry-After를 반환합니다. 자체 작업 ID에서 key를 파생하세요:
headers = {**HEADERS, "Idempotency-Key": f"expand-pipeline-{job_id}"}
인증 실패를 명시적으로 처리하세요. 유효하지 않거나 권한이 부족한 key는 HTTP 401로 실패하며 오류 본문에 "code": "permission_denied"가 포함됩니다—재시도로 해결되지 않으므로 생성 오류와 별도로 포착해야 합니다:
if r.status_code == 401:
raise SystemExit("hiapi key invalid or lacks access to this model")
출력을 즉시 영속화하세요. 작업 출력 URL에는 유효기간이 있습니다. 작업이 성공하면 즉시 바이트를 다운로드해 자체 저장하세요.
확장기에 고삐를 채우세요. 확장을 약 120단어 이내로 제한하고, temperature는 ≤ 0.8로 전달하며, 짧은 프롬프트와 확장된 프롬프트를 taskId와 함께 로그로 남기세요. 생성 결과가 이상해 보일 때 첫 번째 질문은 언제나 "모델이 실제로 받은 것은 무엇인가?"입니다—자체 확장기가 있으면 이 질문에 답할 수 있습니다.
비용 누적을 주의하세요. 이제 이미지 한 장당 두 번의 호출 비용을 지불합니다: 소형 LLM 완료(보통 무시할 수준)와 이미지 생성—ideogram-v4는 rendering_speed 등급 기준 이미지당 과금, flux-2는 해상도 등급 기준 이미지당 과금입니다. 두 모델의 현재 요율은 가격 페이지를 참고하세요.
다음에 할 일
- 모델 상세와 매개변수: ideogram-v4 및 flux-2 모델 페이지
- 플랫폼에 확장기가 내장된 곳에서는 DIY 확장기를 건너뛰세요: qwen-image-2.0(
prompt_extend) 및 flux-1.1-pro(prompt_upsampling) - 작업 API 참조 및 더 많은 통합 가이드: 문서
- 모델별 이미지당 가격 비교: 가격
자주 묻는 질문
같은 확장된 프롬프트를 두 모델에 보낼 수 있나요? 가능하지만 품질을 희생합니다. Ideogram은 큰따옴표로 감싼 리터럴 텍스트와 레이아웃 언어를 선호하고; FLUX.2는 장면, 조명, 카메라 디테일을 선호합니다. 모델 제품군별로 확장기 시스템 프롬프트 하나를 유지하는 비용은 몇 줄의 코드에 불과하지만 매 생성마다 보상이 있습니다.
hiapi에 프롬프트를 대신 확장해 주는 모델이 있나요?
있습니다—qwen-image-2.0은 prompt_extend를, flux-1.1-pro는 prompt_upsampling을 받습니다. ideogram-v4와 flux-2/text-to-image에는 내장 확장기가 없으며, 이것이 이 튜토리얼이 적용되는 상황입니다.
확장된 프롬프트는 얼마나 길어야 하나요? 이 파이프라인에서는 60–120단어가 적정 구간입니다. 이를 넘으면 신호가 희석되고, 일부 저비용 모델은 긴 프롬프트를 강하게 잘라냅니다(예: flux-schnell은 약 256토큰에서 잘림). 밀도가 길이보다 중요합니다: 추가되는 모든 구문은 이미지를 제약해야 합니다.
flux-2는 네거티브 프롬프트를 지원하나요? 아니요. 원하지 않는 것이 아니라 원하는 것을 표현하세요—그리고 이 규칙을 확장기 시스템 프롬프트에 넣어 "no text, no watermark" 같은 문구가 모델에 의해 콘텐츠로 읽히는 일이 없도록 하세요.
LLM이 사용자의 의도를 바꾸면 어떻게 하나요? 시스템 프롬프트에서 제약하고("keep the user's intent exactly"), 적당한 temperature를 유지하며, 전후 비교를 기록하세요. 고위험 흐름의 경우 생성 전에 사용자에게 확장된 프롬프트를 보여주거나, 확장기를 우회하는 "내 프롬프트 그대로 사용" 스위치를 제공하세요.
왜 내 요청이 생성이 일어나기 전에 400으로 실패하나요?
스키마 검증 때문입니다. 각 모델은 input을 엄격히 검증합니다—필수 필드 누락(예: ideogram-v4의 rendering_speed)이나 알 수 없는 필드가 있으면 빠르게 실패하며, error_code: "INVALID_REQUEST"와 위반 필드를 명시한 메시지를 반환합니다. 이러한 실패에는 요금이 청구되지 않습니다; 필드를 수정하고 다시 제출하면 됩니다.
관련 글
모든 아티클 보기 →-
튜토리얼
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 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드