Escalabilidade: Evolution API e LLMs¶
Objetivo¶
Documentar as medidas arquiteturais para escalar o CIBA suportando centenas de milhares de mensagens diárias, considerando:
- Evolution API - Recursos (CPU, memória, WebSocket connections) e rate limits
- 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"] }
]
}
2. Embedding (Busca Semântica)¶
// appsettings.json → KnowledgeRetrieval.Embedding
{
"Provider": "openai",
"Model": "text-embedding-3-small",
"Dimensions": 1536,
"MaxTokens": 8191
}
3. Chunking (Divisão de Conhecimento)¶
// appsettings.json → KnowledgeRetrieval.Chunking
{
"Provider": "openai",
"Model": "gpt-4.1",
"MaxSize": 4000,
"MaxOutputTokens": 8192
}
4. Transcrição de Áudio¶
// appsettings.json → Transcription
{
"Provider": "openai",
"Model": "whisper-1",
"DefaultLanguage": "pt",
"TimeoutSeconds": 60
}
5. Otimização de Prompt¶
// appsettings.json → PromptOptimization
{
"Provider": "anthropic",
"Model": "claude-sonnet-4-5-20250929",
"MaxOutputTokens": 4096,
"MinPromptLength": 500
}
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);
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) - [ ]
ITenantBudgetServicepara 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)