vm:matrix-bot:01-install

Это старая версия документа!


Matrix Bot AI - Полная документация

Matrix Bot AI - это интеллектуальный чат-бот для платформы Matrix, работающий на n8n. Бот запоминает историю разговоров в PostgreSQL и использует OpenAI GPT-4o-mini для генерации ответов с учетом контекста предыдущих сообщений.

Основные возможности:

  • Запоминание истории разговоров в разных комнатах
  • Использование контекста при ответах
  • Сохранение ID и имен собеседников
  • Автоматическая сортировка истории от новых к старым

1. Matrix сервер - отправляет вебхук при новом сообщении 2. n8n Workflow - обрабатывает сообщение и генерирует ответ 3. PostgreSQL - хранит историю разговоров 4. OpenAI API - генерирует ответы через GPT-4o-mini 5. Matrix Bot сервис - отправляет сообщения обратно в чат

1. Webhook приходит → Формирует данные сообщения (текст, room_id, sender, sender_name) 2. Query Memory → Получает последние 10 сообщений из истории этой комнаты 3. Build Context → Собирает контекст разговора для AI с сохранением порядка (новые сообщения в конце) 4. AI Agent → OpenAI генерирует ответ на основе контекста 5. Save and Send → Сохраняет результат в БД и отправляет сообщение в чат 6. Response → Возвращает успешный ответ на вебхук

Таблица conversation_history:

  • room_id - VARCHAR - ID комнаты Matrix
  • sender - VARCHAR - Matrix ID отправителя (@admin:domain)
  • sender_name - VARCHAR - Отображаемое имя в чате (Николай)
  • message - TEXT - Текст сообщения
  • bot_response - TEXT - Ответ бота
  • created_at - TIMESTAMP - Время создания записи

Выполните эти SQL команды в PgAdmin:

-- Создание таблицы conversation_history
CREATE TABLE conversation_history (
  id SERIAL PRIMARY KEY,
  room_id VARCHAR(255) NOT NULL,
  sender VARCHAR(255) NOT NULL,
  sender_name VARCHAR(255),
  message TEXT,
  bot_response TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- Создание индекса для быстрого поиска по room_id
CREATE INDEX idx_room_id ON conversation_history(room_id);
CREATE INDEX idx_created_at ON conversation_history(created_at DESC);
 
-- Добавление колонки sender_name если таблица уже существует
ALTER TABLE conversation_history 
ADD COLUMN sender_name VARCHAR(255);

Тип: n8n-nodes-base.webhook Назначение: Получает HTTP POST запрос от Matrix сервера Параметры:

  • Path: matrix-bot-ai
  • HTTP Method: POST
  • Response Mode: responseNode

Входящие данные:

{
  "body": {
    "text": "сообщение",
    "room_id": "!abc123:domain",
    "sender": "@user:domain",
    "sender_name": "User Name",
    "is_dm": false
  }
}

Тип: n8n-nodes-base.code (JavaScript) Назначение: Форматирует входящие данные в стандартный вид

Код:

const body = $json.body;
return {
  text: body.message || body.text,
  room_id: body.room_id,
  sender: body.sender || '',
  sender_name: body.sender_name || 'User',
  originalMessage: body.message || body.text,
  is_group: !body.is_dm
};

Выходные данные:

{
  "text": "сообщение",
  "room_id": "!abc123:domain",
  "sender": "@user:domain",
  "sender_name": "User Name",
  "originalMessage": "сообщение",
  "is_group": false
}

Тип: n8n-nodes-base.postgres Назначение: Получает историю из PostgreSQL Операция: executeQuery

SQL Query:

SELECT sender, sender_name, message, bot_response, created_at 
FROM conversation_history 
WHERE room_id = '{{room_id}}' 
ORDER BY created_at DESC 
LIMIT 10

Параметр Query (в n8n):

SELECT sender, sender_name, message, bot_response, created_at FROM conversation_history WHERE room_id = '{{ "'" + $json.room_id.replace(/'/g, "''") + "'" }}' ORDER BY created_at DESC LIMIT 10

Выходные данные: Массив последних 10 сообщений (или пусто если их нет)

Тип: n8n-nodes-base.code (JavaScript) Назначение: Собирает контекст разговора для AI из памяти

Код:

// Get Format Input from $node
const formatInput = $node['Format Input'].json;
 
// Get all items from Query Memory
const allItems = $input.all();
const memoryItems = allItems.filter(item => item.json && item.json.message && item.json.sender) || [];
 
let contextText = '';
 
if (memoryItems.length > 0) {
  contextText = 'Recent conversation history:\n\n';
  // NEWEST FIRST - iterate from 0 to end (last item is most recent)
  for (let i = 0; i < memoryItems.length; i++) {
    const record = memoryItems[i].json || {};
    if (record.message && record.sender && record.bot_response && record.created_at) {
      const ts = new Date(record.created_at).toLocaleString('ru-RU');
      const senderName = record.sender_name || record.sender;
      contextText += `[${ts}] ${senderName}: ${record.message}\n`;
      contextText += `  → Jarvice: ${record.bot_response}\n\n`;
    }
  }
}
 
// IMPORTANT: Return ALL fields from Format Input
return {
  context: contextText,
  text: formatInput.text,
  room_id: formatInput.room_id,
  sender: formatInput.sender,
  sender_name: formatInput.sender_name,
  originalMessage: formatInput.originalMessage,
  is_group: formatInput.is_group
};

Выходные данные:

{
  "context": "Recent conversation history:\n\n[время] Николай: 3+2\n  → Jarvice: 5\n\n",
  "text": "сообщение",
  "room_id": "!abc123:domain",
  "sender": "@user:domain",
  "sender_name": "User Name",
  "originalMessage": "сообщение",
  "is_group": false
}

Тип: @n8n/n8n-nodes-langchain.agent Назначение: LangChain Agent использующий OpenAI модель

Параметры:

  • Prompt Type: define
  • Text: json.text
  • System Message: (см. ниже - НЕ МЕНЯТЬ!)

System Message:

Ты полезный AI ассистент для Matrix чата по имени Jarvice🤖.
Ты помощник Администратора (Николай).
Отвечай кратко, используй emoji.
Обязательно используй контекст предыдущих сообщений для лучшего ответа. Отвечай с числовыми результатами и референциями на предыдущие вычисления.

Тип: @n8n/n8n-nodes-langchain.lmChatOpenAi Назначение: Подключение к OpenAI API

Параметры:

  • Model: gpt-4o-mini
  • Credentials: aitunnel (ваш OpenAI API key)

Тип: n8n-nodes-base.code (JavaScript) Назначение: Подготавливает данные для сохранения и отправки

Код:

const ai = $json.output || $node['AI Agent'].json.output || '';
const buildContext = $node['Build Context Code'].json;
 
const roomId = buildContext.room_id || '';
const isGroup = buildContext.is_group || false;
const sender = buildContext.sender || '';
const senderName = buildContext.sender_name || '';
const originalMessage = buildContext.originalMessage || '';
 
const esc = (s) => {
  if (!s) return 'NULL';
  return "'" + String(s).replace(/'/g, "''") + "'";
};
 
const query = "INSERT INTO conversation_history (room_id, sender, sender_name, message, bot_response) VALUES (" +
  esc(roomId) + ", " +
  esc(sender) + ", " +
  esc(senderName) + ", " +
  esc(originalMessage) + ", " +
  esc(ai) + ")";
 
return {
  message: ai,
  room_id: roomId,
  sender_id: isGroup ? sender : null,
  sender_name: isGroup ? senderName : null,
  query: query
};

Выходные данные:

{
  "message": "Ответ бота",
  "room_id": "!abc123:domain",
  "sender_id": "@user:domain",
  "sender_name": "User Name",
  "query": "INSERT INTO conversation_history..."
}

Тип: n8n-nodes-base.httpRequest Назначение: Отправляет ответ в Matrix через Matrix Bot сервис

Параметры:

JSON Body:

{{ JSON.stringify({room_id: $json.room_id, message: $json.message, sender_id: $json.sender_id, sender_name: $json.sender_name}) }}

Тип: n8n-nodes-base.postgres Назначение: Сохраняет разговор в PostgreSQL

Параметры:

Тип: n8n-nodes-base.respondToWebhook Назначение: Отправляет успешный ответ на вебхук

Параметры:

  • Respond With: json
  • Response Body: true

Webhook → Format Input Format Input → Query Memory (index 0) Format Input → Build Context Code (index 1) Query Memory → Build Context Code (index 0) Build Context Code → AI Agent OpenAI Model → AI Agent (ai_languageModel) AI Agent → Save and Send Save and Send → Send to Matrix Save and Send → Save to Database Send to Matrix → Respond to Webhook

Используйте этот JSON для создания workflow в n8n:

{
  "name": "Matrix Bot AI",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "matrix-bot-ai",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [-400, 304],
      "webhookId": "2dad965f-08e2-412f-960a-b16c91dea686",
      "typeVersion": 2
    },
    {
      "parameters": {
        "language": "javascript",
        "jsCode": "const body = $json.body;\nreturn {\n  text: body.message || body.text,\n  room_id: body.room_id,\n  sender: body.sender || '',\n  sender_name: body.sender_name || 'User',\n  originalMessage: body.message || body.text,\n  is_group: !body.is_dm\n};"
      },
      "id": "format-input",
      "name": "Format Input",
      "type": "n8n-nodes-base.code",
      "position": [-144, 304],
      "typeVersion": 1
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ \"SELECT sender, sender_name, message, bot_response, created_at FROM conversation_history WHERE room_id = '\" + $json.room_id.replace(/'/g, \"''\") + \"' ORDER BY created_at DESC LIMIT 10\" }}",
        "additionalFields": {}
      },
      "id": "query-memory",
      "name": "Query Memory",
      "type": "n8n-nodes-base.postgres",
      "position": [0, 464],
      "typeVersion": 1,
      "credentials": {
        "postgres": {
          "id": "Sy23a0Zvp6PlFt6V",
          "name": "Postgres matrix_bot_memory"
        }
      }
    },
    {
      "parameters": {
        "language": "javascript",
        "jsCode": "// Get Format Input from $node\nconst formatInput = $node['Format Input'].json;\n\n// Get all items from Query Memory\nconst allItems = $input.all();\nconst memoryItems = allItems.filter(item => item.json && item.json.message && item.json.sender) || [];\n\nlet contextText = '';\n\nif (memoryItems.length > 0) {\n  contextText = 'Recent conversation history:\\n\\n';\n  // NEWEST FIRST - iterate from 0 to end (last item is most recent)\n  for (let i = 0; i < memoryItems.length; i++) {\n    const record = memoryItems[i].json || {};\n    if (record.message && record.sender && record.bot_response && record.created_at) {\n      const ts = new Date(record.created_at).toLocaleString('ru-RU');\n      const senderName = record.sender_name || record.sender;\n      contextText += `[${ts}] ${senderName}: ${record.message}\\n`;\n      contextText += `  → Jarvice: ${record.bot_response}\\n\\n`;\n    }\n  }\n}\n\n// IMPORTANT: Return ALL fields from Format Input\nreturn {\n  context: contextText,\n  text: formatInput.text,\n  room_id: formatInput.room_id,\n  sender: formatInput.sender,\n  sender_name: formatInput.sender_name,\n  originalMessage: formatInput.originalMessage,\n  is_group: formatInput.is_group\n};"
      },
      "id": "build-context-code",
      "name": "Build Context Code",
      "type": "n8n-nodes-base.code",
      "position": [160, 304],
      "typeVersion": 1
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.context + 'User (' + $json.sender_name + '): ' + $json.text }}",
        "options": {
          "systemMessage": "Ты полезный AI ассистент для Matrix чата по имени Jarvice🤖.\nТы помощник Администратора (Николай).\nОтвечай кратко, используй emoji.\nОбязательно используй контекст предыдущих сообщений для лучшего ответа. Отвечай с числовыми результатами и референциями на предыдущие вычисления."
        }
      },
      "id": "agent-1",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [304, 304],
      "typeVersion": 1.9
    },
    {
      "parameters": {
        "model": {
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "id": "openai-1",
      "name": "OpenAI Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [352, 512],
      "typeVersion": 1.3,
      "credentials": {
        "openAiApi": {
          "id": "P1rSZeqDWn05zkK1",
          "name": "aitunnel"
        }
      }
    },
    {
      "parameters": {
        "language": "javascript",
        "jsCode": "const ai = $json.output || $node['AI Agent'].json.output || '';\nconst buildContext = $node['Build Context Code'].json;\n\nconst roomId = buildContext.room_id || '';\nconst isGroup = buildContext.is_group || false;\nconst sender = buildContext.sender || '';\nconst senderName = buildContext.sender_name || '';\nconst originalMessage = buildContext.originalMessage || '';\n\nconst esc = (s) => {\n  if (!s) return 'NULL';\n  return \"'\" + String(s).replace(/'/g, \"''\") + \"'\";\n};\n\nconst query = \"INSERT INTO conversation_history (room_id, sender, sender_name, message, bot_response) VALUES (\" +\n  esc(roomId) + \", \" +\n  esc(sender) + \", \" +\n  esc(senderName) + \", \" +\n  esc(originalMessage) + \", \" +\n  esc(ai) + \")\";\n\nreturn {\n  message: ai,\n  room_id: roomId,\n  sender_id: isGroup ? sender : null,\n  sender_name: isGroup ? senderName : null,\n  query: query\n};"
      },
      "id": "save-and-send",
      "name": "Save and Send",
      "type": "n8n-nodes-base.code",
      "position": [560, 304],
      "typeVersion": 1
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://matrix_bot:5000/send_message",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({room_id: $json.room_id, message: $json.message, sender_id: $json.sender_id, sender_name: $json.sender_name}) }}",
        "options": {}
      },
      "id": "http-1",
      "name": "Send to Matrix",
      "type": "n8n-nodes-base.httpRequest",
      "position": [752, 208],
      "typeVersion": 4.2
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ $json.query }}",
        "additionalFields": {}
      },
      "id": "save-to-db",
      "name": "Save to Database",
      "type": "n8n-nodes-base.postgres",
      "position": [752, 400],
      "typeVersion": 1,
      "credentials": {
        "postgres": {
          "id": "Sy23a0Zvp6PlFt6V",
          "name": "Postgres matrix_bot_memory"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true }) }}",
        "options": {}
      },
      "id": "respond-1",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [960, 208],
      "typeVersion": 1.1
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Format Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Save and Send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Input": {
      "main": [
        [
          {
            "node": "Query Memory",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Context Code",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "OpenAI Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Query Memory": {
      "main": [
        [
          {
            "node": "Build Context Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save and Send": {
      "main": [
        [
          {
            "node": "Send to Matrix",
            "type": "main",
            "index": 0
          },
          {
            "node": "Save to Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Matrix": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Context Code": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveManualExecutions": true,
    "saveExecutionProgress": true
  }
}

1. Откройте PgAdmin 2. Выполните SQL команды из раздела «Подготовка базы данных» 3. Проверьте что таблица создана

1. Скопируйте JSON из раздела «JSON для импорта в n8n» 2. В n8n нажмите «+» > «Import from URL or paste JSON» 3. Вставьте JSON и нажмите «Import» 4. Откройте импортированный workflow

1. Откройте workflow 2. Найдите node «Query Memory» и установите credentials PostgreSQL 3. Найдите node «OpenAI Model» и установите credentials OpenAI API 4. Найдите node «Save to Database» и установите credentials PostgreSQL

1. Нажмите «Test» чтобы проверить webhook URL 2. Скопируйте webhook URL 3. Настройте Matrix сервер на отправку вебхуков на этот URL 4. Активируйте workflow (кнопка «Activate»)

Пользователь: Сколько будет 7 + 3? Бот: 7 + 3 = 10 ✨

Пользователь: А сколько было в предыдущем вычислении? Бот: В предыдущем вычислении было: 7 + 3 = 10 📝

Пользователь: Какой сегодня день? Бот: Сегодня День недели, число месяца 📅

Пользователь: А вчера какой был? Бот: Вчера был [предыдущий день] 📆

1. Проверьте что workflow активен 2. Проверьте credentials PostgreSQL и OpenAI 3. Проверьте что Matrix сервер отправляет вебхуки на правильный URL 4. Посмотрите Executions в n8n - там будут ошибки

Выполните SQL команду:

ALTER TABLE conversation_history 
ADD COLUMN sender_name VARCHAR(255);

1. Проверьте что SQL query в «Query Memory» выглядит правильно 2. Проверьте что сообщения сохраняются в БД (Look in Save to Database node output) 3. Проверьте что room_id совпадает в разных сообщениях

1. Проверьте что API key валидный 2. Проверьте что у вас есть баланс в OpenAI 3. Проверьте что используется правильная модель (gpt-4o-mini)

Query Memory возвращает сообщения в порядке DESC (новые сверху), но Build Context Code переворачивает их так чтобы новые сообщения были в конце контекста. Это важно чтобы AI правильно понял что является «последним» сообщением.

В Save and Send используется функция esc() которая экранирует одинарные кавычки ('') чтобы избежать SQL инъекций.

Если sender_name не пришел, используется sender ID Если сообщение не пришло, используется пустая строка Все это обрабатывается в Build Context Code и Save and Send

Версия Workflow: 236 Последнее обновление: 2025-11-29 Статус: Production Ready ✅

Contacts: Если возникли вопросы по workflow, смотрите логи в Executions

  • vm/matrix-bot/01-install.1764438935.txt.gz
  • Последнее изменение: 2025/11/29 17:55
  • admin