Pedro Sousa
← Artigos

Artigo · Blog

O agente que funcionava no teste e quebrava em produção

Construir um agente de IA que funciona em demo é simples. O problema aparece quando o contexto real chega: dados ruidosos, sessões longas, estados ambíguos. A maioria dos times descobre isso depois do deploy.

O agente que funcionava no teste e quebrava em produção

O agente passava em todos os testes. Todas as demos funcionavam. A resposta era coerente, o fluxo estava correto, as ferramentas eram chamadas na ordem certa.

Aí foi pra produção.

Em três dias, o suporte começou a receber reclamações de respostas sem sentido. O agente estava tomando decisões baseadas em contexto que já não existia mais. Às vezes repetia uma ação que o usuário tinha cancelado dois turnos atrás. Às vezes simplesmente parava no meio de um fluxo sem sinalizar nada.

Eu já tinha visto esse padrão antes. E toda vez que ele aparece, o problema não é o modelo. É o que você passou pra ele.


A ilusão do contexto limpo

Quando você constrói um agente em ambiente controlado, o contexto é sempre limpo. Você sabe exatamente o que está no histórico. As ferramentas retornam dados previsíveis. Os turnos são curtos. O usuário segue um fluxo esperado.

Em produção, nada disso é garantido.

O usuário começa uma intenção, muda de ideia no meio, faz uma pergunta paralela e volta pra intenção original três mensagens depois. A ferramenta que busca dados externos retorna um payload com campos nulos que não existiam no ambiente de teste. O histórico acumulou doze turnos e agora está empurrando informação crítica pra fora da janela de atenção do modelo.

O agente não quebra. Ele continua respondendo. Só que as respostas começam a derivar do que o usuário realmente quer.

Eu chamo isso de Deriva de Contexto. Não é um erro explícito. É um degradação silenciosa.


O que a maioria faz errado

A maioria dos times trata o contexto como um log. Você vai adicionando mensagens, o modelo lê tudo, e assume que ele vai entender o estado atual do mundo a partir do histórico.

Isso funciona pra conversas curtas. Falha em agentes que precisam manter estado ao longo de múltiplos turnos, múltiplas ferramentas e intenções que mudam no meio do caminho.

O problema técnico é claro: LLMs não têm memória. Eles têm contexto. E contexto é finito, ruidoso e não uniforme em termos de atenção. Uma informação no turno dois de uma conversa de vinte turnos pesa menos do que a mesma informação no turno dezenove.

Mas o problema arquitetural é mais sutil. Quando você usa o histórico como substituto de estado, você transfere a responsabilidade de rastrear o que está acontecendo pro modelo. E o modelo vai errar.


O problema real por trás do problema

O que descobri, depois de depurar esse tipo de falha mais vezes do que gostaria, é que agentes em produção precisam de duas coisas separadas: memória de fatos e memória de estado.

Memória de fatos é o que o usuário disse, o que as ferramentas retornaram, o que ficou acordado.

Memória de estado é onde o agente está no fluxo: qual intenção está ativa, quais passos já foram concluídos, o que está pendente, o que foi cancelado.

Quando você mistura as duas no mesmo histórico de mensagens, o modelo precisa inferir o estado a partir dos fatos. Às vezes ele acerta. Às vezes ele acha que o usuário ainda quer cancelar o pedido porque foi o que ele disse quatro turnos atrás, mesmo que dois turnos depois tenha mudado de ideia.


O que funciona melhor na prática

A mudança que mais impactou a consistência dos agentes que trabalhei foi separar o estado explícito do histórico narrativo.

Em vez de deixar o modelo inferir onde o fluxo está, você passa o estado atual como parte do prompt de sistema, atualizado a cada turno. Não como texto genérico. Como estrutura.

// Exemplo simplificado de como o contexto de estado é injetado
struct AgentContext {
    var activeIntent: String
    var completedSteps: [String]
    var pendingActions: [String]
    var cancelledActions: [String]
    var relevantFacts: [String]
}

Antes de cada chamada ao modelo, você serializa esse contexto, injeta no prompt de sistema e trunca o histórico de mensagens pra manter só o que é recente e relevante.

O modelo para de precisar inferir o estado. Você passa o estado. Ele opera em cima disso.


A parte que ninguém gosta de ouvir

Isso significa que você precisa de uma camada de orquestração que rastreia e atualiza o estado fora do modelo. Que decide o que vai e o que não vai pro contexto. Que tem lógica de negócio sobre o que constitui um passo concluído, uma ação cancelada, uma intenção ativa.

É mais trabalho. É uma peça de software real, com suas próprias responsabilidades, seus próprios bugs e seus próprios testes.

A maioria dos times não quer fazer isso. Quer acreditar que o modelo vai resolver. E o modelo resolve, até o momento em que não resolve mais.

Contexto não é o que você coleta. É o que você decide passar.

Essa distinção parece óbvia escrita assim. Em produção, ela custa dias de depuração pra aprender.


Trade-offs honestos

Essa abordagem tem um custo real: você aumenta a complexidade da camada de orquestração. Agora existe lógica de estado fora do modelo, e essa lógica precisa ser testada, mantida e evoluída junto com os fluxos do agente.

Se o agente muda de comportamento, você precisa atualizar tanto o prompt quanto a lógica de estado. Isso pode causar drift entre as duas partes se não houver disciplina.

Também existe o risco de over-engineering. Pra agentes simples, com fluxos curtos e bem definidos, essa separação pode ser desnecessária. O histórico de mensagens é suficiente.

O problema é que você raramente sabe, antes de ir pra produção, se o fluxo vai ser simples.


O que eu faria diferente hoje

Começaria com a separação de estado desde o início, mesmo que o agente pareça simples.

O custo de adicionar essa camada no começo é baixo. O custo de adicionar depois, quando já existe comportamento inesperado em produção e o histórico de conversas reais virou evidência de debug, é muito maior.

Também investiria mais cedo em observabilidade específica pra contexto: logar o que está sendo passado pro modelo a cada turno, não só a resposta que voltou. A maioria dos sistemas de agentes que vi logam output, não input. Você só descobre o que estava no contexto quando já quebrou.


Reflexão final

Existe uma crença comum de que o modelo é o componente crítico do agente. Você escolhe o modelo certo, ajusta o prompt, e o comportamento emerge.

Na prática, o modelo é o componente mais previsível. Dado o mesmo contexto, ele se comporta de forma consistente.

O que muda em produção é o contexto. E o contexto muda porque o mundo real é ruidoso, os usuários não seguem fluxos esperados, e sistemas externos retornam dados que não existiam no ambiente de teste.

Agentes não falham por causa do modelo. Eles falham porque o contexto que chegou pro modelo não era o que você pensava que estava passando.

Isso muda onde você coloca a atenção durante o desenvolvimento. E muda completamente onde você procura quando algo quebra.