AutomationFlowsAI & RAG › Smarter RAG Agents with Enriched Retrieval and Modular Workflows

Smarter RAG Agents with Enriched Retrieval and Modular Workflows

ByAlejandro Scuncia @ascuncia on n8n.io

An extendable RAG template to build powerful, explainable AI assistants — with query understanding, semantic metadata, and support for free-tier tools like Gemini, Gemma and Supabase.

Chat trigger trigger★★★★★ complexityAI-powered32 nodesDocument Default Data LoaderText Splitter Recursive Character Text SplitterSupabaseChat TriggerSupabase Vector StoreGoogle Gemini EmbeddingsForm TriggerGoogle Gemini
AI & RAG Trigger: Chat trigger Nodes: 32 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Chat Trigger 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": "cN01cF1imXdMQfro",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Smarter RAG Agents with Enriched Retrieval, Filters & Memory - by ascuncia",
  "tags": [],
  "nodes": [
    {
      "id": "6052b910-009b-4db5-a956-096758e7cc07",
      "name": "Default Data Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        320,
        704
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "=doc_id",
                "value": "={{ $('Set File Data').item.json.doc_id }}"
              },
              {
                "name": "doc_title",
                "value": "={{ $('Set File Data').item.json.doc_title }}"
              },
              {
                "name": "doc_type",
                "value": "={{ $('Set File Data').item.json.doc_type }}"
              },
              {
                "name": "source",
                "value": "={{ $('Set File Data').item.json.source }}"
              },
              {
                "name": "processed",
                "value": "false"
              }
            ]
          }
        },
        "jsonData": "={{ $('Extract from File').item.json.text }}",
        "jsonMode": "expressionData"
      },
      "typeVersion": 1
    },
    {
      "id": "c777cf9b-4048-4889-98e1-da1c38aaeb50",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        448
      ],
      "parameters": {
        "color": 5,
        "width": 1751,
        "height": 672,
        "content": "## File Ingestion pipeline"
      },
      "typeVersion": 1
    },
    {
      "id": "cb389645-8257-4542-9255-ada92122865f",
      "name": "Recursive Character Text Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "position": [
        400,
        912
      ],
      "parameters": {
        "options": {},
        "chunkSize": 1500,
        "chunkOverlap": 150
      },
      "typeVersion": 1
    },
    {
      "id": "a034b27b-f602-43b4-a588-c284610a0b90",
      "name": "Delete Old Doc Rows",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -256,
        768
      ],
      "parameters": {
        "tableId": "documents",
        "operation": "delete",
        "filterType": "string",
        "filterString": "=metadata->>doc_id=eq.{{ $('On form submission').item.json.File[0].filename }}"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "95aeede8-3cd7-489b-ae0e-f197af68fdde",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -736,
        -32
      ],
      "parameters": {
        "public": true,
        "options": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "2a479838-750b-4a07-b58b-629040fef5f3",
      "name": "Insert into Supabase Vectorstore",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "position": [
        224,
        480
      ],
      "parameters": {
        "mode": "insert",
        "options": {
          "queryName": "match_documents"
        },
        "tableName": {
          "__rl": true,
          "mode": "list",
          "value": "documents",
          "cachedResultName": "documents"
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d3adc752-00a4-4e06-9d8b-93975b6248c2",
      "name": "Embeddings Google Gemini1",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        192,
        704
      ],
      "parameters": {},
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9b02b9e2-1883-4c81-bf70-0d07aafd8c4d",
      "name": "On form submission",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -928,
        672
      ],
      "parameters": {
        "options": {},
        "formTitle": "Archivo",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "File",
              "multipleFiles": false,
              "requiredField": true,
              "acceptFileTypes": ".pdf"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1545a027-fa95-4344-84eb-54da0b454285",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -704,
        672
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "File"
      },
      "typeVersion": 1
    },
    {
      "id": "495722e8-2746-40ec-8793-667ab334bd89",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -512,
        1360
      ],
      "parameters": {
        "options": {},
        "batchSize": 5
      },
      "typeVersion": 3
    },
    {
      "id": "202868eb-6fab-4d39-a3c6-cdc0601abf86",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        1136
      ],
      "parameters": {
        "width": 1751,
        "height": 672,
        "content": "## Enrichment Pipeline"
      },
      "typeVersion": 1
    },
    {
      "id": "177b1443-2e70-40e0-9a5b-2885e0a95f85",
      "name": "Get many rows",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -736,
        1360
      ],
      "parameters": {
        "limit": 150,
        "tableId": "documents",
        "operation": "getAll",
        "filterType": "string",
        "filterString": "metadata->>processed=not.eq.true"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6cb4273c-9f85-46bf-aae6-5d1a7b33321a",
      "name": "Update a row",
      "type": "n8n-nodes-base.supabase",
      "position": [
        320,
        1360
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "=id",
              "keyValue": "={{ $('Edit Fields').item.json.row_id }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "documents",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "metadata",
              "fieldValue": "={{ JSON.stringify(\n  (() => {\n    const prev = $item(0).$node[\"Get many rows\"].json.metadata || {};\n    let enriched = {};\n    try {\n      const raw = $('Metadata Obtention').item.json.content?.parts?.[0]?.text || \"{}\";\n      enriched = JSON.parse(raw);\n    } catch(_) {}\n    return {\n      ...prev,\n      ...enriched,\n      processed: true,\n      enricher_version: \"v2-generic-2025-08\",\n      enriched_at: new Date().toISOString(),\n    };\n  })()\n) }}\n"
            }
          ]
        },
        "matchType": "allFilters",
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8f6c4eef-8e8a-4149-8547-d5154fc68348",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -32,
        688
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 3.2
    },
    {
      "id": "ec4628dc-0b9b-416a-b9d8-bf5999828382",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        512,
        1360
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "05d41aff-ac21-4f17-a807-485a8c6a58ba",
      "name": "Set File Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -480,
        672
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "10646eae-ae46-4327-a4dc-9987c2d76173",
              "name": "doc_id",
              "type": "string",
              "value": "={{ $('On form submission').item.json.File.filename }}"
            },
            {
              "id": "d75d991a-98ce-4070-943d-22e555a074c5",
              "name": "doc_title",
              "type": "string",
              "value": "={{ $json.info.Title || $json.data.filename }}"
            },
            {
              "id": "d7cff574-0e40-4d67-9c88-dacb8e97c45e",
              "name": "doc_type",
              "type": "string",
              "value": "={{ \"guide\" }}"
            },
            {
              "id": "82b5bfb4-f33d-4268-b9d4-8dd3c2f0f097",
              "name": "source",
              "type": "string",
              "value": "={{ \"uploaded_pdf\" }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "74925cc0-31fb-4ff5-9dea-9af6b155ad63",
      "name": "Metadata Obtention",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "onError": "continueErrorOutput",
      "position": [
        -32,
        1360
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemma-3-12b-it",
          "cachedResultName": "models/gemma-3-12b-it"
        },
        "options": {
          "temperature": 0.1,
          "maxOutputTokens": 512
        },
        "messages": {
          "values": [
            {
              "content": "=You are a metadata enricher for arbitrary business documents (guides, manuals, best practices, policies, FAQs, playbooks).\n\nTASK\nAnalyze the provided TEXT CHUNK and return a SINGLE-LINE, MINIFIED JSON object (no backticks, no prose). Use ONLY what is explicitly present or safely implied by the chunk. If a field does not apply, use null or [] accordingly.\n\nINPUTS\n- chunk_text: {{ $json.content }}\n- file metadata (for disambiguation only; DO NOT copy values if not present in the chunk):\n  - doc_title: {{ $json.metadata.doc_title }}\n  - doc_type: {{ $json.metadata.doc_type }}\n\nOUTPUT SCHEMA (exact keys; keep values short and consistent):\n{\n  \"topics\": [\"keyword1\",\"keyword2\"],             // up to 8\n  \"feature\": \"short_tag\",                         // e.g., \"workspaces\", \"databases\", \"onboarding\", \"security\"\n  \"use_case\": \"short_tag\",                        // e.g., \"workspace_structure\", \"communication\", \"setup\", \"policy\"\n  \"audience\": \"beginner|intermediate|advanced|null\",\n  \"section\": \"section_or_heading_if_any\",\n  \"entities\": [\"product|role|team|concept\"],      // proper names or domain terms\n  \"key_recommendations\": [\"bullet1\",\"bullet2\"],   // up to 5 concise, actionable bullets\n  \"risks_or_pitfalls\": [\"risk1\",\"risk2\"],         // up to 5; [] if none\n  \"examples_present\": true,                       // whether the chunk includes an example\n  \"language\": \"auto_detected_iso_639_1\",          // e.g., \"en\", \"es\", \"pt\", \"it\"; infer from chunk_text\n  \"chunk_summary\": \"1-2 sentence neutral summary\" // plain, factual, grounded in the chunk\n}\n\nGUIDELINES\n- Prefer concrete nouns/verbs over vague words.\n- Do not invent entities or recommendations; keep them grounded in the text.\n- If multiple candidates exist, choose the most central one (or null).\n- Deduplicate arrays; keep strings on a single line; no trailing punctuation in bullets.\n\nRETURN ONLY THE MINIFIED JSON, ONE LINE.\n"
            }
          ]
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 5000
    },
    {
      "id": "507d8646-ef0b-442a-a887-496dcec2dbd2",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -960,
        1360
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 25
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a61859c2-872f-4510-a43f-c8490a76b938",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -256,
        1360
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ea722112-aad6-419d-b187-f40044bf3b8a",
              "name": "row_id",
              "type": "number",
              "value": "={{$json.id}}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "63388b99-9d84-41cf-b8c6-94186db049b2",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1040,
        -384
      ],
      "parameters": {
        "color": 4,
        "width": 1744,
        "height": 800,
        "content": "## RAG Chat Agent"
      },
      "typeVersion": 1
    },
    {
      "id": "1689c6a8-ba5f-493b-b31b-718bf7ce2ada",
      "name": "Query Builder",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        -512,
        -32
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemma-3n-e2b-it",
          "cachedResultName": "models/gemma-3n-e2b-it"
        },
        "options": {
          "temperature": 0.1,
          "maxOutputTokens": 128
        },
        "messages": {
          "values": [
            {
              "content": "=You are a query builder for a document retrieval system (RAG). \nYour task: analyze the user\u2019s question and output ONE SINGLE-LINE, MINIFIED JSON. No prose.\n\nOUTPUT SCHEMA (always valid JSON, one line):\n{\n  \"keywords\": [\"k1\",\"k2\",\"k3\"],          // up to 6 short terms from the question\n    \"filters\": {                           // OPTIONAL filters; include only if clearly implied, domain specific - do not include\n  }\n}\n\nRULES\n- Keep keywords concise; avoid stop-words or full sentences.\n- Only add filters if the intent is clear from the question.\n- If the user just greets or provides no query, return {\"keywords\":[]} with no filters.\n\n\nOUTPUT\n- Output ONLY valid JSON, minified, one single line.\n- Do NOT add ```json, code fences, or any text before/after.\n- Do NOT include newlines.\n\nINPUT\n{{ $json.chatInput }}\n\nNOTE\n- Domain-specific logic (e.g., mapping to product features, principles, policies) can be added here in future.\n"
            }
          ]
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dee30e44-bd9b-421f-b9d0-5365ca8813bf",
      "name": "RAG Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -96,
        -240
      ],
      "parameters": {
        "text": "={{ $('When chat message received').item.json.chatInput }}",
        "options": {
          "systemMessage": "=You are a friendly but objective RAG assistant. You may only answer using passages returned by the tool \"Supabase Vector Store\". \nIf in this turn you did not call the tool at least once, reply:\n\"I don't have enough evidence in the indexed documents to answer this.\"\n\nLANGUAGE\n- Always answer in the user's language. Detect automatically from the user message.\n\nRECOVERY QUERY\n- Use the JSON line produced by \"Query Builder\". If it contains multiple keywords or filters, pass them to the retrieval tool.\n- If the query is ambiguous, still retrieve with general keywords and let the reranker select the best contexts.\n\nANSWER RULES\n\"\n- Prefer 2\u20134 distinct passages. If only 1 is available, say evidence is limited. Always form a concrete answer with the passages, not lost sentences.\n- Do NOT import outside knowledge. If relevant passages are weak or missing, say so and suggest a clearer question.\n- If the user's message is just a greeting (e.g., \"hi\"), do NOT call tools. Respond with a short greeting and invite a question.\n\nOUTPUT STYLE\n- Concise, friendly but objective.\n- Add a section below with References, with the format\n  \"Source: <doc_title> \u2014 <section>. Use a title for the section and bullets.\n- No hallucinations; everything must be grounded in retrieved text.\n\nFAILSAFE\n- If retrieval returns authors/topics unrelated to the user's intent, ignore them and state that no relevant evidence was found.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 2
    },
    {
      "id": "14f8779d-043a-4b3b-8f96-ff6a0f5c1026",
      "name": "Reranker",
      "type": "@n8n/n8n-nodes-langchain.rerankerCohere",
      "position": [
        240,
        192
      ],
      "parameters": {
        "topN": 8
      },
      "credentials": {
        "cohereApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "024e08cb-30a6-4a1d-804c-f715b77ab220",
      "name": "Google Gemini 2.0 Flash",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -160,
        -16
      ],
      "parameters": {
        "options": {
          "temperature": 0.2
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "af9307db-b459-4ce2-8dd6-c4207edfdc16",
      "name": "Postgres Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "position": [
        -32,
        -16
      ],
      "parameters": {},
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ddc2ba09-f1bf-40e3-a48a-1cb08962279d",
      "name": "Embeddings Google Gemini",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        112,
        192
      ],
      "parameters": {},
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3fa897aa-3299-4198-9ed1-05808311e6dc",
      "name": "Supabase Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "position": [
        112,
        -16
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "topK": 48,
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "doc_type",
                "value": "={{ (() => { try { return (JSON.parse(($('Query Builder').item.json.content?.parts?.[0]?.text || '{}')).filters?.doc_type) } catch(e){ return undefined } })() }}"
              },
              {
                "name": "feature",
                "value": "={{ (() => { try { return (JSON.parse(($('Query Builder').item.json.content?.parts?.[0]?.text || '{}')).filters?.feature) } catch(e){ return undefined } })() }}"
              },
              {
                "name": "use_case",
                "value": "={{ (() => { try { return (JSON.parse(($('Query Builder').item.json.content?.parts?.[0]?.text || '{}')).filters?.use_case) } catch(e){ return undefined } })() }}"
              },
              {
                "name": "topics",
                "value": "={{ (() => { try { const t = JSON.parse(($('Query Builder').item.json.content?.parts?.[0]?.text || '{}')).filters?.topics; return Array.isArray(t) && t.length ? t : undefined } catch(e){ return undefined } })() }}"
              },
              {
                "name": "entities",
                "value": "={{ (() => { try { const t = JSON.parse(($('Query Builder').item.json.content?.parts?.[0]?.text || '{}')).filters?.entities; return Array.isArray(t) && t.length ? t : undefined } catch(e){ return undefined } })() }}"
              }
            ]
          }
        },
        "tableName": {
          "__rl": true,
          "mode": "list",
          "value": "documents",
          "cachedResultName": "documents"
        },
        "useReranker": true,
        "toolDescription": "=Semantic retrieval tool. Queries the \"documents\" table and returns passages with metadata.\n\nUsage by the agent:\n- Always use the JSON line produced by the Query Builder to form keywords and (optional) metadata filters.\n- If scope is \"multi_document\", allow results from multiple documents; otherwise bias to a single document if clearly implied.\nOutput: fragments with metadata.doc_title, section (if any), feature, use_case, topics, chunk_summary.\n"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "84305b9d-2042-4f61-8d87-0208675d2d58",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1424,
        480
      ],
      "parameters": {
        "width": 272,
        "height": 256,
        "content": "## \ud83d\udfe9 File Ingestion Pipeline\n\nExtracts and chunks uploaded PDFs.\nEmbeds content and stores it in Supabase vector DB.\nClean and modular, ready for other sources (Notion, Drive, etc)."
      },
      "typeVersion": 1
    },
    {
      "id": "2eb9bc0a-08dc-468e-88d4-fafb76dddbbb",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        1152
      ],
      "parameters": {
        "width": 272,
        "height": 256,
        "content": "## \ud83d\udfe8 Enrichment Pipeline (Async)\n\nEnriches chunks with semantic metadata using a lightweight LLM.\nImproves retrieval and enables filters like audience, use_case, risks.\nRuns asynchronously to reduce cost \u2014 ideal for free-tier models."
      },
      "typeVersion": 1
    },
    {
      "id": "a8e76143-cb9f-412e-96c3-e9f5cfbf4e17",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        -384
      ],
      "parameters": {
        "width": 272,
        "height": 288,
        "content": "## \ud83d\udfe6 RAG Agent Pipeline\n\nHandles user questions with memory, filtering, reranking, and references.\nPowered by a Query Builder + Cohere Reranker + Gemini LLM.\nAnswers only with retrieved content \u2014 safe, explainable, production-grade."
      },
      "typeVersion": 1
    },
    {
      "id": "315431cf-6b7f-43e9-ac3a-a3b2bd1280d7",
      "cid": "Ikx1Y2FzIFBleXJpbiI",
      "name": "Sticky Note26",
      "type": "n8n-nodes-base.stickyNote",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "creator": "Lucas Peyrin",
      "position": [
        -2672,
        -48
      ],
      "parameters": {
        "color": 2,
        "width": 832,
        "height": 1568,
        "content": "## \ud83e\udde9 Prepare the Database (SQL)\n\nNow we\u2019ll build the foundation that powers search, enrichment, and retrieval \u2014 think of it as creating the \u201csemantic filing system\u201d for your document library.\n\n### \u2705 **Action Steps:**\n\n- In your Supabase project, open the SQL Editor.\n\n- Locate the sticky note below that includes the full SQL schema.\n\n- Copy the entire SQL code, paste it into the editor, and click \u201cRUN\u201d.\n\n### \u26a0\ufe0f **Heads-Up:**\n\n- If you get an error saying pgvector already exists, simply delete the first line of the code (create extension vector;) and run it again.\n\n- This workflow uses Gemini embeddings with 768 dimensions.\n\u2192 If you're switching to OpenAI embeddings, you must update the schema line:\nChange vector(768) to vector(1536) to match the correct dimensions.\n\n\n### SQL"
      },
      "typeVersion": 1
    },
    {
      "id": "d9415450-8f09-4120-aacb-af623509c0aa",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2640,
        512
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 896,
        "content": "-- \ud83e\uddf9 (Optional) Clean up any previous table for fresh setup\ndrop table documents;\n\n-- \ud83e\udde0 Enable pgvector to store and search vector embeddings\ncreate extension vector;\n\n-- \ud83d\udcda Create the main table to store your document chunks\ncreate table documents (\n  id bigserial primary key,         -- Unique ID for each chunk\n  content text,                     -- The chunk content (pageContent)\n  metadata jsonb,                   -- Metadata like filename, section, topic, etc.\n  embedding vector(768)             -- Gemini embedding vector (768 dims)\n                                    -- \u26a0\ufe0f If using OpenAI: change to vector(1536)\n);\n\n-- \ud83d\udd27 Drop older or conflicting versions of the matching function\ndrop function if exists public.match_documents(jsonb, int, vector);\ndrop function if exists public.match_documents(int, vector, jsonb);\ndrop function if exists public.match_documents(vector, int);\n\n-- \ud83d\udd0d Create a custom function to perform vector search with optional filters\ncreate or replace function public.match_documents (\n  query_embedding vector(768),      -- Input embedding from user query\n  match_count int,                  -- Number of results to return\n  filter jsonb default '{}'::jsonb -- Optional filter using metadata\n)\nreturns table (\n  id bigint,\n  content text,\n  metadata jsonb,\n  embedding vector(768),\n  similarity float                  -- Cosine similarity score\n)\nlanguage sql stable\nas $$\n  select\n    id,\n    content,\n    metadata,\n    embedding,\n    1 - (embedding <#> query_embedding) as similarity  -- Cosine similarity = 1 - distance\n  from documents\n  where (filter is null or filter = '{}'::jsonb or metadata @> filter)\n  order by embedding <#> query_embedding                -- Order by closest match\n  limit match_count;\n$$;\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "5e6d500c-a7c9-4618-8eb1-9687146b726f",
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Insert into Supabase Vectorstore",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reranker": {
      "ai_reranker": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_reranker",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Metadata Obtention",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update a row": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many rows": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Builder": {
      "main": [
        [
          {
            "node": "RAG Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set File Data": {
      "main": [
        [
          {
            "node": "Delete Old Doc Rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get many rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Set File Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Metadata Obtention": {
      "main": [
        [
          {
            "node": "Update a row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Default Data Loader": {
      "ai_document": [
        [
          {
            "node": "Insert into Supabase Vectorstore",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Delete Old Doc Rows": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Postgres Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "RAG Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Supabase Vector Store": {
      "ai_tool": [
        [
          {
            "node": "RAG Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini 2.0 Flash": {
      "ai_languageModel": [
        [
          {
            "node": "RAG Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Google Gemini": {
      "ai_embedding": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Google Gemini1": {
      "ai_embedding": [
        [
          {
            "node": "Insert into Supabase Vectorstore",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "node": "Query Builder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert into Supabase Vectorstore": {
      "main": [
        []
      ]
    },
    "Recursive Character Text Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Default Data Loader",
            "type": "ai_textSplitter",
            "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.

Pro

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

About this workflow

An extendable RAG template to build powerful, explainable AI assistants — with query understanding, semantic metadata, and support for free-tier tools like Gemini, Gemma and Supabase.

Source: https://n8n.io/workflows/8008/ — 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

• Create a Google Drive folder to watch. • Connect your Google Drive account in n8n and authorize access. • Point the Google Drive Trigger node to this folder (new/modified files trigger the flow).

Agent, Chat Trigger, Memory Buffer Window +14
AI & RAG

⚡AI-Powered YouTube Playlist & Video Summarization and Analysis v2. Uses lmChatGoogleGemini, agent, splitOut, chainLlm. Chat trigger; 72 nodes.

Google Gemini Chat, Agent, Chain Llm +11
AI & RAG

This n8n workflow transforms entire YouTube playlists or single videos into interactive knowledge bases you can chat with. Ask questions and get summaries without needing to watch hours of content. 🔗

Google Gemini Chat, Agent, Chain Llm +11
AI & RAG

use cases: research stock market in Indonesia. analyze the performance of companies belonging to certain subsectors or company comparing financial metrics between BBCA and BBRI providing technical ana

Chat Trigger, Chat, Telegram Trigger +10
AI & RAG

The workflow operates through a three-step process that handles incoming chat messages with intelligent tool orchestration: Message Trigger: The node triggers whenever a user message arrives and passe

Chat Trigger, Memory Postgres Chat, OpenAI Embeddings +16