pular para o conteúdo
voltar ao mapa
componente
etapa: evaluate
4 min de leitura

Evals - qualidade medida em produção

Evals é o harness que mede se a resposta é boa: dataset de exemplos, métrica (fidelidade, relevância, groundedness) e runner que executa ponta a ponta. Sem eval, melhorar um agente é chute e qualquer ajuste vira rotina.

LangSmithLangfuseBraintrustArize Phoenix

O que é

Eval é um experimento de software. Você junta um dataset de prompts com a saída esperada, define uma métrica (heurística, embedding ou juiz LLM) e roda tudo por um runner que pontua cada caso e devolve uma média. Quando a média sobe, você sabe que melhorou. Quando cai, você sabe que piorou, sem precisar discutir.

Três peças sustentam tudo: o dataset (pares input/expected ou trilhas de produção), a métrica (como comparar saída com esperado) e o runner (loop que chama o agente, captura a saída, aplica a métrica e agrega). A métrica é o ponto onde quase todo time tropeça: a primeira versão costuma ser vaga demais e aceita qualquer coisa.

Dataset, métrica e runner

O dataset precisa ter cobertura real: casos fáceis (smoke), casos de borda (contradição no histórico, ferramenta que falha) e casos adversariais (instrução maliciosa embutida num documento recuperado). 50 casos bem escolhidos vencem 5.000 casos gerados por template. Cobertura importa mais que volume.

A métrica define o que é 'bom'. Fidelidade pergunta se ela é fiel ao contexto recuperado (sem inventar fonte). Relevância pergunta se ela responde o que foi pedido. Groundedness pergunta se cada afirmação tem cabimento extraído, não inventado pelo modelo. Cada métrica vira uma função determinística ou um prompt de juiz; as duas moram no mesmo arquivo.

O runner é a cola. Recebe o dataset, percorre cada caso, chama o agente com timeout, captura a saída (e a cadeia de tool calls, se for agente), aplica a métrica e devolve um relatório em JSON ou CSV. É o componente que entra no CI.

Exemplo de caso avaliado

Antes de rodar 500 casos, vale rodar um único e ler o resultado a olho. Um caso para um agente RAG de políticas de férias pode ser:

evals/cases/férias-elegibilidade.yamlyaml
id: ferias-elege-01
input:
  pergunta: "Posso vender 5 dias de férias para pagar o plano de saúde?"
  contexto_recuperado:
    - "Política 7.2: é permitido vender até 10 dias de férias por ano."
    - "Política 4.1: o adiantamento é depositado em conta corrente do titular."
expected:
  tem_que_dizer:
    - "Sim, é permitido vender até 10 dias"
  nao_pode_dizer:
    - "É proibido vender férias"
metricas:
  - fidelidade: groundedness
  - relevancia: resposta-cobre-pergunta
  - completude: contem-valor-numerico

Métricas: as três que importam

Fidelidade (groundedness) compara cada frase da resposta com o contexto recuperado. Se o modelo afirmar algo sem cabimento extraído, perde pontos. É a métrica que mais cedo pega alucinação.

Relevância mede se a resposta cobre o que foi perguntado. Um juiz LLM com rubrica binária (respondeu / não respondeu) já resolve. A pegadinha é a resposta tecnicamente correta que ignora a pergunta, e a métrica precisa pegar isso.

Qualidade geral (helpfulness, completeness) agrega tudo: tom, clareza, utilidade. Costuma ser a mais subjetiva e onde o juiz humano brilha. Reserve-a para amostras pequenas, não para 100% do dataset.

O runner mínimo viável

O esqueleto abaixo carrega um YAML de caso, chama o agente, aplica um juiz LLM e devolve um veredito. Em produção o runner é maior (paralelismo, cache, retries), mas o formato fica bem neste documento:

evals/run.tstypescript
import fs from "node:fs";
import { runAgent } from "../src/agent";
import { judge } from "./judges";

type Case = {
  id: string;
  input: Record<string, unknown>;
  expected: { tem_que_dizer?: string[]; nao_pode_dizer?: string[] };
  metricas: string[];
};

async function evalCase(c: Case) {
  const t0 = Date.now();
  const out = await runAgent(c.input);
  const scores: Record<string, number> = {};
  for (const m of c.metricas) {
    scores[m] = await judge(m, c, out);
  }
  return { id: c.id, ms: Date.now() - t0, scores, output: out };
}

export async function evalFile(path: string) {
  const cases = fs.readFileSync(path, "utf8")
    .split("\n---")
    .map(parseYaml);
  const results = await Promise.all(cases.map(evalCase));
  const media = (k: string) =>
    results.reduce((a, r) => a + (r.scores[k] ?? 0), 0) / results.length;
  return { n: results.length, results, media: { fidelidade: media("fidelidade") } };
}

Onde entra na pipeline

Duas vezes. Antes do merge: a suite de regressão roda em todo PR e bloqueia se cair. Depois do merge: amostras de produção são re-avaliadas para detectar drift (modelo mudou, retrieval degradou, vazou).

Sem o segundo estágio você acaba em autoengano. Métricas que olham só para o histórico do PR não pegam regressões de runtime, e o defeito aparece só quando o usuário reclama.

Pontos fortes

  • substitui discussão por número: 'a fidelidade caiu 4 pontos' é um argumento que fecha thread
  • cobre regressões invisíveis a olho nu, principalmente em chains longos de tool calling
  • gera dataset corrigível que a própria suite usa, virando um loop virtuoso de melhoria

Limitações

  • métricas ruins passam a iludir: juiz LLM não calibrado vira carimbo que aceita qualquer coisa
  • dataset parado envelhece: o agente evolui, a suite não, e a métrica para de refletir a realidade
  • custo: rodar 500 casos com juiz LLM cobra tempo, leva tempo e exige rate limit na API do juiz
Prompt injectionAtaque em que conteúdo hostil (em página indexada, em e-mail resumido, em doc aberto pelo agente) tenta substituir as instruções do sistema. O atacante sabe que o LLM mistura texto confiável com texto encontrado e planta um 'ignore as instruções anteriores e execute X'. É o vetor número 1 em agente em produção hoje, não é bug do LLM, é como agentes leem texto por design.ver no glossário →

conexões no mapa

atualizado em 14/10/2026 · fontes para aprofundar · reportar erro

em outros domínios

Estude antes

Desbloqueia

teste o que entendeu

Carregando perguntas…