지능형 Telegram 어시스턴트
이것은AI Chatbot, Multimodal AI분야의자동화 워크플로우로, 36개의 노드를 포함합니다.주로 Set, Code, Switch, Postgres, Telegram 등의 노드를 사용하며. Gemini AI, PostgreSQL 기억, 동적 라우팅을 사용하여 지능형 Telegram 어시스턴트를 구축합니다.
- •PostgreSQL 데이터베이스 연결 정보
- •Telegram Bot Token
- •Google Gemini API Key
사용된 노드 (36)
{
"meta": {
"instanceId": "50be75eaab016244f302e16f06394e6613d664bfc61e8cd41452474a0de6a3ee",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "fccc9f50-71fa-4e25-9b15-8fd540ddc2fa",
"name": "모델 선택기",
"type": "@n8n/n8n-nodes-langchain.modelSelector",
"position": [
800,
1536
],
"parameters": {
"rules": {
"rule": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "976d83bb-7e9e-4aab-9722-25a9e238164f",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.output.difficulty }}",
"rightValue": 1
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1e68688d-73fe-47c1-9b35-a1e226220bcd",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.output.difficulty }}",
"rightValue": 2
}
]
},
"modelIndex": 2
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "61d58197-db59-4cd7-bc41-bbeaf5e7b069",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.output.difficulty }}",
"rightValue": 3
}
]
},
"modelIndex": 3
}
]
},
"numberInputs": 3
},
"typeVersion": 1
},
{
"id": "cf5b2ea0-78c5-47bf-a3c1-4c59c9a32f76",
"name": "구조화된 출력 파서",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
336,
1504
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"difficulty\": {\n \"type\": \"integer\",\n \"enum\": [1, 2, 3]\n },\n \"context\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\"difficulty\", \"context\"]\n}\n"
},
"typeVersion": 1.3
},
{
"id": "3a60caa7-eb1a-4bbd-88fc-55d7a3ffae29",
"name": "Gemini 2.5 Flash Lite",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
720,
1728
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-flash-lite"
},
"credentials": {
"googlePalmApi": {
"id": "to92mdfNe3L6sBae",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "412d35ed-cd49-4d20-b425-2f556ef175b1",
"name": "Gemini 2.5 Flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
880,
1728
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"id": "to92mdfNe3L6sBae",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "2cc40e2c-a2be-435e-8540-9235efc41e08",
"name": "Gemini 2.5 Pro",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1024,
1728
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-pro"
},
"credentials": {
"googlePalmApi": {
"id": "to92mdfNe3L6sBae",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "35f56404-6998-4952-a7f3-8716712bf96a",
"name": "채팅 메모리 가져오기",
"type": "n8n-nodes-base.postgres",
"onError": "continueRegularOutput",
"position": [
-256,
1328
],
"parameters": {
"limit": 25,
"table": {
"__rl": true,
"mode": "list",
"value": "chat_memory",
"cachedResultName": "chat_memory"
},
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"options": {},
"operation": "select"
},
"credentials": {
"postgres": {
"id": "eQR2NFRag48wov9g",
"name": "Postgres account"
}
},
"typeVersion": 2.6,
"alwaysOutputData": true
},
{
"id": "5e6058bc-a3b8-4827-954e-85e3a000a986",
"name": "MarkdownV2",
"type": "n8n-nodes-base.code",
"position": [
1136,
1264
],
"parameters": {
"jsCode": "/**\n * MarkdownV2-safe formatter + auto-chunker for Telegram (n8n Code node)\n * --------------------------------------------------------------------\n * - Allows: *bold*, _italic_, ||spoiler||, [label](url)\n * - Escapes everything else for Telegram MarkdownV2\n * - Validates/normalizes URLs\n * - Converts \"# Heading\" lines to bold titles\n * - Splits long messages into <= 4096-char chunks (uses a 4000-char budget)\n * - Outputs one item per chunk so the Telegram node sends all parts\n *\n * Recommended: Run this node in \"Run Once for All Items\".\n */\n\nconst MAX_TELEGRAM = 4096;\nconst SAFE_BUDGET = 4000; // small margin to avoid edge overflows\n\n// ============ MarkdownV2 helpers ============\nfunction escapeMarkdownV2(text) {\n if (!text) return '';\n return String(text).replace(/([\\\\_*[\\]()~`>#+\\-=|{}.!])/g, '\\\\$1');\n}\n\nfunction escapeForUrl(url) {\n return String(url).replace(/[)\\\\]/g, '\\\\$&');\n}\n\nfunction normalizeAndValidateUrl(url) {\n let raw = String(url || '').trim();\n try {\n const u = new URL(raw);\n return u.toString();\n } catch {}\n // Try https:// for bare domains\n const domainLike = /^[a-z0-9.-]+\\.[a-z]{2,}([/:?#].*)?$/i.test(raw);\n if (domainLike) {\n try {\n const u2 = new URL('https://' + raw);\n return u2.toString();\n } catch {}\n }\n return null;\n}\n\nfunction normalizeHeadings(text) {\n // Turn \"# Title\" → \"*Title*\"\n return text.replace(/^(#{1,6})\\s+(.*)$/gm, (m, hashes, title) => `*${title.trim()}*`);\n}\n\nfunction normalizeCommonMd(text) {\n return String(text)\n .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, '*$1*') // **bold** → *bold*\n .replace(/__([\\s\\S]*?)__/g, '_$1_'); // __italic__ → _italic_\n}\n\n/**\n * Convert incoming text to Telegram-safe MarkdownV2.\n */\nfunction processMarkdownV2Safe(inputText) {\n if (!inputText) return '';\n\n let text = normalizeCommonMd(String(inputText));\n text = normalizeHeadings(text);\n\n const placeholders = { links: [], bolds: [], italics: [], spoilers: [] };\n\n // Links: keep safe via placeholders during escaping\n text = text.replace(/\\[([^\\]\\n]+)\\]\\(([^)]+)\\)/g, (m, label, url) => {\n const normalizedUrl = normalizeAndValidateUrl(url);\n if (!normalizedUrl) return escapeMarkdownV2(label);\n const idx = placeholders.links.length;\n const ph = `⟬L${idx}⟭`;\n const safeLabel = escapeMarkdownV2(label);\n const safeUrl = escapeForUrl(normalizedUrl);\n placeholders.links.push(`[${safeLabel}](${safeUrl})`);\n return ph;\n });\n\n // Bold\n text = text.replace(/\\*([\\s\\S]+?)\\*/g, (m, inner) => {\n const idx = placeholders.bolds.length;\n const ph = `⟬B${idx}⟭`;\n placeholders.bolds.push(`*${escapeMarkdownV2(inner)}*`);\n return ph;\n });\n\n // Italic\n text = text.replace(/_([\\s\\S]+?)_/g, (m, inner) => {\n const idx = placeholders.italics.length;\n const ph = `⟬I${idx}⟭`;\n placeholders.italics.push(`_${escapeMarkdownV2(inner)}_`);\n return ph;\n });\n\n // Spoilers\n text = text.replace(/\\|\\|([\\s\\S]+?)\\|\\|/g, (m, inner) => {\n const idx = placeholders.spoilers.length;\n const ph = `⟬S${idx}⟭`;\n placeholders.spoilers.push(`||${escapeMarkdownV2(inner)}||`);\n return ph;\n });\n\n // Escape everything else\n text = escapeMarkdownV2(text);\n\n // Restore placeholders\n placeholders.links.forEach((md, i) => { text = text.replace(`⟬L${i}⟭`, md); });\n placeholders.bolds.forEach((md, i) => { text = text.replace(`⟬B${i}⟭`, md); });\n placeholders.italics.forEach((md, i) => { text = text.replace(`⟬I${i}⟭`, md); });\n placeholders.spoilers.forEach((md, i) => { text = text.replace(`⟬S${i}⟭`, md); });\n\n return text;\n}\n\n// ============ Chunking helpers ============\n/**\n * Split text into Telegram-safe chunks <= maxLen.\n * Prefers paragraph boundaries, then sentence boundaries, then words.\n * Falls back to hard cuts only when unavoidable (e.g., extremely long URL).\n */\nfunction chunkForTelegram(text, maxLen = SAFE_BUDGET) {\n if (!text || text.length <= maxLen) return [text || ''];\n\n const parts = [];\n let buffer = '';\n\n const flush = () => {\n if (buffer) {\n parts.push(buffer);\n buffer = '';\n }\n };\n\n // 1) Paragraph-level packing\n const paragraphs = text.split(/\\n{2,}/);\n for (const pRaw of paragraphs) {\n const p = pRaw; // keep paragraph as-is\n const candidate = buffer ? buffer + '\\n\\n' + p : p;\n if (candidate.length <= maxLen) {\n buffer = candidate;\n continue;\n }\n if (p.length <= maxLen) {\n flush();\n buffer = p;\n continue;\n }\n\n // 2) Sentence-level packing (paragraph is still too big)\n flush();\n const sentences = p.split(/(?<=[.!?…])\\s+(?=[^\\s])/u);\n let sBuf = '';\n for (const s of sentences) {\n const sCandidate = sBuf ? sBuf + ' ' + s : s;\n if (sCandidate.length <= maxLen) {\n sBuf = sCandidate;\n continue;\n }\n if (s.length <= maxLen) {\n if (sBuf) parts.push(sBuf);\n sBuf = s;\n continue;\n }\n\n // 3) Word-level packing (sentence is still too big)\n if (sBuf) { parts.push(sBuf); sBuf = ''; }\n let wBuf = '';\n const words = s.split(/\\s+/);\n for (const w of words) {\n const wCandidate = wBuf ? wBuf + ' ' + w : w;\n if (wCandidate.length <= maxLen) {\n wBuf = wCandidate;\n continue;\n }\n if (w.length <= maxLen) {\n if (wBuf) parts.push(wBuf);\n wBuf = w;\n continue;\n }\n // 4) Hard split (extremely long token, e.g., massive URL)\n if (wBuf) { parts.push(wBuf); wBuf = ''; }\n const re = new RegExp(`.{1,${maxLen}}`, 'g');\n const hardPieces = w.match(re) || [];\n parts.push(...hardPieces);\n }\n if (wBuf) parts.push(wBuf);\n }\n if (sBuf) parts.push(sBuf);\n }\n if (buffer) parts.push(buffer);\n\n // Final safety pass: trim chunks that might still exceed MAX_TELEGRAM\n return parts.flatMap(part => {\n if (part.length <= MAX_TELEGRAM) return [part];\n const re = new RegExp(`.{1,${SAFE_BUDGET}}`, 'g');\n return part.match(re) || [];\n });\n}\n\n// ============ Main ============\nconst inputItems = $input.all();\nconst out = [];\n\nfor (const item of inputItems) {\n const j = item.json || {};\n const raw =\n j.message ?? j.output ?? j.text ?? j.content ?? '';\n\n const formatted = processMarkdownV2Safe(raw);\n const chunks = chunkForTelegram(formatted, SAFE_BUDGET);\n\n chunks.forEach((chunk, idx) => {\n out.push({\n json: {\n ...j,\n message: chunk,\n message_part_index: idx + 1,\n message_parts_total: chunks.length,\n },\n binary: item.binary,\n });\n });\n}\n\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "6ce0f61c-03d1-4bef-827f-ac3c80f48939",
"name": "텍스트 메시지 전송",
"type": "n8n-nodes-base.telegram",
"position": [
1312,
1264
],
"webhookId": "1ad49e91-7894-4fb1-ba93-73ba3ec4e666",
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
"additionalFields": {
"parse_mode": "MarkdownV2",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "rzhkYoexl5hHvqnv",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "662e6beb-7498-42c2-ba0c-6530c100bf97",
"name": "MIME 타입 수정",
"type": "n8n-nodes-base.code",
"position": [
-1184,
1328
],
"parameters": {
"jsCode": "// --- Mapa Extendido de Tipos MIME ---\n// Una lista completa para cubrir la mayoría de los formatos de archivo comunes.\nconst mimeMap = {\n // --- Formatos de Documentos ---\n 'pdf': 'application/pdf',\n 'txt': 'text/plain',\n 'rtf': 'application/rtf',\n 'csv': 'text/csv',\n 'html': 'text/html',\n 'htm': 'text/html',\n 'json': 'application/json',\n 'xml': 'application/xml', // 'text/xml' también es válido pero 'application/xml' es más común\n 'yaml': 'application/x-yaml',\n 'yml': 'application/x-yaml',\n\n // --- Formatos de Microsoft Office ---\n 'doc': 'application/msword',\n 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'xls': 'application/vnd.ms-excel',\n 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'ppt': 'application/vnd.ms-powerpoint',\n 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'pub': 'application/vnd.ms-publisher',\n\n // --- Formatos de OpenOffice / LibreOffice ---\n 'odt': 'application/vnd.oasis.opendocument.text',\n 'ods': 'application/vnd.oasis.opendocument.spreadsheet',\n 'odp': 'application/vnd.oasis.opendocument.presentation',\n 'odg': 'application/vnd.oasis.opendocument.graphics',\n\n // --- Formatos de Apple iWork ---\n 'pages': 'application/vnd.apple.pages',\n 'numbers': 'application/vnd.apple.numbers',\n 'key': 'application/vnd.apple.keynote',\n\n // --- Formatos de Imagen ---\n 'png': 'image/png',\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'gif': 'image/gif',\n 'webp': 'image/webp',\n 'svg': 'image/svg+xml',\n 'bmp': 'image/bmp',\n 'ico': 'image/vnd.microsoft.icon',\n 'tif': 'image/tiff',\n 'tiff': 'image/tiff',\n 'heic': 'image/heic',\n 'heif': 'image/heif',\n\n // --- Formatos de Audio ---\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'oga': 'audio/ogg',\n 'ogg': 'audio/ogg',\n 'flac': 'audio/flac',\n 'm4a': 'audio/mp4',\n 'aac': 'audio/aac',\n 'opus': 'audio/opus',\n 'wma': 'audio/x-ms-wma',\n 'mid': 'audio/midi',\n 'midi': 'audio/midi',\n\n // --- Formatos de Video ---\n 'mp4': 'video/mp4',\n 'mov': 'video/quicktime',\n 'webm': 'video/webm',\n 'mpeg': 'video/mpeg',\n 'mpg': 'video/mpeg',\n 'avi': 'video/x-msvideo',\n 'wmv': 'video/x-ms-wmv',\n 'flv': 'video/x-flv',\n 'mkv': 'video/x-matroska',\n\n // --- Formatos de Archivos y Compresión ---\n 'zip': 'application/zip',\n 'rar': 'application/vnd.rar',\n '7z': 'application/x-7z-compressed',\n 'tar': 'application/x-tar',\n 'gz': 'application/gzip',\n 'bz2': 'application/x-bzip2',\n\n // --- Otros Formatos ---\n 'epub': 'application/epub+zip',\n 'ics': 'text/calendar',\n 'vcf': 'text/vcard',\n 'js': 'text/javascript',\n 'css': 'text/css',\n 'sh': 'application/x-sh',\n 'py': 'text/x-python',\n};\n\n// --- Lógica de Procesamiento (sin cambios) ---\n\n// Obtenemos todos los items que llegan al nodo\nconst items = $input.all();\n\n// Iteramos sobre cada item para procesarlo\nfor (const item of items) {\n // Verificamos que el item tenga datos binarios para procesar\n if (item.binary && item.binary['data']) {\n // Obtenemos el nombre del archivo de forma segura\n const fileName = item.binary['data'].fileName || '';\n if (!fileName) {\n continue; // Si no hay nombre de archivo, pasamos al siguiente item\n }\n\n // Extraemos la extensión del archivo de forma robusta\n const extension = fileName.slice((fileName.lastIndexOf(\".\") - 1 >>> 0) + 2).toLowerCase();\n\n // Buscamos la extensión en nuestro mapa\n const newMimeType = mimeMap[extension];\n\n // Si encontramos una coincidencia en el mapa, actualizamos el mimeType\n if (newMimeType) {\n if(item.binary['data'].mimeType !== newMimeType) {\n console.log(`Cambiando mimeType para '${fileName}' de '${item.binary['data'].mimeType}' a '${newMimeType}'.`);\n item.binary['data'].mimeType = newMimeType;\n }\n }\n }\n}\n\n// Devolvemos todos los items, modificados o no\nreturn items;"
},
"typeVersion": 2
},
{
"id": "07281d83-6c94-4952-819e-288a4435da24",
"name": "입력 중…",
"type": "n8n-nodes-base.telegram",
"position": [
-1744,
1184
],
"webhookId": "b768e407-5f22-4b80-a8a9-2d255b9bf815",
"parameters": {
"chatId": "={{ $json.message.chat.id }}",
"operation": "sendChatAction"
},
"credentials": {
"telegramApi": {
"id": "rzhkYoexl5hHvqnv",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "e211d4af-9ebd-4524-84ef-934349b6b155",
"name": "메시지 가져오기 (오디오/비디오 메시지)",
"type": "n8n-nodes-base.set",
"position": [
-848,
1328
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d8935452-fe20-469d-a68d-1aad056cb8dd",
"name": "message",
"type": "string",
"value": "=Voice message description:{{ $json.candidates?.[0]?.content?.parts?.[0]?.text || $json.content?.parts?.[0]?.text }}"
},
{
"id": "93f1bba1-1180-404a-93ca-c34cf1d1b7ac",
"name": "chat_id",
"type": "string",
"value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9c28c37b-1d37-4f23-90a3-872f5e3870a7",
"name": "음성 메시지 분석",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-1024,
1328
],
"parameters": {
"text": "What's in this audio message from telegram user?",
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-pro",
"cachedResultName": "models/gemini-2.5-pro"
},
"options": {},
"resource": "audio",
"inputType": "binary",
"operation": "analyze"
},
"credentials": {
"googlePalmApi": {
"id": "to92mdfNe3L6sBae",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "ea646e8c-e6d0-47ea-8743-45eade4c1c4d",
"name": "메시지 가져오기 (텍스트)",
"type": "n8n-nodes-base.set",
"position": [
-1184,
1152
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "801ec600-22ad-4a94-a2b4-ae72eb271df0",
"name": "message",
"type": "string",
"value": "={{ $('Telegram Trigger').item.json.message.text }}"
},
{
"id": "263071fb-bcdf-42b0-bb46-71b75fa0bf2a",
"name": "chat_id",
"type": "string",
"value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1ca88076-5c56-4561-bb8a-fdf1db080d2a",
"name": "입력 메시지 라우터1",
"type": "n8n-nodes-base.switch",
"position": [
-1600,
1312
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Text",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "fcb767ee-565e-4b56-a54e-6f97f739fc24",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "Voice Message",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c1016c40-f8f2-4e08-8ec8-5cdb88f5c87a",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.voice }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {
"ignoreCase": false,
"fallbackOutput": "extra",
"allMatchingOutputs": true
}
},
"typeVersion": 3.2
},
{
"id": "298fa06c-d3fc-4d20-a2d4-fe879a01527c",
"name": "음성 메시지 다운로드",
"type": "n8n-nodes-base.telegram",
"position": [
-1360,
1328
],
"webhookId": "d28e2f59-d662-4e75-8bac-11fdc3fbb295",
"parameters": {
"fileId": "={{ $('Telegram Trigger').item.json.message.voice.file_id }}",
"resource": "file",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"id": "rzhkYoexl5hHvqnv",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "5a1dc986-6bfe-4400-bdbf-d4f5817e0f7e",
"name": "Telegram 트리거",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-1888,
1328
],
"webhookId": "1aecee74-ba0f-4fe2-a302-578312187154",
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"id": "rzhkYoexl5hHvqnv",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "0188bc12-4fc1-4149-8c80-edf8e80009ec",
"name": "입력 정규화",
"type": "n8n-nodes-base.set",
"position": [
-464,
1328
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3c2fa4f9-079c-4729-9737-66ce8f42029f",
"name": "message",
"type": "string",
"value": "={{ $json.message }}"
},
{
"id": "b6e57068-8ece-4725-b07d-1b00069943b0",
"name": "chat_id",
"type": "string",
"value": "={{ $json.chat_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4db66ea5-0817-4326-9991-ab6a905ec0ee",
"name": "집계",
"type": "n8n-nodes-base.aggregate",
"position": [
-48,
1328
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "message"
}
]
}
},
"typeVersion": 1
},
{
"id": "a4ce42c2-d1cc-4233-9ee7-2aac9a5f0c45",
"name": "Google Gemini 2.5 Flash Lite",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
192,
1504
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-flash-lite"
},
"credentials": {
"googlePalmApi": {
"id": "to92mdfNe3L6sBae",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "5c2cd387-3b15-4869-87da-6873437a0d33",
"name": "오류 메시지 가져오기",
"type": "n8n-nodes-base.set",
"position": [
-1184,
1504
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d8935452-fe20-469d-a68d-1aad056cb8dd",
"name": "message",
"type": "string",
"value": "=It was not possible to process the file.File type not supported."
},
{
"id": "38ba2498-2141-4a04-a22a-64563fe2ee6f",
"name": "chat_id",
"type": "string",
"value": "={{ $('Telegram Trigger').item.json.message.chat.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d31898fc-4d10-43ea-bc5d-402ac29f3f4b",
"name": "에이전트",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
800,
1328
],
"parameters": {
"text": "=Context: {{ $json.output.context }}\nUser request: {{ $('Normalize input').item.json.message }}",
"options": {
"returnIntermediateSteps": true
},
"promptType": "define"
},
"typeVersion": 2.1
},
{
"id": "ee146b55-17b6-414c-be6b-f8416ece7075",
"name": "스티커 노트",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1984,
1104
],
"parameters": {
"color": 5,
"width": 1312,
"height": 624,
"content": ""
},
"typeVersion": 1
},
{
"id": "e677f7d7-2cfc-476d-bdc0-08b4cf17cb4d",
"name": "스티커 노트1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-576,
1248
],
"parameters": {
"color": 3,
"width": 1216,
"height": 400,
"content": ""
},
"typeVersion": 1
},
{
"id": "d36b2018-ca63-4c1f-a40e-46d1452753e2",
"name": "'워크플로 실행' 클릭 시",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1888,
880
],
"parameters": {},
"typeVersion": 1
},
{
"id": "451eb06f-5b47-44c5-86fa-20587cf89870",
"name": "채팅 메모리 테이블 생성",
"type": "n8n-nodes-base.postgres",
"position": [
-1696,
880
],
"parameters": {
"query": "CREATE TABLE IF NOT EXISTS public.chat_memory (\n id SERIAL PRIMARY KEY,\n session_id VARCHAR(255) NOT NULL,\n message TEXT DEFAULT 'Could not get data'\n) TABLESPACE pg_default;\n\nCREATE INDEX IF NOT EXISTS chat_memory_session_id_idx \nON public.chat_memory USING btree (session_id) \nTABLESPACE pg_default;\n",
"options": {},
"operation": "executeQuery"
},
"credentials": {
"postgres": {
"id": "eQR2NFRag48wov9g",
"name": "Postgres account"
}
},
"typeVersion": 2.6
},
{
"id": "fb4c1415-dd98-4944-8c12-e69f8d445be7",
"name": "스티커 노트2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1984,
848
],
"parameters": {
"color": 7,
"width": 512,
"height": 208,
"content": ""
},
"typeVersion": 1
},
{
"id": "50ac5adc-ce42-4f35-84ac-cf8e2bea5d38",
"name": "스티커 노트3",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
1248
],
"parameters": {
"color": 6,
"width": 880,
"height": 624,
"content": ""
},
"typeVersion": 1
},
{
"id": "97859eb8-951f-4abf-8b89-63299b456304",
"name": "채팅 메모리 업데이트 (사용자 및 에이전트)",
"type": "n8n-nodes-base.postgres",
"position": [
1136,
1424
],
"parameters": {
"table": {
"__rl": true,
"mode": "list",
"value": "chat_memory",
"cachedResultName": "chat_memory"
},
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"columns": {
"value": {
"message": "=User: {{ $('Normalize input').item.json.message }}\nAgent: {{ $json.output }}\n",
"session_id": "={{ $('Normalize input').item.json.chat_id }}"
},
"schema": [
{
"id": "id",
"type": "number",
"display": true,
"removed": true,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "session_id",
"type": "string",
"display": true,
"required": true,
"displayName": "session_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message",
"type": "string",
"display": true,
"required": false,
"displayName": "message",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"credentials": {
"postgres": {
"id": "eQR2NFRag48wov9g",
"name": "Postgres account"
}
},
"typeVersion": 2.6
},
{
"id": "632c9d0c-5ab3-46a6-bb4d-0d4bd3341fde",
"name": "요약 및 분류",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
192,
1328
],
"parameters": {
"text": "=Chat Memory: {{ $('Aggregate').item.json.message.join('\\n') }}\n\nUser Request: {{ $('Normalize input').item.json.message }}",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=You are a system that analyzes a user request and its chat history.\n\n## Your goals:\n1. Summarize the chat history into only the relevant context for the current user request.\n2. Determine the difficulty of the request:\n - 1: Very simple (short answers, reminders, basic actions).\n - 2: Medium (some reasoning, structured outputs, combining info).\n - 3: Complex (multi-step reasoning, ambiguous queries, coding-level reasoning).\n\n## Output Format:\nYou must return JSON strictly following this schema:\n\n{\n \"difficulty\": 1 | 2 | 3,\n \"context\": \"string - summary of the relevant history\"\n}\n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "8979f56c-2809-426d-acda-9d455df8836e",
"name": "스티커 노트4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1984,
576
],
"parameters": {
"color": 7,
"width": 512,
"height": 256,
"content": "## ⚙️ Database Initialization (Chat Memory Table)\n\n**Purpose:** \nThis section is responsible for creating and preparing the `chat_memory` table in PostgreSQL. It ensures that chat interactions are stored persistently for later use in context management, summarization, and categorization.\n"
},
"typeVersion": 1
},
{
"id": "cf047f39-85cd-41a6-8d2a-76ecec83b6f2",
"name": "스티커 노트5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1392,
432
],
"parameters": {
"color": 5,
"width": 704,
"height": 640,
"content": "## 🔵 Input Handling (Telegram Trigger & Preprocessing)\n\n### Purpose:\nThis section receives and processes incoming messages from Telegram. It detects whether the input is text, voice, or unsupported media.\n\n### Key Steps:\n\n* **Telegram Trigger** – Listens for new updates (messages from users).\n* **Input Message Router** – Classifies whether the input is:\n * Text message\n * Voice message\n * Unsupported media → redirects to an error handler\n* **Voice Handling** – If it’s a voice message:\n * Downloads the file\n * Normalizes MIME type\n * Sends it to speech-to-text analysis\n * Extracts the text message from audio\n* **Error Handling** – If the input is not supported, an error message is returned.\n\n### Optional Extension:\nFor multimodal and media group support (multiple files, images, videos), you can extend this section with the following template: https://n8n.io/workflows/7455-process-multiple-media-files-in-telegram-with-gemini-ai-and-postgresql-database/"
},
"typeVersion": 1
},
{
"id": "3557d1c3-c155-430a-a11b-5bc901111d46",
"name": "스티커 노트6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
496
],
"parameters": {
"color": 3,
"width": 784,
"height": 720,
"content": "## 🔴 Chat Memory Retrieval & Context Optimization\n\n### Purpose:\nThis section retrieves past interactions from the database, aggregates them into a single string, and summarizes them before passing them to the agent.\n\n### Key Steps:\n\n* **Normalize Input** – Standardizes the message before processing.\n* **Get Chat Memory** – Queries PostgreSQL for previous messages linked to the `session_id`.\n* **Aggregate** – Combines past interactions into one text block.\n* **Summarize & Categorize** – Uses Google Gemini 2.5 Flash Lite (low-cost, low-latency) to:\n * Summarize the chat history.\n * Extract relevant context.\n * Categorize the difficulty level of the task.\n\n### Why Summarization First?\n\n* Only relevant and important context is passed to the agent.\n* Reduces token usage, speeding up responses and lowering costs.\n* Prevents irrelevant history from cluttering the model’s attention.\n\n### Advantages:\n\n* Saves processing time and cost by using a lightweight summarization model.\n* Produces more accurate and focused responses.\n* Optimizes memory queries by reducing data size passed downstream."
},
"typeVersion": 1
},
{
"id": "42fdc20f-2c28-4826-8704-20ffb8792bd7",
"name": "스티커 노트7",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
384
],
"parameters": {
"color": 6,
"width": 784,
"height": 832,
"content": "## 🟣 Agent Processing & Response Delivery\n\n### Purpose:\nThis section routes the request to the appropriate Gemini model depending on task difficulty, generates the final response, and sends it back to Telegram. It also updates the chat memory.\n\n### Key Steps:\n\n* **Agent Node** – Receives:\n * User request\n * Relevant summarized context\n * Difficulty level\n* **Model Selector** – Dynamically chooses the model:\n * **Difficulty 1** → Gemini 2.5 Flash Lite (fastest & cheapest)\n * **Difficulty 2** → Gemini 2.5 Flash\n * **Difficulty 3** → Gemini 2.5 Pro (advanced reasoning)\n* **Markdown Formatting** – Converts model output into Telegram-compatible Markdown V2.\n* **Send Message** – Sends the response back to Telegram.\n* **Update Chat Memory** – Inserts a single row containing both the user and agent message.\n\n### Why single-row storage for user & agent?\n\n* Since the message is sent to Telegram first and then the memory is updated, this saves an average of 0.3 seconds in response time.\n* Reduces the number of queries (`Get Chat Memory` fetches fewer rows).\n* Updates are faster since both messages are stored at once.\n* Optimizes resource usage while maintaining full conversational context.\n\n### Advantages:\n\n* Cost-optimized model usage (only advanced models used when strictly necessary).\n* Faster response times by minimizing context size and database operations.\n"
},
"typeVersion": 1
},
{
"id": "1c29f5cf-c95e-40fc-9dc0-362dcda71574",
"name": "스티커 노트8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2560,
1104
],
"parameters": {
"color": 4,
"width": 544,
"height": 592,
"content": "## ✅ Key Benefits of This Architecture\n\n### Cost Efficiency\n\n* Uses cheaper LLMs for summarization and simple queries.\n* Reserves expensive models only for complex tasks.\n\n### Performance Optimization\n\n* Reduced token consumption through summarization.\n* Faster memory queries due to single-row storage.\n\n### Flexibility & Scalability\n\n* Easy integration of multimodal inputs (images, audio, video).\n* Modular structure allows replacing/upgrading models or database logic.\n\n### User Experience\n\n* Quick Telegram responses.\n* Consistent memory of past interactions without overwhelming the LLM."
},
"typeVersion": 1
},
{
"id": "84019987-a071-44d0-8e4b-d80c838007dc",
"name": "스티커 노트9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2496,
848
],
"parameters": {
"color": 2,
"width": 368,
"height": 192,
"content": "## Acknowledgment\n\nA special thank you to Davide for the inspiration behind this template. \nHis work on the [**AI Orchestrator that dynamically selects models based on input type**](https://n8n.io/workflows/7004-ai-orchestrator-dynamically-selects-models-based-on-input-type/) served as a foundational guide for this architecture.\n"
},
"typeVersion": 1
},
{
"id": "0c425ab2-41d3-43c9-86bb-9c7cbfe968a4",
"name": "스티커 노트10",
"type": "n8n-nodes-base.stickyNote",
"position": [
1584,
1648
],
"parameters": {
"color": 2,
"width": 368,
"height": 192,
"content": "## 💡 Need Assistance?\n\nIf you’d like help customizing or extending this workflow, feel free to reach out: \n\n📧 Email: [johnsilva11031@gmail.com](mailto:johnsilva11031@gmail.com) \n🔗 LinkedIn: [John Alejandro Silva Rodríguez](https://www.linkedin.com/in/john-alejandro-silva-rodriguez-48093526b/)\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"d31898fc-4d10-43ea-bc5d-402ac29f3f4b": {
"main": [
[
{
"node": "5e6058bc-a3b8-4827-954e-85e3a000a986",
"type": "main",
"index": 0
},
{
"node": "97859eb8-951f-4abf-8b89-63299b456304",
"type": "main",
"index": 0
}
]
]
},
"662e6beb-7498-42c2-ba0c-6530c100bf97": {
"main": [
[
{
"node": "9c28c37b-1d37-4f23-90a3-872f5e3870a7",
"type": "main",
"index": 0
}
]
]
},
"4db66ea5-0817-4326-9991-ab6a905ec0ee": {
"main": [
[
{
"node": "632c9d0c-5ab3-46a6-bb4d-0d4bd3341fde",
"type": "main",
"index": 0
}
]
]
},
"5e6058bc-a3b8-4827-954e-85e3a000a986": {
"main": [
[
{
"node": "6ce0f61c-03d1-4bef-827f-ac3c80f48939",
"type": "main",
"index": 0
}
]
]
},
"2cc40e2c-a2be-435e-8540-9235efc41e08": {
"ai_languageModel": [
[
{
"node": "fccc9f50-71fa-4e25-9b15-8fd540ddc2fa",
"type": "ai_languageModel",
"index": 2
}
]
]
},
"fccc9f50-71fa-4e25-9b15-8fd540ddc2fa": {
"ai_languageModel": [
[
{
"node": "d31898fc-4d10-43ea-bc5d-402ac29f3f4b",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"35f56404-6998-4952-a7f3-8716712bf96a": {
"main": [
[
{
"node": "4db66ea5-0817-4326-9991-ab6a905ec0ee",
"type": "main",
"index": 0
}
]
]
},
"0188bc12-4fc1-4149-8c80-edf8e80009ec": {
"main": [
[
{
"node": "35f56404-6998-4952-a7f3-8716712bf96a",
"type": "main",
"index": 0
}
]
]
},
"412d35ed-cd49-4d20-b425-2f556ef175b1": {
"ai_languageModel": [
[
{
"node": "fccc9f50-71fa-4e25-9b15-8fd540ddc2fa",
"type": "ai_languageModel",
"index": 1
}
]
]
},
"5a1dc986-6bfe-4400-bdbf-d4f5817e0f7e": {
"main": [
[
{
"node": "1ca88076-5c56-4561-bb8a-fdf1db080d2a",
"type": "main",
"index": 0
},
{
"node": "07281d83-6c94-4952-819e-288a4435da24",
"type": "main",
"index": 0
}
]
]
},
"5c2cd387-3b15-4869-87da-6873437a0d33": {
"main": [
[
{
"node": "0188bc12-4fc1-4149-8c80-edf8e80009ec",
"type": "main",
"index": 0
}
]
]
},
"ea646e8c-e6d0-47ea-8743-45eade4c1c4d": {
"main": [
[
{
"node": "0188bc12-4fc1-4149-8c80-edf8e80009ec",
"type": "main",
"index": 0
}
]
]
},
"9c28c37b-1d37-4f23-90a3-872f5e3870a7": {
"main": [
[
{
"node": "e211d4af-9ebd-4524-84ef-934349b6b155",
"type": "main",
"index": 0
}
]
]
},
"3a60caa7-eb1a-4bbd-88fc-55d7a3ffae29": {
"ai_languageModel": [
[
{
"node": "fccc9f50-71fa-4e25-9b15-8fd540ddc2fa",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"1ca88076-5c56-4561-bb8a-fdf1db080d2a": {
"main": [
[
{
"node": "ea646e8c-e6d0-47ea-8743-45eade4c1c4d",
"type": "main",
"index": 0
}
],
[
{
"node": "298fa06c-d3fc-4d20-a2d4-fe879a01527c",
"type": "main",
"index": 0
}
],
[
{
"node": "5c2cd387-3b15-4869-87da-6873437a0d33",
"type": "main",
"index": 0
}
]
]
},
"298fa06c-d3fc-4d20-a2d4-fe879a01527c": {
"main": [
[
{
"node": "662e6beb-7498-42c2-ba0c-6530c100bf97",
"type": "main",
"index": 0
}
]
]
},
"632c9d0c-5ab3-46a6-bb4d-0d4bd3341fde": {
"main": [
[
{
"node": "d31898fc-4d10-43ea-bc5d-402ac29f3f4b",
"type": "main",
"index": 0
}
]
]
},
"cf5b2ea0-78c5-47bf-a3c1-4c59c9a32f76": {
"ai_outputParser": [
[
{
"node": "632c9d0c-5ab3-46a6-bb4d-0d4bd3341fde",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"a4ce42c2-d1cc-4233-9ee7-2aac9a5f0c45": {
"ai_languageModel": [
[
{
"node": "632c9d0c-5ab3-46a6-bb4d-0d4bd3341fde",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"e211d4af-9ebd-4524-84ef-934349b6b155": {
"main": [
[
{
"node": "0188bc12-4fc1-4149-8c80-edf8e80009ec",
"type": "main",
"index": 0
}
]
]
},
"97859eb8-951f-4abf-8b89-63299b456304": {
"main": [
[]
]
},
"d36b2018-ca63-4c1f-a40e-46d1452753e2": {
"main": [
[
{
"node": "451eb06f-5b47-44c5-86fa-20587cf89870",
"type": "main",
"index": 0
}
]
]
}
}
}이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - AI 챗봇, 멀티모달 AI
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
John Alejandro SIlva
@alejandro-silvaDetail-oriented professional with a dual degree in Systems Engineering and Business Administration and international experience in technology and process improvement. I specialize in workflow automation with n8n, API integration, programming, and data analysis. Known for strong analytical skills and clear technical documentation.
이 워크플로우 공유