AutomationFlowsAI & RAG › [dev] Eval / Voice-agent-tester

[dev] Eval / Voice-agent-tester

[DEV] eval / voice-agent-tester. Uses httpRequest. Webhook trigger; 18 nodes.

Webhook trigger★★★★☆ complexity18 nodesHTTP Request
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "[DEV] eval / voice-agent-tester",
  "description": null,
  "active": true,
  "nodes": [
    {
      "id": "webhook-trigger",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        200
      ],
      "parameters": {
        "path": "voice-agent-test",
        "httpMethod": "POST",
        "responseMode": "lastNode",
        "options": {},
        "authentication": "headerAuth"
      }
    },
    {
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        400
      ],
      "parameters": {}
    },
    {
      "id": "schedule-trigger",
      "name": "Daily Test Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        600
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "merge-triggers",
      "name": "Merge Triggers",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        220,
        400
      ],
      "parameters": {
        "mode": "append",
        "options": {}
      }
    },
    {
      "id": "set-defaults",
      "name": "Set Default Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        400
      ],
      "parameters": {
        "jsCode": "// Get input from webhook or set defaults\nconst input = $input.first().json?.body || $input.first().json || {};\n\nreturn {\n  json: {\n    filter: {\n      agent_id: input.agent_id || null,\n      priority: input.priority || null,\n      category: input.category || null,\n      scenario_ids: input.scenario_ids ? input.scenario_ids.split(',') : null\n    },\n    ci_metadata: {\n      run_id: input.ci_run_id || null,\n      commit_sha: input.commit_sha || null,\n      trigger_source: input.agent_id ? 'webhook' : 'schedule'\n    }\n  }\n};"
      }
    },
    {
      "id": "load-scenarios",
      "name": "Load All Scenarios",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        660,
        400
      ],
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={{ { scenarios: [\n  {\n    \"id\": \"happy-path-booking\",\n    \"name\": \"Happy Path - User Books Demo\",\n    \"category\": \"happy_path\",\n    \"priority\": \"critical\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You are calling to learn about AI voice agents for your dental practice. You are interested and when offered, you agree to receive an SMS with the booking link. Your name is Dr. Smith.\",\n    \"expected_tool_calls\": [\"send_sms\"],\n    \"evaluation_criteria\": [\n      { \"id\": \"collected_name\", \"name\": \"Collected caller name\", \"prompt\": \"The agent asked for and collected the caller's name before sending SMS.\" },\n      { \"id\": \"got_permission\", \"name\": \"Got SMS permission\", \"prompt\": \"The agent explicitly asked for and received permission to send a text message.\" },\n      { \"id\": \"sent_sms\", \"name\": \"SMS tool called\", \"prompt\": \"The agent successfully called the send_sms tool.\" }\n    ]\n  },\n  {\n    \"id\": \"quick-sale\",\n    \"name\": \"Quick Sale - Ready to Buy\",\n    \"category\": \"happy_path\",\n    \"priority\": \"high\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You already know about AI voice agents. You want to book a demo right away. Say yes to everything and give your name as Jennifer when asked.\",\n    \"expected_tool_calls\": [\"send_sms\"],\n    \"evaluation_criteria\": [\n      { \"id\": \"efficient_handling\", \"name\": \"Efficient handling\", \"prompt\": \"The agent moved quickly to book without unnecessary questions.\" },\n      { \"id\": \"sent_sms\", \"name\": \"SMS sent\", \"prompt\": \"The agent sent the SMS booking link.\" }\n    ]\n  },\n  {\n    \"id\": \"difficult-user\",\n    \"name\": \"Difficult User - Skeptical and Interruptive\",\n    \"category\": \"edge_case\",\n    \"priority\": \"high\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You are very skeptical about AI. You interrupt frequently. You ask tough questions about pricing and want to know if this is a scam. Eventually warm up if the agent handles objections well.\",\n    \"expected_tool_calls\": [],\n    \"evaluation_criteria\": [\n      { \"id\": \"stayed_polite\", \"name\": \"Maintained professionalism\", \"prompt\": \"The agent remained polite and professional despite the difficult caller.\" },\n      { \"id\": \"answered_pricing\", \"name\": \"Answered pricing question\", \"prompt\": \"The agent provided accurate pricing information when asked.\" }\n    ]\n  },\n  {\n    \"id\": \"quick-decline\",\n    \"name\": \"Quick Decline - Not Interested\",\n    \"category\": \"edge_case\",\n    \"priority\": \"medium\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You called by accident. Say you're not interested and want to hang up quickly.\",\n    \"expected_tool_calls\": [],\n    \"evaluation_criteria\": [\n      { \"id\": \"graceful_exit\", \"name\": \"Graceful exit\", \"prompt\": \"The agent ended the call politely and professionally when the caller wasn't interested.\" },\n      { \"id\": \"no_pushy_sales\", \"name\": \"Not pushy\", \"prompt\": \"The agent did not aggressively push for a sale when the caller clearly wasn't interested.\" }\n    ]\n  },\n  {\n    \"id\": \"sms-decline\",\n    \"name\": \"SMS Decline - Interested but No Text\",\n    \"category\": \"edge_case\",\n    \"priority\": \"high\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You are interested in AI voice agents but you don't want to receive text messages. Ask for the booking URL verbally instead.\",\n    \"expected_tool_calls\": [],\n    \"evaluation_criteria\": [\n      { \"id\": \"respected_preference\", \"name\": \"Respected no-SMS preference\", \"prompt\": \"The agent respected the caller's preference not to receive SMS.\" },\n      { \"id\": \"offered_alternative\", \"name\": \"Offered alternative\", \"prompt\": \"The agent offered an alternative way to get the booking information.\" }\n    ]\n  },\n  {\n    \"id\": \"premature-confirm\",\n    \"name\": \"Premature Confirmation Check\",\n    \"category\": \"tool_validation\",\n    \"priority\": \"critical\",\n    \"agent_id\": \"<REDACTED:elevenlabs-agent-id>\",\n    \"simulated_user_prompt\": \"You want the booking link texted to you. Your name is Sam. Pay close attention to whether the agent says they sent the text BEFORE you hear the tool being called.\",\n    \"expected_tool_calls\": [\"send_sms\"],\n    \"evaluation_criteria\": [\n      { \"id\": \"no_premature_confirm\", \"name\": \"No premature confirmation\", \"prompt\": \"The agent did NOT claim the SMS was sent before the tool actually executed.\" },\n      { \"id\": \"proper_sequence\", \"name\": \"Proper tool sequence\", \"prompt\": \"The agent asked for name, asked permission, called tool, then confirmed delivery - in that order.\" }\n    ]\n  }\n], filter: $json.filter, ci_metadata: $json.ci_metadata } }}"
      }
    },
    {
      "id": "filter-scenarios",
      "name": "Filter Scenarios",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        400
      ],
      "parameters": {
        "jsCode": "const { scenarios, filter, ci_metadata } = $input.first().json;\n\nlet filtered = scenarios;\n\n// Filter by agent_id\nif (filter.agent_id) {\n  filtered = filtered.filter(s => s.agent_id === filter.agent_id);\n}\n\n// Filter by priority (include all at or above specified level)\nif (filter.priority) {\n  const priorities = ['critical', 'high', 'medium', 'low'];\n  const minIndex = priorities.indexOf(filter.priority);\n  filtered = filtered.filter(s => \n    priorities.indexOf(s.priority) <= minIndex\n  );\n}\n\n// Filter by category\nif (filter.category) {\n  filtered = filtered.filter(s => s.category === filter.category);\n}\n\n// Filter by specific scenario IDs\nif (filter.scenario_ids?.length > 0) {\n  filtered = filtered.filter(s => \n    filter.scenario_ids.includes(s.id)\n  );\n}\n\n// Sort by priority (critical first)\nconst priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\nfiltered.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n// Add execution metadata\nconst executionPlan = filtered.map((scenario, index) => ({\n  ...scenario,\n  execution_index: index,\n  total_scenarios: filtered.length,\n  ci_metadata\n}));\n\nreturn executionPlan.map(s => ({ json: s }));"
      }
    },
    {
      "id": "loop-scenarios",
      "name": "Loop Through Scenarios",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1100,
        400
      ],
      "parameters": {
        "batchSize": 1,
        "options": {}
      }
    },
    {
      "id": "simulate-conversation",
      "name": "Simulate Conversation",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://api.elevenlabs.io/v1/convai/agents/{{ $json.agent_id }}/simulate-conversation",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"simulation_specification\": {\n    \"simulated_user_config\": {\n      \"prompt\": {\n        \"prompt\": {{ JSON.stringify($json.simulated_user_prompt) }},\n        \"llm\": \"gpt-4o\",\n        \"temperature\": 0.7\n      }\n    },\n    \"max_turns\": 20\n  },\n  \"extra_evaluation_criteria\": {{ JSON.stringify($json.evaluation_criteria.map(c => ({ id: c.id, name: c.name, conversation_goal_prompt: c.prompt, use_knowledge_base: false }))) }}\n}",
        "options": {
          "timeout": 120000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        }
      },
      "onError": "continueErrorOutput",
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 5000
    },
    {
      "id": "rate-limit-wait",
      "name": "Rate Limit Wait",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1320,
        600
      ],
      "parameters": {
        "amount": 1,
        "unit": "seconds"
      }
    },
    {
      "id": "parse-results",
      "name": "Parse Simulation Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        400
      ],
      "parameters": {
        "jsCode": "const scenario = $('Loop Through Scenarios').item.json;\nconst response = $input.first().json;\n\n// Handle API error\nif (response.error || !response.body) {\n  return {\n    json: {\n      scenario_id: scenario.id,\n      scenario_name: scenario.name,\n      scenario_category: scenario.category,\n      scenario_priority: scenario.priority,\n      agent_id: scenario.agent_id,\n      status: 'error',\n      error: response.error?.message || 'No response body',\n      overall_passed: false,\n      ci_metadata: scenario.ci_metadata,\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\nconst result = response.body;\n\n// Extract tool calls from transcript\nconst toolCalls = [];\nconst transcriptTurns = [];\n\nif (result.transcript) {\n  for (const turn of result.transcript) {\n    transcriptTurns.push({\n      role: turn.role,\n      message: turn.message?.substring(0, 200)\n    });\n    \n    if (turn.tool_calls) {\n      for (const call of turn.tool_calls) {\n        toolCalls.push({\n          name: call.tool_name || call.name,\n          arguments: call.arguments\n        });\n      }\n    }\n  }\n}\n\n// Validate expected tool calls\nconst expectedTools = scenario.expected_tool_calls || [];\nconst actualToolNames = toolCalls.map(tc => tc.name);\nconst missingTools = expectedTools.filter(t => !actualToolNames.includes(t));\n\nconst toolValidation = {\n  passed: missingTools.length === 0,\n  expected: expectedTools,\n  actual: actualToolNames,\n  missing: missingTools\n};\n\n// Parse evaluation criteria results\nconst evaluations = {};\nlet allCriteriaPassed = true;\n\nif (result.evaluation?.criteria_results) {\n  for (const [id, data] of Object.entries(result.evaluation.criteria_results)) {\n    const passed = data.passed || data.result === 'passed' || data.result === true;\n    evaluations[id] = {\n      name: data.name || id,\n      passed,\n      reasoning: data.reasoning || data.explanation || 'No reasoning provided'\n    };\n    if (!passed) allCriteriaPassed = false;\n  }\n}\n\n// Overall pass/fail\nconst overallPassed = toolValidation.passed && allCriteriaPassed;\n\nreturn {\n  json: {\n    scenario_id: scenario.id,\n    scenario_name: scenario.name,\n    scenario_category: scenario.category,\n    scenario_priority: scenario.priority,\n    agent_id: scenario.agent_id,\n    status: 'completed',\n    overall_passed: overallPassed,\n    tool_validation: toolValidation,\n    evaluation_results: evaluations,\n    metrics: {\n      turn_count: transcriptTurns.length,\n      tool_call_count: toolCalls.length\n    },\n    ci_metadata: scenario.ci_metadata,\n    timestamp: new Date().toISOString()\n  }\n};"
      }
    },
    {
      "id": "aggregate-results",
      "name": "Aggregate Results",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1760,
        400
      ],
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "test_results",
        "include": "allFieldsExcept"
      }
    },
    {
      "id": "calculate-metrics",
      "name": "Calculate Metrics",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        400
      ],
      "parameters": {
        "jsCode": "const results = $input.first().json.test_results;\n\n// Basic counts\nconst total = results.length;\nconst passed = results.filter(r => r.overall_passed).length;\nconst failed = results.filter(r => !r.overall_passed).length;\nconst errored = results.filter(r => r.status === 'error').length;\n\n// By priority\nconst byPriority = {};\nfor (const pri of ['critical', 'high', 'medium', 'low']) {\n  const priResults = results.filter(r => r.scenario_priority === pri);\n  if (priResults.length > 0) {\n    byPriority[pri] = {\n      total: priResults.length,\n      passed: priResults.filter(r => r.overall_passed).length,\n      success_rate: Math.round(priResults.filter(r => r.overall_passed).length / priResults.length * 100)\n    };\n  }\n}\n\n// Critical failures\nconst criticalFailures = results.filter(r => \n  !r.overall_passed && r.scenario_priority === 'critical'\n);\n\n// Tool failures\nconst toolFailures = results.filter(r => \n  r.tool_validation && !r.tool_validation.passed\n);\n\n// CI metadata from first result\nconst ciMetadata = results[0]?.ci_metadata || {};\n\nreturn {\n  json: {\n    summary: {\n      total,\n      passed,\n      failed,\n      errored,\n      success_rate: Math.round(passed / total * 100),\n      tool_call_accuracy: toolFailures.length === 0 ? 100 : Math.round((total - toolFailures.length) / total * 100)\n    },\n    by_priority: byPriority,\n    critical_failures: criticalFailures.map(r => ({\n      scenario: r.scenario_name,\n      agent: r.agent_id\n    })),\n    tool_failures: toolFailures.map(r => ({\n      scenario: r.scenario_name,\n      missing: r.tool_validation.missing\n    })),\n    ci_metadata: ciMetadata,\n    detailed_results: results,\n    timestamp: new Date().toISOString()\n  }\n};"
      }
    },
    {
      "id": "generate-report",
      "name": "Generate Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        400
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst { summary, by_priority, critical_failures, tool_failures, detailed_results } = data;\n\nlet report = `# Voice Agent Test Report\\n\\n`;\nreport += `**Generated:** ${new Date().toISOString()}\\n\\n`;\n\n// Summary\nreport += `## Summary\\n\\n`;\nreport += `| Metric | Value |\\n`;\nreport += `|--------|-------|\\n`;\nreport += `| Total Tests | ${summary.total} |\\n`;\nreport += `| Passed | ${summary.passed} |\\n`;\nreport += `| Failed | ${summary.failed} |\\n`;\nreport += `| **Success Rate** | **${summary.success_rate}%** |\\n`;\nreport += `| Tool Accuracy | ${summary.tool_call_accuracy}% |\\n\\n`;\n\n// Status badge\nif (summary.success_rate >= 95) {\n  report += `**Status: HEALTHY**\\n\\n`;\n} else if (summary.success_rate >= 80) {\n  report += `**Status: WARNING**\\n\\n`;\n} else {\n  report += `**Status: CRITICAL**\\n\\n`;\n}\n\n// Critical failures\nif (critical_failures.length > 0) {\n  report += `## Critical Failures\\n\\n`;\n  for (const cf of critical_failures) {\n    report += `- **${cf.scenario}** (${cf.agent})\\n`;\n  }\n  report += `\\n`;\n}\n\n// Tool failures\nif (tool_failures.length > 0) {\n  report += `## Tool Validation Failures\\n\\n`;\n  for (const tf of tool_failures) {\n    report += `- **${tf.scenario}**: Missing \\`${tf.missing.join(', ')}\\`\\n`;\n  }\n  report += `\\n`;\n}\n\n// Results by priority\nreport += `## Results by Priority\\n\\n`;\nreport += `| Priority | Total | Passed | Rate |\\n`;\nreport += `|----------|-------|--------|------|\\n`;\nfor (const [pri, stats] of Object.entries(by_priority)) {\n  report += `| ${pri} | ${stats.total} | ${stats.passed} | ${stats.success_rate}% |\\n`;\n}\nreport += `\\n`;\n\n// Detailed results\nreport += `## Detailed Results\\n\\n`;\nfor (const result of detailed_results) {\n  const icon = result.overall_passed ? 'PASS' : 'FAIL';\n  report += `### [${icon}] ${result.scenario_name}\\n`;\n  report += `- Category: ${result.scenario_category} | Priority: ${result.scenario_priority}\\n`;\n  \n  if (result.tool_validation && !result.tool_validation.passed) {\n    report += `- Tool Validation: FAILED - Missing: ${(result.tool_validation.missing || []).join(', ')}\\n`;\n  }\n  \n  for (const [id, ev] of Object.entries(result.evaluation_results || {})) {\n    const evIcon = ev.passed ? 'PASS' : 'FAIL';\n    report += `- [${evIcon}] ${ev.name}: ${ev.reasoning.substring(0, 100)}\\n`;\n  }\n  report += `\\n`;\n}\n\nreturn {\n  json: {\n    report_markdown: report,\n    summary: data.summary,\n    critical_failures: data.critical_failures,\n    ci_metadata: data.ci_metadata,\n    has_failures: summary.failed > 0,\n    has_critical_failures: critical_failures.length > 0,\n    timestamp: new Date().toISOString()\n  }\n};"
      }
    },
    {
      "id": "check-failures",
      "name": "Check Failures",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2420,
        400
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "critical",
              "conditions": {
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.has_critical_failures }}",
                    "rightValue": true,
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "outputKey": "failures",
              "conditions": {
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $json.has_failures }}",
                    "rightValue": true,
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      }
    },
    {
      "id": "send-critical-alert",
      "name": "Send Critical Alert",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "https://n8n.wranngle.com/webhook/voice-agent-test-alert",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"critical_failure\",\n  \"summary\": {{ JSON.stringify($json.summary) }},\n  \"critical_failures\": {{ JSON.stringify($json.critical_failures) }},\n  \"ci_metadata\": {{ JSON.stringify($json.ci_metadata) }}\n}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      }
    },
    {
      "id": "send-failure-alert",
      "name": "Send Failure Alert",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "https://n8n.wranngle.com/webhook/voice-agent-test-alert",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"test_failure\",\n  \"summary\": {{ JSON.stringify($json.summary) }},\n  \"ci_metadata\": {{ JSON.stringify($json.ci_metadata) }}\n}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      }
    },
    {
      "id": "success-response",
      "name": "Success Response",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2640,
        600
      ],
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={\n  \"status\": \"completed\",\n  \"summary\": {{ JSON.stringify($json.summary) }},\n  \"ci_metadata\": {{ JSON.stringify($json.ci_metadata) }},\n  \"report_url\": \"https://n8n.wranngle.com/webhook/voice-agent-report\"\n}"
      }
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Merge Triggers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Merge Triggers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Test Schedule": {
      "main": [
        [
          {
            "node": "Merge Triggers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Triggers": {
      "main": [
        [
          {
            "node": "Set Default Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Default Params": {
      "main": [
        [
          {
            "node": "Load All Scenarios",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load All Scenarios": {
      "main": [
        [
          {
            "node": "Filter Scenarios",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Scenarios": {
      "main": [
        [
          {
            "node": "Loop Through Scenarios",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Through Scenarios": {
      "main": [
        [
          {
            "node": "Simulate Conversation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simulate Conversation": {
      "main": [
        [
          {
            "node": "Parse Simulation Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rate Limit Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rate Limit Wait": {
      "main": [
        [
          {
            "node": "Parse Simulation Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Simulation Results": {
      "main": [
        [
          {
            "node": "Loop Through Scenarios",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Results": {
      "main": [
        [
          {
            "node": "Calculate Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Metrics": {
      "main": [
        [
          {
            "node": "Generate Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Report": {
      "main": [
        [
          {
            "node": "Check Failures",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Failures": {
      "main": [
        [
          {
            "node": "Send Critical Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Failure Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "tags": [
    "DEV"
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

[DEV] eval / voice-agent-tester. Uses httpRequest. Webhook trigger; 18 nodes.

Source: https://github.com/wranngle/n8n_showcase/blob/main/workflows/eval/voice-agent-tester.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Jigsaw API key for image processing, I use this as a gatekeeper/second pair of eyes. LINK to their website https://jigsawstack.com/ SECOND A postgress DATABASE (I use Supabase) LlamaCloud for the pars

HTTP Request, Postgres, Stop And Error +2
AI & RAG

Onsite Photos to Jobs (SMS Agent). Uses dataTable, twilio, httpRequest, airtable. Webhook trigger; 62 nodes.

Data Table, Twilio, HTTP Request +1
AI & RAG

AML Alert Triage. Uses httpRequest. Webhook trigger; 57 nodes.

HTTP Request
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 50 nodes.

Postgres, Redis, HTTP Request
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 48 nodes.

Postgres, Redis, HTTP Request