This workflow follows the HTTP Request → Telegram 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 →
{
"name": "Local AI Agent (HTTP-based)",
"nodes": [
{
"parameters": {
"path": "telegram-ai-agent",
"httpMethod": "POST",
"responseMode": "onReceived",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-520,
300
]
},
{
"parameters": {
"functionCode": "const body = $json.body || {};\nconst message = body.message || {};\nconst chat = message.chat || {};\nconst text = message.text || '';\nconst document = message.document;\n\nconst isWebSearch = text.startsWith('/search ');\nconst isDocument = !!document;\n\nreturn [{\n chatId: chat.id,\n originalText: text,\n isWebSearch,\n isDocument,\n document,\n query: isWebSearch ? text.replace('/search ', '') : text\n}];"
},
"name": "Parse Telegram",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
-300,
300
]
},
{
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "Thinking...",
"additionalFields": {}
},
"name": "Telegram Typing",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
-90,
140
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.isDocument }}",
"value2": true
}
],
"string": []
}
},
"name": "If Document",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
-90,
560
]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.isWebSearch }}",
"value2": true
}
],
"string": []
}
},
"name": "If Web Search",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
90,
420
]
},
{
"parameters": {
"requestMethod": "GET",
"url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_TOKEN + '/getFile' }}",
"jsonParameters": true,
"options": {},
"sendQuery": true,
"queryParametersJson": "={{ JSON.stringify({ \"file_id\": $json.document.file_id }) }}"
},
"name": "Get File Path",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
200,
700
]
},
{
"parameters": {
"requestMethod": "GET",
"url": "={{ 'https://api.telegram.org/file/bot' + $env.TELEGRAM_TOKEN + '/' + $json.result.file_path }}",
"jsonParameters": true,
"options": {},
"responseFormat": "string"
},
"name": "Download File",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
400,
700
]
},
{
"parameters": {
"functionCode": "const text = $json.data || '';\n// Simple chunking by paragraphs or length\nconst chunkSize = 1000;\nconst chunks = [];\n\n// Basic split by newlines first\nconst paragraphs = text.split(/\\n\\s*\\n/);\n\nlet currentChunk = '';\nfor (const p of paragraphs) {\n if ((currentChunk + p).length > chunkSize) {\n if (currentChunk) chunks.push(currentChunk.trim());\n currentChunk = p;\n } else {\n currentChunk += (currentChunk ? '\\n\\n' : '') + p;\n }\n}\nif (currentChunk) chunks.push(currentChunk.trim());\n\n// If no paragraphs (single line), force split\nif (chunks.length === 0 && text.length > 0) {\n for (let i = 0; i < text.length; i += chunkSize) {\n chunks.push(text.slice(i, i + chunkSize));\n }\n}\n\nreturn chunks.map((chunk, index) => ({\n chunk,\n chunkIndex: index,\n totalChunks: chunks.length,\n fileName: $node[\"Parse Telegram\"].json.document.file_name,\n chatId: $node[\"Parse Telegram\"].json.chatId\n}));"
},
"name": "Split Text",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
600,
700
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ ($env.OLLAMA_BASE_URL || 'http://ollama:11434') + '/api/embeddings' }}",
"jsonParameters": true,
"options": {},
"headerParameters": [
{
"name": "Authorization",
"value": "={{ $env.OLLAMA_API_KEY ? ('Bearer ' + $env.OLLAMA_API_KEY) : '' }}"
}
],
"bodyParametersJson": "={{ JSON.stringify({\n \"model\": $env.OLLAMA_MODEL || \"llama3.2:3b\",\n \"prompt\": $json.chunk\n}) }}"
},
"name": "Embed Chunk",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
800,
700
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ 'http://chroma:8000/api/v1/collections/' + $('Merge Collection ID').first().json.collectionId + '/upsert' }}",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "={{ JSON.stringify({\n \"ids\": [ $('Parse Telegram').first().json.document.file_id + '_' + $json.chunkIndex ],\n \"embeddings\": [ $json.embedding ],\n \"documents\": [ $json.chunk ],\n \"metadatas\": [ { \"source\": \"file\", \"filename\": $json.fileName } ]\n}) }}"
},
"name": "Upsert Chunk",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1000,
700
]
},
{
"parameters": {
"chatId": "={{ $('Parse Telegram').first().json.chatId }}",
"text": "={{ 'I have read \"' + $('Parse Telegram').first().json.document.file_name + '\". Processed ' + ($('Split Text').item.json.chunkIndex + 1) + ' of ' + $('Split Text').item.json.totalChunks + ' chunks.' }}",
"additionalFields": {}
},
"name": "Notify Progress",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1200,
700
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"requestMethod": "POST",
"url": "http://chroma:8000/api/v1/collections",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "{\"name\": \"ai_memory\", \"get_or_create\": true}"
},
"name": "Get Collection",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
-200,
420
]
},
{
"parameters": {
"functionCode": "const original = $node[\"Parse Telegram\"].json;\nconst collection = $json;\nreturn [{ ...original, collectionId: collection.id }];"
},
"name": "Merge Collection ID",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
200,
420
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ ($env.OLLAMA_BASE_URL || 'http://ollama:11434') + '/api/embeddings' }}",
"jsonParameters": true,
"options": {},
"headerParameters": [
{
"name": "Authorization",
"value": "={{ $env.OLLAMA_API_KEY ? ('Bearer ' + $env.OLLAMA_API_KEY) : '' }}"
}
],
"bodyParametersJson": "={{ JSON.stringify({\n \"model\": $env.OLLAMA_MODEL || \"llama3.2:3b\",\n \"prompt\": $json.query\n}) }}"
},
"name": "Generate Query Embedding",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
140,
220
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ 'http://chroma:8000/api/v1/collections/' + $node[\"Merge Collection ID\"].json.collectionId + '/query' }}",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "={{ JSON.stringify({ \"query_embeddings\": [ $json.embedding ], \"n_results\": 3 }) }}"
},
"name": "Chroma Query",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
340,
220
]
},
{
"parameters": {
"functionCode": "const results = $json.documents || [];\nlet context = '';\nif (Array.isArray(results) && results.length > 0) {\n const docs = results[0];\n if (Array.isArray(docs)) {\n context = docs.join('\\n');\n }\n}\n\n// Access original data from Parse Telegram node\nconst originalData = $('Parse Telegram').first().json;\n\nreturn [{\n chatId: originalData.chatId,\n originalText: originalData.originalText,\n query: originalData.query,\n isWebSearch: originalData.isWebSearch,\n memoryContext: context\n}];"
},
"name": "Build RAG Context",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
540,
220
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "https://api.tavily.com/search",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "={{ JSON.stringify({\n \"api_key\": $env.TAVILY_API_KEY,\n \"query\": $json.query,\n \"search_depth\": \"advanced\",\n \"max_results\": 7,\n \"include_answer\": true,\n \"include_raw_content\": true\n}) }}"
},
"name": "Tavily Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
140,
520
]
},
{
"parameters": {
"functionCode": "let webContext = '';\n\nif ($json.answer) {\n webContext += `Tavily Direct Answer:\\n${$json.answer}\\n\\n`;\n}\n\nif (Array.isArray($json.results)) {\n webContext += \"Search Results:\\n\";\n webContext += $json.results.map((r, i) => `[${i+1}] Title: ${r.title}\\nURL: ${r.url}\\nContent: ${r.content}`).join('\\n\\n');\n}\n\n// Access original data from Parse Telegram node\nconst originalData = $('Parse Telegram').first().json;\n\nreturn [{\n chatId: originalData.chatId,\n originalText: originalData.originalText,\n query: originalData.query,\n memoryContext: '',\n webContext\n}];"
},
"name": "Build Web Context",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
360,
520
]
},
{
"parameters": {
"functionCode": "const system = '\u0422\u044b \u0440\u0443\u0441\u0441\u043a\u043e\u044f\u0437\u044b\u0447\u043d\u044b\u0439 \u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442 \u0432 Telegram. \u041e\u0442\u0432\u0435\u0447\u0430\u0439 \u0441\u0442\u0440\u043e\u0433\u043e \u043f\u043e \\\"Web search context\\\". \u041d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0437\u043d\u0430\u043d\u0438\u044f. \u0415\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u043d\u0435\u0442 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435, \u043f\u0440\u044f\u043c\u043e \u0441\u043a\u0430\u0436\u0438 \u043e\u0431 \u044d\u0442\u043e\u043c. \u041f\u0440\u0438\u0432\u043e\u0434\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 [1], [2]. \u0411\u0443\u0434\u044c \u043a\u0440\u0430\u0442\u043e\u043a.';\n\nconst parts = [];\nif ($json.memoryContext) {\n parts.push('Memory context:\\n' + $json.memoryContext);\n}\nif ($json.webContext) {\n parts.push('Web search context:\\n' + $json.webContext);\n}\nconst context = parts.join('\\n\\n');\n\nlet content = '';\nif (context) {\n content = `<<CONTEXT_START>>\\n${context}\\n<<CONTEXT_END>>\\n\\n\u0412\u043e\u043f\u0440\u043e\u0441: ${$json.query}\\n\u041e\u0442\u0432\u0435\u0442:`;\n} else {\n content = `\u0412\u043e\u043f\u0440\u043e\u0441: ${$json.query}\\n\u041e\u0442\u0432\u0435\u0442:`;\n}\n\nreturn [{\n chatId: $json.chatId,\n prompt: content,\n system,\n query: $json.query\n}];"
},
"name": "Build Prompt",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
580,
340
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ ($env.OLLAMA_BASE_URL || 'http://ollama:11434') + '/api/chat' }}",
"jsonParameters": true,
"options": {},
"headerParameters": [
{
"name": "Authorization",
"value": "={{ $env.OLLAMA_API_KEY ? ('Bearer ' + $env.OLLAMA_API_KEY) : '' }}"
}
],
"bodyParametersJson": "={{ JSON.stringify({\n \"model\": ($env.OLLAMA_MODEL || \"llama3.2:3b\"),\n \"keep_alive\": \"30m\",\n \"stream\": false,\n \"options\": { \"temperature\": 0 },\n \"messages\": [\n {\"role\": \"system\", \"content\": $json.system},\n {\"role\": \"user\", \"content\": $json.prompt}\n ]\n}) }}"
},
"name": "Ollama Chat",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
800,
340
]
},
{
"parameters": {
"functionCode": "const resp = $json;\nlet llmAnswer = '';\nif (resp.message && resp.message.content) {\n llmAnswer = resp.message.content;\n} else if (resp.error) {\n llmAnswer = \"\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0442 AI: \" + JSON.stringify(resp.error);\n} else {\n llmAnswer = \"\u041d\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 AI. \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442: \" + JSON.stringify(resp);\n}\n\nlet tavilyAnswer = '';\ntry {\n const t = $('Tavily Search').first().json;\n if (t && t.answer) tavilyAnswer = t.answer;\n} catch (e) {}\n\nconst finalAnswer = tavilyAnswer || llmAnswer;\n\nconst originalData = $('Parse Telegram').first().json;\nconst promptData = $('Build Prompt').first().json;\n\nreturn [{\n chatId: originalData.chatId,\n answer: finalAnswer,\n question: promptData.query\n}];"
},
"name": "Extract Answer",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1020,
340
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ ($env.OLLAMA_BASE_URL || 'http://ollama:11434') + '/api/embeddings' }}",
"jsonParameters": true,
"options": {},
"headerParameters": [
{
"name": "Authorization",
"value": "={{ $env.OLLAMA_API_KEY ? ('Bearer ' + $env.OLLAMA_API_KEY) : '' }}"
}
],
"bodyParametersJson": "={{ JSON.stringify({\n \"model\": $env.OLLAMA_MODEL || \"llama3.2:3b\",\n \"prompt\": ($json.question + \"\\n\\n\" + $json.answer).substring(0, 4000)\n}) }}"
},
"name": "Generate Answer Embedding",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1240,
220
]
},
{
"parameters": {
"requestMethod": "POST",
"url": "={{ 'http://chroma:8000/api/v1/collections/' + $('Merge Collection ID').first().json.collectionId + '/upsert' }}",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "={{ JSON.stringify({\n \"ids\": [ Date.now().toString() ],\n \"embeddings\": [ $json.embedding ],\n \"documents\": [ $('Extract Answer').first().json.question + \"\\n\\n\" + $('Extract Answer').first().json.answer ],\n \"metadatas\": [ { \"source\": \"telegram\" } ]\n}) }}"
},
"name": "Chroma Upsert",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
1440,
220
]
},
{
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "={{ $json.answer }}",
"additionalFields": {}
},
"name": "Telegram Send Answer",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
1640,
460
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Parse Telegram",
"type": "main",
"index": 0
}
]
]
},
"Parse Telegram": {
"main": [
[
{
"node": "Telegram Typing",
"type": "main",
"index": 0
},
{
"node": "Get Collection",
"type": "main",
"index": 0
}
]
]
},
"Get Collection": {
"main": [
[
{
"node": "Merge Collection ID",
"type": "main",
"index": 0
}
]
]
},
"Merge Collection ID": {
"main": [
[
{
"node": "If Document",
"type": "main",
"index": 0
}
]
]
},
"If Document": {
"main": [
[
{
"node": "Get File Path",
"type": "main",
"index": 0
}
],
[
{
"node": "If Web Search",
"type": "main",
"index": 0
}
]
]
},
"If Web Search": {
"main": [
[
{
"node": "Tavily Search",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Query Embedding",
"type": "main",
"index": 0
}
]
]
},
"Generate Query Embedding": {
"main": [
[
{
"node": "Chroma Query",
"type": "main",
"index": 0
}
]
]
},
"Chroma Query": {
"main": [
[
{
"node": "Build RAG Context",
"type": "main",
"index": 0
}
]
]
},
"Tavily Search": {
"main": [
[
{
"node": "Build Web Context",
"type": "main",
"index": 0
}
]
]
},
"Build RAG Context": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build Web Context": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build Prompt": {
"main": [
[
{
"node": "Ollama Chat",
"type": "main",
"index": 0
}
]
]
},
"Ollama Chat": {
"main": [
[
{
"node": "Extract Answer",
"type": "main",
"index": 0
}
]
]
},
"Extract Answer": {
"main": [
[
{
"node": "Generate Answer Embedding",
"type": "main",
"index": 0
},
{
"node": "Telegram Send Answer",
"type": "main",
"index": 0
}
]
]
},
"Generate Answer Embedding": {
"main": [
[
{
"node": "Chroma Upsert",
"type": "main",
"index": 0
}
]
]
},
"Get File Path": {
"main": [
[
{
"node": "Download File",
"type": "main",
"index": 0
}
]
]
},
"Download File": {
"main": [
[
{
"node": "Split Text",
"type": "main",
"index": 0
}
]
]
},
"Split Text": {
"main": [
[
{
"node": "Embed Chunk",
"type": "main",
"index": 0
}
]
]
},
"Embed Chunk": {
"main": [
[
{
"node": "Upsert Chunk",
"type": "main",
"index": 0
}
]
]
},
"Upsert Chunk": {
"main": [
[
{
"node": "Notify Progress",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
telegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Local AI Agent (HTTP-based). Uses telegram, httpRequest. Webhook trigger; 24 nodes.
Source: https://github.com/Katapios/N8N-telegram-manager/blob/42a0f215059060fe5a22eeb0d76383a9859567b6/n8n/workflows/ai-agent.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content
This workflow automates document processing using LlamaParse to extract and analyze text from various file formats. It intelligently processes documents, extracts structured data, and delivers actiona
|Overview |Sample| |-|-| |This template is the first of its kind: it automatically generates both the caption and the image for your Instagram posts by analysing your existing feed, with zero spreadsh
🔍🛠️Perplexity Researcher to HTML Web Page. Uses stickyNote, lmChatOpenAi, outputParserStructured, respondToWebhook. Webhook trigger; 47 nodes.