UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

95 lines (69 loc) 5.39 kB
--- title: "Judge" description: "Grade evals with an LLM judge via t.judge.autoevals, set thresholds on the assertion, and configure the judge model." --- When no deterministic [assertion](./assertions) captures what "good" means (factual correctness, summary quality, free-form criteria), grade the run with an LLM judge. The `t.judge.*` assertions are the only model-backed ones, and they use a judge model that is resolved separately from the agent under test. eve only uses it for scoring, never to swap out the agent. ```ts import { defineEval } from "eve/evals"; export default defineEval({ async test(t) { await t.send("Explain quantum tunneling to a 10-year-old."); t.completed(); t.judge.autoevals.closedQA("uses no math beyond arithmetic").atLeast(0.8); }, }); ``` ## The graders The judges live under `t.judge.autoevals`. The namespace names the [Braintrust autoevals](https://github.com/braintrustdata/autoevals) grader family, so the factuality and closedQA semantics are autoevals', not eve-invented. Each grader scores `t.reply` by default and is soft by default (tracked, no gate): | Grader | Grades | | ---------------------------------------- | -------------------------------------------------------------------------------------- | | `t.judge.autoevals.factuality(expected)` | Factual consistency of the reply against an expected answer (AE buckets) | | `t.judge.autoevals.summarizes(expected)` | How well the reply summarizes the expected text | | `t.judge.autoevals.closedQA(criteria)` | Whether the reply satisfies a free-form yes/no criterion (no expected answer to match) | | `t.judge.autoevals.sql(expected)` | Semantic equivalence of two SQL statements | The reference or criteria is the positional argument. An options object follows: - `on` is the value to grade, defaulting to `t.reply`. Pass an intermediate draft or parsed value to grade it instead. - `model` and `modelOptions` are a per-call judge override (see below). ```ts const draft = await t.send("Draft the welcome email."); t.judge.autoevals.closedQA("professional tone", { on: draft.message }).atLeast(0.6); ``` ## Soft scoring and thresholds Judge assertions are soft, so the threshold rides on the assertion handle. There is no separate thresholds map: - **No threshold** is tracked-only. The score lands in reports and artifacts and never fails the eval. Use it to watch a metric without gating on it. - `.atLeast(threshold)` is a soft bar. A below-threshold score marks the eval `scored`, fatal only under `eve eval --strict`. - `.gate(threshold)` promotes a judge to a hard gate that fails the eval outright. ```ts t.judge.autoevals.closedQA("cites a source"); // tracked, never fails t.judge.autoevals.closedQA("cites a source").atLeast(0.6); // soft, fails under --strict below 0.6 t.judge.autoevals.factuality(reference).gate(0.8); // hard gate at 0.8 ``` A judge runs once per assertion and burns tokens, so reach for one only when nothing deterministic will do. Several slow judge calls in one eval can fan out with `await Promise.all([...])`. ## Configuring the judge model The judge model is resolved once when the runner builds `t`. It is **never** the model under test. Three levels resolve innermost-wins: 1. **Per-call**: `t.judge.autoevals.closedQA("…", { model, modelOptions })`. 2. **Per-eval**: `defineEval({ judge: { model, modelOptions }, test })`. 3. **Project default**: `defineEvalConfig({ judge: { model, modelOptions } })` in `evals.config.ts`. ```ts title="evals/evals.config.ts" import { defineEvalConfig } from "eve/evals"; export default defineEvalConfig({ judge: { model: "openai/gpt-5.4-mini" }, // the default judge for every eval in this tree }); ``` ```ts title="evals/quantum.eval.ts" import { defineEval } from "eve/evals"; export default defineEval({ judge: { model: "anthropic/claude-opus-4.8" }, // a stronger judge for this eval async test(t) { await t.send("Explain quantum tunneling to a 10-year-old."); t.judge.autoevals.factuality(reference).atLeast(0.7); t.judge.autoevals.closedQA("is concise", { model: "anthropic/claude-haiku-4.5" }); // cheaper, per-call }, }); ``` `judge` in `evals.config.ts` is optional, and a tree of fully deterministic evals can omit it. Calling `t.judge.*` with no judge model resolved records a failed gate: the runner scores the assertion after the `test` function runs, the missing model throws, and the eval fails with that message. A **string model id** (e.g. `"anthropic/claude-opus-4.8"`) routes through the Vercel AI Gateway and needs `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` in the environment. An **AI SDK `LanguageModel` instance** is used directly. With a model configured but no credentials, a judge-backed eval **skips visibly** rather than failing, so the run reports the skip instead of a spurious error. For provider-specific judge settings, use `modelOptions.providerOptions`. ## What to read next - [Assertions](./assertions): deterministic run-level and value assertions - [Reporters](./reporters): ship judged scores to Braintrust experiments - [Targets](./targets): local vs remote targets for judge-backed evals