{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "7a174902-d92d-4f7f-9cc1-c31bf03cdc9e",
      "name": "Respond - Result",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -160,
        1264
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ feedbackId: $json.feedbackId, category: $json.category, action: $json.action, destination: $json.destination, confidence: $json.confidence }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "618c9dfe-6534-4540-8421-a61c0c0d3d13",
      "name": "Queue for Human Review",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1648
      ],
      "parameters": {
        "jsCode": "return { json: { ...$('Validate AI Output').first().json, action: 'needs_human_review', reason: 'low_confidence_or_validation_failure' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "c0344566-9966-4633-9fc1-3f14715f0ac9",
      "name": "Flag as Testimonial",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1456
      ],
      "parameters": {
        "jsCode": "return { json: { ...$('Validate AI Output').first().json, action: 'testimonial_candidate', destination: 'marketing' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "3d1670fb-5f75-4ca5-bd33-e2d710005e2c",
      "name": "Escalate to CS",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1264
      ],
      "parameters": {
        "jsCode": "return { json: { ...$('Validate AI Output').first().json, action: 'escalated_to_cs', destination: 'customer_success', priority: 'high' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "b537f155-c8f6-4b92-ae19-15cad4ce696e",
      "name": "Add to Backlog",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1072
      ],
      "parameters": {
        "jsCode": "return { json: { ...$('Validate AI Output').first().json, action: 'added_to_backlog', destination: 'product' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "4ef1f784-f94a-4479-aca2-a75cf7f5f024",
      "name": "Create Jira Ticket",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        880
      ],
      "parameters": {
        "jsCode": "return { json: { ...$('Validate AI Output').first().json, action: 'jira_ticket_created', destination: 'engineering' } };"
      },
      "typeVersion": 2
    },
    {
      "id": "52463424-564d-4b05-94e0-b08cd3408a8b",
      "name": "Route by Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        -608,
        1136
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Product Issue",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d6",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Validate AI Output').item.json.category }}",
                    "rightValue": "product_issue"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Feature Request",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d7",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Validate AI Output').item.json.category }}",
                    "rightValue": "feature_request"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Complaint",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d8",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Validate AI Output').item.json.category }}",
                    "rightValue": "complaint"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Praise",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "ad5fb6da-9fe3-4938-afa0-1224711fe3e4",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Validate AI Output').item.json.category }}",
                    "rightValue": "praise"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": 2
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "de3d0075-8164-49c0-9628-f5b16a6f4a30",
      "name": "High Confidence + Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        -832,
        1264
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d4",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $('Validate AI Output').item.json.confidence }}",
              "rightValue": 0.7
            },
            {
              "id": "d5",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $('Validate AI Output').item.json.validationPassed }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "efd5f629-be98-49c7-a329-841aca4e53fb",
      "name": "AI - Classify + Draft",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1760,
        1344
      ],
      "parameters": {
        "text": "=Analyze this customer feedback and return ONLY valid JSON.\n\nCustomer: {{ $('Has Required Fields?').item.json.customerName }}\nProduct: {{ $('Has Required Fields?').item.json.product }}\nFeedback: {{ $('Has Required Fields?').item.json.feedbackText }}\n\nReturn exactly these fields: sentiment, category, priority, suggestedResponse, confidence\n\nUse only these categories: bug_report, feature_request, praise, complaint, question",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "f43c9080-933e-4fd7-8920-f919cea636ed",
      "name": "Respond - Missing Fields",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -2048,
        1904
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'error', message: 'Email and feedback text are required' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c1be1b91-c98e-4c4c-bbab-5b9b6281b444",
      "name": "Has Required Fields?",
      "type": "n8n-nodes-base.if",
      "position": [
        -2336,
        1760
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "req-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.hasRequiredFields }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "30628f38-dd8b-4990-b6f2-867317326951",
      "name": "Normalize Feedback",
      "type": "n8n-nodes-base.code",
      "position": [
        -2560,
        1760
      ],
      "parameters": {
        "jsCode": "// Stage 1: Normalize and clean feedback data\nconst raw = $input.first().json.body;\n\nconst normalized = {\n  feedbackId: 'FB-' + Date.now(),\n  customerName: (raw.name || raw.fullName || 'Anonymous').trim(),\n  customerEmail: (raw.email || '').toLowerCase().trim(),\n  feedbackText: (raw.feedback || raw.message || raw.body || '').trim(),\n  product: (raw.product || raw.service || 'general').toLowerCase().trim(),\n  source: (raw.source || 'web').toLowerCase(),\n  receivedAt: new Date().toISOString()\n};\n\nnormalized.hasRequiredFields = !!(normalized.customerEmail && normalized.feedbackText);\n\nreturn { json: normalized };"
      },
      "typeVersion": 2
    },
    {
      "id": "a46e4d0c-7bce-4568-a516-5cec16564a44",
      "name": "Webhook - Feedback Intake",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -2784,
        1760
      ],
      "parameters": {
        "path": "customer-feedback",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "6ab78c6a-8b90-4030-948a-cbcd1df3e0fd",
      "name": "Input Guardrails",
      "type": "@n8n/n8n-nodes-langchain.guardrails",
      "position": [
        -2112,
        1504
      ],
      "parameters": {
        "text": "={{ $json.feedbackText }}",
        "guardrails": {
          "pii": {
            "value": {
              "type": "all"
            }
          },
          "jailbreak": {
            "value": {
              "threshold": 0.8
            }
          },
          "secretKeys": {
            "value": {
              "permissiveness": "balanced"
            }
          }
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ed87ab69-58d9-41df-844b-e8730ba2cdae",
      "name": "Input Guardrails LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -2048,
        1728
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "gpt-4o"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1e2c8e68-1c4e-4f08-96b5-b6788bf5c340",
      "name": "Validate AI Output",
      "type": "n8n-nodes-base.code",
      "position": [
        -1408,
        1456
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const input = $json;\nlet parsed;\ntry {\n  let raw = typeof input.output === 'string' ? input.output : JSON.stringify(input.output);\n  raw = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  parsed = JSON.parse(raw);\n} catch (e) {\n  return { json: { ...input, validationPassed: false, validationError: 'Invalid JSON in AI output' } };\n}\nconst required = ['sentiment', 'category', 'priority', 'suggestedResponse'];\nconst missing = required.filter(f => !parsed[f]);\nif (missing.length > 0) {\n  return { json: { ...input, ...parsed, validationPassed: false, validationError: 'Missing fields: ' + missing.join(', ') } };\n}\nconst validSentiments = ['positive', 'negative', 'neutral', 'mixed'];\nconst validCategories = ['bug_report', 'feature_request', 'praise', 'complaint', 'question'];\nconst validPriorities = ['critical', 'high', 'medium', 'low'];\nconst errors = [];\nif (!validSentiments.includes(parsed.sentiment)) errors.push('Invalid sentiment');\nif (!validCategories.includes(parsed.category)) errors.push('Invalid category');\nif (!validPriorities.includes(parsed.priority)) errors.push('Invalid priority');\nif (errors.length > 0) {\n  return { json: { ...input, ...parsed, validationPassed: false, validationError: errors.join(', ') } };\n}\nreturn { json: { ...input, ...parsed, validationPassed: true } };"
      },
      "typeVersion": 2
    },
    {
      "id": "2146e518-bbbf-4c05-bbb2-83fc7e9c5651",
      "name": "Output Guardrails",
      "type": "@n8n/n8n-nodes-langchain.guardrails",
      "position": [
        -1184,
        1456
      ],
      "parameters": {
        "text": "={{ $json.suggestedResponse }}",
        "guardrails": {
          "nsfw": {
            "value": {
              "threshold": 0.8
            }
          },
          "secretKeys": {
            "value": {
              "permissiveness": "balanced"
            }
          }
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3c0cf427-51c0-4895-ad7f-e324f51efef4",
      "name": "Respond - Input Blocked",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1696,
        1744
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'input_blocked', message: 'Your message could not be processed. Please rephrase and try again.' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "7b874469-8620-433a-8e16-f4449781a278",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -1120,
        1680
      ],
      "parameters": {
        "model": "google/gemini-3-flash-preview",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dae51f1b-2d3f-42fd-8a74-fc0bfeb9acb1",
      "name": "OpenRouter Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        -1696,
        1568
      ],
      "parameters": {
        "model": "google/gemini-3-flash-preview",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2ebce685-2ada-4dd1-87f2-c89d7f5454b4",
      "name": "Respond - Output Blocked",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -832,
        1456
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'input_blocked', message: 'Your message could not be processed. Please rephrase and try again.' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "b3c38b11-0c9a-4a6e-92f3-121c57f929eb",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3536,
        1264
      ],
      "parameters": {
        "width": 624,
        "height": 912,
        "content": "## Complete Customer Feedback Pipeline\n\n### How it works\nThis workflow chains all five stages of the hybrid deterministic + AI pattern:\n1. **Intake** (Deterministic): Webhook receives feedback, Code node normalizes it, IF node validates required fields.\n2. **Input Guardrails** (Deterministic): Guardrails node checks for PII, injection attempts, and blocked keywords. Flagged inputs are blocked.\n3. **AI Classification + Drafting** (AI): AI Agent classifies the feedback (product issue, feature request, complaint, praise) and drafts a personalized response.\n4. **Output Validation** (Deterministic): Code node validates the classification. Guardrails node checks the draft for policy violations.\n5. **Routing** (Deterministic): High-confidence, valid results route by type: product issues create Jira tickets, feature requests go to backlog, complaints escalate to CS, and praise is flagged for testimonials. Low-confidence results queue for human review.\n\n### Setup\n- Connect your **LLM credentials** to the Chat Model nodes (AI Agent + both Guardrails nodes)\n- Copy the Webhook test URL and send sample feedback with varying tone and content\n- Review the **Route by Type** Switch node to map categories to your actual team integrations\n\n### Customization\n- Replace placeholder Code nodes (Jira, Backlog, Escalate, Testimonial) with real integrations\n- Tune confidence thresholds in the **High Confidence + Valid?** node as you validate accuracy\n- Add more feedback categories by extending the AI prompt and the Route by Type switch\n\nThis template is a learning companion to the Production AI Playbook, a  series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n.  \n\n\nThis template is a learning companion to the Production AI Playbook, a series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n.  \n\nhttps://go.n8n.io/PAP-D&A-Blog"
      },
      "typeVersion": 1
    },
    {
      "id": "1e649a11-e414-4b21-b833-ab85ea22cf6a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2864,
        1520
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 544,
        "content": "## Intake & Normalize"
      },
      "typeVersion": 1
    },
    {
      "id": "a733f629-82ac-4d5b-9d48-1dba94a415f7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2384,
        1440
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 624,
        "content": "## Input Guardrails"
      },
      "typeVersion": 1
    },
    {
      "id": "8dd60983-46fb-46a4-a230-f223d6d5b5b7",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1808,
        1264
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 640,
        "content": "## AI Classification & Drafting"
      },
      "typeVersion": 1
    },
    {
      "id": "b1dadf6d-de05-4f9a-8de4-81d1816890b3",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1232,
        1184
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 688,
        "content": "## Output Validation"
      },
      "typeVersion": 1
    },
    {
      "id": "60fe62ba-e773-4bfc-8660-88ba2eead7bd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        800
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 1072,
        "content": "## Route by Type"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Route by Type": {
      "main": [
        [
          {
            "node": "Create Jira Ticket",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add to Backlog",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Escalate to CS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag as Testimonial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to Backlog": {
      "main": [
        [
          {
            "node": "Respond - Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Escalate to CS": {
      "main": [
        [
          {
            "node": "Respond - Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Guardrails": {
      "main": [
        [
          {
            "node": "AI - Classify + Draft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond - Input Blocked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Guardrails": {
      "main": [
        [
          {
            "node": "High Confidence + Valid?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond - Output Blocked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Jira Ticket": {
      "main": [
        [
          {
            "node": "Respond - Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Feedback": {
      "main": [
        [
          {
            "node": "Has Required Fields?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate AI Output": {
      "main": [
        [
          {
            "node": "Output Guardrails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag as Testimonial": {
      "main": [
        [
          {
            "node": "Respond - Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Required Fields?": {
      "main": [
        [
          {
            "node": "Input Guardrails",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond - Missing Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Guardrails LLM": {
      "ai_languageModel": [
        [
          {
            "node": "Input Guardrails",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI - Classify + Draft": {
      "main": [
        [
          {
            "node": "Validate AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Output Guardrails",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Classify + Draft",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Queue for Human Review": {
      "main": [
        [
          {
            "node": "Respond - Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High Confidence + Valid?": {
      "main": [
        [
          {
            "node": "Route by Type",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Queue for Human Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Feedback Intake": {
      "main": [
        [
          {
            "node": "Normalize Feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}