DEV Community

Cover image for Como Testar Agentes de IA Não Determinísticos: Quando temperature=0 Não Basta
Lucas
Lucas

Posted on • Originally published at apidog.com

Como Testar Agentes de IA Não Determinísticos: Quando temperature=0 Não Basta

Seu teste passou na segunda-feira com a mesma entrada, o mesmo código e temperature=0. Na terça-feira, falhou sem nenhuma alteração no seu lado: a asserção esperava uma string exata, mas o modelo respondeu com o mesmo significado em uma formulação diferente. O agente continua correto, mas sua equipe agora está depurando a suíte de testes em vez do produto.

Experimente o Apidog hoje

Esse é o custo de testar sistemas que chamam modelos de linguagem. A saída pode variar mesmo com temperature=0, portanto testes que dependem de igualdade textual tendem a se tornar instáveis. Este guia mostra como substituir asserções frágeis por verificações de contrato: esquema, tipos, intervalos, campos obrigatórios e comportamento de ferramentas. É um aprofundamento do terceiro modo de falha descrito em por que agentes de IA falham em produção.

Por que temperature=0 não significa determinismo

A temperatura controla a amostragem do próximo token. Em 0, o modelo seleciona o token mais provável, mas isso não garante respostas idênticas byte a byte entre execuções.

O problema está em toda a pilha de inferência:

  • Operações de ponto flutuante em GPU podem produzir pequenas diferenças conforme a ordem de execução.
  • Uma diferença mínima nos logits pode alterar o token escolhido.
  • O provedor pode agrupar sua requisição com outro tráfego, rotear para outro hardware ou região e atualizar kernels e bibliotecas.
  • Pesos podem ser requantizados ou o runtime de inferência pode mudar.

Uma discussão no vLLM detalha por que seed fixa e temperature=0 não bastam para reprodutibilidade bit a bit.

A conclusão prática é simples: determinismo é uma propriedade da pilha inteira, não de um único parâmetro da requisição. Portanto, não use texto idêntico como linha de base para seus testes.

Por que asserções de string exata tornam a suíte instável

Esta asserção parece razoável:

assert response == "Your order total is $42.00."
Enter fullscreen mode Exit fullscreen mode

Mas ela falha se o modelo responder:

Seu total é de $42.00.
Enter fullscreen mode Exit fullscreen mode

A resposta continua correta, mas o teste fica vermelho.

Esse tipo de falso positivo cria um efeito perigoso:

  1. A equipe passa a confiar menos nos testes.
  2. Falhas são reexecutadas até ficarem verdes.
  3. Alertas reais ficam escondidos no ruído.
  4. Regressões importantes deixam de receber atenção.

Já discutimos o que causa testes instáveis e por que eles se espalham. Saídas não determinísticas de LLMs são uma das formas mais rápidas de introduzir esse problema.

Não tente resolver capturando mais snapshots de texto. Isso aumenta o acoplamento justamente ao elemento que mais pode variar: a redação.

Teste estrutura e significado, não o texto exato

A formulação pode mudar, mas o contrato não deveria mudar.

Por exemplo, um agente de suporte pode confirmar um reembolso de várias formas, mas toda resposta válida deve conter:

  • o ID do pedido;
  • o valor do reembolso;
  • um status válido.

Em vez de perguntar:

“O modelo escreveu exatamente esta frase?”

Pergunte:

“A resposta possui os campos, tipos, valores e limites esperados?”

Essa abordagem mantém o teste estável diante de reformulações, mas ainda detecta regressões reais: campos ausentes, JSON inválido, valores fora dos limites e chamadas de ferramenta malformadas.

1. Valide a resposta com um esquema JSON

Quando o agente retorna dados estruturados, defina um esquema e valide cada resposta contra ele.

Exemplo de resposta esperada:

{
  "order_id": "ORD-12345",
  "status": "refunded",
  "amount": 42.0
}
Enter fullscreen mode Exit fullscreen mode

Um esquema pode exigir:

  • order_id como string em um formato específico;
  • status limitado a valores permitidos;
  • amount como número;
  • campos obrigatórios presentes;
  • ausência de propriedades inesperadas.

Exemplo com JSON Schema:

{
  "type": "object",
  "required": ["order_id", "status", "amount"],
  "properties": {
    "order_id": {
      "type": "string",
      "pattern": "^ORD-[0-9]+$"
    },
    "status": {
      "type": "string",
      "enum": ["refunded", "pending", "denied"]
    },
    "amount": {
      "type": "number",
      "minimum": 0
    }
  },
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

Essa é uma das asserções mais fortes para respostas não determinísticas. Ela detecta problemas que realmente importam:

  • o modelo omitiu um campo;
  • retornou prosa em vez de JSON;
  • aninhou o objeto de forma incorreta;
  • retornou uma string onde era esperado um número;
  • produziu um status não permitido.

Você pode carregar o esquema no Apidog e validar as respostas da API do agente contra esse contrato.

2. Valide chamadas de ferramenta, não o raciocínio textual

Se o agente chama ferramentas, teste a chamada estruturada em vez da frase que o levou a tomá-la.

Para um agente de reservas, valide:

  1. Se ele escolheu a ferramenta correta.
  2. Se chamou o endpoint esperado.
  3. Se o payload segue o esquema da ferramenta.

Exemplo de payload esperado:

{
  "guests": 2,
  "date": "2025-08-15",
  "room_type": "double"
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de validações:

assert tool_call.name == "create_reservation"
assert tool_call.method == "POST"
assert tool_call.path == "/reservations"

assert isinstance(tool_call.payload["guests"], int)
assert tool_call.payload["guests"] > 0
assert is_valid_date(tool_call.payload["date"])
assert tool_call.payload["room_type"] in ["single", "double", "suite"]
Enter fullscreen mode Exit fullscreen mode

Também valide que o agente não inventou parâmetros:

allowed_fields = {"guests", "date", "room_type"}
assert set(tool_call.payload.keys()) <= allowed_fields
Enter fullscreen mode Exit fullscreen mode

O método ponta a ponta para testar chamadas de API de um agente mostra como capturar contratos de ferramentas e verificar requisições geradas pelo agente.

3. Use intervalos numéricos em vez de valores exatos

Para números gerados ou transmitidos pelo modelo, prefira limites a igualdade exata.

Considere um agente de carrinho de compras. Em vez de validar:

assert response["total"] == 42.00
Enter fullscreen mode Exit fullscreen mode

Valide regras de negócio:

assert response["total"] >= 0
assert response["total"] <= cart_subtotal + max_shipping + max_tax
Enter fullscreen mode Exit fullscreen mode

Essa verificação captura erros relevantes:

  • total negativo;
  • valor muito acima do esperado;
  • total zero para um carrinho com itens;
  • valor enviado como string ou campo ausente, se combinado com validação de esquema.

Use o mesmo padrão para:

  • pontuações de confiança;
  • quantidade de itens;
  • uso de tokens;
  • custo estimado;
  • orçamento de latência;
  • número de resultados retornados.

Escolha o intervalo mais amplo que ainda detecte um bug real.

4. Verifique campos obrigatórios e campos proibidos

Duas verificações simples têm alto valor:

  1. Campos necessários existem e não são nulos.
  2. Campos que não podem ser expostos estão ausentes.

Exemplo:

assert response.get("resolution") is not None
assert "internal_notes" not in response
assert "raw_prompt" not in response
Enter fullscreen mode Exit fullscreen mode

Para um ticket de suporte, resolution pode ser obrigatório. Por outro lado, internal_notes e raw_prompt nunca devem chegar ao cliente.

Essas asserções são resistentes à reformulação porque verificam a estrutura da resposta. Elas também ajudam a detectar vazamentos de privacidade e dados internos.

5. Para texto livre, valide propriedades semânticas e limites

Nem toda resposta será JSON. Quando o payload for prosa, não compare o texto inteiro. Verifique propriedades observáveis.

Exemplo:

assert order_id in response_text
assert len(response_text) <= 500
assert "senha" not in response_text.lower()
assert "prompt interno" not in response_text.lower()
Enter fullscreen mode Exit fullscreen mode

Você também pode testar listas de termos proibidos:

for forbidden_phrase in forbidden_phrases:
    assert forbidden_phrase.lower() not in response_text.lower()
Enter fullscreen mode Exit fullscreen mode

Quando precisar validar o significado geral, compare embeddings com uma resposta de referência e use um limiar:

similarity = cosine_similarity(
    embed(response_text),
    embed(reference_answer)
)

assert similarity >= 0.82
Enter fullscreen mode Exit fullscreen mode

Use esse tipo de teste como um filtro amplo, não como prova de correção factual. Similaridade semântica pode detectar uma resposta fora do tópico, mas não substitui validação de esquema, regras de negócio e verificações de segurança.

6. Faça snapshots de contrato, não snapshots de texto

Snapshots ainda são úteis se registrarem apenas partes estáveis da resposta.

Em vez de congelar isto:

Seu pedido ORD-12345 foi reembolsado no valor de R$ 42,00.
Enter fullscreen mode Exit fullscreen mode

Congele o contrato:

{
  "required_keys": ["order_id", "status", "amount"],
  "status_enum": ["refunded", "pending", "denied"],
  "amount_range": {
    "min": 0,
    "max": 500
  }
}
Enter fullscreen mode Exit fullscreen mode

Um snapshot útil responde:

  • Quais chaves existem?
  • Quais tipos são esperados?
  • Quais enums são permitidos?
  • Quais intervalos numéricos são válidos?
  • Quais campos não podem aparecer?

Assim, uma falha de snapshot representa uma mudança estrutural que vale a pena revisar, e não apenas um sinônimo.

Estado e memória tornam os testes mais difíceis

As estratégias anteriores parecem simples quando há uma entrada e uma saída. Agentes com memória adicionam estado entre interações.

A resposta pode depender de:

  • documentos recuperados;
  • memória persistida;
  • resumos de conversas anteriores;
  • ordem das mensagens;
  • chamadas de ferramentas feitas anteriormente.

Duas execuções da mesma conversa podem divergir porque a recuperação classificou documentos em outra ordem ou porque um resumo produzido em uma etapa anterior alterou o contexto. O guia sobre como a memória de agentes de IA funciona explica onde esse estado pode residir.

Para manter os testes controláveis, adote dois hábitos.

Inicialize o estado antes de cada teste

Comece cada cenário com memória conhecida:

agent.reset_memory()
agent.seed_memory([
    {"role": "user", "content": "Meu pedido é ORD-12345."}
])
Enter fullscreen mode Exit fullscreen mode

Isso reduz as variáveis em movimento e torna falhas mais fáceis de reproduzir.

Teste invariantes independentes do caminho

Nem toda conversa seguirá a mesma sequência, mas algumas regras devem ser sempre verdadeiras.

Exemplos:

assert account_balance >= 0
assert reservation_count == 1
assert "internal_notes" not in final_response
Enter fullscreen mode Exit fullscreen mode

Um agente que concluiu uma reserva deve terminar com exatamente uma reserva, independentemente de quantas mensagens foram necessárias para chegar lá.

Simule dependências para tornar os testes repetíveis

Não execute esses testes contra APIs externas reais.

APIs de terceiros podem:

  • impor limites de taxa;
  • alterar dados;
  • ficar indisponíveis;
  • retornar resultados diferentes;
  • introduzir uma segunda fonte de aleatoriedade.

Para isolar o comportamento do agente, simule suas dependências com respostas controladas.

Exemplo de configuração:

API de pagamento  -> sempre retorna o mesmo recibo
API de busca      -> sempre retorna os mesmos três resultados
API de estoque    -> retorna cenários definidos pelo teste
Enter fullscreen mode Exit fullscreen mode

Com mocks, você também pode forçar casos extremos:

{
  "payment_api": {
    "status": 503,
    "body": {
      "error": "service_unavailable"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Então valide se o agente responde corretamente:

assert response["status"] == "pending"
assert response["retry_recommended"] is True
Enter fullscreen mode Exit fullscreen mode

Use o Apidog para configurar simulações de dependências com corpos estáveis e controláveis, combinando mocks com validação de esquema. Isso faz parte da prática mais ampla de testes de IA agêntica, na qual simulação e asserção trabalham juntas.

Onde o Apidog se encaixa — e onde não

É importante delimitar o papel da ferramenta.

O Apidog é uma plataforma para design, teste e simulação de APIs. Ele não é:

  • framework de agentes;
  • host de modelo;
  • runtime de agente;
  • orquestrador de etapas;
  • plataforma de avaliação de raciocínio;
  • ferramenta de observabilidade de LLMs.

Ele não constrói nem executa seu agente.

O encaixe está na camada de API. É nela que você pode:

  1. Validar respostas da API do agente contra esquemas, tipos, intervalos e regras de campos.
  2. Validar payloads enviados para ferramentas.
  3. Simular APIs externas para remover variabilidade das dependências.
  4. Executar testes de contrato repetíveis.

O Apidog cobre o contrato das requisições e respostas, não o modelo que produz a saída.

Teste o contrato, não a redação

O não determinismo não é um bug que você elimina com temperature=0. Ele é uma característica de executar modelos de linguagem em uma pilha de inferência real.

Para uma suíte confiável:

  • valide esquemas JSON;
  • verifique tipos e enums;
  • use intervalos numéricos;
  • exija campos obrigatórios;
  • bloqueie campos proibidos;
  • valide chamadas de ferramentas;
  • use verificações semânticas apenas como complemento;
  • controle estado e memória;
  • simule dependências externas.

Comece com uma asserção instável da sua suíte. Substitua uma comparação de string exata por validação de esquema e intervalo. Depois, use o Apidog para validar o contrato das respostas do agente e simular as dependências necessárias para testes repetíveis.

Top comments (0)