OpenAI-compatible adapter for ComfyUI image generation and editing
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# 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 <ADAPTER_API_KEY>`):
|
||||
|
||||
```json
|
||||
{"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` — без ключа.
|
||||
|
||||
```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=<ключ>
|
||||
```
|
||||
Reference in New Issue
Block a user