Z-Image-Base API 완벽 가이드: CFG + 네거티브 프롬프트 실전 활용
Z-Image-Base API 완벽 튜토리얼: CFG 파라미터 튜닝, negative_prompt 사용법, 참조 이미지 가이던스, 강도 제어. Python/cURL 코드 예제 포함.
안녕하세요, 저는 Dora입니다. 작은 불편함이 저를 Z-Image-Base API로 이끌었다는 게 놀랍습니다. 참조 이미지 배치와 일관된 스타일이 필요한 카피, 그리고 프롬프트를 일일이 다듬을 시간이 부족했습니다. 사람들이 설정을 수동으로 조정하고 일회성 결과물을 올리는 걸 계속 봤습니다. 데모라면 괜찮습니다. 제 작업에는 예상치 못한 결과 없이 주 2회 실행할 수 있는 무언가가 필요했습니다. 그래서 2026년 1~2월에 걸쳐 몇 차례 세션을 통해 Z-Image-Base를 연결하고, 저항이 생기는 지점에 주의를 기울이며 실제로 안정적으로 느껴지게 만든 것들을 메모했습니다.
이 글은 모든 파라미터를 둘러보는 투어가 아닙니다. 제가 계속 손을 댔던 부분들과 정신적 부담을 줄여준 작은 조정들입니다. AI 도구들에 둘러싸여 그냥 제대로 작동하는 하나를 원한다면, 도움이 될 수 있습니다.
API 기본 설정
WaveSpeed API 키 발급
WaveSpeed 대시보드에 가입하고 API 키를 받았습니다. 특별히 어렵지 않았습니다. 한 가지 참고할 점: 키 권한은 프로젝트 단위로 범위가 지정되어 있었습니다. 공유 워크플로에서는 유용하지만, 프로젝트 이름을 처음부터 명확하게 지정해야 한다는 의미입니다. 저는 이미지 실험만을 위한 프로젝트를 별도로 만들어 로그와 할당량이 프로덕션과 섞이지 않도록 했습니다.
키를 교체하는 경우(저는 매달 합니다), Z-Image-Base는 깔끔하게 처리했습니다. 환경 변수에서 시크릿을 교체하고 스크립트를 다시 실행했는데, 남아있는 인증 상태가 보이지 않았습니다. 원래 그래야 하지만, 첫 시도에 작동했을 때 안도감이 들었습니다.
엔드포인트 URL 구조
기본 구조는 모델 전환이 있는 표준 REST 설정처럼 보였습니다:
- 기본:
https://api.wavespeed.ai - 버전 경로:
/v1/images - 모델 선택자:
model: "Z-Image-Base"
실제로는 프롬프트만 사용하는 경우 /v1/images/generations에 POST 요청을 보내고, 참조 이미지를 전달할 때는 /v1/images/edits를 사용했습니다. 복잡한 쿼리 문자열보다 명확한 경로를 선호하는데, 이 방식이 제 사고방식과 일치했습니다. 라우팅이 다르더라도 모델 이름은 여전히 중요합니다: Z-Image-Base는 화려한 모델들보다 조금 더 보수적인 경향이 있었는데, 저는 그 점이 마음에 들었습니다.
인증 헤더 설정
특별히 까다로운 것은 없지만, 정확한 철자가 중요합니다:
Authorization: Bearer YOUR_WAVESPEED_API_KEY- 프롬프트만 사용할 때:
Content-Type: application/json - 이미지 업로드 시:
Content-Type: multipart/form-data
키는 .env에 저장하고 런타임에 불러옵니다. 번거롭지만 실수로 커밋하는 것을 방지합니다. 또한: CI에서 스크립팅하는 경우 키를 마스킹된 시크릿으로 설정하고 전체 요청을 로깅하지 마세요. 당연한 말이지만 짚고 넘어갈 가치가 있습니다. 로그 한 줄이 나중에 큰 문제가 될 수 있습니다. WaveSpeed의 보안 모범 사례에 따르면 API 키는 환경 변수에 저장하고, 프론트엔드 코드에는 절대 노출하지 않으며, 정기적으로 교체해야 합니다.
핵심 파라미터 상세 설명
prompt - 순방향 프롬프트 작성법
프롬프트 스타일에 대해 너무 깊이 고민하지 않았습니다. Z-Image-Base에서는 화려한 문장보다 단순한 순방향 프롬프트가 더 잘 작동했습니다. 의도를 먼저 쓰고 제약 조건을 나중에 썼습니다:
- “리넨 테이블 위의 세라믹 머그를 찍은 에디토리얼 스타일 제품 사진, 부드러운 아침 빛.”
- 그런 다음 세부 사항: “35mm 느낌, 중립적인 색상 그레이딩, 텍스트 없음, 깔끔한 배경.”
동작이 필요하지 않으면 동사를 제외했습니다. 구체적인 내용에 집중했을 때 모델이 안정적이었습니다: 재료, 빛, 렌즈 느낌, 배경 유형. 분위기를 추가하면 결과가 흔들렸습니다. “아늑한 미니멀리즘”을 쓰면 양초가 가득한 혼란스러운 이미지가 나왔습니다. “부드러운 빛, 확산 광원 하나, 양초 없음”이라고 쓰면 그대로 따랐습니다.
negative_prompt - 원하지 않는 요소 제외
처음에는 네거티브 프롬프트를 사용하지 않았지만, 결국 안전망이 되었습니다. 재사용하는 짧은 기본값을 유지했습니다:
- “no text, no watermark, no extra hands, no extra limbs, no logo, no signature, no border”
이것을 추가하면 정리 시간이 줄었습니다. 모든 엣지 케이스를 해결하지는 못했지만(머그에 반지가 여전히 몰래 나타날 때가 있습니다), 이상한 결과의 빈도를 낮춰서 소규모 배치에서 재렌더링을 줄였습니다. 엄격한 제품 앵글이 필요할 때는 “no dramatic perspective, no tilt”를 추가해서 더 수평적인 샷을 얻었습니다.
guidance_scale (CFG) - 프롬프트 준수도 제어
이 파라미터가 예상보다 훨씬 중요했습니다. 제 메모:
- 5–6: 여유로움, 질감과 즉흥성이 조금 더 살아남
- 7–8: 신뢰할 수 있음: 대부분의 작업에 사용
- 9+: 문자적 준수, 하지만 때로는 딱딱한 조명과 평평한 색상
좋은 참조 이미지가 있을 때는 CFG를 6–7로 낮추면 더 좋은 미세 대비를 얻었습니다. 참조 없이는 7.5가 중앙값이었습니다. Z-Image 기술 문서에 따르면 이 모델은 완전한 Classifier-Free Guidance(CFG)를 지원하여 복잡한 프롬프트 엔지니어링에 정밀도를 제공합니다. 9 이상은 신중한 친구에게 더 신중하라고 말하는 것 같았습니다. 틀리지는 않지만, 약간 생기가 없었습니다.
image + strength - 참조 이미지 가이던스
이 두 가지가 핵심 역할을 했습니다. 단일 참조 JPG를 첨부하고 strength를 조정해 모델이 얼마나 따르는지 제어했습니다:
- 0.25–0.35: 가벼운 터치: 팔레트와 분위기 유지
- 0.45–0.6: 확실한 준수: 구도가 가깝게 유지됨
- 0.7+: 거의 복사 수준: 작은 스타일링 변경이 있는 변형에 유용
0.5가 최적의 지점이 될 거라고 예상하지 못했지만, 그랬습니다. 레이아웃을 유지하기에 충분한 구조와 조명 조정을 위한 충분한 자유를 제공했습니다. 높은 강도에서는 모델이 고집스러워졌습니다: 소스에 잘못된 위치에 그림자가 있으면 모델이 그것을 고수했습니다. 프롬프트에 “shadow on camera-left”를 추가하고 strength를 낮추는 방법을 시작했습니다. 그렇게 하면 재시도가 줄었습니다.
실용적인 참고 사항: 업로드 전에 참조 입력을 긴 쪽을 기준으로 1024로 리사이즈했습니다. 일관된 크기 덕분에 더 예측 가능한 크롭과 속도를 얻었습니다. 많은 반복 작업에서 대역폭도 조금 절약되었습니다.
코드 구현
cURL 빠른 테스트
새 엔드포인트를 연결할 때, 응답 형태와 오류 코드를 확인하기 위해 cURL로 시작합니다.
curl -X POST \
https://api.wavespeed.ai/v1/images/generations \
-H "Authorization: Bearer $WAVESPEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Z-Image-Base",
"prompt": "Editorial-style product photo of a ceramic mug on a linen table, soft morning light. 35mm look, neutral color grading, no text, uncluttered background.",
"negative_prompt": "no text, no watermark, no extra hands, no extra limbs, no logo, no signature, no border",
"guidance_scale": 7.5,
"size": "1024x1024"
}'
이미지 배열이 포함된 JSON 페이로드를 반환받았습니다(옵션에 따라 base64 또는 URL). 직접 URL을 원한다면 API가 지원하는 경우 출력 대상이나 스토리지 플래그를 설정하세요. 테스트에서는 base64를 사용하고 로컬에서 디코딩했습니다.
Python 완전한 예제
화요일 아침 배치 작업에서 실행한 것과 거의 동일합니다. 재시도를 위한 최소한의 구조만 갖춘 단순한 코드입니다.
import os
import base64
import requests
API_KEY = os.getenv("WAVESPEED_API_KEY")
BASE_URL = "https://api.wavespeed.ai/v1"
headers = {
"Authorization": f"Bearer {API_KEY}",
}
def generate_image(prompt, negative_prompt, guidance_scale=7.5, size="1024x1024"):
url = f"{BASE_URL}/images/generations"
payload = {
"model": "Z-Image-Base",
"prompt": prompt,
"negative_prompt": negative_prompt,
"guidance_scale": guidance_scale,
"size": size,
}
response = requests.post(
url,
headers={**headers, "Content-Type": "application/json"},
json=payload,
timeout=60
)
response.raise_for_status()
data = response.json()
b64_string = data["data"][0]["b64_json"]
return base64.b64decode(b64_string)
if __name__ == "__main__":
prompt = (
"Editorial-style product photo of a ceramic mug on a linen table, soft morning light. "
"35mm look, neutral color grading, no text, uncluttered background."
)
negative_prompt = (
"no text, no watermark, no extra hands, no extra limbs, "
"no logo, no signature, no border"
)
try:
image_bytes = generate_image(prompt, negative_prompt)
with open("output.png", "wb") as f:
f.write(image_bytes)
print("Saved output.png")
except requests.HTTPError as e:
print("HTTP error:", e.response.text if e.response else str(e))
except Exception as e:
print("Error:", str(e))
불안정한 Wi-Fi에서 작업이 중단된 후 60초 타임아웃을 추가했습니다. 예외 블록은 단순하지만, 프로덕션에서는 상태 코드(429, 503)를 백오프에 매핑합니다.
JavaScript/Node.js 예제
Node.js에서는 fetch와 작은 헬퍼를 사용했습니다. 같은 아이디어입니다: 인증을 단순하게 유지하고, 오류를 명확하게 표시합니다.
import fs from 'node:fs/promises';
import fetch from 'node-fetch';
const API_KEY = process.env.WAVESPEED_API_KEY;
const BASE_URL = 'https://api.wavespeed.ai/v1';
async function generateImage({
prompt,
negativePrompt,
guidanceScale = 7.5,
size = '1024x1024',
}) {
const response = await fetch(`${BASE_URL}/images/generations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'Z-Image-Base',
prompt,
negative_prompt: negativePrompt,
guidance_scale: guidanceScale,
size,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const json = await response.json();
const base64String = json.data[0].b64_json;
return Buffer.from(base64String, 'base64');
}
(async () => {
try {
const prompt =
'Editorial-style product photo of a ceramic mug on a linen table, soft morning light. ' +
'35mm look, neutral color grading, no text, uncluttered background.';
const negativePrompt =
'no text, no watermark, no extra hands, no extra limbs, ' +
'no logo, no signature, no border';
const imageBuffer = await generateImage({ prompt, negativePrompt });
await fs.writeFile('node-output.png', imageBuffer);
console.log('Saved node-output.png');
} catch (err) {
console.error('Error:', err.message);
}
})();
이것이 작동하면, 배관에 대해 더 이상 생각하지 않아도 됩니다. 그게 핵심입니다. Z-Image-Base API는 방해가 되지 않았습니다.
비동기 vs 동기 모드
두 가지 모드를 모두 시도해봤습니다. 동기는 단일 이미지나 빠른 확인에 괜찮았습니다. 1024 정사각형 출력의 경우 평균 지연 시간은 몇 초 정도였습니다. 8~12개의 렌더링을 큐에 넣을 때는 스크립트가 각각에 대해 블로킹되지 않도록 비동기로 전환했습니다.
비동기 패턴은 예측 가능했습니다: 작업을 전송하고 id를 받아 상태 엔드포인트를 폴링한 다음 state: "succeeded"가 되면 결과를 가져옵니다. 폴링 간격은 2~3초, 상한은 2분으로 설정했습니다. 불안정한 네트워크에서 비동기는 작업을 다시 제출하지 않고 폴링을 재개할 수 있어 타임아웃을 방지했습니다. 작은 디테일이지만, 여행 중 Wi-Fi에서는 큰 차이였습니다.
워크플로가 대화형(조정, 확인, 조정)인 경우 동기가 더 편합니다. 배치 작업이거나 예약된 경우 비동기가 파이프라인을 더 원활하게 유지하고 더 우아하게 실패합니다.
오류 처리
오류를 평범하게 유지하는 데 도움이 된 두 가지 패턴이 있습니다.
- 속도 제한(429): 지터가 있는 지수 백오프를 사용했습니다. ~500ms에서 시작하고,
20s에서 상한을 두며, 56번 시도 후 중지합니다. 비동기 작업의 경우 API가 작업이 없다고 말하지 않는 한 렌더링을 다시 큐에 넣는 대신 상태 호출을 재시도했습니다. - 유효성 검사 오류(400/422): 일반적으로 콘텐츠 타입 불일치나 지원되지 않는 크기에서 발생했습니다. 이미지 편집에서 422가 발생했을 때 거의 항상 form-data 필드 이름이 잘못되었습니다(
imagevsreference_image). 이를 방지하기 위해 요청 전에 작은 스키마 검사를 추가했습니다.
한 가지 더: model, guidance_scale, size, 그리고 프롬프트의 해시를 로깅했습니다. 이상한 것이 통과했을 때 정확하게 재현할 수 있었습니다. 그게 어깨를 으쓱하고 넘어가는 것과 고치는 것의 차이입니다.
Z-Image-Base가 다른 모델보다 실패가 적은가요? 말하기 어렵습니다. 제가 주목한 것은 실패했을 때 메시지가 구체적이어서 원인을 추측하느라 시간을 낭비하지 않았다는 점입니다. 그것이 좋은 사용성입니다.





