AutomationFlowsAI & RAG › Run a Telegram RAG Support Chatbot with Openai, Pinecone, and Google Sheets

Run a Telegram RAG Support Chatbot with Openai, Pinecone, and Google Sheets

ByisaWOW @isawow on n8n.io

This workflow turns a Telegram bot into a multi-modal support chatbot that accepts text, voice, images, and PDFs, converts them to text, retrieves relevant knowledge from Pinecone using OpenAI embeddings, generates a grounded answer with OpenAI Chat Completions, and logs each…

Event trigger★★★★☆ complexityAI-powered25 nodesTelegram TriggerTelegramHTTP RequestOpenAIGoogle Sheets
AI & RAG Trigger: Event Nodes: 25 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16928 — we link there as the canonical source.

This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "J3ZSS0MAxb5x4l04",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Telegram AI Support Chatbot with RAG - Text, Voice, Image, PDF + Pinecone + Sheets Log",
  "tags": [],
  "nodes": [
    {
      "id": "4ac5a868-25f5-43c2-aa85-50886b54cad2",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2384,
        -416
      ],
      "parameters": {
        "color": 4,
        "width": 560,
        "height": 1600,
        "content": "## \ud83e\udd16 Telegram AI Support Chatbot with RAG - Text, Voice, Image, PDF + Pinecone + Sheets Log\n\nA multi-modal AI support chatbot on Telegram. Users send any message type: plain text, voice notes, photos, or PDF documents. The bot converts everything to text, searches your knowledge base in Pinecone using RAG (Retrieval Augmented Generation), and a ChatGPT AI model answers using only that retrieved context, so the bot never makes things up. Every exchange is logged to Google Sheets with a per-user session ID. Works instantly with any Telegram bot token, no business account or approval process needed.\n\n## What each node does\n- **1. Telegram Trigger - Receive Messages** receives every incoming message sent to the bot\n- **2. Code - Detect Message Type** detects text, voice, photo, document, or the admin /ingest command and extracts the user, chat, and file IDs\n- **3. Switch - Route by Message Type** sends each message type to the right processing branch\n- **4 to 6. Voice branch** - Telegram gets the voice file URL, HTTP downloads the OGG audio, and OpenAI Whisper transcribes it to text\n- **7 to 9. Photo branch** - Telegram gets the photo file URL, HTTP downloads the image, and ChatGPT AI Vision describes it and reads any visible text\n- **10 to 12. Document branch** - Telegram gets the document file URL, HTTP downloads the PDF, and Extract From File reads the text\n- **13. Code - Build Query Text from Any Input** merges all branches into one final text query\n- **14. HTTP - OpenAI Embed Query** turns the query into a vector embedding\n- **15. HTTP - Pinecone Vector Search** finds the top 3 matching knowledge chunks\n- **16. Code - Build RAG Context from Results** assembles the retrieved chunks into a context block\n- **17. HTTP - AI Generate Answer** the ChatGPT AI model writes the final answer using only the knowledge base context\n- **18. Code - Format Reply and Prepare Log** trims the reply to the Telegram limit and prepares the log row\n- **19. Telegram - Send Reply to User** sends the answer back to the user (parallel branch)\n- **20. Sheets - Log Conversation** appends the full exchange to the conversation log (parallel branch)\n\n## Use cases\nCustomer support bot for a product or service, internal team knowledge base assistant, school or college Q&A bot, restaurant menu and ordering info bot, real estate property info bot.\n\n## Key things to know\n1. **Pinecone free tier** - the free Starter plan gives 1 index with 100K vectors, more than enough for most support knowledge bases. One document chunk equals one vector\n2. **Voice notes** - Telegram voice notes are OGG format and OpenAI Whisper handles OGG natively, no conversion needed\n3. **Session ID** - every user gets a session ID (tg_ plus their user ID) attached to each logged exchange. For true multi-turn memory, store chat history against this session ID or connect an n8n memory node\n4. **Admin ingestion** - only the Telegram user whose ID matches YOUR_TELEGRAM_ADMIN_USER_ID can send the /ingest command. This workflow recognizes and acknowledges the command; run your separate ingestion workflow to chunk, embed, and upload documents to Pinecone\n5. **PDF size** - PDF text is truncated to 8000 characters before the AI call, so split very large PDFs into sections before uploading\n\n## Google Sheet setup\nCreate a Google Sheet, add a tab named **Conversations**, and put these column headers in row 1:\nTimestamp, User ID, Username, Message Type, User Message, Bot Reply, RAG Chunks Used, Session ID\n\n## Set up steps\n1. **Telegram bot** - open Telegram, search @BotFather, send /newbot, follow the steps, and copy the Bot Token. In n8n create a Telegram API credential with this token and connect it to nodes 1, 4, 7, 10, and 19. Also replace `YOUR_TELEGRAM_BOT_TOKEN` inside the download URL in nodes 5, 8, and 11\n2. **OpenAI** - connect your OpenAI API credential in nodes 6, 9, 14, and 17. Whisper transcription, ChatGPT AI Vision, embeddings, and the answer model all use this one credential\n3. **Pinecone** - sign up free at https://pinecone.io and create an index named support-kb with 1536 dimensions and cosine metric. Replace `YOUR_PINECONE_API_KEY` and `YOUR_PINECONE_INDEX_HOST` in node 15 (the host looks like https://support-kb-abc123.svc.aped-4627-b74a.pinecone.io)\n4. **Google Sheets** - create the sheet as described above, connect Google Sheets OAuth2, and replace `YOUR_CONVERSATION_LOG_SHEET_ID` in node 20\n5. **Admin ID** - replace `YOUR_TELEGRAM_ADMIN_USER_ID` in node 2 so only you can use the /ingest command\n6. **Activate** - activate the workflow, open your Telegram bot, and send a test message in any format"
      },
      "typeVersion": 1
    },
    {
      "id": "732f0e36-6833-4280-a0fd-6d3676b5aa54",
      "name": "Section - Message Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1744,
        48
      ],
      "parameters": {
        "color": 5,
        "width": 720,
        "height": 516,
        "content": "## Message Routing\nTelegram Trigger receives every message. Code detects the message type (text, voice, photo, document, or the admin /ingest command) and extracts all user, chat, and file IDs. Switch then routes each type to the right processing branch."
      },
      "typeVersion": 1
    },
    {
      "id": "48b85dd9-6c9a-49d1-aecf-ea6ae1397f2e",
      "name": "Section - Media Processing Branches",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 780,
        "height": 1148,
        "content": "## Media Processing Branches\nVoice branch: get the file URL, download the OGG audio, and Whisper transcribes it to text. Photo branch: get the file URL, download the image, and ChatGPT AI Vision describes it and reads any visible text. Document branch: get the file URL, download the PDF, and Extract From File reads the text. All branches converge on the query builder."
      },
      "typeVersion": 1
    },
    {
      "id": "fd6deecf-6927-454f-98c7-a78e86a22e96",
      "name": "Section - RAG Pipeline",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        80
      ],
      "parameters": {
        "color": 5,
        "width": 1244,
        "height": 420,
        "content": "## RAG Pipeline\nCode merges every input type into one final query. OpenAI embeddings turn the query into a vector, Pinecone returns the top 3 matching knowledge chunks, Code assembles them into a context block, and the ChatGPT AI model writes the final answer using only that context."
      },
      "typeVersion": 1
    },
    {
      "id": "53c0aa94-b19f-43a4-b253-3cc291cdc9be",
      "name": "Section - Reply and Log",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -224
      ],
      "parameters": {
        "color": 4,
        "width": 480,
        "height": 1152,
        "content": "## Reply and Log\nCode trims the answer to the Telegram 4096 character limit and prepares the log row. Two parallel branches run: Telegram sends the reply back to the user, and Sheets appends the full exchange to the conversation log for audit and analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "819fbf36-a699-4ae0-9543-6198e2062402",
      "name": "1. Telegram Trigger - Receive Messages",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1680,
        224
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "dd76663a-8a64-4764-8f87-6d391d9c5259",
      "name": "2. Code - Detect Message Type",
      "type": "n8n-nodes-base.code",
      "position": [
        -1440,
        224
      ],
      "parameters": {
        "jsCode": "// Detect message type and extract relevant fields\nconst msg = $json.message || $json;\n\nconst userId    = String(msg.from?.id || '');\nconst username  = msg.from?.username || msg.from?.first_name || 'User';\nconst chatId    = String(msg.chat?.id || userId);\nconst text      = msg.text || msg.caption || '';\nconst messageId = String(msg.message_id || '');\n\n// Detect type\nlet messageType = 'text';\nif (msg.voice)    messageType = 'voice';\nif (msg.audio)    messageType = 'voice';\nif (msg.photo)    messageType = 'photo';\nif (msg.document) messageType = 'document';\n\n// Check for admin /ingest command\nconst adminUserId    = 'YOUR_TELEGRAM_ADMIN_USER_ID';\nconst isAdminIngest  = text.startsWith('/ingest') && userId === adminUserId;\nif (isAdminIngest) messageType = 'admin_ingest';\n\n// Extract media IDs\nconst voiceFileId    = msg.voice?.file_id    || msg.audio?.file_id    || '';\nconst photoFileId    = (msg.photo && msg.photo.length > 0) ? msg.photo[msg.photo.length - 1].file_id : '';\nconst documentFileId = msg.document?.file_id || '';\nconst documentMime   = msg.document?.mime_type || '';\n\n// Session ID per user (used in the conversation log)\nconst sessionId = 'tg_' + userId;\n\n// Extract doc URL from ingest command if applicable\nconst ingestUrl = isAdminIngest ? text.replace('/ingest', '').trim() : '';\n\nreturn [{\n  json: {\n    userId, username, chatId, text, messageId,\n    messageType, voiceFileId, photoFileId,\n    documentFileId, documentMime, sessionId,\n    ingestUrl, isAdminIngest\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3023a8df-1e05-4913-b0ab-962b957a5439",
      "name": "3. Switch - Route by Message Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        -1200,
        224
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "text",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "r-text",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "text"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "voice",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "r-voice",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "voice"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "photo",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "r-photo",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "photo"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "document",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "r-doc",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "document"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "admin_ingest",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "r-ingest",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "admin_ingest"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "273e1b2f-7b5a-4a26-b040-4616f620550b",
      "name": "4. Telegram - Get Voice File URL",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -768,
        0
      ],
      "parameters": {
        "fileId": "={{ $json.voiceFileId }}",
        "resource": "file",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "66501ffb-acec-4354-9db2-ed9eb0f9edef",
      "name": "5. HTTP - Download Voice File",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -528,
        0
      ],
      "parameters": {
        "url": "=https://api.telegram.org/file/botYOUR_TELEGRAM_BOT_TOKEN/{{ $json.result.file_path }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "ccbdc157-8ac0-45b4-8afa-a38e3362ff9e",
      "name": "6. OpenAI - Transcribe Voice to Text",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -288,
        0
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "typeVersion": 1.8
    },
    {
      "id": "c93e9202-66c5-4829-9d3c-ea0d86bf7b68",
      "name": "7. Telegram - Get Photo File URL",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -768,
        240
      ],
      "parameters": {
        "fileId": "={{ $json.photoFileId }}",
        "resource": "file",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "1e66b681-ee81-4532-959a-bcb6334bf4cd",
      "name": "8. HTTP - Download Photo File",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -528,
        240
      ],
      "parameters": {
        "url": "=https://api.telegram.org/file/botYOUR_TELEGRAM_BOT_TOKEN/{{ $json.result.file_path }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "d951a29f-a22d-476e-b677-62dbc8f2576a",
      "name": "9. OpenAI - Analyze Image with Vision",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -288,
        240
      ],
      "parameters": {
        "text": "Describe this image in detail. Extract any text visible in the image. Focus on information that would be relevant for a customer support question.",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "detail": "auto"
        },
        "resource": "image",
        "inputType": "base64",
        "operation": "analyze"
      },
      "typeVersion": 1.8
    },
    {
      "id": "3bc2a410-6974-403e-bdcb-abcfa0fb249b",
      "name": "10. Telegram - Get Document File URL",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -768,
        480
      ],
      "parameters": {
        "fileId": "={{ $json.documentFileId }}",
        "resource": "file",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "4a5546de-e869-4c46-8828-3580b9f5bc14",
      "name": "11. HTTP - Download Document File",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -528,
        480
      ],
      "parameters": {
        "url": "=https://api.telegram.org/file/botYOUR_TELEGRAM_BOT_TOKEN/{{ $json.result.file_path }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "ade91c4c-d30f-4635-9f9e-2eefbaa790d3",
      "name": "12. Extract From File - Read PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -288,
        480
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1.1
    },
    {
      "id": "4f3fde66-5713-46db-9a1c-fd180d5bd149",
      "name": "13. Code - Build Query Text from Any Input",
      "type": "n8n-nodes-base.code",
      "position": [
        112,
        240
      ],
      "parameters": {
        "jsCode": "// Build the final query text from whatever input type came in\n// All branches converge here\nconst detect = $('2. Code - Detect Message Type').first().json;\n\nlet queryText  = '';\nlet inputType  = detect.messageType;\n\n// Text branch\nif (inputType === 'text') {\n  queryText = detect.text || '';\n}\n\n// Voice branch - Whisper output\nif (inputType === 'voice') {\n  try {\n    const transcribeOut = $('6. OpenAI - Transcribe Voice to Text').first().json;\n    queryText = transcribeOut.text || transcribeOut.transcript || '';\n    if (!queryText) queryText = detect.text || '';\n  } catch(e) {\n    queryText = detect.text || '';\n  }\n}\n\n// Photo branch - Vision description\nif (inputType === 'photo') {\n  try {\n    const visionOut = $('9. OpenAI - Analyze Image with Vision').first().json;\n    const description = visionOut.content || visionOut.text || '';\n    const caption = detect.text || '';\n    queryText = (caption ? 'Image caption: ' + caption + '\\n' : '') + 'Image description: ' + description;\n  } catch(e) {\n    queryText = detect.text || 'User sent an image';\n  }\n}\n\n// Document branch - PDF text\nif (inputType === 'document') {\n  try {\n    const pdfOut = $('12. Extract From File - Read PDF Text').first().json;\n    const pdfText  = (pdfOut.text || '').substring(0, 8000);\n    const caption  = detect.text || '';\n    queryText = (caption ? 'Document caption: ' + caption + '\\n' : '') + 'Document content: ' + pdfText;\n  } catch(e) {\n    queryText = detect.text || 'User sent a document';\n  }\n}\n\n// Admin ingest - no query needed, handled separately\nif (inputType === 'admin_ingest') {\n  queryText = '__ADMIN_INGEST__';\n}\n\nif (!queryText && !detect.text) {\n  queryText = 'User sent a message I could not read. Please ask them to send text.';\n}\n\nreturn [{\n  json: {\n    queryText,\n    inputType,\n    userId:    detect.userId,\n    username:  detect.username,\n    chatId:    detect.chatId,\n    sessionId: detect.sessionId,\n    ingestUrl: detect.ingestUrl,\n    isAdminIngest: detect.isAdminIngest\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ca8f2a8b-fd2e-43cd-91e5-d01b4b7fd9a3",
      "name": "14. HTTP - OpenAI Embed Query",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        352,
        240
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/embeddings",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"model\": \"text-embedding-ada-002\",\n  \"input\": {{ JSON.stringify($json.queryText.substring(0, 8000)) }}\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "9ffa1fcc-0f1a-4b9f-92ea-239d52abc926",
      "name": "15. HTTP - Pinecone Vector Search",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        240
      ],
      "parameters": {
        "url": "=https://YOUR_PINECONE_INDEX_HOST/query",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"vector\": {{ JSON.stringify($json.data[0].embedding) }},\n  \"topK\": 3,\n  \"includeMetadata\": true,\n  \"namespace\": \"support-kb\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Api-Key",
              "value": "YOUR_PINECONE_API_KEY"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d1ca003f-1e05-42b5-be45-840ae357265c",
      "name": "16. Code - Build RAG Context from Results",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        240
      ],
      "parameters": {
        "jsCode": "// Build RAG context from Pinecone search results\n// Merge query context with retrieved chunks\nconst pineconeResp = $json;\nconst queryCtx     = $('13. Code - Build Query Text from Any Input').first().json;\nconst embedResp    = $('14. HTTP - OpenAI Embed Query').first().json;\n\nconst matches = pineconeResp.matches || [];\n\n// Build context string from top 3 matches\nconst chunkTexts = matches.map(function(m, i) {\n  const text   = m.metadata?.text || m.metadata?.content || '';\n  const source = m.metadata?.source || m.metadata?.doc_id || 'Knowledge Base';\n  const score  = m.score ? m.score.toFixed(3) : '?';\n  return 'Chunk ' + (i + 1) + ' (relevance: ' + score + ', source: ' + source + '):\\n' + text;\n}).join('\\n\\n---\\n\\n');\n\nconst ragContext = chunkTexts || 'No relevant knowledge base content found for this query.';\nconst chunksUsed = matches.length;\n\n// Check if admin ingest command\nif (queryCtx.isAdminIngest) {\n  return [{\n    json: {\n      queryText:   queryCtx.queryText,\n      ragContext,\n      chunksUsed,\n      userId:    queryCtx.userId,\n      username:  queryCtx.username,\n      chatId:    queryCtx.chatId,\n      sessionId: queryCtx.sessionId,\n      ingestUrl: queryCtx.ingestUrl,\n      isAdminIngest: true,\n      inputType: queryCtx.inputType\n    }\n  }];\n}\n\nreturn [{\n  json: {\n    queryText:   queryCtx.queryText,\n    ragContext,\n    chunksUsed,\n    userId:    queryCtx.userId,\n    username:  queryCtx.username,\n    chatId:    queryCtx.chatId,\n    sessionId: queryCtx.sessionId,\n    ingestUrl: queryCtx.ingestUrl,\n    isAdminIngest: false,\n    inputType: queryCtx.inputType\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "55090c97-834a-4396-a038-e81953b1c4cb",
      "name": "17. HTTP - AI Generate Answer",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        240
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 45000
        },
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0.3,\n  \"max_tokens\": 600,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a helpful and friendly AI support assistant. Answer the user's question using ONLY the information from the Knowledge Base Context provided below. If the context does not contain enough information to answer the question accurately, say so clearly and suggest the user contact the support team directly. Keep answers concise and clear. Do not make up information that is not in the context.\\n\\nKnowledge Base Context:\\n\" + {{ JSON.stringify($json.ragContext) }}\n    },\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify($json.queryText) }}\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "a223df06-f037-4f75-b5a0-27e35265dae5",
      "name": "18. Code - Format Reply and Prepare Log",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        240
      ],
      "parameters": {
        "jsCode": "// Format bot reply and collect all data for logging\nconst aiResp   = $json;\nconst ragCtx   = $('16. Code - Build RAG Context from Results').first().json;\n\nconst rawReply = aiResp?.choices?.[0]?.message?.content || 'Sorry, I could not generate a response. Please try again.';\n\n// For admin ingest - give status message\nif (ragCtx.isAdminIngest) {\n  return [{\n    json: {\n      replyText:   'Admin: Ingestion command received for URL: ' + ragCtx.ingestUrl + '. The ingestion pipeline runs as a separate workflow. Please run your ingestion workflow to chunk, embed, and upload this document to Pinecone.',\n      queryText:   ragCtx.queryText,\n      ragContext:  ragCtx.ragContext,\n      chunksUsed:  ragCtx.chunksUsed,\n      userId:      ragCtx.userId,\n      username:    ragCtx.username,\n      chatId:      ragCtx.chatId,\n      sessionId:   ragCtx.sessionId,\n      inputType:   ragCtx.inputType,\n      loggedAt:    new Date().toISOString()\n    }\n  }];\n}\n\n// Telegram message limit is 4096 chars. Truncate if needed.\nconst replyText = rawReply.length > 4000\n  ? rawReply.substring(0, 3997) + '...'\n  : rawReply;\n\nreturn [{\n  json: {\n    replyText,\n    queryText:   ragCtx.queryText,\n    ragContext:  ragCtx.ragContext,\n    chunksUsed:  ragCtx.chunksUsed,\n    userId:      ragCtx.userId,\n    username:    ragCtx.username,\n    chatId:      ragCtx.chatId,\n    sessionId:   ragCtx.sessionId,\n    inputType:   ragCtx.inputType,\n    loggedAt:    new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c2d61614-4124-4bed-a134-46b81f7b2d60",
      "name": "19. Telegram - Send Reply to User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1552,
        96
      ],
      "parameters": {
        "text": "={{ $json.replyText }}",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3a5f57f3-85fc-4c60-bb58-f829c628d2b2",
      "name": "20. Sheets - Log Conversation",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1584,
        368
      ],
      "parameters": {
        "columns": {
          "value": {
            "User ID": "={{ $json.userId }}",
            "Username": "={{ $json.username }}",
            "Bot Reply": "={{ $json.replyText.substring(0, 500) }}",
            "Timestamp": "={{ $json.loggedAt }}",
            "Session ID": "={{ $json.sessionId }}",
            "Message Type": "={{ $json.inputType }}",
            "User Message": "={{ $json.queryText.substring(0, 500) }}",
            "RAG Chunks Used": "={{ $json.chunksUsed }}"
          },
          "schema": [
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "User ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "User ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Username",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Message Type",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Message Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "User Message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "User Message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Bot Reply",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Bot Reply",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "RAG Chunks Used",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "RAG Chunks Used",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Session ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Session ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Conversations"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_CONVERSATION_LOG_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a9063c31-7a73-4114-beae-c3092d92831b",
  "nodeGroups": [],
  "connections": {
    "14. HTTP - OpenAI Embed Query": {
      "main": [
        [
          {
            "node": "15. HTTP - Pinecone Vector Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "17. HTTP - AI Generate Answer": {
      "main": [
        [
          {
            "node": "18. Code - Format Reply and Prepare Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Code - Detect Message Type": {
      "main": [
        [
          {
            "node": "3. Switch - Route by Message Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. HTTP - Download Voice File": {
      "main": [
        [
          {
            "node": "6. OpenAI - Transcribe Voice to Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. HTTP - Download Photo File": {
      "main": [
        [
          {
            "node": "9. OpenAI - Analyze Image with Vision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Telegram - Get Voice File URL": {
      "main": [
        [
          {
            "node": "5. HTTP - Download Voice File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Telegram - Get Photo File URL": {
      "main": [
        [
          {
            "node": "8. HTTP - Download Photo File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. HTTP - Download Document File": {
      "main": [
        [
          {
            "node": "12. Extract From File - Read PDF Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "15. HTTP - Pinecone Vector Search": {
      "main": [
        [
          {
            "node": "16. Code - Build RAG Context from Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Switch - Route by Message Type": {
      "main": [
        [
          {
            "node": "13. Code - Build Query Text from Any Input",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "4. Telegram - Get Voice File URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "7. Telegram - Get Photo File URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "10. Telegram - Get Document File URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "13. Code - Build Query Text from Any Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Telegram - Get Document File URL": {
      "main": [
        [
          {
            "node": "11. HTTP - Download Document File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. OpenAI - Transcribe Voice to Text": {
      "main": [
        [
          {
            "node": "13. Code - Build Query Text from Any Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Extract From File - Read PDF Text": {
      "main": [
        [
          {
            "node": "13. Code - Build Query Text from Any Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. OpenAI - Analyze Image with Vision": {
      "main": [
        [
          {
            "node": "13. Code - Build Query Text from Any Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Telegram Trigger - Receive Messages": {
      "main": [
        [
          {
            "node": "2. Code - Detect Message Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18. Code - Format Reply and Prepare Log": {
      "main": [
        [
          {
            "node": "19. Telegram - Send Reply to User",
            "type": "main",
            "index": 0
          },
          {
            "node": "20. Sheets - Log Conversation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "16. Code - Build RAG Context from Results": {
      "main": [
        [
          {
            "node": "17. HTTP - AI Generate Answer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13. Code - Build Query Text from Any Input": {
      "main": [
        [
          {
            "node": "14. HTTP - OpenAI Embed Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow turns a Telegram bot into a multi-modal support chatbot that accepts text, voice, images, and PDFs, converts them to text, retrieves relevant knowledge from Pinecone using OpenAI embeddings, generates a grounded answer with OpenAI Chat Completions, and logs each…

Source: https://n8n.io/workflows/16928/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Alfred (funcional). Uses gmailTool, googleCalendarTool, gmail, embeddingsOpenAi. Event-driven trigger; 83 nodes.

Gmail Tool, Google Calendar Tool, Gmail +24
AI & RAG

This comprehensive workflow bundle is designed as a powerful starter kit, enabling you to build a multi-functional AI assistant on Telegram. It seamlessly integrates AI-powered voice interactions, an

Telegram Trigger, Telegram, OpenAI +19
AI & RAG

Agent IA Projet Client. Uses executeWorkflowTrigger, lmChatOpenAi, toolWorkflow, vectorStoreQdrant. Event-driven trigger; 79 nodes.

Execute Workflow Trigger, OpenAI Chat, Tool Workflow +16
AI & RAG

This n8n template automatically classifies incoming emails (Sales, Support, Internal, Finance, Promotions) and routes them to a dedicated OpenAI LLM Agent for processing. Depending on the category, th

OpenAI, Gmail, Text Classifier +16
AI & RAG

This n8n workflow is built for AI and automation agencies to promote their workflows through an interactive demo that prospects can try themselves. The featured system is a deep personalized email dem

Telegram Trigger, HTTP Request, OpenAI +10