Blog • Artigo
    IAArquiteturaAPI

    A arquitetura que separa protótipos de sistemas em produção

    Todo tutorial de “agente de IA com FastAPI” mostra a mesma coisa: um endpoint, uma chamada de modelo, uma resposta. Isso funciona em vinte linhas de código e quebra na primeira semana de produção, porque nenhuma dessas três coisas descreve o problema real. O problema real é como a mesma camada de API sustenta dado relacional, dado vetorial, estado de conversa, streaming e segurança, tudo ao mesmo tempo, sem virar um emaranhado que só quem escreveu entende.

    Alexsander
    AlexsanderEngenheiro de Software
    01 de ago. de 2026
    12 min de leitura
    A arquitetura que separa protótipos de sistemas em produção

    Este artigo é sobre essa camada. Não o agente em si, não o modelo, a fronteira que conecta os três: onde o dado vive, onde o agente decide, e onde o FastAPI entra para expor isso como um contrato de API estável.

    O papel real do FastAPI nessa arquitetura

    O erro mais comum que vejo é tratar o FastAPI como se ele fosse o cérebro do sistema. Ele não é. FastAPI é a camada HTTP assíncrona, responsável por validação de entrada com Pydantic, autenticação, rate limiting, CORS e serialização de resposta. A lógica de decisão do agente, o estado da conversa, os checkpoints de execução, nada disso deveria morar dentro de um handler de rota.

    Isso conecta diretamente com o que já estabeleci no primeiro artigo desta série: LangGraph, CrewAI e Agno resolvem orquestração, LangChain resolve composição, e nenhum deles compete pelo mesmo espaço do FastAPI. O FastAPI expõe o que essas camadas fazem, ele não faz o trabalho delas. Um handler de rota que chama diretamente um LLM, monta prompt e trata o retorno é a mesma armadilha do agente-como-prompt que citei antes, só que na camada de API.

    Onde cada peça entra na arquitetura

    Código
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     Observabilidade: OpenTelemetry, LangSmith ou Phoenix, Prometheus e Grafana
     Logs estruturados e tracing por execução, atravessando todas as camadas abaixo
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    Cliente
      │
      ▼
    FastAPI (camada HTTP assíncrona)
      Pydantic (validação de entrada e saída)
      JWT (autenticação)
      Rate limiting por endpoint
      CORS
      │
      ▼
    Camada de serviço
      Repositório de dados (acesso a Postgres, não SQL solto no handler)
      Serviço de embedding (gera e cacheia vetores)
      Invocação do agente (inicia ou retoma o grafo)
      │
      ▼
    Orquestrador (LangGraph, por exemplo)
      Estado do agente
      Checkpoint persistente
      Ferramentas via SDK direto ou via servidor MCP
      │
      ├──────────────┬──────────────┐
      ▼              ▼              ▼
    PostgreSQL     pgvector /     Redis
    (dado           Qdrant        (cache de embedding,
    relacional)     (dado         sessão, resultado
                     vetorial)     intermediário)
      │
      ▼
    LLM (Anthropic, OpenAI, ou outro provedor)
      │
      ▼
    FastAPI → StreamingResponse (SSE) → Cliente
    

    O detalhe que a maioria dos diagramas de arquitetura esconde está no canto inferior direito: a resposta não volta como um payload único. Ela volta como eventos, token por token, transmitidos por Server-Sent Events. Isso não é um detalhe de frontend, é uma decisão de arquitetura de API que muda como o endpoint é desenhado desde o início.

    O ciclo de vida de uma requisição

    O diagrama em camadas mostra onde cada peça mora. Vale complementar com a visão de uma requisição específica atravessando esse sistema do início ao fim, porque é essa sequência que geralmente falta na cabeça de quem está desenhando o endpoint pela primeira vez.

    Código
    Request
      │
      ▼
    Validação (Pydantic)
      │
      ▼
    Autenticação (JWT)
      │
      ▼
    Recuperação de contexto (histórico, estado do agente)
      │
      ▼
    Busca vetorial (pgvector ou Qdrant)
      │
      ▼
    Execução do agente (LangGraph)
      │
      ▼
    Ferramentas (SDK direto ou MCP)
      │
      ▼
    Persistência (checkpoint em PostgreSQL)
      │
      ▼
    Streaming (SSE de volta ao cliente)
      │
      ▼
    Resposta
    
    Observabilidade atravessa toda essa execução, do primeiro ao último passo, não é uma etapa que acontece depois.
    

    Cada seta dessa cadeia é um ponto onde algo pode falhar, e cada ponto de falha precisa de uma resposta diferente: validação falha retorna 422 antes de gastar um token, busca vetorial vazia não deveria travar o agente, e falha na ferramenta precisa aparecer no log antes de vazar como erro genérico para o cliente.

    As decisões que realmente importam

    O estado do agente mora no banco, não no processo. Um handler HTTP tradicional é sem estado, processa a requisição e esquece. Um agente não, ele acumula histórico de conversa, uso de ferramentas e contexto entre turnos. Se esse estado vive na memória do processo Python, um restart do servidor derruba toda conversa em andamento. A prática correta é o orquestrador persistir checkpoint no PostgreSQL a cada transição de nó, e o handler FastAPI vira um wrapper fino que inicia a execução e retorna imediatamente, sem segurar a requisição presa ao processamento inteiro.

    Para experiências conversacionais, streaming deixa de ser opcional. Um agente que leva vinte e cinco segundos para responder por completo perde o usuário no oitavo segundo. A mesma resposta, transmitida token por token via SSE, entrega o primeiro token em frações de segundo, e a taxa de abandono despenca, mesmo que o tempo total de processamento seja idêntico. Isso muda o desenho do endpoint: em vez de retornar um JSON no final, ele retorna um StreamingResponse com media_type="text/event-stream", e é preciso desabilitar buffering no proxy reverso na frente dele, porque um nginx com buffer ligado anula o streaming inteiro sem avisar. Vale a ressalva: embedding, classificação, OCR, geração de score e processamento em lote não têm essa exigência, o usuário não está esperando em uma tela olhando pontinhos piscarem, e forçar streaming nesses casos é complexidade sem propósito.

    Desacople o ciclo de vida HTTP do processamento do agente. Uma requisição amarrada ao processo que a atende não sobrevive a queda de conexão, timeout de load balancer, ou restart de servidor. Isso não precisa vir de uma vez. O primeiro degrau costuma ser BackgroundTasks do próprio FastAPI ou asyncio.create_task(), suficiente enquanto o volume é baixo. O degrau seguinte, quando o volume justifica, é mover o processamento de longa duração para um worker assíncrono dedicado, Celery, Dramatiq, Arq ou Temporal, e deixar o FastAPI responsável só por iniciar o trabalho e servir o stream de progresso, permitindo que o cliente reconecte a uma execução que já está em andamento.

    Observabilidade não é o que você adiciona depois que algo quebra. Todo agente em produção precisa responder cinco perguntas a qualquer momento: quanto essa execução custou, qual ferramenta foi chamada, qual prompt rodou, onde exatamente falhou, e quantos tokens foram gastos. Isso significa tracing por execução, não só log de erro, com OpenTelemetry, LangSmith ou Phoenix para o que é específico de LLM, e Prometheus com Grafana para as métricas de infraestrutura por cima. Sem essa camada, cada incidente em produção vira investigação manual de log solto.

    Acesso a dado consumido por agente não é o mesmo CRUD de sempre. Um agente não faz uma query, ele faz várias, iterativamente, e às vezes erra a primeira tentativa e tenta de novo com outro filtro. Um repositório de dados bem desenhado para esse consumidor expõe métodos pensados para busca exploratória, não só para leitura e escrita pontual, e isola o agente de mudança de schema, exatamente como um repositório isola qualquer outro consumidor.

    Segurança entra em camadas, não em um único middleware. Validação de schema com Pydantic barra entrada malformada antes de chegar perto do agente. Autenticação por JWT e rate limiting por endpoint contêm abuso e custo. Defesa contra prompt injection e mascaramento de PII antes de qualquer dado sensível tocar o modelo protegem a entrada. Na saída, a lógica é a mesma aplicada ao contrário: impedir que uma resposta fora do schema esperado produza qualquer efeito colateral. O output parser valida o formato, o schema garante a estrutura, e, quando o domínio exige, um policy engine decide se aquela ação pode realmente ser executada antes de liberá-la. Isso vale também para como o agente chama ferramenta: a camada de serviço pode invocar diretamente via SDK, ou através de um servidor MCP, e essa escolha muda onde essa validação de saída precisa acontecer.

    Onde a fronteira determinística entra

    Esse ponto eu não abro mão em nenhuma arquitetura que desenho: IA analisa e recomenda, motor determinístico decide, sistema executa. Na prática dessa camada, isso significa que o endpoint que expõe uma recomendação do agente e o endpoint que executa uma ação com efeito real no negócio não são o mesmo endpoint, e frequentemente nem deveriam ter o mesmo contrato de resposta. Se o agente sugere aprovar uma transação, cancelar um pedido, ou gerar uma peça jurídica, a camada de API precisa deixar explícito, no formato da resposta, que aquilo é uma recomendação, não uma decisão já tomada.

    Um exemplo real de decisão

    "

    Caso real

    Em um gateway de pagamentos que arquitetei, a camada de dados e inteligência em Python com FastAPI funcionava como um BFF de Dados e IA, consumindo os microsserviços transacionais escritos em Go. O núcleo transacional, em Go, cuidava do fluxo de pagamento em si, rápido, determinístico, sem IA nenhuma no caminho crítico.

    O FastAPI expunha o AI Risk Agent como uma capacidade assistiva, consultando PostgreSQL para dado transacional e Qdrant e pgvector para similaridade de padrões de risco, sempre devolvendo uma recomendação, nunca uma decisão final sobre a transação.

    Separar essas camadas por linguagem, por responsabilidade e por contrato de API deixou explícito, em cada ponto do sistema, onde a IA analisa e onde o motor determinístico decide, sem depender de ninguém lembrar dessa regra na hora de programar. Isso também permitiu evoluir a camada de IA de forma independente do núcleo financeiro, sem tocar no código que processa o pagamento em si.

    Quando essa arquitetura completa é exagero

    Nem todo projeto precisa das cinco camadas inteiras desde o primeiro commit. Se o volume de uso ainda é baixo e a conversa cabe em uma única requisição-resposta, um worker dedicado para desacoplar processamento é complexidade adiantada, BackgroundTasks resolve por enquanto. Em muitas arquiteturas iniciais, pgvector sozinho, dentro do mesmo PostgreSQL que já guarda o resto do dado relacional, resolve por muito mais tempo do que se imagina, enquanto índice, volume e latência ainda cabem confortavelmente no banco relacional, e um banco vetorial dedicado só compensa quando pgvector mostra sinal real de limite, não antes. O critério que uso é simples: adiciono uma camada quando o problema que ela resolve já apareceu, não porque ela apareceu em um artigo de arquitetura de referência.

    O custo real da escolha

    Latência percebida versus latência real. Streaming não reduz o tempo total de processamento, ele muda a percepção de espera. Ignorar isso e entregar resposta só no final é a forma mais barata de perder usuário em um produto que tecnicamente funciona.

    Complexidade operacional de múltiplos armazenamentos. PostgreSQL, banco vetorial e Redis juntos significam três superfícies de falha, três estratégias de backup e três pontos de monitoramento, não um. Cada armazenamento adicional precisa justificar esse custo operacional recorrente.

    Acoplamento ao proxy reverso. Um endpoint de streaming que funciona perfeitamente em desenvolvimento e falha em produção quase sempre tem a mesma causa: buffering no nginx ou no load balancer engolindo os eventos antes de chegar ao cliente. Esse é o tipo de bug que só aparece depois do deploy, e que nenhum teste local pega.

    Auditoria de estado distribuído. Quando o estado do agente vive em checkpoint no banco e o processamento roda em um worker separado do handler que recebeu a requisição, rastrear o que aconteceu em uma execução específica exige correlacionar logs entre pelo menos três componentes, não um.

    A pergunta que quase ninguém faz

    Antes de desenhar essa camada, eu respondo cinco perguntas sobre o sistema que estou construindo.

    O agente precisa manter estado entre turnos de conversa, ou cada chamada é independente o suficiente para não precisar de checkpoint.

    A resposta demora o suficiente para o usuário perceber a espera, a ponto de streaming deixar de ser opcional.

    Existe uma ação de efeito real no negócio no fim do fluxo, ou o agente só recomenda, e algo determinístico decide depois.

    O volume de uso já justifica desacoplar o processamento do ciclo de vida da requisição HTTP, ou isso ainda é complexidade antecipada.

    Alguém vai precisar reconstruir o que aconteceu em uma execução específica depois que ela terminou.

    Cada resposta decide quantas das cinco camadas dessa arquitetura entram na primeira versão, e quantas ficam para quando o problema que elas resolvem aparecer de verdade.

    Como isso evolui na prática

    Código
    Endpoint único
    Request síncrona, resposta no final
        │
        ▼
    Streaming
    SSE, primeiro token em milissegundos
        │
        ▼
    Estado persistente
    Checkpoint em PostgreSQL, handler vira wrapper fino
        │
        ▼
    Processamento assíncrono simples
    BackgroundTasks ou asyncio.create_task()
        │
        ▼
    Processamento desacoplado
    Celery, Dramatiq, Arq ou Temporal, reconexão a execução em andamento
        │
        ▼
    Produção madura
    Observabilidade por execução (OpenTelemetry, LangSmith ou Phoenix)
    Guardrails de entrada e saída
    Fronteira explícita entre recomendação e decisão
    

    O erro mais caro que vejo nessa evolução é pular etapas no sentido errado: construir observabilidade e guardrails sofisticados sobre uma arquitetura que ainda responde de forma síncrona e sem estado persistente. Cada camada dessa escada depende da anterior estar sólida.

    Minha recomendação como arquiteto

    Antes de desenhar essa camada, eu respondo três perguntas.

    O estado do agente sobrevive a um restart do servidor, ou desaparece junto com o processo. Se desaparece, não existe arquitetura de produção ainda, existe um protótipo com endpoint.

    A resposta chega ao usuário em streaming, ou ele espera em silêncio até o processamento terminar. Silêncio acima de alguns segundos é a forma mais simples de parecer que o sistema travou.

    O contrato de API distingue explicitamente uma recomendação de agente de uma decisão que já produziu efeito no negócio. Sem essa distinção no formato da resposta, a fronteira entre IA que analisa e sistema que decide vira uma convenção verbal que ninguém necessariamente respeita sob pressão de prazo.

    FastAPI não é onde a inteligência do sistema mora. É onde ela se torna um contrato estável, auditável e que sobrevive a um restart de servidor. Um agente brilhante atrás de uma API frágil ainda é um sistema frágil.

    Este conteúdo foi útil?
    Compartilhar artigo

    Quer aplicar isso no seu contexto?

    Vamos conversar sobre seus desafios e encontrar o melhor caminho para sua operação.

    Agendar conversa