Todos os case studies

01Detalhamento · evals

Testar o que não é determinístico: 26 conversas de prova

Não dá pra cobrar a resposta exata de um modelo de linguagem, mas dá pra cobrar o que ele nunca pode fazer. Como 'o agente tá mentindo sobre preço?' virou uma bateria que roda em cinco minutos e trava o deploy na hora.

Em produçãoDavid RochaFaz parte do sistema ZapBeta
01

O problema

Modelo de linguagem não se testa com assert de string. Pergunta a mesma coisa duas vezes e as palavras mudam, a suíte fica vermelha por nada e alguém acaba desligando ela. Só que variar a forma não é poder tudo: o agente pode dizer um preço de cem jeitos, desde que o preço exista no catálogo. Pode confirmar um horário com as palavras dele, desde que o horário tenha sido reservado de verdade. Foi essa fronteira que eu transformei em teste.

02

As restrições

  • 01Determinístico o bastante pra travar deploy. Uma suíte que falha uma rodada a cada cinco é pior do que suíte nenhuma: o time aprende a ignorar o vermelho.
  • 02Rápida. Roda a cada mudança que encosta no agente, então meia hora de execução significa que ninguém roda.
  • 03Cobrar comportamento, não a frase. "Cotou R$ 54,90?" quebra à toa; "cotou um preço que existe no catálogo?" é o que eu preciso garantir.
  • 04Agente real, ferramentas reais. Mockar o modelo é testar o mock. A suíte dirige o mesmo runner que responde no WhatsApp.
03

A arquitetura

  1. cases.ts

    26 conversas roteirizadas + os fatos da empresa que o agente pode saber

  2. harness.ts

    dirige o runner real do agente, registra resposta + trace inteiro

  3. checks.ts

    "provas" de invariante, turno a turno, contra resposta + trace

  4. npm run eval

    determinístico, ~5 min; falhar é build vermelho

As provas são a parte que interessa. provaSupervisor derruba qualquer preço, total ou produto que não esteja no catálogo. provaNomeInterno falha se o agente admite ser uma "IA" em vez de seguir como Especialista. provaDiaDaSemana confere se o dia da semana que ele escreveu bate com a data. E provaPedido decodifica o Pix que ele mandou: faz o parse do payload EMV, recalcula o CRC16 e confere o valor. Se o código sai errado, quem descobre é o cliente no banco, não eu.

04

A parte difícil

As provas foram fáceis de escrever, e eu confiei demais nelas. Montei um supervisor que acusava de mentira todo número na tela que o agente não tivesse acabado de buscar numa ferramenta. Na primeira rodada completa, 126 acusações. Pareceu minucioso. Estava quase tudo errado: o agente tinha buscado os números, só não no formato que a trava esperava.

Antes de deixar essa trava barrar deploy, fui medir ela contra os traces — o que o agente de fato fez naquele turno — e ajustei até as acusações baterem com a realidade. Uma trava é uma hipótese até alguém medir. E uma que acusa 126 vezes à toa o time desliga na primeira semana.

05

Trade-offs

  • Roteirizado, não generativo. Sou eu que escrevo os turnos do cliente, então a cobertura vai até onde vai a minha imaginação. Preferi profundidade nos modos de falha que custam dinheiro.
  • Assert de comportamento não pega tom. A suíte prova que o agente não mentiu num preço; não prova que ele foi acolhedor. Isso eu leio na transcrição.
  • Cinco minutos é orçamento de portão de deploy, não de pré-commit. Roda no CI, não a cada tecla.
06

O que eu faria diferente

Eu inverteria uma coisa. Em vez de escrever a conversa e cobrar invariante em cima dela, deixaria o modelo gerar clientes adversariais contra um conjunto fixo de invariantes: um cliente com o único objetivo de fazer o agente cotar um preço que não existe. O roteiro prova que os bugs que eu imaginei sumiram; o adversário gerado acharia os que eu não imaginei. Hoje eu sei o formato desse harness — quando comecei, não sabia, e subir a versão roteirizada primeiro foi a escolha certa.

Vamos conversar

Estou aberto a trabalhos remotos construindo com LLMs: agentes, avaliação, ferramental e o produto inteiro em volta disso. Ficarei feliz em mostrar muito mais numa call. Pode chamar.

Disponível para posições remotas, trabalhando do Brasil.Fuso: UTC−3 (Brazil)