8
n8n 한국어amn8n.com

지능형 Telegram 어시스턴트

고급

이것은AI Chatbot, Multimodal AI분야의자동화 워크플로우로, 36개의 노드를 포함합니다.주로 Set, Code, Switch, Postgres, Telegram 등의 노드를 사용하며. Gemini AI, PostgreSQL 기억, 동적 라우팅을 사용하여 지능형 Telegram 어시스턴트를 구축합니다.

사전 요구사항
  • PostgreSQL 데이터베이스 연결 정보
  • Telegram Bot Token
  • Google Gemini API Key
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
  "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)는 사용자 직접 비용을 지불해야 할 수 있습니다.

워크플로우 정보
난이도
고급
노드 수36
카테고리2
노드 유형15
난이도 설명

고급 사용자를 위한 16+개 노드의 복잡한 워크플로우

저자
John Alejandro SIlva

John Alejandro SIlva

@alejandro-silva

Detail-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.

외부 링크
n8n.io에서 보기

이 워크플로우 공유

카테고리

카테고리: 34