This workflow follows the Chainllm → 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 →
{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "rag-chat",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"id": "ae899ed1-09f0-4723-a0fb-2d00f1f0f81f",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
48,
-96
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "3cc9a8f0-213c-4f90-8dc3-44fe586e8919",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
3600,
-192
]
},
{
"parameters": {
"jsCode": "// ====================================\n// QUERY ANALYZER - Intent Detection\n// ====================================\n\nconst body = $input.first().json.body || $input.first().json;\nconst query = body.query || body.chatInput || '';\n\n// Intent detection\nconst isGreeting = /^(hi|hello|hey|good morning|good afternoon)/i.test(query.trim());\nconst isListDocs = /\\b(list|show|what)\\s+(documents|docs|manuals)\\b/i.test(query);\n\n// Characteristic detection\nconst hasTechnicalTerms = /\\b(span|block|gpm|pump|valve|sensor|controller|wire|wiring|component|module|analog|digital|bacnet|niagara|kitcontrol|honeywell)\\b/i.test(query);\nconst wantsVisuals = /\\b(show|image|diagram|picture|visual|schematic|wiring|drawing)\\b/i.test(query) || /wir(e|ing)/i.test(query);\nconst wantsDetails = /\\b(detailed|spec|specification|table|parameter|configuration|breakdown)\\b/i.test(query);\n\n// Determine query type\nlet queryType = 'technical';\nif (isGreeting) queryType = 'greeting';\nelse if (isListDocs) queryType = 'list_documents';\n\n// Routing flags\nconst needsDocRouting = queryType === 'technical' && !body.doc_id;\nconst needsImages = wantsVisuals && queryType === 'technical';\nconst needsTables = wantsDetails && queryType === 'technical';\n\nreturn [{\n json: {\n query,\n queryType,\n needsDocRouting,\n needsImages,\n needsTables,\n doc_id: body.doc_id || null,\n top_k: wantsDetails ? 30 : 20,\n fts_weight: hasTechnicalTerms ? 0.6 : 0.4,\n vector_weight: hasTechnicalTerms ? 0.4 : 0.6,\n user_id: body.userId || body.user_id || 'anonymous',\n username: body.username || 'User'\n }\n}];"
},
"id": "1fe1d8d3-d668-45e6-a4c1-fbde8e1570b8",
"name": "1. Query Analyzer",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
304,
-96
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.queryType }}",
"value2": "greeting"
}
]
}
},
"id": "25f66f1d-e7e1-4402-9fec-c0bfa41a56d2",
"name": "2a. Is Greeting?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
512,
-96
]
},
{
"parameters": {
"jsCode": "const analyzer = $input.first().json;\n\nreturn [{\n json: {\n answer: \"Hello! I'm your technical documentation assistant. I can help you with information about pumps, valves, wiring, configurations, and more. What would you like to know?\",\n citations: [],\n images: [],\n tables: [],\n metadata: {\n timestamp: new Date().toISOString(),\n queryType: 'greeting',\n query: analyzer.query,\n user_id: analyzer.user_id,\n username: analyzer.username\n },\n debug: {\n pipeline: 'greeting-shortcut',\n executionPath: 'direct'\n }\n }\n}];"
},
"id": "c5b356f4-7435-4d2d-b963-a2eb40025182",
"name": "Greeting Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
704,
-224
]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.needsDocRouting }}",
"value2": true
}
]
}
},
"id": "e4ddbfa0-046f-4895-a4ed-70c92ac6e35c",
"name": "2b. Needs Doc Routing?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
704,
32
]
},
{
"parameters": {
"promptType": "define",
"text": "=You are a document router for a technical documentation system.\n\nUser query: {{ $json.query }}\n\nYour task:\n1. Identify which document types would contain this information\n2. Enhance the query with technical terms and synonyms for better search\n\nReturn ONLY valid JSON (no markdown, no explanation):\n{\n \"doc_ids\": [\"document_id_1\", \"document_id_2\"],\n \"enhanced_query\": \"improved search query with technical terms\",\n \"reasoning\": \"brief explanation\"\n}\n\nExamples:\nQuery: \"How to wire a pump?\"\nResponse: {\"doc_ids\": [\"kitcontrol_manual\"], \"enhanced_query\": \"pump wiring connection configuration analog output controller\", \"reasoning\": \"Wiring info typically in control system manuals\"}\n\nNow process this query and return JSON:"
},
"id": "df1d624f-b1cd-4656-86bc-fa4200af90ed",
"name": "3. Document Router Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.4,
"position": [
928,
224
]
},
{
"parameters": {
"options": {
"maxTokens": 500,
"temperature": 0.3
}
},
"id": "0d33a6c5-d23f-473a-8b23-fc508a78eeb6",
"name": "OpenAI Router Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1,
"position": [
832,
416
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// ====================================\n// PARSE ROUTER OUTPUT\n// ====================================\n\nconst routerOutput = $input.first().json;\nconst analyzer = $('1. Query Analyzer').first().json;\n\nlet parsed;\n\n// Handle different output formats\nif (routerOutput.response) {\n // LLM Chain returns {response: \"...\"}\n const responseText = routerOutput.response;\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n parsed = jsonMatch ? JSON.parse(jsonMatch[0]) : { doc_ids: [], enhanced_query: analyzer.query };\n} else if (routerOutput.doc_ids) {\n // Already parsed JSON\n parsed = routerOutput;\n} else {\n // Fallback\n parsed = { doc_ids: [], enhanced_query: analyzer.query };\n}\n\nconst doc_ids = parsed.doc_ids || [];\nconst enhanced_query = parsed.enhanced_query || analyzer.query;\n\nreturn [{\n json: {\n query: enhanced_query,\n doc_ids,\n top_k: analyzer.top_k,\n fts_weight: analyzer.fts_weight,\n vector_weight: analyzer.vector_weight,\n original_query: analyzer.query,\n routing_reasoning: parsed.reasoning || 'No routing performed'\n }\n}];"
},
"id": "f41b0b1f-7e98-4dff-bf07-197988c371eb",
"name": "4. Parse Router Output",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1216,
224
]
},
{
"parameters": {
"jsCode": "// ====================================\n// PREPARE SEARCH (No Routing Path)\n// ====================================\n\nconst analyzer = $input.first().json;\n\nreturn [{\n json: {\n query: analyzer.query,\n doc_ids: analyzer.doc_id ? [analyzer.doc_id] : null,\n top_k: analyzer.top_k,\n fts_weight: analyzer.fts_weight,\n vector_weight: analyzer.vector_weight,\n original_query: analyzer.query,\n routing_reasoning: 'Direct search without routing'\n }\n}];"
},
"id": "01989b7d-b0f8-44e4-bf3b-f5629dc985ce",
"name": "4b. Prepare Direct Search",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
-96
]
},
{
"parameters": {
"method": "POST",
"url": "https://dwisbglrutplhcotbehy.supabase.co/functions/v1/hybrid-search",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n query: $json.query,\n doc_ids: $json.doc_ids,\n top_k: $json.top_k,\n fts_weight: $json.fts_weight,\n vector_weight: $json.vector_weight\n}) }}",
"options": {}
},
"id": "45d1f9e6-b94a-4780-bfd6-935d7647dcfc",
"name": "5. Hybrid Search",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
1424,
-96
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// ====================================\n// EXTRACT CHUNK IDS\n// ====================================\n\nconst searchResult = $input.first().json;\nconst chunks = searchResult.chunks || [];\n\nif (chunks.length === 0) {\n return [{\n json: {\n error: 'No chunks found',\n chunk_ids: [],\n doc_id: null,\n chunks: []\n }\n }];\n}\n\nconst chunk_ids = chunks.map(c => c.id);\nconst doc_id = chunks[0].doc_id;\n\nreturn [{\n json: {\n chunk_ids,\n doc_id,\n chunks_count: chunks.length,\n chunks_preview: chunks.slice(0, 3).map(c => ({\n id: c.id,\n page: c.page_number,\n preview: c.content.substring(0, 100)\n }))\n }\n}];"
},
"id": "2feb600c-cbb6-4dd0-b309-2b303c5bf2e3",
"name": "6. Extract Chunk IDs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1616,
-96
]
},
{
"parameters": {
"method": "POST",
"url": "https://dwisbglrutplhcotbehy.supabase.co/functions/v1/context-expansion",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n chunk_ids: $json.chunk_ids,\n doc_id: $json.doc_id,\n token_budget: 6000,\n expand_siblings: true,\n expand_parents: true\n}) }}",
"options": {}
},
"id": "41dfbbde-e284-48b3-970d-3648484e0d5f",
"name": "7. Context Expansion",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
1792,
-96
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $('1. Query Analyzer').first().json.needsImages }}",
"value2": true
}
]
}
},
"id": "23a0798b-eba7-46da-8b98-f8c98af32e0d",
"name": "8. Needs Images?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1968,
96
]
},
{
"parameters": {
"method": "POST",
"url": "https://dwisbglrutplhcotbehy.supabase.co/functions/v1/retrieve-with-images",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n chunk_ids: $('6. Extract Chunk IDs').first().json.chunk_ids\n}) }}",
"options": {}
},
"id": "84a3429b-ba43-4fd3-8fe5-d9f7b2dd1414",
"name": "9a. Retrieve Images",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
2144,
-96
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $('1. Query Analyzer').first().json.needsTables }}",
"value2": true
}
]
}
},
"id": "2d52a50a-9b96-45ab-9899-dcf0c17c8600",
"name": "9b. Needs Tables?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2336,
112
]
},
{
"parameters": {
"method": "POST",
"url": "https://dwisbglrutplhcotbehy.supabase.co/functions/v1/retrieve-with-tables",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "supabaseApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n chunk_ids: $('6. Extract Chunk IDs').first().json.chunk_ids\n}) }}",
"options": {}
},
"id": "564f35be-f4c0-4d42-aaa2-823dcf34fcd2",
"name": "10a. Retrieve Tables",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
2528,
-96
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// ====================================\n// MERGE ALL RESULTS - FIXED\n// ====================================\n\nconst expansionResult = $('7. Context Expansion').first().json;\n\n// Safely get nodes - they may not have executed\nlet imagesNode = null;\nlet tablesNode = null;\n\ntry {\n imagesNode = $('9a. Retrieve Images').first();\n} catch (e) {\n // Node didn't execute, that's ok\n}\n\ntry {\n tablesNode = $('10a. Retrieve Tables').first();\n} catch (e) {\n // Node didn't execute, that's ok\n}\n\nconst expanded_chunks = expansionResult.expanded_chunks || [];\n\n// Extract images if retrieved\nconst images = (imagesNode && imagesNode.json && imagesNode.json.chunks)\n ? imagesNode.json.chunks.flatMap(c => c.images || [])\n : [];\n\n// Extract tables if retrieved\nconst tables = (tablesNode && tablesNode.json && tablesNode.json.chunks)\n ? tablesNode.json.chunks.flatMap(c => c.tables || [])\n : [];\n\n// Format context for LLM\nconst contextText = expanded_chunks\n .map(chunk => `[Page ${chunk.page_number}]\\n${chunk.content}`)\n .join('\\n\\n---\\n\\n');\n\nconst imagesText = images.length > 0\n ? images\n .map(img => `[Image - Page ${img.page_number}]: ${img.caption || img.summary || 'Diagram'}`)\n .join('\\n')\n : 'No images available';\n\nconst tablesText = tables.length > 0\n ? tables\n .map(tbl => `[Table - Page ${tbl.page_number}]:\\n${tbl.markdown}\\n${tbl.description || ''}`)\n .join('\\n\\n')\n : 'No tables available';\n\nreturn [\n {\n json: {\n context: contextText,\n images_description: imagesText,\n tables_content: tablesText,\n images_array: images,\n tables_array: tables,\n metadata: {\n chunks_count: expanded_chunks.length,\n images_count: images.length,\n tables_count: tables.length,\n },\n },\n },\n];\n"
},
"id": "131b3e5f-2916-4844-b826-0ed76c2d03e6",
"name": "11. Merge Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2736,
128
]
},
{
"parameters": {
"promptType": "define",
"text": "=You are a technical documentation assistant. Generate a comprehensive answer based on the retrieved context.\n\nUser Query: {{ $('1. Query Analyzer').first().json.query }}\n\n=== RETRIEVED CONTEXT ===\n{{ $json.context }}\n\n=== AVAILABLE IMAGES ===\n{{ $json.images_description }}\n\n=== AVAILABLE TABLES ===\n{{ $json.tables_content }}\n\n=== INSTRUCTIONS ===\n1. Answer the user's query comprehensively using the context provided\n2. Cite specific pages using [Page X] format after each fact\n3. If images are available and relevant, reference them: \"See the wiring diagram [Page 45]\"\n4. If tables are available, reference them: \"See specifications table [Page 46]\"\n5. Use clear formatting with headings and bullet points\n6. Be technical and accurate\n7. If context doesn't fully answer the query, acknowledge limitations\n\nGenerate your answer:"
},
"id": "99a4f4a5-9f8e-40fb-9921-3a105ca362b0",
"name": "12. Answer Generator Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.4,
"position": [
2960,
128
]
},
{
"parameters": {
"options": {
"maxTokens": 2000,
"temperature": 0.3
}
},
"id": "53f0f11f-4c37-4e6b-9d33-dfbe475270f5",
"name": "OpenAI Answer Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1,
"position": [
2912,
320
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// ====================================\n// FORMAT FINAL RESPONSE\n// ====================================\n\nconst answerOutput = $input.first().json;\nconst answerText = answerOutput.response || answerOutput.text || answerOutput.output || 'Error generating answer';\nconst mergedData = $('11. Merge Results').first().json;\nconst analyzer = $('1. Query Analyzer').first().json;\n\n// Extract citations\nconst citationRegex = /\\[Page\\s+(\\d+)\\]/gi;\nconst citations = [];\nlet match;\nwhile ((match = citationRegex.exec(answerText)) !== null) {\n citations.push({ page: parseInt(match[1]) });\n}\n\n// Get unique citations\nconst uniqueCitations = [...new Set(citations.map(c => c.page))].map(page => ({ page }));\n\nreturn [{\n json: {\n answer: answerText,\n citations: uniqueCitations,\n images: mergedData.images_array,\n tables: mergedData.tables_array,\n metadata: {\n timestamp: new Date().toISOString(),\n query: analyzer.query,\n queryType: analyzer.queryType,\n chunks_retrieved: mergedData.metadata.chunks_count,\n images_retrieved: mergedData.metadata.images_count,\n tables_retrieved: mergedData.metadata.tables_count,\n user_id: analyzer.user_id,\n username: analyzer.username\n },\n debug: {\n needsImages: analyzer.needsImages,\n needsTables: analyzer.needsTables,\n needsDocRouting: analyzer.needsDocRouting,\n pipeline: 'deterministic-chains-v1'\n }\n }\n}];"
},
"id": "1b223ac8-9ba5-42e7-9beb-4a8e66aef0ef",
"name": "13. Format Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3296,
-32
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "1. Query Analyzer",
"type": "main",
"index": 0
}
]
]
},
"1. Query Analyzer": {
"main": [
[
{
"node": "2a. Is Greeting?",
"type": "main",
"index": 0
}
]
]
},
"2a. Is Greeting?": {
"main": [
[
{
"node": "Greeting Response",
"type": "main",
"index": 0
}
],
[
{
"node": "2b. Needs Doc Routing?",
"type": "main",
"index": 0
}
]
]
},
"Greeting Response": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"2b. Needs Doc Routing?": {
"main": [
[
{
"node": "3. Document Router Chain",
"type": "main",
"index": 0
}
],
[
{
"node": "4b. Prepare Direct Search",
"type": "main",
"index": 0
}
]
]
},
"3. Document Router Chain": {
"main": [
[
{
"node": "4. Parse Router Output",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Router Model": {
"ai_languageModel": [
[
{
"node": "3. Document Router Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"4. Parse Router Output": {
"main": [
[
{
"node": "5. Hybrid Search",
"type": "main",
"index": 0
}
]
]
},
"4b. Prepare Direct Search": {
"main": [
[
{
"node": "5. Hybrid Search",
"type": "main",
"index": 0
}
]
]
},
"5. Hybrid Search": {
"main": [
[
{
"node": "6. Extract Chunk IDs",
"type": "main",
"index": 0
}
]
]
},
"6. Extract Chunk IDs": {
"main": [
[
{
"node": "7. Context Expansion",
"type": "main",
"index": 0
}
]
]
},
"7. Context Expansion": {
"main": [
[
{
"node": "8. Needs Images?",
"type": "main",
"index": 0
}
]
]
},
"8. Needs Images?": {
"main": [
[
{
"node": "9a. Retrieve Images",
"type": "main",
"index": 0
}
],
[
{
"node": "9b. Needs Tables?",
"type": "main",
"index": 0
}
]
]
},
"9a. Retrieve Images": {
"main": [
[
{
"node": "9b. Needs Tables?",
"type": "main",
"index": 0
}
]
]
},
"9b. Needs Tables?": {
"main": [
[
{
"node": "10a. Retrieve Tables",
"type": "main",
"index": 0
}
],
[
{
"node": "11. Merge Results",
"type": "main",
"index": 0
}
]
]
},
"10a. Retrieve Tables": {
"main": [
[
{
"node": "11. Merge Results",
"type": "main",
"index": 0
}
]
]
},
"11. Merge Results": {
"main": [
[
{
"node": "12. Answer Generator Chain",
"type": "main",
"index": 0
}
]
]
},
"12. Answer Generator Chain": {
"main": [
[
{
"node": "13. Format Response",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Answer Model": {
"ai_languageModel": [
[
{
"node": "12. Answer Generator Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"13. Format Response": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
}
}
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.
openAiApisupabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Ame-Rag-Agent. Uses chainLlm, lmChatOpenAi, httpRequest. Webhook trigger; 21 nodes.
Source: https://github.com/Cnoccir/docling-n8n/blob/7617a1740fc3811b51061839de4bfd77688ef322/n8n-workflows/AME-RAG-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.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
my-secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 86 nodes.
The "Short Content" automation is a powerful, all-in-one solution designed to streamline the creation of short videos for social media, marketing, or personal projects. Leveraging cutting-edge AI tool
🔥 LIMITED-TIME OFFER: AI Video Automation (Previously \$59) Previously Template