# Arquitetura de uma aplicação de IA

> Como uma aplicação de IA cresce: contexto, guardrails, roteador, gateway, cache, orquestração e feedback do usuário.

- Autor: Marcelo Silva
- Publicado em: 2026-02-22
- Tema: IA
- Série: Engenharia de IA, parte 14 de 14 (https://marcelxsilva.dev/series/engenharia-de-ia/)
- URL: https://marcelxsilva.dev/artigos/arquitetura-de-aplicacoes-de-ia/

Depois de passar por modelos, avaliação, prompt, RAG e agentes, falta juntar tudo. Uma aplicação de IA completa pode ficar bem complexa, mas ela não nasce assim. O caminho mais saudável é começar com a arquitetura mais simples possível, só a consulta indo para o modelo e a resposta voltando, e ir adicionando componentes conforme a necessidade aparece.

Apesar de existirem aplicações de IA de todo tipo, elas compartilham muitos componentes. A ordem que eu sigo neste artigo é mais ou menos esta:

1. Melhorar o contexto que chega ao modelo, dando acesso a fontes de dados e ferramentas.
2. Colocar barreiras de proteção para proteger o sistema e os usuários.
3. Adicionar roteador e gateway de modelos para suportar pipelines mais complexos e ganhar segurança.
4. Reduzir latência e custo com cache.
5. Adicionar lógica mais complexa e ações de escrita para aproveitar todo o potencial do sistema.

## Melhorar o contexto

O primeiro passo quase sempre é dar ao sistema uma forma de montar o contexto que o modelo precisa para responder cada consulta. É aqui que entram RAG, agentes e reescrita de consultas, que vimos nos artigos anteriores.

Eu gosto de pensar que **construir contexto é a engenharia de features dos modelos de fundação**. É o que entrega ao modelo a informação necessária para gerar uma boa resposta. Por ser tão central para a qualidade, quase todo provedor de API suporta isso de alguma forma: OpenAI, Claude e Gemini deixam você enviar arquivos e usar ferramentas.

## Barreiras de proteção

As barreiras de proteção (guardrails) reduzem riscos e protegem você e seus usuários. A regra é simples: onde houver exposição a risco, coloque uma. Em geral elas ficam em dois lugares, na entrada e na saída.

### Na entrada

Os guardrails de entrada costumam proteger contra dois riscos:

- **Vazamento de informação privada** para APIs externas. Um exemplo comum é mascarar dados pessoais (PII) antes de enviar a consulta para o modelo.
- **Prompts maliciosos** que tentam comprometer o sistema, como prompt injection e jailbreak.

Dá para reduzir esses riscos, mas não dá para zerar. Faz parte da natureza de como os modelos geram respostas, e as falhas humanas também sempre vão existir.

### Na saída

Um modelo pode falhar de muitos jeitos. Os guardrails de saída têm duas funções:

- **Detectar** as falhas na resposta (conteúdo tóxico, informação inventada, formato quebrado).
- **Definir a política** para cada tipo de falha: tentar de novo, devolver uma resposta padrão, encaminhar para uma pessoa.

Os provedores de modelos já colocam guardrails nos próprios modelos, mas eles precisam equilibrar segurança e flexibilidade. Restrição demais deixa o modelo mais seguro e, ao mesmo tempo, menos útil para alguns casos. Por isso você também precisa das suas próprias barreiras, pensadas para o seu produto.

## Roteador e gateway

Conforme a aplicação cresce e passa a usar mais de um modelo, aparecem dois componentes para controlar a complexidade e o custo: o roteador e o gateway.

### Roteador

Em vez de mandar todas as consultas para o mesmo modelo, você pode ter soluções diferentes para tipos diferentes de consulta. Isso traz dois ganhos:

- **Modelos especializados**, que podem ir melhor do que um modelo geral em consultas específicas. Um para suporte técnico, outro para cobrança.
- **Economia**, mandando consultas simples para modelos mais baratos e deixando o modelo caro só para o que precisa dele.

Normalmente o roteador é um **classificador de intenção**, o mesmo conceito que apareceu nos artigos de agentes. Num chatbot de suporte, por exemplo:

- Se o usuário quer redefinir a senha, mande para a página de perguntas frequentes sobre recuperação de senha.
- Se quer corrigir um erro de cobrança, mande para um atendente humano.
- Se tem um problema técnico, mande para um chatbot especializado em suporte técnico.

O classificador também evita conversas fora do escopo. Se a consulta não tem nada a ver com o produto, o chatbot recusa com uma resposta padrão, sem gastar uma chamada de API. Se o usuário pergunta em quem o bot votaria na próxima eleição, ele pode responder algo como: "Como chatbot, eu não voto. Se tiver dúvidas sobre nossos produtos, posso ajudar."

Roteadores também ajudam o modelo a decidir o próximo passo:

- Num agente com várias ações, o roteador pode prever a **próxima ação**: usar o interpretador de código ou a API de busca?
- Num sistema com memória, ele pode prever **de qual memória** buscar informação. Se o usuário anexou um documento que fala de Melbourne e depois pergunta "Qual é o animal mais bonito de Melbourne?", o sistema precisa decidir entre confiar no documento ou buscar na internet.

Outro cuidado é o tamanho do contexto. Imagine uma consulta de 1.000 tokens roteada para um modelo com limite de 4K. Aí o sistema faz uma busca na web que devolve 8.000 tokens. Você pode cortar o contexto para caber no modelo escolhido ou rotear a consulta para um modelo com contexto maior.

O roteamento normalmente acontece **antes** da recuperação: decidir se a consulta está no escopo e se precisa buscar informação. Mas também pode acontecer **depois**, por exemplo para decidir se a consulta vai para um atendente humano.

### Gateway

O gateway de modelos é uma **interface única** para falar com modelos diferentes, sejam hospedados por você ou de APIs comerciais. Se a API de um provedor muda, você atualiza só o gateway, e não todas as aplicações que usam aquela API.

Na forma mais simples, ele é só um wrapper. O código abaixo dá uma ideia de como seria, sem tratamento de erro nem otimização:

```python
import os

import google.generativeai as genai
from flask import Flask, jsonify, request
from openai import OpenAI

app = Flask(__name__)


def openai_model(input_data, model_name, max_tokens):
    client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
    response = client.chat.completions.create(
        model=model_name,
        messages=[{"role": "user", "content": input_data}],
        max_tokens=max_tokens,
    )
    return {"response": response.choices[0].message.content}


def gemini_model(input_data, model_name, max_tokens):
    genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
    model = genai.GenerativeModel(model_name=model_name)
    response = model.generate_content(
        input_data,
        generation_config={"max_output_tokens": max_tokens},
    )
    return {"response": response.text}


@app.route("/model", methods=["POST"])
def model_gateway():
    data = request.get_json()
    model_type = data.get("model_type")
    model_name = data.get("model_name")
    input_data = data.get("input_data")
    max_tokens = data.get("max_tokens")

    if model_type == "openai":
        result = openai_model(input_data, model_name, max_tokens)
    elif model_type == "gemini":
        result = gemini_model(input_data, model_name, max_tokens)
    return jsonify(result)
```

Além de unificar a interface, o gateway resolve outros problemas:

- **Controle de acesso e custo.** Em vez de espalhar o token da organização para todo mundo que precisa da API (e correr o risco de vazar), você dá acesso só ao gateway. Ele vira um ponto central e controlado, define quem pode usar qual modelo e monitora e limita as chamadas para evitar abuso.
- **Fallback.** Quando a API principal cai ou estoura o limite de requisições (e isso acontece bastante), o gateway manda a requisição para outro modelo, tenta de novo depois de um tempo ou trata a falha de outra forma. A aplicação continua funcionando.
- **Privacidade.** Dá para direcionar consultas com informação sensível para um modelo hospedado por você.
- **Logs.** Tudo passa por um lugar só, então fica fácil registrar.

Como um gateway é relativamente simples de construir, existem várias opções prontas, como o [AI Gateway da Portkey](https://github.com/Portkey-AI/gateway), o MLflow AI Gateway, o TrueFoundry, o Kong e o Cloudflare.

## Cache para reduzir latência

Cache sempre foi uma forma de reduzir latência e custo em software, e muitas dessas ideias servem para aplicações de IA. Existem dois tipos principais: o cache exato e o cache semântico.

### Cache exato

No cache exato, um item só é reaproveitado quando exatamente aquele item é pedido de novo. Se um usuário pede o resumo de um produto, o sistema verifica se já existe um resumo daquele produto no cache. Se existir, devolve. Se não, gera o resumo e guarda.

Ele também é usado na recuperação por embeddings, para evitar buscas vetoriais repetidas: se a consulta já está no cache, devolve o resultado guardado.

O cache vale ainda mais a pena para consultas com várias etapas (como cadeia de raciocínio) ou com ações demoradas (recuperação, SQL, busca na web).

Ele pode ficar em memória para ser rápido, mas memória é limitada. Por isso também dá para usar PostgreSQL, Redis ou um armazenamento em camadas para equilibrar velocidade e capacidade. E é importante ter uma **política de remoção** para controlar o tamanho do cache. As mais comuns são:

- **LRU** (Least Recently Used): remove o que foi usado há mais tempo.
- **LFU** (Least Frequently Used): remove o que é usado com menos frequência.
- **FIFO** (First In, First Out): remove o que entrou primeiro.

Quanto tempo manter uma consulta no cache depende da chance de ela ser feita de novo. Consultas específicas do usuário, como "Qual é o status do meu último pedido?", dificilmente serão repetidas por outra pessoa, então não deveriam ir para o cache. O mesmo vale para consultas que dependem do momento, como "Como está o tempo?". Muitos times treinam um classificador só para decidir se uma consulta deve ou não ser cacheada.

> Cache mal feito pode **vazar dados**. Imagine uma loja online em que o usuário X pergunta algo que parece genérico: "Qual é a política de devolução de eletrônicos?". Como a política depende do plano de cada usuário, o sistema busca os dados de X e gera uma resposta com informações dele. Se o sistema tratar essa pergunta como genérica e guardar a resposta no cache, quando o usuário Y fizer a mesma pergunta vai receber a resposta com os dados de X.

### Cache semântico

No cache semântico, a resposta guardada é reaproveitada mesmo quando a nova consulta é só **parecida**, e não idêntica. Se alguém pergunta "Qual é a capital do Vietnã?" e o modelo responde "Hanói", depois outra pessoa pergunta "Qual a capital do Vietnã?" e o sistema reaproveita a mesma resposta.

Isso aumenta a taxa de acerto do cache e pode reduzir custo, mas também pode **piorar a qualidade** das respostas, porque nem toda consulta parecida tem a mesma resposta.

Ele só funciona se você tiver uma forma confiável de dizer que duas consultas são parecidas. O jeito mais comum é por similaridade semântica:

1. Gerar o embedding de cada consulta com um modelo de embeddings.
2. Fazer uma busca vetorial para achar o embedding em cache mais parecido com o da consulta atual. Digamos que a similaridade seja *X*.
3. Se *X* passar de um limite definido, devolver o resultado em cache. Se não, processar a consulta e guardar o resultado junto com o embedding.

Para isso você vai precisar de um banco vetorial guardando os embeddings das consultas em cache.

## Padrões de agente

Com o tempo, o fluxo da aplicação deixa de ser uma linha reta e passa a ter loops, execução paralela e decisões condicionais. É aí que os padrões de agente ajudam.

Um exemplo: o sistema gera uma resposta, avalia e percebe que não cumpriu a tarefa porque faltou informação. Ele faz uma nova recuperação, junta a resposta original com o contexto novo e manda de novo para o mesmo modelo ou para outro. Isso cria um loop.

![Arquitetura completa com cache, construção de contexto, guardrails e gateway de modelos](https://marcelxsilva.dev/articles/images/arquitetura-de-aplicacoes-de-ia/01.png)
*A arquitetura completa: cache, construção de contexto, guardrails de entrada e saída e o gateway de modelos. A linha laranja é o loop em que a resposta volta para uma nova construção de contexto.*

Também é aqui que entram as **ações de escrita**, como enviar um e-mail, atualizar um banco ou fazer um pedido. Elas aumentam muito o que o sistema consegue fazer, e por isso mesmo pedem mais cuidado com permissões e aprovação humana, como vimos nos artigos de agentes.

## Orquestração do pipeline

Uma aplicação de IA pode acabar com vários modelos, vários bancos de dados e um monte de ferramentas. Um **orquestrador** define como esses componentes trabalham juntos para formar um pipeline de ponta a ponta, garantindo que os dados passem de um para o outro sem problema.

Ele funciona em duas etapas:

- **Definição dos componentes:** você informa quais modelos, fontes de dados e ferramentas o sistema usa.
- **Encadeamento:** você descreve os passos desde a chegada da consulta até a conclusão da tarefa. No fundo é composição de funções. Por exemplo:
    1. Processar a consulta original.
    2. Recuperar os dados relevantes com base na consulta processada.
    3. Juntar a consulta e os dados recuperados num prompt no formato que o modelo espera.
    4. Gerar a resposta.
    5. Avaliar a resposta.
    6. Se estiver boa, devolver ao usuário. Se não, encaminhar para um atendente humano.

O orquestrador cuida da passagem de dados entre os componentes. Ele deve ajudar a garantir que a saída de uma etapa esteja no formato que a próxima espera, e o ideal é que ele avise quando o fluxo quebra, seja por falha de um componente ou por dados incompatíveis.

Existem várias ferramentas de orquestração, como [LangChain](https://github.com/langchain-ai/langchain), [LlamaIndex](https://github.com/run-llama/llama_index), [Flowise](https://github.com/FlowiseAI/Flowise), [Langflow](https://github.com/langflow-ai/langflow) e [Haystack](https://github.com/deepset-ai/haystack). Como recuperação e uso de ferramentas são padrões muito comuns, muitos frameworks de RAG e de agentes também funcionam como orquestradores.

## Feedback do usuário

O feedback do usuário serve para personalizar o modelo para cada pessoa e também para treinar as próximas versões dele. Com dados públicos ficando cada vez mais escassos, dados próprios valem mais do que nunca. Um produto que chega cedo e conquista usuários consegue coletar dados para melhorar continuamente, e isso fica difícil de copiar.

Só não dá para esquecer que feedback é **dado do usuário**. Ele exige os mesmos cuidados de qualquer outro dado: respeitar a privacidade e deixar claro para o usuário como aquilo vai ser usado.

Com este artigo eu fecho a série. A ideia foi registrar o caminho que fiz estudando, desde como um modelo de linguagem prevê a próxima palavra até como montar uma aplicação inteira em volta dele. Muita coisa aqui vai mudar rápido, mas a base (contexto, avaliação, segurança e custo) tende a continuar valendo.

## Referência

Este artigo faz parte dos meus estudos sobre engenharia de IA, baseados no livro *AI Engineering: Building Applications with Foundation Models*, de Chip Huyen (O'Reilly, 2025). As imagens usadas aqui também vêm do livro.
