Это старая версия документа!
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);
Nodes в Workflow
1. Webhook
Тип: 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
}
}
2. Format Input
Тип: 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
}
3. Query Memory
Тип: 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 сообщений (или пусто если их нет)
4. Build Context Code
Тип: 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
}
5. AI Agent
Тип: @n8n/n8n-nodes-langchain.agent Назначение: LangChain Agent использующий OpenAI модель
Параметры:
- Prompt Type: define
- Text:
json.text - System Message: (см. ниже - НЕ МЕНЯТЬ!)
System Message:
Ты полезный AI ассистент для Matrix чата по имени Jarvice🤖. Ты помощник Администратора (Николай). Отвечай кратко, используй emoji. Обязательно используй контекст предыдущих сообщений для лучшего ответа. Отвечай с числовыми результатами и референциями на предыдущие вычисления.
6. OpenAI Model
Тип: @n8n/n8n-nodes-langchain.lmChatOpenAi Назначение: Подключение к OpenAI API
Параметры:
- Model: gpt-4o-mini
- Credentials: aitunnel (ваш OpenAI API key)
7. Save and Send
Тип: 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..."
}
8. Send to Matrix
Тип: n8n-nodes-base.httpRequest Назначение: Отправляет ответ в Matrix через Matrix Bot сервис
Параметры:
- Method: POST
- Send Body: true
- Specify Body: json
JSON Body:
{{ JSON.stringify({room_id: $json.room_id, message: $json.message, sender_id: $json.sender_id, sender_name: $json.sender_name}) }}
9. Save to Database
Тип: n8n-nodes-base.postgres Назначение: Сохраняет разговор в PostgreSQL
Параметры:
- Operation: executeQuery
- Query:
json.query
10. Respond to Webhook
Тип: n8n-nodes-base.respondToWebhook Назначение: Отправляет успешный ответ на вебхук
Параметры:
- Respond With: json
- Response Body:
true
Connections (Связи между nodes)
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 для импорта в n8n
Используйте этот 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. Подготовка базы данных
1. Откройте PgAdmin 2. Выполните SQL команды из раздела «Подготовка базы данных» 3. Проверьте что таблица создана
2. Импорт workflow в n8n
1. Скопируйте JSON из раздела «JSON для импорта в n8n» 2. В n8n нажмите «+» > «Import from URL or paste JSON» 3. Вставьте JSON и нажмите «Import» 4. Откройте импортированный workflow
3. Настройка credentials
1. Откройте workflow 2. Найдите node «Query Memory» и установите credentials PostgreSQL 3. Найдите node «OpenAI Model» и установите credentials OpenAI API 4. Найдите node «Save to Database» и установите credentials PostgreSQL
4. Проверка и активация
1. Нажмите «Test» чтобы проверить webhook URL 2. Скопируйте webhook URL 3. Настройте Matrix сервер на отправку вебхуков на этот URL 4. Активируйте workflow (кнопка «Activate»)
Примеры использования
Пример 1: Простой расчет
Пользователь: Сколько будет 7 + 3? Бот: 7 + 3 = 10 ✨
Пользователь: А сколько было в предыдущем вычислении? Бот: В предыдущем вычислении было: 7 + 3 = 10 📝
Пример 2: Разговор с контекстом
Пользователь: Какой сегодня день? Бот: Сегодня День недели, число месяца 📅
Пользователь: А вчера какой был? Бот: Вчера был [предыдущий день] 📆
Решение проблем
Бот не отвечает
1. Проверьте что workflow активен 2. Проверьте credentials PostgreSQL и OpenAI 3. Проверьте что Matrix сервер отправляет вебхуки на правильный URL 4. Посмотрите Executions в n8n - там будут ошибки
Сообщение: column "sender_name" does not exist
Выполните 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 совпадает в разных сообщениях
OpenAI API error
1. Проверьте что API key валидный 2. Проверьте что у вас есть баланс в OpenAI 3. Проверьте что используется правильная модель (gpt-4o-mini)
Технические детали
Порядок сообщений в контексте
Query Memory возвращает сообщения в порядке DESC (новые сверху), но Build Context Code переворачивает их так чтобы новые сообщения были в конце контекста. Это важно чтобы AI правильно понял что является «последним» сообщением.
Экранирование SQL
В Save and Send используется функция esc() которая экранирует одинарные кавычки ( → '') чтобы избежать SQL инъекций.
Fallback значения
Если sender_name не пришел, используется sender ID Если сообщение не пришло, используется пустая строка Все это обрабатывается в Build Context Code и Save and Send
Дополнительная информация
Версия Workflow: 236 Последнее обновление: 2025-11-29 Статус: Production Ready ✅
Contacts: Если возникли вопросы по workflow, смотрите логи в Executions