Nas últimas décadas, Engenharia significava uma coisa: remover a ambiguidade. Significava definir interfaces estritas, impor segurança de tipos e garantir que Input A \+ Code B \= Output C.

A engenharia de software tradicional é determinística. Desempenhamos o papel de Controladores de Tráfego; somos donos das estradas, dos semáforos e das leis. Decidimos exatamente para onde os dados vão e quando.A Engenharia de Agentes é probabilística. Nós somos Despachantes. Damos instruções a um motorista (um LLM) que pode pegar um atalho, se perder ou decidir dirigir na calçada porque “parecia mais rápido”.

É um paradoxo que engenheiros juniores frequentemente entreguem agentes funcionais mais rapidamente do que os seniores. Por quê? Quanto mais sênior o engenheiro, menos ele tende a confiar nas capacidades de raciocínio e de seguir instruções do Agente. Nós lutamos contra o modelo e tentamos “eliminar via código” a natureza probabilística.

Aqui estão 5 exemplos onde hábitos tradicionais de engenharia entram em conflito com a nova realidade da Engenharia de Agentes.

1. O Texto é o Novo Estado

Na engenharia tradicional, modelamos o mundo com estruturas de dados. Definimos esquemas, interfaces e tipos estritos. Isso parece seguro porque é previsível. Instintivamente, tentamos forçar os Agentes a entrar nessa caixa.

A Armadilha: Intenções, preferências ou configurações do mundo real raramente são binárias/estruturadas. As entradas do usuário são contínuas (linguagem natural) em vez de discretas (campos estruturados).

O Texto é o novo Estado. Devemos abandonar o conforto dos booleanos em favor do significado semântico.

Imagine um caso de uso de aprovação de plano de Deep Research (Pesquisa Aprofundada) onde o usuário diz: “Este plano parece bom, mas, por favor, concentre-se no mercado dos EUA.” Um sistema determinístico força isso para is_approved: true/false e nós lobotomizamos o contexto.

Engenharia de Software:

{
  "plan_id": "123",
  "status": "APPROVED" // Nuance is lost here
}

Engenharia de Agentes:

{
  "plan_id": "123",
  "text": "This plan looks good, but please focus on the US market."
}

Ao preservar o texto, o agente subsequente pode ler o feedback (“Aprovado, mas foque no mercado dos EUA”) e ajustar seu comportamento dinamicamente.

Outro exemplo são as preferências do usuário. Um sistema determinístico pode armazenar is_celsius: true. Um sistema agêntico armazena “Prefiro Celsius para o clima, mas use Fahrenheit para cozinhar”. O agente pode alternar contextos dinamicamente com base na tarefa.

2. Entregue o Controle

Em microsserviços, a intenção de um usuário corresponde a uma rota POST /subscription/cancel. Em Agentes, temos um único ponto de entrada de linguagem natural com um Cérebro (LLM) que decide o fluxo de controle com base nas ferramentas disponíveis, na entrada e nas instruções.

A Armadilha: Nós tentamos codificar rigidamente o fluxo no agente, mas as interações não seguem linhas retas. Elas entram em loops, retrocedem e mudam de direção. Um usuário pode querer cancelar e acabar concordando em renovar.

  • Usuário: “Eu quero cancelar minha assinatura.” (Intenção: Cancelamento)
  • Agente: “Eu posso te oferecer um desconto de 50% para ficar.”
  • Usuário: “Na verdade, sim, isso funciona.” (Intenção: Retenção)

Confie no agente para navegar pelo fluxo. Se tentarmos codificar rigidamente cada caso de borda, não estaremos construindo um agente de IA. Devemos confiar no agente para entender a intenção atual com base em todo o contexto.

3. Erros são apenas entradas

No software tradicional, se uma chamada de API falha ou uma variável está ausente, lançamos uma exceção. Queremos que o programa falhe imediatamente para que possamos corrigir o bug.

A Armadilha: Um agente pode levar 5 minutos e custar $0.50. Se o passo 4 de 5 falhar devido a uma entrada ausente ou incorreta, travar toda a execução é inaceitável.

Um erro é apenas mais uma entrada. Em vez de travar, capturamos o erro, o enviamos de volta ao agente e tentamos a recuperação.

agent-error-handling

4. De Testes Unitários a Avaliações

O Desenvolvimento Orientado a Testes (TDD) nos ajuda a escrever código mais robusto, mas não podemos aplicar testes unitários a um Agente. Engenheiros podem desperdiçar semanas procurando por exatidão binária em um sistema probabilístico. Devemos avaliar o comportamento.

A Armadilha: Não podemos escrever asserções binárias para tarefas criativas ou de raciocínio. “Escreva um resumo deste e-mail” tem infinitas saídas válidas. Se tentarmos fazer um Mock do LLM, não estamos testando o agente, estamos testando nossa concatenação de strings.

Avaliações acima de Testes. Não podemos testar unitariamente o raciocínio. Devemos validar a Confiabilidade e a Qualidade, e rastrear as verificações intermediárias:

  • Confiabilidade (Pass^k): Nós não perguntamos “Funcionou?”. Nós perguntamos “Com que frequência funciona?”
  • Qualidade (LLM como Juiz): “A resposta é útil? O tom está correto? O resumo é preciso?”
  • Rastreamento: Não verifique apenas a resposta final. Verifique as etapas intermediárias. O agente pesquisou na base de conhecimento antes de responder?

Se o nosso agente tiver sucesso 45 de 50 vezes com uma pontuação de qualidade de 4,5/5, ele pode estar pronto para produção. Estamos gerenciando riscos, não eliminando a variância.

5. Agentes Evoluem, APIs Não

No passado, projetávamos APIs para desenvolvedores humanos, confiando em contexto implícito e interfaces “limpas”. Humanos inferem contexto. Agentes não. Agentes são Literalistas. Se um formato de ID for ambíguo, o agente alucinará um.

A Armadilha: Frequentemente construímos APIs de “Nível Humano” — endpoints que dependem de contexto implícito. Por exemplo, uma variável chamada id é obviamente o user_unique_identifier (UUID) para nós, e pode ser usada em get_user(id). Um Agente pode não ter esse contexto e pode tentar usar o e-mail ou o nome em get_user(id).

Agentes exigem verbosidade, tipagem semântica “à prova de idiotas” (ex: "user_email_address" em vez de "email") e docstrings altamente descritivas que atuam como “contexto”.

  • Ruim: delete_item(id) (O ID é um inteiro? Um UUID? O que acontece se não for encontrado?)
  • Bom: delete_item_by_uuid(uuid: str) com uma docstring/descrição: “Exclui um item. Se o item não for encontrado, retorne uma string de erro descritiva.”

Além disso, Agentes permitem adaptação Just-in-Time. APIs normais são promessas aos desenvolvedores; nós fazemos commit de código que depende dessas APIs e, em seguida, nos afastamos. Se mudarmos uma API de get_user_by_id(id) para get_user_by_email(email), quebramos essa promessa e tudo quebra imediatamente. Um agente, no entanto, lê a nova definição da ferramenta e pode se ajustar a ela.

Conclusão: Confie, mas Verifique

A transição de sistemas determinísticos para agentes probabilísticos é desconfortável. Isso requer que troquemos a certeza pela flexibilidade semântica. Nós não conhecemos mais nem somos donos do caminho exato de execução. Efetivamente, entregamos o fluxo de controle a um modelo não determinístico e armazenamos o estado da nossa aplicação em linguagem natural.

Isso parece errado para uma mente treinada em interfaces estritas. Mas tentar forçar um Agente em uma caixa determinística anula o propósito de usar um. Você não pode eliminar a probabilidade via código. Você deve gerenciá-la por meio de avaliações e autocorreção.

No entanto, “confiar” em um agente não significa deixá-lo correr solto. Devemos encontrar o meio-termo. Agentes falharão de muitas maneiras inesperadas, mas a trajetória é clara. Devemos parar de tentar eliminar a ambiguidade via código e começar a construir sistemas que sejam resilientes o suficiente para lidar com ela.

Isso também significa saber quando usar fluxos de trabalho em vez de agentes. Eu entro em detalhes sobre como arquitetar essas diferenças no meu post sobre Modelos de Agente.

Créditos

Esta postagem é uma tradução autorizada do post Why (Senior) Engineers Struggle to Build AI Agents, de autoria de Philipp Schmid, AI Developer Experience do Google.