Skip to main content
A code scorer is a function your eval runs on each case’s output to turn it into a number from 0 to 1, where higher is better. Use one for anything you can check deterministically: equality, structure, tool calls, keywords, or budgets. Reach for an LLM judge only for what code cannot decide; to judge production sessions instead of eval cases, see Write hosted judges.

Before you begin

Before you start, make sure you have:
1

Write scorer function

Declare only the arguments you use.
  • Arguments: Python matches parameters by keyword from input, output, expected, and metadata, so accept **_ for the rest. TypeScript passes one object with input, output, expected, metadata, tags, and id. metadata is the inline case’s metadata, or the dataset record’s extra.
  • Optional expected in TypeScript: expected is optional on every case. Type it expected?: and return null when it is missing; a required expected fails tsc --strict.
  • Passing scorers to Eval: Python accepts bare functions, named after the function, or {"name": ..., "scorer": fn} mappings. Always wrap a lambda in a mapping with a name. TypeScript accepts { name, scorer } objects only.
2

Return score

Return one of these shapes:A non-finite number raises. Two scorers that emit the same score name fail the run: every score name belongs to exactly one scorer. If a scorer raises, its message is appended to that case’s error and the other scorers still run. If the task raises, the case records the error and its scorers are skipped.Scores gate well when they follow these rules:
  • One behaviour per score. A score that mixes three rules cannot tell you which one regressed.
  • Partial credit where it carries information. “3 of 4 facts” is more useful than pass or fail.
  • Abstain rather than pass. Return None when a rule does not apply. A free 1.0 hides regressions in the mean.
  • Stable names. The CI gate and trend views key on the score name. Renaming a score starts a new series.
3

Copy scorer from catalog

Each entry says what it measures and the case shape it needs. Import ScoreValue from atlanai. To score which tools an agent called, in what order, and with which arguments, see Score agent behaviour.
Measures whether the output equals the expected value, after normalising case and whitespace. Needs expected as a string.
Measures the share of required phrases that appear in the output. Partial credit shows how much was missed. Needs expected as {"must_include": [...]}.
Measures whether the output parses as JSON and carries every required key. Needs expected as {"keys": [...]}.
Measures whether a numeric answer is within a relative tolerance set on the case. Needs expected as a number, and extra.tolerance on the record or metadata.tolerance inline.
Measures a format rule or a forbidden pattern, such as a leaked internal ID, or a refusal where an answer was required. Needs only the output.
Measures a rule that only some cases exercise. Return None or null elsewhere, so the mean covers only the cases the rule applies to. Needs a category on the case.
Measures a quality that code cannot decide, such as tone or faithfulness, with your own model client. Needs a model you are approved to send case data to.Hosted llm_judge scorers run over recorded sessions, not inside Eval. When a CI run needs a judge, call your model from a code scorer, parse a constrained answer, and keep the rationale:
Keep judge scorers few and deterministic: set temperature=0, constrain the answer to fixed labels, and delimit the case content as data. Changing the rubric records a new scorer version, so compare runs only when the same version scored them.
4

Name and version scorer

Eval registers each scorer in the workspace as <eval-name>-<scorer-name>, lowercased with _ turned into -: scorer exact_match in eval support-agent becomes support-agent-exact-match. Score names on results keep the function’s own name, exact_match. Eval stores a digest of the function’s source; when the source changes, the next run records a new scorer version, and each score cites the version that produced it.
  • Keep the Eval name stable. Renaming the eval creates new scorers, so a baseline and a candidate with different eval names are not scored by the same scorer. The CI action also matches baselines on this name.
  • Share one scorer across evals by passing registry_name (registryName in TypeScript) in the scorer mapping.
  • Pin an existing scorer with scorer_id and scorer_version (scorerId, scorerVersion). Eval then writes nothing to the scorer.
  • Version behaviour changes yourself. The digest covers the function’s source text only. A changed constant it closes over, or a helper it calls, does not create a version. Put thresholds inside the function, or bump registry_name when behaviour changes.
5

Verify scores in run

Run the eval and check its result:
  • Check summary: run.summary["scores"] (run.summary.scores) has one entry per score name, each with a count equal to the cases the score applies to. run.summary["scorers"] names the scorer ID and version behind each score.
  • Check a case: Each case in run.results has the score under its name, and its trace has a score.<name> span.
If a score is missing from some cases, the scorer returned None or raised on them; a raise appears in that case’s error. See Eval errors.

Troubleshooting

Scorer failures include two scorers that emit the same name, a scorer name that collides with another scorer in the workspace, and an empty summary because every task raised. See Eval errors.

Next steps

See also