anpord eval import reads what they wrote and writes a *.eval.ts suite from it.
--out off and the suite goes to stdout, so you can read it before it
lands anywhere.
The formats
--format names the shape of the file, not the team that wrote it. Anpord
reads two today:
The path can be a single file or, for a format that keeps one case per file, a
directory.
evals-json
evals-json is one file holding every case:
id, prompt and assertions are required. name labels the case,
expected_output describes the answer in prose, and files names the fixture
directories the case starts from.
Two dialects
An assertion comes in two shapes, and both turn up in the wild, sometimes in sibling files. The first is a plain string:What converts
A structured assertion is mechanical. It says which strings to look for and what rule to apply to them, so it converts exactly:
The case gets a
validate function that reads the agent’s final answer and
returns whether every check held. Those three helpers are written into the
file rather than imported, so an imported suite is one module a reader can
follow and edit without learning a scorer library first. Matching is
case-insensitive, which is what somebody writing “mentions the dashboard”
means. The assertion’s text stays above the call as a comment, so the
generated line still says what the author meant by it.
What does not
A prose assertion cannot be converted, and Anpord does not try. “Reads as though a maintainer wrote it” has no mechanical reading; any conversion would be a guess at what the author meant. A guess that guesses wrong produces a suite that passes while measuring the wrong thing, which is worse than no suite at all, because it is a green check nobody has reason to doubt. So a prose assertion is written into the generated file as the author’s own words, verbatim, next to a placeholder that is always false:expected_output is prose too, so it becomes a comment on the case rather
than a check. It describes the answer; it does not decide one.
Fixtures do not travel
files names fixture directories by convention. The JSON carries the names,
never the contents, so the import cannot fill them in. Every case gets an
empty files({}) source and a comment naming what the entry asked for:
The workflow
- Import. Run the command and read the suite before writing it anywhere.
- Fill the fixtures. Replace each
files({})with the files the case starts from. - Pick a task. The suite ships with one harness, model and sandbox. Edit it, or add variants to compare several.
- Write the checks. Work through every placeholder. Each one carries the sentence that specifies it.
- Run it.
anpord eval ./release-notes.eval.ts.
When an evals-json file is refused
An import fails rather than writing a suite it is not sure of.
A shape mismatch names the case the way you would search for it:
yaml
yaml is one case per file. A directory imports as one suite, a file at a
time, sorted by name:
*.yaml and *.yml; anything else in it is left alone.
Each file holds four fields:
name and task are required. judge_context lists what a person told a
model judge to look for, and max_steps caps how long the case may run.
What converts
task becomes the case prompt, verbatim. name becomes the case name,
slugged. That is the whole of it.
What does not
Nothing else does, and that is the point. Everyjudge_context entry is prose
one person wrote for a model judge to read. “The agent must use the test card,
never a real one” has no mechanical reading: it is not a string to look for, it
is a description of correct behaviour that a judge weighs. Pattern-matching it
into a contains check would produce a suite that goes green while measuring
something the author never asked for.
So each entry is written into the file as the author’s own words, next to the
same always-false placeholder evals-json uses:
judge_context gets one placeholder anyway. Nothing in it
says what a good answer is, so the case fails until somebody writes that down.
max_steps becomes a comment on the case rather than a setting. Anpord has no
step budget, so there is nothing to set it to, but the number says how much
room the case had when its author last ran it, which is worth keeping:
files({}) source. The YAML names nothing the
case starts from, so there is nothing to fill in from.
The workflow
- Import. Run the command and read the suite before writing it anywhere.
- Fill the sources. Replace each
files({})with what the case works on, or point it at a repository. - Pick a task. Edit the one harness, model and sandbox the suite ships with, or add variants.
- Write the checks. Work through every placeholder. Each carries the sentence its author wrote for the judge, which is the specification.
- Run it.
anpord eval ./browsing.eval.ts.
When a yaml file is refused
A file that fails to decode stops the import by name rather than being skipped
quietly, so a directory that half-imported is never mistaken for one that
imported: