spicyapiДокументация
Руководства
Основное содержимое

Руководство: генерация и редактирование изображений

Как ведут себя эндпоинты изображений как класс — две формы запроса, три способа записать размер вывода, что референсная картинка добавляет к счёту, веса адаптеров и как читать отказ.

Это руководство — о классе эндпоинтов изображений, а не об отдельной модели. Оно объясняет, что у них одинаково, и — что полезнее — те места, где они расходятся настолько, что тело запроса, написанное для одного, на следующем уже не пройдёт.

Таблицы параметров здесь намеренно нет. У параметров ровно один источник: inputSchema, который живой каталог возвращает для той самой модели, которую вы вызываете. Эта страница объясняет, как читать эту схему и о чём она умалчивает.

Если вам нужноЧитайте вместо этого
Первое изображение целиком: от ключа и баланса до файлаБыстрый старт
Тот же путь для видео, по шагамУчебник: превратить фотографию в видео
Все поля конкретной моделиЕё inputSchema — см. Каталог моделей и схемы
Перенести лицо или голову с одной фотографии на другуюПодмена объекта

MODEL_ID_FROM_CATALOG — это заполнитель

Во всех примерах ниже MODEL_ID_FROM_CATALOG стоит там, где должен быть настоящий идентификатор модели. Возьмите его из аутентифицированного живого каталога — GET /api/v1/models?modality=image — и требуйте одновременно enabled: true и available: true, прежде чем что-то тратить. Чтение каталога бесплатно. Отправка самого заполнителя вернёт лишь ошибку «not found».

Две формы запроса

Работа с изображениями приходит в виде двух задач, и каталог их разделяет:

  • Генерирующие эндпоинты заканчиваются на /text-to-image. Вы отправляете слова и получаете картинку.
  • Редактирующие эндпоинты заканчиваются на /edit. Вдобавок вы отправляете хотя бы одну картинку.

У редактирования два допустимых написания, и оба стоит знать до того, как фильтровать каталог: эндпоинты публикуются с суффиксом edit, тогда как массив tasks классифицирует их как image-to-image. Параметр запроса task принимает любое из двух. См. Каталог моделей и схемы.

Обе формы используют ту же оболочку создания, тот же жизненный цикл задачи и тот же формат результата, что и любая другая медиамодель. Меняется только input.

prompt обязателен в обеих формах

Соблазнительно решить, что редактирующему эндпоинту достаточно одной картинки и prompt там необязателен. На эндпоинтах, которые вообще принимают подсказку, это не так: prompt входит в корневой список required у обеих форм, и его пропуск отклоняется ещё до того, как что-то будет списано.

Отклонено редактирующим эндпоинтом
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": {
    "image_urls": ["spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3"]
  }
}
prompt: is required

Картинка говорит, от чего вы отталкиваетесь; подсказка — что с этим сделать. Редактирующему эндпоинту, не получившему указания, не над чем работать.

Исключение одно, и это целое семейство, а не случайный эндпоинт: некоторые редактирующие эндпоинты не принимают prompt вовсе и берут только картинки, потому что операция задана самим эндпоинтом. Они относятся к набору инструментов подмены объекта и несут в каталоге поле toolkit, по которому их можно отличить; см. Подмена объекта. Для них prompt: is not a supported field — правильный ответ, а не сбой.

У поля входной картинки три имени

Какое использует эндпоинт — часть его схемы, и из вида задачи это не выводится:

ПолеФормаЧто обычно означает
image_urlодна ссылка на изображениеЕсть ровно одна картинка-объект, и понятия второй у эндпоинта нет
image_urlsмассив ссылок на изображенияКартинки — это сам материал, который редактируют или совмещают, в том порядке, на который ссылается указание
reference_image_urlsмассив ссылок на изображенияКартинки направляют генерацию, которая иначе была бы чисто текстовой: это ориентир, а не объект

Удивляет именно reference_image_urls: оно встречается на генерирующих эндпоинтах, так что эндпоинт /text-to-image вполне может принимать картинки. Не считайте, что «text-to-image» означает «в запросе изображений нет».

Ссылка на изображение — это публичный HTTPS-адрес, подтверждённый после загрузки URI spicy:// или небольшой URI data:image/…;base64,…. О загрузках см. Медиа, о канонических именах полей — контракт Spicy Schema.

Границы массивов разнятся сильнее, чем кажется

У перечисленных массивов есть minItems и maxItems, сервер соблюдает и то и другое, а числа различаются от эндпоинта к эндпоинту: сегодня диапазон — от массива, принимающего ровно один элемент, до массивов на десять. Эндпоинт того же семейства с тем же именем поля может иметь другой потолок.

image_urls: must contain at least 1 items
image_urls: must contain at most 3 items

Читайте minItems и maxItems из схемы той модели, которую вызываете. Таблица параметров, где массив показан голым array без формы элемента и без ограничения длины, — это ровно та строка, с которой читателю нечего делать; именно поэтому такой таблицы на этом сайте нет.

Размер вывода записывается тремя способами

Это первое, обо что спотыкаются, направляя готовое тело запроса на второй эндпоинт.

В каталоге изображений в ходу три написания, и эндпоинт принимает ровно одно из них:

НаписаниеПоляПримечания
Раздельноеaspect_ratio и resolutionФорма кадра и ступень разрешения выбираются по отдельности
ПредустановкаsizeОдно значение перечисления задаёт сразу оба
Явноеwidth и heightЦелые пиксели, у каждого свои minimum и maximum

На новых эндпоинтах чаще всего встречается раздельное написание, и оно же самое полезное из трёх: aspect_ratio компонует кадр до генерации, а не обрезает после, так что форму можно менять, не трогая бюджет пикселей.

Поле из чужого написания отклоняется, а не игнорируется

В каждой inputSchema изображения стоит additionalProperties: false. Поле, которого у эндпоинта нет в списке, отвергается: тело запроса, перенесённое с другого эндпоинта, падает целиком, а не откатывается молча к значению по умолчанию.

Падает в обе стороны, и сообщение называет поле:

size: is not a supported field
aspect_ratio: is not a supported field

Ни то ни другое не тарифицируется. Цена здесь не денежная, а в самом допущении: нельзя написать один объект input и направить его сразу на несколько эндпоинтов изображений, и «совместимого» написания, которое приняли бы все, не существует.

Какое из них выбирает ценовую ступень

На эндпоинте с раздельным написанием эти два поля обычно играют в счёте разные роли:

  • resolution обычно выбирает ценовую ступень. В расчёте стоимости оно проявляется как variant, и его изменение меняет сумму.
  • aspect_ratio обычно нет. Перекадрировать — не то же самое, что масштабировать.

Слово «обычно» в этих двух фразах работает по-настоящему. Машиночитаемый ответ — на самом поле: x-ui.affects_price: true и, если эндпоинт это объявляет, членство в x-pricing.variantFields. Сверяйтесь со схемой, а не с привычкой. См. Как читать цены и справочник аннотаций в контракте Spicy Schema.

Допустимые значения тоже свои у каждого эндпоинта. Отказ их перечисляет — бесплатный способ их увидеть:

aspect_ratio: must be one of: "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9"

Удобно при изучении, но чтение enum это не заменяет: в схеме есть ещё и default, который применяется, если поле опустить.

Что референсная картинка добавляет к счёту

Это тихая статья расхода, и она заслуживает отдельного раздела, потому что в ответе на неё ничто не указывает.

Обычная арифметика медиамодели — цена × количество, где количество берётся из значения quantityField в вашем вводе, либо равно одной единице, если quantityField пуст. На эндпоинтах изображений quantityField чаще всего num_outputs, и ведёт он себя ровно так, как ожидается: попросите четыре картинки — и в расчёте quantity покажет 4. Это честное измерение: его видно.

Некоторые эндпоинты вдобавок берут отдельную плату за каждую входную картинку, и вот её движения не видно. Запросите расчёт для одного и того же эндпоинта дважды — с единственным референсом и с полным массивом — и получите:

  • тот же unit;
  • то же quantity;
  • тот же variant;
  • другую сумму.

Три из четырёх полей, по которым обычно собирают оценку, остаются ровно на месте. Оценка, посчитанная как цена × количество, занижает итог, и ничто в ответе об этом не сигналит.

Машиночитаемая примета

Поле, помеченное x-ui.affects_price: true, которое не является quantityField и не перечислено в x-pricing.variantFields, двигает сумму, не выбирая ступень. Для такого эндпоинта читайте pricing каталога как нижнюю границу, а сумму берите из расчёта стоимости.

Это то же правило, что сформулировано в разделе Каталог моделей и схемы, и именно при редактировании изображений вы столкнётесь с ним чаще всего: массив входных картинок нередко помечен так.

Насколько именно вырастет сумма, зависит от эндпоинта и не подчиняется постоянной пропорции. Сегодня в каталоге есть эндпоинты, где заполненный массив стоит в несколько раз дороже одного референса, и эндпоинты, где дополнительные референсы не стоят ничего, — и отличить их можно только спросив. Поэтому не оценивайте цену массива по соседнему эндпоинту и не выводите стоимость полного массива из расчёта с одним референсом.

Считайте стоимость того тела, которое реально отправите

Узнать цену именно этого запроса до оплаты
curl -sS https://api.spicyapi.ai/api/v1/jobs/quote \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID_FROM_CATALOG","input":{"prompt":"your instruction","image_urls":["spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3"]}}'

Расчёт стоимости выполняет ту же проверку, что и создание задачи, ничего не стоит, ничего не запускает и ничего не резервирует. Он возвращает estimatedCost, maxCharge, quantity и unit для этого самого тела. Изменили массив — запросите расчёт заново. Подробности и порядок действий при потерянном ответе — в разделе Расчёт стоимости и совместимость; где расчёт уместен в реальной интеграции — в разделе Внедрение в продакшене.

Веса адаптеров

Некоторые эндпоинты позволяют наложить поверх базовой модели ваши собственные дообученные веса. Каноническое поле — loras, либо high_noise_loras и low_noise_loras, если эндпоинт выставляет свои стадии по отдельности.

Массив объектов со своими правилами внутри

Эти поля несут x-ui.widget: "object-list": массив, у которого items — полноценная схема объекта сама по себе. У неё свой required, свой additionalProperties: false и свои тип, диапазон и default для каждого подполя. Типичный элемент называет один набор весов и силу его применения.

Два элемента, применяемые вместе
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": {
    "prompt": "your scene",
    "loras": [
      { "path": "https://weights.example.com/style.safetensors", "scale": 1 },
      { "path": "https://weights.example.com/character.safetensors", "scale": 0.6 }
    ]
  }
}

Форма элемента не одинакова на всех эндпоинтах. Что считается файлом весов — прямой адрес или ключ из встроенной библиотеки — сужает собственная подсхема items конкретного эндпоинта; то же касается имён подполей, числа элементов и того, какие из них обязательны. Читайте items, а не этот пример. Виджет описан в контракте Spicy Schema; как превратить его во вложенную таблицу — в разделе Каталог моделей и схемы.

Одно правило внутри этих элементов легко прочесть неверно: значения по умолчанию достают до элементов массива. Подполе, которое одновременно есть в items.required и имеет default, фактически необязательно: опустите его — и значение по умолчанию подставится до проверки. Подполе без значения по умолчанию при отсутствии отклоняется. То есть items.required не означает «должно присутствовать в том JSON, который я отправляю».

Откуда берутся веса

В path можно указать одно из трёх:

  • Публичную ссылку на файл .safetensors или, если эндпоинт это допускает, id репозитория Hugging Face вида owner/repo. Модель скачивает файл в момент генерации, поэтому первый вызов с новыми весами заметно медленнее последующих.
  • Загрузку. Загрузите файл как application/octet-stream через прямую загрузку, до 2 GiB, и используйте возвращённый URI spicy://f/…. На этапе commit проверяется, что это полный файл safetensors.
  • Импорт. Для файла, который требует входа, например из закрытого или gated-репозитория Hugging Face либо файла Civitai за ключом API, отправьте ссылку и токен доступа на POST /api/v1/files/import. SpicyAPI скачивает файл один раз и возвращает URI spicy://f/…. Токен используется только для этой загрузки и нигде не сохраняется.
Импорт LoRA из закрытого репозитория Hugging Face
curl -sS https://api.spicyapi.ai/api/v1/files/import \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  --data "$(jq -n --arg url "https://huggingface.co/OWNER/REPO/blob/main/your_lora.safetensors" --arg token "$HF_TOKEN" '{url: $url, token: $token}')"

url может быть и id репозитория (OWNER/REPO), и страницей репозитория, если в нём ровно один файл .safetensors. Ответ имеет ту же форму, что и ответ commit; подставьте data.uri в loras[].path. Загруженные и импортированные веса хранятся один день, как и любая другая загрузка, и никогда не доступны другим аккаунтам. После этого загрузите или импортируйте их заново.

При отправке проверяется ссылка, а не веса внутри

Перед созданием задачи каждая ссылка на веса пробуется. Ссылка, которая требует входа, отвечает 404 или открывает веб-страницу, отклоняется с ошибкой 400, ничего не списывается, а в сообщении указан элемент. Всё остальное проходит, поэтому хост, который позже упадёт, или веса, которые не удастся загрузить, всё равно придут к вам как сбой задачи.

Проблемы, которые проба увидеть не может, приходят позже — сбоем задачи с errorCode из девяти публичных классов платформы и сообщением о том, что пошло не так; сумма, удержанная под задачу, возвращается. См. Ошибки.

Худший случай ещё тише: веса, собранные под другую базовую модель, могут загрузиться без ошибки и почти не изменить результат. Читать нечего — есть только картинка, похожая на результат базовой модели.

Значит, проверяйте на этапе развёртывания

Вынесите проверку из пути запроса в то, что выполняется при выкладке:

  1. Достижимость. Запрос HEAD к каждому адресу весов, который собираетесь вывести в продакшен. Платформа пробует каждую ссылку при создании задачи, но ссылка, работающая сегодня, может перестать работать на следующей неделе, а проверка при развёртывании заметит это раньше, чем ваш трафик.
  2. Действие. Одна генерация на каждый новый файл весов, сравниваемая с тем же промптом и тем же зерном без него. Если различить их нельзя, файл делает не то, что вы думаете, — убедитесь в этом до того, как от него будет зависеть пакет.
  3. Устойчивость. Ваши адреса должны работать всё то время, пока они в продакшене. Исчезнувший файл весов превращается в сбои задач без очевидной причины — через несколько дней после выкладки, на которую их и спишут.

Советы по этим полям, привязанные к конкретному эндпоинту, — относительно чего на самом деле меряется значение, какая настройка заметно замедляет вызов, какие входные данные схема принимает, но вы о них пожалеете — живут в paramNotes публичного каталога, с ключом по имени параметра и на всех десяти языках документации. Это проза, а не правило; что именно принимается, по-прежнему решает inputSchema. См. Каталог моделей и схемы.

Асинхронно, как и любая медиамодель

Эндпоинты изображений — это задачи, а не вызовы «запрос-ответ». Вы создаёте задачу, затем забираете результат опросом или через вебхук; queued и running — нетерминальные состояния, succeeded, failed и expired — терминальные. Первоисточник по состояниям — Асинхронные задачи.

Изображения готовятся быстрее видео, и это соблазняет написать вызов так, будто он синхронный. Не поддавайтесь: ограниченный опрос здесь достаточно короток, чтобы ощущаться синхронным, но коду всё равно придётся обработать задачу, которая ещё выполняется, когда бюджет ожидания исчерпан, — а передать это вебхуку масштабируется лучше, чем держать запрос открытым. Опрос использует отдельный лимит чтения аккаунта и не расходует квоту отправки задач. Слишком частый опрос может ограничить запросы состояния; см. Ограничения частоты.

Чтение результата

Успешная задача перечисляет свои файлы в output.assets[]. Три вещи об этом списке на эндпоинтах изображений:

  • В нём может быть больше одного файла. Если num_outputs больше единицы, на каждую картинку приходится свой ресурс. Пишите обработчик циклом с самого начала: обработчик, читающий assets[0], молча выбрасывает всё остальное, за что вы заплатили.
  • Расширение определяет mime, а не имя эндпоинта и не подсказка. Если эндпоинт выставляет output_format, кодировку выбирает это поле; если нет — кодировка своя у эндпоинта, и mime остаётся единственным способом узнать, что вы получили.
  • У файла, который ещё копируется, стоит pending: true и нет url. Результаты копируются в собственное хранилище SpicyAPI прежде чем быть выданными, и это занимает несколько секунд после того, как задача сообщила об успехе. Перечитайте задачу, а не считайте отсутствие ссылки сбоем.

Ссылки на скачивание живут около двадцати минут и не требуют API-ключа; за новой просто перечитайте задачу. Сгенерированные файлы хранятся не дольше четырнадцати дней, меньше — если срок хранения вашей учётной записи короче. См. Результаты и скачивание и Хранение и уничтожение.

Как читать отказ

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

Сообщаются все проблемы, а не только первая. В сообщении они соединены через ; , так что один отклонённый расчёт может перечислить всё, что не так с телом:

prompt: is required; loras.0.path: is required; loras.0.scale: must be at most 4; bogus: is not a supported field

Проблема внутри массива указывает на конкретный элемент. Указатель имеет вид поле.индекс.подполе, индекс отсчитывается с нуля:

loras.1.scale: must be at most 4

Передайте этот указатель тому, кто будет исправлять. Когда элементов несколько, знание того, какой именно вышел за границы, — это разница между правкой в одну строку и бинарным поиском.

Формы, которые вам встретятся:

СообщениеЧто значитЧто делать
X: is requiredX есть в корневом списке requiredДобавить. На редактирующих эндпоинтах это обычно prompt или поле картинки
X: is not a supported fieldX нет в properties; неизвестные поля отклоняются, а не игнорируютсяУбрать или взять написание этого эндпоинта — см. Размер вывода
X: must be one of: …Значение вне enumСообщение перечисляет допустимые значения; enum и default есть в схеме
X: must contain at least N itemsНиже minItemsМассив, которого эндпоинт требует, нельзя отправить пустым
X: must contain at most N itemsВыше maxItemsПрочитайте потолок именно этого эндпоинта; он не везде одинаков
X: must be an arrayЗначение не того типа, что объявленОбернуть одиночный элемент в список
X: must be at most N charactersВыше maxLengthУ подсказок тоже есть свой потолок на каждом эндпоинте
X.<n>.<sub>: is requiredПодполе элемента <n> обязательно и не имеет значения по умолчаниюДобавить только в этот элемент
X.<n>.<sub>: is not a supported fieldВ схеме элемента стоит additionalProperties: falseУбрать этот ключ из этого элемента
X.<n>.<sub>: must be at most NПодполе вышло за собственный диапазонОграничить; диапазоны заданы на подполе, а не на массив

Если отказ приходит там, где вы ждали успеха, обычная причина — устаревшая схема. Перечитайте каталог после 400 и никогда не держите постоянный снимок — Кэширование каталога объясняет, сколько живёт копия. Остальное разбирает Диагностика.

Что дальше

На этой странице