AutomationFlowsAI & RAG › Answer Hr Questions with Gpt-4o-mini, Postgresql, Pinecone and Elevenlabs

Answer Hr Questions with Gpt-4o-mini, Postgresql, Pinecone and Elevenlabs

ByCybernative Technologies @cybernative on n8n.io

This template provides a fully functional, enterprise-grade HR chatbot that employees can interact with using plain language in text or voice in any of the 10 supported Indian languages.

Webhook trigger★★★★★ complexityAI-powered33 nodes@Elevenlabs/N8N Nodes ElevenlabsOpenAI ChatChain LlmPostgresAgentPostgres ToolPinecone Vector StoreOpenAI Embeddings
AI & RAG Trigger: Webhook Nodes: 33 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Chainllm 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "4d42cfd3-3cab-4a24-9ee1-5a067a9268dd",
      "name": "\ud83d\udccc Setup & Configuration Guide",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        -240
      ],
      "parameters": {
        "color": 2,
        "width": 500,
        "height": 960,
        "content": "## \ud83d\ude80 MULTILINGUAL AI HR ASSISTANT SETUP GUIDE\n\n### Prerequisites\nBefore activating this workflow, configure the following credentials in n8n:\n\n**1. OpenAI API** (used in 3 places)\n- AI Agent (GPT-4o-mini, temperature 0.1)\n- Translation model (temperature 0.3)\n- Text Embeddings (text-embedding-3-small, 1536 dims)\n\n**2. PostgreSQL: HR Database**\n- Stores: employees, leave_requests, leave_types,\n  employee_leave_balances, departments, companies\n- Schema: see the Setup Guide in the template description\n\n**3. PostgreSQL: Chat Memory DB**\n- Can be the same DB or a separate one\n- n8n auto-creates the `n8n_chat_histories` table\n\n**4. Pinecone Vector DB**\n- Create an index with **1536 dimensions**\n- Upload HR policy documents with metadata:\n  `{ policy_namespace: \"company_slug\" }`\n- Set matching namespace in your `companies` table\n\n**5. ElevenLabs API**\n- Required only if you enable voice/audio input\n- Can be skipped for text-only deployments\n\n### Test the Webhook\n```\nPOST /webhook/hr-assistant\n{\n  \"mobile_number\": \"9876543210\",\n  \"message\": \"What is my leave balance?\",\n  \"session_id\": \"session_001\"\n}\n```\n\n### Supported Languages\nEnglish \u00b7 Hindi \u00b7 Gujarati \u00b7 Marathi \u00b7 Tamil\nTelugu \u00b7 Kannada \u00b7 Malayalam \u00b7 Bengali \u00b7 Punjabi"
      },
      "typeVersion": 1
    },
    {
      "id": "a1fa6096-ef02-4e0e-85b2-0f465662990c",
      "name": "Section 1 - Input Layer",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -832,
        400
      ],
      "parameters": {
        "color": 3,
        "width": 640,
        "height": 420,
        "content": "## \ud83d\udce5 SECTION 1 - INPUT & IDENTITY VALIDATION\nWebhook receives POST request. Validates that mobile number OR email is present before proceeding. Returns HTTP 400 if missing."
      },
      "typeVersion": 1
    },
    {
      "id": "1e298eba-17e2-4a48-9e60-406ebb24d95c",
      "name": "Section 2 - Voice Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -176,
        160
      ],
      "parameters": {
        "color": 4,
        "width": 544,
        "height": 456,
        "content": "## \ud83d\udd0a SECTION 2 - VOICE PROCESSING\nIf `is_audio: true`, the audio file is sent to ElevenLabs for Speech-to-Text transcription. The transcript then flows into the same text pipeline.\n\n\ud83d\udca1 Remove these 3 nodes if you only need text input."
      },
      "typeVersion": 1
    },
    {
      "id": "aa999708-582c-40bd-85fd-4520ca585699",
      "name": "Section 3 - Language Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        48
      ],
      "parameters": {
        "color": 5,
        "width": 1104,
        "height": 712,
        "content": "## \ud83c\udf10 SECTION 3 - LANGUAGE DETECTION & TRANSLATION\nDetects language via Unicode script ranges (no API call needed). If non-English, translates to English before the AI Agent processes it. The original language is preserved and the final response is returned in the user's detected language."
      },
      "typeVersion": 1
    },
    {
      "id": "af178997-fe49-46ef-90d4-ed3a41a241dd",
      "name": "Section 4 - Employee Auth",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        128
      ],
      "parameters": {
        "color": 3,
        "width": 664,
        "height": 632,
        "content": "## \ud83d\udc64 SECTION 4 \u2014 EMPLOYEE AUTHENTICATION\nLooks up the employee by mobile number OR email address. Fetches the `policy_namespace` from the `companies` table via JOIN \u2014 this is used to filter Pinecone vector search to this company's HR policies only.\n\n\ud83d\udd12 Returns HTTP 404 if employee not found."
      },
      "typeVersion": 1
    },
    {
      "id": "301dc46d-07ee-40d7-a0fe-6765c5c6c228",
      "name": "Section 5 - AI Agent",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2336,
        96
      ],
      "parameters": {
        "width": 848,
        "height": 802,
        "content": "## \ud83e\udd16 SECTION 5 \u2014 AI AGENT (CORE)\nGPT-4o-mini agent with access to:\n\u2022 PostgreSQL Tool \u2014 queries live HR data (leave balances, history, manager, probation)\n\u2022 Pinecone Knowledge Base \u2014 retrieves company-specific HR policies (namespaced per company)\n\u2022 PostgreSQL Memory \u2014 persists conversation history per session\n\n\ud83d\udd12 Agent is restricted to SELECT queries and current employee_id only."
      },
      "typeVersion": 1
    },
    {
      "id": "fc3ec889-fbbd-48fc-868b-51183d73bc8a",
      "name": "Section 6 - Response",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3248,
        32
      ],
      "parameters": {
        "color": 2,
        "width": 504,
        "height": 424,
        "content": "## \ud83d\udce4 SECTION 6 \u2014 RESPONSE FORMATTING\nEnriches the AI response with:\n\u2022 Contextual quick action buttons (apply leave, view payslip, etc.)\n\u2022 Related questions in the user's language\n\u2022 Full metadata (session ID, processing time, language detected)\n\u2022 CORS headers for browser clients"
      },
      "typeVersion": 1
    },
    {
      "id": "2f3b5601-66e0-4914-956f-b6cf70272d56",
      "name": "Webhook - HR Bot Chat",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -768,
        544
      ],
      "parameters": {
        "path": "hr-assistant",
        "options": {
          "rawBody": false,
          "allowedOrigins": "*"
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "fddb2fca-f9c9-4a2d-b36b-69dc5e6150b1",
      "name": "Validate Identity (Mobile/Email)",
      "type": "n8n-nodes-base.if",
      "position": [
        -576,
        544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "check-mobile",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.body.mobile_number }}",
              "rightValue": ""
            },
            {
              "id": "check-email",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.email_id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "dada5f1b-be65-41a0-a512-8ffeb853c0aa",
      "name": "Error - Invalid Mobile/Email",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -368,
        640
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"error\": \"Invalid request. Please provide a mobile number or email address.\",\n  \"timestamp\": \"{{ new Date().toISOString() }}\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "7b431c5a-dd7c-4309-87f6-223d1ec1c123",
      "name": "Check Audio Input",
      "type": "n8n-nodes-base.if",
      "position": [
        -144,
        448
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "check-audio",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.body.is_audio }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "75da670b-362f-4017-98f7-5fe7c9ae0875",
      "name": "ElevenLabs - Transcribe Audio",
      "type": "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs",
      "position": [
        32,
        336
      ],
      "parameters": {
        "file": "audio_file",
        "resource": "speech",
        "operation": "speechToText",
        "requestOptions": {},
        "additionalOptions": {}
      },
      "credentials": {
        "elevenLabsApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "22d44c62-a37e-4739-9956-28a65cce9d53",
      "name": "Extract Transcript",
      "type": "n8n-nodes-base.set",
      "position": [
        224,
        336
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "extract-text",
              "name": "message",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "041e5b1a-bcd6-46c5-a883-1991b0571c56",
      "name": "Validate Message",
      "type": "n8n-nodes-base.if",
      "position": [
        448,
        464
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "validate-body-message",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.message }}",
              "rightValue": ""
            },
            {
              "id": "validate-direct-message",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.message }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "fd3dcb95-ca92-4502-8ef8-8ccb4fd30add",
      "name": "Error - Invalid Message",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        672,
        592
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"error\": \"Invalid message. Please provide a text or voice message.\",\n  \"timestamp\": \"{{ new Date().toISOString() }}\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "3434d8a8-f7c6-40b7-91ce-3f950c622e2e",
      "name": "Parse Input & Detect Language",
      "type": "n8n-nodes-base.code",
      "position": [
        656,
        304
      ],
      "parameters": {
        "jsCode": "// Parse & sanitize input, detect language via Unicode script ranges\nconst body = $('Webhook - HR Bot Chat').first().json.body;\nconst rawMessage = $input.first().json.message || body.message || '';\n\n// Sanitize inputs to prevent SQL injection and abuse\nconst sanitizedMobile = (body.mobile_number || '').replace(/[^0-9+\\-\\s]/g, '').trim().substring(0, 20);\nconst sanitizedEmail  = (body.email_id || '').trim().toLowerCase().substring(0, 100);\nconst message         = rawMessage.trim().substring(0, 2000); // limit message length\n\nfunction detectLanguage(text) {\n  if (/[\\u0900-\\u097F]/.test(text)) {\n    // Devanagari \u2014 distinguish Marathi from Hindi by common Marathi-only characters\n    if (/[\\u0902\\u0948\\u094D][\\u0915-\\u0939]/.test(text) && /\u0906\u0939\u0947|\u0906\u0939\u0947\u0924|\u092e\u0932\u093e|\u0906\u092a\u0923/.test(text)) return 'mr';\n    return 'hi';\n  }\n  if (/[\\u0A80-\\u0AFF]/.test(text)) return 'gu';\n  if (/[\\u0B80-\\u0BFF]/.test(text)) return 'ta';\n  if (/[\\u0C00-\\u0C7F]/.test(text)) return 'te';\n  if (/[\\u0C80-\\u0CFF]/.test(text)) return 'kn';\n  if (/[\\u0D00-\\u0D7F]/.test(text)) return 'ml';\n  if (/[\\u0980-\\u09FF]/.test(text)) return 'bn';\n  if (/[\\u0A00-\\u0A7F]/.test(text)) return 'pa';\n  return 'en';\n}\n\nconst userLanguage     = body.language || 'auto';\nconst detectedLanguage = userLanguage === 'auto' ? detectLanguage(message) : userLanguage;\n\nconst languageConfig = {\n  'en': { name: 'English',   needsTranslation: false },\n  'hi': { name: 'Hindi',     needsTranslation: true  },\n  'gu': { name: 'Gujarati',  needsTranslation: true  },\n  'mr': { name: 'Marathi',   needsTranslation: true  },\n  'ta': { name: 'Tamil',     needsTranslation: true  },\n  'te': { name: 'Telugu',    needsTranslation: true  },\n  'kn': { name: 'Kannada',   needsTranslation: true  },\n  'ml': { name: 'Malayalam', needsTranslation: true  },\n  'bn': { name: 'Bengali',   needsTranslation: true  },\n  'pa': { name: 'Punjabi',   needsTranslation: true  }\n};\n\nconst langConfig  = languageConfig[detectedLanguage] || languageConfig['en'];\nconst sessionId   = body.session_id || `session_${sanitizedMobile || sanitizedEmail}_${Date.now()}`;\n\nreturn {\n  json: {\n    userMessage:       message,\n    originalMessage:   message,\n    detectedLanguage,\n    languageName:      langConfig.name,\n    needsTranslation:  langConfig.needsTranslation,\n    mobileNumber:      sanitizedMobile,\n    emailId:           sanitizedEmail,\n    sessionId,\n    messageType:       body.message_type || 'text',\n    isAudio:           body.is_audio === 'true' || body.is_audio === true,\n    timestamp:         new Date().toISOString(),\n    requestId:         `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6080eaa0-3a1a-4cc8-950f-bf64661c7421",
      "name": "Needs Translation?",
      "type": "n8n-nodes-base.if",
      "position": [
        832,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-translation",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.needsTranslation }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1a43a253-c7bc-496b-8ac5-f28b57556836",
      "name": "OpenAI Model - Translation",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1088,
        400
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {
          "temperature": 0.3
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.3
    },
    {
      "id": "4cd19b75-db45-4e41-aea7-154d0446af64",
      "name": "Translate to English",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1024,
        208
      ],
      "parameters": {
        "text": "=Translate the following {{ $json.languageName }} text to English. Return ONLY the translated text, nothing else.\n\nText: {{ $json.userMessage }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "f6abd450-4144-4164-8f18-cf22a35a3952",
      "name": "Skip Translation (English)",
      "type": "n8n-nodes-base.set",
      "position": [
        1120,
        544
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "no-translation",
              "name": "translatedMessage",
              "type": "string",
              "value": "={{ $json.userMessage }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "51d1ebb2-0960-449a-8895-fed50804be7d",
      "name": "Merge Translation Context",
      "type": "n8n-nodes-base.set",
      "position": [
        1328,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "add-translated",
              "name": "translatedMessage",
              "type": "string",
              "value": "={{ $json.text }}"
            },
            {
              "id": "keep-original",
              "name": "userMessage",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.userMessage }}"
            },
            {
              "id": "keep-language",
              "name": "detectedLanguage",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.detectedLanguage }}"
            },
            {
              "id": "keep-mobile",
              "name": "mobileNumber",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.mobileNumber }}"
            },
            {
              "id": "keep-session",
              "name": "sessionId",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.sessionId }}"
            },
            {
              "id": "keep-timestamp",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.timestamp }}"
            },
            {
              "id": "keep-requestid",
              "name": "requestId",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.requestId }}"
            },
            {
              "id": "keep-email",
              "name": "emailId",
              "type": "string",
              "value": "={{ $('Parse Input & Detect Language').first().json.emailId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "13a4bdad-0684-42e4-aaa6-5c1da54f4c0f",
      "name": "Fetch Employee Data",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1616,
        400
      ],
      "parameters": {
        "query": "SELECT\n  e.*,\n  c.policy_namespace\nFROM employees e\nJOIN companies c ON e.company_id = c.id\nWHERE\n  (e.mobile_phone = $1 OR e.email = $2)\nLIMIT 1;",
        "options": {
          "queryReplacement": "={{ [$json.mobileNumber, $json.emailId] }}"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.6,
      "alwaysOutputData": true
    },
    {
      "id": "cc7a4ca7-7f38-4e55-be8a-4a893944c680",
      "name": "Validate Employee",
      "type": "n8n-nodes-base.if",
      "position": [
        1776,
        352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-employee",
              "operator": {
                "type": "number",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e2063593-8f5b-4028-be9f-a5216c41cbbb",
      "name": "Error - Employee Not Found",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1792,
        592
      ],
      "parameters": {
        "options": {
          "responseCode": 404
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": false,\n  \"error\": \"Employee not found. Please check your mobile number or email address.\",\n  \"timestamp\": \"{{ new Date().toISOString() }}\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "48b30609-61dc-47f5-9b89-2e46458f286b",
      "name": "Merge Employee & Language Context",
      "type": "n8n-nodes-base.code",
      "position": [
        2016,
        288
      ],
      "parameters": {
        "jsCode": "// Merge employee DB record with language/session context\nlet languageContext = {};\ntry { languageContext = $('Merge Translation Context').first().json; } catch (e) {}\nif (!languageContext || Object.keys(languageContext).length === 0) {\n  try { languageContext = $('Skip Translation (English)').first().json; } catch (e) {}\n}\n\nconst row = $('Fetch Employee Data').first().json;\n\nreturn {\n  json: {\n    // Employee profile\n    employeeId:         row.id,\n    employeeNumber:     row.employee_number,\n    displayName:        row.display_name,\n    email:              row.email,\n    mobilePhone:        row.mobile_phone,\n    dateJoined:         row.date_joined,\n    employmentStatus:   row.employment_status,\n    departmentId:       row.department_id,\n    locationId:         row.location_id,\n    jobTitleId:         row.job_title_id,\n    reportingManagerId: row.reporting_manager_id,\n    companyId:          row.company_id,\n    inProbation:        row.in_probation,\n    probationEndDate:   row.probation_end_date,\n    // Pinecone namespace (from companies JOIN) \u2014 filters policies to this company\n    policyNamespace:    row.policy_namespace || '',\n    // Language & session context\n    userMessage:        languageContext.userMessage,\n    translatedMessage:  languageContext.translatedMessage,\n    detectedLanguage:   languageContext.detectedLanguage,\n    sessionId:          languageContext.sessionId,\n    timestamp:          languageContext.timestamp,\n    requestId:          languageContext.requestId\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "27f39cbc-396c-448e-9664-e8c301c915ea",
      "name": "HR Assistant AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2544,
        272
      ],
      "parameters": {
        "text": "={{ $json.userMessage }}",
        "options": {
          "systemMessage": "=# HR Assistant \u2014 PolicyPal\n## Multi-Language, Database-Driven Employee Support\n\nYou are **PolicyPal**, an intelligent HR virtual assistant with direct access to the company's PostgreSQL HR database and policy knowledge base.\n\n---\n\n## EMPLOYEE CONTEXT (Current Session)\n\n- **Employee ID:** {{ $('Merge Employee & Language Context').first().json.employeeId }}\n- **Name:** {{ $('Merge Employee & Language Context').first().json.displayName }}\n- **Email:** {{ $('Merge Employee & Language Context').first().json.email }}\n- **Employee Number:** {{ $('Merge Employee & Language Context').first().json.employeeNumber }}\n- **Date Joined:** {{ $('Merge Employee & Language Context').first().json.dateJoined }}\n- **Employment Status:** {{ $('Merge Employee & Language Context').first().json.employmentStatus === 0 ? 'Active' : 'Inactive' }}\n- **In Probation:** {{ $('Merge Employee & Language Context').first().json.inProbation ? 'Yes' : 'No' }}\n- **Probation End Date:** {{ $('Merge Employee & Language Context').first().json.probationEndDate || 'N/A' }}\n- **Company ID:** {{ $('Merge Employee & Language Context').first().json.companyId }}\n- **Policy Namespace:** {{ $('Merge Employee & Language Context').first().json.policyNamespace }}\n\n---\n\n## PROBATION RULE\n\nIf **In Probation = Yes**, treat this employee as a **New Joiner** and apply new-joiner policy rules.\nIf **In Probation = No**, treat as a **Regular Employee**.\nNever apply new-joiner rules to regular employees, or vice versa.\n\n---\n\n## LANGUAGE CONTEXT\n\n- **Detected Language:** {{ $('Merge Employee & Language Context').first().json.detectedLanguage }}\n- **Original Query:** {{ $('Merge Employee & Language Context').first().json.userMessage }}\n- **English Translation:** {{ $('Merge Employee & Language Context').first().json.translatedMessage }}\n\n**CRITICAL:** You MUST respond in **{{ $('Merge Employee & Language Context').first().json.detectedLanguage }}** language only.\n\n---\n\n## TOOLS AVAILABLE\n\n1. **PostgreSQL Tool** \u2014 Query live HR data for this employee\n2. **Policy Knowledge Base** \u2014 Search company HR policies (pre-filtered to this company's namespace: `{{ $('Merge Employee & Language Context').first().json.policyNamespace }}`)\n3. **Conversation Memory** \u2014 Maintain context across follow-up questions\n\n---\n\n## DECISION FRAMEWORK\n\n**Use Database for:** Leave balances, leave history, attendance, reporting manager, department, probation status, personal employee details\n\n**Use Policy Knowledge Base for:** Leave rules, HR procedures, company guidelines, how-to instructions, entitlements by policy\n\n**Use Both for:** Queries needing personal data + policy context (e.g., \"Can I take leave?\" = check balance in DB + explain policy from vector store)\n\n---\n\n## SAMPLE SQL QUERIES\n\n**Leave Balance:**\n```sql\nSELECT lt.name AS leave_type, elb.annual_quota, elb.available_balance, elb.consumed_balance\nFROM employee_leave_balances elb\nJOIN leave_types lt ON elb.leave_type_id = lt.id\nWHERE elb.employee_id = {{ $('Merge Employee & Language Context').first().json.employeeId }};\n```\n\n**Leave History (last 5):**\n```sql\nSELECT lt.name AS leave_type, lr.from_date, lr.to_date, lr.total_leave_days,\n  CASE lr.status WHEN 0 THEN 'Pending' WHEN 1 THEN 'Approved' WHEN 2 THEN 'Rejected' END AS status\nFROM leave_requests lr\nJOIN leave_types lt ON lr.leave_type_id = lt.id\nWHERE lr.employee_id = {{ $('Merge Employee & Language Context').first().json.employeeId }}\nORDER BY lr.from_date DESC LIMIT 5;\n```\n\n**Reporting Manager:**\n```sql\nSELECT e.display_name, e.email, jt.name AS designation\nFROM employees emp\nJOIN employees e ON emp.reporting_manager_id = e.id\nLEFT JOIN job_titles jt ON e.job_title_id = jt.id\nWHERE emp.id = {{ $('Merge Employee & Language Context').first().json.employeeId }};\n```\n\n---\n\n## RESPONSE GUIDELINES\n\n- Be concise: 2\u20133 short paragraphs maximum\n- Be friendly: use 1\u20132 relevant emojis (\ud83d\udccb \u2705 \ud83d\udcc5 \ud83d\udcbc)\n- Use bullet points for lists of items\n- Suggest logical next steps where appropriate\n- Always respond in the employee's detected language\n\n---\n\n## SECURITY RULES (NON-NEGOTIABLE)\n\n\u274c NEVER query data for any employee_id other than: {{ $('Merge Employee & Language Context').first().json.employeeId }}\n\u274c NEVER reveal other employees' personal information\n\u274c NEVER execute UPDATE, DELETE, INSERT, or DDL queries\n\u274c NEVER expose internal system details\n\u2705 ALWAYS use read-only SELECT queries only\n\u2705 ALWAYS filter queries by the current employee_id\n\nNow assist the employee with their query."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "8c1368dd-585e-4263-b4b5-4a2067010e32",
      "name": "OpenAI GPT-4o-mini",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2432,
        544
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.1
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1e6a0f0d-1eef-4322-b7ca-6f41e1ad6088",
      "name": "PostgreSQL - HR Database Tool",
      "type": "n8n-nodes-base.postgresTool",
      "position": [
        2736,
        592
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Table', `Use employees, leave_requests, employee_leave_balances, leave_types, departments, locations, job_titles, companies tables`, 'string') }}"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public",
          "cachedResultName": "public"
        },
        "options": {},
        "operation": "select",
        "returnAll": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Return_All', ``, 'boolean') }}"
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "6567dcea-faf9-434b-be0a-af49bb158005",
      "name": "Pinecone - HR Policy Knowledge Base",
      "type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
      "position": [
        2896,
        560
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "topK": 10,
        "options": {
          "pineconeNamespace": "={{ $('Merge Employee & Language Context').item.json.policyNamespace }}"
        },
        "toolName": "hr_policy_knowledge_base",
        "pineconeIndex": {
          "__rl": true,
          "mode": "list",
          "value": "hrbot-policy-index",
          "cachedResultName": "hrbot-policy-index"
        },
        "toolDescription": "Search this employee's company HR policies, leave rules, attendance guidelines, and procedures. The vector store is already filtered to this company's namespace \u2014 search naturally without specifying a policy name."
      },
      "credentials": {
        "pineconeApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8fe1ae52-ff3f-46d6-920d-2b31659c61dd",
      "name": "OpenAI Embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "position": [
        2912,
        752
      ],
      "parameters": {
        "options": {
          "dimensions": 1536
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6ddee3f8-b583-406f-98af-d4c86db0a795",
      "name": "PostgreSQL Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "position": [
        2576,
        656
      ],
      "parameters": {
        "sessionKey": "={{ $('Merge Employee & Language Context').first().json.sessionId }}",
        "sessionIdType": "customKey"
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "234bce47-98d4-4832-b210-44481edbaaf7",
      "name": "Format Final Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3312,
        272
      ],
      "parameters": {
        "jsCode": "const finalResponse   = $input.first().json.output;\nconst ctx             = $('Merge Employee & Language Context').first().json;\n\n// Contextual quick actions based on response content\nconst quickActions = [];\nconst lower = finalResponse.toLowerCase();\n\nif (lower.includes('leave balance') || lower.includes('available')) {\n  quickActions.push(\n    { label: 'Apply for Leave',    action: 'apply_leave'        },\n    { label: 'View Leave History', action: 'view_leave_history' }\n  );\n}\nif (lower.includes('salary') || lower.includes('payslip') || lower.includes('ctc')) {\n  quickActions.push(\n    { label: 'Download Payslip',      action: 'download_payslip'    },\n    { label: 'View Salary Structure', action: 'view_salary_structure' }\n  );\n}\nif (lower.includes('manager') || lower.includes('reporting')) {\n  quickActions.push(\n    { label: 'Contact Manager', action: 'contact_manager' }\n  );\n}\n\n// Suggested follow-up questions in user's language\nconst relatedQuestions = {\n  'en': ['What is my leave balance?', 'Show my recent leave history', 'Who is my reporting manager?', 'What is the company leave policy?'],\n  'hi': ['\u092e\u0947\u0930\u0940 \u091b\u0941\u091f\u094d\u091f\u0940 \u0936\u0947\u0937 \u0930\u093e\u0936\u093f \u0915\u094d\u092f\u093e \u0939\u0948?', '\u092e\u0947\u0930\u093e \u0939\u093e\u0932 \u0915\u093e \u091b\u0941\u091f\u094d\u091f\u0940 \u0907\u0924\u093f\u0939\u093e\u0938 \u0926\u093f\u0916\u093e\u090f\u0902', '\u092e\u0947\u0930\u093e \u0930\u093f\u092a\u094b\u0930\u094d\u091f\u093f\u0902\u0917 \u092e\u0948\u0928\u0947\u091c\u0930 \u0915\u094c\u0928 \u0939\u0948?', '\u0915\u0902\u092a\u0928\u0940 \u0915\u0940 \u091b\u0941\u091f\u094d\u091f\u0940 \u0928\u0940\u0924\u093f \u0915\u094d\u092f\u093e \u0939\u0948?'],\n  'gu': ['\u0aae\u0abe\u0ab0\u0ac0 \u0ab0\u0a9c\u0abe \u0aac\u0abe\u0a95\u0ac0 \u0a95\u0ac7\u0a9f\u0ab2\u0ac0 \u0a9b\u0ac7?', '\u0aae\u0abe\u0ab0\u0acb \u0aa4\u0abe\u0a9c\u0ac7\u0aa4\u0ab0\u0aa8\u0acb \u0ab0\u0a9c\u0abe \u0a87\u0aa4\u0abf\u0ab9\u0abe\u0ab8 \u0aac\u0aa4\u0abe\u0ab5\u0acb', '\u0aae\u0abe\u0ab0\u0acb \u0ab0\u0abf\u0aaa\u0acb\u0ab0\u0acd\u0a9f\u0abf\u0a82\u0a97 \u0aae\u0ac7\u0aa8\u0ac7\u0a9c\u0ab0 \u0a95\u0acb\u0aa3 \u0a9b\u0ac7?', '\u0a95\u0a82\u0aaa\u0aa8\u0ac0\u0aa8\u0ac0 \u0ab0\u0a9c\u0abe \u0aa8\u0ac0\u0aa4\u0abf \u0ab6\u0ac1\u0a82 \u0a9b\u0ac7?'],\n  'mr': ['\u092e\u093e\u091d\u0940 \u0930\u091c\u093e \u0936\u093f\u0932\u094d\u0932\u0915 \u0915\u093f\u0924\u0940 \u0906\u0939\u0947?', '\u092e\u093e\u091d\u093e \u0905\u0932\u0940\u0915\u0921\u0940\u0932 \u0930\u091c\u093e \u0907\u0924\u093f\u0939\u093e\u0938 \u0926\u093e\u0916\u0935\u093e', '\u092e\u093e\u091d\u093e \u0930\u093f\u092a\u094b\u0930\u094d\u091f\u093f\u0902\u0917 \u092e\u0945\u0928\u0947\u091c\u0930 \u0915\u094b\u0923 \u0906\u0939\u0947?', '\u0915\u0902\u092a\u0928\u0940\u091a\u0947 \u0930\u091c\u093e \u0927\u094b\u0930\u0923 \u0915\u093e\u092f \u0906\u0939\u0947?']\n};\n\nconst lang             = ctx.detectedLanguage || 'en';\nconst suggestedQns     = relatedQuestions[lang] || relatedQuestions['en'];\nconst processingTimeMs = Date.now() - new Date(ctx.timestamp).getTime();\n\nreturn {\n  json: {\n    success: true,\n    data: {\n      message:           finalResponse,\n      original_message:  ctx.userMessage,\n      quick_actions:     quickActions,\n      related_questions: suggestedQns,\n      metadata: {\n        employee_id:        ctx.employeeId,\n        employee_name:      ctx.displayName,\n        company_id:         ctx.companyId,\n        policy_namespace:   ctx.policyNamespace,\n        detected_language:  ctx.detectedLanguage,\n        was_translated:     ctx.detectedLanguage !== 'en',\n        session_id:         ctx.sessionId,\n        request_id:         ctx.requestId,\n        timestamp:          new Date().toISOString(),\n        processing_time_ms: processingTimeMs\n      }\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "52da64f4-c72a-4250-99c6-f50c5193ca66",
      "name": "Send Success Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3520,
        272
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ $json }}"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Validate Message": {
      "main": [
        [
          {
            "node": "Parse Input & Detect Language",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error - Invalid Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Audio Input": {
      "main": [
        [
          {
            "node": "ElevenLabs - Transcribe Audio",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validate Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Embeddings": {
      "ai_embedding": [
        [
          {
            "node": "Pinecone - HR Policy Knowledge Base",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Validate Employee": {
      "main": [
        [
          {
            "node": "Merge Employee & Language Context",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error - Employee Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Transcript": {
      "main": [
        [
          {
            "node": "Validate Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Translation?": {
      "main": [
        [
          {
            "node": "Translate to English",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Translation (English)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT-4o-mini": {
      "ai_languageModel": [
        [
          {
            "node": "HR Assistant AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Employee Data": {
      "main": [
        [
          {
            "node": "Validate Employee",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Translate to English": {
      "main": [
        [
          {
            "node": "Merge Translation Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Final Response": {
      "main": [
        [
          {
            "node": "Send Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HR Assistant AI Agent": {
      "main": [
        [
          {
            "node": "Format Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - HR Bot Chat": {
      "main": [
        [
          {
            "node": "Validate Identity (Mobile/Email)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "HR Assistant AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Merge Translation Context": {
      "main": [
        [
          {
            "node": "Fetch Employee Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Model - Translation": {
      "ai_languageModel": [
        [
          {
            "node": "Translate to English",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Skip Translation (English)": {
      "main": [
        [
          {
            "node": "Fetch Employee Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ElevenLabs - Transcribe Audio": {
      "main": [
        [
          {
            "node": "Extract Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Input & Detect Language": {
      "main": [
        [
          {
            "node": "Needs Translation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL - HR Database Tool": {
      "ai_tool": [
        [
          {
            "node": "HR Assistant AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Validate Identity (Mobile/Email)": {
      "main": [
        [
          {
            "node": "Check Audio Input",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error - Invalid Mobile/Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Employee & Language Context": {
      "main": [
        [
          {
            "node": "HR Assistant AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pinecone - HR Policy Knowledge Base": {
      "ai_tool": [
        [
          {
            "node": "HR Assistant AI Agent",
            "type": "ai_tool",
            "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

This template provides a fully functional, enterprise-grade HR chatbot that employees can interact with using plain language in text or voice in any of the 10 supported Indian languages.

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

Hi! I’m Amanda, a creator of intelligent automations using n8n and Make. I’ve been building AI-powered workflows for over 2 years, always focused on usability and innovation. This one here is very spe

OpenAI Chat, Redis, OpenAI +11
AI & RAG

HeyDinastia. Uses executeCommand, httpRequest, youTube, postgres. Webhook trigger; 66 nodes.

Execute Command, HTTP Request, YouTube +15
AI & RAG

RAG AI Agent Template V5. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 56 nodes.

OpenAI Chat, Document Default Data Loader, OpenAI Embeddings +12
AI & RAG

My workflow 2529. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 54 nodes.

OpenAI Chat, Document Default Data Loader, OpenAI Embeddings +11
AI & RAG

05. Base_To_Copy. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 54 nodes.

OpenAI Chat, Document Default Data Loader, OpenAI Embeddings +11