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.
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() diretoIndexaçã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.
- 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.
- 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.
- 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.
| Critério | Loop de prompts | Compound (3 componentes) |
|---|---|---|
| Latência por query | Alta (busca + geração no mesmo request) | Baixa (índice pré-computado, query enxuta) |
| Custo de tokens | Cresce linear com contexto bruto | Controlado — top-k chunks com reranking |
| Taxa de alucinação | Alta em domínios densos | Reduzida — modelo responde sobre dados verificados |
| Manutenção | Prompt vira monolito frágil | Cada componente evolui separado |
| Feedback | Nenhum (ou manual) | Estruturado desde o dia 1 |
| Complexidade inicial | Baixa (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
setIntervalnum servidor simples). Saída vai pro banco. - Índice: Supabase com
pgvector(embedding) +tsvector(full-text como fallback). Alternativa: Postgres puro com extensãopgvector. - 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_logsno 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.
Artigos relacionados
Qual LLM escolher como freelancer em 2026: Claude, GPT ou open-source?
Matriz decisória para solopreneur brasileiro: Claude vs GPT vs Llama 4 por custo, contexto, reasoning e deploy local. Escolha o LLM certo para cada task.
MCP: conecte agentes IA a ferramentas reais em 2026
Model Context Protocol vira padrão universal de agentes IA. Veja como conectar Claude a Notion, Stripe e APIs próprias sem DevOps em 2026.
Agentes de IA para automação de tarefas freelancer
Configure agentes de IA sem código para automatizar cobranças, organizar clientes e gerar relatórios. Economize 10+ horas por semana.
Reduzir 90% do custo de API Claude com batch e caching
Aprenda a usar batch API e prompt caching do Claude pra cortar despesa com tokens. Guia prático com exemplos reais pra solopreneur.