{ "name": "DBBot Unified (Ingest + RAG)", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "tg-inbox", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -640, -120 ], "id": "31d39bff-4f9a-4645-94b2-b38d4ebf0bf0", "name": "Webhook TG Inbox", "webhookId": "dbbot-tg-inbox-webhook" }, { "parameters": { "jsCode": "const body = $input.item.json.body || {};\nconst meta = body.metadata || {};\nconst rawText = (body.text || '').trim();\n\nif (!rawText) {\n return { json: { skip: true, reason: 'empty_text' } };\n}\n\nconst authorLabel = meta.author_label || meta.sender_name || 'Unknown';\nconst authorId = meta.sender_id || meta.author?.id || '0';\nconst repliedTo = meta.replied_to_author_label ? ` (в ответ ${meta.replied_to_author_label})` : '';\n\nreturn {\n json: {\n skip: false,\n text: `Автор ${authorLabel} (ID: ${authorId})${repliedTo}: ${rawText}`,\n metadata: {\n ...meta,\n original_text: rawText,\n source: meta.source || 'telegram_group',\n processed_at: new Date().toISOString()\n }\n }\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -416, -120 ], "id": "a9347d4b-fad0-4fb8-a23c-bcad6d2f3186", "name": "Normalize Telegram Payload" }, { "parameters": { "conditions": { "boolean": [ { "value1": "={{ $json.skip }}", "value2": true } ] }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ -192, -120 ], "id": "de52e6d1-98be-4c24-bc03-d90d7fa80b93", "name": "Skip Empty Text" }, { "parameters": { "mode": "insert", "qdrantCollection": { "__rl": true, "value": "telegram_kb", "mode": "list", "cachedResultName": "telegram_kb" }, "options": {} }, "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant", "typeVersion": 1.3, "position": [ 288, -120 ], "id": "2444f3f3-d57d-45fa-b80e-e40f73f0c70f", "name": "Qdrant Insert", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "jsCode": "const WINDOW_MS = 5 * 60 * 1000;\nconst MAX_MESSAGES = 3;\nconst MIN_MESSAGES_TO_FLUSH = 1;\n\nfunction aggregateBuffer(buffer) {\n const lines = buffer.messages.map((m) => {\n const repliedTo = m.replyTo ? ` -> ${m.replyTo}` : '';\n return `[${m.date}] ${m.author}${repliedTo}: ${m.text}`;\n });\n\n return {\n skip: false,\n text: [\n `Контекст диалога (${buffer.messages.length} сообщений, участники: ${buffer.participants.join(', ')})`,\n ...lines,\n ].join('\\n'),\n metadata: {\n ...buffer.lastMeta,\n context_id: buffer.contextId,\n context_message_count: buffer.messages.length,\n context_start_date: new Date(buffer.startTs).toISOString(),\n context_end_date: new Date(buffer.lastTs).toISOString(),\n context_participants: buffer.participants,\n context_mode: 'thread_time_window_5m_fast_flush'\n }\n };\n}\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.contextBuffers = staticData.contextBuffers || {};\n\nconst meta = $json.metadata || {};\nconst rawText = (meta.original_text || '').trim();\nif (!rawText) {\n return { json: { skip: true, reason: 'empty_original_text' } };\n}\n\nconst nowTs = meta.date ? new Date(meta.date).getTime() : Date.now();\nconst safeTs = Number.isNaN(nowTs) ? Date.now() : nowTs;\nconst chatId = String(meta.chat_id || meta.group_id || 'unknown_chat');\nconst threadId = String(meta.thread_id || meta.reply_to_message_id || meta.message_id || 'single');\nconst contextId = `${chatId}:${threadId}`;\n\nlet buffer = staticData.contextBuffers[contextId];\n\nif (buffer && safeTs - buffer.lastTs > WINDOW_MS) {\n const staleBuffer = buffer;\n buffer = null;\n\n const author = meta.author_label || meta.sender_name || 'Unknown';\n staticData.contextBuffers[contextId] = {\n contextId,\n startTs: safeTs,\n lastTs: safeTs,\n participants: [author],\n lastMeta: meta,\n messages: [\n {\n date: meta.date || new Date(safeTs).toISOString(),\n author,\n replyTo: meta.replied_to_author_label || '',\n text: rawText\n }\n ]\n };\n\n if (staleBuffer.messages.length >= MIN_MESSAGES_TO_FLUSH) {\n return { json: aggregateBuffer(staleBuffer) };\n }\n\n return { json: { skip: true, reason: 'buffer_reset_on_timeout', context_id: contextId } };\n}\n\nif (!buffer) {\n buffer = {\n contextId,\n startTs: safeTs,\n lastTs: safeTs,\n participants: [],\n lastMeta: meta,\n messages: []\n };\n}\n\nconst author = meta.author_label || meta.sender_name || 'Unknown';\nif (!buffer.participants.includes(author)) {\n buffer.participants.push(author);\n}\n\nbuffer.messages.push({\n date: meta.date || new Date(safeTs).toISOString(),\n author,\n replyTo: meta.replied_to_author_label || '',\n text: rawText\n});\nbuffer.lastMeta = meta;\nbuffer.lastTs = safeTs;\n\nconst shouldFlush =\n buffer.messages.length >= MAX_MESSAGES ||\n buffer.lastTs - buffer.startTs >= WINDOW_MS;\n\nif (!shouldFlush) {\n staticData.contextBuffers[contextId] = buffer;\n return {\n json: {\n skip: true,\n reason: 'buffering',\n context_id: contextId,\n buffered_messages: buffer.messages.length\n }\n };\n}\n\ndelete staticData.contextBuffers[contextId];\nreturn { json: aggregateBuffer(buffer) };" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 32, -120 ], "id": "b3866944-b7ca-4505-b89e-a5f3d331f43d", "name": "Build Context Block" }, { "parameters": { "conditions": { "boolean": [ { "value1": "={{ $json.skip }}", "value2": true } ] }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 160, -120 ], "id": "a343f1ca-2423-43e7-9b8d-ce4f48f1e5ff", "name": "Skip Buffered Context" }, { "parameters": { "jsonMode": "expressionData", "jsonData": "={{ $json.text }}", "options": {} }, "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", "typeVersion": 1.1, "position": [ 288, 56 ], "id": "3de4bfe4-bd8a-4f36-a9ec-8955f2f26f83", "name": "Document Loader" }, { "parameters": { "model": "qwen3-embedding:8b" }, "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", "typeVersion": 1, "position": [ 96, 56 ], "id": "5f48d9eb-ec81-4a00-b2fa-ec6a1fc2f009", "name": "Embeddings For Insert", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "updates": [ "message" ], "additionalFields": {} }, "type": "n8n-nodes-base.telegramTrigger", "typeVersion": 1.2, "position": [ -640, 288 ], "id": "163fbd89-644c-4858-a2ce-8c572a2d3593", "name": "Telegram Trigger", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "promptType": "define", "text": "=Ответь на вопрос пользователя, используя данные из Qdrant Vector Store: {{ $json.message.text }}", "options": {} }, "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 3.1, "position": [ -384, 288 ], "id": "e67a8860-3c52-407e-8fd7-1f481e3c5019", "name": "AI Agent" }, { "parameters": { "model": "qwen3:30b-a3b", "options": { "temperature": 0 } }, "type": "@n8n/n8n-nodes-langchain.lmChatOllama", "typeVersion": 1, "position": [ -352, 496 ], "id": "3c7db13e-1be9-4f2f-ab26-a613db89ea71", "name": "Ollama Chat Model", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "mode": "retrieve-as-tool", "toolDescription": "Всегда используй этот инструмент для ответа: в нем релевантные документы из базы знаний.", "qdrantCollection": { "__rl": true, "value": "telegram_kb", "mode": "list", "cachedResultName": "telegram_kb" }, "includeDocumentMetadata": true, "options": {} }, "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant", "typeVersion": 1.3, "position": [ -128, 528 ], "id": "2f204e2e-58e7-4514-9f3c-3e23949f8520", "name": "Qdrant Retrieve Tool", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "model": "qwen3-embedding:8b" }, "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", "typeVersion": 1, "position": [ -128, 704 ], "id": "e5d5f376-bb95-4a16-ab11-e5e3e7d74524", "name": "Embeddings For Retrieve", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 }, { "parameters": { "jsCode": "let rawText = $input.item.json.text || $input.item.json.response || $input.item.json.output || '';\nlet cleanText = rawText.replace(/[\\s\\S]*?<\\/think>/g, '').trim();\n\nif (!cleanText) {\n cleanText = 'Не смог сформировать ответ. Попробуйте переформулировать вопрос.';\n}\n\nreturn {\n json: {\n clean_response: cleanText\n }\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -128, 288 ], "id": "81f5d5e9-3f13-4d43-b8e2-7112e2ca63e4", "name": "Clean Agent Output" }, { "parameters": { "chatId": "={{ $('Telegram Trigger').item.json.message.from.id }}", "text": "={{ $json.clean_response }}", "additionalFields": { "appendAttribution": false } }, "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 96, 288 ], "id": "e8476b38-8344-4aaf-828e-5da8344b79d2", "name": "Send Telegram Reply", "retryOnFail": true, "maxTries": 5, "waitBetweenTries": 5000 } ], "pinData": {}, "connections": { "Webhook TG Inbox": { "main": [ [ { "node": "Normalize Telegram Payload", "type": "main", "index": 0 } ] ] }, "Normalize Telegram Payload": { "main": [ [ { "node": "Skip Empty Text", "type": "main", "index": 0 } ] ] }, "Skip Empty Text": { "main": [ [], [ { "node": "Build Context Block", "type": "main", "index": 0 } ] ] }, "Build Context Block": { "main": [ [ { "node": "Skip Buffered Context", "type": "main", "index": 0 } ] ] }, "Skip Buffered Context": { "main": [ [], [ { "node": "Qdrant Insert", "type": "main", "index": 0 } ] ] }, "Embeddings For Insert": { "ai_embedding": [ [ { "node": "Qdrant Insert", "type": "ai_embedding", "index": 0 } ] ] }, "Document Loader": { "ai_document": [ [ { "node": "Qdrant Insert", "type": "ai_document", "index": 0 } ] ] }, "Telegram Trigger": { "main": [ [ { "node": "AI Agent", "type": "main", "index": 0 } ] ] }, "Ollama Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 } ] ] }, "Qdrant Retrieve Tool": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }, "Embeddings For Retrieve": { "ai_embedding": [ [ { "node": "Qdrant Retrieve Tool", "type": "ai_embedding", "index": 0 } ] ] }, "AI Agent": { "main": [ [ { "node": "Clean Agent Output", "type": "main", "index": 0 } ] ] }, "Clean Agent Output": { "main": [ [ { "node": "Send Telegram Reply", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1", "availableInMCP": false }, "tags": [] }