Пишем свою ноду в n8n под любой API за вечер

в 10:15, , рубрики: n8n, n8n community nodes, n8n create own node, n8n custom nodes, n8n node, n8n node как создать свою, как написать community nodes, как сделать n8n ноду, своя нода n8n

Сегодня многие разработчики различного уровня знают n8n как гибкий инструмент для автоматизации. В нем можно собрать почти любой сценарий - от простейшего Telegram-бота до сложных бизнес-процессов, используя множество готовых нод (узлов) для работы с популярными сервисами.

Но что делать, если в это множество не входит один из используемых вами сервисов, а через ноду HTTP Requests работать крайне сложно и неудобно? Или, может, хочется подключить собственный API и работать с ним по собственной логике?

Здесь напрашивается довольно простой ответ: написать свою ноду на n8n. Изначально это кажется сложным, однако, прочитав эту статью, вы сможете буквально за 1 вечер собрать минимально-рабочую ноду под ваши "хотелки".

Community nodes - что это?

Что очевидно из названия, это нода, написанная каким-то разработчиком из комьюнити (сообщества) n8n.

Комьюнити-ноды недоступны в списке нод в воркфлоу без предварительной их установки. Все ноды, которые доступны без дополнительных манипуляций, проверены и добавлены именно разработчиками n8n.

Такие ноды публикуются разработчиками как пакет в npm Registry, название которого обязательно начинается с n8n-nodes для того, чтобы n8n смог увидеть его.

Например: n8n-nodes-amvera-inference или n8n-nodes-my-first-node

Где можно развернуть n8n

Перед тем как писать свою ноду, нужно подготовить среду. n8n можно установить локально, в Docker, развернуть в облаке или на VDS/VPS.

В моём случае всё происходило в Amvera Cloud - сервисе для простого и быстрого развертывания IT-приложений, в котором n8n уже есть как настроенный сервис с бесплатным доменом, запуском "одной кнопкой" и собственной нодой доступа к LLM c оплатой в рублях.

Установка комьюнити-ноды

Первым делом необходимо убедиться, что ваша версия n8n и окружение поддерживает установку комьюнити-нод.

Если вы используете преднастроенный сервис в Amvera, дополнительно ничего настраивать не нужно и установка community nodes должна быть доступна в настройках n8n.

Настройки n8n

Настройки n8n
Community nodes

Community nodes

Если же таких нет - попробуйте обновить n8n или создать/изменить переменную окружения N8N_COMMUNITY_NODES_ENABLED в значении true.

Здесь можно установить любую комьюнити-ноду из списка.

  • Если при установке комьюнити-ноды n8n возвращает ошибку:

  • Error installing new package: Request failed with status 504,

  • Попробуйте установить ноду еще раз через несколько минут, это нормально.

Создаем свою ноду шаг за шагом

Итак, когда у нас готово все окружение, мы можем приступать к практической части.

Мы разберем нашу ноду n8n-nodes-amvera-inference, которая упрощает работу с Amvera LLM Inference API, отправляя запросы к API с определенным токеном и возвращая ответ в двух видах: RAW JSON и Ответ модели (только сам ответ).

Подготовка проекта

Важно понимать, что нода - обычный npm-пакет, но с особой структурой.

Официальный репозиторий для быстрого старта доступен на GitHub: https://github.com/n8n-io/n8n-nodes-starter

Структура проекта

Структура нашей ноды выглядит следующим образом:

n8n-nodes-amvera-inference
├── credentials
│   └── AmveraLlmApi.credentials.ts
├── index.ts
├── nodes
│   └── AmveraLlm.node.ts
├── package.json
└── tsconfig.json
  • package.json - можно полностью скопировать с официального репозитория n8n-nodes-starter. Важно изменить название пакета.

  • tsconfig.json - для предложенной структуры выше будет выглядеть так:

 {
  "compilerOptions": {
    "target": "ES2019",
    "module": "commonjs",
    "declaration": true,
    "outDir": "dist",
    "rootDir": ".",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true
  },
  "include": ["nodes/**/*.ts", "credentials/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}
  • AmveraLlmApi.credentials.ts - содержит в себе описание Credentials, используемых для ноды, если она требует авторизацию на TypeScript.

  • AmveraLlm.node.ts - основной код (логика) работы ноды. Чуть ниже будет его разбор на рабочем примере.

  • index.ts - необязательный файл, содержащий в себе export всех модулей.

AmveraLlmApi.credentials.ts

import { ICredentialType, INodeProperties } from 'n8n-workflow';

export class AmveraLlmApi implements ICredentialType {
    name = 'amveraLlmApi';
    displayName = 'Amvera LLM API';
    properties: INodeProperties[] = [
        {
            displayName: 'API Token', // имя, которое будет отображаться в n8n
            name: 'apiToken', // имя, по которому мы будем общаться дальше в коде
            type: 'string', // тип кредов
            typeOptions: { password: true }, // доп. настройки
            default: '', // значение по дефолту - пустое
            required: true, // обязательно ли наличие кредов
            description: 'Токен от модели LLM', // описание в UI n8n
        },
    ];
}

Как и говорилось ранее - этот файл содержит в себе описание вида Credentials для того, чтобы n8n понимал, как формировать окно создания кредов.

AmveraLlm.node.ts

import {
    IExecuteFunctions,
    INodeExecutionData,
    INodeType,
    INodeTypeDescription,
} from 'n8n-workflow';

export class AmveraLlm implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'Amvera LLM', // отображаемое имя
        name: 'amveraLlm', // имя для обращений в коде
        group: ['transform'], // группа (тип) ноды
        version: 1, // версия
        description: 'Работа с Amvera LLM API', // описание
        defaults: { name: 'Amvera LLM' }, // дефолтные значения
        inputs: ['main'], // входные данные (из предыдущей ноды)
        outputs: ['main'], // выходные данные (в следующую ноду)
        credentials: [{ name: 'amveraLlmApi', required: true }], // какие креды используются. Здесь мы как раз пишем имя из созданного ранее файла.
        properties: [ // Описание полей внутри ноды. У нас это: выбор модели, создание сообщений для LLM, выбор режима вывода
            {
                displayName: 'Model', // отображаемое имя настройки ноды
                name: 'model', // внутреннее имя настройки ноды
                type: 'options', // тип (options - выпадающий список)
                options: [ // что можно выбрать {name, value}
                    { name: 'llama8b', value: 'llama8b' },
                    { name: 'llama70b', value: 'llama70b' },
                    { name: 'gpt-4.1', value: 'gpt-4.1' },
                    { name: 'gpt-5', value: 'gpt-5' },
                ],
                default: 'llama8b', // значение по умолчанию
            },
            {
                displayName: 'Messages',
                name: 'messages',
                type: 'fixedCollection', // 
                typeOptions: { multipleValues: true },
                default: {},
                options: [
                    {
                        name: 'message',
                        displayName: 'Message',
                        values: [
                            {
                                displayName: 'Role',
                                name: 'role',
                                type: 'options',
                                options: [
                                    { name: 'system', value: 'system' },
                                    { name: 'user', value: 'user' },
                                    { name: 'assistant', value: 'assistant' },
                                ],
                                default: 'user',
                            },
                            {
                                displayName: 'Text',
                                name: 'text',
                                type: 'string',
                                default: '',
                            },
                        ],
                    },
                ],
            },
            {
                displayName: 'Режим вывода',
                name: 'returnMode',
                type: 'options',
                options: [
                    { name: 'Ответ модели', value: 'first' },
                    { name: 'JSON', value: 'raw' },
                ],
                default: 'first',
            },
        ],
    };

    async execute(this: IExecuteFunctions): Promise {
        const items = this.getInputData();
        const output: INodeExecutionData[] = [];

        const creds = await this.getCredentials('amveraLlmApi');
        const token = creds.apiToken as string;

        for (let i = 0; i < items.length; i++) {
            const model = this.getNodeParameter('model', i) as string;
            const messages = this.getNodeParameter('messages.message', i, []) as Array<{ role: string; text: string }>;
            const mode = this.getNodeParameter('returnMode', i) as string;

            const endpoint = model.startsWith('llama') ? 'llama' : 'gpt';
            const body = { model, messages };

            const res = await this.helpers.httpRequest({
                method: 'POST',
                url: `https://kong-proxy.yc.amvera.ru/api/v1/models/${endpoint}`,
                headers: {
                    'X-Auth-Token': `Bearer ${token}`,
                    'Content-Type': 'application/json',
                },
                body,
                json: true,
            });

            let answer = '';

            if (model.startsWith('llama')) {
                const result = res?.result;
                if (result?.alternatives?.length) {
                    answer = result.alternatives[0]?.message?.text ?? '';
                }
            } else {
                if (res?.choices?.length) {
                    answer = res.choices[0]?.message?.content ?? '';
                }
            }
            
            if (mode === 'first') {
                output.push({ json: { text: answer } });
            } else {
                output.push({ json: res });
            }
        }

        return [output];
    }
}

Здесь чуть сложнее, но все также шаблонно.

Первая часть кода

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

Вторая часть кода - execute()

Всё, что происходит внутри метода execute(), - это реальная логика работы ноды.
n8n вызывает этот метод каждый раз, когда workflow доходит до твоей ноды.
В этот момент нода:

  1. получает входные данные;

  2. читает настройки из интерфейса;

  3. делает запрос к API (или любое другое действие), описанное вами в коде;

  4. формирует выходной JSON и передает его дальше.

Разберем метод подробнее.

1. Получаем входные данные

const items = this.getInputData();
const output: INodeExecutionData[] = [];
  • getInputData() возвращает массив объектов (JSON) от предыдущей ноды;

  • output - пустой массив, куда мы будем складывать результаты.

n8n умеет работать с массивами данных - если предыдущая нода передала, например, 10 строк, execute() выполнится 10 раз в цикле.

2. Получаем креды и параметры

const creds = await this.getCredentials('amveraLlmApi');
const token = creds.apiToken as string;
  • getCredentials('amveraLlmApi') - достает токен, который пользователь ввел в Credentials ноды.

Дальше - параметры, которые пользователь задаёт в интерфейсе ноды:

const model = this.getNodeParameter('model', i) as string;
const messages = this.getNodeParameter('messages.message', i, []) as Array<{ role: string; text: string }>;
const mode = this.getNodeParameter('returnMode', i) as string;

Каждый вызов getNodeParameter() получает значение поля из properties (той самой секции в описании ноды).
i - индекс текущего элемента из входных данных (если их несколько).

3. Формируем запрос к API

const endpoint = model.startsWith('llama') ? 'llama' : 'gpt';
const body = { model, messages };

Здесь мы определяем, какой эндпоинт использовать - llama или gpt и формируем body запроса.

4. Отправляем HTTP-запрос

const res = await this.helpers.httpRequest({
    method: 'POST',
    url: `https://kong-proxy.yc.amvera.ru/api/v1/models/${endpoint}`,
    headers: {
        'X-Auth-Token': `Bearer ${token}`,
        'Content-Type': 'application/json',
    },
    body,
    json: true,
});
  • this.helpers.httpRequest - встроенный метод n8n для HTTP-запросов (автоматически обрабатывает JSON, ошибки, HTTPS). Короче говоря: удобно.

  • В заголовок добавляется токен авторизации (Bearer ${token}), который мы получили из кредов.

  • json: true говорит n8n автоматически парсить ответ как JSON.

5. Обрабатываем ответ

Amvera LLM API может вернуть результат в разных форматах - у LLaMA и GPT структура ответа отличается.

let answer = '';

if (model.startsWith('llama')) {
    const result = res?.result;
    if (result?.alternatives?.length) {
        answer = result.alternatives[0]?.message?.text ?? '';
    }
} else {
    if (res?.choices?.length) {
        answer = res.choices[0]?.message?.content ?? '';
    }
}
  • Для моделей LLaMA ответ находится в res.result.alternatives[0].message.text.

  • Для GPT-моделей - в res.choices[0].message.content.

  • ?? '' - оператор, который возвращает строку, если undefined.

6. Формируем выходной результат

if (mode === 'first') {
    output.push({ json: { text: answer } });
} else {
    output.push({ json: res });
}
  • Если выбран режим "Ответ модели", нода вернет только текст (удобно для чат-ботов).

  • Если выбран режим "JSON", в вывод попадёт весь ответ API

7. Возвращаем результат n8n

return [output];

С такими настройками выглядит следующим образом

Настраиваем ноду

Настраиваем ноду
  • Весь код ноды доступен на нашем Github

Публикация пакета

Если у вас уже готов код (т.е. вы настроили вид ноды и ее логику в execute()), пакет нужно опубликовать в npm.

Для этого нужно иметь аккаунт в npm и залогиниться через команду npm login, которая перенаправить вас в браузер для авторизации.

Еще раз убедитесь, что в package.json название пакета начинается с n8n-nodes.

После этого, проект необходимо собрать. Для этого пишем:

npm i
npm run build # если у вас есть скрипт build в package.json для компиляции TS 

Если у вас появились папки node_modules и dist с скомпилированными js файлами, ваш пакет готов к публикации. Пишем:

npm publish --access public

Если все получилось правильно, пакет должен появиться и быть доступен в npm Registry.

Итог

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

На самом деле, написание нод для n8n, не такая уж сложная задача, как может казаться на первый взгляд. Достаточно лишь по шаблону заполнить описание ноды и описать логику в методе execute().

Таким образом вы можете подключить любой API буквально за вечер, что дает полную свободу.

Автор: MarkovM

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js