{
  "id": "aqlIB54QNx9xtMjo",
  "name": "AI-Powered Multi-Model Research Analysis & Report Generation",
  "tags": [],
  "nodes": [
    {
      "id": "7f8374e8-47be-476b-af21-84099ffe0241",
      "name": "LLM Request Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        928,
        1960
      ],
      "parameters": {
        "path": "llm-orchestration",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "baa4054a-1a64-466d-b099-3f5e3a91241b",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        1152,
        1960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "azureHealthEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Azure OpenAI health check endpoint URL__>"
            },
            {
              "id": "id-2",
              "name": "awsHealthEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__AWS Bedrock health check endpoint URL__>"
            },
            {
              "id": "id-3",
              "name": "googleHealthEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Google Vertex AI health check endpoint URL__>"
            },
            {
              "id": "id-4",
              "name": "localHealthEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Local model health check endpoint URL__>"
            },
            {
              "id": "id-5",
              "name": "azureApiEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Azure OpenAI API endpoint URL__>"
            },
            {
              "id": "id-6",
              "name": "awsApiEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__AWS Bedrock API endpoint URL__>"
            },
            {
              "id": "id-7",
              "name": "googleApiEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Google Vertex AI API endpoint URL__>"
            },
            {
              "id": "id-8",
              "name": "localApiEndpoint",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Local model API endpoint URL__>"
            },
            {
              "id": "id-9",
              "name": "maxRetries",
              "type": "number",
              "value": 3
            },
            {
              "id": "id-10",
              "name": "retryBaseDelay",
              "type": "number",
              "value": 1000
            },
            {
              "id": "id-11",
              "name": "circuitBreakerThreshold",
              "type": "number",
              "value": 5
            },
            {
              "id": "id-12",
              "name": "anomalyThreshold",
              "type": "number",
              "value": 0.8
            },
            {
              "id": "id-13",
              "name": "costCeilingDefault",
              "type": "number",
              "value": 1
            },
            {
              "id": "id-14",
              "name": "latencySLODefault",
              "type": "number",
              "value": 5000
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "5616905c-5cbe-449d-a14c-41bb52a96ef3",
      "name": "Parse Request & Validate",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        1960
      ],
      "parameters": {
        "jsCode": "// Parse and validate incoming webhook request\n// Extract task metadata, regulatory constraints, cost ceilings, latency SLOs, quality expectations, and tenant ID\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const body = item.json.body || item.json;\n  \n  // Initialize validation errors array\n  const validationErrors = [];\n  \n  // Parse and validate required fields\n  const prompt = body.prompt || body.query || body.text;\n  if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {\n    validationErrors.push('Missing or invalid prompt/query/text');\n  }\n  \n  const tenantId = body.tenantId || body.tenant_id || body.tenant;\n  if (!tenantId) {\n    validationErrors.push('Missing tenant ID');\n  }\n  \n  // Parse task metadata\n  const taskMetadata = {\n    taskId: body.taskId || body.task_id || `task_${Date.now()}`,\n    taskType: body.taskType || body.task_type || 'general',\n    priority: body.priority || 'medium',\n    timestamp: new Date().toISOString()\n  };\n  \n  // Parse regulatory constraints\n  const regulatoryConstraints = {\n    dataResidency: body.dataResidency || body.data_residency || 'any',\n    complianceRequirements: body.complianceRequirements || body.compliance_requirements || [],\n    piiHandling: body.piiHandling || body.pii_handling || 'standard',\n    dataClassification: body.dataClassification || body.data_classification || 'internal'\n  };\n  \n  // Parse cost ceilings\n  const costCeilings = {\n    maxCostPerRequest: parseFloat(body.maxCostPerRequest || body.max_cost_per_request || 0.10),\n    budgetLimit: parseFloat(body.budgetLimit || body.budget_limit || 100.0),\n    costOptimizationEnabled: body.costOptimizationEnabled !== false\n  };\n  \n  // Parse latency SLOs\n  const latencySLOs = {\n    maxLatencyMs: parseInt(body.maxLatencyMs || body.max_latency_ms || 5000),\n    p95LatencyMs: parseInt(body.p95LatencyMs || body.p95_latency_ms || 3000),\n    p99LatencyMs: parseInt(body.p99LatencyMs || body.p99_latency_ms || 4000),\n    timeoutMs: parseInt(body.timeoutMs || body.timeout_ms || 30000)\n  };\n  \n  // Parse quality expectations\n  const qualityExpectations = {\n    minConfidenceScore: parseFloat(body.minConfidenceScore || body.min_confidence_score || 0.7),\n    requireFactChecking: body.requireFactChecking || body.require_fact_checking || false,\n    hallucinationTolerance: body.hallucinationTolerance || body.hallucination_tolerance || 'low',\n    outputFormat: body.outputFormat || body.output_format || 'json',\n    maxTokens: parseInt(body.maxTokens || body.max_tokens || 2000),\n    temperature: parseFloat(body.temperature || 0.7)\n  };\n  \n  // Validate numeric ranges\n  if (costCeilings.maxCostPerRequest < 0) {\n    validationErrors.push('maxCostPerRequest must be non-negative');\n  }\n  \n  if (latencySLOs.maxLatencyMs < 100 || latencySLOs.maxLatencyMs > 60000) {\n    validationErrors.push('maxLatencyMs must be between 100 and 60000');\n  }\n  \n  if (qualityExpectations.minConfidenceScore < 0 || qualityExpectations.minConfidenceScore > 1) {\n    validationErrors.push('minConfidenceScore must be between 0 and 1');\n  }\n  \n  if (qualityExpectations.temperature < 0 || qualityExpectations.temperature > 2) {\n    validationErrors.push('temperature must be between 0 and 2');\n  }\n  \n  // Build validated request object\n  const validatedRequest = {\n    prompt,\n    tenantId,\n    taskMetadata,\n    regulatoryConstraints,\n    costCeilings,\n    latencySLOs,\n    qualityExpectations,\n    validationErrors,\n    isValid: validationErrors.length === 0,\n    rawRequest: body\n  };\n  \n  results.push({\n    json: validatedRequest\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "3f668bc0-5e9a-4e27-9d62-b9ce59691728",
      "name": "Analyze Prompt Complexity",
      "type": "n8n-nodes-base.code",
      "position": [
        1600,
        1960
      ],
      "parameters": {
        "jsCode": "// Analyze Prompt Complexity\n// This code analyzes incoming prompts for token count, semantic complexity, domain classification, and processing time estimation\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const prompt = item.json.prompt || item.json.text || item.json.message || '';\n  \n  // Token count estimation (rough approximation: ~4 chars per token)\n  const tokenCount = Math.ceil(prompt.length / 4);\n  \n  // Semantic complexity score (0-100)\n  // Based on: sentence count, avg word length, unique words, punctuation density\n  const sentences = prompt.split(/[.!?]+/).filter(s => s.trim().length > 0);\n  const words = prompt.split(/\\s+/).filter(w => w.length > 0);\n  const uniqueWords = new Set(words.map(w => w.toLowerCase()));\n  const avgWordLength = words.reduce((sum, w) => sum + w.length, 0) / (words.length || 1);\n  const punctuationCount = (prompt.match(/[,;:()\\[\\]{}\"']/g) || []).length;\n  const punctuationDensity = punctuationCount / (words.length || 1);\n  \n  const complexityScore = Math.min(100, Math.round(\n    (sentences.length * 5) +\n    (avgWordLength * 8) +\n    ((uniqueWords.size / (words.length || 1)) * 30) +\n    (punctuationDensity * 20)\n  ));\n  \n  // Domain classification\n  const domainKeywords = {\n    technical: ['code', 'function', 'algorithm', 'database', 'api', 'programming', 'software', 'debug', 'error', 'system'],\n    creative: ['write', 'story', 'poem', 'creative', 'imagine', 'describe', 'narrative', 'character', 'plot'],\n    analytical: ['analyze', 'compare', 'evaluate', 'assess', 'calculate', 'determine', 'measure', 'statistics', 'data'],\n    conversational: ['hello', 'hi', 'thanks', 'please', 'help', 'question', 'what', 'how', 'why', 'explain'],\n    business: ['strategy', 'market', 'revenue', 'customer', 'sales', 'business', 'profit', 'growth', 'roi']\n  };\n  \n  const lowerPrompt = prompt.toLowerCase();\n  const domainScores = {};\n  \n  for (const [domain, keywords] of Object.entries(domainKeywords)) {\n    const matchCount = keywords.filter(kw => lowerPrompt.includes(kw)).length;\n    domainScores[domain] = matchCount;\n  }\n  \n  const primaryDomain = Object.entries(domainScores)\n    .sort((a, b) => b[1] - a[1])[0]?.[0] || 'general';\n  \n  // Estimated processing time (in seconds)\n  // Base time + token factor + complexity factor\n  const baseTime = 0.5;\n  const tokenFactor = tokenCount * 0.01;\n  const complexityFactor = complexityScore * 0.02;\n  const estimatedProcessingTime = Math.round((baseTime + tokenFactor + complexityFactor) * 100) / 100;\n  \n  results.push({\n    json: {\n      ...item.json,\n      promptAnalysis: {\n        tokenCount,\n        complexityScore,\n        primaryDomain,\n        domainScores,\n        estimatedProcessingTime,\n        promptLength: prompt.length,\n        sentenceCount: sentences.length,\n        wordCount: words.length,\n        uniqueWordRatio: Math.round((uniqueWords.size / (words.length || 1)) * 100) / 100\n      }\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "7fbcd725-fb37-4be8-b929-280981454365",
      "name": "Check Azure OpenAI Health",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1824,
        1768
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.azureHealthEndpoint }}",
        "options": {
          "timeout": 3000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "allowUnauthorizedCerts": true
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "5cbf7f8d-e33f-4fbf-8976-ce5b3f8f160c",
      "name": "Check AWS Bedrock Health",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1824,
        1960
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.awsHealthEndpoint }}",
        "options": {
          "timeout": 3000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "a5e5565e-9668-4619-a8f1-7d45b5b72da4",
      "name": "Merge Health Checks",
      "type": "n8n-nodes-base.merge",
      "position": [
        2048,
        1960
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "9155f3f1-8949-4b2d-b2c1-0694e25dec3d",
      "name": "Score & Rank Models",
      "type": "n8n-nodes-base.code",
      "position": [
        2272,
        1960
      ],
      "parameters": {
        "jsCode": "// Score & Rank Models based on multiple criteria\n// Inputs: health checks, historical performance, prompt complexity, config\n\nconst items = $input.all();\n\n// Extract data from merged inputs\nconst healthChecks = [];\nconst historicalPerformance = [];\nlet promptComplexity = {};\nlet config = {};\n\n// Parse input items to separate health checks, historical data, and config\nfor (const item of items) {\n  if (item.json.type === 'health_check') {\n    healthChecks.push(item.json);\n  } else if (item.json.type === 'historical_performance') {\n    historicalPerformance.push(item.json);\n  } else if (item.json.type === 'prompt_complexity') {\n    promptComplexity = item.json;\n  } else if (item.json.type === 'config') {\n    config = item.json;\n  }\n}\n\n// Default configuration values\nconst costWeight = config.costWeight || 0.25;\nconst latencyWeight = config.latencyWeight || 0.30;\nconst qualityWeight = config.qualityWeight || 0.30;\nconst healthWeight = config.healthWeight || 0.15;\n\nconst maxCostPerRequest = config.maxCostPerRequest || 0.10;\nconst maxLatencySLO = config.maxLatencySLO || 5000; // ms\nconst minQualityScore = config.minQualityScore || 0.7;\n\n// Model definitions with base characteristics\nconst modelCharacteristics = {\n  'azure_openai': {\n    baseCost: 0.03,\n    baseLatency: 2000,\n    baseQuality: 0.92,\n    complexityMultiplier: 1.2\n  },\n  'aws_bedrock': {\n    baseCost: 0.025,\n    baseLatency: 1800,\n    baseQuality: 0.90,\n    complexityMultiplier: 1.15\n  },\n  'google_vertex': {\n    baseCost: 0.028,\n    baseLatency: 2200,\n    baseQuality: 0.91,\n    complexityMultiplier: 1.18\n  },\n  'local_model': {\n    baseCost: 0.001,\n    baseLatency: 3500,\n    baseQuality: 0.75,\n    complexityMultiplier: 1.5\n  }\n};\n\n// Function to calculate model score\nfunction calculateModelScore(modelName, health, historical, complexity) {\n  const characteristics = modelCharacteristics[modelName];\n  if (!characteristics) return 0;\n\n  // Health score (0-1)\n  const healthScore = health.status === 'healthy' ? 1.0 : \n                      health.status === 'degraded' ? 0.5 : 0.0;\n\n  // Cost score (inverse - lower cost is better)\n  const estimatedCost = characteristics.baseCost * (complexity.score || 1);\n  const costScore = Math.max(0, 1 - (estimatedCost / maxCostPerRequest));\n\n  // Latency score (inverse - lower latency is better)\n  const estimatedLatency = characteristics.baseLatency * \n                          (complexity.score || 1) * \n                          characteristics.complexityMultiplier;\n  const latencyScore = Math.max(0, 1 - (estimatedLatency / maxLatencySLO));\n\n  // Quality score (adjusted by historical performance)\n  const historicalQuality = historical?.averageQuality || characteristics.baseQuality;\n  const qualityScore = Math.min(1, historicalQuality / minQualityScore);\n\n  // Weighted total score\n  const totalScore = (\n    (healthScore * healthWeight) +\n    (costScore * costWeight) +\n    (latencyScore * latencyWeight) +\n    (qualityScore * qualityWeight)\n  );\n\n  return {\n    modelName,\n    totalScore: Math.round(totalScore * 1000) / 1000,\n    breakdown: {\n      healthScore: Math.round(healthScore * 1000) / 1000,\n      costScore: Math.round(costScore * 1000) / 1000,\n      latencyScore: Math.round(latencyScore * 1000) / 1000,\n      qualityScore: Math.round(qualityScore * 1000) / 1000\n    },\n    estimates: {\n      cost: Math.round(estimatedCost * 10000) / 10000,\n      latency: Math.round(estimatedLatency),\n      quality: Math.round(historicalQuality * 1000) / 1000\n    },\n    health: health.status,\n    meetsConstraints: (\n      estimatedCost <= maxCostPerRequest &&\n      estimatedLatency <= maxLatencySLO &&\n      historicalQuality >= minQualityScore\n    )\n  };\n}\n\n// Score all models\nconst modelScores = [];\n\nfor (const health of healthChecks) {\n  const modelName = health.model;\n  const historical = historicalPerformance.find(h => h.model === modelName);\n  \n  const score = calculateModelScore(modelName, health, historical, promptComplexity);\n  modelScores.push(score);\n}\n\n// Sort by total score (descending)\nmodelScores.sort((a, b) => b.totalScore - a.totalScore);\n\n// Add rank\nmodelScores.forEach((score, index) => {\n  score.rank = index + 1;\n});\n\n// Filter to only models that meet constraints\nconst eligibleModels = modelScores.filter(m => m.meetsConstraints && m.health !== 'unhealthy');\nconst ineligibleModels = modelScores.filter(m => !m.meetsConstraints || m.health === 'unhealthy');\n\n// Return results\nreturn [{\n  json: {\n    type: 'model_ranking',\n    timestamp: new Date().toISOString(),\n    promptComplexity: promptComplexity.score || 1,\n    constraints: {\n      maxCostPerRequest,\n      maxLatencySLO,\n      minQualityScore\n    },\n    weights: {\n      cost: costWeight,\n      latency: latencyWeight,\n      quality: qualityWeight,\n      health: healthWeight\n    },\n    rankedModels: modelScores,\n    eligibleModels,\n    ineligibleModels,\n    recommendedModel: eligibleModels.length > 0 ? eligibleModels[0].modelName : null,\n    totalModelsEvaluated: modelScores.length,\n    eligibleCount: eligibleModels.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d7a2c5ec-9580-4fc2-8112-8a070c53f8e1",
      "name": "Check Policy Constraints",
      "type": "n8n-nodes-base.if",
      "position": [
        2496,
        1960
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $('Score & Rank Models').item.json.dataResidency }}"
            },
            {
              "id": "id-2",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $('Score & Rank Models').item.json.complianceRequirements }}"
            },
            {
              "id": "id-3",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $('Score & Rank Models').item.json.sensitiveDataFlag }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "23dba12b-34ca-4e3b-be73-c23628002ec6",
      "name": "Apply Policy Routing",
      "type": "n8n-nodes-base.code",
      "position": [
        2720,
        1816
      ],
      "parameters": {
        "jsCode": "// Apply Policy Routing - Filter and re-rank models based on regulatory constraints\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const rankedModels = item.json.rankedModels || [];\n  const promptAnalysis = item.json.promptAnalysis || {};\n  const policyConstraints = item.json.policyConstraints || {};\n  \n  // Extract sensitivity flags\n  const containsPII = promptAnalysis.containsPII || false;\n  const containsPHI = promptAnalysis.containsPHI || false;\n  const containsFinancial = promptAnalysis.containsFinancial || false;\n  const dataClassification = promptAnalysis.dataClassification || 'public';\n  \n  // Define regulatory requirements for each model\n  const modelCompliance = {\n    'azure-openai': {\n      hipaaCompliant: true,\n      gdprCompliant: true,\n      soc2Certified: true,\n      dataResidency: ['US', 'EU'],\n      piiHandling: true\n    },\n    'aws-bedrock': {\n      hipaaCompliant: true,\n      gdprCompliant: true,\n      soc2Certified: true,\n      dataResidency: ['US', 'EU', 'APAC'],\n      piiHandling: true\n    },\n    'google-vertex': {\n      hipaaCompliant: true,\n      gdprCompliant: true,\n      soc2Certified: true,\n      dataResidency: ['US', 'EU'],\n      piiHandling: true\n    },\n    'local-model': {\n      hipaaCompliant: false,\n      gdprCompliant: false,\n      soc2Certified: false,\n      dataResidency: ['on-premise'],\n      piiHandling: false\n    }\n  };\n  \n  // Filter models based on policy constraints\n  let filteredModels = rankedModels.filter(model => {\n    const compliance = modelCompliance[model.provider] || {};\n    \n    // Check HIPAA compliance for PHI data\n    if (containsPHI && !compliance.hipaaCompliant) {\n      console.log(`Filtering out ${model.provider}: Not HIPAA compliant`);\n      return false;\n    }\n    \n    // Check GDPR compliance for PII data\n    if (containsPII && policyConstraints.requireGDPR && !compliance.gdprCompliant) {\n      console.log(`Filtering out ${model.provider}: Not GDPR compliant`);\n      return false;\n    }\n    \n    // Check data residency requirements\n    if (policyConstraints.dataResidency && !compliance.dataResidency.includes(policyConstraints.dataResidency)) {\n      console.log(`Filtering out ${model.provider}: Data residency requirement not met`);\n      return false;\n    }\n    \n    // Check PII handling capability\n    if ((containsPII || containsPHI) && !compliance.piiHandling) {\n      console.log(`Filtering out ${model.provider}: Cannot handle PII/PHI data`);\n      return false;\n    }\n    \n    // Check classification level restrictions\n    if (dataClassification === 'confidential' && model.provider === 'local-model') {\n      console.log(`Filtering out ${model.provider}: Not approved for confidential data`);\n      return false;\n    }\n    \n    return true;\n  });\n  \n  // Re-rank filtered models with compliance bonus\n  filteredModels = filteredModels.map(model => {\n    const compliance = modelCompliance[model.provider] || {};\n    let complianceBonus = 0;\n    \n    // Add bonus points for compliance features\n    if (compliance.hipaaCompliant) complianceBonus += 5;\n    if (compliance.gdprCompliant) complianceBonus += 5;\n    if (compliance.soc2Certified) complianceBonus += 3;\n    \n    // Adjust score with compliance bonus\n    const adjustedScore = (model.score || 0) + complianceBonus;\n    \n    return {\n      ...model,\n      complianceScore: complianceBonus,\n      originalScore: model.score,\n      score: adjustedScore,\n      complianceDetails: compliance\n    };\n  });\n  \n  // Sort by adjusted score (descending)\n  filteredModels.sort((a, b) => b.score - a.score);\n  \n  // Select the top model after policy filtering\n  const selectedModel = filteredModels.length > 0 ? filteredModels[0] : null;\n  \n  if (!selectedModel) {\n    throw new Error('No models available that meet policy constraints');\n  }\n  \n  console.log(`Policy routing selected: ${selectedModel.provider} (score: ${selectedModel.score})`);\n  \n  results.push({\n    json: {\n      ...item.json,\n      filteredModels,\n      selectedModel,\n      policyApplied: true,\n      modelsFiltered: rankedModels.length - filteredModels.length,\n      policyReason: {\n        containsPII,\n        containsPHI,\n        containsFinancial,\n        dataClassification,\n        constraints: policyConstraints\n      }\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "8a0c5e8e-9566-4a62-a7bf-4ea58cb7a2e2",
      "name": "Route to Selected Model",
      "type": "n8n-nodes-base.switch",
      "position": [
        2944,
        1912
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Azure",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.selectedModel }}",
                    "rightValue": "azure"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "AWS",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.selectedModel }}",
                    "rightValue": "aws"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Google",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.selectedModel }}",
                    "rightValue": "google"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Local",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.selectedModel }}",
                    "rightValue": "local"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Fallback"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cf08c853-68c4-4af4-8a5a-20e198b7bfdf",
      "name": "Rewrite Prompt for Azure",
      "type": "n8n-nodes-base.code",
      "position": [
        3168,
        1480
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Rewrite and optimize prompt for Azure OpenAI format\nconst item = $input.item.json;\n\n// Extract prompt and configuration from previous nodes\nconst userPrompt = item.prompt || item.userPrompt || item.message || '';\nconst complexity = item.complexity || 'medium';\nconst selectedModel = item.selectedModel || 'gpt-4';\nconst maxTokens = item.maxTokens || 2000;\nconst temperature = item.temperature || 0.7;\n\n// Build Azure OpenAI compatible request format\nconst azureRequest = {\n  messages: [\n    {\n      role: 'system',\n      content: 'You are a helpful AI assistant. Provide accurate, concise, and well-structured responses.'\n    },\n    {\n      role: 'user',\n      content: userPrompt\n    }\n  ],\n  temperature: temperature,\n  max_tokens: maxTokens,\n  top_p: 0.95,\n  frequency_penalty: 0,\n  presence_penalty: 0,\n  stop: null\n};\n\n// Adjust parameters based on complexity\nif (complexity === 'high') {\n  azureRequest.temperature = Math.min(temperature, 0.5);\n  azureRequest.max_tokens = Math.max(maxTokens, 3000);\n} else if (complexity === 'low') {\n  azureRequest.temperature = Math.min(temperature + 0.2, 1.0);\n  azureRequest.max_tokens = Math.min(maxTokens, 1000);\n}\n\n// Add model-specific optimizations\nif (selectedModel.includes('gpt-4')) {\n  azureRequest.messages[0].content += ' Use your advanced reasoning capabilities to provide detailed analysis.';\n} else if (selectedModel.includes('gpt-3.5')) {\n  azureRequest.messages[0].content += ' Provide clear and efficient responses.';\n}\n\n// Return the formatted request\nreturn {\n  json: {\n    ...item,\n    azureRequest: azureRequest,\n    provider: 'azure',\n    formattedPrompt: userPrompt,\n    systemMessage: azureRequest.messages[0].content,\n    rewrittenAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e83198a9-a2c4-43fe-a54a-95df1d07be6e",
      "name": "Rewrite Prompt for AWS",
      "type": "n8n-nodes-base.code",
      "position": [
        3168,
        1720
      ],
      "parameters": {
        "jsCode": "// Rewrite and optimize prompt for AWS Bedrock format\nconst items = $input.all();\nconst outputItems = [];\n\nfor (const item of items) {\n  const originalPrompt = item.json.prompt || item.json.userPrompt || '';\n  const complexity = item.json.complexity || 'medium';\n  const selectedModel = item.json.selectedModel || 'anthropic.claude-v2';\n  const maxTokens = item.json.maxTokens || 2048;\n  const temperature = item.json.temperature || 0.7;\n  \n  // Extract model provider from model ID\n  const modelProvider = selectedModel.split('.')[0];\n  \n  // Rewrite prompt based on AWS Bedrock best practices\n  let rewrittenPrompt = originalPrompt;\n  \n  // Add system context for better results\n  let systemPrompt = 'You are a helpful AI assistant. Provide accurate, concise, and relevant responses.';\n  \n  // Adjust based on complexity\n  if (complexity === 'high') {\n    systemPrompt += ' Take your time to think through complex problems step by step.';\n  }\n  \n  // Format request body based on model provider\n  let bedrockRequestBody = {};\n  \n  if (modelProvider === 'anthropic') {\n    // Claude format\n    bedrockRequestBody = {\n      prompt: `\\n\\nHuman: ${rewrittenPrompt}\\n\\nAssistant:`,\n      max_tokens_to_sample: maxTokens,\n      temperature: temperature,\n      top_p: 0.9,\n      top_k: 250,\n      stop_sequences: ['\\n\\nHuman:']\n    };\n  } else if (modelProvider === 'ai21') {\n    // AI21 Jurassic format\n    bedrockRequestBody = {\n      prompt: rewrittenPrompt,\n      maxTokens: maxTokens,\n      temperature: temperature,\n      topP: 0.9,\n      stopSequences: [],\n      countPenalty: {\n        scale: 0\n      },\n      presencePenalty: {\n        scale: 0\n      },\n      frequencyPenalty: {\n        scale: 0\n      }\n    };\n  } else if (modelProvider === 'amazon') {\n    // Amazon Titan format\n    bedrockRequestBody = {\n      inputText: rewrittenPrompt,\n      textGenerationConfig: {\n        maxTokenCount: maxTokens,\n        temperature: temperature,\n        topP: 0.9,\n        stopSequences: []\n      }\n    };\n  } else if (modelProvider === 'cohere') {\n    // Cohere format\n    bedrockRequestBody = {\n      prompt: rewrittenPrompt,\n      max_tokens: maxTokens,\n      temperature: temperature,\n      p: 0.9,\n      k: 0,\n      stop_sequences: []\n    };\n  } else {\n    // Default format\n    bedrockRequestBody = {\n      prompt: rewrittenPrompt,\n      max_tokens: maxTokens,\n      temperature: temperature\n    };\n  }\n  \n  outputItems.push({\n    json: {\n      ...item.json,\n      rewrittenPrompt: rewrittenPrompt,\n      systemPrompt: systemPrompt,\n      bedrockModelId: selectedModel,\n      bedrockRequestBody: bedrockRequestBody,\n      bedrockRequestBodyString: JSON.stringify(bedrockRequestBody),\n      provider: 'aws_bedrock',\n      modelProvider: modelProvider,\n      inferenceConfig: {\n        maxTokens: maxTokens,\n        temperature: temperature,\n        topP: 0.9\n      }\n    }\n  });\n}\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "71106353-66c9-4ac6-a0c2-6fb703afa9bf",
      "name": "Rewrite Prompt for Google",
      "type": "n8n-nodes-base.code",
      "position": [
        3168,
        1912
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Rewrite and optimize prompt for Google Vertex AI format\nconst item = $input.item.json;\n\n// Extract prompt and configuration from previous nodes\nconst originalPrompt = item.prompt || item.userPrompt || item.query || '';\nconst complexity = item.complexity || 'medium';\nconst selectedModel = item.selectedModel || 'gemini-pro';\nconst maxTokens = item.maxTokens || 2048;\nconst temperature = item.temperature || 0.7;\n\n// Map complexity to Vertex AI parameters\nconst complexityConfig = {\n  low: { temperature: 0.3, topP: 0.8, topK: 20 },\n  medium: { temperature: 0.7, topP: 0.9, topK: 40 },\n  high: { temperature: 0.9, topP: 0.95, topK: 60 }\n};\n\nconst config = complexityConfig[complexity] || complexityConfig.medium;\n\n// Optimize prompt for Vertex AI (Gemini models prefer clear, structured prompts)\nlet optimizedPrompt = originalPrompt;\n\n// Add context markers for better Gemini understanding\nif (!optimizedPrompt.includes('Context:') && !optimizedPrompt.includes('Task:')) {\n  optimizedPrompt = `Task: ${optimizedPrompt}\\n\\nPlease provide a clear, accurate, and well-structured response.`;\n}\n\n// Build Vertex AI request payload\nconst vertexPayload = {\n  instances: [\n    {\n      content: optimizedPrompt\n    }\n  ],\n  parameters: {\n    temperature: temperature || config.temperature,\n    maxOutputTokens: maxTokens,\n    topP: config.topP,\n    topK: config.topK,\n    // Safety settings for Vertex AI\n    safetySettings: [\n      {\n        category: 'HARM_CATEGORY_HATE_SPEECH',\n        threshold: 'BLOCK_MEDIUM_AND_ABOVE'\n      },\n      {\n        category: 'HARM_CATEGORY_DANGEROUS_CONTENT',\n        threshold: 'BLOCK_MEDIUM_AND_ABOVE'\n      },\n      {\n        category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',\n        threshold: 'BLOCK_MEDIUM_AND_ABOVE'\n      },\n      {\n        category: 'HARM_CATEGORY_HARASSMENT',\n        threshold: 'BLOCK_MEDIUM_AND_ABOVE'\n      }\n    ]\n  }\n};\n\n// Return optimized payload for Google Vertex AI\nreturn {\n  json: {\n    ...item,\n    originalPrompt: originalPrompt,\n    optimizedPrompt: optimizedPrompt,\n    vertexPayload: vertexPayload,\n    provider: 'google-vertex',\n    model: selectedModel,\n    rewriteTimestamp: new Date().toISOString(),\n    promptOptimizations: [\n      'Added task structure',\n      'Applied Vertex AI safety settings',\n      'Configured generation parameters',\n      'Set complexity-based tuning'\n    ]\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "93ff3a10-55ef-4055-bf2c-90cead9e5e5f",
      "name": "Rewrite Prompt for Local",
      "type": "n8n-nodes-base.code",
      "position": [
        3168,
        2104
      ],
      "parameters": {
        "jsCode": "// Rewrite and optimize prompt for local model format\n// Adapts the prompt to the specific local model API structure\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const originalPrompt = item.json.prompt || '';\n  const modelConfig = item.json.selectedModel || {};\n  const complexity = item.json.complexity || {};\n  const metadata = item.json.metadata || {};\n  \n  // Extract local model specific configuration\n  const localModelName = modelConfig.localModelName || 'llama2';\n  const maxTokens = modelConfig.maxTokens || 2048;\n  const temperature = modelConfig.temperature || 0.7;\n  \n  // Optimize prompt for local model\n  let optimizedPrompt = originalPrompt;\n  \n  // Add system context if needed for local models\n  const systemContext = metadata.systemContext || '';\n  if (systemContext) {\n    optimizedPrompt = `System: ${systemContext}\\n\\nUser: ${originalPrompt}`;\n  }\n  \n  // Format for common local model APIs (Ollama, LM Studio, etc.)\n  const localModelRequest = {\n    model: localModelName,\n    prompt: optimizedPrompt,\n    options: {\n      temperature: temperature,\n      num_predict: maxTokens,\n      top_p: 0.9,\n      top_k: 40,\n      repeat_penalty: 1.1\n    },\n    stream: false\n  };\n  \n  // Add complexity-based optimizations\n  if (complexity.level === 'high') {\n    localModelRequest.options.temperature = Math.min(temperature + 0.1, 1.0);\n    localModelRequest.options.num_predict = Math.min(maxTokens * 1.5, 4096);\n  }\n  \n  results.push({\n    json: {\n      ...item.json,\n      rewrittenPrompt: optimizedPrompt,\n      localModelRequest: localModelRequest,\n      originalPrompt: originalPrompt,\n      modelProvider: 'local',\n      rewriteTimestamp: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "92217e1d-c378-4b3a-a42e-c8cfaa9e46db",
      "name": "Call Azure OpenAI",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3392,
        1480
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.azureApiEndpoint }}",
        "body": "={{ JSON.stringify($json.requestBody) }}",
        "method": "POST",
        "options": {
          "timeout": "={{ $json.latencySLO || 30000 }}"
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "c6388998-9adc-4628-b474-41a5a04be133",
      "name": "Call AWS Bedrock",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3392,
        1720
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.awsApiEndpoint }}",
        "body": "={{ JSON.stringify($json.requestBody) }}",
        "method": "POST",
        "options": {
          "timeout": "={{ $json.latencySLO || 30000 }}"
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "e92653ff-bfbe-4fd3-907f-1152cefae06d",
      "name": "Call Google Vertex AI",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3392,
        1912
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.googleApiEndpoint }}",
        "body": "={{ JSON.stringify($json.requestBody) }}",
        "method": "POST",
        "options": {
          "timeout": "={{ $json.latencySLO || 30000 }}"
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "0842cfa1-25bc-481e-a10e-ad7f6f31e6cc",
      "name": "Call Local Model",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3392,
        2104
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.localApiEndpoint }}",
        "body": "={{ JSON.stringify($json.requestBody) }}",
        "method": "POST",
        "options": {
          "timeout": "={{ $json.latencySLO || 30000 }}"
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "aaf3d78d-a12f-4a80-b3b0-4f2e5f43f86e",
      "name": "Handle Circuit Breaker",
      "type": "n8n-nodes-base.code",
      "position": [
        3616,
        1408
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Circuit Breaker Logic for Multi-Cloud LLM Orchestration\n// Tracks failure counts per model, opens circuit after threshold, determines retry/fallback\n\nconst item = $input.item.json;\n\n// Configuration\nconst FAILURE_THRESHOLD = 3;\nconst CIRCUIT_OPEN_DURATION_MS = 60000; // 1 minute\nconst MAX_RETRIES = 2;\n\n// Initialize circuit breaker state (in production, use external state store)\nconst circuitState = $('Workflow Configuration').item.json.circuitState || {};\n\n// Extract model information and response status\nconst modelName = item.selectedModel || item.model || 'unknown';\nconst isSuccess = item.statusCode >= 200 && item.statusCode < 300;\nconst responseTime = item.responseTime || 0;\nconst errorMessage = item.error || '';\n\n// Initialize model state if not exists\nif (!circuitState[modelName]) {\n  circuitState[modelName] = {\n    failureCount: 0,\n    lastFailureTime: null,\n    circuitStatus: 'closed', // closed, open, half-open\n    consecutiveSuccesses: 0,\n    totalRequests: 0,\n    totalFailures: 0\n  };\n}\n\nconst modelState = circuitState[modelName];\nmodelState.totalRequests++;\n\n// Check current circuit status\nlet shouldRetry = false;\nlet shouldFallback = false;\nlet retryCount = item.retryCount || 0;\n\n// If circuit is open, check if enough time has passed to try half-open\nif (modelState.circuitStatus === 'open') {\n  const timeSinceFailure = Date.now() - modelState.lastFailureTime;\n  \n  if (timeSinceFailure >= CIRCUIT_OPEN_DURATION_MS) {\n    // Move to half-open state for testing\n    modelState.circuitStatus = 'half-open';\n    console.log(`Circuit for ${modelName} moved to HALF-OPEN state`);\n  } else {\n    // Circuit still open, trigger immediate fallback\n    shouldFallback = true;\n    console.log(`Circuit for ${modelName} is OPEN. Triggering fallback.`);\n  }\n}\n\n// Process current request result\nif (isSuccess) {\n  // Success handling\n  modelState.consecutiveSuccesses++;\n  \n  if (modelState.circuitStatus === 'half-open' && modelState.consecutiveSuccesses >= 2) {\n    // Close circuit after successful half-open tests\n    modelState.circuitStatus = 'closed';\n    modelState.failureCount = 0;\n    console.log(`Circuit for ${modelName} CLOSED after successful recovery`);\n  } else if (modelState.circuitStatus === 'closed') {\n    // Reset failure count on success in closed state\n    modelState.failureCount = 0;\n  }\n  \n  shouldRetry = false;\n  shouldFallback = false;\n  \n} else {\n  // Failure handling\n  modelState.failureCount++;\n  modelState.totalFailures++;\n  modelState.lastFailureTime = Date.now();\n  modelState.consecutiveSuccesses = 0;\n  \n  console.log(`Failure detected for ${modelName}. Count: ${modelState.failureCount}/${FAILURE_THRESHOLD}`);\n  \n  // Check if we should open the circuit\n  if (modelState.failureCount >= FAILURE_THRESHOLD) {\n    modelState.circuitStatus = 'open';\n    console.log(`Circuit for ${modelName} OPENED due to ${FAILURE_THRESHOLD} consecutive failures`);\n    shouldFallback = true;\n    shouldRetry = false;\n  } else if (retryCount < MAX_RETRIES) {\n    // Still within retry limits and circuit not open\n    shouldRetry = true;\n    shouldFallback = false;\n    retryCount++;\n  } else {\n    // Exceeded retry limit\n    shouldFallback = true;\n    shouldRetry = false;\n    console.log(`Max retries (${MAX_RETRIES}) exceeded for ${modelName}. Triggering fallback.`);\n  }\n}\n\n// Calculate failure rate\nconst failureRate = modelState.totalRequests > 0 \n  ? (modelState.totalFailures / modelState.totalRequests * 100).toFixed(2)\n  : 0;\n\n// Prepare output\nreturn {\n  json: {\n    ...item,\n    circuitBreaker: {\n      modelName,\n      circuitStatus: modelState.circuitStatus,\n      failureCount: modelState.failureCount,\n      failureThreshold: FAILURE_THRESHOLD,\n      consecutiveSuccesses: modelState.consecutiveSuccesses,\n      totalRequests: modelState.totalRequests,\n      totalFailures: modelState.totalFailures,\n      failureRate: `${failureRate}%`,\n      lastFailureTime: modelState.lastFailureTime,\n      shouldRetry,\n      shouldFallback,\n      retryCount,\n      maxRetries: MAX_RETRIES,\n      timestamp: Date.now()\n    },\n    shouldRetry,\n    shouldFallback,\n    retryCount,\n    circuitState // Pass updated state forward\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "003a5db0-1db5-4593-aa66-5bb6bc1edda5",
      "name": "Check Retry Needed",
      "type": "n8n-nodes-base.if",
      "position": [
        3840,
        1408
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $('Handle Circuit Breaker').item.json.shouldRetry }}"
            },
            {
              "id": "id-2",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $('Handle Circuit Breaker').item.json.retryCount }}",
              "rightValue": "={{ $('Handle Circuit Breaker').item.json.maxRetries }}"
            },
            {
              "id": "id-3",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $('Handle Circuit Breaker').item.json.circuitBreakerState }}",
              "rightValue": "open"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "6d1bd635-cd97-4e8a-aeb2-bf7bf33e4de6",
      "name": "Adaptive Retry Delay",
      "type": "n8n-nodes-base.wait",
      "position": [
        4064,
        1552
      ],
      "parameters": {
        "amount": "={{ $('Workflow Configuration').first().json.retryBaseDelay * Math.pow(2, $json.retryCount || 0) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "f24bc275-47d1-498b-82b1-4bfabb76907e",
      "name": "Merge Model Responses",
      "type": "n8n-nodes-base.merge",
      "position": [
        4064,
        1768
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "4cf06d1b-698e-4f9f-9dbd-c52083dd5da9",
      "name": "Normalize Output",
      "type": "n8n-nodes-base.code",
      "position": [
        4288,
        1768
      ],
      "parameters": {
        "jsCode": "// Normalize model outputs into a consistent format\n// Extract text, metadata, and provider-specific fields into a unified structure\n\nconst items = $input.all();\nconst normalizedOutputs = [];\n\nfor (const item of items) {\n  const rawResponse = item.json;\n  \n  // Initialize normalized structure\n  const normalized = {\n    text: '',\n    provider: '',\n    model: '',\n    metadata: {\n      tokensUsed: 0,\n      promptTokens: 0,\n      completionTokens: 0,\n      latencyMs: 0,\n      finishReason: '',\n      requestId: ''\n    },\n    providerSpecific: {},\n    timestamp: new Date().toISOString(),\n    success: true\n  };\n  \n  // Detect provider and normalize accordingly\n  if (rawResponse.provider === 'azure' || rawResponse.choices) {\n    // Azure OpenAI format\n    normalized.provider = 'azure';\n    normalized.text = rawResponse.choices?.[0]?.message?.content || rawResponse.choices?.[0]?.text || '';\n    normalized.model = rawResponse.model || 'gpt-4';\n    normalized.metadata.tokensUsed = rawResponse.usage?.total_tokens || 0;\n    normalized.metadata.promptTokens = rawResponse.usage?.prompt_tokens || 0;\n    normalized.metadata.completionTokens = rawResponse.usage?.completion_tokens || 0;\n    normalized.metadata.finishReason = rawResponse.choices?.[0]?.finish_reason || '';\n    normalized.metadata.requestId = rawResponse.id || '';\n    normalized.providerSpecific = {\n      systemFingerprint: rawResponse.system_fingerprint,\n      created: rawResponse.created\n    };\n  } \n  else if (rawResponse.provider === 'aws' || rawResponse.body) {\n    // AWS Bedrock format\n    normalized.provider = 'aws';\n    const body = typeof rawResponse.body === 'string' ? JSON.parse(rawResponse.body) : rawResponse.body;\n    normalized.text = body.completion || body.results?.[0]?.outputText || '';\n    normalized.model = rawResponse.modelId || 'claude-v2';\n    normalized.metadata.tokensUsed = body.usage?.total_tokens || (body.inputTextTokenCount || 0) + (body.results?.[0]?.tokenCount || 0);\n    normalized.metadata.promptTokens = body.inputTextTokenCount || 0;\n    normalized.metadata.completionTokens = body.results?.[0]?.tokenCount || 0;\n    normalized.metadata.finishReason = body.stop_reason || body.results?.[0]?.completionReason || '';\n    normalized.metadata.requestId = rawResponse.ResponseMetadata?.RequestId || '';\n    normalized.providerSpecific = {\n      stopReason: body.stop_reason,\n      amazonBedrockInvocationMetrics: rawResponse.ResponseMetadata?.HTTPHeaders?.['x-amzn-bedrock-invocation-latency']\n    };\n  }\n  else if (rawResponse.provider === 'google' || rawResponse.predictions) {\n    // Google Vertex AI format\n    normalized.provider = 'google';\n    normalized.text = rawResponse.predictions?.[0]?.content || rawResponse.predictions?.[0]?.candidates?.[0]?.content || '';\n    normalized.model = rawResponse.deployedModelId || 'text-bison';\n    normalized.metadata.tokensUsed = rawResponse.metadata?.tokenMetadata?.totalTokens || 0;\n    normalized.metadata.promptTokens = rawResponse.metadata?.tokenMetadata?.inputTokens || 0;\n    normalized.metadata.completionTokens = rawResponse.metadata?.tokenMetadata?.outputTokens || 0;\n    normalized.metadata.finishReason = rawResponse.predictions?.[0]?.safetyAttributes?.blocked ? 'blocked' : 'stop';\n    normalized.metadata.requestId = rawResponse.metadata?.requestId || '';\n    normalized.providerSpecific = {\n      safetyAttributes: rawResponse.predictions?.[0]?.safetyAttributes,\n      citationMetadata: rawResponse.predictions?.[0]?.citationMetadata\n    };\n  }\n  else if (rawResponse.provider === 'local' || rawResponse.response) {\n    // Local model format (e.g., Ollama, LM Studio)\n    normalized.provider = 'local';\n    normalized.text = rawResponse.response || rawResponse.text || rawResponse.output || '';\n    normalized.model = rawResponse.model || 'local-llm';\n    normalized.metadata.tokensUsed = rawResponse.eval_count || 0;\n    normalized.metadata.promptTokens = rawResponse.prompt_eval_count || 0;\n    normalized.metadata.completionTokens = rawResponse.eval_count || 0;\n    normalized.metadata.finishReason = rawResponse.done ? 'stop' : 'length';\n    normalized.metadata.requestId = rawResponse.created_at || '';\n    normalized.providerSpecific = {\n      totalDuration: rawResponse.total_duration,\n      loadDuration: rawResponse.load_duration,\n      evalDuration: rawResponse.eval_duration\n    };\n  }\n  else {\n    // Generic fallback\n    normalized.provider = rawResponse.provider || 'unknown';\n    normalized.text = rawResponse.text || rawResponse.content || rawResponse.output || JSON.stringify(rawResponse);\n    normalized.model = rawResponse.model || 'unknown';\n    normalized.success = false;\n  }\n  \n  // Calculate latency if available\n  if (rawResponse.startTime && rawResponse.endTime) {\n    normalized.metadata.latencyMs = rawResponse.endTime - rawResponse.startTime;\n  } else if (rawResponse.latency) {\n    normalized.metadata.latencyMs = rawResponse.latency;\n  }\n  \n  normalizedOutputs.push({ json: normalized });\n}\n\nreturn normalizedOutputs;"
      },
      "typeVersion": 2
    },
    {
      "id": "5f4baa3c-b785-4d5a-8314-9e1b87e0314c",
      "name": "Calculate Telemetry Metrics",
      "type": "n8n-nodes-base.code",
      "position": [
        4512,
        1768
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Calculate Telemetry Metrics\nconst item = $input.item.json;\n\n// Extract model response data\nconst modelResponse = item.modelResponse || {};\nconst selectedModel = item.selectedModel || 'unknown';\nconst startTime = item.startTime || Date.now();\n\n// Calculate token usage\nconst promptTokens = modelResponse.usage?.prompt_tokens || 0;\nconst completionTokens = modelResponse.usage?.completion_tokens || 0;\nconst totalTokens = promptTokens + completionTokens;\n\n// Calculate actual latency (in milliseconds)\nconst endTime = Date.now();\nconst actualLatency = endTime - startTime;\n\n// Model pricing per 1K tokens (adjust based on actual pricing)\nconst modelPricing = {\n  'azure-openai': { prompt: 0.0015, completion: 0.002 },\n  'aws-bedrock': { prompt: 0.0008, completion: 0.0024 },\n  'google-vertex': { prompt: 0.00025, completion: 0.0005 },\n  'local-model': { prompt: 0, completion: 0 }\n};\n\n// Get pricing for selected model\nconst pricing = modelPricing[selectedModel] || { prompt: 0, completion: 0 };\n\n// Calculate cost\nconst promptCost = (promptTokens / 1000) * pricing.prompt;\nconst completionCost = (completionTokens / 1000) * pricing.completion;\nconst totalCost = promptCost + completionCost;\n\n// Create telemetry metrics object\nconst telemetryMetrics = {\n  totalTokens: totalTokens,\n  promptTokens: promptTokens,\n  completionTokens: completionTokens,\n  actualLatency: actualLatency,\n  latencySeconds: (actualLatency / 1000).toFixed(3),\n  cost: {\n    promptCost: parseFloat(promptCost.toFixed(6)),\n    completionCost: parseFloat(completionCost.toFixed(6)),\n    totalCost: parseFloat(totalCost.toFixed(6)),\n    currency: 'USD'\n  },\n  timestamp: new Date().toISOString(),\n  model: selectedModel,\n  startTime: startTime,\n  endTime: endTime\n};\n\n// Return enriched item with telemetry metrics\nreturn {\n  json: {\n    ...item,\n    telemetryMetrics: telemetryMetrics\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "724e0d2f-097b-4e90-9aa0-f07cc7f1a047",
      "name": "Assess Hallucination Risk",
      "type": "n8n-nodes-base.code",
      "position": [
        4736,
        1768
      ],
      "parameters": {
        "jsCode": "// Assess Hallucination Risk\n// Analyzes LLM response for potential hallucinations using multiple heuristics\n// Produces a risk score from 0 (low risk) to 1 (high risk)\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const response = item.json.response || item.json.output || '';\n  const prompt = item.json.prompt || item.json.input || '';\n  const modelName = item.json.selectedModel || 'unknown';\n  \n  // Initialize risk factors\n  let riskScore = 0;\n  const riskFactors = [];\n  \n  // 1. Response Consistency Check\n  // Look for contradictions within the response\n  const sentences = response.split(/[.!?]+/).filter(s => s.trim().length > 0);\n  let contradictionScore = 0;\n  \n  // Check for common contradiction patterns\n  const contradictionPatterns = [\n    { pattern: /(however|but|although|despite).*?(not|never|no|none)/gi, weight: 0.1 },\n    { pattern: /(always|never|all|none).*?(sometimes|maybe|possibly)/gi, weight: 0.15 },\n    { pattern: /(yes|correct|true).*?(no|incorrect|false)/gi, weight: 0.2 }\n  ];\n  \n  for (const { pattern, weight } of contradictionPatterns) {\n    const matches = response.match(pattern);\n    if (matches && matches.length > 0) {\n      contradictionScore += weight * matches.length;\n      riskFactors.push(`Contradiction pattern detected (${matches.length} instances)`);\n    }\n  }\n  \n  riskScore += Math.min(contradictionScore, 0.3);\n  \n  // 2. Confidence Markers Analysis\n  // Lack of confidence markers or excessive hedging indicates uncertainty\n  const uncertaintyMarkers = [\n    'might', 'maybe', 'possibly', 'perhaps', 'could be', 'may be',\n    'i think', 'i believe', 'probably', 'likely', 'unclear',\n    'not sure', 'uncertain', 'approximately', 'roughly'\n  ];\n  \n  let uncertaintyCount = 0;\n  const lowerResponse = response.toLowerCase();\n  \n  for (const marker of uncertaintyMarkers) {\n    const regex = new RegExp(marker, 'gi');\n    const matches = lowerResponse.match(regex);\n    if (matches) {\n      uncertaintyCount += matches.length;\n    }\n  }\n  \n  // High uncertainty marker density suggests hallucination risk\n  const uncertaintyDensity = uncertaintyCount / Math.max(sentences.length, 1);\n  if (uncertaintyDensity > 0.3) {\n    riskScore += 0.2;\n    riskFactors.push(`High uncertainty marker density: ${uncertaintyDensity.toFixed(2)}`);\n  } else if (uncertaintyDensity > 0.15) {\n    riskScore += 0.1;\n    riskFactors.push(`Moderate uncertainty marker density: ${uncertaintyDensity.toFixed(2)}`);\n  }\n  \n  // 3. Factual Grounding Indicators\n  // Check for specific dates, numbers, citations, or references\n  const groundingPatterns = [\n    /\\b(19|20)\\d{2}\\b/g, // Years\n    /\\b\\d+(\\.\\d+)?%\\b/g, // Percentages\n    /\\b\\d+(\\.\\d+)?\\s*(million|billion|thousand)\\b/gi, // Large numbers\n    /\\b(according to|based on|research shows|studies indicate|data suggests)\\b/gi, // Citations\n    /\\b(source:|reference:|citation:)\\b/gi // Explicit references\n  ];\n  \n  let groundingScore = 0;\n  for (const pattern of groundingPatterns) {\n    const matches = response.match(pattern);\n    if (matches) {\n      groundingScore += matches.length;\n    }\n  }\n  \n  // Low grounding in factual responses increases risk\n  const groundingDensity = groundingScore / Math.max(sentences.length, 1);\n  if (groundingDensity < 0.1 && response.length > 200) {\n    riskScore += 0.15;\n    riskFactors.push('Low factual grounding indicators');\n  }\n  \n  // 4. Specificity vs Vagueness\n  // Vague responses are more likely to be hallucinated\n  const vagueTerms = [\n    'some', 'many', 'various', 'several', 'often', 'sometimes',\n    'generally', 'typically', 'usually', 'commonly', 'frequently',\n    'thing', 'stuff', 'something', 'somewhere'\n  ];\n  \n  let vaguenessCount = 0;\n  for (const term of vagueTerms) {\n    const regex = new RegExp(`\\\\b${term}\\\\b`, 'gi');\n    const matches = lowerResponse.match(regex);\n    if (matches) {\n      vaguenessCount += matches.length;\n    }\n  }\n  \n  const vaguenessDensity = vaguenessCount / Math.max(sentences.length, 1);\n  if (vaguenessDensity > 0.4) {\n    riskScore += 0.15;\n    riskFactors.push(`High vagueness density: ${vaguenessDensity.toFixed(2)}`);\n  }\n  \n  // 5. Response Length Analysis\n  // Extremely short or verbose responses can indicate issues\n  if (response.length < 50 && prompt.length > 100) {\n    riskScore += 0.1;\n    riskFactors.push('Response too short for complex prompt');\n  } else if (response.length > 5000 && sentences.length > 50) {\n    riskScore += 0.05;\n    riskFactors.push('Unusually verbose response');\n  }\n  \n  // 6. Repetition Detection\n  // Excessive repetition suggests model confusion\n  const words = response.toLowerCase().split(/\\s+/);\n  const wordFrequency = {};\n  \n  for (const word of words) {\n    if (word.length > 4) { // Only count meaningful words\n      wordFrequency[word] = (wordFrequency[word] || 0) + 1;\n    }\n  }\n  \n  let repetitionScore = 0;\n  for (const [word, count] of Object.entries(wordFrequency)) {\n    if (count > 5) {\n      repetitionScore += (count - 5) * 0.02;\n    }\n  }\n  \n  if (repetitionScore > 0.1) {\n    riskScore += Math.min(repetitionScore, 0.15);\n    riskFactors.push('Excessive word repetition detected');\n  }\n  \n  // 7. Prompt-Response Alignment\n  // Check if response actually addresses the prompt\n  const promptKeywords = prompt.toLowerCase()\n    .split(/\\s+/)\n    .filter(w => w.length > 4 && !['what', 'when', 'where', 'which', 'would', 'could', 'should'].includes(w));\n  \n  let alignmentScore = 0;\n  for (const keyword of promptKeywords.slice(0, 10)) { // Check top 10 keywords\n    if (lowerResponse.includes(keyword)) {\n      alignmentScore++;\n    }\n  }\n  \n  const alignmentRatio = alignmentScore / Math.max(promptKeywords.slice(0, 10).length, 1);\n  if (alignmentRatio < 0.3) {\n    riskScore += 0.2;\n    riskFactors.push(`Low prompt-response alignment: ${(alignmentRatio * 100).toFixed(0)}%`);\n  }\n  \n  // Normalize final risk score to 0-1 range\n  riskScore = Math.min(Math.max(riskScore, 0), 1);\n  \n  // Determine risk level\n  let riskLevel = 'low';\n  if (riskScore > 0.7) {\n    riskLevel = 'critical';\n  } else if (riskScore > 0.5) {\n    riskLevel = 'high';\n  } else if (riskScore > 0.3) {\n    riskLevel = 'medium';\n  }\n  \n  results.push({\n    json: {\n      ...item.json,\n      hallucinationRisk: {\n        score: parseFloat(riskScore.toFixed(3)),\n        level: riskLevel,\n        factors: riskFactors,\n        metrics: {\n          contradictionScore: parseFloat(contradictionScore.toFixed(3)),\n          uncertaintyDensity: parseFloat(uncertaintyDensity.toFixed(3)),\n          groundingDensity: parseFloat(groundingDensity.toFixed(3)),\n          vaguenessDensity: parseFloat(vaguenessDensity.toFixed(3)),\n          alignmentRatio: parseFloat(alignmentRatio.toFixed(3)),\n          repetitionScore: parseFloat(repetitionScore.toFixed(3))\n        },\n        assessedAt: new Date().toISOString(),\n        modelName: modelName\n      }\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "8005e1aa-415a-4754-868a-0bc27e10ee99",
      "name": "Calculate Confidence Score",
      "type": "n8n-nodes-base.code",
      "position": [
        4960,
        1768
      ],
      "parameters": {
        "jsCode": "// Calculate Confidence Score based on multiple factors\n// Inputs: model certainty, response completeness, hallucination risk, quality indicators\n// Output: confidence score between 0 and 1\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json;\n  \n  // Extract input factors (with defaults if not present)\n  const modelCertainty = data.modelCertainty || 0.5;\n  const responseCompleteness = data.responseCompleteness || 0.5;\n  const hallucinationRisk = data.hallucinationRisk || 0.5;\n  const qualityIndicators = data.qualityIndicators || {};\n  \n  // Calculate quality score from indicators\n  let qualityScore = 0.5;\n  if (Object.keys(qualityIndicators).length > 0) {\n    const scores = Object.values(qualityIndicators).filter(v => typeof v === 'number');\n    if (scores.length > 0) {\n      qualityScore = scores.reduce((sum, val) => sum + val, 0) / scores.length;\n    }\n  }\n  \n  // Weighted confidence calculation\n  // Higher hallucination risk reduces confidence\n  const weights = {\n    modelCertainty: 0.3,\n    responseCompleteness: 0.25,\n    hallucinationRiskInverse: 0.25,\n    qualityScore: 0.2\n  };\n  \n  const confidenceScore = (\n    (modelCertainty * weights.modelCertainty) +\n    (responseCompleteness * weights.responseCompleteness) +\n    ((1 - hallucinationRisk) * weights.hallucinationRiskInverse) +\n    (qualityScore * weights.qualityScore)\n  );\n  \n  // Ensure score is between 0 and 1\n  const normalizedConfidence = Math.max(0, Math.min(1, confidenceScore));\n  \n  // Determine confidence level\n  let confidenceLevel = 'low';\n  if (normalizedConfidence >= 0.8) {\n    confidenceLevel = 'high';\n  } else if (normalizedConfidence >= 0.6) {\n    confidenceLevel = 'medium';\n  }\n  \n  results.push({\n    json: {\n      ...data,\n      confidenceScore: normalizedConfidence,\n      confidenceLevel: confidenceLevel,\n      confidenceFactors: {\n        modelCertainty: modelCertainty,\n        responseCompleteness: responseCompleteness,\n        hallucinationRisk: hallucinationRisk,\n        qualityScore: qualityScore\n      },\n      confidenceCalculatedAt: new Date().toISOString()\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "8c868644-89f3-43b1-8464-708c154de750",
      "name": "Log Telemetry to Database",
      "type": "n8n-nodes-base.postgres",
      "position": [
        5184,
        1552
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "llm_telemetry"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {
            "cost": "={{ $json.cost }}",
            "tenant_id": "={{ $json.tenant_id }}",
            "timestamp": "={{ $json.timestamp }}",
            "latency_ms": "={{ $json.latency_ms }}",
            "model_name": "={{ $json.model_name }}",
            "request_id": "={{ $json.request_id }}",
            "total_tokens": "={{ $json.total_tokens }}",
            "prompt_tokens": "={{ $json.prompt_tokens }}",
            "model_provider": "={{ $json.model_provider }}",
            "confidence_score": "={{ $json.confidence_score }}",
            "completion_tokens": "={{ $json.completion_tokens }}",
            "hallucination_risk": "={{ $json.hallucination_risk }}"
          },
          "schema": [
            {
              "id": "tenant_id",
              "required": false,
              "displayName": "tenant_id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "request_id",
              "required": false,
              "displayName": "request_id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "model_provider",
              "required": false,
              "displayName": "model_provider",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "model_name",
              "required": false,
              "displayName": "model_name",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "prompt_tokens",
              "required": false,
              "displayName": "prompt_tokens",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "completion_tokens",
              "required": false,
              "displayName": "completion_tokens",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "total_tokens",
              "required": false,
              "displayName": "total_tokens",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "latency_ms",
              "required": false,
              "displayName": "latency_ms",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "cost",
              "required": false,
              "displayName": "cost",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "hallucination_risk",
              "required": false,
              "displayName": "hallucination_risk",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "confidence_score",
              "required": false,
              "displayName": "confidence_score",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "tenant_id",
            "request_id",
            "model_provider",
            "model_name",
            "prompt_tokens",
            "completion_tokens",
            "total_tokens",
            "latency_ms",
            "cost",
            "hallucination_risk",
            "confidence_score",
            "timestamp"
          ]
        },
        "options": {}
      },
      "typeVersion": 2.6
    },
    {
      "id": "0af43b67-0af6-484c-a69f-d9f7523ff1b9",
      "name": "Store Result in Datastore",
      "type": "n8n-nodes-base.postgres",
      "position": [
        5184,
        2032
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "llm_results"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {
            "prompt": "={{ $json.prompt }}",
            "metadata": "={{ $json.metadata }}",
            "response": "={{ $json.response }}",
            "tenant_id": "={{ $json.tenant_id }}",
            "created_at": "={{ $json.created_at }}",
            "model_name": "={{ $json.model_name }}",
            "request_id": "={{ $json.request_id }}",
            "model_provider": "={{ $json.model_provider }}",
            "confidence_score": "={{ $json.confidence_score }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {}
      },
      "typeVersion": 2.6
    },
    {
      "id": "1faa9ae1-badf-4261-aa50-55c5edf1056a",
      "name": "Detect Anomalies",
      "type": "n8n-nodes-base.code",
      "position": [
        5184,
        2224
      ],
      "parameters": {
        "jsCode": "// Detect Anomalies - Compare current metrics against historical baselines\n// Uses z-score statistical method to identify anomalies\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const data = item.json;\n  \n  // Extract current metrics\n  const currentMetrics = {\n    latency: data.latency || 0,\n    cost: data.cost || 0,\n    tokenUsage: data.tokenUsage || 0,\n    errorRate: data.errorRate || 0\n  };\n  \n  // Extract historical baselines (mean and standard deviation)\n  const baselines = data.historicalBaselines || {\n    latency: { mean: 1000, stdDev: 200 },\n    cost: { mean: 0.05, stdDev: 0.01 },\n    tokenUsage: { mean: 500, stdDev: 100 },\n    errorRate: { mean: 0.02, stdDev: 0.01 }\n  };\n  \n  // Calculate z-scores for each metric\n  const calculateZScore = (value, mean, stdDev) => {\n    if (stdDev === 0) return 0;\n    return (value - mean) / stdDev;\n  };\n  \n  const zScores = {\n    latency: calculateZScore(currentMetrics.latency, baselines.latency.mean, baselines.latency.stdDev),\n    cost: calculateZScore(currentMetrics.cost, baselines.cost.mean, baselines.cost.stdDev),\n    tokenUsage: calculateZScore(currentMetrics.tokenUsage, baselines.tokenUsage.mean, baselines.tokenUsage.stdDev),\n    errorRate: calculateZScore(currentMetrics.errorRate, baselines.errorRate.mean, baselines.errorRate.stdDev)\n  };\n  \n  // Determine anomaly flags and severity\n  // Z-score thresholds: |z| > 3 = critical, |z| > 2 = high, |z| > 1.5 = medium\n  const anomalies = [];\n  let maxSeverity = 'normal';\n  \n  Object.keys(zScores).forEach(metric => {\n    const z = Math.abs(zScores[metric]);\n    let severity = 'normal';\n    let isAnomaly = false;\n    \n    if (z > 3) {\n      severity = 'critical';\n      isAnomaly = true;\n    } else if (z > 2) {\n      severity = 'high';\n      isAnomaly = true;\n    } else if (z > 1.5) {\n      severity = 'medium';\n      isAnomaly = true;\n    }\n    \n    if (isAnomaly) {\n      anomalies.push({\n        metric: metric,\n        currentValue: currentMetrics[metric],\n        expectedMean: baselines[metric].mean,\n        zScore: zScores[metric],\n        severity: severity,\n        deviation: ((currentMetrics[metric] - baselines[metric].mean) / baselines[metric].mean * 100).toFixed(2) + '%'\n      });\n      \n      // Update max severity\n      const severityLevels = { normal: 0, medium: 1, high: 2, critical: 3 };\n      if (severityLevels[severity] > severityLevels[maxSeverity]) {\n        maxSeverity = severity;\n      }\n    }\n  });\n  \n  // Prepare output\n  results.push({\n    json: {\n      ...data,\n      anomalyDetection: {\n        hasAnomalies: anomalies.length > 0,\n        anomalyCount: anomalies.length,\n        maxSeverity: maxSeverity,\n        anomalies: anomalies,\n        zScores: zScores,\n        currentMetrics: currentMetrics,\n        timestamp: new Date().toISOString()\n      }\n    }\n  });\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "8eaceaf7-f1cf-4ce3-97a5-81406932c8c6",
      "name": "Check Anomaly Threshold",
      "type": "n8n-nodes-base.if",
      "position": [
        5408,
        2224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $('Detect Anomalies').item.json.anomalySeverity }}",
              "rightValue": "={{ $('Workflow Configuration').item.json.anomalyThreshold }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "dd4a07bb-7396-4e8a-8abe-1831a6632f48",
      "name": "Send Anomaly Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        5632,
        2224
      ],
      "parameters": {
        "text": "=\ud83d\udea8 **Anomaly Detected in LLM Orchestration**\n\n**Model:** {{ $json.model }}\n**Metric:** {{ $json.metric }}\n**Severity:** {{ $json.severity }}\n\n**Current Value:** {{ $json.currentValue }}\n**Baseline Value:** {{ $json.baselineValue }}\n**Deviation:** {{ $json.deviation }}%\n\n**Timestamp:** {{ $json.timestamp }}\n**Request ID:** {{ $json.requestId }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Slack channel ID for alerts__>"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "7ffbb901-d5a6-4e44-a332-58c51e4b136f",
      "name": "Generate Cost Report Data",
      "type": "n8n-nodes-base.postgres",
      "position": [
        5408,
        1456
      ],
      "parameters": {
        "query": "SELECT \n  model_name,\n  tenant_id,\n  DATE_TRUNC('day', timestamp) as report_date,\n  COUNT(*) as total_requests,\n  SUM(cost) as total_cost,\n  AVG(cost) as avg_cost_per_request,\n  SUM(tokens_used) as total_tokens,\n  AVG(latency_ms) as avg_latency_ms\nFROM llm_telemetry\nWHERE timestamp >= NOW() - INTERVAL '30 days'\nGROUP BY model_name, tenant_id, DATE_TRUNC('day', timestamp)\nORDER BY report_date DESC, total_cost DESC",
        "options": {},
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "3c0f9e1e-1f7a-48d0-93b6-bc08760d7cdb",
      "name": "Generate Performance Report Data",
      "type": "n8n-nodes-base.postgres",
      "position": [
        5408,
        1648
      ],
      "parameters": {
        "query": "SELECT \n  model_name,\n  tenant_id,\n  COUNT(*) as total_requests,\n  AVG(latency_ms) as avg_latency_ms,\n  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms) as p95_latency_ms,\n  SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END)::FLOAT / COUNT(*) * 100 as success_rate_percent,\n  SUM(tokens_used) / EXTRACT(EPOCH FROM (MAX(timestamp) - MIN(timestamp))) as throughput_tokens_per_sec\nFROM llm_telemetry\nWHERE timestamp >= NOW() - INTERVAL '24 hours'\nGROUP BY model_name, tenant_id\nORDER BY model_name, tenant_id",
        "options": {},
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "063237ff-ce84-45ba-b6f3-3a47110e6020",
      "name": "Generate Governance Report Data",
      "type": "n8n-nodes-base.postgres",
      "position": [
        5408,
        1840
      ],
      "parameters": {
        "query": "SELECT \n  DATE_TRUNC('day', t.timestamp) as report_date,\n  COUNT(*) as total_requests,\n  COUNT(CASE WHEN t.policy_violations > 0 THEN 1 END) as policy_violation_count,\n  AVG(t.policy_violations) as avg_policy_violations,\n  COUNT(CASE WHEN r.contains_sensitive_data = true THEN 1 END) as sensitive_data_requests,\n  COUNT(CASE WHEN t.compliance_check_passed = true THEN 1 END) as compliant_requests,\n  COUNT(CASE WHEN t.compliance_check_passed = false THEN 1 END) as non_compliant_requests,\n  ROUND(COUNT(CASE WHEN t.compliance_check_passed = true THEN 1 END)::numeric / COUNT(*)::numeric * 100, 2) as compliance_rate,\n  COUNT(DISTINCT t.user_id) as unique_users,\n  COUNT(DISTINCT t.request_id) as unique_requests,\n  JSONB_AGG(DISTINCT t.policy_applied) as policies_applied,\n  COUNT(CASE WHEN t.audit_logged = true THEN 1 END) as audit_trail_entries\nFROM llm_telemetry t\nLEFT JOIN llm_results r ON t.request_id = r.request_id\nWHERE t.timestamp >= NOW() - INTERVAL '30 days'\nGROUP BY DATE_TRUNC('day', t.timestamp)\nORDER BY report_date DESC",
        "options": {},
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "b6a2d5fe-c948-4949-bf7a-3cd8e9bf9e3a",
      "name": "Merge Report Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        5632,
        1456
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "caa7a9e3-d223-4116-9e49-a792054785e0",
      "name": "Format Report",
      "type": "n8n-nodes-base.code",
      "position": [
        5856,
        1456
      ],
      "parameters": {
        "jsCode": "// Format Report - Generate HTML report with cost, performance, governance sections\n\nconst costData = $input.first().json.costData || {};\nconst performanceData = $input.first().json.performanceData || {};\nconst governanceData = $input.first().json.governanceData || {};\n\n// Helper function to format currency\nfunction formatCurrency(amount) {\n  return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount || 0);\n}\n\n// Helper function to format percentage\nfunction formatPercent(value) {\n  return `${(value * 100).toFixed(2)}%`;\n}\n\n// Generate HTML report\nconst htmlReport = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Multi-Cloud LLM Orchestration Report</title>\n  <style>\n    body {\n      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n      line-height: 1.6;\n      color: #333;\n      max-width: 1200px;\n      margin: 0 auto;\n      padding: 20px;\n      background-color: #f5f5f5;\n    }\n    .header {\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      color: white;\n      padding: 30px;\n      border-radius: 10px;\n      margin-bottom: 30px;\n      box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n    }\n    .header h1 {\n      margin: 0;\n      font-size: 2.5em;\n    }\n    .header p {\n      margin: 10px 0 0 0;\n      opacity: 0.9;\n    }\n    .section {\n      background: white;\n      padding: 25px;\n      margin-bottom: 20px;\n      border-radius: 8px;\n      box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n    }\n    .section h2 {\n      color: #667eea;\n      border-bottom: 3px solid #667eea;\n      padding-bottom: 10px;\n      margin-top: 0;\n    }\n    .metric-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n      gap: 20px;\n      margin: 20px 0;\n    }\n    .metric-card {\n      background: #f8f9fa;\n      padding: 20px;\n      border-radius: 6px;\n      border-left: 4px solid #667eea;\n    }\n    .metric-card h3 {\n      margin: 0 0 10px 0;\n      font-size: 0.9em;\n      color: #666;\n      text-transform: uppercase;\n    }\n    .metric-card .value {\n      font-size: 2em;\n      font-weight: bold;\n      color: #333;\n    }\n    .metric-card .change {\n      font-size: 0.9em;\n      margin-top: 5px;\n    }\n    .change.positive { color: #28a745; }\n    .change.negative { color: #dc3545; }\n    table {\n      width: 100%;\n      border-collapse: collapse;\n      margin: 20px 0;\n    }\n    th, td {\n      padding: 12px;\n      text-align: left;\n      border-bottom: 1px solid #ddd;\n    }\n    th {\n      background-color: #667eea;\n      color: white;\n      font-weight: 600;\n    }\n    tr:hover {\n      background-color: #f5f5f5;\n    }\n    .chart-placeholder {\n      background: #f8f9fa;\n      padding: 40px;\n      text-align: center;\n      border-radius: 6px;\n      border: 2px dashed #ddd;\n      margin: 20px 0;\n    }\n    .executive-summary {\n      background: #fff3cd;\n      border-left: 4px solid #ffc107;\n      padding: 20px;\n      margin: 20px 0;\n      border-radius: 6px;\n    }\n    .footer {\n      text-align: center;\n      padding: 20px;\n      color: #666;\n      font-size: 0.9em;\n    }\n    .status-badge {\n      display: inline-block;\n      padding: 4px 12px;\n      border-radius: 12px;\n      font-size: 0.85em;\n      font-weight: 600;\n    }\n    .status-badge.success { background: #d4edda; color: #155724; }\n    .status-badge.warning { background: #fff3cd; color: #856404; }\n    .status-badge.danger { background: #f8d7da; color: #721c24; }\n  </style>\n</head>\n<body>\n  <div class=\"header\">\n    <h1>\ud83d\ude80 Multi-Cloud LLM Orchestration Report</h1>\n    <p>Generated on ${new Date().toLocaleString()}</p>\n  </div>\n\n  <!-- Executive Summary -->\n  <div class=\"section executive-summary\">\n    <h2>\ud83d\udcca Executive Summary</h2>\n    <p><strong>Total Requests:</strong> ${costData.totalRequests || 0}</p>\n    <p><strong>Total Cost:</strong> ${formatCurrency(costData.totalCost)}</p>\n    <p><strong>Average Response Time:</strong> ${performanceData.avgResponseTime || 0}ms</p>\n    <p><strong>Compliance Score:</strong> ${formatPercent(governanceData.complianceScore || 0)}</p>\n    <p><strong>Key Insight:</strong> The system processed ${costData.totalRequests || 0} requests across multiple cloud providers with an average cost per request of ${formatCurrency((costData.totalCost || 0) / (costData.totalRequests || 1))}.</p>\n  </div>\n\n  <!-- Cost Analysis Section -->\n  <div class=\"section\">\n    <h2>\ud83d\udcb0 Cost Analysis</h2>\n    <div class=\"metric-grid\">\n      <div class=\"metric-card\">\n        <h3>Total Cost</h3>\n        <div class=\"value\">${formatCurrency(costData.totalCost)}</div>\n        <div class=\"change positive\">\u2193 ${costData.costReduction || 0}% vs last period</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Cost per Request</h3>\n        <div class=\"value\">${formatCurrency((costData.totalCost || 0) / (costData.totalRequests || 1))}</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Most Economical Provider</h3>\n        <div class=\"value\">${costData.cheapestProvider || 'N/A'}</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Cost Savings</h3>\n        <div class=\"value\">${formatCurrency(costData.savings)}</div>\n        <div class=\"change positive\">Through intelligent routing</div>\n      </div>\n    </div>\n\n    <h3>Cost Breakdown by Provider</h3>\n    <table>\n      <thead>\n        <tr>\n          <th>Provider</th>\n          <th>Requests</th>\n          <th>Total Cost</th>\n          <th>Avg Cost/Request</th>\n          <th>% of Total</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>Azure OpenAI</td>\n          <td>${costData.azureRequests || 0}</td>\n          <td>${formatCurrency(costData.azureCost)}</td>\n          <td>${formatCurrency((costData.azureCost || 0) / (costData.azureRequests || 1))}</td>\n          <td>${formatPercent((costData.azureCost || 0) / (costData.totalCost || 1))}</td>\n        </tr>\n        <tr>\n          <td>AWS Bedrock</td>\n          <td>${costData.awsRequests || 0}</td>\n          <td>${formatCurrency(costData.awsCost)}</td>\n          <td>${formatCurrency((costData.awsCost || 0) / (costData.awsRequests || 1))}</td>\n          <td>${formatPercent((costData.awsCost || 0) / (costData.totalCost || 1))}</td>\n        </tr>\n        <tr>\n          <td>Google Vertex AI</td>\n          <td>${costData.googleRequests || 0}</td>\n          <td>${formatCurrency(costData.googleCost)}</td>\n          <td>${formatCurrency((costData.googleCost || 0) / (costData.googleRequests || 1))}</td>\n          <td>${formatPercent((costData.googleCost || 0) / (costData.totalCost || 1))}</td>\n        </tr>\n        <tr>\n          <td>Local Model</td>\n          <td>${costData.localRequests || 0}</td>\n          <td>${formatCurrency(costData.localCost)}</td>\n          <td>${formatCurrency((costData.localCost || 0) / (costData.localRequests || 1))}</td>\n          <td>${formatPercent((costData.localCost || 0) / (costData.totalCost || 1))}</td>\n        </tr>\n      </tbody>\n    </table>\n\n    <div class=\"chart-placeholder\">\n      \ud83d\udcc8 Cost Trend Chart (Last 30 Days)\n      <br><small>Chart visualization would be rendered here</small>\n    </div>\n  </div>\n\n  <!-- Performance Metrics Section -->\n  <div class=\"section\">\n    <h2>\u26a1 Performance Metrics</h2>\n    <div class=\"metric-grid\">\n      <div class=\"metric-card\">\n        <h3>Average Response Time</h3>\n        <div class=\"value\">${performanceData.avgResponseTime || 0}ms</div>\n        <div class=\"change positive\">\u2193 ${performanceData.responseTimeImprovement || 0}% faster</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Success Rate</h3>\n        <div class=\"value\">${formatPercent(performanceData.successRate || 0)}</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>P95 Latency</h3>\n        <div class=\"value\">${performanceData.p95Latency || 0}ms</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Fastest Provider</h3>\n        <div class=\"value\">${performanceData.fastestProvider || 'N/A'}</div>\n      </div>\n    </div>\n\n    <h3>Performance by Provider</h3>\n    <table>\n      <thead>\n        <tr>\n          <th>Provider</th>\n          <th>Avg Response Time</th>\n          <th>Success Rate</th>\n          <th>Error Rate</th>\n          <th>Uptime</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>Azure OpenAI</td>\n          <td>${performanceData.azureAvgTime || 0}ms</td>\n          <td>${formatPercent(performanceData.azureSuccessRate || 0)}</td>\n          <td>${formatPercent(performanceData.azureErrorRate || 0)}</td>\n          <td><span class=\"status-badge success\">${formatPercent(performanceData.azureUptime || 0.99)}</span></td>\n        </tr>\n        <tr>\n          <td>AWS Bedrock</td>\n          <td>${performanceData.awsAvgTime || 0}ms</td>\n          <td>${formatPercent(performanceData.awsSuccessRate || 0)}</td>\n          <td>${formatPercent(performanceData.awsErrorRate || 0)}</td>\n          <td><span class=\"status-badge success\">${formatPercent(performanceData.awsUptime || 0.99)}</span></td>\n        </tr>\n        <tr>\n          <td>Google Vertex AI</td>\n          <td>${performanceData.googleAvgTime || 0}ms</td>\n          <td>${formatPercent(performanceData.googleSuccessRate || 0)}</td>\n          <td>${formatPercent(performanceData.googleErrorRate || 0)}</td>\n          <td><span class=\"status-badge success\">${formatPercent(performanceData.googleUptime || 0.99)}</span></td>\n        </tr>\n        <tr>\n          <td>Local Model</td>\n          <td>${performanceData.localAvgTime || 0}ms</td>\n          <td>${formatPercent(performanceData.localSuccessRate || 0)}</td>\n          <td>${formatPercent(performanceData.localErrorRate || 0)}</td>\n          <td><span class=\"status-badge success\">${formatPercent(performanceData.localUptime || 0.99)}</span></td>\n        </tr>\n      </tbody>\n    </table>\n\n    <div class=\"chart-placeholder\">\n      \ud83d\udcca Response Time Distribution Chart\n      <br><small>Chart visualization would be rendered here</small>\n    </div>\n  </div>\n\n  <!-- Governance & Compliance Section -->\n  <div class=\"section\">\n    <h2>\ud83d\udd12 Governance & Compliance</h2>\n    <div class=\"metric-grid\">\n      <div class=\"metric-card\">\n        <h3>Overall Compliance Score</h3>\n        <div class=\"value\">${formatPercent(governanceData.complianceScore || 0)}</div>\n        <div class=\"change positive\">Meeting all requirements</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Policy Violations</h3>\n        <div class=\"value\">${governanceData.violations || 0}</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Data Residency Compliance</h3>\n        <div class=\"value\">${formatPercent(governanceData.dataResidencyCompliance || 0)}</div>\n      </div>\n      <div class=\"metric-card\">\n        <h3>Security Incidents</h3>\n        <div class=\"value\">${governanceData.securityIncidents || 0}</div>\n      </div>\n    </div>\n\n    <h3>Compliance Checklist</h3>\n    <table>\n      <thead>\n        <tr>\n          <th>Requirement</th>\n          <th>Status</th>\n          <th>Last Checked</th>\n          <th>Notes</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>Data Encryption at Rest</td>\n          <td><span class=\"status-badge success\">Compliant</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>All providers use AES-256</td>\n        </tr>\n        <tr>\n          <td>Data Encryption in Transit</td>\n          <td><span class=\"status-badge success\">Compliant</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>TLS 1.3 enforced</td>\n        </tr>\n        <tr>\n          <td>Access Control & Authentication</td>\n          <td><span class=\"status-badge success\">Compliant</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>OAuth 2.0 + MFA enabled</td>\n        </tr>\n        <tr>\n          <td>Audit Logging</td>\n          <td><span class=\"status-badge success\">Compliant</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>All requests logged</td>\n        </tr>\n        <tr>\n          <td>Data Residency (EU)</td>\n          <td><span class=\"status-badge ${governanceData.euCompliance ? 'success' : 'warning'}\">${governanceData.euCompliance ? 'Compliant' : 'Review Required'}</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>${governanceData.euCompliance ? 'EU regions only' : 'Some requests routed outside EU'}</td>\n        </tr>\n        <tr>\n          <td>PII Detection & Masking</td>\n          <td><span class=\"status-badge success\">Compliant</span></td>\n          <td>${new Date().toLocaleDateString()}</td>\n          <td>Automated PII detection active</td>\n        </tr>\n      </tbody>\n    </table>\n\n    <div class=\"chart-placeholder\">\n      \ud83d\udee1\ufe0f Compliance Score Trend\n      <br><small>Chart visualization would be rendered here</small>\n    </div>\n  </div>\n\n  <!-- Recommendations Section -->\n  <div class=\"section\">\n    <h2>\ud83d\udca1 Recommendations</h2>\n    <ul>\n      <li><strong>Cost Optimization:</strong> Consider increasing usage of ${costData.cheapestProvider || 'the most economical provider'} for non-critical workloads to reduce costs by an estimated ${formatPercent(0.15)}.</li>\n      <li><strong>Performance:</strong> ${performanceData.fastestProvider || 'The fastest provider'} shows best response times. Route time-sensitive requests there when possible.</li>\n      <li><strong>Reliability:</strong> Implement additional circuit breaker thresholds for providers with error rates above ${formatPercent(0.05)}.</li>\n      <li><strong>Governance:</strong> ${governanceData.violations > 0 ? `Address ${governanceData.violations} policy violation(s) identified in this period.` : 'Maintain current governance practices - no violations detected.'}</li>\n      <li><strong>Capacity Planning:</strong> Based on current growth trends, consider scaling infrastructure by ${formatPercent(0.20)} in the next quarter.</li>\n    </ul>\n  </div>\n\n  <div class=\"footer\">\n    <p>This report was automatically generated by the Multi-Cloud LLM Orchestration System</p>\n    <p>For questions or concerns, contact the AI Operations team</p>\n  </div>\n</body>\n</html>\n`;\n\nreturn {\n  json: {\n    htmlReport: htmlReport,\n    reportGenerated: new Date().toISOString(),\n    reportType: 'comprehensive',\n    sections: ['executive_summary', 'cost_analysis', 'performance_metrics', 'governance_compliance', 'recommendations']\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e1368e94-ea5c-40a3-a1ca-1e21be63072f",
      "name": "Email Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        6080,
        1456
      ],
      "parameters": {
        "html": "={{ $json.reportHtml }}",
        "options": {},
        "subject": "=LLM Orchestration Report - {{ new Date().toISOString().split(\"T\")[0] }}",
        "toEmail": "<__PLACEHOLDER_VALUE__Report recipient email addresses__>",
        "fromEmail": "<__PLACEHOLDER_VALUE__Sender email address__>"
      },
      "typeVersion": 2.1
    },
    {
      "id": "cbf1d35c-752b-4286-828e-5c561977f0e4",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.set",
      "position": [
        5408,
        2032
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "success",
              "type": "boolean",
              "value": true
            },
            {
              "id": "id-2",
              "name": "requestId",
              "type": "string",
              "value": "={{ $json.request_id }}"
            },
            {
              "id": "id-3",
              "name": "response",
              "type": "string",
              "value": "={{ $json.response }}"
            },
            {
              "id": "id-4",
              "name": "modelUsed",
              "type": "string",
              "value": "={{ $json.model_provider }}"
            },
            {
              "id": "id-5",
              "name": "latency",
              "type": "number",
              "value": "={{ $json.latency_ms }}"
            },
            {
              "id": "id-6",
              "name": "tokens",
              "type": "number",
              "value": "={{ $json.total_tokens }}"
            },
            {
              "id": "id-7",
              "name": "cost",
              "type": "number",
              "value": "={{ $json.cost }}"
            },
            {
              "id": "id-8",
              "name": "confidenceScore",
              "type": "number",
              "value": "={{ $json.confidence_score }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "cd9529fb-c46c-4411-a1af-86dc92174a34",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        944
      ],
      "parameters": {
        "width": 1072,
        "height": 192,
        "content": "\n## How It Works\nThis workflow automates end-to-end research analysis by coordinating multiple AI models\u2014including NVIDIA NIM (Llama), OpenAI GPT-4, and Claude to analyze uploaded documents, extract insights, and generate polished reports delivered via email. Built for researchers, academics, and business analysts, it enables fast, accurate synthesis of information from multiple sources. The workflow eliminates the manual burden of document review, cross-referencing, and report compilation by running parallel AI analyses, aggregating and validating model outputs, and producing structured, publication-ready documents in minutes instead of hours. Data flows from Google Sheets (user input) through document extraction, parallel AI processing, response aggregation, quality validation, structured storage in Google Sheets, automated report formatting, and final delivery via Gmail with attachments."
      },
      "typeVersion": 1
    },
    {
      "id": "780f1d2d-189d-41be-b013-57d4beb29c87",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2208,
        912
      ],
      "parameters": {
        "width": 496,
        "height": 224,
        "content": "## Setup Steps\n1. Configure API credentials \n2. Add OpenAI API key with GPT-4 access enabled\n3. Connect Anthropic Claude API credentials\n4. Set up Google Sheets integration with read/write permissions\n5. Configure Gmail credentials with OAuth2 authentication for automated email\n6. Customize email templates and report formatting preferences "
      },
      "typeVersion": 1
    },
    {
      "id": "fa1912c9-84dd-4c0a-a757-a5b1f3b9501f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2768,
        816
      ],
      "parameters": {
        "color": 4,
        "width": 544,
        "height": 336,
        "content": "## Prerequisites\nNVIDIA NIM API access, OpenAI API key (GPT-4 enabled), Anthropic Claude API key\n## Use Cases\nAcademic literature reviews, competitive intelligence reports\n## Customization\nAdjust AI model parameters (temperature, tokens) per analysis depth needs\n## Benefits\nReduces research analysis time by 80%, eliminates single-source bias through multi-model consensus"
      },
      "typeVersion": 1
    },
    {
      "id": "ac573813-96df-413f-9a54-89f435619876",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5088,
        1200
      ],
      "parameters": {
        "color": 7,
        "width": 1184,
        "height": 1232,
        "content": "## Automated Report Formatting & Distribution\n**Why:** Generates professional HTML/PDF reports with structured sections and delivers via Gmail, providing immediate, shareable documentation."
      },
      "typeVersion": 1
    },
    {
      "id": "0ea2bd76-c7d8-475c-b926-69212ea451a7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        1448
      ],
      "parameters": {
        "color": 7,
        "width": 1120,
        "height": 672,
        "content": "## Parallel Multi-Model Analysis\n**Why:** Leverages diverse AI model strengths (NVIDIA for speed, GPT-4 for reasoning, Claude for nuance) to generate comprehensive, balanced insights and reduce single-model bias."
      },
      "typeVersion": 1
    },
    {
      "id": "455052ef-7379-4c38-a99e-89277e31eedd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        1624
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 496,
        "content": "## Document Ingestion & Preprocessing\n**Why:** Extracts text from uploaded PDFs/documents and structures data for AI consumption, ensuring consistent input format across all processing models."
      },
      "typeVersion": 1
    },
    {
      "id": "80855b23-5b3d-402f-89a4-4025dcd74cee",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2912,
        1200
      ],
      "parameters": {
        "color": 7,
        "width": 2112,
        "height": 1088,
        "content": "## Response Aggregation & Validation\n**Why:** Combines model outputs, identifies consensus findings, flags discrepancies, and ensures accuracy before final report compilation."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "11065fd6-ab04-4499-9bba-5d5accb16807",
  "connections": {
    "Format Report": {
      "main": [
        [
          {
            "node": "Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call AWS Bedrock": {
      "main": [
        [
          {
            "node": "Handle Circuit Breaker",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Model Responses",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Call Local Model": {
      "main": [
        [
          {
            "node": "Handle Circuit Breaker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Anomalies": {
      "main": [
        [
          {
            "node": "Check Anomaly Threshold",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Output": {
      "main": [
        [
          {
            "node": "Calculate Telemetry Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Azure OpenAI": {
      "main": [
        [
          {
            "node": "Handle Circuit Breaker",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Model Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Report Data": {
      "main": [
        [
          {
            "node": "Format Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Retry Needed": {
      "main": [
        [
          {
            "node": "Adaptive Retry Delay",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Model Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM Request Webhook": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Health Checks": {
      "main": [
        [
          {
            "node": "Score & Rank Models",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score & Rank Models": {
      "main": [
        [
          {
            "node": "Check Policy Constraints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Adaptive Retry Delay": {
      "main": [
        [
          {
            "node": "Route to Selected Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply Policy Routing": {
      "main": [
        [
          {
            "node": "Route to Selected Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Google Vertex AI": {
      "main": [
        [
          {
            "node": "Handle Circuit Breaker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Model Responses": {
      "main": [
        [
          {
            "node": "Normalize Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Circuit Breaker": {
      "main": [
        [
          {
            "node": "Check Retry Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rewrite Prompt for AWS": {
      "main": [
        [
          {
            "node": "Call AWS Bedrock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Parse Request & Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Anomaly Threshold": {
      "main": [
        [
          {
            "node": "Send Anomaly Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route to Selected Model": {
      "main": [
        [
          {
            "node": "Rewrite Prompt for Azure",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rewrite Prompt for AWS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rewrite Prompt for Google",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rewrite Prompt for Local",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check AWS Bedrock Health": {
      "main": [
        [
          {
            "node": "Merge Health Checks",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Check Policy Constraints": {
      "main": [
        [
          {
            "node": "Apply Policy Routing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Route to Selected Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Request & Validate": {
      "main": [
        [
          {
            "node": "Analyze Prompt Complexity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rewrite Prompt for Azure": {
      "main": [
        [
          {
            "node": "Call Azure OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rewrite Prompt for Local": {
      "main": [
        [
          {
            "node": "Call Local Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Prompt Complexity": {
      "main": [
        [
          {
            "node": "Check Azure OpenAI Health",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check AWS Bedrock Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assess Hallucination Risk": {
      "main": [
        [
          {
            "node": "Calculate Confidence Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Azure OpenAI Health": {
      "main": [
        [
          {
            "node": "Merge Health Checks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Cost Report Data": {
      "main": [
        [
          {
            "node": "Merge Report Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Telemetry to Database": {
      "main": [
        [
          {
            "node": "Generate Cost Report Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Performance Report Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Governance Report Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rewrite Prompt for Google": {
      "main": [
        [
          {
            "node": "Call Google Vertex AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Result in Datastore": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Confidence Score": {
      "main": [
        [
          {
            "node": "Log Telemetry to Database",
            "type": "main",
            "index": 0
          },
          {
            "node": "Store Result in Datastore",
            "type": "main",
            "index": 0
          },
          {
            "node": "Detect Anomalies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Telemetry Metrics": {
      "main": [
        [
          {
            "node": "Assess Hallucination Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Performance Report Data": {
      "main": [
        [
          {
            "node": "Merge Report Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}