26 KiB
Checklist реализации MpakoImageAIbot
Источник: plan.md. Бот на python-telegram-bot v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в SQLite.
Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD).
0. Допущения и решения по умолчанию
| Вопрос | Решение по умолчанию |
|---|---|
| HTTP-клиент | httpx.AsyncClient, без блокирующего requests |
| Очередь | Собственная очередь в приложении, по умолчанию 1 воркер; в ComfyUI уходит не более 1 задачи одновременно |
| Прогресс | WebSocket /ws + fallback-опрос /history/{prompt_id} |
| Один активный запрос на пользователя | Проверка в памяти + частичный уникальный индекс в SQLite |
Задача в статусе running при рестарте |
Пометить failed (не перезапускать автоматически) |
Задачи queued при рестарте |
Восстановить из БД и поставить в очередь заново |
| i2i без подписи | Отклонить и попросить промпт (не использовать дефолтный) |
| Чаты | Приватные; user_id считается id чата |
| Workflow | JSON в API-формате (экспорт «Save (API Format)» из ComfyUI) |
| Язык интерфейса | Русский |
Согласовано: 1 воркер, задача running при рестарте → failed, workflow готовим вместе (см. блок 1).
1. Подготовка workflow и окружения (до кода)
1.1 Экспорт workflow
- Открыть ComfyUI, включить Settings → Dev mode Options, чтобы появился пункт Save (API Format).
- Собрать t2i-граф:
CheckpointLoaderSimple → CLIPTextEncode (positive) → CLIPTextEncode (negative) → KSampler → VAEDecode → SaveImage. - Собрать i2i-граф:
LoadImage → VAEEncode → KSampler (denoise 0.4–0.7) → VAEDecode → SaveImage. - Экспортировать оба графа кнопкой Save (API Format) в
workflows/txt2img_api.jsonиworkflows/img2img_api.json. - Убедиться, что в JSON только API-узлы (
class_type,inputs), без UI-метаданных и безextra-полей. - Определить ID нужных узлов. Удобно: выделить узел →
Properties→Node ID, либо найти поclass_typeв JSON (CLIPTextEncode,KSampler,LoadImage). - Заполнить
workflows/mapping.json(ID ниже — примеры, заменить на реальные):
{
"txt2img": {
"positive": "6",
"negative": "7",
"seed": "3",
"checkpoint": "4"
},
"img2img": {
"positive": "6",
"negative": "7",
"seed": "3",
"input_image": "10",
"denoise": "3"
}
}
- Проверить t2i вручную: отправить JSON на
POST /promptи получитьprompt_id, затем забрать результат через/historyи/view. - Проверить i2i: загрузить тестовое фото через
/upload/image, подставить имя вLoadImage, выполнить генерацию. - Проверить i2i с фото произвольного размера (портрет/ландшафт); при необходимости добавить
ImageScaleв граф. - Зафиксировать дефолтные
steps,cfg,denoise, negative prompt и размер изображения.
1.2 Окружение
- Убедиться, что Python 3.10+ доступен локально и в Docker-образе.
- Проверить сетевую доступность ComfyUI:
GET http://IP_WINDOWS:PORT/system_stats. - Получить токен бота у
@BotFather. - Определить значения лимитов: максимальная длина промпта, максимальный размер фото, размер очереди, таймаут генерации.
- Решить, где будет запускаться бот (локально / Docker) и как он видит ComfyUI (LAN, VPN,
host.docker.internal).
DoD: есть токен, два валидных workflow JSON, заполненный mapping.json; тестовый POST /prompt возвращает prompt_id, результат скачивается.
2. Каркас проекта и конфигурация
- Создать структуру:
.
├── bot.py
├── comfy_client.py
├── db.py
├── queue_manager.py
├── config.py
├── workflows/
│ ├── txt2img_api.json
│ ├── img2img_api.json
│ └── mapping.json
├── storage/ # создаётся автоматически, в .gitignore
├── data/ # SQLite, в .gitignore
├── tests/
├── requirements.txt
├── requirements-dev.txt
├── Dockerfile
├── .dockerignore
├── .env.example
├── .gitignore
└── README.md
requirements.txt:python-telegram-bot,httpx,aiosqlite,websockets,python-dotenv(версии — диапазонами, например>=21,<23).requirements-dev.txt:pytest,pytest-asyncio,respx.config.py: dataclass/Settings, загрузка.env, валидация обязательных полей, вычисление WS-URL из base URL.- Переменные окружения:
BOT_TOKEN,COMFY_BASE_URL,WORKFLOW_T2I,WORKFLOW_I2I,WORKFLOW_MAPPING,STORAGE_DIR,DB_PATH,WORKER_CONCURRENCY=1,MAX_QUEUE_SIZE,ALLOWED_USER_IDS,PROGRESS_INTERVAL,GENERATION_TIMEOUT,LOG_LEVEL. - Настроить логирование в stdout: уровень из env, формат с timestamp/level/logger, никогда не логировать токен и содержимое
.env. .env.exampleсо всеми переменными и комментариями..gitignore:.env,storage/,data/,__pycache__/,.pytest_cache/.- Проверить импорт конфига и падение с понятной ошибкой при отсутствии
BOT_TOKEN.
DoD: python -c "from config import get_settings; print(get_settings())" работает; секреты не попадают в репозиторий.
3. Слой хранения (db.py)
- Реализовать
init_db(): подключениеaiosqlite,PRAGMA journal_mode=WAL,PRAGMA busy_timeout,PRAGMA foreign_keys=ON,row_factory. - Создать таблицу
requestsпо плану:
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
timestamp TEXT NOT NULL,
prompt_text TEXT NOT NULL,
input_file_path TEXT,
output_file_path TEXT,
status TEXT NOT NULL DEFAULT 'queued'
CHECK (status IN ('queued', 'running', 'done', 'failed')),
comfy_prompt_id TEXT
);
- Добавить расширения схемы (согласовать):
mode TEXT NOT NULL CHECK (mode IN ('t2i','i2i')),error_text TEXT,finished_at TEXT,seed INTEGER. - Индексы:
(user_id, status),(status, id). - Частичный уникальный индекс «один активный запрос на пользователя»:
CREATE UNIQUE INDEX IF NOT EXISTS uq_requests_one_active
ON requests(user_id) WHERE status IN ('queued', 'running');
- CRUD:
create_request,get_request,get_active_by_user,set_status,set_comfy_prompt_id,set_output_path,set_error,list_queued,count_ahead,mark_running_as_failed. - Все временные метки — ISO 8601 UTC (
datetime.now(timezone.utc).isoformat()). - Атомарность: создание запроса и проверка активного — в одной транзакции; при конфликте unique-индекса возвращать понятную ошибку
UserAlreadyQueued. - Восстановление при старте:
queuedчитаются поid,running→failedс текстом «прервано перезапуском». - Тесты: создание, смена статусов, запрет второго активного запроса, выборка очереди, восстановление.
DoD: тесты БД зелёные; повторный init_db() не ломает существующую БД.
4. Клиент ComfyUI (comfy_client.py)
- Класс
ComfyClientс ленивой инициализациейhttpx.AsyncClientи явнымclose(). submit_workflow(workflow, client_id) -> prompt_id:POST /promptс телом{"prompt": workflow, "client_id": client_id}.- Обработка ответа
/prompt: HTTP-ошибки,error,node_errors— подниматьComfyErrorс человекочитаемым текстом. upload_image(path, request_id) -> image_ref:POST /upload/image(multipart), уникальное имя файла,type=input; вернуть имя/подпапку дляLoadImage.get_history(prompt_id) -> dict:GET /history/{prompt_id}; извлекать список изображений изoutputs[*]["images"].download_image(filename, subfolder, type, dest_path):GET /viewпотоково, запись через временный файл +os.replace.wait_for_result(prompt_id, timeout): fallback-опрос истории с интервалом 2 с; таймаут →ComfyTimeout.- WebSocket-прогресс:
ws://.../ws?clientId=..., подключение доPOST /prompt, чтобы не потерять ранние события. - Разбор событий:
progress(value/max, при наличииprompt_id— фильтровать),executing,execution_success,execution_error,status. - Переподключение WS с backoff; при недоступности WS — не падать, переходить на polling.
- Таймауты: connect 10 с, запросы 30 с, ожидание генерации —
GENERATION_TIMEOUT. - Не ретраить слепо
POST /prompt(риск дублей); ретраить только идемпотентные GET/upload. - Типы ошибок:
ComfyConnectionError,ComfyHTTPError,ComfyExecutionError,ComfyTimeout. - Тесты с
respx/fake-сервером: успешный submit,node_errors, таймаут, скачивание, парсинг progress.
DoD: отдельный скрипт/тест отправляет тестовый workflow на реальный ComfyUI, получает prompt_id, progress и сохраняет картинку.
5. Шаблоны workflow
- Положить
txt2img_api.jsonиimg2img_api.jsonвworkflows/. workflows/mapping.json: соответствие «роль узла → node id» для каждого workflow (positive, negative, seed, input_image, denoise).- Валидация mapping при старте: все указанные узлы существуют, у них ожидаемые поля; иначе — падение с понятной ошибкой.
build_txt2img(prompt, negative, seed) -> dict: глубокое копирование шаблона, подстановка prompt/negative/seed.build_img2img(prompt, negative, seed, image_name, denoise) -> dict: то же + имя загруженного изображения.- Генерация seed:
secrets.randbelow(2**32), сохранять использованный seed в БД. - Обрезка/нормализация промпта (strip, лимит длины), значение negative prompt по умолчанию из конфига.
- Гарантировать, что исходный шаблон не мутируется между запросами.
- Тесты: подстановка значений, отсутствующий узел, отсутствующий файл workflow.
DoD: юнит-тесты подтверждают корректную подстановку; при старте приложения шаблоны и mapping валидны.
6. Очередь и менеджер задач (queue_manager.py)
QueueManagerс зависимостями:db,ComfyClient,WorkflowBuilder,Settings.- Внутренняя очередь
collections.deque+asyncio.Event(неasyncio.Queue, чтобы уметь считать позицию). asyncio.Lockдля всех операций с очередью и реестром активных пользователей.enqueue(user_id, mode, prompt, input_path) -> EnqueueResult(request, position):- проверить активный запрос пользователя (память + БД);
- проверить
MAX_QUEUE_SIZE; - создать запись в БД, добавить id в очередь, вернуть позицию.
- Позиция: количество задач впереди + выполняющаяся задача; считать уникальных пользователей (по плану — «X человек»).
- Воркер: цикл
while not stopping, брать задачу, никогда не умирать из-за ошибки одной задачи. - Жизненный цикл задачи:
queued → running → done/failed; обновлятьcomfy_prompt_id,output_file_path,error_text,finished_at. - Для i2i: upload изображения → подстановка имени → submit.
- Уникальный
client_idна задачу; WS-подключение до submit, закрытие после завершения. - Прогресс: throttle (не чаще
PROGRESS_INTERVAL, шаг ≥ 5%), вычисление процента изvalue/max. - События наружу через callback/event bus:
request_started,progress,request_done,request_failed— без импорта Telegram в менеджере. - Обработка ошибок: любое исключение →
failed, лог сrequest_id, событие для бота. start(): восстановитьqueuedиз БД, пометитьrunningкакfailed, запустить воркер.stop(): остановить приём, дождаться воркера, пометить незавершённые задачи, закрыть клиент.- Тесты с fake-клиентом: порядок FIFO, позиции, дубль пользователя, ошибка генерации, отмена при остановке.
DoD: два пользователя получают корректные позиции; повторная заявка отклоняется; прогресс доходит до подписчика; после рестарта queued восстанавливаются.
7. Telegram-слой (bot.py)
- Инициализация
Application(PTB v21+, async), регистрацияpost_init/post_shutdownдля старта/остановкиQueueManager. - Команды
/startи/help: краткая инструкция по двум режимам и формату ввода. - (Опционально)
/status: позиция и статус текущего запроса. - Текстовый хендлер (
filters.TEXT & ~filters.COMMAND): создать t2i-запрос, поставить в очередь. - Фото-хендлер (
filters.PHOTO): взятьphoto[-1], скачать черезget_file().download_to_drive(), сохранить вstorage/{user_id}/. - Для i2i: caption обязателен; пустой caption → подсказка «пришлите фото с описанием».
- Валидация: пустой/слишком длинный промпт, размер фото, поддерживаемые форматы; лимит caption 1024 символа.
- Ответ при дубле: «Ты уже в очереди» + текущая позиция (по плану).
- Ответ при постановке: «Принято. Перед тобой X человек» / «Ты первый, начинаю…».
- Статусное сообщение: сохранить
message_idв памяти; редактировать приrequest_started(«Генерация… 0%») иprogress. - Throttle редактирования статуса (не чаще 3 с), игнорировать
Message is not modified. - По завершении: отправить результат как фото (
FSInputFile), статусное сообщение удалить или заменить на «Готово». - По ошибке: редактировать статус на понятный текст, логировать техническую причину с
request_id. - (Опционально)
ALLOWED_USER_IDS: отказ остальным. - (Опционально)
ChatAction.UPLOAD_PHOTOпри отправке результата. - Аккуратно обрабатывать
TelegramError/BadRequest, не ронять приложение. - Проверить, что команды не перехватываются текстовым хендлером.
DoD: вручную проходят сценарии t2i и i2i; при дубле приходит корректный отказ; прогресс обновляется без флуда.
8. Сквозная интеграция
- Связать
bot.py → QueueManager → db → ComfyClient → storage. - t2i: текст → картинка → файл в
storage/{user_id}/→ отправка в чат →doneв БД. - i2i: фото + caption → upload → workflow → картинка → отправка →
done. - Очередь из 3 пользователей: позиции 1/2/3, затем корректное продвижение.
- ComfyUI недоступен: задача →
failed, пользователь получает понятную ошибку, воркер жив. - Ошибка выполнения workflow (
execution_error): сообщение пользователю без стектрейса. - Рестарт с задачами в очереди:
queuedпродолжают выполняться,runningпомеченыfailed. - Повторный запуск после падения: БД и файлы консистентны.
- Проверить отсутствие блокировки event loop (все I/O — async).
DoD: все сценарии выше пройдены на реальном ComfyUI.
9. Файловое хранилище
- Структура:
storage/{user_id}/{request_id}_input.<ext>,storage/{user_id}/{request_id}_output.<ext>. - Безопасные имена (только сгенерированные, не из пользовательского ввода), защита от path traversal.
- Атомарная запись:
.part→os.replace. - Создание директорий при старте/записи.
- (Опционально) очистка файлов старше N дней по расписанию; настройка
FILE_RETENTION_DAYS. - (Опционально) проверка свободного места и понятная ошибка при переполнении.
- Не хранить бинарные данные в SQLite — только пути.
DoD: входные и выходные файлы лежат по ожидаемым путям; повторный запуск не конфликтует по именам.
10. Docker и деплой
Dockerfileнаpython:3.11-slim, непривилегированный пользователь,PYTHONUNBUFFERED=1.- Копирование кода и установка
requirements.txt; без лишних build-зависимостей. .dockerignore:.env,storage/,data/,__pycache__/,tests/,.git/.docker-compose.yml:env_file: .env, volume для./storageи./data,restart: unless-stopped.- Сетевой доступ к ComfyUI:
host.docker.internalдля Windows/macOS или LAN-IP для Linux; зафиксировать в README. - Проверить, что SQLite и storage переживают пересоздание контейнера.
- (Опционально) healthcheck.
- Логи только в stdout/stderr.
DoD: docker compose up -d поднимает бота, генерация работает, данные сохраняются после down/up.
11. Тесты и приёмка
pytest+pytest-asyncio, общая фикстура временной БД и storage.- Юнит-тесты:
db.py, подстановка workflow, расчёт позиции, throttle прогресса, парсинг WS-событий. - Интеграционный тест: fake ComfyUI (HTTP + WS) → полный цикл
enqueue → running → done, файл создан. - Тест ошибок: ComfyUI 500,
node_errors, таймаут, обрыв WS. - Тест конкуренции: одновременные сообщения одного пользователя → только один запрос.
- Ручной e2e на реальном ComfyUI (t2i, i2i, очередь, рестарт).
- Проверка лимитов Telegram: размер результата, длина caption, частота редактирований.
DoD: тесты зелёные; ручной чек-лист из блока 8 пройден.
12. Документация
README.md: описание, схема работы, требования.- Инструкция по экспорту workflow в API-формате и заполнению
mapping.json. - Таблица переменных окружения с примерами.
- Запуск локально и через Docker.
- Раздел troubleshooting: ComfyUI недоступен, WS не подключается, «ты уже в очереди», ошибки workflow.
- Краткое описание архитектуры и границ MVP.
DoD: новый разработчик поднимает проект по README без устных пояснений.
Порядок работ
- Блок 1 (подготовка) — без него нельзя валидировать клиент и workflow.
- Блок 2 (каркас) → Блок 3 (БД) → Блок 4 (ComfyUI-клиент) → Блок 5 (workflow).
- Блок 6 (очередь) → Блок 7 (бот) → Блок 8 (интеграция).
- Блоки 9 (storage) и 10 (Docker) можно делать параллельно после блока 2.
- Блоки 11 (тесты) и 12 (документация) — параллельно с 6–10, финализация после 8.
Открытые вопросы
Согласовано: 1 воркер; running при рестарте → failed; workflow готовим вместе. Осталось уточнить:
- Какой чекпоинт/модель используется (SD 1.5, SDXL, Flux) и какие дефолтные
steps/cfg/denoiseи размер изображения? - Какая версия ComfyUI? В старых сборках событие
progressможет не содержатьprompt_id— нужен другой способ корреляции. - i2i без подписи: отклонять (по умолчанию) или использовать фиксированный промпт?
- Нужны ли
/cancel, удаление из очереди и ограничение доступаALLOWED_USER_IDSв MVP? - Сколько хранить входные/выходные файлы? Нужна ли автоочистка?
- Где будет хоститься бот и как он видит ComfyUI (LAN/VPN/интернет)?
- Добавлять ли в схему
mode,seed,error_text,finished_at(сейчас они предложены как расширение плана)? - Уведомлять ли пользователя, если его
running-задача была помеченаfailedпосле рестарта?
Вне скоупа MVP
- Платежи, подписки, квоты.
- Несколько моделей/чекпоинтов и выбор модели пользователем.
- Галерея, история генераций в боте, повтор генерации.
- LoRA/ControlNet и другие продвинутые workflow.
- Горизонтальное масштабирование и несколько экземпляров бота на одной SQLite.
- Админ-панель.