mozak.tech Engenharia de IA Corporativa 100%

Parte V — Projetando Sistemas Inteligentes para Produção

5.5 — Arquitetura Corporativa de IA: Camadas de Modelo e Registro de Auditoria (Enterprise AI)

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 minuto

Sliding 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ás

JSONL 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 excedido

Exemplo 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 write

Padrã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_tokens

Armadilhas

⚠️ 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.

⚗ Laboratório prático — mozak.tech
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.