{
  "id": "7BH4qa8EUakMmnlT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Consensus Engine: 4 Models, 1 Trusted Answer",
  "tags": [],
  "nodes": [
    {
      "id": "f8513ac8-1f67-43b9-9d8f-1b4e0554f308",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1968,
        992
      ],
      "parameters": {
        "width": 496,
        "height": 768,
        "content": "## AI Consensus Engine: 4 Models, 1 Trusted Answer\nThink of this as a panel of experts that actually checks each other's work. Instead of trusting one AI's answer blindly, the system cross-examines multiple models and calls out the ones that are bluffing.\n\n### How it works\n\nAsk: Your question goes to four LLMs in parallel, each self-reporting its confidence.\nCompare: Dual similarity analysis (Jaccard + Cosine) measures how much the answers actually agree.\nCalibrate: Overconfident outliers get penalized. Underconfident models matching consensus get boosted.\nDeliver: Strong agreement returns a single weighted answer. True disagreement switches to peer review mode showing every perspective.\n\n### Setup\n\n- [ ] Add API credentials for the four LLM providers ( OpenAI, Anthropic, Google Gemini, Groq)\n- [ ] Activate the workflow and open the chat window\n- [ ] Type any question and wait for the consensus analysis to come back\n\n### Customization\nSwap any LLM for another or add more parallel branches. Adjust similarity weights in the Similarity Analysis node. Change agreement tier thresholds in the Format Chat Message node. Replace the chat trigger with a webhook, Slack command, or any other entry point."
      },
      "typeVersion": 1
    },
    {
      "id": "2fc46ee0-0a8b-4153-9967-fea815845acf",
      "name": "Groq Chat Model3",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "onError": "continueRegularOutput",
      "position": [
        -656,
        2160
      ],
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {}
      },
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3a55a608-4a8c-49cd-920e-ba7cf9bbb4c1",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1008,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 928,
        "height": 1872,
        "content": "## Parallel LLM Generation\n\nEach model answers independently with confidence rating"
      },
      "typeVersion": 1
    },
    {
      "id": "623b21cc-381b-4441-992e-c376c8b49962",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        1104
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 480,
        "content": "## Calibration Engine\n\nDetects overconfident outliers and underconfident consensus"
      },
      "typeVersion": 1
    },
    {
      "id": "628254df-a7ba-4541-b08a-105896589d36",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        1056
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 576,
        "content": "## Consensus or Fallback\n\nWeighted average if consensus exists, peer review if extreme divergence"
      },
      "typeVersion": 1
    },
    {
      "id": "fc013140-4191-4c12-b901-aa244810c0b9",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -1376,
        1328
      ],
      "parameters": {
        "public": true,
        "options": {
          "responseMode": "responseNodes"
        },
        "initialMessages": "Hi there! \ud83d\udc4b\nAsk me any question and I'll analyze it using 4 AI models!"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ab784aaf-fac4-4eb3-af21-617704a5eaef",
      "name": "Chat",
      "type": "@n8n/n8n-nodes-langchain.chat",
      "position": [
        2224,
        1328
      ],
      "parameters": {
        "message": "={{ $json.chatResponse }}",
        "options": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "2fed4605-3342-4080-9d63-252aefb0e14b",
      "name": "Parse & Validate Responses",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        1328
      ],
      "parameters": {
        "jsCode": "// Extracts each LLM's answer and confidence from raw output, validates the JSON structure, and flags invalid responses.\n\nconst allItems = $input.all();\nconst parsedResponses = [];\n\nfor (let i = 0; i < allItems.length; i++) {\n  const item = allItems[i];\n\n  let modelName = `Model ${i + 1}`;\n\n  try {\n    const rawNodeName = item.json?.$node?.name || \"\";\n    const cleanedNodeName = rawNodeName\n      .replace(/^LLM \\d+ - /, \"\")\n      .replace(/\\d+$/, \"\")\n      .trim();\n\n    if (cleanedNodeName.length >= 3) {\n      modelName = cleanedNodeName;\n    }\n  } catch (nameError) {\n    modelName = `Model ${i + 1}`;\n  }\n\n  try {\n    const rawOutput = item.json.output || item.json.text || \"\";\n    let cleanedOutput = rawOutput.trim();\n\n    if (cleanedOutput.startsWith(\"```json\")) cleanedOutput = cleanedOutput.slice(7);\n    if (cleanedOutput.startsWith(\"```\")) cleanedOutput = cleanedOutput.slice(3);\n    if (cleanedOutput.endsWith(\"```\")) cleanedOutput = cleanedOutput.slice(0, -3);\n    cleanedOutput = cleanedOutput.trim();\n\n    const parsedJson = JSON.parse(cleanedOutput);\n\n    const hasValidAnswer = parsedJson.answer && typeof parsedJson.answer === \"string\";\n    const hasValidConfidence = typeof parsedJson.confidence === \"number\";\n\n    if (!hasValidAnswer || !hasValidConfidence) {\n      throw new Error(\"Invalid response structure: missing answer or confidence\");\n    }\n\n    const clampedConfidence = Math.max(0, Math.min(1, parsedJson.confidence));\n\n    parsedResponses.push({\n      model: modelName,\n      answer: parsedJson.answer.trim(),\n      originalConfidence: clampedConfidence,\n      reasoning: parsedJson.reasoning || \"\",\n      valid: true,\n    });\n  } catch (parseError) {\n    parsedResponses.push({\n      model: modelName,\n      answer: \"Parse error\",\n      originalConfidence: 0.0,\n      reasoning: parseError.message,\n      valid: false,\n    });\n  }\n}\n\nconst validResponseCount = parsedResponses.filter((r) => r.valid).length;\n\nreturn {\n  json: {\n    responses: parsedResponses,\n    validCount: validResponseCount,\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "ef6b2f68-1d25-4cb0-98e3-8c001c1d667f",
      "name": "Similarity Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        560,
        1328
      ],
      "parameters": {
        "jsCode": "// Compares every valid LLM response using both Jaccard and Cosine similarity for accurate agreement detection, then attaches an average similarity score to each.\n\nconst allResponses = $input.first().json.responses;\nconst validResponses = allResponses.filter((r) => r.valid);\n\nif (validResponses.length < 2) {\n  return {\n    json: {\n      responses: validResponses,\n      similarityMatrix: [],\n      error: \"Not enough valid responses to compare\",\n    },\n  };\n}\n\nfunction normalizeAnswer(text) {\n  return text\n    .toLowerCase()\n    .replace(/[.,!?;:'\"()\\-]/g, \"\")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\nfunction getWordFrequency(text) {\n  const words = text.split(/\\s+/);\n  const frequency = {};\n\n  for (const word of words) {\n    frequency[word] = (frequency[word] || 0) + 1;\n  }\n\n  return frequency;\n}\n\nfunction getJaccardSimilarity(textA, textB) {\n  const wordsA = new Set(textA.split(/\\s+/));\n  const wordsB = new Set(textB.split(/\\s+/));\n\n  const sharedWords = new Set([...wordsA].filter((word) => wordsB.has(word)));\n  const allWords = new Set([...wordsA, ...wordsB]);\n\n  return sharedWords.size / allWords.size;\n}\n\nfunction getCosineSimilarity(textA, textB) {\n  const frequencyA = getWordFrequency(textA);\n  const frequencyB = getWordFrequency(textB);\n\n  const allUniqueWords = new Set([\n    ...Object.keys(frequencyA),\n    ...Object.keys(frequencyB),\n  ]);\n\n  let dotProduct = 0;\n  let magnitudeA = 0;\n  let magnitudeB = 0;\n\n  for (const word of allUniqueWords) {\n    const countA = frequencyA[word] || 0;\n    const countB = frequencyB[word] || 0;\n\n    dotProduct += countA * countB;\n    magnitudeA += countA * countA;\n    magnitudeB += countB * countB;\n  }\n\n  const magnitude = Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB);\n\n  if (magnitude === 0) return 0;\n\n  return dotProduct / magnitude;\n}\n\nfunction getCombinedSimilarity(textA, textB) {\n  const normalizedA = normalizeAnswer(textA);\n  const normalizedB = normalizeAnswer(textB);\n\n  const shorter = normalizedA.length <= normalizedB.length ? normalizedA : normalizedB;\n  const longer = normalizedA.length > normalizedB.length ? normalizedA : normalizedB;\n\n  if (shorter.length > 0 && longer.includes(shorter)) {\n    return 0.95;\n  }\n\n  const firstSentenceA = normalizedA.split(/[.!?]/)[0].trim();\n  const firstSentenceB = normalizedB.split(/[.!?]/)[0].trim();\n\n  const firstSentenceSimilarity = getCosineSimilarity(firstSentenceA, firstSentenceB);\n\n  const jaccardScore = getJaccardSimilarity(normalizedA, normalizedB);\n  const cosineScore = getCosineSimilarity(normalizedA, normalizedB);\n\n  const JACCARD_WEIGHT = 0.25;\n  const COSINE_WEIGHT = 0.45;\n  const FIRST_SENTENCE_WEIGHT = 0.30;\n\n  return (jaccardScore * JACCARD_WEIGHT) + (cosineScore * COSINE_WEIGHT) + (firstSentenceSimilarity * FIRST_SENTENCE_WEIGHT);\n}\n\nconst similarityMatrix = [];\n\nfor (let i = 0; i < validResponses.length; i++) {\n  const rowScores = [];\n\n  for (let j = 0; j < validResponses.length; j++) {\n    if (i === j) {\n      rowScores.push(1.0);\n    } else {\n      const score = getCombinedSimilarity(\n        validResponses[i].answer,\n        validResponses[j].answer\n      );\n      rowScores.push(score);\n    }\n  }\n\n  similarityMatrix.push(rowScores);\n}\n\nconst averageSimilarityPerResponse = similarityMatrix.map((row) => {\n  const totalExcludingSelf = row.reduce((sum, value) => sum + value, 0) - 1;\n  const otherResponseCount = row.length - 1;\n  return totalExcludingSelf / otherResponseCount;\n});\n\nconst responsesWithSimilarity = validResponses.map((response, index) => ({\n  ...response,\n  avgSimilarityToOthers: averageSimilarityPerResponse[index],\n}));\n\nreturn {\n  json: {\n    responses: responsesWithSimilarity,\n    similarityMatrix: similarityMatrix,\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8a00b00c-0930-4bab-9f03-5b03c8fcbca7",
      "name": "Confidence Calibration",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        1328
      ],
      "parameters": {
        "jsCode": "// Adjusts each model's confidence based on agreement patterns: penalizes overconfident outliers, boosts underconfident consensus matches, and moderately reduces mid-confidence outliers.\n\nconst responses = $input.first().json.responses;\n\nconst HIGH_CONFIDENCE_THRESHOLD = 0.80;\nconst LOW_AGREEMENT_THRESHOLD = 0.30;\nconst STRONG_AGREEMENT_THRESHOLD = 0.50;\nconst MODERATE_DISAGREEMENT_THRESHOLD = 0.30;\nconst PENALTY_MULTIPLIER = 0.70;\n\nconst calibratedResponses = responses.map((response) => {\n  let adjustedConfidence = response.originalConfidence;\n  let adjustmentReason = \"No calibration needed\";\n  let wasCalibrated = false;\n\n  const isHighConfidence = response.originalConfidence >= HIGH_CONFIDENCE_THRESHOLD;\n  const isLowAgreement = response.avgSimilarityToOthers < LOW_AGREEMENT_THRESHOLD;\n  const isLowConfidence = response.originalConfidence < 0.50;\n  const isStrongAgreement = response.avgSimilarityToOthers >= STRONG_AGREEMENT_THRESHOLD;\n  const isMidConfidence = response.originalConfidence >= 0.60;\n  const isModerateDisagreement = response.avgSimilarityToOthers < MODERATE_DISAGREEMENT_THRESHOLD;\n\n  if (isHighConfidence && isLowAgreement) {\n    adjustedConfidence = Math.min(response.originalConfidence, 0.40);\n    adjustmentReason = \"Overconfident outlier: High self-reported confidence but low agreement with other models. Likely hallucination, confidence reduced.\";\n    wasCalibrated = true;\n  } else if (isLowConfidence && isStrongAgreement) {\n    adjustedConfidence = Math.max(response.originalConfidence, 0.65);\n    adjustmentReason = \"Consensus boost: Low self-confidence but strong agreement with other models. Confidence increased.\";\n    wasCalibrated = true;\n  } else if (isMidConfidence && isModerateDisagreement) {\n    adjustedConfidence = response.originalConfidence * PENALTY_MULTIPLIER;\n    adjustmentReason = \"Moderate outlier: Disagreement with consensus detected. Confidence reduced moderately.\";\n    wasCalibrated = true;\n  }\n\n  return {\n    ...response,\n    calibratedConfidence: Math.round(adjustedConfidence * 100) / 100,\n    calibrated: wasCalibrated,\n    calibrationReason: adjustmentReason,\n  };\n});\n\nconst totalSimilarity = responses.reduce((sum, r) => sum + r.avgSimilarityToOthers, 0);\nconst averageSimilarity = totalSimilarity / responses.length;\nconst hasStrongConsensus = averageSimilarity >= STRONG_AGREEMENT_THRESHOLD;\nconst hasExtremeDivergence = averageSimilarity < 0.10;\n\nreturn {\n  json: {\n    responses: calibratedResponses,\n    consensusMetrics: {\n      avgSimilarity: Math.round(averageSimilarity * 100) / 100,\n      hasStrongConsensus: hasStrongConsensus,\n      hasExtremeDivergence: hasExtremeDivergence,\n    },\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "284a9008-3bd8-4130-842a-4e5cf6cb7b66",
      "name": "Weighted Consensus",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        1424
      ],
      "parameters": {
        "jsCode": "// Ranks all responses by calibrated confidence weight and picks the top answer as primary consensus, collecting minority views above 15% weight.\n\nconst responses = $input.first().json.responses;\nconst consensusMetrics = $input.first().json.consensusMetrics;\n\nconst totalCalibratedConfidence = responses.reduce((sum, r) => sum + r.calibratedConfidence, 0);\n\nconst responsesWithWeights = responses.map((response) => ({\n  ...response,\n  weight: totalCalibratedConfidence > 0\n    ? response.calibratedConfidence / totalCalibratedConfidence\n    : 1 / responses.length,\n}));\n\nresponsesWithWeights.sort((a, b) => b.weight - a.weight);\n\nconst topWeightedResponse = responsesWithWeights[0];\nconst remainingResponses = responsesWithWeights.slice(1);\nconst significantMinorityViews = remainingResponses.filter((r) => r.weight > 0.15);\n\nconst calibratedModelCount = responsesWithWeights.filter((r) => r.calibrated).length;\n\nconst consensusSummary = {\n  primaryAnswer: topWeightedResponse.answer,\n  primaryWeight: Math.round(topWeightedResponse.weight * 100),\n  primaryModel: topWeightedResponse.model,\n  consensusStrength: consensusMetrics.avgSimilarity,\n  calibrationsApplied: calibratedModelCount,\n  minorityViews: significantMinorityViews.map((r) => ({\n    answer: r.answer,\n    weight: Math.round(r.weight * 100),\n    model: r.model,\n  })),\n};\n\nreturn {\n  json: {\n    consensusSummary: consensusSummary,\n    allResponses: responsesWithWeights,\n    mode: \"weighted_consensus\",\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8c655fa2-d923-4eb9-b72b-3ec05f2ab071",
      "name": "Peer Review Fallback",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        1232
      ],
      "parameters": {
        "jsCode": "// When models disagree too much for weighted consensus, sorts all responses by original confidence and returns them as individual perspectives for the user to review.\n\nconst responses = $input.first().json.responses;\n\nconst sortedByOriginalConfidence = [...responses].sort(\n  (a, b) => b.originalConfidence - a.originalConfidence\n);\n\nconst formattedPerspectives = sortedByOriginalConfidence.map((response) => ({\n  model: response.model,\n  answer: response.answer,\n  confidence: response.originalConfidence,\n  similarityToOthers: response.avgSimilarityToOthers,\n}));\n\nreturn {\n  json: {\n    peerReviewSummary: {\n      status: \"extreme_divergence_detected\",\n      explanation: \"Models disagree significantly. Showing all perspectives instead of weighted consensus.\",\n      responses: formattedPerspectives,\n    },\n    mode: \"peer_review_fallback\",\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f256d865-3c18-4f82-9612-4e1f27bc5dec",
      "name": "Format Output (chat message)",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        1328
      ],
      "parameters": {
        "jsCode": "// Formats the final JSON result into a readable chat message with tiered agreement display, calibration notes, and per-model breakdowns.\n\nconst inputData = $input.first().json;\nconst consensusMode = inputData.mode;\n\nlet chatMessage = \"\";\n\nif (consensusMode === \"weighted_consensus\") {\n  const answer = inputData.result.answer;\n  const agreementLevel = inputData.result.consensusStrength;\n\n  const agreementPercent = parseInt(agreementLevel);\n  const filledBlocks = Math.floor(agreementPercent / 10);\n  const emptyBlocks = 10 - filledBlocks;\n  const agreementBar = \"\u2588\".repeat(filledBlocks) + \"\u2591\".repeat(emptyBlocks);\n\n  let agreementLabel = \"\";\n\n  if (agreementPercent >= 70) {\n    agreementLabel = \"\u2705 **Strong consensus across all models**\";\n  } else if (agreementPercent >= 40) {\n    agreementLabel = \"\ud83d\udfe1 **Models mostly agree, with some variation in detail**\";\n  } else {\n    agreementLabel = \"\ud83d\udfe0 **Models loosely agree, but answers vary significantly**\";\n  }\n\n  chatMessage = `${agreementLabel}\\n\\n${answer}\\n\\n`;\n  chatMessage += `**Agreement level:** ${agreementBar} ${agreementLevel}\\n`;\n\n  const calibrationCount = inputData.calibrationReport?.calibrationsApplied || 0;\n\n  if (calibrationCount > 0) {\n    const plural = calibrationCount > 1 ? \"s\" : \"\";\n    chatMessage += `\\n\u2699\ufe0f I adjusted ${calibrationCount} model${plural} that seemed overconfident\\n`;\n  }\n\n  const minorityOpinions = inputData.minorityOpinions || [];\n\n  if (minorityOpinions.length > 0) {\n    chatMessage += `\\n**\ud83d\udcad Other perspectives:**\\n`;\n\n    for (const opinion of minorityOpinions) {\n      const maxAnswerLength = 150;\n      const truncatedAnswer = opinion.answer.length > maxAnswerLength\n        ? opinion.answer.substring(0, maxAnswerLength) + \"...\"\n        : opinion.answer;\n      chatMessage += `\u2022 ${truncatedAnswer}\\n`;\n    }\n  }\n} else {\n  chatMessage = `\u26a0\ufe0f **The models don't agree on this one**\\n\\n`;\n  chatMessage += `This usually means the question is:\\n`;\n  chatMessage += `\u2022 Controversial or debatable\\n`;\n  chatMessage += `\u2022 Ambiguous (could mean different things)\\n`;\n  chatMessage += `\u2022 Based on outdated information\\n\\n`;\n  chatMessage += `Here's what each model thinks:\\n\\n`;\n\n  const allPerspectives = inputData.allPerspectives || [];\n\n  for (let i = 0; i < allPerspectives.length; i++) {\n    const perspective = allPerspectives[i];\n    const confidencePercent = Math.round(perspective.confidence * 100);\n\n    let confidenceEmoji = \"\ud83e\udd37\";\n    if (confidencePercent >= 90) confidenceEmoji = \"\ud83d\udcaa\";\n    else if (confidencePercent >= 70) confidenceEmoji = \"\ud83d\udc4d\";\n    else if (confidencePercent >= 50) confidenceEmoji = \"\ud83e\udd14\";\n\n    chatMessage += `${confidenceEmoji} **Model ${i + 1}** (${confidencePercent}% sure)\\n`;\n    chatMessage += `${perspective.answer}\\n\\n`;\n  }\n\n  chatMessage += `---\\n\ud83d\udca1 **My recommendation:** Review all perspectives above and use your judgment.`;\n}\n\nconst failedModels = inputData.failedModels || [];\n\nif (failedModels.length > 0) {\n  const failedNames = failedModels.join(\", \");\n  const plural = failedModels.length > 1 ? \"s\" : \"\";\n  chatMessage += `\\n\\n\u26a0\ufe0f **Note:** ${failedNames} model${plural} failed to respond and was excluded from the analysis.`;\n}\n\nreturn {\n  json: {\n    chatResponse: chatMessage,\n  },\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0dd53837-ab81-486a-be77-593b20203039",
      "name": "Format Final Output",
      "type": "n8n-nodes-base.code",
      "position": [
        1616,
        1328
      ],
      "parameters": {
        "jsCode": "// Builds the structured output object with result summary, calibration report, and full model breakdown depending on whether consensus or peer review mode was used.\n\nconst inputData = $input.first().json;\nconst consensusMode = inputData.mode;\n\nlet finalOutput = {\n  mode: consensusMode,\n  timestamp: new Date().toISOString(),\n};\n\nif (consensusMode === \"weighted_consensus\") {\n  const summary = inputData.consensusSummary;\n  const allModelResponses = inputData.allResponses;\n\n  finalOutput.result = {\n    answer: summary.primaryAnswer,\n    confidence: `${summary.primaryWeight}% (weighted)`,\n    consensusStrength: `${Math.round(summary.consensusStrength * 100)}%`,\n    source: `${summary.primaryModel} (${summary.primaryWeight}% weight)`,\n  };\n\n  finalOutput.minorityOpinions = summary.minorityViews;\n\n  const calibratedModels = allModelResponses.filter((r) => r.calibrated);\n\n  finalOutput.calibrationReport = {\n    calibrationsApplied: summary.calibrationsApplied,\n    details: calibratedModels.map((r) => ({\n      model: r.model,\n      originalConfidence: `${Math.round(r.originalConfidence * 100)}%`,\n      calibratedConfidence: `${Math.round(r.calibratedConfidence * 100)}%`,\n      reason: r.calibrationReason,\n    })),\n  };\n\n  finalOutput.fullBreakdown = allModelResponses.map((r) => ({\n    model: r.model,\n    answer: r.answer,\n    originalConfidence: `${Math.round(r.originalConfidence * 100)}%`,\n    calibratedConfidence: `${Math.round(r.calibratedConfidence * 100)}%`,\n    weight: `${Math.round(r.weight * 100)}%`,\n    similarityToOthers: `${Math.round(r.avgSimilarityToOthers * 100)}%`,\n    wasCalibrated: r.calibrated,\n  }));\n} else {\n  finalOutput.result = {\n    answer: \"Multiple conflicting perspectives - see all responses below\",\n    confidence: \"N/A (extreme divergence)\",\n    consensusStrength: \"< 25%\",\n    source: \"Peer review fallback mode\",\n  };\n\n  finalOutput.allPerspectives = inputData.peerReviewSummary.responses;\n  finalOutput.explanation = inputData.peerReviewSummary.explanation;\n}\n\nfinalOutput.failedModels = (inputData.allResponses || [])\n  .filter((r) => r.originalConfidence === 0 && r.answer === \"Parse error\")\n  .map((r) => r.model);\n\nreturn { json: finalOutput };"
      },
      "typeVersion": 2
    },
    {
      "id": "bdbd9213-9c82-4a72-961f-f6b9f7f17682",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        1104
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 464,
        "content": "## Chat Response\n\nFormats the report into a readable message and sends it to the user"
      },
      "typeVersion": 1
    },
    {
      "id": "aa47a3a4-a663-440a-9847-d741df86ce7c",
      "name": "Merge All 4 LLMs",
      "type": "n8n-nodes-base.merge",
      "position": [
        -224,
        1296
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3
    },
    {
      "id": "6bd18a44-0e5b-4a5f-ac54-b346afe4ab4f",
      "name": "Set User Prompt",
      "type": "n8n-nodes-base.set",
      "position": [
        -1152,
        1328
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "fb70c030-9f27-40d6-8805-41863a150088",
              "name": "userPrompt",
              "type": "string",
              "value": "={{ $json.chatInput }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "07810333-b719-4393-a7e1-c15a553bb078",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "onError": "continueRegularOutput",
      "position": [
        -656,
        848
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "bdb00463-6a77-40ac-a7b8-68ff6b100828",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "onError": "continueRegularOutput",
      "position": [
        -656,
        1248
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {}
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ead472f9-e7b4-4564-9e66-6c1c98316cf6",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "onError": "continueRegularOutput",
      "position": [
        -656,
        1648
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": false,
      "typeVersion": 1.3
    },
    {
      "id": "ddb55708-a6f5-4652-830d-c9269a49cc43",
      "name": "LLM1 ",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueRegularOutput",
      "position": [
        -720,
        624
      ],
      "parameters": {
        "text": "=Answer this question and rate your confidence:\n\nQuestion: {{ $json.userPrompt }}\n\nReturn ONLY valid JSON:\n{\n  \"answer\": \"your detailed answer here\",\n  \"confidence\": 0.75,\n  \"reasoning\": \"brief explanation of your confidence level\"\n}\n\nRules:\n- confidence: float between 0.0 and 1.0\n- Be honest about uncertainty\n- Lower confidence when speculating\n- Higher confidence only when certain",
        "options": {
          "systemMessage": "You are a helpful AI assistant. Always return responses in valid JSON format as requested."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "93167b26-228d-4c8d-bd7a-2d3bff9a7093",
      "name": "LLM2 ",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueRegularOutput",
      "position": [
        -720,
        1024
      ],
      "parameters": {
        "text": "=Answer this question and rate your confidence:\n\nQuestion: {{ $json.userPrompt }}\n\nReturn ONLY valid JSON:\n{\n  \"answer\": \"your detailed answer here\",\n  \"confidence\": 0.75,\n  \"reasoning\": \"brief explanation of your confidence level\"\n}\n\nRules:\n- confidence: float between 0.0 and 1.0\n- Be honest about uncertainty\n- Lower confidence when speculating\n- Higher confidence only when certain",
        "options": {
          "systemMessage": "You are a helpful AI assistant. Always return responses in valid JSON format as requested."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "d54b80a2-aabe-4886-a96d-5aa8ad4008c9",
      "name": "LLM3",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -720,
        1424
      ],
      "parameters": {
        "text": "=Answer this question and rate your confidence:\n\nQuestion: {{ $json.userPrompt }}\n\nReturn ONLY valid JSON:\n{\n  \"answer\": \"your detailed answer here\",\n  \"confidence\": 0.75,\n  \"reasoning\": \"brief explanation of your confidence level\"\n}\n\nRules:\n- confidence: float between 0.0 and 1.0\n- Be honest about uncertainty\n- Lower confidence when speculating\n- Higher confidence only when certain",
        "options": {
          "systemMessage": "You are a helpful AI assistant. Always return responses in valid JSON format as requested."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "05fa9b24-c695-4179-ab34-c9aec1a75b6a",
      "name": "LLM4 ",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -720,
        1936
      ],
      "parameters": {
        "text": "=Answer this question and rate your confidence:\n\nQuestion: {{ $json.userPrompt }}\n\nReturn ONLY valid JSON:\n{\n  \"answer\": \"your detailed answer here\",\n  \"confidence\": 0.75,\n  \"reasoning\": \"brief explanation of your confidence level\"\n}\n\nRules:\n- confidence: float between 0.0 and 1.0\n- Be honest about uncertainty\n- Lower confidence when speculating\n- Higher confidence only when certain",
        "options": {
          "systemMessage": "You are a helpful AI assistant. Always return responses in valid JSON format as requested."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "df756265-010b-461f-8152-04abf4420b9f",
      "name": "Check for Extreme Divergence",
      "type": "n8n-nodes-base.if",
      "position": [
        1168,
        1328
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "extreme-divergence-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.consensusMetrics.hasExtremeDivergence }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "84e67570-876d-439b-a80d-3d74b2c42552",
  "connections": {
    "LLM3": {
      "main": [
        [
          {
            "node": "Merge All 4 LLMs",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "LLM1 ": {
      "main": [
        [
          {
            "node": "Merge All 4 LLMs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM2 ": {
      "main": [
        [
          {
            "node": "Merge All 4 LLMs",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "LLM4 ": {
      "main": [
        [
          {
            "node": "Merge All 4 LLMs",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Set User Prompt": {
      "main": [
        [
          {
            "node": "LLM4 ",
            "type": "main",
            "index": 0
          },
          {
            "node": "LLM3",
            "type": "main",
            "index": 0
          },
          {
            "node": "LLM2 ",
            "type": "main",
            "index": 0
          },
          {
            "node": "LLM1 ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "LLM4 ",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Merge All 4 LLMs": {
      "main": [
        [
          {
            "node": "Parse & Validate Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM3",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Weighted Consensus": {
      "main": [
        [
          {
            "node": "Format Final Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Final Output": {
      "main": [
        [
          {
            "node": "Format Output (chat message)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Similarity Analysis": {
      "main": [
        [
          {
            "node": "Confidence Calibration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM2 ",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Peer Review Fallback": {
      "main": [
        [
          {
            "node": "Format Final Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confidence Calibration": {
      "main": [
        [
          {
            "node": "Check for Extreme Divergence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM1 ",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Validate Responses": {
      "main": [
        [
          {
            "node": "Similarity Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "node": "Set User Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Extreme Divergence": {
      "main": [
        [
          {
            "node": "Peer Review Fallback",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Weighted Consensus",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Output (chat message)": {
      "main": [
        [
          {
            "node": "Chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}