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 →
{
"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.
elevenLabsApiopenAiApipineconeApipostgres
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
HeyDinastia. Uses executeCommand, httpRequest, youTube, postgres. Webhook trigger; 66 nodes.
RAG AI Agent Template V5. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 56 nodes.
My workflow 2529. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 54 nodes.
05. Base_To_Copy. Uses lmChatOpenAi, documentDefaultDataLoader, embeddingsOpenAi, googleDrive. Event-driven trigger; 54 nodes.