Artigo AI·Inteligência Artificial·12 min de leitura

Arquitetura mínima de um agente IA em produção

Os 3 componentes que separam um agente IA funcional de um loop de prompts frágil: indexação, query layer e feedback — sem overengineering.

Vitor Morais

Por Vitor Morais

Fundador do MochaLabz ·

A arquitetura de um agente IA em produção não é um loop que chama a API do modelo dentro de um while True. Parece óbvio escrito assim, mas a maioria dos projetos que morrem nas primeiras semanas morrem exatamente por isso: prompt entra, resposta sai, ninguém controla o que acontece no meio. Um sistema compound com ~3.000 usuários reais — focado em busca de grants acadêmicos — reduziu o tempo de descoberta de 30–45 minutos em portais fragmentados para menos de 10 minutos numa interface conversacional unificada. A diferença entre esse sistema e um chatbot wrapper não é o modelo; é a separação de responsabilidades.

Por que um loop de prompts não sobrevive em produção

O padrão "manda pro modelo, exibe a resposta" funciona pra demo. Em produção, três coisas explodem rápido. Primeira: o contexto relevante não está na janela do modelo — está espalhado em bases de dados, APIs externas, PDFs, planilhas. Segunda: sem uma camada de verificação, o modelo alucina com confiança e o usuário não percebe. Terceira: não existe feedback estruturado — o agente não sabe se a resposta anterior foi útil ou lixo, então repete os mesmos erros.

Esses três problemas mapeiam direto nos três componentes que separam um agente funcional de um wrapper frágil: agregação e indexação de dados, query layer com separação de concerns e feedback loop sem overengineering.

Componente 1 — Agregação e indexação separada do modelo

O erro mais caro é tratar o LLM como banco de dados. Enfiar tudo no prompt e esperar que o modelo "ache" a informação certa. Funciona com 500 tokens de contexto. Quebra com 50 mil. O componente de agregação existe pra resolver isso: ele coleta dados de múltiplas fontes (APIs, scrapers, bancos internos), normaliza o formato e indexa num store separado — pode ser um banco vetorial, pode ser Postgres com tsvector, pode ser Elasticsearch. O ponto é: o índice é atualizado fora do runtime do agente.

No caso do sistema de grants, a arquitetura agrega editais de dezenas de portais governamentais e privados num índice unificado. Quando o usuário pergunta "quais grants aceitam projetos de saúde pública no Nordeste?", o agente não rastreia os portais em tempo real — consulta o índice já processado. Isso corta latência, corta custo de tokens e, mais importante, corta alucinação: o modelo responde sobre dados que existem, não sobre o que ele "acha" que existe.

Pseudocódigo: pipeline de agregação separada

// Roda em cron (diário/horário), NÃO no request do usuário async function aggregateAndIndex() { const rawData = await Promise.all([ fetchPortalA(), fetchPortalB(), scrapePortalC(), ]); const normalized = rawData .flat() .map(normalizeSchema) // campo único: title, body, tags, source_url .filter(deduplication); // hash do conteúdo pra não duplicar await vectorStore.upsert(normalized); // pgvector, Pinecone, Qdrant... await fullTextIndex.upsert(normalized); // Postgres tsvector como fallback } // O agente NUNCA chama fetchPortalA() direto

Indexação no runtime é armadilha comum

Indexar durante a query do usuário parece mais "atualizado", mas na prática gera timeout, custo alto de tokens (o modelo fica esperando) e inconsistência — cada request pode pegar um estado diferente da fonte. Indexe em batch, consulte em tempo real.

Componente 2 — Query layer: o modelo não decide tudo

O segundo componente é a camada que recebe a pergunta do usuário e decide o que buscar, onde buscar e como formatar antes de chamar o modelo pra gerar a resposta final. A tentação é delegar tudo pro LLM: "aqui está o contexto inteiro, responda". Isso funciona quando o contexto cabe com folga na janela. Em produção com milhares de documentos indexados, não cabe — e mesmo que coubesse, a qualidade degrada com contexto longo demais.

A separação funcional é: um router classifica a intenção (busca semântica? filtro por data? comparação entre itens?), um retriever puxa os chunks relevantes do índice (não todos — os top-k mais relevantes com reranking), e só então o generator recebe um prompt enxuto com os dados já filtrados. Em linguagem de framework, isso é o padrão RAG com retriever-augmented generation, mas a chave é que o retriever não é só similaridade de embedding — tem filtro hard (por data, região, tipo de documento) que corta antes do ranking vetorial.

Query layer com router + retriever + generator

async function handleQuery(userMessage: string) { // 1. Router: classifica intenção const intent = await classifyIntent(userMessage); // ex: { type: 'semantic_search', filters: { region: 'nordeste' } } // 2. Retriever: busca no índice com filtros hard const chunks = await vectorStore.search({ query: userMessage, topK: 8, filter: intent.filters, // corta ANTES do ranking vetorial }); // 3. Reranker (opcional mas recomendado): reordena por relevância const reranked = await rerank(chunks, userMessage); // 4. Generator: prompt enxuto com dados filtrados const response = await llm.chat({ system: SYSTEM_PROMPT, messages: [ { role: 'user', content: buildPrompt(userMessage, reranked.slice(0, 4)) }, ], }); return { answer: response, sources: reranked.slice(0, 4) }; }

Notar que o generator recebe no máximo 4 chunks. Não 40, não "todos os que passaram do threshold". Menos contexto irrelevante = menos alucinação, menos tokens, resposta mais rápida. Se a busca retornou lixo, melhor o agente responder "não encontrei dados sobre isso" do que fabricar uma resposta confiante com chunks vagamente relacionados.

Componente 3 — Feedback loop sem overengineering

Agente sem feedback é um sistema estático que degrada silenciosamente. A qualidade do índice muda (fontes somem, formatos mudam), o perfil das perguntas muda (usuários começam a pedir coisas que o agente não cobre), e ninguém percebe até o NPS despencar. O terceiro componente é um loop de feedback que não precisa ser RLHF em escala de paper acadêmico — precisa ser simples o bastante pra rodar desde o dia 1.

  1. Log estruturado de cada interação: query original, intent classificada, chunks retornados, resposta gerada, ação do usuário (clicou numa source? reformulou a pergunta? saiu?). Guardar num append-only table no Postgres resolve.
  2. Sinal binário de sucesso: o mínimo viável é um thumbs up/down no front. Sinais implícitos (reformulou = insatisfação, clicou na source = satisfação parcial) funcionam como proxy quando o usuário não clica em nada.
  3. Revisão periódica (semanal ou quinzenal): filtrar as queries com thumbs down ou reformulação, agrupar por padrão, e ajustar — pode ser tuning do prompt do router, pode ser adicionar uma fonte no aggregator, pode ser corrigir um filtro hard que estava cortando demais.

A Anthropic chamou esse padrão de "Dreaming" no lançamento recente dos Claude Managed Agents: o agente inspeciona sessões anteriores pra identificar o que perdeu e se auto-melhorar. A ideia é a mesma — mas não precisa de infra da Anthropic pra implementar. Um script que roda semanalmente, agrupa queries com baixa satisfação e gera um relatório já entrega 80% do valor.

Feedback mínimo que funciona no dia 1

Tabela agent_logs com colunas: id, query, intent, chunks_ids, response_hash, user_action (enum: thumbs_up, thumbs_down, reformulated, clicked_source, abandoned), created_at. Crie uma view que agrupa por user_action = 'thumbs_down' na última semana. Pronto: é o backlog do agente.

Como os 3 componentes se conectam

Olhando de cima: o aggregator roda em batch e alimenta o índice. O query layer consome o índice em tempo real e devolve respostas. O feedback loop observa as respostas e retroalimenta tanto o aggregator (falta cobertura de fonte X) quanto o query layer (o router classifica errado intenção Y). Não é microserviço — pode ser três módulos no mesmo repo, três cron jobs e uma API. A separação é lógica, não infra.

Loop de prompts vs. arquitetura compound mínima
CritérioLoop de promptsCompound (3 componentes)
Latência por queryAlta (busca + geração no mesmo request)Baixa (índice pré-computado, query enxuta)
Custo de tokensCresce linear com contexto brutoControlado — top-k chunks com reranking
Taxa de alucinaçãoAlta em domínios densosReduzida — modelo responde sobre dados verificados
ManutençãoPrompt vira monolito frágilCada componente evolui separado
FeedbackNenhum (ou manual)Estruturado desde o dia 1
Complexidade inicialBaixa (1 arquivo)Moderada (3 módulos + 1 tabela de logs)

Quando NÃO usar essa arquitetura

Nem tudo precisa de agente compound. Se a tarefa é one-shot e descartável — gerar um e-mail, resumir um PDF, traduzir um bloco de código — um prompt direto pra API resolve e qualquer camada extra é overhead. A arquitetura com indexação separada e query layer faz sentido quando:

  • O agente acessa múltiplas fontes de dados que mudam com frequência.
  • O usuário faz perguntas recorrentes sobre o mesmo domínio (suporte, busca interna, análise de portfólio).
  • A precisão importa — alucinação tem custo real (financeiro, jurídico, reputacional).
  • O volume de uso justifica otimizar custo de tokens (a partir de centenas de queries/dia, a conta de API fica relevante).

Pra protótipo de fim de semana validando se o problema existe, o loop simples é a escolha certa. A migração pra compound acontece quando o protótipo prova que tem demanda e a fragilidade começa a custar — queries que retornam lixo, usuários reclamando de respostas erradas, custo de API subindo sem controle.

Armadilhas menos óbvias

Reranking sem baseline. Muitos tutoriais mandam adicionar um reranker (Cohere, cross-encoder local) sem medir se a busca vetorial pura já resolve. Antes de adicionar camada, meça o recall com 50 queries reais. Se o top-4 do embedding já contém o documento certo em 90%+ dos casos, o reranker é overhead. Se cai pra 60%, vale adicionar.

Router que depende do LLM pra tudo. Classificar intenção com chamada pro modelo funciona, mas adiciona latência e custo em toda query. Pra sistemas com poucas intenções (3–5), um classificador baseado em regras (regex + keyword match) ou um modelo pequeno local (distilbert fine-tunado) é mais rápido e mais barato. Escala com volume sem explodir a fatura.

Indexar sem deduplicação. Fontes externas mudam URL sem mudar conteúdo, ou duplicam o mesmo edital em portais diferentes. Sem dedup por hash do conteúdo, o índice infla, o retriever retorna chunks repetidos, e o modelo gasta tokens gerando resposta redundante. Um md5 ou sha256 do body normalizado (lowercase, sem whitespace extra) resolve.

Separação de concerns vs. multi-agent

A moda é "multi-agent": vários agentes conversando entre si pra resolver uma tarefa. Na prática, orquestrar múltiplos LLMs é caro, lento e difícil de debugar. A arquitetura compound descrita aqui usa um modelo no generator e lógica determinística no router e no aggregator. Multi-agent faz sentido quando tarefas são genuinamente paralelas e independentes (ex.: um agente analisa financeiro, outro analisa jurídico, um terceiro consolida). Pra maioria dos casos com menos de 10 mil queries/dia, um agente bem estruturado bate uma frota mal orquestrada.

Stack mínima pra começar hoje

Não precisa de Kubernetes, não precisa de Langchain, não precisa de framework de agentes. A stack mínima pra colocar o compound agent em produção com os 3 componentes:

  • Aggregator: script Node.js/Python rodando em cron (GitHub Actions, Railway cron, ou um setInterval num servidor simples). Saída vai pro banco.
  • Índice: Supabase com pgvector (embedding) + tsvector (full-text como fallback). Alternativa: Postgres puro com extensão pgvector.
  • Query layer: API route no Next.js ou endpoint Express. Router com regras simples, retriever chama o Supabase, generator chama a API do modelo (Claude, GPT, o que preferir).
  • Feedback: tabela agent_logs no mesmo Supabase. View semanal filtrando insatisfação.
  • Front: chat simples com thumbs up/down e link pras sources retornadas.

Essa stack roda dentro do free tier do Supabase até um volume considerável. O custo relevante é a API do modelo — e é justamente isso que a arquitetura compound otimiza: menos tokens por query, menos queries desperdiçadas, menos retrabalho.

Quem já montou automações com MCP e agentes IA reconhece o padrão: a conexão entre ferramentas externas e o modelo é o gargalo. A arquitetura compound formaliza essa conexão com camadas que podem ser testadas e evoluídas de forma independente. E pra quem está escolhendo qual modelo usar no generator, o comparativo entre Claude, GPT e Llama ajuda a decidir sem viés de marketing.

O custo de API é a parte que mais escala — e técnicas como batch processing e caching no Claude se encaixam direto no query layer descrito aqui, reduzindo a fatura sem sacrificar qualidade de resposta.

Perguntas frequentes

Qual a diferença entre RAG e compound AI system?+

RAG (Retrieval-Augmented Generation) é um padrão específico: buscar documentos relevantes e injetar no prompt. Compound AI system é o conceito mais amplo: combinar múltiplos componentes (retriever, router, generator, feedback) num pipeline onde o LLM é apenas uma das partes. Todo RAG é compound, mas nem todo compound é RAG — pode incluir tool use, web search, ou lógica determinística sem retrieval.

Preciso de um framework como Langchain ou LlamaIndex?+

Não pra começar. Frameworks adicionam abstração que ajuda em protótipos rápidos, mas esconde decisões importantes (como o retriever rankeia, quantos chunks passam pro modelo, como o router classifica). Pra produção com controle fino, código próprio com chamadas diretas à API do modelo e ao banco vetorial dá mais visibilidade e menos surpresa em debug.

Quantos chunks devo passar pro modelo na geração?+

Entre 3 e 6 na maioria dos casos. Menos de 3 arrisca não ter contexto suficiente. Mais de 6 aumenta custo de tokens e dilui a relevância — o modelo começa a dar peso pra chunks vagamente relacionados. Meça com queries reais: se a resposta melhora com 8 chunks vs. 4, mantenha 8. Se não melhora, corte.

Como medir se o agente está alucinando?+

O método mais direto é comparar afirmações da resposta com os chunks que o retriever passou. Se o agente afirma algo que não está nos chunks fornecidos, é alucinação. Automatizar isso com um segundo modelo ("verificador") é possível, mas caro. Na prática, o log estruturado + revisão semanal das queries com thumbs down pega os casos mais graves.

Essa arquitetura funciona pra agentes que executam ações (não só respondem)?+

Sim, com uma adição: entre o generator e a ação, entra uma camada de confirmação (humano ou regra automática). O generator propõe a ação, a camada de confirmação valida, e só então executa. Sem essa camada, um agente que envia e-mails ou modifica dados pode causar dano irreversível a partir de uma alucinação.

#agente-ia-producao#arquitetura-agente-ia#multi-agent-orquestracao#compound-ai-system#rag-avancado#claude-managed-agents#automacao-ia#llm-producao

Artigos relacionados