Avaliação (evals)
A lib promete trocar provider/modelo sem mudar o código. As evals são a
outra metade: provar que a troca não piorou a qualidade nem estourou o custo.
Você roda um target sobre um conjunto de casos (Dataset), dá notas com
Evaluators (heurística e/ou LLM juiz) e compara execuções (Experiment) por
acerto × R$ × latência.
Funciona 100% offline (igual à observabilidade): evaluate() devolve os
scores localmente; enviar ao painel é opcional (push=True).
from jangada_ai import LLM
from jangada_ai.eval import Evaluator, Dataset, evaluateAs peças
| Peça | Responde |
|---|---|
Dataset/Example | "Em cima de quais casos eu meço?" (entradas + gabarito) |
Evaluator | "Essa saída está boa?" (heurística ou juiz LLM) |
evaluate() | roda o target sobre o dataset, aplica os evaluators e agrega |
ExperimentResult | o resultado: notas por evaluator, custo, p50, erros |
1. Dataset — os casos
Um Example tem inputs (o que vai para o target), reference (gabarito,
opcional) e metadata.
ds = Dataset.from_records([
{"inputs": {"q": "Capital da França?"}, "reference": "Paris"},
{"inputs": {"q": "2 + 2?"}, "reference": "4"},
], name="perguntas")
# ou de um JSONL: {"inputs": {...}, "reference": ...} por linha
ds = Dataset.from_jsonl("casos.jsonl", name="casos")2. Evaluators — as notas
Heurística (Evaluator.fn)
Função pura (output, reference) → float, bool ou EvalResult. Sem rede.
exato = Evaluator.fn(
"exato",
lambda out, ref: out.text.strip().lower() == ref.lower(),
)out é o que o target devolveu (tipicamente um Completion, com .text,
.parsed, .cost); ref é o reference do exemplo.
Juiz LLM (Evaluator.judge)
Um LLM avalia a saída — por baixo é um parse() com schema fixo {score, reason}.
Use para critérios subjetivos (utilidade, tom, equivalência semântica).
util = Evaluator.judge(
"util",
"A resposta responde à pergunta de forma correta e útil? score de 0 a 1.",
judge=LLM("openai", "gpt-4o-mini"), # juiz barato, separado do alvo
)3. evaluate() — rodar e agregar
def alvo(ex):
return LLM("openai", "gpt-4o-mini").complete(ex.inputs["q"])
res = evaluate(ds, target=alvo, evaluators=[exato, util], name="baseline")
print(res.summary())
# {'exato': 0.9, 'util': 0.95, 'costUsd': 0.0021, 'p50_ms': 740, 'count': 2, 'errors': 0}O summary() traz: média por evaluator, costUsd (custo do target), evalCostUsd
(custo dos juízes), p50_ms, count e errors. Cada item fica em res.runs. Um
target que falha vira um run com error — não derruba o experimento.
Async: aevaluate(..., max_concurrency=8) (o target pode ser coroutine).
4. Comparar modelos (o que importa)
Mesmo dataset, modelos diferentes — decida com evidência:
def alvo(modelo):
return lambda ex: LLM("openai", modelo).complete(ex.inputs["q"])
gpt = evaluate(ds, target=alvo("gpt-5"), evaluators=[exato, util], name="gpt5")
flash = evaluate(ds, target=alvo("gemini-3-flash"), evaluators=[exato, util], name="flash")
# gpt5: {'exato': 0.94, 'util': 0.97, 'costUsd': 0.21, ...}
# flash: {'exato': 0.90, 'util': 0.95, 'costUsd': 0.02, ...} → ~10× mais barato5. Enviar ao painel (push=True)
Com a observabilidade configurada (mesma chave de projeto), push=True envia o
experiment ao backend — aparece no dashboard nas abas Experiments (tabela
comparativa) e Datasets (gráfico de score ao longo do tempo).
res = evaluate(
ds, target=alvo("gpt-5"), evaluators=[exato, util], name="gpt5",
push=True, target_info={"provider": "openai", "model": "gpt-5"},
)
print(res.pushed, res.push_error) # True NoneO push é best-effort (falha de rede/sem chave não levanta — marca
res.pushed/res.push_error). Cada run guarda o traceId quando disponível,
ligando o score de volta ao trace que o gerou.
Como funciona
Evaluator.fnroda local;Evaluator.judgefaz umparse()no juiz com schema{score, reason}.evaluate()mede latência, lêCompletion.costdo target, soma o custo dos juízes emevalCostUsde calcula média/p50/erros.pushreusa a config da observabilidade (POST /v1/experiments).