This workflow follows the Chat Trigger → 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 →
{
"name": "RAG Query - Document Q&A",
"nodes": [
{
"parameters": {
"content": "## RAG Query Workflow\n\nThis workflow handles document Q&A using vector similarity search.\n\n**Flow:**\n1. Receive question from Chat or Webhook\n2. Generate query embedding\n3. Search similar chunks in pgvector\n4. Build context from retrieved chunks\n5. Send to Ollama LLM with context\n6. Return answer",
"height": 300,
"width": 320
},
"id": "sticky-note",
"name": "Instructions",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-200,
-100
]
},
{
"parameters": {
"options": {
"responseMode": "responseNode"
}
},
"id": "chat-trigger",
"name": "Chat Trigger",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [
0,
200
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "rag-query",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
400
]
},
{
"parameters": {
"jsCode": "// Get question from either Chat Trigger or Webhook\nlet question = '';\nlet sessionId = '';\n\ntry {\n // Try Chat Trigger first\n const chatData = $('Chat Trigger').first()?.json;\n if (chatData?.chatInput) {\n question = chatData.chatInput;\n sessionId = chatData.sessionId || 'default';\n }\n} catch (e) {\n // Fall back to Webhook\n const webhookData = $('Webhook Trigger').first()?.json;\n if (webhookData) {\n question = webhookData.question || webhookData.query || webhookData.message || '';\n sessionId = webhookData.sessionId || webhookData.session_id || 'webhook';\n }\n}\n\nif (!question) {\n throw new Error('No question provided. Send {\"question\": \"your question\"}');\n}\n\nreturn [{\n json: {\n question,\n sessionId\n }\n}];"
},
"id": "extract-question",
"name": "Extract Question",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
300
]
},
{
"parameters": {
"method": "POST",
"url": "http://192.168.50.49:11434/api/embeddings",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: 'mxbai-embed-large:latest', prompt: $json.question }) }}",
"options": {
"timeout": 60000
}
},
"id": "query-embedding",
"name": "Generate Query Embedding",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
]
},
{
"parameters": {
"jsCode": "const embedding = $input.first().json.embedding;\nconst question = $('Extract Question').first().json.question;\nconst sessionId = $('Extract Question').first().json.sessionId;\n\nif (!embedding || !Array.isArray(embedding)) {\n throw new Error('Invalid embedding response from Ollama');\n}\n\n// Format embedding as PostgreSQL vector string\nconst vectorString = '[' + embedding.join(',') + ']';\n\nreturn [{\n json: {\n question,\n sessionId,\n queryEmbedding: vectorString\n }\n}];"
},
"id": "format-embedding",
"name": "Format Query Embedding",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT \n id,\n document_name,\n chunk_index,\n content,\n 1 - (embedding <=> '{{ $json.queryEmbedding }}') as similarity\nFROM document_chunks\nWHERE embedding IS NOT NULL\nORDER BY embedding <=> '{{ $json.queryEmbedding }}'\nLIMIT 5;",
"options": {}
},
"id": "vector-search",
"name": "Vector Similarity Search",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
880,
300
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const chunks = $input.all();\nconst question = $('Extract Question').first().json.question;\nconst sessionId = $('Extract Question').first().json.sessionId;\n\nif (chunks.length === 0) {\n return [{\n json: {\n question,\n sessionId,\n context: 'No relevant documents found in the knowledge base.',\n hasContext: false\n }\n }];\n}\n\n// Build context from retrieved chunks\nlet context = '';\nconst sources = [];\n\nfor (const chunk of chunks) {\n const data = chunk.json;\n const similarity = (parseFloat(data.similarity) * 100).toFixed(1);\n context += `\\n\\n--- From: ${data.document_name} (Chunk ${data.chunk_index}, Relevance: ${similarity}%) ---\\n${data.content}`;\n \n if (!sources.includes(data.document_name)) {\n sources.push(data.document_name);\n }\n}\n\nreturn [{\n json: {\n question,\n sessionId,\n context: context.trim(),\n sources,\n chunkCount: chunks.length,\n hasContext: true\n }\n}];"
},
"id": "build-context",
"name": "Build Context",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1100,
300
]
},
{
"parameters": {
"method": "POST",
"url": "http://192.168.50.49:11434/api/generate",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n model: 'gpt-oss:120b',\n prompt: `You are a helpful assistant that answers questions based on the provided document context. Respond in the same language as the user's question. If the context doesn't contain relevant information, say so.\n\n=== DOCUMENT CONTEXT ===\n${$json.context}\n=== END CONTEXT ===\n\nUser Question: ${$json.question}\n\nProvide a clear, accurate answer based on the context above:`,\n stream: false\n}) }}",
"options": {
"timeout": 300000
}
},
"id": "ask-ollama",
"name": "Ask Ollama LLM",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1320,
300
]
},
{
"parameters": {
"jsCode": "const ollamaResponse = $input.first().json;\nconst contextData = $('Build Context').first().json;\n\nconst answer = ollamaResponse.response || 'Sorry, I could not generate an answer.';\n\n// Build response object\nconst response = {\n answer,\n question: contextData.question,\n sources: contextData.sources || [],\n chunksUsed: contextData.chunkCount || 0\n};\n\nreturn [{\n json: response\n}];"
},
"id": "format-response",
"name": "Format Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1540,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO chat_memory (session_id, role, content)\nVALUES \n ('{{ $('Extract Question').first().json.sessionId }}', 'user', '{{ $('Extract Question').first().json.question.replace(/'/g, \"''\") }}'),\n ('{{ $('Extract Question').first().json.sessionId }}', 'assistant', '{{ $json.answer.replace(/'/g, \"''\").substring(0, 10000) }}');",
"options": {}
},
"id": "save-to-memory",
"name": "Save to Chat Memory",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1760,
300
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"id": "respond-webhook",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1980,
400
]
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.answer }}"
},
"id": "respond-chat",
"name": "Respond to Chat",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1980,
200
]
}
],
"connections": {
"Chat Trigger": {
"main": [
[
{
"node": "Extract Question",
"type": "main",
"index": 0
}
]
]
},
"Webhook Trigger": {
"main": [
[
{
"node": "Extract Question",
"type": "main",
"index": 0
}
]
]
},
"Extract Question": {
"main": [
[
{
"node": "Generate Query Embedding",
"type": "main",
"index": 0
}
]
]
},
"Generate Query Embedding": {
"main": [
[
{
"node": "Format Query Embedding",
"type": "main",
"index": 0
}
]
]
},
"Format Query Embedding": {
"main": [
[
{
"node": "Vector Similarity Search",
"type": "main",
"index": 0
}
]
]
},
"Vector Similarity Search": {
"main": [
[
{
"node": "Build Context",
"type": "main",
"index": 0
}
]
]
},
"Build Context": {
"main": [
[
{
"node": "Ask Ollama LLM",
"type": "main",
"index": 0
}
]
]
},
"Ask Ollama LLM": {
"main": [
[
{
"node": "Format Response",
"type": "main",
"index": 0
}
]
]
},
"Format Response": {
"main": [
[
{
"node": "Save to Chat Memory",
"type": "main",
"index": 0
}
]
]
},
"Save to Chat Memory": {
"main": [
[
{
"node": "Respond to Chat",
"type": "main",
"index": 0
},
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
{
"name": "RAG"
},
{
"name": "Q&A"
}
],
"triggerCount": 2
}
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.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
RAG Query - Document Q&A. Uses chatTrigger, httpRequest, postgres. Chat trigger; 13 nodes.
Source: https://github.com/mohsp-99/n8n-ollama/blob/0ba7d1fd1a606f212cfdf346f85fb70f905f5696/n8n-workflows/rag-query.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.
This workflow allows users to ask portfolio-related questions in a simple format (). It validates the input, fetches client data and holdings from Google Sheets, retrieves live market prices via API,
This workflow is for: People who want to quickly launch simple landing pages without paying monthly fees to landing page creators. It’s ideal for rapid prototyping, generation of large amounts of land
This n8n template demonstrates how to automatically download an Instagram Reel, analyze its content using AI video understanding, and regenerate a similar video using AI video generation models. The w
Automate the creation of high-performing YouTube Shorts in minutes! Content Creators: Generate engaging short videos effortlessly. Marketing Agencies: Produce client-ready content quickly. Business Ow
I prepared a comprehensive guide demonstrating how to build a multi-level retrieval AI agent in n8n that smartly narrows down search results first by file descriptions, then retrieves detailed vector