Pular para conteúdo

Escalabilidade: Evolution API e LLMs

Objetivo

Documentar as medidas arquiteturais para escalar o CIBA suportando centenas de milhares de mensagens diárias, considerando:

  1. Evolution API - Recursos (CPU, memória, WebSocket connections) e rate limits
  2. LLMs/IA - Rate limiting e custos de todos os modelos de IA usados

0. Inventário de Modelos de IA

O CIBA utiliza 6 tipos diferentes de chamadas a modelos de IA, cada um com seus próprios rate limits:

Resumo de Modelos

Funcionalidade Provider Modelo Rate Limit Custo/1K tokens
Chat (resposta) Anthropic claude-haiku-4-5 4.000 RPM $0.25 in / $1.25 out
Chat (complexo) Anthropic claude-sonnet-4-5 1.000 RPM $3 in / $15 out
Embedding OpenAI text-embedding-3-small 3.000 RPM $0.02
Chunking OpenAI gpt-4.1 500 RPM $2 in / $8 out
Transcrição OpenAI whisper-1 50 RPM $0.006/min áudio
Prompt Opt. Anthropic claude-sonnet-4-5 (compartilhado) $3 in / $15 out
Vision (img) Anthropic claude-haiku/sonnet (compartilhado) (mesmo do chat)

Detalhamento por Funcionalidade

1. Chat Principal (Resposta ao Usuário)

// appsettings.json → Llm
{
  "DefaultModel": "claude-sonnet-4-5-20250929",
  "Providers": [
    { "Name": "anthropic", "Models": ["claude-haiku-4-5-20251001", "claude-sonnet-4-5-20250929", "claude-opus-4-5-20251101"] },
    { "Name": "openai", "Models": ["gpt-4.1", "gpt-4.1-mini", "gpt-4o-mini", "gpt-5", "gpt-5-mini"] }
  ]
}
- Frequência: 1 chamada por mensagem recebida (quando AI mode ativo) - Tokens médios: ~500-1500 input, ~100-300 output

2. Embedding (Busca Semântica)

// appsettings.json → KnowledgeRetrieval.Embedding
{
  "Provider": "openai",
  "Model": "text-embedding-3-small",
  "Dimensions": 1536,
  "MaxTokens": 8191
}
- Frequência: - 1 chamada por mensagem (embedding da query do usuário) - N chamadas ao criar/atualizar knowledge blocks (embedding dos chunks) - Tokens médios: ~50-500 por chamada

3. Chunking (Divisão de Conhecimento)

// appsettings.json → KnowledgeRetrieval.Chunking
{
  "Provider": "openai",
  "Model": "gpt-4.1",
  "MaxSize": 4000,
  "MaxOutputTokens": 8192
}
- Frequência: 1 chamada por knowledge block criado/atualizado - Tokens médios: ~2000-8000 input, ~1000-4000 output - Observação: Operação de backoffice, não tempo real

4. Transcrição de Áudio

// appsettings.json → Transcription
{
  "Provider": "openai",
  "Model": "whisper-1",
  "DefaultLanguage": "pt",
  "TimeoutSeconds": 60
}
- Frequência: 1 chamada por mensagem de áudio recebida - Limite: 50 RPM (requests per minute) - GARGALO CRÍTICO - Custo: $0.006 por minuto de áudio

5. Otimização de Prompt

// appsettings.json → PromptOptimization
{
  "Provider": "anthropic",
  "Model": "claude-sonnet-4-5-20250929",
  "MaxOutputTokens": 4096,
  "MinPromptLength": 500
}
- Frequência: 1 chamada por agente (quando system prompt > 500 chars) - Tokens médios: ~500-2000 input, ~300-1500 output - Observação: Operação de backoffice, resultado cacheado em Agent.OptimizedSystemPrompt

6. Descrição de Imagem (Vision)

// ProcessMessageHandler.cs → DescribeImageAsync()
// Usa o LLM principal com suporte multimodal
var response = await _llm.GenerateResponseAsync(
    "Você é um assistente que descreve imagens...",
    messages,  // ChatMessage.WithImage()
    null,      // modelo padrão
    256,       // max tokens
    ct);
- Frequência: 1 chamada por mensagem de imagem recebida - Tokens médios: ~1000-2000 input (imagem em base64), ~100-256 output - Observação: Compartilha rate limit com chat principal

Fluxo de Chamadas por Mensagem

Mensagem WhatsApp Recebida
         ├─► [ÁUDIO?] ──► Whisper (transcrição) ──────────────────┐
         │                                                        │
         ├─► [IMAGEM?] ─► Claude Vision (descrição) ─────────────┤
         │                                                        │
         ▼                                                        ▼
    Texto extraído ◄──────────────────────────────────────────────┘
         ├─► OpenAI Embedding (query do usuário)
    Busca semântica no PostgreSQL (pgvector)
    Claude Chat (system prompt + contexto + knowledge + mensagem)
    Resposta enviada via Evolution API

Rate Limits Consolidados

Provider Modelo RPM TPM Observação
Anthropic claude-haiku-4-5 4.000 400.000 Chat rápido
Anthropic claude-sonnet-4-5 1.000 80.000 Chat/Prompt Opt
Anthropic claude-opus-4-5 500 40.000 Reservado
OpenAI text-embedding-3-small 3.000 1.000.000 Embeddings
OpenAI gpt-4.1 500 30.000 Chunking
OpenAI whisper-1 50 N/A GARGALO

Gargalo identificado: Whisper-1 com apenas 50 RPM é o limite mais restritivo para mensagens de áudio.

Estratégias por Modelo

Whisper (Transcrição) - GARGALO CRÍTICO

Problema: 50 RPM = máximo ~50 áudios/minuto = ~72.000 áudios/dia

Soluções:

Estratégia Implementação Impacto
Fila dedicada Stream separado webhook:audio com rate limiting Controla fluxo
Fallback local Whisper.cpp ou faster-whisper local Elimina rate limit
Provider alternativo AssemblyAI, Deepgram, Google Speech Mais RPM
Degradação graciosa Mensagem "áudio recebido, processando..." UX aceitável
Recomendação para 100k+ msg/dia:
├─ Whisper local (GPU) como primary
├─ OpenAI Whisper como fallback
└─ Fila com prioridade (primeiras mensagens > continuação)

Embedding (OpenAI)

Problema: 3.000 RPM é suficiente para chat, mas pode ser gargalo em batch operations (criar muitos knowledge blocks).

Soluções:

Estratégia Implementação Impacto
Batching Agrupar múltiplos textos em 1 request Reduz RPM
Cache Redis cache por hash do texto Evita re-embedding
Provider alternativo Voyage AI, Cohere Mais RPM/qualidade

Claude Chat

Problema: 4.000 RPM (Haiku) é confortável, mas picos podem exceder.

Soluções:

Estratégia Implementação Impacto
Fallback OpenAI gpt-4o-mini quando Claude 429 Resiliência
Rate limit interno 80% do limite (3.200 RPM) Margem segura
Prompt caching anthropic-beta header -90% tokens input
Response cache Redis por hash(agentId + mensagem) Evita chamadas

Vision (Imagens)

Problema: Compartilha rate limit com chat principal.

Soluções:

Estratégia Implementação Impacto
Rate limit separado Contador dedicado para vision Isolamento
Fallback OpenAI GPT-4o Vision quando Claude limitado Resiliência
Descrição opcional Flag por agente para habilitar/desabilitar Reduz chamadas

Estimativa de Chamadas por Volume

Para 100.000 mensagens/dia

Assumindo distribuição típica: - 70% texto puro - 20% imagens - 10% áudios

Modelo Chamadas/dia Chamadas/min (pico) Limite Status
Claude Chat 100.000 ~350 4.000 ✅ OK
Claude Vision 20.000 ~70 (compartilhado) ✅ OK
OpenAI Embedding 100.000 ~350 3.000 ✅ OK
Whisper 10.000 ~35 50 ⚠️ LIMITE

Ação necessária para áudios: Implementar Whisper local ou fila com throttling.

Para 500.000 mensagens/dia

Modelo Chamadas/dia Chamadas/min (pico) Limite Status
Claude Chat 500.000 ~1.750 4.000 ✅ OK
Claude Vision 100.000 ~350 (compartilhado) ⚠️ Monitorar
OpenAI Embedding 500.000 ~1.750 3.000 ✅ OK
Whisper 50.000 ~175 50 ❌ CRÍTICO

Ações necessárias: 1. Whisper local obrigatório 2. Rate limiting interno para todos os modelos 3. Fallback chain para Claude e Embedding


1. Evolution API - Desafios de Recursos

O Problema

Cada instância WhatsApp no Evolution API mantém: - 1 conexão WebSocket persistente com WhatsApp Web - ~50-150 MB de RAM por instância (varia com histórico de mensagens) - CPU para criptografia E2E e processamento de mídia - Estado em memória (sessão, chaves, cache de contatos)

Implicação: Um servidor Evolution API com 8GB RAM suporta ~50-100 instâncias WhatsApp simultaneamente.

Limites Práticos por Servidor Evolution

Recurso Limite Recomendado Observação
Instâncias WhatsApp 50-80 Depende da RAM disponível
RAM por instância 100-150 MB Aumenta com histórico
Conexões simultâneas ~100 WebSocket + HTTP
Throughput ~500 msg/min CPU-bound na criptografia

Arquitetura Atual (Single Evolution)

┌─────────────────────────────────────────────────────────────┐
│                    Evolution API (único)                    │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐       ┌─────────┐     │
│  │ WA #1   │ │ WA #2   │ │ WA #3   │  ...  │ WA #N   │     │
│  │ 100MB   │ │ 120MB   │ │ 90MB    │       │ 150MB   │     │
│  └─────────┘ └─────────┘ └─────────┘       └─────────┘     │
│                                                             │
│  RAM Total: N × ~120MB                                      │
│  CPU: Criptografia E2E + Media Processing                   │
└─────────────────────────────────────────────────────────────┘

Gargalos: - RAM limita número de instâncias - CPU limita throughput de mensagens - Single point of failure - Sem isolamento entre tenants


2. Estratégias de Escalabilidade - Evolution API

2.1 Sharding por Servidor (Recomendado)

Conceito: Distribuir instâncias WhatsApp entre múltiplos servidores Evolution.

┌───────────────────────────────────────────────────────────────────────┐
│                         Load Balancer / Router                        │
│              (roteia baseado em instanceId → servidor)                │
└───────────────────────────────────────────────────────────────────────┘
                    │                    │                    │
        ┌───────────┴───────────┐       │       ┌───────────┴───────────┐
        ▼                       ▼       ▼       ▼                       ▼
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Evolution #1  │       │ Evolution #2  │       │ Evolution #3  │
│ 4 vCPU, 8GB   │       │ 4 vCPU, 8GB   │       │ 4 vCPU, 8GB   │
│               │       │               │       │               │
│ Instâncias:   │       │ Instâncias:   │       │ Instâncias:   │
│ - Tenant A    │       │ - Tenant D    │       │ - Tenant G    │
│ - Tenant B    │       │ - Tenant E    │       │ - Tenant H    │
│ - Tenant C    │       │ - Tenant F    │       │ - Tenant I    │
│ (~60 inst.)   │       │ (~60 inst.)   │       │ (~60 inst.)   │
└───────────────┘       └───────────────┘       └───────────────┘

Implementação no CIBA:

// WhatsAppInstance entity - adicionar campo
public class WhatsAppInstance
{
    // ... campos existentes ...

    /// <summary>
    /// URL do servidor Evolution API onde esta instância está hospedada.
    /// Ex: "https://evo-1.internal:8080"
    /// </summary>
    public string EvolutionServerUrl { get; set; } = null!;
}

// EvolutionApiClient - rotear para servidor correto
public class EvolutionApiClient : IWhatsAppClient
{
    public async Task<SendMessageResult> SendTextMessageAsync(
        WhatsAppInstance instance,  // Passa instância completa
        string phone,
        string text,
        CancellationToken ct)
    {
        // Usa o servidor específico da instância
        var serverUrl = instance.EvolutionServerUrl;
        var client = _httpClientFactory.CreateClient();
        client.BaseAddress = new Uri(serverUrl);

        // ... resto da implementação
    }
}

Roteamento de webhooks:

// Cada Evolution API envia webhooks para o mesmo endpoint
// O CIBA identifica a origem pelo campo "instance" no payload
POST /api/webhook/evolution
{
    "event": "messages.upsert",
    "instance": "tenant-abc-instance-1",  // Identifica origem
    "data": { ... }
}

2.2 Alocação de Servidores por Tenant

Estratégias de distribuição:

Estratégia Quando Usar Implementação
Round-robin Tenants similares Nova instância → próximo servidor com capacidade
Dedicado Tenants enterprise Tenant X sempre no servidor Y
Por região Multi-região Servidor mais próximo geograficamente
Por volume Otimização de recursos Alto volume → servidor dedicado

Tabela de mapeamento (Redis ou DB):

evo:server:evo-1.internal → { capacity: 80, current: 45, region: "us-east" }
evo:server:evo-2.internal → { capacity: 80, current: 62, region: "us-east" }
evo:server:evo-3.internal → { capacity: 80, current: 30, region: "eu-west" }

evo:instance:session-abc → "evo-1.internal"
evo:instance:session-def → "evo-2.internal"

2.3 Auto-scaling Evolution API

Com Kubernetes:

# evolution-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: evolution-api
spec:
  replicas: 3  # Mínimo
  template:
    spec:
      containers:
      - name: evolution
        image: atendai/evolution-api:v2.3.7
        resources:
          requests:
            memory: "4Gi"
            cpu: "2"
          limits:
            memory: "8Gi"
            cpu: "4"
        env:
        - name: STORE_MESSAGES
          value: "false"  # Reduz uso de memória
        - name: STORE_CONTACTS
          value: "false"
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: evolution-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: evolution-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70  # Escala quando RAM > 70%
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60

Sem Kubernetes (VM-based):

Monitoramento:
1. Prometheus coleta métricas de RAM/CPU de cada Evolution
2. Alertmanager dispara quando RAM > 80%
3. Terraform/Ansible provisiona novo servidor
4. Novas instâncias são direcionadas para servidor novo

2.4 Otimização de Recursos no Evolution

Configurações para reduzir uso de memória:

# docker-compose.yml ou .env do Evolution API

# Desabilita armazenamento em memória (usa apenas webhook)
STORE_MESSAGES=false
STORE_CONTACTS=false
STORE_CHATS=false

# Limita histórico (se habilitado)
CLEAN_STORE_MESSAGES=true
CLEAN_STORE_MESSAGE_UP=true
CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true

# Intervalo de limpeza
CLEAN_STORE_CRON="0 */6 * * *"  # A cada 6 horas

# Desabilita features não usadas
CHATWOOT_ENABLED=false
TYPEBOT_ENABLED=false

Impacto:

Configuração RAM economizada Trade-off
STORE_MESSAGES=false ~30-50% Sem histórico local (OK se salva no CIBA)
STORE_CONTACTS=false ~10-20% Precisa buscar contato via API
Clean store habilitado ~20-40% Limpeza periódica

2.5 Persistência de Sessão

Problema: Se Evolution reinicia, instâncias precisam reconectar (QR code novamente).

Soluções:

Solução Implementação Complexidade
Volume persistente Docker volume ou PVC no K8s Baixa
Backup periódico Cron job exporta sessions para S3/R2 Média
Redis/PostgreSQL store Evolution suporta store externo Média
# docker-compose com volume persistente
services:
  evolution:
    image: atendai/evolution-api:v2.3.7
    volumes:
      - evolution_data:/evolution/instances  # Persiste sessões

volumes:
  evolution_data:
    driver: local  # ou EBS/EFS na AWS

3. Escalabilidade - Claude/LLM API

Limites da Anthropic

Modelo RPM TPM Observação
claude-3-5-haiku 4.000 400.000 Recomendado para chatbots
claude-3-5-sonnet 1.000 80.000 Para casos complexos
claude-3-opus 500 40.000 Alto custo, baixo volume

Nota: Limites aumentam com histórico de uso e tier da conta.

3.1 Rate Limiting Interno

Objetivo: Evitar HTTP 429 controlando fluxo antes de bater no limite.

// IRateLimitService.cs
public interface IRateLimitService
{
    Task<bool> TryAcquireAsync(string resource, int cost = 1);
    Task<TimeSpan?> GetRetryAfterAsync(string resource);
}

// Implementação com Redis (sliding window)
public class RedisRateLimitService : IRateLimitService
{
    public async Task<bool> TryAcquireAsync(string resource, int cost = 1)
    {
        var key = $"rate:{resource}";
        var window = TimeSpan.FromMinutes(1);
        var limit = GetLimitForResource(resource);  // Ex: 3000 RPM para Claude

        var script = @"
            local current = redis.call('INCR', KEYS[1])
            if current == 1 then
                redis.call('PEXPIRE', KEYS[1], ARGV[1])
            end
            return current
        ";

        var count = await _redis.ScriptEvaluateAsync(script,
            new[] { key },
            new[] { (RedisValue)window.TotalMilliseconds });

        return (int)count <= limit;
    }
}

// Uso no LLM client
public async Task<LlmResponse> GenerateResponseAsync(...)
{
    // Usa 80% do limite como margem de segurança
    if (!await _rateLimiter.TryAcquireAsync("llm:anthropic"))
    {
        // Tenta fallback
        return await _fallbackClient.GenerateResponseAsync(...);
    }

    return await _claudeClient.GenerateResponseAsync(...);
}

3.2 Provider Fallback Chain

// LlmOptions.cs
public class LlmOptions
{
    public List<LlmProviderConfig> Providers { get; set; } = new()
    {
        new() { Name = "anthropic", Model = "claude-3-5-haiku-latest", Priority = 1 },
        new() { Name = "openai", Model = "gpt-4o-mini", Priority = 2 },
        new() { Name = "groq", Model = "llama-3.1-70b-versatile", Priority = 3 }
    };
}

// LlmGatewayService.cs
public class LlmGatewayService : ILlmClient
{
    public async Task<LlmResponse> GenerateResponseAsync(
        string systemPrompt,
        List<ChatMessage> messages,
        CancellationToken ct)
    {
        var providers = _options.Providers.OrderBy(p => p.Priority);

        foreach (var provider in providers)
        {
            try
            {
                if (!await _rateLimiter.TryAcquireAsync($"llm:{provider.Name}"))
                {
                    _logger.LogWarning("{Provider} rate limited, trying next", provider.Name);
                    continue;
                }

                var client = _clientFactory.GetClient(provider.Name);
                return await client.GenerateResponseAsync(systemPrompt, messages, provider.Model, ct);
            }
            catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
            {
                _logger.LogWarning("{Provider} returned 429, trying next", provider.Name);
                await _circuitBreaker.RecordFailureAsync(provider.Name);
                continue;
            }
        }

        // Todos falharam - enfileira para retry
        throw new AllProvidersUnavailableException();
    }
}

3.3 Token Budget por Tenant

// TenantTokenUsage entity
public class TenantTokenUsage
{
    public Guid TenantId { get; set; }
    public DateOnly Date { get; set; }
    public long InputTokens { get; set; }
    public long OutputTokens { get; set; }
}

// ITenantBudgetService.cs
public interface ITenantBudgetService
{
    Task<bool> HasBudgetAsync(Guid tenantId, int estimatedTokens);
    Task RecordUsageAsync(Guid tenantId, int inputTokens, int outputTokens);
    Task<TenantUsageSummary> GetUsageAsync(Guid tenantId, DateOnly date);
}

// Uso no ProcessMessageHandler
var estimatedTokens = EstimateTokens(systemPrompt, messages);
if (!await _budgetService.HasBudgetAsync(tenant.Id, estimatedTokens))
{
    // Opções:
    // 1. Degradar para modelo mais barato
    // 2. Responder com mensagem de limite
    // 3. Notificar admin do tenant
}

3.4 Caching de Respostas

Cache exato (mesma pergunta):

public async Task<LlmResponse> GenerateWithCacheAsync(
    Guid agentId,
    string userMessage,
    ...)
{
    // Hash da mensagem + agentId (contexto pode variar por agente)
    var cacheKey = $"llm:cache:{agentId}:{ComputeHash(userMessage)}";

    var cached = await _cache.GetAsync<LlmResponse>(cacheKey);
    if (cached != null)
    {
        _logger.LogDebug("LLM cache hit for agent {AgentId}", agentId);
        return cached;
    }

    var response = await _llmClient.GenerateResponseAsync(...);

    // Cache por 1 hora (configurável)
    await _cache.SetAsync(cacheKey, response, TimeSpan.FromHours(1));

    return response;
}

Prompt caching (Anthropic beta):

// Adiciona header para prompt caching
_httpClient.DefaultRequestHeaders.Add("anthropic-beta", "prompt-caching-2024-07-31");

// System prompt com cache_control
var request = new
{
    model = "claude-3-5-haiku-latest",
    max_tokens = 1024,
    system = new[]
    {
        new
        {
            type = "text",
            text = systemPrompt,
            cache_control = new { type = "ephemeral" }  // Cache por 5 min
        }
    },
    messages = chatMessages
};

4. Arquitetura Escalada Completa

                                         ┌─────────────────────────────────────┐
                                         │          Redis Cluster              │
                                         │  - Streams (webhook:messages:*)     │
                                         │  - Rate limits (rate:*)             │
                                         │  - Locks (lock:*)                   │
                                         │  - Cache (llm:cache:*)              │
                                         │  - Instance mapping (evo:*)         │
                                         └─────────────────────────────────────┘
┌────────────────────────────────────────┐               │
│      Evolution API Cluster             │               │
│  ┌──────────┐ ┌──────────┐ ┌────────┐  │               │
│  │ Evo #1   │ │ Evo #2   │ │ Evo #N │  │               │
│  │ 8GB RAM  │ │ 8GB RAM  │ │ 8GB RAM│  │    ┌──────────┴──────────┐
│  │ ~60 inst │ │ ~60 inst │ │ ~60 ins│  │    │                     │
│  └──────────┘ └──────────┘ └────────┘  │    │   Load Balancer     │
│                                        │    │                     │
│  Webhooks: POST /api/webhook/evolution─┼───►│                     │
└────────────────────────────────────────┘    └──────────┬──────────┘
                              ┌───────────────────────────┼───────────────────────────┐
                              │                           │                           │
                              ▼                           ▼                           ▼
                    ┌──────────────────┐       ┌──────────────────┐       ┌──────────────────┐
                    │   Ciba.Api #1    │       │   Ciba.Api #2    │       │   Ciba.Api #N    │
                    │  ┌────────────┐  │       │  ┌────────────┐  │       │  ┌────────────┐  │
                    │  │ Consumers  │  │       │  │ Consumers  │  │       │  │ Consumers  │  │
                    │  │ (4 threads)│  │       │  │ (4 threads)│  │       │  │ (4 threads)│  │
                    │  └────────────┘  │       │  └────────────┘  │       │  └────────────┘  │
                    │  ┌────────────┐  │       │  ┌────────────┐  │       │  ┌────────────┐  │
                    │  │ LLM Gateway│  │       │  │ LLM Gateway│  │       │  │ LLM Gateway│  │
                    │  │ + Fallback │  │       │  │ + Fallback │  │       │  │ + Fallback │  │
                    │  └────────────┘  │       │  └────────────┘  │       │  └────────────┘  │
                    └──────────────────┘       └──────────────────┘       └──────────────────┘
                              │                           │                           │
              ┌───────────────┼───────────────────────────┼───────────────────────────┼───────────────┐
              │               │                           │                           │               │
              ▼               ▼                           ▼                           ▼               ▼
    ┌──────────────┐ ┌──────────────┐           ┌──────────────┐           ┌──────────────┐ ┌──────────────┐
    │ Claude API   │ │ OpenAI API   │           │  Groq API    │           │ PostgreSQL   │ │ R2 Storage   │
    │ (primary)    │ │ (fallback 1) │           │ (fallback 2) │           │ + Replica    │ │ (media)      │
    └──────────────┘ └──────────────┘           └──────────────┘           └──────────────┘ └──────────────┘

5. Estimativas de Capacidade

Evolution API

Volume (msg/dia) Instâncias WA Servidores Evolution RAM Total
10.000 1-2 1 (4GB) 4 GB
50.000 5-10 1 (8GB) 8 GB
100.000 10-20 2-3 (8GB cada) 16-24 GB
500.000 50-100 5-8 (8GB cada) 40-64 GB

Ciba.Api (Consumers)

Volume (msg/dia) Msg/min (pico) Consumers Pods (4 cons/pod)
10.000 ~35 2 1
50.000 ~175 4-6 2
100.000 ~350 8-12 3-4
500.000 ~1.750 20-30 6-8

LLM (Claude Haiku)

Volume (msg/dia) Tokens/dia Custo/dia Custo/mês
10.000 ~12M ~$3 ~$90
50.000 ~60M ~$15 ~$450
100.000 ~120M ~$30 ~$900
500.000 ~600M ~$150 ~$4.500

6. Custos Totais Estimados

Custos de IA por Volume

Fórmulas de Cálculo

Modelo Fórmula Observação
Claude Haiku (input × $0.00025 + output × $0.00125) × msgs ~1200 tokens in, ~200 out
Claude Sonnet (input × $0.003 + output × $0.015) × msgs Só para prompt opt
Embedding $0.00002 × tokens × msgs ~200 tokens/msg
Whisper $0.006 × minutos_audio ~1 min/áudio
Vision (tokens_imagem × $0.00025) × imgs ~1500 tokens/img

100.000 mensagens/dia (70% texto, 20% img, 10% áudio)

Serviço Cálculo Custo/dia Custo/mês
Claude Chat (70k texto) 70k × (1200×$0.00025 + 200×$0.00125) ~$38 ~$1.140
Claude Vision (20k img) 20k × 1500×$0.00025 ~$7.5 ~$225
Embedding (100k) 100k × 200 × $0.00002 ~$0.40 ~$12
Whisper (10k áudios, 1min) 10k × 1 × $0.006 ~$60 ~$1.800
Prompt Opt (batch) ~100 agentes × $0.05 desprezível ~$5
Total IA ~$106/dia ~$3.182/mês

500.000 mensagens/dia

Serviço Cálculo Custo/dia Custo/mês
Claude Chat (350k texto) 350k × (1200×$0.00025 + 200×$0.00125) ~$192 ~$5.760
Claude Vision (100k img) 100k × 1500×$0.00025 ~$37.5 ~$1.125
Embedding (500k) 500k × 200 × $0.00002 ~$2 ~$60
Whisper (50k áudios) 50k × 1 × $0.006 ~$300 ~$9.000
Total IA ~$531/dia ~$15.945/mês

Observação: Whisper é o maior custo em cenários com muitos áudios. Whisper local (GPU) pode reduzir isso a zero.

Custos Totais (Infra + IA)

100k mensagens/dia

Componente Especificação Custo/mês
Evolution API (3x) 4 vCPU, 8GB cada ~$180
Ciba.Api (4 pods) 2 vCPU, 4GB cada ~$160
Redis 4GB, cluster ~$80
PostgreSQL 4 vCPU, 16GB + replica ~$300
Load Balancer Managed ~$30
R2 Storage ~100GB ~$5
Infraestrutura ~$755/mês
Claude (Chat + Vision) ~$1.365/mês
OpenAI (Embedding) ~$12/mês
OpenAI (Whisper) ~$1.800/mês
Total IA ~$3.177/mês
TOTAL GERAL ~$3.932/mês

500k mensagens/dia

Componente Especificação Custo/mês
Evolution API (8x) 4 vCPU, 8GB cada ~$480
Ciba.Api (8 pods) 2 vCPU, 4GB cada ~$320
Redis Cluster 8GB, 3 nodes ~$200
PostgreSQL 8 vCPU, 32GB + 2 replicas ~$600
Load Balancer Managed ~$50
R2 Storage ~500GB ~$25
Whisper GPU Server 1x T4/A10 ~$300
Infraestrutura ~$1.975/mês
Claude (Chat + Vision) ~$6.885/mês
OpenAI (Embedding) ~$60/mês
Whisper Local (incluído na GPU) ~$0
Total IA ~$6.945/mês
TOTAL GERAL ~$8.920/mês

Economia com Whisper local: ~$9.000/mês evitados usando GPU própria (~$300/mês)


7. Checklist de Implementação

Fase 1: Preparação (antes de escalar)

Evolution API: - [ ] Adicionar campo EvolutionServerUrl em WhatsAppInstance - [ ] Modificar EvolutionApiClient para rotear por instância - [ ] Configurar Evolution API com STORE_*=false

Rate Limiting: - [ ] Implementar IRateLimitService com Redis (sliding window) - [ ] Rate limit para Claude Chat (3.200 RPM = 80% do limite) - [ ] Rate limit para OpenAI Embedding (2.400 RPM) - [ ] Rate limit para Whisper (40 RPM = 80% do limite)

Circuit Breaker: - [ ] Circuit breaker no ClaudeClient - [ ] Circuit breaker no OpenAiClient (embedding) - [ ] Circuit breaker no TranscriptionService

Fase 2: Multi-server Evolution

  • [ ] Provisionar servidores Evolution adicionais
  • [ ] Implementar lógica de alocação de instâncias
  • [ ] Configurar volumes persistentes para sessões
  • [ ] Load balancer com sticky sessions por instanceId
  • [ ] Dashboard de monitoramento por servidor

Fase 3: Resiliência de IA

Chat/Vision: - [ ] Implementar LlmGatewayService com fallback chain - [ ] Claude Haiku → Claude Sonnet → OpenAI GPT-4o-mini - [ ] Cache de respostas (hash de agentId + mensagem) - [ ] Prompt caching (anthropic-beta header)

Transcrição (CRÍTICO): - [ ] Implementar fila dedicada webhook:audio - [ ] Throttling: máximo 40 áudios/minuto - [ ] Avaliar Whisper local (faster-whisper + GPU) - [ ] Fallback: AssemblyAI ou Deepgram

Embedding: - [ ] Batching para operações de knowledge block - [ ] Cache por hash do texto - [ ] Fallback: Voyage AI ou Cohere

Vision: - [ ] Rate limit separado para descrição de imagens - [ ] Flag por agente para habilitar/desabilitar - [ ] Fallback: GPT-4o Vision

Fase 4: Token Budget

  • [ ] Tabela TenantTokenUsage (diário)
  • [ ] ITenantBudgetService para controle de quota
  • [ ] Alertas quando tenant atinge 80% do limite
  • [ ] Degradação automática para modelo mais barato

Fase 5: Observabilidade

Métricas por modelo: - [ ] ai_requests_total{provider, model, status} - [ ] ai_tokens_total{provider, model, direction} - [ ] ai_latency_seconds{provider, model} - [ ] ai_rate_limit_hits{provider, model}

Métricas de infra: - [ ] Instâncias por servidor Evolution - [ ] RAM/CPU por servidor - [ ] Mensagens/min processadas

Alertas: - [ ] Rate limit hit > 10/min por modelo - [ ] Circuit breaker aberto - [ ] Latência p99 > 10s - [ ] Token budget excedido por tenant

Dashboards: - [ ] Custos por tenant (tokens consumidos) - [ ] Distribuição de chamadas por modelo - [ ] Throughput de mensagens


8. Decisões Arquiteturais

Por que Whisper local para scale?

Aspecto OpenAI Whisper API Whisper Local (GPU)
Rate limit 50 RPM Ilimitado
Custo 100k áudios/mês ~$18.000 ~$300 (GPU)
Latência ~2-5s ~1-3s
Privacidade Dados na OpenAI Dados locais
Setup Zero Médio (faster-whisper)

Recomendação: Whisper local a partir de 5.000 áudios/dia.

Por que não usar embedding local?

Aspecto OpenAI Embedding Embedding Local
Rate limit 3.000 RPM Ilimitado
Custo 500k/dia ~$60/mês ~$100/mês (GPU)
Qualidade Alta Média-Alta
Setup Zero Alto

Recomendação: OpenAI Embedding é suficiente até 1M+ mensagens/dia.

Fallback chain recomendada

Chat:      Claude Haiku → Claude Sonnet → GPT-4o-mini → Groq Llama
Vision:    Claude → GPT-4o
Embedding: text-embedding-3-small → Voyage AI
Whisper:   Local → OpenAI API → AssemblyAI

9. Referências

APIs e Limites: - Anthropic Rate Limits - OpenAI Rate Limits - Whisper API Limits

Otimizações: - Anthropic Prompt Caching - OpenAI Embedding Best Practices

Whisper Local: - faster-whisper - 4x mais rápido que OpenAI - whisper.cpp - CPU otimizado

Infraestrutura: - Evolution API Documentation - WhatsApp Business Messaging Limits - Redis Streams - Polly Circuit Breaker

Providers Alternativos: - AssemblyAI - Transcrição (mais RPM) - Deepgram - Transcrição (real-time) - Voyage AI - Embeddings (alta qualidade) - Groq - LLM (muito rápido)