Генерация картинок по шаблону: Genviso и BetterToken

Как отделить визуальный поиск от серверной генерации, превратить удачный prompt в шаблон и безопасно запустить выпуск изображений через API.

Удачная генерация ещё не становится производственным процессом. Для одного изображения можно несколько раз переписать prompt, открыть ответы и выбрать лучший вариант вручную. При сотнях SKU тот же подход превращается в длинную цепочку дорогих проб. Каждое изменение света, ракурса или материала запускает новый API-запрос; причины удачного результата остаются в голове у автора.

Рабочую схему удобнее разделить на два контура. Сначала команда проверяет визуальную идею и фиксирует повторяемые правила. Затем backend подставляет бизнес-данные в утверждённый шаблон, отправляет запросы, сохраняет результаты и учитывает ошибки. Творческий поиск остаётся вне очереди production-задач. Серверный код не приходится менять после каждой правки композиции.

Почему отладка prompt в коде быстро выходит из-под контроля

Слишком много связанных переменных

Модель одновременно реагирует на объект, окружение, свет, положение камеры, материал, глубину резкости и палитру. Для фотографии флакона сыворотки заметно различаются, например:

  • фронтальный кадр и съёмка под углом 45°;
  • жёсткий направленный свет и мягкий рассеянный;
  • стекло с выраженными отражениями и матовая поверхность;
  • фон из травертина, металла или однотонной бумаги;
  • макросъёмка на 85 mm и широкий угол.

Если менять несколько параметров за раз, команда не понимает, какая формулировка улучшила результат. Если менять по одному, число запросов быстро растёт. Backend через BetterToken может выполнять Image API-вызовы по шаблону, но преждевременный запуск очереди только размножит непроверенную визуальную гипотезу.

У разных задач разная визуальная грамматика

Карточке товара нужны читаемый силуэт, контролируемые отражения и место под верстку. У 3D-иллюстрации другие требования к форме и материалу. Постеру для соцсетей важны иерархия, контраст и безопасные зоны. Универсальный prompt для всех этих случаев обычно обрастает противоречивыми прилагательными.

Практичнее хранить несколько семейств шаблонов:

skincare_product luxury_watch food_photography 3d_illustration social_poster

Каждое семейство задаёт собственные обязательные поля и критерии приёмки. Программа выбирает подходящий шаблон по категории, а затем подставляет данные конкретного товара или кампании.

Исследование и production требуют разных правил

Во время поиска допустимы десятки вариантов и субъективное сравнение. В production важны предсказуемый контракт, версия шаблона, ограниченные повторы, идентификатор задачи и понятный результат проверки.

Плохая схема выглядит так:

изменить prompt в коде → отправить API-запрос → открыть изображение → снова изменить код → снова отправить запрос

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

Архитектура процесса: от визуальной гипотезы до файла в CMS

На этапе визуального поиска команда работает в Genviso: сравнивает варианты через визуальную галерею prompts, проверяет композицию, свет и стиль, затем сохраняет структуру удачного prompt. На серверном этапе приложение обращается через BetterToken к OpenAI-compatible Base URL с собственным API Key пользователя, подставляет данные, вызывает доступную модель и записывает результат. Между этими этапами передаётся версия Prompt Template, а не выбранная картинка и не набор устных пожеланий.

Чтобы проверить эту границу до подключения очереди, создайте собственный API Key, выполните один контрольный запрос с утверждённым шаблоном и сразу сопоставьте модель, status и фактическое списание в Dashboard. Такой тест подтверждает серверный маршрут, не превращая визуальный поиск в серию production-запросов.

визуальный поиск ↓ проверка Prompt Template ↓ фиксация переменных и ограничений ↓ подстановка данных из PIM / CMS / базы SKU ↓ Image API-вызов через backend ↓ сохранение файла, статуса и метаданных ↓ визуальная приёмка результата

У каждого перехода должен быть проверяемый артефакт:

ЭтапРезультатУсловие перехода
Визуальный поискНабор удачных и неудачных вариантовПонятно, какие параметры влияют на результат
Проверка шаблонаPrompt с именованными переменнымиШаблон работает на нескольких типичных объектах
ИнтеграцияФункция рендеринга и схема входных данныхВсе обязательные поля валидируются до API-вызова
Тестовый запросОдин сохранённый файл и запись запросаФайл открывается, модель и status совпадают с ожиданием
ProductionЗадача с template_id, версией и job_idПовторы ограничены, результат связан с исходным SKU

Этап 1. Превращаем визуальное решение в шаблон

Для студийной фотографии косметики исходная структура может выглядеть так:

Commercial studio product photography of {subject}. Environment: {environment} Visual style: {visual_style} Lighting: {lighting} Composition: {composition} Color palette: {color_palette} Crisp reflections, premium material texture, high-end commercial editorial photography.

Здесь стабилизируется порядок описания и набор визуальных измерений. Значения меняются отдельно:

subject environment visual_style lighting composition color_palette

До передачи шаблона разработчикам полезно зафиксировать ещё четыре вещи:

  1. Обязательные поля. Без subject или composition запрос не должен уходить в API.
  2. Допустимые значения. Если ракурс выбирается из трёх вариантов, лучше хранить enum, чем свободный текст из CMS.
  3. Запрещённые сочетания. Например, прозрачная упаковка на зеркальном фоне может потребовать отдельного шаблона.
  4. Критерии приёмки. Силуэт товара читается, логотип не искажён, объект не обрезан, фон подходит для дальнейшей верстки.

Сам prompt можно хранить рядом с машинно-читаемым контрактом:

{ "template_id": "skincare_product_v3", "required_variables": [ "subject", "environment", "visual_style", "lighting", "composition", "color_palette" ], "output_size": "1024x1024" }

Версия в template_id нужна для воспроизводимости. Если дизайнер меняет свет или композицию, новые задачи получают следующую версию; уже созданные материалы остаются связанными со старой.

Этап 2. Подключаем шаблон к backend

Для первого теста достаточно официального Python SDK openai, собственного BetterToken API Key и актуального Model ID из текущей документации Image API. Ключ и Model ID хранятся в окружении:

python -m pip install openai export BETTERTOKEN_API_KEY="your_api_key_here" export BETTERTOKEN_IMAGE_MODEL="current_image_model_id"

Не добавляйте настоящий ключ в репозиторий, prompt, скриншот или логи. Для production используйте менеджер секретов и отдельные ключи для разных приложений или окружений.

Следующий пример рендерит шаблон, отправляет один запрос и сохраняет PNG из b64_json:

import base64 import os from pathlib import Path from typing import Mapping from openai import OpenAI client = OpenAI( base_url="https://www.bettertoken.ai/v1", api_key=os.environ["BETTERTOKEN_API_KEY"], ) def render_product_prompt(variables: Mapping[str, str]) -> str: return f""" Commercial studio product photography of {variables['subject']}. Environment: {variables['environment']} Visual style: {variables['visual_style']} Lighting: {variables['lighting']} Composition: {variables['composition']} Color palette: {variables['color_palette']} Crisp reflections, premium material texture, high-end commercial editorial photography. """.strip() product = { "subject": ( "frosted amber glass serum bottle " "with a minimalist gold dropper" ), "environment": ( "organic travertine pedestal " "surrounded by subtle water ripples" ), "visual_style": "high-end botanical skincare editorial", "lighting": ( "warm directional morning rim light " "with soft diffused fill" ), "composition": ( "centered 85mm macro product photography " "with shallow depth of field" ), "color_palette": "earthy amber, warm beige and subtle gold", } response = client.images.generate( model=os.environ["BETTERTOKEN_IMAGE_MODEL"], prompt=render_product_prompt(product), size="1024x1024", n=1, ) image_base64 = response.data[0].b64_json if not image_base64: raise RuntimeError("Image API response does not contain b64_json") output_path = Path("serum-product.png") output_path.write_bytes(base64.b64decode(image_base64)) print(f"Saved: {output_path}")

Синтаксис client.images.generate(...) и декодирование b64_json соответствуют текущему контракту OpenAI Python SDK. Model ID вынесен в BETTERTOKEN_IMAGE_MODEL, поскольку список доступных моделей и параметры нужно проверять перед запуском. Замена модели тогда не требует переписывать Prompt Template и бизнес-логику.

Минимальный контур пакетной задачи

Ниже — намеренно явный псевдокод интеграционного слоя. save_job, generate_image и ApiError обозначают адаптеры вашего хранилища и API-клиента, а не дополнительные методы SDK. Важна последовательность состояний и решений:

MAX_ATTEMPTS = 3 RETRYABLE_STATUS = {429, 500, 502, 503, 504} for sku in sku_rows: variables = validate_variables(sku) # до API-вызова prompt = render_product_prompt(variables) job_id = uuid4().hex prompt_hash = sha256(prompt.encode()).hexdigest() save_job( job_id=job_id, sku_id=sku["id"], template_id="skincare_product_v3", prompt_hash=prompt_hash, status="pending", ) for attempt in range(1, MAX_ATTEMPTS + 1): save_job(job_id=job_id, status="running", attempt=attempt) try: result = generate_image(prompt) except ApiError as error: if error.status_code in {400, 401}: save_job(job_id=job_id, status="failed", error_code=error.status_code) break # сначала исправить данные, Key или конфигурацию if error.status_code not in RETRYABLE_STATUS: save_job(job_id=job_id, status="failed", error_code=error.status_code) break if attempt == MAX_ATTEMPTS: save_job(job_id=job_id, status="failed", error_code=error.status_code) break sleep(min(2 ** attempt, 8)) continue except TimeoutError: save_job(job_id=job_id, status="unknown", error_code="timeout") break # проверить Dashboard и хранилище до повторной отправки if not result.b64_json: save_job(job_id=job_id, status="failed", error_code="empty_output") break output_path = persist_png(job_id, result.b64_json) save_job( job_id=job_id, status="succeeded", output_path=output_path, model=result.model, attempt=attempt, ) break

Локальный job_id связывает SKU, шаблон и файл, но сам по себе не делает запрос идемпотентным. После timeout статус остаётся unknown: сначала найдите запрос по времени в Dashboard и проверьте объектное хранилище, затем решите, нужна ли одна повторная отправка. Так очередь не маскирует возможный дубликат.

Порядок диагностики

СимптомЧто проверить сначалаИсправлениеКак перепроверить
400Обязательные переменные, текущий Model ID, поддерживаемый sizeИсправить данные или параметр; не повторять тот же запрос автоматическиЗапустить один контрольный SKU и открыть PNG
401Загружена ли переменная Key, принадлежит ли ключ пользователю и подходит ли текущему протоколуЗаменить или пересоздать Key, не выводя его в логОтправить минимальный запрос и найти его status в Dashboard
429Текущую конкуренцию и частоту задачОстановить набор новых задач, снизить конкуренцию, применить ограниченный backoffСначала пропустить один запрос, затем постепенно вернуть нагрузку
5xxВремя запроса и число уже сделанных попытокПовторять только до MAX_ATTEMPTS; при сохранении ошибки зафиксировать время и statusПроверить один новый запрос после задержки, не меняя утверждённый шаблон
timeoutDashboard и хранилище: результат мог быть создан без ответа клиентуОставить unknown, не считать запрос автоматически неуспешнымЕсли записи и файла нет, разрешить одну новую отправку с тем же локальным job_id
Пустой b64_json или ошибка декодированияТекущий формат ответа, модель и параметры по документацииСохранить код ошибки без содержимого Key, исправить разбор или конфигурациюПовторить на одном SKU и убедиться, что PNG декодируется и открывается

Что добавить перед пакетной генерацией

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

Валидируйте данные до запроса

Пустой material, неожиданная разметка в product_name или свободный текст вместо утверждённой палитры меняют prompt. Проверяйте обязательные поля, длину строк и допустимые значения до обращения к модели. Сохраняйте итоговый prompt hash, template_id и идентификатор SKU рядом с задачей.

Ограничьте повторы

Повтор после timeout может создать ещё одно изображение, даже если приложение не успело получить первый ответ. Задайте конечное число попыток, используйте задержку и помечайте каждый запуск собственным job_id. Не запускайте бесконечный retry на 400, 401 или ошибке модели: сначала исправьте данные, ключ или конфигурацию.

Разделите техническую и визуальную приёмку

HTTP 200 и корректный PNG подтверждают технический успех. Композицию, искажение товара и соответствие бренду оценивают отдельно. Автоматическая задача сохраняет файл и метаданные; следующий этап применяет визуальные критерии шаблона.

Сопоставьте запрос с учётом использования

После контрольной генерации найдите запрос в Dashboard по времени. Сверьте модель, status и соответствующее списание; доступные поля использования показывают input, output и cache Token. Dashboard предназначен для метаданных использования и расхода, не для хранения полного prompt или ответа. Перед расчётом бюджета берите текущую ставку со страницы моделей и цен. Фактический расход тестового вызова смотрите в записи запроса.

Пример потока для каталога товаров

Для магазина с несколькими сотнями SKU задача может проходить через такую цепочку:

PIM / база SKU ↓ категория товара → template_id ↓ название / материал / цвет / фон ↓ валидация обязательных полей ↓ рендер Prompt Template ↓ задача генерации с job_id ↓ Image API ↓ объектное хранилище ↓ визуальная приёмка ↓ CMS / медиатека

В этой схеме prompt становится версионируемым производственным объектом. Можно увидеть, какой шаблон создал конкретный файл, сравнить долю отклонённых результатов по версиям и откатить неудачное изменение без правки всей интеграции.

Контрольный список перед запуском

  • Prompt Template проверен на типичных товарах и сложных пограничных случаях.
  • Для шаблона заданы template_id, обязательные переменные и критерии приёмки.
  • API Key хранится вне исходного кода и не попадает в логи.
  • Model ID читается из окружения или конфигурации, а не зашит в бизнес-логику.
  • Один тестовый запрос создаёт открываемый файл ожидаемого размера.
  • Ошибки 400/401 требуют исправления данных или конфигурации до нового запроса.
  • Для 429/5xx задан ограниченный retry.
  • Каждая задача связана с SKU, job_id, версией шаблона и местом хранения результата.
  • Техническая проверка и визуальная приёмка выполняются отдельно.
  • Модель, status и расход контрольного запроса сверены в Dashboard.

Как разделение ролей упрощает совместную работу

Genviso отвечает за интерактивный контур: быстрый поиск визуального направления, сравнение prompts и проверку шаблона до передачи разработчикам. BetterToken отвечает за серверный контур: API Key, OpenAI-compatible подключение, вызов текущей доступной модели и запись использования. Команды согласуют один контракт — Prompt Template с переменными, версией и критериями приёмки.

Чтобы перенести утверждённый шаблон в работающий backend и проверить расход на одном типичном SKU, создайте собственный API Key, выполните минимальный запрос по Image API reference, затем сверьте модель, status и списание в Dashboard до подключения очереди. Такая граница оставляет визуальный поиск в интерактивном контуре, а production-код занимается воспроизводимым исполнением.

Готовы оптимизировать LLM workflow?

Подключите единый API, управляйте ключами и контролируйте расходы на AI-модели в BetterToken.