7.0 KiB
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— LoRAQwen-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=<ключ>