# 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 ``` ## Запуск ```bash 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: ```bash 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 `): ```json {"prompt": "a red cube", "model": "z-image-turbo", "n": 1, "size": "1024x1024", "seed": 123} ``` - `n` поддерживается только 1 (иначе 400); `seed` необязателен — без него случайный (повтор с тем же seed + prompt даёт идентичное изображение). - Ответ: `{"created": , "data": [{"b64_json": ""}]}`. `POST /v1/images/edits` — два транспорта: - multipart: поля `image` (файл PNG/JPEG) + `prompt` (+ необязательные `model`, `seed`); - JSON: `{"image": "", "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` — без ключа. ```bash # генерация 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 ``` ```python 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рирует. ## Тесты ```bash 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=<ключ> ```