Уверен, у многих есть хотя бы один пет-проект, перспективный B2B SaaS, CRM, другими словами — сервис, который когда-нибудь обязательно выстрелит. В процессе разработки у многих стильных, модных и молодежных проектов рано или поздно появляется вопрос: «А что если добавить генерацию изображений или видео?». На первый взгляд все выглядит просто: берем Comfy UI, собираем workflow, выбираем модели — и готово. Локально все действительно можно собрать за один вечер.
Проблемы начинаются ровно в тот момент, когда решением пользуетесь не только вы. Первый пользователь ждет 20-30 секунд, второй стоит в очереди, остальные уже получили свои тайм-ауты, а вы внезапно ловите себя на том, что читаете документацию про Kubernetes и настраиваете балансировку запросов, вместо того, чтобы сконцентрироваться на развитии продукта... И вот тут уже развертывание Comfy UI кажется не настолько простой задачей как в начале.
Если вам знакома эта ситуация или вы только планируете добавить генеративный ИИ в свое приложение, эта статья для вас. Покажу, как превратить Comfy UI workflow в API, которым можно пользоваться из любого приложения, не поднимая инфраструктуру и не привлекая внимания санитаров.

Давайте сразу оговорюсь: это будет разбор типичной проблемы, с которой сталкиваются почти все, кто пытается перенести генеративный ИИ из ноутбука в продакшен. Сервис, который я продемонстрирую, лишь один из способов с этим разобраться. Пользоваться не принуждаем, просто показываем, что айсберг добавления асинхронной генерации в продукт под водой намного больше чем на поверхности, и в некоторых случаях формат управляемого сервиса оправдан. В каких случаях? Давайте по порядку.
За последние пару лет вокруг Comfy UI сформировался практически стандарт де-факто для построения сложных генеративных пайплайнов. Но сам по себе Comfy не решает вопросы масштабирования, очередей, хранения результатов и асинхронной обработки запросов — все это приходится выстраивать самостоятельно. В этой статье покажу, как можно решить вопрос и не мутировать в DevOps'а по дороге.
Почему Comfy UI — это только 20% решения
ComfyUI отлично решает задачу построения генеративного пайплайна. Можно комбинировать модели, подключать LoRA, ControlNet, собственные Python-ноды, генерировать изображения, видео, аудио и вообще строить довольно сложные workflow без написания большого количества кода. Но ComfyUI именно строит пайплайн. Он не отвечает на вопрос, как этот пайплайн превратить в сервис, которым одновременно пользуются десятки или сотни людей. А это уже совсем другой класс задач.
Представьте, что вы решили добавить генерацию изображений в свое приложение. Пока пользователей мало, все выглядит идеально.

Проходит неделя, о вашем сервисе узнают, приходит несколько десятков пользователей и картина резко меняется. Первый запрос занимает 30 секунд. Второй начинает ждать. Третий получает HTTP-timeout. У четвертого пользователя ничего не происходит. В этот момент становится понятно, что проблема не в Comfy UI runtime, а в инфраструктуре вокруг него.
Задача | Реальность |
Запустить Comfy UI | 15 минут |
Собрать workflow | Пара часов |
Интегрироваться с API | Несколько часов |
Хранить результаты | День–два на Object Storage и раздачу файлов клиенту |
Не терять запросы | Еще несколько дней: очередь, статусы задач, retry и защита от дублей |
Масштабировать при нагрузке | От нескольких дней до недель: воркеры, балансировка, несколько GPU-инстансов |
Следить, чтобы не упало | Постоянно: мониторинг, алерты, разбор падений GPU и переполнения очереди |
Почему генерация изображений и видео — это не HTTP-запрос
Большинство веб-приложений работают по очень простой схеме:

И это быстро, синхронно, предсказуемо. Однако с генеративными пайплайнами это не работает. Если обернуть ComfyUI в синхронный HTTP-endpoint («отправил запрос — жду картинку в ответе»), прокси или балансировщик оборвет соединение через свой лимит — часто 30–60 секунд, иногда 2 минуты. ComfyUI при этом продолжит работу: задача останется в очереди, а клиент уже получит ошибку. Даже относительно простая генерация может занимать десятки секунд, генерация сложных видео — минуты. Держать HTTP-соединение открытым все время не выйдет, поэтому при использовании схемы выше нас ждут:
таймауты;
повторные запросы от клиента;
потерянные задачи и результаты;
невозможность нормально масштабироваться и обрабатывать запросы в параллель.
Именно поэтому почти все продакшен-системы, генерирующие какие-то комплексные артефакты, используют другую архитектуру:

Клиент не ждет окончания генерации. Он получает id задачи и продолжает заниматься своими делами. Сейчас так работают практически все крупные сервисы генерации изображений.
Что обычно приходится строить самостоятельно
На этом этапе многие понимают, что сам Comfy UI занимает лишь небольшую часть всей системы. А вот вокруг нее уже приходится обустраивать целый инфраструктурный хоровод, чтобы она хоть как-нибудь заработала.
Задача | Если делать самостоятельно |
Очередь задач | Kafka, Redis, Rabbit MQ |
API | Собственный Backend |
Масштабирование | Собственный Kubernetes-кластер |
Хранение результатов | S3 Object Storage |
Мониторинг | Prometheus + Grafana |
Задачи и их статусы | Отдельная БД |
Нельзя сказать, что это сложно. Все это можно собрать самостоятельно. Вопрос только в том, сколько времени вы хотите потратить именно на инфраструктуру и готовы ли вообще погружаться в хитросплетения. Если ваша цель — разобраться в Kubernetes, это отличный путь. Если же задача — как можно быстрее встроить генерацию изображений в существующий продукт, хочется, чтобы все перечисленное уже было готово.
Именно здесь иногда имеет смысл использовать управляемый сервис.
Что дает Evolution ML Inference
В нашем случае роль такой инфраструктуры выполняет сервис Evolution ML Inference. Если коротко — он берет на себя все, что находится вокруг самого workflow.
Получается примерно такая картина:

То есть вам по-прежнему нужно собрать workflow. Но уже не нужно отдельно думать о том:
как запускать GPU;
как масштабировать обработчики;
где хранить результаты;
как отдавать статусы задач;
как организовать очередь.
Вместо этого можно дальше тестировать продуктовые гипотезы и улучшать уже свой продукт, а не навыки администрирования кластеров Kubernetes.
Давайте запустим Comfy UI
Предположим, что workflow уже работает локально. Следующая задача — превратить его в API. Процесс состоит буквально из нескольких шагов:
Шаг 1. Экспортируем workflow
В ComfyUI экспортируем готовый workflow через Export API. Получаем JSON-файл, который будет использоваться при создании ComfyRun.

Шаг 2. Создаем ComfyRun
Загружаем экспортированный workflow:

В этом же окне выбираем конфигурацию GPU для запуска.
Шаг 3. Настраиваем масштабирование
На этом шаге задается политика автоматического масштабирования сервиса — определяется, сколько реплик инференса система может запускать при росте нагрузки.
Для сценариев с асинхронной обработкой задач используется масштабирование по длине очереди. В параметре «Длина очереди» указывается, какое количество задач может одновременно приходиться на одну реплику инференса.

Шаг 4. Настраиваем Object Storage
Теперь выбираем S3 Bucket в Object Storage Cloud.ru. В него будут автоматически сохраняться результаты генерации. Здесь же можно указать токен Hugging Face для загрузки моделей, используемых внутри workflow.

Шаг 5. Связываем параметры API с workflow
Если локально вы вводили prompt вручную, теперь необходимо указать, какие параметры будут приходить через API, являющееся OpenAI-совместимым.
Например:
prompt;
negative_prompt;
seed;
width;
height.
После этого остается указать имя выходного файла и сопоставить используемые модели с репозиториями Hugging Face.

Также для корректной работы нужно указать в output-узлах поле, задающее префикс выходного файла:

Далее во всех узлах, использующих модели, необходимо их добавить из репозиториев Hugging Face.

После успешной конфигурации пайплайна нажимаем Создать.
Готово. Теперь ваш workflow превратился в полноценный сервис.
Работа с API
После создания ComfyRun автоматически становится доступна OpenAPI-спецификация. Можно открыть Scalar Docs и сразу протестировать сервис.
Работа с API выглядит максимально привычно.

Далее отправляем запрос на генерацию, после чего получаем task_id отправленной задачи:

После чего по этому task_id можно получить статус и результат генерации:

А теперь о том, как это интегрировать
Scalar Docs — отличный способ «потыкать» API руками. Но в реальном приложении генерация обычно живет в бэкенде: пользователь нажал кнопку, вы поставили задачу, вернули task_id, а дальше — polling или обновление UI.
Хорошая новость: интегрироваться можно через привычный OpenAI Python SDK. Ответы сервиса OpenAI-compatible — task_id, status, data[] с картинками. Единственный нюанс: endpoint не стандартный /v1/images/generations, а POST /api/v1/edits с multipart-полями. Но это пара строк кода.
Ниже — типичный сценарий на async/await, как в FastAPI или любом другом asyncio-бэкенде: отправили задачу → дождались → скачали результат.
Шаг 1: Настраиваем клиент
URL берем из ComfyRun, тот же, что в Scalar Docs, с префиксом /api/v1:
PROXY_BASE_URL=https://your-comfyrun.example.com/api/v1 PROXY_API_KEY=unused
API-ключ сервис не проверяет, но OpenAI SDK требует непустое значение — ставим unused:
import os import httpx from dotenv import load_dotenv from openai import OpenAI load_dotenv() BASE_URL = os.getenv("PROXY_BASE_URL") API_KEY = os.getenv("PROXY_API_KEY", "unused") client = OpenAI( base_url=BASE_URL, api_key=API_KEY, http_client=httpx.Client(timeout=600.0), ) http = client._client
Дальше все запросы идут через http — это httpx.AsyncClient внутри OpenAI SDK.
Шаг 2: Отправляем задачу на генерацию
Поля те же, что в Scalar: prompt, n, seed. Они маппятся в ваш ComfyUI workflow на стороне сервиса — JSON пайплайна собирать не нужно.
Ставим задачу в async-режиме — сервис сразу возвращает task_id, соединение не висит 20–30 секунд:
response = await http.post( "/edits", params={"async": "true"}, data={ "prompt": "minimalist logo of a mountain, flat design", "n": "2", "seed": "42", "response_format": "url", }, ) job = response.json() task_id = job["task_id"] print(f"Задача поставлена: {task_id}, статус: {job['status']}")
Если нужен быстрый прототип или скрипт «запустил и забыл» — можно без async=true. Тогда сервис сам дождется ComfyUI и вернет готовый результат в одном ответе:
result = http.post( "/edits", data={ "prompt": "minimalist logo of a mountain, flat design", "n": "1", "seed": "42", "response_format": "url", }, ).json() # result["status"] == "completed", result["data"] уже заполнен
Шаг 3: Ждем завершения и забираем артефакты
По task_id опрашиваем GET /tasks/{task_id}. Пока задача в работе — приходит статус и прогресс. Когда status станет completed, в data[] появятся ссылки на результат:
import asyncio TERMINAL = {"completed", "failed", "cancelled"} while True: response = await http.get( f"/tasks/{task_id}", params={"response_format": "url"}, ) task = response.json() print(task["status"], task.get("progress")) if task["status"] in TERMINAL: break await asyncio.sleep(2) if task["status"] != "completed": raise RuntimeError(task.get("reason", task["status"]))
Скачиваем файлы по presigned URL из ответа:
async with httpx.AsyncClient() as downloader: for i, image in enumerate(task["data"]): url = image["presigned_url"] content = (await downloader.get(url)).content with open(f"output_{i}.png", "wb") as f: f.write(content)
Если удобнее работать с base64 — передайте response_format=b64_json при генерации или при GET, и декодируйте data[].b64_json.
Все вместе одним листингом
import asyncio import httpx from openai import AsyncOpenAI async def generate_image(prompt: str, n: int = 1, seed: int = 42) -> list[str]: client = AsyncOpenAI( base_url="https://your-comfyrun.example.com/api/v1", api_key="unused", http_client=httpx.AsyncClient(timeout=600.0), ) http = client._client try: # 1. Ставим задачу job = (await http.post("/edits", params={"async": "true"}, data={ "prompt": prompt, "n": str(n), "seed": str(seed), "response_format": "url", })).json() task_id = job["task_id"] # 2. Ждём завершения while True: task = (await http.get( f"/tasks/{task_id}", params={"response_format": "url"}, )).json() if task["status"] in {"completed", "failed", "cancelled"}: break await asyncio.sleep(2) if task["status"] != "completed": raise RuntimeError(task.get("reason", task["status"])) # 3. Скачиваем результат paths = [] async with httpx.AsyncClient() as downloader: for i, img in enumerate(task["data"]): path = f"output_{i}.png" content = (await downloader.get(img["presigned_url"])).content with open(path, "wb") as f: f.write(content) paths.append(path) return paths finally: await client.close() if __name__ == "__main__": paths = asyncio.run(generate_image("watercolor fox in autumn forest")) print("Saved:", paths)
Как это ложится в FastAPI
Тот же код — в endpoint: пользователь отправляет промпт, вы возвращаете task_id, фронт polling'ом дергает статус. Или сразу await-ите результат, если нужен синхронный UX:
from fastapi import FastAPI import httpx from openai import AsyncOpenAI app = FastAPI() client = AsyncOpenAI( base_url="https://your-comfyrun.example.com/api/v1", api_key="unused", http_client=httpx.AsyncClient(timeout=600.0), ) @app.post("/generate") async def generate(prompt: str): http = client._client job = (await http.post("/edits", params={"async": "true"}, data={ "prompt": prompt, "n": "1", "seed": "42", "response_format": "url", })).json() return {"task_id": job["task_id"], "status": job["status"]}
Инфраструктура — очереди, ComfyUI, хранение артефактов — остается на стороне сервиса. С вашей стороны — три HTTP-вызова и знакомый OpenAI SDK. Можно вернуться к тому самому пет-проекту, который «обязательно выстрелит» и добавить генерацию картинок за вечер, а не за спринт.
Где посмотреть изображения
После завершения генерации результаты доступны сразу в двух местах.
В веб-интерфейсе — в разделе Галерея.
В Object Storage — по пути, соответствующему идентификатору задачи.
Это удобно, если изображения дальше используются другими сервисами или сразу попадают в CDN.

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


Enterprise-сценарии
Маркетинг, дизайн, HR-бренд внутри крупных компаний часто хотят генерировать контент по шаблону. А ИТ-отдел не всегда хочет поддерживать для них инфраструктуру. Решение: завернуть workflow в API, отдать команде простой интерфейс и снять с себя операционную нагрузку.

AI-фоторедактор


GameDev, оживление персонажей и концепт-арт
Я просто взял несколько фоток, сделанных прямо в офисе и наскоро соорудил workflow, чтобы их оживить.



Во всех случаях интеграция выглядит одинаково. Отправляем запрос. Получаем task_id. Через некоторое время забираем готовый результат.
Что в итоге
ComfyUI — рабочий инструмент для пайплайнов, но добавить генерацию в продукт значит не «поднять ComfyUI», а встроить долгую асинхронную операцию: очередь, статусы, хранение результатов и нормальный UX при десятках одновременных пользователей. Разница между демо и продакшеном — не в модели, а в том, кто поддерживает инфраструктуру вокруг workflow: ваша команда или, например, платформа.
Теперь у вас достаточно информации, чтобы сделать осознанный выбор. Свой Kubernetes и GPU-кластер — если нужен полный контроль и есть команда, которая готова этим заниматься. Управляемый сервис — если цель быстро добавить генерацию в продукт, а не превратиться в DevOps по дороге.
Пользователю все равно, сколько под капотом Redis и GPU. Ему важно: нажал — получил результат, без зависаний и тайм-аутов. Выбор инфраструктуры определяет на что уйдут следующие недели вашей жизни: на фичи продукта или на то, чтобы генерация не падала под нагрузкой.
Уверен, половина читателей уже скажет «мы все это на одном VPS подняли за вечер». Если так — добро пожаловать в комменты: интересно, как это живет, когда пользователей не трое, а тридцать. Ну и смело делитесь альтернативами описанному сервису, чтобы те, кто придет в статью позже, уже могли выбирать из нескольких вариантов под свои запросы. Всем бобра!
