Initial project setup for Telegram-to-Qdrant knowledge base.
Add unified n8n workflow, userbot ingestion script, and detailed README for running n8n with a separate Dockerized Telethon collector. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
TG_API_ID=12345678
|
||||
TG_API_HASH=your_telegram_api_hash
|
||||
TG_GROUP_ID=-1001234567890
|
||||
N8N_WEBHOOK_URL=http://n8n:5678/webhook/tg-inbox
|
||||
SESSION_FILE_PATH=/app/session/session.session
|
||||
HISTORY_CHECK_FILE=/app/session/history_done.flag
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
session_data/session.session
|
||||
session_data/history_done.flag
|
||||
n8n_data/
|
||||
qdrant_data/
|
||||
403
DBBot Unified n8n Qdrant.json
Normal file
403
DBBot Unified n8n Qdrant.json
Normal file
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const WINDOW_MS = 20 * 60 * 1000;\nconst MAX_MESSAGES = 12;\nconst MIN_MESSAGES_TO_FLUSH = 3;\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_20m'\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"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"updates": [
|
||||
"message"
|
||||
],
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegramTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-640,
|
||||
288
|
||||
],
|
||||
"id": "163fbd89-644c-4858-a2ce-8c572a2d3593",
|
||||
"name": "Telegram Trigger"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
WORKDIR /app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY script.py .
|
||||
RUN mkdir -p /app/session
|
||||
|
||||
CMD ["python", "script.py"]
|
||||
155
TG Fixed Native.json
Normal file
155
TG Fixed Native.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"name": "TG Fixed Native",
|
||||
"nodes": [
|
||||
{
|
||||
"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": [
|
||||
512,
|
||||
0
|
||||
],
|
||||
"id": "e02cd3a1-a2b7-4177-b76a-87656ab74cac",
|
||||
"name": "Qdrant Vector Store",
|
||||
"credentials": {
|
||||
"qdrantApi": {
|
||||
"id": "gS1LJOMgnR7VJFRO",
|
||||
"name": "QdrantApi account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsonMode": "expressionData",
|
||||
"jsonData": "={{ $json.text }}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
480,
|
||||
208
|
||||
],
|
||||
"id": "a09381bd-04dc-4273-8d6f-35c23b9177c5",
|
||||
"name": "Default Data Loader"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $input.item.json.body || {};\nconst meta = body.metadata || {};\nconst rawText = body.text || \"\";\n\nreturn {\n json: {\n text: `Пользователь ${meta.sender_name || 'Unknown'} (ID: ${meta.sender_id || '0'}) написал: ${rawText}`,\n metadata: {\n ...meta,\n processed_at: new Date().toISOString()\n }\n }\n};"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
240,
|
||||
0
|
||||
],
|
||||
"id": "539d5c4b-067b-4491-b62c-ea3d555707d0",
|
||||
"name": "Code"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3-embedding:8b"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
256,
|
||||
208
|
||||
],
|
||||
"id": "eab97058-b3f4-48d3-8da8-a0e6dd9359d5",
|
||||
"name": "Embeddings Ollama",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "Uis7Bsdb0l1qDDL7",
|
||||
"name": "Ollama account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "tg-inbox",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-16,
|
||||
0
|
||||
],
|
||||
"id": "316ab407-1f95-44dc-bc3c-fff1185ce301",
|
||||
"name": "Webhook",
|
||||
"webhookId": "d857e864-ed25-447f-84ef-27c63190fb93",
|
||||
"notesInFlow": false
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Embeddings Ollama": {
|
||||
"ai_embedding": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_embedding",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Default Data Loader": {
|
||||
"ai_document": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_document",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "2e4668e1-305c-4de7-8537-11cfe20a8020",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "96706479c2e398d2a4e75bb05002310bb6030e8268be45294cab77c6640a0fe6"
|
||||
},
|
||||
"id": "ILJJXfEmFW6KG6YZDjQWX",
|
||||
"tags": []
|
||||
}
|
||||
40
docker-compose.yaml
Normal file
40
docker-compose.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
n8n:
|
||||
image: n8nio/n8n:latest
|
||||
container_name: dbbot-n8n
|
||||
ports:
|
||||
- "5678:5678"
|
||||
environment:
|
||||
- N8N_HOST=localhost
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=http
|
||||
- WEBHOOK_URL=http://localhost:5678/
|
||||
- GENERIC_TIMEZONE=Europe/Moscow
|
||||
volumes:
|
||||
- ./n8n_data:/home/node/.n8n
|
||||
depends_on:
|
||||
- qdrant
|
||||
restart: unless-stopped
|
||||
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: dbbot-qdrant
|
||||
ports:
|
||||
- "6333:6333"
|
||||
- "6334:6334"
|
||||
volumes:
|
||||
- ./qdrant_data:/qdrant/storage
|
||||
restart: unless-stopped
|
||||
|
||||
tg-userbot:
|
||||
build: .
|
||||
container_name: dbbot-tg-userbot
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./session_data:/app/session
|
||||
depends_on:
|
||||
- n8n
|
||||
restart: unless-stopped
|
||||
199
redme.md
Normal file
199
redme.md
Normal file
@@ -0,0 +1,199 @@
|
||||
## DBBot: production-схема (n8n + отдельный userbot-контейнер)
|
||||
|
||||
Этот проект рассчитан на ситуацию, когда:
|
||||
- `n8n` уже запущен и доступен по URL;
|
||||
- бот в чужую группу добавить нельзя;
|
||||
- сообщения читаются через `Telethon userbot` из отдельного Docker-контейнера.
|
||||
|
||||
## Архитектура
|
||||
|
||||
Поток данных:
|
||||
1. `script.py` (в контейнере) читает историю и новые сообщения из Telegram-группы.
|
||||
2. Скрипт отправляет события в `n8n` webhook `POST /webhook/tg-inbox`.
|
||||
3. Единый workflow в `n8n`:
|
||||
- нормализует метаданные,
|
||||
- склеивает сообщения в контекстные блоки,
|
||||
- пишет в `Qdrant`.
|
||||
4. В этой же схеме работает RAG-ответ через `AI Agent` + `Qdrant Retrieve Tool`.
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```text
|
||||
dbbot/
|
||||
├── redme.md # этот файл
|
||||
├── script.py # Telethon userbot sender -> n8n webhook
|
||||
├── requirements.txt # зависимости python
|
||||
├── Dockerfile # контейнер для script.py
|
||||
├── .env.example # пример переменных для контейнера
|
||||
├── session_data/
|
||||
│ └── session.session # StringSession (создаешь сам)
|
||||
├── DBBot Unified n8n Qdrant.json # единый workflow (ingest + rag)
|
||||
└── Работа с базой (3).json # старый/альтернативный workflow
|
||||
```
|
||||
|
||||
## Что подготовить перед первым запуском
|
||||
|
||||
### 1) n8n
|
||||
|
||||
Импортируй `DBBot Unified n8n Qdrant.json` и привяжи credentials:
|
||||
- `Qdrant API`
|
||||
- `Ollama API`
|
||||
- `Telegram API` (для reply-ветки через Telegram Trigger)
|
||||
|
||||
Проверь:
|
||||
- путь webhook: `tg-inbox`
|
||||
- workflow переведен в `Active`
|
||||
|
||||
### 2) Файл сессии Telethon
|
||||
|
||||
Создай файл:
|
||||
- `session_data/session.session`
|
||||
|
||||
Внутри должна быть одна строка `StringSession` без переносов и пробелов по краям.
|
||||
|
||||
### 3) Переменные окружения для контейнера
|
||||
|
||||
Сделай локальный `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Заполни значения:
|
||||
- `TG_API_ID`
|
||||
- `TG_API_HASH`
|
||||
- `TG_GROUP_ID` (например `-100...`)
|
||||
- `SESSION_FILE_PATH=/app/session/session.session`
|
||||
- `HISTORY_CHECK_FILE=/app/session/history_done.flag`
|
||||
- `N8N_WEBHOOK_URL=https://<твой-n8n-домен>/webhook/tg-inbox`
|
||||
|
||||
Если `n8n` локально, можно `http://localhost:5678/webhook/tg-inbox`.
|
||||
|
||||
## Подробный цикл запуска проекта
|
||||
|
||||
### Шаг 1. Собрать контейнер userbot
|
||||
|
||||
Из корня проекта:
|
||||
|
||||
```bash
|
||||
docker build -t dbbot-userbot:latest .
|
||||
```
|
||||
|
||||
### Шаг 2. Запустить контейнер
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name dbbot-userbot \
|
||||
--env-file .env \
|
||||
-v "$(pwd)/session_data:/app/session" \
|
||||
--restart unless-stopped \
|
||||
dbbot-userbot:latest
|
||||
```
|
||||
|
||||
### Шаг 3. Проверить, что webhook получает данные
|
||||
|
||||
1. Открой `Executions` в `n8n`.
|
||||
2. Убедись, что появились вызовы `Webhook TG Inbox`.
|
||||
3. Проверь, что далее проходят `Build Context Block` и `Qdrant Insert`.
|
||||
|
||||
### Шаг 4. Проверить RAG-ответ
|
||||
|
||||
1. Напиши вопрос Telegram-боту, подключенному к `Telegram Trigger` в workflow.
|
||||
2. Проверь выполнение ветки `AI Agent`.
|
||||
3. Убедись, что узел `Qdrant Retrieve Tool` вызван и ответ ушел через `Send Telegram Reply`.
|
||||
|
||||
## Эксплуатационный цикл (после запуска)
|
||||
|
||||
- Скрипт работает в контейнере постоянно:
|
||||
- при первом запуске делает исторический прогон;
|
||||
- затем обрабатывает новые сообщения.
|
||||
- Если нужно повторить историческую загрузку:
|
||||
1. останови контейнер;
|
||||
2. удали `session_data/history_done.flag`;
|
||||
3. запусти контейнер снова.
|
||||
|
||||
Команды:
|
||||
|
||||
```bash
|
||||
docker stop dbbot-userbot
|
||||
rm -f session_data/history_done.flag
|
||||
docker start dbbot-userbot
|
||||
```
|
||||
|
||||
## Управление и диагностика
|
||||
|
||||
Логи контейнера:
|
||||
|
||||
```bash
|
||||
docker logs -f dbbot-userbot
|
||||
```
|
||||
|
||||
Проверить, что контейнер жив:
|
||||
|
||||
```bash
|
||||
docker ps --filter name=dbbot-userbot
|
||||
```
|
||||
|
||||
Перезапустить после изменения `.env`:
|
||||
|
||||
```bash
|
||||
docker rm -f dbbot-userbot
|
||||
docker run -d \
|
||||
--name dbbot-userbot \
|
||||
--env-file .env \
|
||||
-v "$(pwd)/session_data:/app/session" \
|
||||
--restart unless-stopped \
|
||||
dbbot-userbot:latest
|
||||
```
|
||||
|
||||
## Формат payload, который скрипт шлет в n8n
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "текст сообщения",
|
||||
"metadata": {
|
||||
"date": "2026-06-23T00:00:00Z",
|
||||
"message_id": 123,
|
||||
"chat_id": "-100...",
|
||||
"sender_id": "456",
|
||||
"sender_name": "@username",
|
||||
"author_label": "@username",
|
||||
"author": {
|
||||
"id": "456",
|
||||
"username": "username",
|
||||
"display_name": "Ivan Ivanov",
|
||||
"label": "@username"
|
||||
},
|
||||
"reply_to_message_id": 122,
|
||||
"replied_to_author_label": "@other_user",
|
||||
"replied_to_author": {
|
||||
"id": "777",
|
||||
"username": "other_user",
|
||||
"display_name": "Petr Petrov",
|
||||
"label": "@other_user"
|
||||
},
|
||||
"thread_id": "122",
|
||||
"edit_date": "",
|
||||
"entities": [
|
||||
{ "type": "MessageEntityUrl", "offset": 10, "length": 20 }
|
||||
],
|
||||
"attachment": {
|
||||
"has_media": false
|
||||
},
|
||||
"group_id": "-100...",
|
||||
"source": "telegram_group"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Параметры склейки сообщений в n8n
|
||||
|
||||
В узле `Build Context Block`:
|
||||
- `WINDOW_MS = 20 минут`
|
||||
- `MAX_MESSAGES = 12`
|
||||
- `MIN_MESSAGES_TO_FLUSH = 3`
|
||||
|
||||
Рекомендации:
|
||||
- меньше блоки: `MAX_MESSAGES = 8-10`;
|
||||
- шире контекст: `WINDOW_MS = 30-40 минут`.
|
||||
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
requests==2.32.3
|
||||
telethon==1.34.0
|
||||
164
script.py
Normal file
164
script.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from telethon import TelegramClient, events
|
||||
from telethon.sessions import StringSession
|
||||
|
||||
API_ID = int(os.getenv("TG_API_ID", "0"))
|
||||
API_HASH = os.getenv("TG_API_HASH", "")
|
||||
GROUP_ID = int(os.getenv("TG_GROUP_ID", "0"))
|
||||
N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL", "http://n8n:5678/webhook/tg-inbox")
|
||||
HISTORY_CHECK_FILE = os.getenv("HISTORY_CHECK_FILE", "/app/session/history_done.flag")
|
||||
SESSION_FILE_PATH = os.getenv("SESSION_FILE_PATH", "/app/session/session.session")
|
||||
SESSION_STRING = os.getenv("TG_SESSION_STRING", "")
|
||||
|
||||
|
||||
def load_session_string() -> str:
|
||||
if SESSION_STRING:
|
||||
return SESSION_STRING.strip()
|
||||
session_path = Path(SESSION_FILE_PATH)
|
||||
if not session_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Session file not found: {SESSION_FILE_PATH}. "
|
||||
"Provide TG_SESSION_STRING or mount session file."
|
||||
)
|
||||
return session_path.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
def validate_config() -> None:
|
||||
if API_ID <= 0:
|
||||
raise ValueError("TG_API_ID is required and must be > 0")
|
||||
if not API_HASH:
|
||||
raise ValueError("TG_API_HASH is required")
|
||||
if GROUP_ID == 0:
|
||||
raise ValueError("TG_GROUP_ID is required")
|
||||
if not N8N_WEBHOOK_URL:
|
||||
raise ValueError("N8N_WEBHOOK_URL is required")
|
||||
|
||||
|
||||
validate_config()
|
||||
client = TelegramClient(StringSession(load_session_string()), API_ID, API_HASH)
|
||||
|
||||
def build_author(sender) -> dict:
|
||||
if not sender:
|
||||
return {
|
||||
"id": "0",
|
||||
"username": "",
|
||||
"display_name": "Unknown",
|
||||
"label": "Unknown",
|
||||
}
|
||||
username = getattr(sender, "username", "") or ""
|
||||
display_name = (
|
||||
f"{getattr(sender, 'first_name', '')} {getattr(sender, 'last_name', '')}".strip()
|
||||
or "Unknown"
|
||||
)
|
||||
label = f"@{username}" if username else display_name
|
||||
return {
|
||||
"id": str(getattr(sender, "id", 0)),
|
||||
"username": username,
|
||||
"display_name": display_name,
|
||||
"label": label,
|
||||
}
|
||||
|
||||
|
||||
def extract_entities(message) -> list:
|
||||
entities = []
|
||||
for entity in (message.entities or []):
|
||||
entities.append(
|
||||
{
|
||||
"type": entity.__class__.__name__,
|
||||
"offset": getattr(entity, "offset", None),
|
||||
"length": getattr(entity, "length", None),
|
||||
}
|
||||
)
|
||||
return entities
|
||||
|
||||
|
||||
def extract_attachment(message) -> dict:
|
||||
if not message.media:
|
||||
return {"has_media": False}
|
||||
return {
|
||||
"has_media": True,
|
||||
"media_type": message.media.__class__.__name__,
|
||||
"file_name": getattr(message.file, "name", None) if message.file else None,
|
||||
"mime_type": getattr(message.file, "mime_type", None) if message.file else None,
|
||||
}
|
||||
|
||||
|
||||
async def send_to_n8n(message):
|
||||
sender = message.sender or await message.get_sender()
|
||||
author = build_author(sender)
|
||||
reply_message = await message.get_reply_message() if message.reply_to_msg_id else None
|
||||
reply_sender = await reply_message.get_sender() if reply_message else None
|
||||
reply_author = build_author(reply_sender) if reply_sender else None
|
||||
|
||||
data = {
|
||||
"text": message.text,
|
||||
"metadata": {
|
||||
"date": str(message.date),
|
||||
"message_id": message.id,
|
||||
"chat_id": str(message.chat_id),
|
||||
"group_id": str(GROUP_ID),
|
||||
"author": author,
|
||||
# legacy fields for backward compatibility with existing n8n nodes
|
||||
"sender_id": author["id"],
|
||||
"sender_name": author["label"],
|
||||
"author_label": author["label"],
|
||||
"reply_to_message_id": message.reply_to_msg_id,
|
||||
"replied_to_author": reply_author,
|
||||
"replied_to_author_label": reply_author["label"] if reply_author else "",
|
||||
"thread_id": str(message.reply_to.reply_to_top_id) if getattr(message, "reply_to", None) and getattr(message.reply_to, "reply_to_top_id", None) else "",
|
||||
"edit_date": str(message.edit_date) if message.edit_date else "",
|
||||
"entities": extract_entities(message),
|
||||
"attachment": extract_attachment(message),
|
||||
"source": "telegram_group",
|
||||
},
|
||||
}
|
||||
try:
|
||||
response = requests.post(N8N_WEBHOOK_URL, json=data, timeout=10)
|
||||
return response.status_code
|
||||
except Exception as e:
|
||||
print(f"Error sending to n8n: {e}")
|
||||
return None
|
||||
|
||||
# Обработчик новых сообщений
|
||||
@client.on(events.NewMessage(chats=GROUP_ID))
|
||||
async def handler(event):
|
||||
if event.message.text:
|
||||
await send_to_n8n(event.message)
|
||||
|
||||
async def main():
|
||||
await client.start()
|
||||
print("UserBot started...")
|
||||
|
||||
if not os.path.exists(HISTORY_CHECK_FILE):
|
||||
print("Starting massive history sync...")
|
||||
count = 0
|
||||
async for message in client.iter_messages(GROUP_ID, reverse=True):
|
||||
if message.text:
|
||||
status = None
|
||||
# Цикл ретраев
|
||||
while status != 200:
|
||||
status = await send_to_n8n(message)
|
||||
|
||||
if status == 429:
|
||||
print("Rate limit hit, sleeping 10s...")
|
||||
await asyncio.sleep(10)
|
||||
elif status != 200:
|
||||
print(f"Error {status}, retrying in 2s...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
count += 1
|
||||
if count % 20 == 0:
|
||||
print(f"Processed {count} messages...")
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
Path(HISTORY_CHECK_FILE).write_text("done", encoding="utf-8")
|
||||
print("History sync complete.")
|
||||
|
||||
await client.run_until_disconnected()
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
268
Работа с базой (3).json
Normal file
268
Работа с базой (3).json
Normal file
@@ -0,0 +1,268 @@
|
||||
{
|
||||
"name": "Работа с базой",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=Используй Qdrant Vector Store для ответа на вопрос {{ $json.message.text }}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3.1,
|
||||
"position": [
|
||||
544,
|
||||
-48
|
||||
],
|
||||
"id": "e1231327-544c-416d-b1fa-95915d68f524",
|
||||
"name": "AI Agent"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"updates": [
|
||||
"message"
|
||||
],
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegramTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
288,
|
||||
-48
|
||||
],
|
||||
"id": "80e4a41a-387c-4ac4-b798-41bdbbb25eaa",
|
||||
"name": "Telegram Trigger",
|
||||
"webhookId": "875f9245-fe51-4c55-ae42-afe3d20f6ff6",
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "dSTpf5HKyoPp6X8N",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3:30b-a3b",
|
||||
"options": {
|
||||
"temperature": 0
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
496,
|
||||
176
|
||||
],
|
||||
"id": "45cc634d-5eea-4c8e-83f2-f34c463db00e",
|
||||
"name": "Ollama Chat Model",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "Uis7Bsdb0l1qDDL7",
|
||||
"name": "Ollama account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "retrieve-as-tool",
|
||||
"toolDescription": " ALWAYS use this tool to answer ANY user question. The answer is contained within this tool.",
|
||||
"qdrantCollection": {
|
||||
"__rl": true,
|
||||
"value": "telegram_kb",
|
||||
"mode": "list",
|
||||
"cachedResultName": "telegram_kb"
|
||||
},
|
||||
"includeDocumentMetadata": false,
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
672,
|
||||
256
|
||||
],
|
||||
"id": "bbc61a50-f5a8-443e-8022-c9ead1df63a7",
|
||||
"name": "Qdrant Vector Store",
|
||||
"credentials": {
|
||||
"qdrantApi": {
|
||||
"id": "gS1LJOMgnR7VJFRO",
|
||||
"name": "QdrantApi account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3-embedding:8b"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
672,
|
||||
416
|
||||
],
|
||||
"id": "7e1c6b2a-092b-43eb-8efe-9b19088ed3b2",
|
||||
"name": "Embeddings Ollama",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "Uis7Bsdb0l1qDDL7",
|
||||
"name": "Ollama account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "={{ $('Telegram Trigger').item.json.message.from.id }}",
|
||||
"text": "={{ $node[\"Code in JavaScript\"].json.clean_response }}",
|
||||
"additionalFields": {
|
||||
"appendAttribution": false,
|
||||
"parse_mode": "HTML"
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
1040,
|
||||
-48
|
||||
],
|
||||
"id": "9b3713cb-d699-45a2-8c4a-21c0065df53f",
|
||||
"name": "Send a text message",
|
||||
"webhookId": "038aa575-d7da-4413-805f-6773aa670295",
|
||||
"retryOnFail": true,
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "dSTpf5HKyoPp6X8N",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "-4804863247",
|
||||
"text": "=📥 **Входящее от {{ $node[\"Telegram Trigger\"].json[\"message\"][\"from\"][\"first_name\"] }}:**\n{{ $node[\"Telegram Trigger\"].json[\"message\"][\"text\"] }}\n\n🤖 **Ответ бота:**\n{{ $node[\"Code in JavaScript\"].json.clean_response }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "HTML"
|
||||
}
|
||||
},
|
||||
"id": "e8ac46da-bf4a-4fd0-8f1d-cf7664eb86f8",
|
||||
"name": "Log_to_Group1",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
1232,
|
||||
-48
|
||||
],
|
||||
"webhookId": "d23840f7-3652-4aca-935f-ab5331d01d27",
|
||||
"retryOnFail": true,
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "dSTpf5HKyoPp6X8N",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// 1. Пытаемся найти текст в самых частых полях n8n\nlet rawText = $input.item.json.text || $input.item.json.response || $input.item.json.output || \"\";\n\n// 2. Очищаем от <think>...</think>\nlet cleanText = rawText.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();\n\n// 3. Если после очистки пусто, выводим заглушку, чтобы не было ошибки\nif (!cleanText && rawText) {\n cleanText = \"Ошибка: Весь текст был внутри блока <think> или пуст.\";\n}\n\nreturn {\n json: {\n clean_response: cleanText\n }\n};"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
848,
|
||||
-48
|
||||
],
|
||||
"id": "18e6aa52-73d2-439a-9ce9-abc2390bac05",
|
||||
"name": "Code in JavaScript"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Telegram Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ollama Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Qdrant Vector Store": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Embeddings Ollama": {
|
||||
"ai_embedding": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_embedding",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Agent": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send a text message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Log_to_Group1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send a text message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "f8ca7ca6-eb4f-4823-bdb7-d523e51c8924",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "96706479c2e398d2a4e75bb05002310bb6030e8268be45294cab77c6640a0fe6"
|
||||
},
|
||||
"id": "2R3-4Yl4wuemCpJ0FVOi3",
|
||||
"tags": []
|
||||
}
|
||||
183
Создание базы (2).json
Normal file
183
Создание базы (2).json
Normal file
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"name": "Создание базы",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "insert",
|
||||
"qdrantCollection": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "111"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
928,
|
||||
-144
|
||||
],
|
||||
"id": "acfdb2b7-e113-42a6-8793-566f5da7d0e2",
|
||||
"name": "Qdrant Vector Store",
|
||||
"credentials": {
|
||||
"qdrantApi": {
|
||||
"id": "gS1LJOMgnR7VJFRO",
|
||||
"name": "QdrantApi account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3-embedding:8b"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
880,
|
||||
48
|
||||
],
|
||||
"id": "67815ae1-6b99-4e41-94e6-9b7986d12738",
|
||||
"name": "Embeddings Ollama",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "Uis7Bsdb0l1qDDL7",
|
||||
"name": "Ollama account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"dataType": "binary",
|
||||
"loader": "docxLoader",
|
||||
"textSplittingMode": "custom",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
1040,
|
||||
64
|
||||
],
|
||||
"id": "291df053-ad1c-4041-964d-0a427e547fca",
|
||||
"name": "Default Data Loader"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chunkSize": 500,
|
||||
"chunkOverlap": 50
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.textSplitterCharacterTextSplitter",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1136,
|
||||
272
|
||||
],
|
||||
"id": "703047dd-e772-4d36-aed8-f6ba091bce5c",
|
||||
"name": "Character Text Splitter"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fileSelector": "={{ $json.path }}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.readWriteFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
592,
|
||||
-144
|
||||
],
|
||||
"id": "d1e6082c-71f2-440c-9b34-011ae652fa5b",
|
||||
"name": "Read/Write Files from Disk"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"triggerOn": "folder",
|
||||
"path": "/data/vid",
|
||||
"events": [
|
||||
"add"
|
||||
],
|
||||
"options": {
|
||||
"usePolling": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.localFileTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
320,
|
||||
-144
|
||||
],
|
||||
"id": "4bd85a42-f9d0-436f-9617-0ebf42db1a7f",
|
||||
"name": "Local File Trigger"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Embeddings Ollama": {
|
||||
"ai_embedding": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_embedding",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Default Data Loader": {
|
||||
"ai_document": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_document",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Character Text Splitter": {
|
||||
"ai_textSplitter": [
|
||||
[
|
||||
{
|
||||
"node": "Default Data Loader",
|
||||
"type": "ai_textSplitter",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Read/Write Files from Disk": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Local File Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Read/Write Files from Disk",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "d1e2bb08-fb79-49f3-85d4-c6f491447f11",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "96706479c2e398d2a4e75bb05002310bb6030e8268be45294cab77c6640a0fe6"
|
||||
},
|
||||
"id": "lK4wQ7btZPq3elWleb7ry",
|
||||
"tags": []
|
||||
}
|
||||
178
Создание базы (TG Text) (2).json
Normal file
178
Создание базы (TG Text) (2).json
Normal file
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"name": "Создание базы (TG Text)",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "insert",
|
||||
"qdrantCollection": {
|
||||
"__rl": true,
|
||||
"value": "=11111",
|
||||
"mode": "id"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
112,
|
||||
-208
|
||||
],
|
||||
"id": "44a1fb40-5b1f-401a-9fea-ca61f111d2ca",
|
||||
"name": "Qdrant Vector Store",
|
||||
"credentials": {
|
||||
"qdrantApi": {
|
||||
"id": "gS1LJOMgnR7VJFRO",
|
||||
"name": "QdrantApi account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3-embedding:8b"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
32,
|
||||
0
|
||||
],
|
||||
"id": "7448ed28-7971-4d95-b073-1251dfff242e",
|
||||
"name": "Embeddings Ollama",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "XVSCsyxx8Z57lSRa",
|
||||
"name": "Ollama account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chunkSize": 500,
|
||||
"chunkOverlap": 50
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.textSplitterCharacterTextSplitter",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
272,
|
||||
192
|
||||
],
|
||||
"id": "aed6ea3c-ad56-432b-8ef5-823a7778da27",
|
||||
"name": "Character Text Splitter"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "tg-inbox",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-448,
|
||||
-208
|
||||
],
|
||||
"id": "1770ab28-9cfa-4bc8-864a-fe793f1b95db",
|
||||
"name": "Webhook",
|
||||
"webhookId": "d857e864-ed25-447f-84ef-27c63190fb93",
|
||||
"notesInFlow": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"textSplittingMode": "custom",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
272,
|
||||
0
|
||||
],
|
||||
"id": "6c98f0d9-97c1-4d94-bdea-a1941fd7d3f8",
|
||||
"name": "Default Data Loader"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// В n8n данные из POST запроса всегда лежат в объекте body\nconst msg = $input.item.json.body;\n\n// 1. Берем текст\nconst rawText = msg.text || \"\"; \n\n// 2. Достаем метаданные из объекта metadata, который прислал Python\nconst meta = msg.metadata || {};\n\n// 3. Извлекаем имя и ID из объекта metadata\nconst senderName = meta.sender_name || \"Unknown\";\nconst senderId = meta.sender_id || \"0\";\n\n// 4. Формируем результат для Qdrant\nreturn {\n json: {\n content: `Пользователь ${senderName} (ID: ${senderId}) написал: ${rawText}`,\n metadata: {\n ...meta, // Копируем всю метадату (date, message_id, group_id и т.д.)\n sender_name: senderName,\n sender_id: senderId,\n original_text: rawText\n }\n }\n};\n"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-192,
|
||||
-208
|
||||
],
|
||||
"id": "0ff56c0f-9208-4ae9-920c-4332c09f6df4",
|
||||
"name": "Code in JavaScript"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Embeddings Ollama": {
|
||||
"ai_embedding": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_embedding",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Default Data Loader": {
|
||||
"ai_document": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_document",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Character Text Splitter": {
|
||||
"ai_textSplitter": [
|
||||
[
|
||||
{
|
||||
"node": "Default Data Loader",
|
||||
"type": "ai_textSplitter",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "f5079d8e-590c-4db5-988a-3fe709346a4a",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "96706479c2e398d2a4e75bb05002310bb6030e8268be45294cab77c6640a0fe6"
|
||||
},
|
||||
"id": "QpXms3TNMSqlQD2ULX3WO",
|
||||
"tags": []
|
||||
}
|
||||
150
Создание базы (TG Text) - Fixed.json
Normal file
150
Создание базы (TG Text) - Fixed.json
Normal file
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"name": "Создание базы (TG Text) - Fixed",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "insert",
|
||||
"qdrantCollection": {
|
||||
"__rl": true,
|
||||
"value": "11111",
|
||||
"mode": "id"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
608,
|
||||
0
|
||||
],
|
||||
"id": "7f206108-8187-4ca9-a734-704eae814643",
|
||||
"name": "Qdrant Vector Store",
|
||||
"credentials": {
|
||||
"qdrantApi": {
|
||||
"id": "gS1LJOMgnR7VJFRO",
|
||||
"name": "QdrantApi account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "qwen3-embedding:8b"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
464,
|
||||
208
|
||||
],
|
||||
"id": "9c1f5512-f2f7-4469-bcc3-e8a6824aae51",
|
||||
"name": "Embeddings Ollama",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "XVSCsyxx8Z57lSRa",
|
||||
"name": "Ollama account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "tg-inbox",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "f784ec7d-bbc5-4ed4-af89-ec894d99d053",
|
||||
"name": "Webhook",
|
||||
"webhookId": "d857e864-ed25-447f-84ef-27c63190fb93"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const msg = $input.item.json.body || {};\nconst rawText = msg.text || \"\"; \nconst meta = msg.metadata || {};\n\nconst senderName = meta.sender_name || \"Unknown\";\nconst senderId = meta.sender_id || \"0\";\n\n// Формируем ОДНУ строку, которая станет ОДНИМ вектором\nreturn {\n json: {\n text: `Пользователь ${senderName} (ID: ${senderId}) написал: ${rawText}`,\n metadata: {\n ...meta,\n sender_name: senderName,\n sender_id: senderId\n }\n }\n};\n"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
272,
|
||||
0
|
||||
],
|
||||
"id": "6bb63840-979a-4453-8284-a52df680a972",
|
||||
"name": "Format Data"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
752,
|
||||
208
|
||||
],
|
||||
"id": "5260623b-ec01-4fa6-baed-c340d74d1eab",
|
||||
"name": "Default Data Loader"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Embeddings Ollama": {
|
||||
"ai_embedding": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_embedding",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Default Data Loader": {
|
||||
"ai_document": [
|
||||
[
|
||||
{
|
||||
"node": "Qdrant Vector Store",
|
||||
"type": "ai_document",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "483acdf0-ad7b-4ade-ab5a-61f6e3a73f5a",
|
||||
"meta": {
|
||||
"instanceId": "96706479c2e398d2a4e75bb05002310bb6030e8268be45294cab77c6640a0fe6"
|
||||
},
|
||||
"id": "R9JVik9-vpScLvVK-27nM",
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user