comfy-adapter

OpenAI-совместимый адаптер поверх ComfyUI: POST /v1/images/generations (генерация, модель z-image-turbo) и POST /v1/images/edits (редактирование, модель qwen-image-edit). Адаптер подставляет параметры запроса в шаблоны workflow, отправляет их в ComfyUI, дождаться выполнения и возвращает изображение как data[0].b64_json (base64 PNG).

Client --(OpenAI contract)--> comfy-adapter --(ComfyUI HTTP API)--> ComfyUI
                              /v1/images/generations              POST /prompt
                              /v1/images/edits                    POST /upload/image
                                                                  GET  /history/{id}
                                                                  GET  /view

Запуск

python -m venv .venv && .venv/bin/pip install -e .          # Windows: .venv\Scripts\
cp .env.example .env                                         # заполните COMFY_BASE_URL, ADAPTER_API_KEY
uvicorn app.main:app --host 0.0.0.0 --port 8000

Или Docker:

docker build -t comfy-adapter .
docker run -p 8000:8000 --env-file .env comfy-adapter

При старте адаптер загружает workflow_create.json / workflow_edit.json, локально проверяет графы на битые ссылки (битый шаблон — падение старта со списком проблемных узлов) и прогоняет серверную проверку POST /prompt?front=false (отключается: VALIDATE_TEMPLATES_ON_SERVER=false). Логи стартовой проверки видны с uvicorn --log-level info.

Переменные окружения

Переменная Default Назначение
COMFY_BASE_URL http://192.168.0.3:8188 Адрес ComfyUI
ADAPTER_API_KEY — (обязательна) Статический Bearer-ключ клиентов
EXECUTION_TIMEOUT 300 Секунд ожидания результата ComfyUI
POLL_INTERVAL 1.5 Интервал опроса /history, сек
MAPPING_FILE — JSON-файл, переопределяющий карту маппинга
VALIDATE_TEMPLATES_ON_SERVER true Проверять шаблоны на сервере при старте

Контракт

POST /v1/images/generations (JSON, Authorization: Bearer <ADAPTER_API_KEY>):

{"prompt": "a red cube", "model": "z-image-turbo", "n": 1, "size": "1024x1024", "seed": 123}
  • n поддерживается только 1 (иначе 400); seed необязателен — без него случайный (повтор с тем же seed + prompt даёт идентичное изображение).
  • Ответ: {"created": <unix>, "data": [{"b64_json": "<base64 PNG>"}]}.

POST /v1/images/edits — два транспорта:

  • multipart: поля image (файл PNG/JPEG) + prompt (+ необязательные model, seed);
  • JSON: {"image": "<base64>", "prompt": "...", "model": "qwen-image-edit", "seed": 5}.

Поле size в edits принимается, но не влияет на выход: итоговый размер — ~1 Мп с аспектом входного изображения (это делает сам workflow).

Ошибки — в формате OpenAI (error.message/type/param/code): 400 — невалидное тело; 401 — ключ отсутствует/неверен; 502 — ComfyUI недоступен, отверг workflow (comfyui_validation_failed), ошибко исполнения (comfyui_execution_failed) или таймаут (comfyui_timeout). GET /health — без ключа.

# генерация
curl -s http://localhost:8000/v1/images/generations \
  -H "Authorization: Bearer $ADAPTER_API_KEY" -H "Content-Type: application/json" \
  -d '{"prompt":"a red cube on white background","size":"1024x1024","seed":42}' | jq -r .data[0].b64_json | base64 -d > out.png

# редактирование
curl -s http://localhost:8000/v1/images/edits \
  -H "Authorization: Bearer $ADAPTER_API_KEY" \
  -F image=@input.png -F prompt="make the background deep blue" | jq -r .data[0].b64_json | base64 -d > edited.png
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="sk-...")
r = client.images.generate(model="z-image-turbo", prompt="a red cube", size="1024x1024", n=1)
open("out.png", "wb").write(__import__("base64").b64decode(r.data[0].b64_json))

Карта маппинга

Параметр -> узел workflow (правится без кода через MAPPING_FILE):

Параметр workflow_create.json workflow_edit.json
prompt 83:27.inputs.text 115:111.inputs.prompt
negative — (ConditioningZeroOut) 115:110.inputs.prompt (всегда "")
size 83:13.inputs.width/height — (размер = ~1 Мп входа)
seed 83:3.inputs.seed 115:3.inputs.seed
image — upload -> 78.inputs.image

Починка шаблонов относительно экспорта OpenWebUI

Выгруженные из OpenWebUI шаблоны не проходили валидацию ComfyUI (KeyError по несуществующим узлам). Исправления в репозиторных файлах:

  • workflow_create.json: ссылки [83:43, 0]/[83:43, 1] (узла нет в графе) переподключены на существующие лоадеры: 83:3.inputs.model -> [83:28, 0] (UNETLoader z_image_turbo), 83:27.inputs.clip -> [83:30, 0] (CLIPLoader qwen_3_4b).
  • workflow_edit.json: 115:66.inputs.model -> [115:89, 0] вместо несуществующего 115:117 — LoRA Qwen-Image-Edit-2509-Lightning-4steps (нода 115:89, в экспорте «висячая») подключена в цепочку модели.

Оба шаблона проходят POST /prompt?front=false и реально генерируют (проверено на ComfyUI 0.37.0 / V100).

Замечания по продакшену

  • Ответ синхронный: соединение висит до конца генерации. За reverse proxy поднимите proxy_read_timeout (nginx) / эквивалент выше, чем EXECUTION_TIMEOUT.
  • Фоновые интеграции (OpenWebUI) продолжают работать с ComfyUI напрямую — адаптер ничего не migрирует.

Тесты

pip install -e ".[dev]"
pytest                    # unit + mocked-ComfyUI (respx)
pytest -m live            # живые прогоны: нужен запущенный адаптер и ADAPTER_LIVE=1,
                          # ADAPTER_URL=http://127.0.0.1:8000 ADAPTER_KEY=<ключ>
S
Description
OpenAI совместимый адаптер для подключения к ComfyUI
Readme 94 KiB
Languages
Python 99.5%
Dockerfile 0.5%