Flush context blocks faster to avoid delayed indexing and use sender_id-based labels when Telegram sender objects are unavailable in historical messages. Co-authored-by: Cursor <cursoragent@cursor.com>
425 lines
13 KiB
JSON
425 lines
13 KiB
JSON
{
|
||
"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(/<think>[\\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": []
|
||
}
|