n8n: создаём workflow для автоматической генерации картинок на сервере с локальными моделями

n8n: создаём workflow для автоматической генерации картинок на сервере с локальными моделями

В этой статье я поделюсь своим опытом создания рабочего процесса (workflow) в n8n, который позволяет автоматически генерировать изображения на моём сервере, используя локально размещённые модели (ComfyUI и Gemma3). Это отличный способ оптимизировать работу и использовать свои ресурсы максимально эффективно!

Что понадобится:

  • Сервер/Рабочая станция: где будет работать вся наша система (сервер с Linux).
  • Генератор изображений:
    • ComfyUI: (рекомендуется из-за простого API и гибкости графа узлов).
  • n8n (Workflow Automation Tool): устанавливаем локально (например, через Docker).
  • Модели: например, Stable Diffusion Turbo или Flux.

Теперь давай детально разберём каждую ноду (или ещё называют узел) моего workflow в n8n.

Рабочий процесс состоит из 9 узлов, которые последовательно выполняют задачи: от генерации промпта до загрузки готовой картинки.

1. ⚡️ Schedule Trigger (триггер по расписанию)

Это стартовая точка всего рабочего процесса. Он отвечает за запуск автоматизации в заданное время или с определенной периодичностью (в моём случае задан интервал 1 раз в 10 минут). Это идеальный вариант для регулярного контента (например, «Картинка дня» для блога) и для полной автономности.

 

2. 🌐 Нода Ollama_Gemma_Prompt (генерация промпта)

Использует локально запущенную большую языковую модель (LLM) Gemma3:12b через Ollama для создания текста, который станет входным промптом для генерации изображения.

  • Тип узла: HTTP Request
  • Метод: POST
  • URL: http://192.168.0.240:11434/api/generate
  • Аутентификация: none
  • Send Headers:
    • Name: Content-Type
    • Value: application/json
  • Send Body:
    • Body Content Type: JSON
    • Specify Body: Using JSON
  • JSON
    • {
      "model": "gemma3:12b",
      "prompt": "Твоя задача — действовать как генератор промптов. Всегда используй эстетику, вдохновленную Поп-артом, Мемфисом и Абстрактным стилем. Создай ОДИН промпт для FLUX, состоящий из 3-5 предложений. Промпт должен быть предназначен для генерации бесшовного паттерна. Обязательно включи: 'seamless tile pattern', смелую, контрастную цветовую палитру (указывай 3-4 цвета) и четкое описание абстрактных, волнообразных или геометрических форм. Выдай ТОЛЬКО сам промпт, без заголовков, объяснений, вопросов и форматирования Markdown (без жирного шрифта, звездочек и т.п.)**",
      "stream": false,
      "options": {
      "temperature": 0.7
      }
      }

Это делает процесс уникальным — изображения генерируются не просто по-статичному промпту, а по промпту, сгенерированному ИИ!

3. 📝 Clean_Prompt (очистка/фильтрация промпта)

Обработка и форматирование текста (конечная очистка на всякий случай, чтобы исключить попадание любых нежелательных символов), полученного от LLM. API ComfyUI требует чистый текст без лишних символов (иначе вы можете столкнуться с ошибками при обработке json-запросов в дальнейшем).

Нода называется Edit Fields (Set).

  • Mode: Manual Mapping
  • Первый параметр:
    • Имя: safe_prompt
    • Значение: {{ JSON.stringify($node['Ollama_Gemma_Prompt'].json.response).slice(1, -1).replace(/\\n/g, ' ').replace(/"/g, '\\"') }}
  • Второй параметр:
    • Имя: new_seed (это поле нам понадобится для генерации случайного числа, которое будем передавать в json ComfyUI, чтобы генерировались разные картинки)
    • Значение: {{ Math.floor(Math.random() * 1000000000000) }}

4. 🌐 ComfyUI_Send_Workflow (отправка рабочего процесса генерации)

Отправка готового JSON-пэйлоада с промптом в ComfyUI для начала генерации. Немного отходя от процесса объясню, где взять шаблон json файла, который ожидает ComfyUI. Для его получения необходимо войти в ComfyUI, загрузить шаблон, который вы хотите использовать (я выбрала flux_dev_checkpoint_example, его можно найти в поиске шаблонов). Когда шаблон выбран, в верхнем меню нажимаем на иконку ComfyUI -> File -> Export API. И скачиваем готовый json. В дальнейшем мы будем использовать именно его, заменяя некоторые параметры.

  • Тип узла: HTTP Request
  • Метод: POST
  • URL: http://192.168.0.240:8188/prompt
  • Send Headers:
    • Name: Content-Type
    • Value: application/json
  • Send Body
    • Body Content Type: JSON
    • Specify Body: Using JSON
    • JSON: {
      "prompt": {
      "6": {
      "inputs": {
      "text": "{{ $node['Clean_Prompt'].json.safe_prompt }}",
      "clip": [
      "30",
      1
      ]
      },
      "class_type": "CLIPTextEncode",
      "_meta": {
      "title": "CLIP Text Encode (Positive Prompt)"
      }
      },
      "8": {
      "inputs": {
      "samples": [
      "31",
      0
      ],
      "vae": [
      "30",
      2
      ]
      },
      "class_type": "VAEDecode",
      "_meta": {
      "title": "VAE Decode"
      }
      },
      "9": {
      "inputs": {
      "filename_prefix": "{{ $node['Clean_Prompt'].json.new_seed }}",
      "images": [
      "8",
      0
      ]
      },
      "class_type": "SaveImage",
      "_meta": {
      "title": "Save Image"
      }
      },
      "27": {
      "inputs": {
      "width": 1024,
      "height": 1024,
      "batch_size": 1
      },
      "class_type": "EmptySD3LatentImage",
      "_meta": {
      "title": "EmptySD3LatentImage"
      }
      },
      "30": {
      "inputs": {
      "ckpt_name": "flux1-dev-fp8.safetensors"
      },
      "class_type": "CheckpointLoaderSimple",
      "_meta": {
      "title": "Load Checkpoint"
      }
      },
      "31": {
      "inputs": {
      "seed": {{ $json.new_seed }},
      "steps": 30,
      "cfg": 3.5,
      "sampler_name": "dpmpp_3m_sde",
      "scheduler": "simple",
      "denoise": 1,
      "model": [
      "30",
      0
      ],
      "positive": [
      "35",
      0
      ],
      "negative": [
      "33",
      0
      ],
      "latent_image": [
      "27",
      0
      ]
      },
      "class_type": "KSampler",
      "_meta": {
      "title": "KSampler"
      }
      },
      "33": {
      "inputs": {
      "text": "3D, depth of field, shadow, low resolution, watermark, text, texturing, border, frames, ugly, soft focus, blurry, out of focus, cropped.\n",
      "clip": [
      "30",
      1
      ]
      },
      "class_type": "CLIPTextEncode",
      "_meta": {
      "title": "CLIP Text Encode (Negative Prompt)"
      }
      },
      "35": {
      "inputs": {
      "guidance": 3.5,
      "conditioning": [
      "6",
      0
      ]
      },
      "class_type": "FluxGuidance",
      "_meta": {
      "title": "FluxGuidance"
      }
      }
      }
      }

В этот JSON (в Body) мы динамически вставляем очищенный промпт из узла Clean_Prompt и значение для ‘seed’. Результат: Узел возвращает ID генерации (Prompt ID), который нам понадобится для проверки статуса в следующих шагах.

5. ⏸️ Wait (Ожидание)

Приостановка выполнения workflow, пока генерация изображения не завершится. В моём случае (с моим железом), генерация занимала максимум 3 минуты, поэтому я поставила задержку на 350 секунд.

  • Resume: After Time Interval
  • Wait Amount: 350.00
  • Wait Unit: Seconds

6. 🌐 ComfyUI_Get_History1 (проверка статуса и получение истории)

После паузы (когда отработает свой временной интервал предыдущая нода), этот узел проверяет, что генерация завершена, и получает данные истории.

  • Тип узла: HTTP Request
  • Метод: GET
  • URL: http://192.168.0.240:8188/history
  • Send Query Parameters: true
    • Specify Query Parameters: Using Fields Below
    • Name: prompt_id
    • Value: {{ $node['ComfyUI_Send_Workflow'].json.prompt_id }} (мы динамически вставляем ID, чтобы получить информацию только о нашей текущей задаче)

Вкладка Settings:

  • Retry on Fail: true
  • Max. Tries: 5
  • Wait Between Tries (ms): 1500
  • On Error: Continue (using error output)
  • Notes: 404 (тут мы указываем какая ошибка нас интересует)

Обработка ошибок тут ключевой момент. И мы используем продвинутые возможности n8n для обработки исключений.

  • Сценарий успеха (Success): Если генерация прошла успешно, ComfyUI возвращает данные истории с HTTP-статусом 200 OK. n8n обрабатывает этот ответ, и рабочий процесс автоматически переходит к следующему узлу, подключенному к ветке Success (в нашем случае, это ComfyUI_Get_History).
  • Сценарий ошибки (Error): Если генерация не успела завершиться или ComfyUI не находит историю по указанному ID, он может вернуть HTTP-статус 404 Not Found.

7. 🌐 ComfyUI_Get_History (получение имени файла)

Из данных истории, полученных на предыдущем шаге, извлекаем точное имя сгенерированного файла (например, ComfyUI_00001_.png). Это критический шаг, так как имя файла уникально и нужно для его скачивания.

  • Тип узла: HTTP Request
  • Метод: GET
  • URL: http://192.168.0.240:8188/history
  • Send Query Parameters: true
    • Specify Query Parameters: Using Fields Below
    • Name: prompt_id
    • Value: {{ $node['ComfyUI_Send_Workflow'].json.prompt_id }}

8. 🌐 ComfyUI_Get_Image (скачивание изображения)

Запрос к API ComfyUI для скачивания файла.

  • Тип узла: HTTP Request
  • Метод: GET
  • URL: http://192.168.0.240:8188/view
  • Send Query Parameters: true
    • Specify Query Parameters: Using Fields Below
    • Name: filename
    • Value: {{ Object.values($json)[Object.values($json).length - 1].outputs['9'].images[0].filename }}
  • Response Format: Должен быть настроен на получение бинарных данных (Binary или File).
    • Options -> Response
    • Response Format: File
    • Put Output in Field: data

9. 🗄️ FTP (загрузка файла)

Последний шаг — сохранение сгенерированного файла на внешний ресурс или в хранилище. Тут можно использовать разные ноды, ФТП это одна из них, так же можно подключить сохранение в Гугл Диск или другие места хранения.

  • Тип узла: Специализированный узел FTP
  • Действие: Upload
  • Path: /home/user/n8n_patterns_storage/{{ $node['ComfyUI_Get_History'].json[Object.keys($node['ComfyUI_Get_History'].json)[0]].outputs['9'].images[0].filename.replace(/\.([^.]+)$/, '_' + Date.now() + '.$1') }} тут ещё я добавляю штамп времени к имени файла

 

В статье подробно описан автоматизированный рабочий процесс (workflow) в n8n для генерации уникальных изображений на собственном сервере. Этот процесс использует локальную языковую модель Ollama (Gemma) для динамического создания текстовых промптов, которые затем передаются через API в ComfyUI, работающий на сервере с Linux и Docker, для выполнения фактической генерации изображений. Ключевые этапы включают запуск по расписанию, очистку промпта, отправку запроса к ComfyUI, ожидание завершения и, что важно, обработку ошибок (404 Not Found) через ветвление workflow. Завершается процесс скачиванием готового изображения и его автоматической загрузкой на удалённый FTP-сервер, демонстрируя полностью замкнутый и автономный цикл создания контента, который позволяет эффективно использовать локальные ресурсы и не зависеть от облачных сервисов.

В следующей статье, я расскажу как установить n8n на свой локальный (и не локлаьный) сервер на Linux с использованием Docker Compose.

Комментарии

Комментариев пока нет. Почему бы ’Вам не начать обсуждение?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *