Pular para conteúdo

RANDOM - Seleção Aleatória

O que é este Node?

O RANDOM é o node responsável por escolher aleatoriamente uma opção de uma lista com suporte a pesos para probabilidades customizadas.

Por que este Node existe?

Conversas e automações precisam de variabilidade e decisões probabilísticas. O RANDOM existe para:

  1. Variabilidade: Evitar respostas repetitivas e previsíveis
  2. A/B Testing: Distribuir usuários entre diferentes variações de flows
  3. Distribuição de carga: Rotacionar entre múltiplos recursos (APIs, agentes, etc.)
  4. Gamificação: Criar elementos de surpresa e aleatoriedade
  5. Seleção ponderada: Controlar probabilidade de cada opção com pesos

Como funciona internamente?

Quando o RANDOM é executado, o sistema:

  1. Valida opções: Verifica se há pelo menos uma opção disponível
  2. Verifica pesos: Se fornecidos, valida se há peso para cada opção
  3. Calcula seleção:
  4. Com pesos: Usa seleção aleatória ponderada (weighted random)
  5. Sem pesos: Cada opção tem chance igual (uniform random)
  6. Escolhe opção: Seleciona uma das opções baseado no algoritmo
  7. Retorna resultado: Devolve a opção escolhida para uso no flow

Código interno (logic-control-executor.service.ts:302-329):

private async executeRandom(parameters: any, context: any): Promise<any> {
  const { options, weights } = parameters;

  this.logger.log(`🎲 RANDOM - Choosing from ${options?.length || 0} options`);

  if (!options || options.length === 0) {
    throw new Error('No options provided for random selection');
  }

  let selectedOption;

  if (weights && weights.length === options.length) {
    // Weighted random selection
    selectedOption = this.weightedRandomChoice(options, weights);
  } else {
    // Simple random selection
    selectedOption = options[Math.floor(Math.random() * options.length)];
  }

  return {
    success: true,
    action: 'random_choice_made',
    selectedOption: selectedOption,
    allOptions: options,
    weights: weights || null,
    timestamp: new Date().toISOString()
  };
}

Código do algoritmo de seleção ponderada (logic-control-executor.service.ts:381-393):

private weightedRandomChoice(options: any[], weights: number[]): any {
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
  let random = Math.random() * totalWeight;

  for (let i = 0; i < options.length; i++) {
    random -= weights[i];
    if (random <= 0) {
      return options[i];
    }
  }

  return options[options.length - 1]; // Fallback
}

Quando você DEVE usar este Node?

Use RANDOM quando precisar de seleção aleatória ou probabilística:

Casos de uso:

  1. Variar mensagens: Escolher entre múltiplas saudações
  2. A/B Testing: Distribuir usuários em grupos de teste
  3. Distribuir atendimento: Rotacionar entre atendentes disponíveis
  4. Gamificação: Sortear prêmios ou desafios
  5. Load balancing: Distribuir entre múltiplos servidores/APIs
  6. Personalização: Variar conteúdo para evitar monotonia

Quando NÃO usar RANDOM:

  • Decisões baseadas em dados: Use CONDITION ou SWITCH
  • Múltiplos critérios: Use SWITCH com cases específicos
  • Lógica determinística: Se precisa do mesmo resultado sempre, use lógica condicional

Parâmetros Detalhados

options (array, obrigatório)

O que é: Array com as opções disponíveis para seleção. Pode conter strings, números ou objetos.

Flow completo para testar:

{
  "name": "Teste RANDOM - Options Simples",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Escolher Cor",
        "parameters": {
          "options": ["vermelho", "azul", "verde", "amarelo"]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Salvar Escolha",
        "parameters": {
          "name": "cor_escolhida",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Mostrar Resultado",
        "parameters": {
          "message": "🎨 A cor sorteada foi: {{cor_escolhida}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Execute o flow múltiplas vezes. Cada execução escolherá uma cor diferente aleatoriamente!

weights (array de números, opcional)

O que é: Array de números que define o peso (probabilidade) de cada opção. Deve ter o mesmo tamanho do array options.

Padrão: Todos os pesos iguais (distribuição uniforme)

Como funciona: - Peso 1 = probabilidade normal - Peso 2 = duas vezes mais chance - Peso 10 = dez vezes mais chance - Peso 0 = nunca será escolhido

Flow completo para testar:

{
  "name": "Teste RANDOM - Weights (Ponderado)",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Sortear Prêmio",
        "parameters": {
          "options": ["Comum", "Raro", "Épico", "Lendário"],
          "weights": [70, 20, 8, 2]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Salvar Prêmio",
        "parameters": {
          "name": "premio",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Anunciar Prêmio",
        "parameters": {
          "message": "🎁 Você ganhou um prêmio {{premio}}!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 900, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_1" },
    { "source": "message_1", "target": "end_1" }
  ]
}

Teste: Execute várias vezes. "Comum" aparecerá 70% das vezes, "Raro" 20%, "Épico" 8%, "Lendário" 2%!

Cálculo de probabilidades:

Total de pesos = 70 + 20 + 8 + 2 = 100

Comum: 70/100 = 70% de chance
Raro: 20/100 = 20% de chance
Épico: 8/100 = 8% de chance
Lendário: 2/100 = 2% de chance

Parâmetros

Campo Tipo Obrigatório Descrição
options array Sim Array com as opções disponíveis para escolha
weights array de números Não Pesos para cada opção (mesmo tamanho que options)

Exemplo 1: Variar Saudações

Objetivo: Evitar mensagens repetitivas variando a saudação inicial

JSON para Importar

{
  "name": "RANDOM - Saudações Variadas",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Escolher Saudação",
        "parameters": {
          "options": [
            "Olá! Como posso ajudar você hoje?",
            "Oi! Em que posso ser útil?",
            "Seja bem-vindo! Como posso auxiliar?",
            "Olá! É um prazer atender você!",
            "Oi! Estou aqui para ajudar!"
          ]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Salvar Saudação",
        "parameters": {
          "name": "saudacao",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Enviar Saudação",
        "parameters": {
          "message": "{{saudacao}}"
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Receber Resposta",
        "parameters": {
          "message": "Descreva sua necessidade:",
          "variable": "necessidade"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Confirmar",
        "parameters": {
          "message": "Entendi! Vou processar: {{necessidade}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_1" },
    { "source": "message_1", "target": "input_1" },
    { "source": "input_1", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: [Uma das 5 saudações aleatoriamente]
Sistema: Descreva sua necessidade:
Usuário: Preciso de ajuda com pedido
Sistema: Entendi! Vou processar: Preciso de ajuda com pedido

Exemplo 2: A/B Testing com 3 Variações

Objetivo: Distribuir usuários em 3 grupos de teste para testar diferentes abordagens

JSON para Importar

{
  "name": "RANDOM - A/B/C Testing",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Distribuir Grupo",
        "parameters": {
          "options": ["Grupo_A", "Grupo_B", "Grupo_C"]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Salvar Grupo",
        "parameters": {
          "name": "grupo_teste",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "switch_1",
      "type": "switch",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Rotear por Grupo",
        "parameters": {
          "variable": "{{grupo_teste}}",
          "cases": [
            {
              "value": "Grupo_A",
              "output": "grupo_a"
            },
            {
              "value": "Grupo_B",
              "output": "grupo_b"
            },
            {
              "value": "Grupo_C",
              "output": "grupo_c"
            }
          ]
        }
      }
    },
    {
      "id": "message_a",
      "type": "message",
      "position": { "x": 900, "y": 0 },
      "data": {
        "label": "Abordagem A",
        "parameters": {
          "message": "🔵 GRUPO A: Bem-vindo! Você está no grupo de teste A - Abordagem Formal."
        }
      }
    },
    {
      "id": "message_b",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Abordagem B",
        "parameters": {
          "message": "🟢 GRUPO B: E aí! Você tá no grupo B - Abordagem Casual."
        }
      }
    },
    {
      "id": "message_c",
      "type": "message",
      "position": { "x": 900, "y": 200 },
      "data": {
        "label": "Abordagem C",
        "parameters": {
          "message": "🟡 GRUPO C: Hey! You're in group C - English Version."
        }
      }
    },
    {
      "id": "merge_1",
      "type": "merge",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Reunir Fluxos"
      }
    },
    {
      "id": "message_final",
      "type": "message",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Continuar",
        "parameters": {
          "message": "Você foi atribuído ao: {{grupo_teste}}"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1500, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "switch_1" },
    { "source": "switch_1", "target": "message_a", "sourceHandle": "grupo_a" },
    { "source": "switch_1", "target": "message_b", "sourceHandle": "grupo_b" },
    { "source": "switch_1", "target": "message_c", "sourceHandle": "grupo_c" },
    { "source": "message_a", "target": "merge_1" },
    { "source": "message_b", "target": "merge_1" },
    { "source": "message_c", "target": "merge_1" },
    { "source": "merge_1", "target": "message_final" },
    { "source": "message_final", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: [Mensagem do Grupo A, B ou C aleatoriamente - 33.3% cada]
Sistema: Você foi atribuído ao: Grupo_A (ou B ou C)

Exemplo 3: Sorteio de Prêmios com Pesos (Gamificação)

Objetivo: Sistema de sorteio com raridades diferentes (comum mais frequente, lendário raro)

JSON para Importar

{
  "name": "RANDOM - Sorteio com Raridade",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Introdução",
        "parameters": {
          "message": "🎰 Bem-vindo ao sorteio diário!\n\nGirando a roleta..."
        }
      }
    },
    {
      "id": "delay_1",
      "type": "delay",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Suspense",
        "parameters": {
          "duration": 2,
          "unit": "seconds"
        }
      }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Sortear Prêmio",
        "parameters": {
          "options": [
            "🥉 Bronze (10 pontos)",
            "🥈 Prata (25 pontos)",
            "🥇 Ouro (50 pontos)",
            "💎 Diamante (100 pontos)",
            "👑 Lendário (500 pontos)"
          ],
          "weights": [50, 30, 15, 4, 1]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Salvar Resultado",
        "parameters": {
          "name": "premio_sorteado",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Anunciar Prêmio",
        "parameters": {
          "message": "🎉 PARABÉNS!\n\nVocê ganhou: {{premio_sorteado}}\n\n✨ Os pontos foram adicionados à sua conta!"
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1300, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "message_1" },
    { "source": "message_1", "target": "delay_1" },
    { "source": "delay_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_2" },
    { "source": "message_2", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: 🎰 Bem-vindo ao sorteio diário!
         Girando a roleta...
[2 segundos de espera]
Sistema: 🎉 PARABÉNS!
         Você ganhou: 🥉 Bronze (10 pontos)
         ✨ Os pontos foram adicionados à sua conta!

Probabilidades: - Bronze: 50% (metade das vezes) - Prata: 30% (quase 1/3 das vezes) - Ouro: 15% (raro) - Diamante: 4% (muito raro) - Lendário: 1% (extremamente raro)

Exemplo 4: Distribuir entre Atendentes

Objetivo: Distribuir atendimentos entre múltiplos atendentes com pesos baseados em disponibilidade

JSON para Importar

{
  "name": "RANDOM - Distribuição de Atendentes",
  "nodes": [
    {
      "id": "start_1",
      "type": "start",
      "position": { "x": 100, "y": 100 },
      "data": { "label": "Início" }
    },
    {
      "id": "message_1",
      "type": "message",
      "position": { "x": 300, "y": 100 },
      "data": {
        "label": "Receber Contato",
        "parameters": {
          "message": "👋 Olá! Estou conectando você com um atendente..."
        }
      }
    },
    {
      "id": "random_1",
      "type": "random",
      "position": { "x": 500, "y": 100 },
      "data": {
        "label": "Escolher Atendente",
        "parameters": {
          "options": ["Maria", "João", "Ana", "Carlos"],
          "weights": [40, 30, 20, 10]
        }
      }
    },
    {
      "id": "variable_1",
      "type": "variable",
      "position": { "x": 700, "y": 100 },
      "data": {
        "label": "Salvar Atendente",
        "parameters": {
          "name": "atendente",
          "value": "{{selectedOption}}"
        }
      }
    },
    {
      "id": "message_2",
      "type": "message",
      "position": { "x": 900, "y": 100 },
      "data": {
        "label": "Apresentar Atendente",
        "parameters": {
          "message": "✅ Você foi conectado com {{atendente}}!\n\n{{atendente}} irá atender você agora. Como podemos ajudar?"
        }
      }
    },
    {
      "id": "input_1",
      "type": "input",
      "position": { "x": 1100, "y": 100 },
      "data": {
        "label": "Receber Mensagem",
        "parameters": {
          "message": "Digite sua mensagem:",
          "variable": "mensagem_usuario"
        }
      }
    },
    {
      "id": "message_3",
      "type": "message",
      "position": { "x": 1300, "y": 100 },
      "data": {
        "label": "Confirmar Envio",
        "parameters": {
          "message": "📨 Mensagem enviada para {{atendente}}. Aguarde resposta..."
        }
      }
    },
    {
      "id": "end_1",
      "type": "end",
      "position": { "x": 1500, "y": 100 },
      "data": { "label": "Fim" }
    }
  ],
  "edges": [
    { "source": "start_1", "target": "message_1" },
    { "source": "message_1", "target": "random_1" },
    { "source": "random_1", "target": "variable_1" },
    { "source": "variable_1", "target": "message_2" },
    { "source": "message_2", "target": "input_1" },
    { "source": "input_1", "target": "message_3" },
    { "source": "message_3", "target": "end_1" }
  ]
}

Saída esperada:

Sistema: 👋 Olá! Estou conectando você com um atendente...
Sistema: ✅ Você foi conectado com Maria!
         Maria irá atender você agora. Como podemos ajudar?
Sistema: Digite sua mensagem:
Usuário: Preciso de ajuda com meu pedido
Sistema: 📨 Mensagem enviada para Maria. Aguarde resposta...

Nota: Maria recebe 40% dos atendimentos (mais experiente), Carlos apenas 10% (trainee).

Resposta do Node

Seleção simples (sem pesos):

{
  "success": true,
  "action": "random_choice_made",
  "selectedOption": "azul",
  "allOptions": ["vermelho", "azul", "verde", "amarelo"],
  "weights": null,
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Seleção ponderada (com pesos):

{
  "success": true,
  "action": "random_choice_made",
  "selectedOption": "Comum",
  "allOptions": ["Comum", "Raro", "Épico", "Lendário"],
  "weights": [70, 20, 8, 2],
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Entendendo Pesos (Weights)

Conceito Básico

Os pesos funcionam como uma "roleta" onde cada opção ocupa um espaço proporcional ao seu peso:

Pesos: [50, 30, 15, 4, 1]
Total: 100

Roleta:
[==================================================] Bronze (50%)
[==============================] Prata (30%)
[===============] Ouro (15%)
[====] Diamante (4%)
[=] Lendário (1%)

Exemplos de Distribuição

Pesos Resultado
[1, 1, 1] 33.3% cada (igual)
[2, 1, 1] 50%, 25%, 25%
[3, 2, 1] 50%, 33%, 17%
[100, 1] 99%, 1% (muito desbalanceado)

Calculando Probabilidade

Probabilidade = Peso da Opção / Soma Total dos Pesos

Exemplo: [70, 20, 8, 2]
Total = 70 + 20 + 8 + 2 = 100

Opção 1: 70/100 = 70%
Opção 2: 20/100 = 20%
Opção 3: 8/100 = 8%
Opção 4: 2/100 = 2%

Casos de Uso Reais

Caso Options Weights Uso
A/B Test 50/50 [A, B] [1, 1] Teste igualitário
A/B Test 80/20 [A, B] [80, 20] Versão A prioritária
Atendentes [Maria, João, Pedro] [50, 30, 20] Por experiência
Gamificação [Comum, Raro, Épico] [70, 25, 5] Sistema de raridade
Mensagens [Msg1, Msg2, Msg3] null Variação igual

Boas Práticas

SIM: - Use RANDOM para criar variabilidade e evitar monotonia - Combine com SWITCH para rotear baseado na escolha - Use weights para controlar distribuição (A/B testing com 80/20) - Salve o resultado em VARIABLE para usar depois - Use para distribuir carga entre múltiplos recursos

NÃO: - Não use RANDOM para decisões que precisam de lógica (use CONDITION) - Não confie em RANDOM para dados críticos (não é criptograficamente seguro) - Não esqueça de salvar selectedOption em variável se precisar usar depois - Não use weights desbalanceados demais ([9999, 1]) a menos que intencional

Dicas

💡 A/B Testing balanceado: Use weights [1, 1] ou omita weights para distribuição 50/50 perfeita

💡 A/B Testing desbalanceado: Use [80, 20] quando quiser testar nova versão com cautela (80% versão antiga, 20% nova)

💡 Gamificação realista: Use progressão exponencial nos pesos: [64, 32, 16, 8, 4, 2, 1] para raridades

💡 Acessar resultado: O selectedOption retorna a opção escolhida - salve em VARIABLE para usar em nodes seguintes

💡 Combinar com SWITCH: Use RANDOM + VARIABLE + SWITCH para criar rotas diferentes baseadas na escolha aleatória

💡 Load balancing: Use weights para distribuir baseado em capacidade (servidor forte = peso maior)

Diferenças entre RANDOM e outros Nodes

Node Quando Usar
RANDOM Escolha aleatória/probabilística
CONDITION Decisão baseada em condição (if/else)
SWITCH Múltiplos caminhos baseados em valor exato
LOOP Repetir ações múltiplas vezes

Próximos Nodes

CONDITION - Decisões baseadas em condições → SWITCH - Múltiplos caminhos baseados em valor → VARIABLE - Salvar e gerenciar variáveis → MERGE - Reunir múltiplos fluxos