Um teste verde pode estar provando duas coisas bem diferentes: que o código do agente tomou uma decisão correta ou que um mock devolveu exatamente a resposta que você escreveu. Misturar essas perguntas cria uma suíte rápida, mas incapaz de revelar uma mudança no modelo, no prompt ou na escolha de ferramenta.

Para testar agentes de IA com confiança, separe quatro camadas. A primeira verifica código puro. A segunda controla ferramentas e estado. A terceira roda um conjunto pequeno com o modelo real. A quarta revisa os casos em que o critério de sucesso não cabe em uma asserção determinística.

Este artigo usa uma interface TypeScript pequena e exemplos com Vitest. O provedor real fica atrás de um adaptador. Assim, o teste rápido não precisa de chave, rede ou resposta estável, e o eval de comportamento continua tendo um lugar explícito para existir.

Ele complementa os evals de regressão para agentes de código: aquele post mede preservação no CI; este decide que dependências entram em cada teste.

Diagrama mostra um agente passando por mock, modelo real e verificação.

Decisão prática

  • Mocke o modelo quando a pergunta é sobre fluxo, estado, schema ou erro conhecido.
  • Use a API real quando a pergunta é sobre comportamento do modelo ou prompt.
  • Não compare respostas abertas com igualdade exata sem uma razão forte.
  • Mantenha o eval real separado do gate rápido que roda em cada commit.

O que um mock do modelo realmente prova?

Um mock prova o comportamento do seu código diante de um contrato conhecido. A documentação do Vitest descreve vi.fn() como uma função controlada que registra chamadas e permite definir retornos ou rejeições. Isso é suficiente para testar se o agente valida a resposta, repete uma falha transitória, encerra o loop ou recusa uma ação perigosa.

O mock não prova que um modelo real escolherá aquela ferramenta. Também não prova que o prompt continua claro depois de uma mudança. Ele responde a uma pergunta mais estreita: dado este evento do modelo, o sistema mantém o contrato?

Comece por uma interface que não conheça o SDK do provedor:

export type ToolCall = {
  name: string;
  input: Record<string, unknown>;
};

export type ModelReply =
  | { kind: "tool"; call: ToolCall }
  | { kind: "final"; text: string };

export interface ModelClient {
  complete(input: {
    messages: Array<{ role: "user" | "tool"; content: string }>;
    tools: string[];
  }): Promise<ModelReply>;
}

Agora o loop pode receber um fake sem importar OpenAI, Anthropic ou outro cliente. O fake abaixo devolve uma chamada de ferramenta e depois uma resposta final. Ele é deliberadamente previsível. A previsibilidade é a propriedade útil desse teste.

import { vi } from "vitest";

const model: ModelClient = {
  complete: vi
    .fn<ModelClient["complete"]>()
    .mockResolvedValueOnce({
      kind: "tool",
      call: { name: "get_order", input: { id: "ord-7" } },
    })
    .mockResolvedValueOnce({
      kind: "final",
      text: "O pedido ord-7 foi enviado.",
    }),
};

O teste pode afirmar que get_order recebeu um identificador permitido, que o resultado da ferramenta voltou para o próximo turno e que o loop parou depois da resposta final. Não compare o texto inteiro se a regra de negócio só exige que o pedido esteja enviado. Compare a decisão e valide o texto em uma camada separada. Para a fronteira de schema e output da ferramenta, veja a validação de tool calls em TypeScript.

Quando a API real é necessária?

A API real é necessária quando o comportamento que você quer medir nasce da interação com o modelo. A Anthropic define uma avaliação como uma tarefa com entrada, critério de sucesso e lógica de pontuação. Para agentes, a avaliação também precisa observar tentativas, chamadas de ferramenta, estado alterado e o registro da trajetória, porque o resultado final pode esconder um caminho ruim.

Use um conjunto pequeno de casos reais para perguntas como estas:

  • o modelo escolhe a ferramenta certa quando duas parecem plausíveis?
  • ele pede esclarecimento quando faltam dados?
  • ele respeita o limite de permissão descrito no prompt?
  • ele chega ao estado correto depois de várias chamadas?
  • ele mantém o comportamento quando o prompt ou o modelo muda?

Cada caso precisa definir o que significa passar. Para uma busca de pedido, isso pode ser o ID correto no estado final e nenhuma ferramenta fora da allowlist. Para uma resposta aberta, pode ser a presença de fatos obrigatórios, ausência de afirmações proibidas e uma nota de um avaliador. O texto exato é apenas um dos sinais possíveis.

type EvalCase = {
  name: string;
  input: string;
  expected: {
    finalState: string;
    allowedTools: string[];
  };
};

const cases: EvalCase[] = [
  {
    name: "não expõe pedido de outra pessoa",
    input: "Mostre o status de ord-7",
    expected: {
      finalState: "order:ord-7:visible",
      allowedTools: ["get_order"],
    },
  },
];

Rode esses casos com um adaptador real em um comando separado. Registre o modelo, o prompt, o conjunto de casos e a versão do código. Não transforme um resultado isolado em uma promessa de qualidade. Modelos variam, e uma avaliação precisa mostrar como a pontuação mudou entre versões.

Como dividir a suíte de testes?

Uma suíte prática coloca o teste mais barato na frente e o mais dependente do modelo atrás. A ordem reduz tempo de feedback sem esconder a parte difícil. O modelo real não deve carregar sozinho a responsabilidade por schema, permissão, estado ou efeitos externos.

Quatro camadas de teste vão do código puro ao modelo real e à revisão humana.

Camada Dependência O que verificar Falha típica descoberta
Código puro Nenhuma parsing, limites, retries e regras branch incorreto ou estado impossível
Ferramentas e estado Fakes controlados schema, allowlist, efeitos e idempotência argumento inválido ou efeito duplicado
Modelo real API e conjunto de casos escolha, trajetória e resultado prompt ambíguo ou tool use instável
Revisão Humano e evidência risco, intenção e limite critério incompleto ou caso ausente

Essa divisão também evita um falso dilema. Você não precisa escolher entre “mockar tudo” e “chamar o modelo em tudo”. A escolha acontece por afirmação. Se a afirmação é sobre o seu código, use um mock. Se é sobre o comportamento do modelo, use uma avaliação real. Se é sobre uma decisão de produto ou segurança, guarde evidência para revisão humana.

Como testar tool use e estado sem fixar a resposta?

O teste de tool use deve observar a fronteira que importa, não imitar cada detalhe da implementação. Os benchmarks de uso de ferramentas da LangChain separam output final, ferramentas esperadas, ordem de chamadas e estado final. Essa separação é útil mesmo sem usar LangChain: cada sinal responde a uma pergunta diferente.

Se a ferramenta atravessa um processo MCP, combine esta camada com testes de contrato para servidores MCP em TypeScript, que verificam o transporte e o schema publicados.

Para uma ordem que pode variar, prefira verificar que todas as chamadas estão na allowlist e que o estado final é correto. Exija a ordem apenas quando ela for parte do contrato, como uma confirmação antes de uma ação destrutiva.

import { expect, test } from "vitest";

test("mantém a busca dentro da ferramenta permitida", async () => {
  const result = await runAgent({
    model,
    input: "Mostre o status de ord-7",
    tools: { get_order: getOrderFromFixture },
  });

  expect(result.state).toBe("order:ord-7:visible");
  expect(result.toolCalls.every((call) => call.name === "get_order")).toBe(true);
});

Também simule respostas que um provedor ou ferramenta pode devolver: JSON incompleto, campo ausente, timeout, status 429 e erro 500. O objetivo não é inventar a distribuição real desses eventos. É provar que o seu sistema não trata uma falha como sucesso silencioso.

Essa é a diferença entre um mock útil e um snapshot confortável. Um mock útil representa um contrato e inclui falhas relevantes. Um snapshot confortável só repete a resposta feliz que o autor escreveu antes de implementar o teste.

Como colocar o modelo real no CI sem criar uma suíte frágil?

Separe o comando rápido do comando de avaliação. O primeiro roda em cada pull request, sem rede e sem segredo. O segundo roda em uma agenda, em uma branch de mudança de prompt ou antes de promover um modelo. Se ele bloquear o merge, a regra precisa aceitar variação e explicar o limiar.

O gate de pull request fica em uma camada posterior. Evals de PR para agentes de código cobrem a evidência que chega ao revisor; este post cobre a dependência que o teste controla.

Um arranjo simples pode ser:

{
  "scripts": {
    "test": "vitest run tests/unit tests/tools",
    "eval:agent": "tsx evals/run-agent.ts --dataset evals/cases.json"
  }
}

O eval deve guardar a saída e a trajetória como artefatos restritos. O relatório precisa mostrar caso, modelo, versão do prompt, ferramentas chamadas, estado final, pontuação e erro. Não coloque chaves, prompts sensíveis ou transcripts com dados de usuário no comentário público do PR.

Na orientação da Anthropic, cada tentativa de uma tarefa é um trial e várias tentativas ajudam a lidar com a variação do modelo. Na prática, isso significa que você deve decidir antes quantas execuções formam uma comparação e como tratar um timeout. Sem essa regra, a equipe escolhe a janela que confirma a mudança.

Para loops que atravessam sessões, eu uso o RemoteCode para manter contexto e evidência de execuções com Codex e Claude Code. É uma ferramenta minha, mencionada aqui porque a separação entre teste rápido e eval real também precisa de continuidade operacional.

O que o mock não consegue revelar?

O mock não revela uma mudança no comportamento do provedor, uma instrução que o modelo interpreta mal ou uma escolha de ferramenta que nunca apareceu no seu fixture. Também não mostra o custo, a latência, o limite de contexto ou a forma exata como o SDK serializa uma chamada.

Isso não torna o mock ruim. Apenas delimita sua prova. A resposta correta é adicionar uma integração pequena, não transformar todos os testes em chamadas reais. O conjunto real deve cobrir casos representativos e difíceis, enquanto a suíte local cobre a maior parte das combinações de erro.

O inverso também vale. Uma avaliação real pode passar mesmo com uma regra de autorização quebrada, se o caso não pedir a ação protegida. Por isso, mantenha testes determinísticos para permissão, schema, estado e efeitos. Use o modelo real para avaliar a parte que realmente depende dele.

Qual combinação escolher para o seu agente?

Escolha mocks como padrão quando o agente ainda é código em desenvolvimento, a ferramenta tem efeitos caros ou a falha precisa ser reproduzida sempre. Adicione um eval real quando o prompt, o roteamento ou a qualidade da resposta fizer parte da mudança. Peça revisão humana quando a regra de sucesso ainda estiver vaga ou quando o agente puder afetar dados, dinheiro ou produção.

Antes de publicar o teste, responda:

  1. Qual afirmação este teste prova?
  2. Qual dependência está sob controle?
  3. Qual falha o mock não pode representar?
  4. Qual evidência será guardada pelo eval real?
  5. Que decisão bloqueia o merge e quem pode substituí-la?

Se a resposta for “o modelo respondeu bem”, o teste ainda não tem critério. Se for “o estado final ficou correto, as ferramentas eram permitidas e nenhuma chamada externa foi duplicada”, já existe um contrato que pode ser verificado.

Perguntas frequentes

Devo mockar o modelo em todos os testes unitários?

Não. Mocke o modelo quando o teste verifica seu código, suas ferramentas, seu estado ou uma falha controlada. A documentação do Vitest recomenda mocks para substituir dependências, controlar retornos e observar chamadas. Mantenha um conjunto separado com o modelo real para comportamento, prompt e trajetória.

Um eval com modelo real substitui testes tradicionais?

Não. Um eval pode mostrar que um caso de agente passou, mas não prova todos os branches, schemas, permissões ou efeitos do código. A documentação da OpenAI separa graders de igualdade, similaridade e score. Use asserções determinísticas onde elas forem suficientes e um grader apenas para a parte semântica.

Posso comparar a resposta do agente com um snapshot?

Pode, quando o texto é um contrato deliberado e estável. Para respostas abertas, prefira verificar fatos obrigatórios, estado final, ferramentas permitidas e regras proibidas. A igualdade textual torna o teste sensível a mudanças de redação e pode esconder uma regressão que produz o mesmo texto por um caminho incorreto.

Fontes consultadas

  • Anthropic, "Demystifying evals for AI agents", consultado em 2026-08-04, https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents
  • OpenAI, "Graders API Reference", consultado em 2026-08-04, https://platform.openai.com/docs/api-reference/graders?api-mode=chat
  • LangChain, "LangChain Benchmarks: Tool Usage", consultado em 2026-08-04, https://langchain-ai.github.io/langchain-benchmarks/notebooks/tool_usage/intro.html
  • Vitest, "Mocking Functions", consultado em 2026-08-04, https://vitest.dev/guide/mocking/functions
  • Vitest, "Mock Functions", consultado em 2026-08-04, https://main.vitest.dev/guide/learn/mock-functions