← Блог

Полное руководство по Z-Image-Base API: Практическое применение CFG и негативных промптов

Полное руководство по Z-Image-Base API: настройка параметра CFG, использование negative_prompt, руководство по опорным изображениям, управление силой воздействия. Включает примеры кода на Python/cURL.

By Dora 9 min read
Полное руководство по Z-Image-Base API: Практическое применение CFG и негативных промптов

Привет, я Дора. Удивительно, как небольшое неудобство втянуло меня в работу с Z-Image-Base API. У меня был пакет референсных изображений, тексты, которым нужен единый стиль, и недостаточно времени на ручную настройку промптов. Я постоянно видела, как люди вручную подбирают параметры и публикуют разовые удачные результаты. Для демонстраций это нормально. Моя работа требовала чего-то, что можно запускать дважды в неделю без неожиданностей. Поэтому за несколько сессий в январе–феврале 2026 года я подключила Z-Image-Base, внимательно наблюдала за тем, где он сопротивлялся, и записывала, что на самом деле делало его надёжным.

Это не обзор каждого параметра. Это те части, которые я постоянно трогала, плюс небольшие корректировки, снизившие умственные усилия. Если вы окружены инструментами ИИ и просто хотите один, который ведёт себя предсказуемо, это может помочь.

Базовая настройка API

Получение API-ключа WaveSpeed

Я зарегистрировалась и получила API-ключ в дашборде WaveSpeed. Ничего неожиданного. Маленькое замечание: разрешения ключа были ограничены проектом. Удобно в совместных рабочих процессах, но это означает, что проекты стоит сразу называть понятно. Я создала отдельный проект для экспериментов с изображениями, чтобы логи и квоты не смешивались с продакшном.

Если вы ротируете ключи (я делаю это ежемесячно), Z-Image-Base справился с этим чисто. Я поменяла секрет в переменных окружения, снова запустила скрипт и не заметила никаких зависших состояний аутентификации. Так и должно быть, но я всё равно почувствовала небольшое облегчение, когда это сработало с первого раза.

Структура URL эндпоинта

База выглядела как стандартная REST-схема с переключателем модели:

  • Основа: https://api.wavespeed.ai
  • Версионированный путь: /v1/images
  • Селектор модели: model: "Z-Image-Base"

На практике я отправляла POST-запросы на /v1/images/generations для запусков только с промптом и на /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 простые прямые промпты работали лучше, чем витиеватые описания. Я писала намерение сначала, ограничения потом:

  • «Редакционное фото продукта — керамическая кружка на льняном столе, мягкий утренний свет.»
  • Затем детали: «Вид на 35 мм, нейтральная цветокоррекция, без текста, незахламлённый фон.»

Я избегала глаголов, если мне не нужно было движение. Модель была стабильной, когда я оставалась конкретной: материалы, свет, ощущение объектива, тип фона. Она уходила в сторону, когда я добавляла настроения. Если я писала «уютный минимализм», получала хаос со свечами. Когда я писала «мягкий свет, один рассеянный источник, без свечей», она подчинялась.

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))

Я добавила таймаут 60 секунд после того, как одно задание зависло из-за нестабильного Wi-Fi. Блок исключений прямолинеен, но в продакшне я сопоставляю коды статусов (429, 503) с задержкой повторов.

Пример на JavaScript/Node.js

Для Node я использовала 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 не мешал работе.

Асинхронный и синхронный режим

Я попробовала оба режима. Синхронный подходил для одиночных изображений или быстрых проверок. Задержка в среднем составляла несколько секунд для вывода 1024×1024. Когда я ставила в очередь 8–12 рендеров, я переходила на асинхронный, чтобы скрипт не блокировался на каждом.

Асинхронная схема была предсказуемой: отправить задание, получить id, опрашивать эндпоинт статуса, затем забрать результат при state: "succeeded". Я установила интервал опроса 2–3 секунды и потолок 2 минуты. В нестабильных сетях асинхронный режим спасал от таймаутов, потому что я могла возобновить опрос без повторной отправки задания. Маленькая деталь, большая разница на дорожном Wi-Fi.

Если ваш рабочий процесс интерактивный (правка, просмотр, правка), синхронный удобнее. Если он пакетный или по расписанию, асинхронный делает конвейер более плавным и более изящно обрабатывает сбои.

Обработка ошибок

Два подхода помогли мне сделать ошибки скучными.

  • Ограничения скорости (429): я использовала экспоненциальную задержку с джиттером. Начинать с ~500 мс, ограничить до ~20 с и остановиться после 5–6 попыток. При асинхронных заданиях я просто повторяла вызов статуса, а не ставила рендер в очередь заново, если только API не сообщал об отсутствии задания.
  • Ошибки валидации (400/422): обычно возникали из-за несовпадающих типов контента или неподдерживаемых размеров. Когда я получала 422 при редактировании изображений, почти всегда это было из-за неправильного имени поля в form-data (image вместо reference_image). Я добавила небольшую проверку схемы перед запросами, чтобы это предотвратить.

Ещё одно замечание: я логировала model, guidance_scale, size и хэш промпта. Когда что-то странное проскальзывало, я могла воспроизвести это точно. Вот разница между пожиманием плечами и исправлением.

Даёт ли Z-Image-Base меньше сбоев, чем другие модели? Сложно сказать. Что я заметила: когда он давал сбой, сообщения были достаточно конкретными, чтобы я не теряла время на догадки. Это хорошая эргономика.

Поделиться