Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Implementar selecionarModeloPorTiering() com lógica free/pro/enterprise e degradação a 90%
Implementar auditLog() que persiste eventos em arquivo JSONL imutável
Entender Rate Limiter com sliding window e por que não é suficiente sem tiering
Completar os 2 TODOs do starter TypeScript
Projetar pipeline enterprise com Rate Limiter → Model Tiering → LLM → Observabilidade
Por que isso importa
Decisão de Arquitetura: Model Tiering Multi-Tenant
Em sistemas SaaS com IA, o custo de inferência varia por modelo em até 50x (Haiku vs Opus). Sem controle, um plano gratuito pode consumir o mesmo compute que um enterprise. Model tiering define: plano do cliente + complexidade da query → modelo a usar. Regras típicas: free nunca usa acima do modelo mais barato; pro usa o intermediário; enterprise tem acesso ao mais capaz. Degradação automática: quando um tenant atinge 90% da cota de tokens do mês, força automaticamente para o modelo mais barato. Campos obrigatórios no audit log de LLM para compliance (LGPD, SOX, ISO 27001): requestId, tenantId, userId, modelo usado, tokens de input/output, custo em USD, latência, hash do prompt, classificação do output. O audit log deve ser imutável — escreva em storage append-only.
Em produção multi-tenant, cada cliente tem um plano diferente. Free users que acessam Opus colocam a fatura da empresa em risco. Enterprise users que recebem Haiku pagando por Opus é violação de contrato. Model Tiering é a camada que garante que cada cliente receba exatamente o que pagou.
Audit log é obrigatório em ambientes regulados (LGPD, SOX, ISO 27001) — toda ação do sistema precisa ser rastreável por auditores externos.
Conceitos Fundamentais
TODO 1: Model Tiering por Plano
function selecionarModeloPorTiering(clienteId: string, complexidade: 'baixa' | 'media' | 'alta'): string {
const cliente = CLIENTES.get(clienteId)
if (!cliente) {
console.log(` [TIERING] Cliente desconhecido — usando haiku`)
return 'claude-haiku-4-5-20251001'
}
// Verificar utilização
const pctUsado = cliente.tokens_usados_hoje / cliente.limite_tokens_dia
const sobCotaAlta = pctUsado > 0.9
if (sobCotaAlta) {
console.log(` [TIERING] ${clienteId} em ${Math.round(pctUsado * 100)}% da cota — forçando haiku`)
return 'claude-haiku-4-5-20251001'
}
// Mapa: plano × complexidade → modelo
const TIERING_MAP: Record<string, Record<string, string>> = {
free: {
baixa: 'claude-haiku-4-5-20251001',
media: 'claude-haiku-4-5-20251001', // free nunca acima de haiku
alta: 'claude-haiku-4-5-20251001',
},
pro: {
baixa: 'claude-haiku-4-5-20251001',
media: 'claude-haiku-4-5-20251001', // usar sonnet em produção real
alta: 'claude-haiku-4-5-20251001', // usar sonnet em produção real
},
enterprise: {
baixa: 'claude-haiku-4-5-20251001',
media: 'claude-haiku-4-5-20251001', // usar sonnet em produção real
alta: 'claude-haiku-4-5-20251001', // usar opus em produção real
},
}
const modelo = TIERING_MAP[cliente.plano]?.[complexidade] || 'claude-haiku-4-5-20251001'
console.log(` [TIERING] ${clienteId} (${cliente.plano}/${complexidade}) → ${modelo}`)
return modelo
}Em produção real: substitua os valores do mapa por claude-sonnet-4-6 para pro/media/alta e claude-opus-4-8 para enterprise/alta.
TODO 2: Audit Log JSONL
import * as fs from 'fs'
import * as path from 'path'
const AUDIT_LOG_PATH = path.join(process.cwd(), 'audit.jsonl')
function auditLog(evento: object) {
const entrada = {
timestamp: new Date().toISOString(),
...evento,
}
// Modo 'a' (append): nunca sobrescreve, adiciona ao final
fs.appendFileSync(AUDIT_LOG_PATH, JSON.stringify(entrada) + '\n', { encoding: 'utf-8' })
// Também logar no console para debugging
console.log(` [AUDIT] ${JSON.stringify(entrada).slice(0, 100)}`)
}Campos obrigatórios num audit log de LLM enterprise:
auditLog({
requestId: 'req-1234',
clienteId: 'user-pro-1',
acao: 'llm_call',
modelo: 'claude-haiku-4-5-20251001',
tokens_input: 150,
tokens_output: 300,
custo_usd: 0.000165,
latencia_ms: 850,
resultado: 'success', // ou 'error', 'rate_limited'
})Aprofundamento Técnico
Rate Limiter: Sliding Window vs Fixed Window
Fixed window (simples, mas com bug no limite):
Janela 00:00-01:00: 10 requests
Janela 01:00-02:00: 10 requests
Problema: 10 no final da janela 1 + 10 no início da janela 2 = 20 em 1 minutoSliding window (implementado no starter):
const timestamps = (this.janelas.get(clienteId) || []).filter(t => agora - t < this.janelaMs)
// Remove timestamps fora da janela atual — sempre considera exatamente 1 minuto atrásJSONL como Audit Trail
// audit.jsonl
{"timestamp":"2026-01-15T10:00:01Z","clienteId":"user-free-1","acao":"llm_call","custo":0.0001}
{"timestamp":"2026-01-15T10:00:03Z","clienteId":"user-pro-1","acao":"llm_call","custo":0.0015}
{"timestamp":"2026-01-15T10:00:05Z","clienteId":"user-free-1","acao":"rate_limited","custo":0}JSONL é preferível a JSON para logs porque: - Pode ser lido linha a linha (streaming) - Não precisa de lock para append - Fácil de processar com grep, jq, pandas
Exemplos Anotados
Exemplo 1: Pipeline Completo para 3 Clientes
[user-free-1] "O que é IA?"
[TIERING] free/baixa → haiku
[AUDIT] {"acao":"llm_call","modelo":"haiku","custo":0.0001}
✓ Resposta em 450ms
[user-pro-1] "Explique RAG em detalhes" (complexidade: media)
[TIERING] pro/media → sonnet (haiku no lab)
[AUDIT] {"acao":"llm_call","modelo":"sonnet","custo":0.0045}
✓ Resposta em 1200ms
[user-free-1] (6ª request em 60s)
[RATE LIMITER] free-1: 0 restante
[AUDIT] {"acao":"rate_limited","custo":0}
✗ Rate limit excedidoExemplo 2: Degradação por Cota
[user-free-1] tokens usados: 9200/10000 = 92% → acima de 90%
[TIERING] user-free-1 em 92% — forçando haiku (mesmo que plano permitisse mais)Padrões e Armadilhas
Padrões
Padrão 1: Audit log imutável com append-only
fs.appendFileSync(path, linha) // sempre append, nunca writePadrão 2: Tokens usados hoje: atualizar após cada request
const config = CLIENTES.get(clienteId)!
config.tokens_usados_hoje += response.usage.input_tokens + response.usage.output_tokensArmadilhas
⚠️ Armadilha 1: Audit log síncrono pode atrasar response Em produção de alta frequência, use appendFile assíncrono ou enfileire logs.
⚠️ Armadilha 2: Tiering sem atualizar tokens_usados_hoje Se não atualizar o contador, o degradation-at-90% nunca dispara.
⚠️ Armadilha 3: Rate limiter sem cleanup de timestamps antigos
// CRÍTICO: filtrar timestamps fora da janela antes de contar
const timestamps = (janelas.get(id) || []).filter(t => agora - t < janelaMs)Sem esse filtro, os timestamps acumulam indefinidamente.
A arquitetura corporativa define o sistema — a Parte IX completa o quadro mostrando como especializar os próprios modelos que esse sistema serve, quando os modelos genéricos não são suficientes.
Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
TODO 1 — selecionarModeloPorTiering(clienteId, complexidade): busque o cliente em CLIENTES, calcule pctUsado, se >90% → haiku, senão → mapa plano × complexidade.
TODO 2 — auditLog(evento): use fs.appendFileSync com JSON.stringify(entrada) + '\n' para gerar linha JSONL com timestamp e campos do evento.
Agora você está pronto para o lab.