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 ─── CustoVocê 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 fallbackPadrõ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 limiteArmadilhas
⚠️ 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.
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.