mozak.tech Engenharia de IA Corporativa 89%

Parte V — Projetando Sistemas Inteligentes para Produção

5.1 — Princípios de Arquitetura para Sistemas com Modelos em Produção (AI-first Architecture)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Aplicar princípios AI-first no design de sistemas com LLMs

Projetar para latência variável sem degradar UX

Implementar idempotência em pipelines de LLM

Calcular custo como constraint arquitetural (não como afterthought)

Criar fallbacks que não degradam gravemente a experiência do usuário

Por que isso importa

Decisão de Arquitetura: LLM como Dependência Não-Determinística

Um LLM não se comporta como um serviço REST convencional. Quatro propriedades que mudam suas decisões de arquitetura:
1. Latência variável (500ms a 30s): você não tem um p99 estável. Use streaming por padrão — entregue tokens conforme chegam em vez de esperar a resposta completa.
2. Não-determinismo: mesma entrada → saídas diferentes. Cache por hash do input não funciona; idempotência exige cache por requestId.
3. Custo proporcional por chamada: não há custo marginal zero. Defina orçamento de tokens por feature e monitore.
4. Falhas de conteúdo: HTTP 200 não garante conteúdo correto. Valide o output, não só o status.
Checklist AI-first: fallback em camadas com pelo menos um nível determinístico garantido; circuit breaker por custo acumulado; logging de input/output/tokens/latência por request.

Sistemas com LLMs têm características únicas que quebram as heurísticas de sistemas tradicionais: - Latência: 500ms a 30s para o mesmo tipo de request - Determinismo: mesma entrada gera saídas diferentes - Custo por call: cada request custa dinheiro de forma proporcional ao output - Falhas de conteúdo: o sistema pode “funcionar” tecnicamente mas produzir output incorreto

Ignorar essas características leva a sistemas frágeis. Sistemas AI-first incorporam essas restrições desde o design inicial.

Conceitos Fundamentais

Latência Variável: Streaming como Padrão

// NÃO faça isso para respostas longas:

const response = await client.messages.create({ max_tokens: 2000, ... })

res.json({ texto: response.content[0].text })  // usuário espera 20s sem feedback



// Faça streaming:

const stream = client.messages.stream({ max_tokens: 2000, ... })

for await (const chunk of stream) {

  if (chunk.type === 'content_block_delta') {

    res.write(chunk.delta.text)  // usuário vê tokens chegando

  }

}

Regra: se o output pode levar mais de 2 segundos, use streaming ou progress indicator.

Idempotência em Pipelines

// Pipeline com retry seguro:

async function processarComIdempotencia(requestId: string, input: string): Promise<string> {

  // 1. Verificar cache antes de chamar LLM

  const cached = await cache.get(requestId)

  if (cached) return cached

  

  // 2. Chamar LLM

  const response = await client.messages.create({ ... })

  const output = response.content[0].text

  

  // 3. Salvar resultado (idempotente: se chamar de novo com mesmo requestId, retorna cache)

  await cache.set(requestId, output, { ttl: 3600 })

  return output

}

Se o sistema cair e o retry acontecer com o mesmo requestId, o resultado é o mesmo. Sem idempotência: custa 2x e pode gerar outputs inconsistentes.

Custo como Constraint Arquitetural

// Orçamento de tokens por feature:

const BUDGET = {

  summary_page: { input: 2000, output: 300, modelo: 'haiku' },       // $0.001

  analysis_report: { input: 8000, output: 1000, modelo: 'sonnet' },  // $0.05

  code_review: { input: 15000, output: 2000, modelo: 'opus' },       // $0.25

}



// Budget exceeded → fallback para modelo mais barato:

function selecionarModelo(feature: string, tokens_estimados: number): string {

  const budget = BUDGET[feature]

  if (tokens_estimados > budget.input * 1.2) return 'haiku'  // over budget

  return budget.modelo

}

Defina orçamentos antes de implementar, não depois de receber a fatura.

Fallback sem Degradação Grave

// 3 níveis de fallback:

async function gerarResumo(texto: string): Promise<string> {

  try {

    // Nível 1: modelo ideal

    return await resumirComSonnet(texto)

  } catch {

    try {

      // Nível 2: modelo mais barato/simples

      return await resumirComHaiku(texto)

    } catch {

      // Nível 3: fallback determinístico (nunca falha)

      return texto.slice(0, 200) + '...'

    }

  }

}

Regra do fallback: o nível 3 deve ser sempre disponível, nunca chamar LLM, e oferecer valor mínimo aceitável.

Aprofundamento Técnico

O Triângulo Impossível dos LLMs

        Qualidade

           △

          / \

         /   \

        /     \

Velocidade ─── Custo

Você pode otimizar 2 desses 3, não os 3 simultaneamente: - Alta qualidade + rápido = modelo poderoso com hardware dedicado (caro) - Alta qualidade + barato = modelo grande com cache agressivo (lento sem cache) - Rápido + barato = modelo pequeno (qualidade comprometida)

Non-Determinismo como Feature

// Não use LLM para decisões que exigem reprodutibilidade:

// ❌ Calcular imposto = LLM não é determinístico

// ✅ Gerar explicação sobre imposto = variação OK



// Para decisões críticas: use seed ou cache output

const response = await client.messages.create({

  messages: [...],

  // claude não suporta seed, mas você pode usar cache semântico

})

Projete para aceitar variação de output — teste deve verificar qualidade, não exatidão textual.

Exemplos Anotados

Exemplo 1: Design de Feature AI-First

Feature: "Resumir contrato de 50 páginas"



Decisão AI-First:

1. Custo: 50 páginas ≈ 40.000 tokens → usar Haiku ($0.04) não Opus ($3.00)

2. Latência: 40K tokens → 30-60s → STREAMING obrigatório

3. Idempotência: mesmo contrato = mesmo resumo → cache por hash do PDF

4. Fallback: se LLM falhar → retornar primeiras 500 palavras + aviso



Não AI-First:

"Vamos usar GPT-4 para resumir" → $3 por uso, 45s sem feedback, sem cache, sem fallback

Padrões e Armadilhas

Padrões

Padrão 1: Cache by content hash

const chave = crypto.createHash('md5').update(texto).digest('hex')

const cacheResult = await redis.get(chave)

Padrão 2: Orçamento por tier de usuário

Free: Haiku only, max 500 tokens output

Pro: Sonnet, max 2000 tokens

Enterprise: Opus, sem limite

Armadilhas

⚠️ Armadilha 1: Rate limit como surprise Planeje rate limiting antes do lançamento. APIs externas têm limites, não é bug — é feature que você precisa respeitar.

⚠️ Armadilha 2: Timeout muito curto para LLMs

// HTTP timeout padrão de 5s vai quebrar geração de texto longo

const response = await fetch(url, { signal: AbortSignal.timeout(60_000) })

⚠️ Armadilha 3: Log de LLM com dados sensíveis Prompts contêm dados do usuário. Configure log scrubbing para CPF, email, tokens de acesso.

⚗ 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

Esta unidade é conceitual — não há starter para executar. Use os conceitos desta aula ao analisar o código dos starters das próximas unidades:

Unidade 02: veja como Memory-Enhanced Agent gerencia estado (latência e idempotência)

Unidade 04: veja Model Router implementando custo como constraint

Unidade 05: veja como Rate Limiter e Model Tiering implementam orçamento por plano

Agora você está pronto para o lab.