Padrões de Design de Sistemas com LLM

Visão geral

Modelos de Linguagem de Grande Porte (LLMs, Large Language Models) são sistemas probabilísticos: podem ser muito capazes, mas ocasionalmente errados, inseguros, lentos ou caros. Como resultado, aplicações de LLM em produção raramente são “um prompt para um modelo”. Sistemas robustos compõem múltiplos componentes — modelos, ferramentas, recuperação, checagens de política, caches e escalonamento para humanos — usando padrões arquiteturais recorrentes.

Este artigo apresenta padrões comuns de design de sistemas de LLMroteadores, cascatas, fallbacks, guardrails, e blocos de construção relacionados — e explica quando usá-los, como funcionam e o que medir em produção. Ele foca em arquitetura de sistema em vez de detalhes internos do modelo (veja Arquitetura Transformer) e assume que você opera LLMs como parte de um serviço (veja Serviço de LLM).

Por que padrões de design importam para apps com LLM

Serviços tradicionais falham de maneiras previsíveis (timeouts, exceções, entradas inválidas). Serviços de LLM também falham de maneiras semânticas:

  • Alucinação / afirmações sem suporte
  • Falhas de seguimento de instruções (ignorar restrições, formato errado)
  • Violações de segurança e política
  • Uso indevido de ferramentas (chamar ferramentas com argumentos errados, loops intermináveis)
  • Não determinismo (mesmo prompt → saídas diferentes)
  • Variação de custo/latência (prompts com muitos tokens, saídas longas)

Padrões de design ajudam você a atender requisitos não funcionais:

  • Confiabilidade: taxa de sucesso, comportamento seguro, degradação graciosa
  • Qualidade: correção, aderência às fontes, utilidade
  • Latência: tempos de resposta p95/p99, mitigação de cauda
  • Custo: gasto de tokens, chamadas de ferramentas, utilização de GPU (veja Custo/Desempenho)
  • Governança: auditabilidade, privacidade, conformidade (veja Privacidade em Logs)

Um bom modelo mental: um app de LLM é um sistema de decisão sob incerteza com orçamentos (latência/custo) e políticas (segurança/conformidade). Padrões são a “lógica de controle” ao redor do modelo.

Blocos de construção centrais (conceituais)

Antes dos padrões, ajuda nomear os primitivos que eles compõem:

  • Chamada de modelo: prompt → conclusão (possivelmente saída estruturada)
  • Chamada de ferramenta: o LLM escolhe uma invocação de função/API (busca, BD, executor de código)
  • Recuperação: buscar documentos/contexto (RAG, Retrieval-Augmented Generation) e citar fontes
  • Estado: memória da conversa, perfil do usuário, dados da sessão
  • Políticas: conteúdo permitido, ferramentas permitidas, limites de taxa
  • Pontuação: sinais de confiança/qualidade/segurança vindos de heurísticas ou modelos secundários
  • Orquestrador: o plano de controle que roteia, faz retries e registra logs

Muitos padrões são apenas diferentes formas de combinar roteamento + pontuação + restrições.

Padrão 1: Roteadores (roteamento por tarefa/modelo/ferramenta)

O que é

Um roteador seleciona qual caminho seguir — por exemplo, qual modelo, template de prompt, cadeia de ferramentas ou estratégia de recuperação — com base em características da requisição.

Roteadores aparecem como:

  • Roteadores de modelo: escolhem entre modelos “pequenos/rápidos” vs “grandes/fortes”
  • Roteadores de ferramentas: decidem se/quais ferramentas chamar (busca, SQL, calculadora)
  • Roteadores de prompt: escolhem prompts de sistema especializados por tarefa (suporte, programação, sumarização)
  • Roteadores de política: enviam requisições de alto risco para pipelines mais restritos

Por que funciona (fundamentos)

Roteamento é uma versão prática da ideia de “mistura de especialistas” (mixture of experts): diferentes “especialistas” lidam melhor com diferentes entradas. Em produção, o roteador costuma ser um classificador leve (regras, similaridade por embeddings, modelo pequeno), porque você quer que o roteamento seja rápido e barato.

Sinais comuns para roteamento

  • Intenção do usuário (classificação)
  • Domínio (cobrança vs suporte técnico)
  • Nível de risco (autoagressão, médico, jurídico)
  • Ferramentas necessárias (precisa de base de dados vs texto puro)
  • Tamanho do contexto (cabe na janela do modelo pequeno?)
  • Orçamento de latência/custo (camada gratuita vs enterprise)

Exemplo: roteamento de modelo por complexidade e risco

def route_request(req):
    # Fast heuristics first
    if req.user_tier == "free":
        budget = "low"
    else:
        budget = "high"

    if looks_like_high_risk(req.text):  # policy classifier
        return "safe_pipeline"

    if is_simple_faq(req.text) and budget == "low":
        return "small_model_no_tools"

    if needs_database(req.text):
        return "tool_sql_pipeline"

    return "large_model_with_retrieval"

Notas práticas

  • Comece com regras + logging; evolua para um roteador aprendido conforme os dados se acumulam (veja Volantes de Dados).
  • Adicione uma rota “desconhecido/outro” com padrões conservadores.
  • Avalie explicitamente as decisões do roteador (roteamentos incorretos são um modo de falha importante).

Padrão 2: Cascatas (aprimoramento progressivo)

O que é

Uma cascata tenta opções mais baratas/rápidas primeiro e então escala para opções mais caras/precisas apenas se necessário.

Níveis típicos de cascata:

  1. Cache / resposta conhecida / template
  2. Modelo pequeno
  3. Modelo médio ou modelo pequeno com ferramentas
  4. Modelo grande com recuperação + ferramentas
  5. Escalonamento humano

Por que funciona

A maior parte do tráfego em produção é “fácil”. Cascatas reduzem custo e latência médios, preservando a qualidade para casos difíceis.

Requisito-chave: uma checagem de gate/aceitação

Cascatas precisam de uma forma de decidir: “Esta resposta está boa o suficiente?” Essa checagem pode ser:

  • Validação estruturada (schema, parse de JSON)
  • Checagens de citação/aderência às fontes (fontes presentes)
  • Checagens heurísticas (tamanho, frases proibidas)
  • Um modelo verificador (verifier model) (LLM como juiz ou classificador)
  • Confirmação visível ao usuário (“Isso resolveu seu problema?”)

Exemplo: cascata com um verificador

def answer_with_cascade(question):
    # Level 1: cache
    cached = cache.get(question)
    if cached:
        return cached

    # Level 2: small model draft
    draft = small_llm.generate(question)

    if verifier.is_acceptable(question, draft):
        cache.set(question, draft)
        return draft

    # Level 3: large model with retrieval
    ctx = retriever.fetch(question, k=8)
    final = large_llm.generate(render_prompt(question, ctx))

    if verifier.is_acceptable(question, final):
        return final

    # Level 4: fallback response / escalation
    return "I can't answer that reliably. Please contact support."

Notas práticas

  • Cascatas podem criar latência de cauda (tail latency) se muitas requisições escalarem. Use orçamentos de tempo e cortes antecipados.
  • Registre logs em cada nível: a taxa de escalonamento é uma métrica-chave de custo/qualidade.
  • Combine com Cache & Limitação de Taxa para manter o gasto previsível.

Padrão 3: Fallbacks (degradação graciosa)

O que é

Um fallback é um comportamento alternativo quando algo dá errado: timeouts, falhas de ferramentas, bloqueios de segurança, modelos sobrecarregados ou baixa confiança.

Tipos comuns de fallback:

  • Fallback de modelo: alternar provedores/regiões ou usar um modelo menor
  • Fallback de ferramenta: se a busca na web falhar, responder sem ela (com ressalvas)
  • Fallback de formato: se o parse de JSON falhar, pedir novamente com restrições mais rígidas
  • Fallback de capacidade: retornar resultados parciais, fazer perguntas de esclarecimento
  • Fallback para humano: ticket de escalonamento ou handoff

Exemplo: fallback com retries e orçamento de tempo

import time

def robust_generate(llm, prompt, timeout_s=8):
    start = time.time()
    try:
        return llm.generate(prompt, timeout=timeout_s)
    except TimeoutError:
        # Fallback to faster model within remaining budget
        remaining = max(1, timeout_s - (time.time() - start))
        return fast_llm.generate(prompt, timeout=remaining)

def answer(req):
    try:
        return robust_generate(primary_llm, req.prompt)
    except Exception:
        return "I'm having trouble right now. Please try again shortly."

Notas práticas

  • Implemente disjuntores (circuit breakers): se uma dependência estiver falhando, pare de chamá-la temporariamente.
  • Torne fallbacks explícitos na UX quando a precisão importar (“Não consigo verificar isto agora…”).
  • Meça taxas de fallback e correlacione com incidentes (veja Monitoramento).

Padrão 4: Guardrails (restrições de política, segurança e correção)

O que é

Guardrails restringem entradas, saídas e ações de ferramentas para manter o sistema seguro, conforme e bem formatado.

Guardrails comumente incluem:

  1. Guardrails de entrada

    • Detecção/remoção de PII (Personally Identifiable Information)
    • Detecção de injeção de prompt (prompt injection)
    • Classificação de política (autoagressão, ódio, conteúdo sexual etc.)
    • Limites de taxa e prevenção de abuso
  2. Guardrails de ferramentas

    • Lista de permissão (allowlist) de ferramentas por rota
    • Validar argumentos de ferramentas contra schemas
    • Aplicar privilégio mínimo (consultas somente leitura no BD)
    • Isolamento (sandboxing) (restrições de execução de código)
  3. Guardrails de saída

    • Moderação de conteúdo e checagens de política
    • Validação de saída estruturada (schema JSON)
    • Requisitos de aderência às fontes (“deve citar fontes do conjunto recuperado”)
    • Afirmações proibidas (ressalvas para conselhos médicos/jurídicos)

Por que funciona (fundamentos)

LLMs otimizam por texto plausível, não por conformidade com políticas. Guardrails adicionam restrições determinísticas e checagens independentes — semelhante a validação de entrada e autorização em sistemas clássicos, mas estendido para linguagem natural e ações de ferramentas.

Exemplo: saída estruturada + validação de schema

Se você precisa de uma resposta com parse confiável, exija JSON e valide:

from jsonschema import validate, ValidationError

RESPONSE_SCHEMA = {
    "type": "object",
    "properties": {
        "answer": {"type": "string"},
        "citations": {"type": "array", "items": {"type": "string"}},
        "confidence": {"type": "number", "minimum": 0, "maximum": 1}
    },
    "required": ["answer", "citations", "confidence"],
    "additionalProperties": False
}

def generate_structured(prompt):
    raw = llm.generate(prompt)
    data = parse_json(raw)  # your robust parser
    validate(instance=data, schema=RESPONSE_SCHEMA)
    return data

def safe_generate(prompt, max_attempts=2):
    for _ in range(max_attempts):
        try:
            return generate_structured(prompt)
        except (ValueError, ValidationError):
            prompt = prompt + "\nReturn ONLY valid JSON matching the schema."
    raise RuntimeError("Could not produce valid output")

Notas práticas

  • Guardrails não são apenas filtros de moderação; são contratos do sistema.
  • Evite “um prompt gigante” que tenta fazer tudo. Coloque restrições em validadores e camadas de política.
  • Faça red team de injeção de prompt e abuso de ferramentas explicitamente; trate como trabalho de segurança, não como ajuste de prompt.

Padrão 5: O “Juiz/Verificador” (autochecagem e pontuação independente)

O que é

Um verificador é um componente que avalia a saída candidata e decide se deve aceitar, tentar novamente, escalar ou recusar.

Variações:

  • LLM como juiz (LLM-as-judge): um modelo separado pontua utilidade/aderência às fontes/política
  • Classificadores especializados: toxicidade, PII, detecção de jailbreak
  • Checagens determinísticas: citações presentes, restrições atendidas, testes passando (para código)

Exemplo: geração de código com testes como verificador

Para assistentes de programação, o melhor verificador costuma ser a execução:

  1. Gerar o código
  2. Rodar testes unitários em um sandbox
  3. Se falhar, alimentar as falhas de volta para correção (iterações limitadas)

Esse padrão é mais confiável do que pedir “por favor, revise seu trabalho” no prompt.

Notas práticas

  • Verificadores devem ser independentes: use prompts/modelos/sinais diferentes para reduzir falhas correlacionadas.
  • Limite o número de loops de reparo para evitar custos e latência descontrolados.

Padrão 6: Agentes com ferramentas e autonomia restrita

O que é

Um “agente” planeja iterativamente, chama ferramentas e atualiza seu estado. Em produção, o ponto-chave é autonomia restrita: agentes devem ser guiados por orçamentos, guardrails e conjuntos de ferramentas específicos por tarefa.

Subpadrões úteis:

  • Allowlists de ferramentas por rota (o roteador decide o conjunto de ferramentas)
  • Orçamentos de passos (máx. chamadas de ferramenta, máx. tokens, tempo de relógio)
  • Checkpoints de estado (persistir etapas intermediárias para depuração)
  • Execução determinística de ferramentas (validar args, tratar erros de ferramentas)

Exemplo: loop de agente restrito (pseudocódigo)

def run_agent(task, tools, max_steps=5, max_tool_calls=3):
    tool_calls = 0
    state = {"task": task, "notes": []}

    for step in range(max_steps):
        action = planner_llm.decide_next_action(state, tools)

        if action.type == "FINAL":
            return action.output

        if action.type == "TOOL_CALL":
            if action.tool_name not in tools:
                state["notes"].append("Blocked: tool not allowed")
                continue

            tool_calls += 1
            if tool_calls > max_tool_calls:
                return "I couldn't complete this within the allowed steps."

            args = validate_args(action.tool_name, action.args)
            result = tools[action.tool_name](/ia-generativa/recuperacao-ferramentas/chamada-de-funcoes-uso-de-ferramentas)
            state["notes"].append({"tool": action.tool_name, "result": result})

Notas práticas

  • Se você consegue expressar o workflow como um DAG fixo, prefira isso a um agente totalmente livre.
  • Registre argumentos e resultados de ferramentas com cuidado e controles de privacidade (veja Privacidade em Logs).

Padrão 7: Recuperação primeiro vs geração primeiro (gate de RAG)

O que é

Muitos sistemas precisam decidir se respondem com base em conhecimento interno ou em fontes recuperadas. Dois designs comuns:

  • Recuperação primeiro: sempre recuperar; LLM responde ancorado em fontes
  • Geração primeiro com fallback para recuperação: rascunhar resposta; se baixa confiança ou precisar de fatos, recuperar e regenerar

Como escolher entre eles

  • Use recuperação primeiro para QA corporativo/documentos, conformidade e casos de “deve citar fontes”.
  • Use geração primeiro para tarefas criativas ou quando a recuperação é cara/ruidosa.

Um roteador pode decidir a estratégia de recuperação com base na intenção e no benefício esperado.

Padrão 8: Prompting em múltiplas passagens (rascunho → refinar → formatar)

O que é

Em vez de um prompt, use etapas:

  1. Rascunhar conteúdo
  2. Criticar/refinar (opcionalmente com um modelo diferente)
  3. Formatar para um schema ou guia de estilo

Isso melhora a consistência e facilita o tratamento de falhas (você pode repetir apenas a etapa de formatação).

Notas práticas

  • Trate cada etapa como um componente com seus próprios testes (veja PromptOps).
  • Observe o custo: pipelines de múltiplas passagens podem silenciosamente dobrar ou triplicar tokens.

Padrão 9: Cache e memoização (semântica e exata)

O que é

Cache reduz custo e latência ao reutilizar resultados anteriores:

  • Cache exato: prompt idêntico → resposta idêntica (funciona melhor quando temperature=0)
  • Cache semântico: similaridade por embeddings → reutilizar/adaptar respostas anteriores

Cache frequentemente faz parte de cascatas (Nível 1).

Notas práticas

  • A invalidação de cache é mais difícil com ferramentas/recuperação dinâmicas. Inclua versionamento de contexto (hash do corpus de documentos, timestamp de dados da ferramenta).
  • Garanta que respostas em cache ainda satisfaçam as políticas atuais.

(Aprofundamento: Cache & Limitação de Taxa.)

Padrão 10: Escalonamento humano no loop

O que é

Quando correção ou segurança são críticas, roteie casos incertos ou de alto impacto para humanos.

Gatilhos comuns:

  • Baixas pontuações de confiança
  • Domínios de alto risco (médico/jurídico/finanças)
  • Ausência de fontes para uma solicitação “deve citar”
  • Insatisfação repetida do usuário

Notas práticas

  • Projete uma UX de operador que mostre: solicitação do usuário, contexto recuperado, saída do modelo, rastros de ferramentas e por que escalou.
  • Capture resultados como dados rotulados para melhorar roteadores/verificadores (veja Volantes de Dados).

Padrão 11: Canary, shadow e rollback para mudanças em LLM

O que é

Apps de LLM mudam com frequência: prompts, roteadores, índices de recuperação, modelos. Trate como releases de produção:

  • Canary: pequena % do tráfego recebe o novo pipeline
  • Shadow: executar o novo pipeline em paralelo, sem mostrar a saída (comparar offline)
  • Rollback: reversão rápida para a última configuração conhecida como boa

Isso depende de forte rastreamento de experimentos e versionamento (veja Rastreamento de Experimentos, Versionamento (Dados, Código, Modelos) e Registro de Modelos).

Notas práticas

  • “Mudanças apenas no prompt” podem ser de alto risco. Teste-as como código.
  • Execuções shadow são valiosas para avaliar novos roteadores/verificadores sem impacto ao usuário.

Preocupações transversais: o que medir e registrar em logs

Padrões robustos exigem ciclos de feedback. No mínimo, capture:

  • Latência: ponta a ponta e por etapa (roteador, recuperação, modelo, ferramentas)
  • Custo: tokens de entrada/saída, chamadas de ferramentas, consultas de recuperação
  • Sinais de qualidade: avaliações de usuários, sucesso da tarefa, pontuações do verificador
  • Sinais de segurança: flags de moderação, saídas bloqueadas, tentativas de jailbreak
  • Decisões de roteamento: qual rota, por quê, features usadas
  • Escalonamentos: nível de cascata alcançado, motivo do fallback

Use tracing projetado para pipelines de LLM (veja Observabilidade para Apps de LLM) e defina metas de confiabilidade (veja SLOs para Funcionalidades de IA).

Tenha cuidado ao registrar conteúdo sensível; aplique remoção de dados e controles de retenção (veja Privacidade em Logs).

Juntando tudo: uma arquitetura de referência (exemplo)

Considere um assistente de suporte ao cliente que pode responder FAQs, consultar status de pedidos e redigir respostas.

Um pipeline robusto poderia ser:

  1. Guardrails de entrada
    • Detecção/remoção de PII
    • Classificação de política (autoagressão, assédio etc.)
  2. Roteador
    • Se intenção “status do pedido” → rota com ferramenta
    • Se FAQ simples → rota com modelo barato
    • Se complexo ou cliente irritado → modelo mais forte + recuperação
  3. Cascata
    • Cache → modelo pequeno → modelo grande com recuperação
  4. Execução de ferramentas (restrita)
    • Apenas ferramentas na allowlist (consulta de pedido)
    • Args validados por schema
  5. Guardrails de saída
    • Filtro de política
    • Checagens de marca/estilo
    • Validação de JSON/schema se necessário
  6. Fallbacks
    • Se a ferramenta falhar: pedir informação faltante ou escalar
    • Se o modelo der timeout: trocar para um modelo mais rápido
  7. Escalonamento humano
    • Para reembolsos, contestação de cobrança ou falhas repetidas
  8. Observabilidade
    • Traces e métricas por etapa, logs com consciência de privacidade

Este não é um único “melhor” design; é um kit de ferramentas. A composição correta depende do seu risco, orçamentos e objetivos de produto.

Modos de falha comuns e como os padrões os endereçam

  • Injeção de prompt leva à exfiltração de dados
    • Use allowlists de ferramentas, ferramentas com privilégio mínimo, detecção de injeção e validação forte de argumentos de ferramentas (guardrails).
  • Modelo retorna JSON inválido
    • Prompting em múltiplas passagens + validação de schema + fallback de formato.
  • Custos explodem sob carga
    • Cascatas + cache + limites de taxa; acompanhe taxas de escalonamento e gasto de tokens.
  • Qualidade regride após um ajuste de prompt
  • Agentes entram em loops intermináveis
    • Orçamentos de passos, limites de chamadas de ferramenta e disjuntores.

Roteiro prático de adoção

  1. Comece simples: um pipeline, validação básica de entrada/saída, logging forte.
  2. Adicione fallbacks: timeouts, retries, fallback de modelo/provedor.
  3. Adicione um roteador: separar intenções óbvias e rotas de alto risco.
  4. Adicione uma cascata: barato primeiro com uma checagem clara de aceitação.
  5. Adicione verificadores e guardrails: especialmente para ferramentas e saídas estruturadas.
  6. Operacionalize: canaries, rollback, dashboards, SLOs, controles de privacidade.

Resumo

“Padrões de design de sistemas de LLM” são a resposta de engenharia à incerteza dos LLMs. Roteadores escolhem a capacidade certa para a tarefa, cascatas controlam o custo ao escalar apenas quando necessário, fallbacks mantêm o produto responsivo durante falhas, e guardrails impõem restrições de segurança e correção — especialmente em torno do uso de ferramentas e saídas estruturadas. Quando combinados com forte observabilidade e processos disciplinados de release, esses padrões transformam chamadas de LLM em sistemas confiáveis para produção.