Use cases / Data labeling and evals

Grading model outputs, and comparing models by replay

Grade model outputs. Swap the judge. See where the judges disagree.

Unit
One response to one task, then both responses
Ask
Correct? Quality? Failure mode? Then: which is better?
Get
Per task: both grades, a preference, two judges

Files · 2 reducers · 4 questions · updated Sep 21, 2026

The flow

0 responses source 1,200 tasks, 2 models
0 Grade each response reducer requests, 3 questions each
0 Group by task group no model call
0 Prefer reducer requests, 1 question each
0 Outputs output per task, both grades and the preference

Grade each response alone. Prefer with both in view. Replay with another judge. 3,600 requests per judge. The replay is another 3,600 on the same snapshot.

Follow one record

What comes out

Eval set v1, judged by jev and by kev-4bruns 2f8b9a77 and 6c1e44d0, 1,200 rows
TaskA correctB correctA qualityB qualityPreference (jev)Preference (kev-4b)
TK-00870.940.313.942.31A 0.86A 0.79
TK-00880.620.713.103.22B 0.51tie 0.44
TK-00890.080.911.424.05B 0.93B 0.90
TK-00900.880.873.803.77tie 0.58A 0.49
TK-00910.450.122.601.31A 0.81A 0.85

Synthetic rows in the app's Outputs layout. The two preference columns come from two runs on the same snapshot; the second is a replay of the first with a different model profile.

Details, if you want them

whyWhy this shape

Why this shape

  • Grade alone, prefer together. Independent grades, then A against B in one view.
  • Typed, not parsed. A probability, a rubric position, a failure name, a preference split.
  • Swap the judge. One field in the manifest. Same snapshot. Two columns.
  • Disagreements are the labelling queue. Both judges’ raw responses are in the trace.
questionsThe 4 questions, verbatim
  • Grade each response correctbinary, noul

    Does `response` satisfy every acceptance criterion listed in `task.criteria`?

  • Grade each response qualityordinal, score

    Using `rubric` as the scale, how good is `response` as an answer to `task.prompt`?

    UnusablePoorAcceptableGoodExcellent
  • Grade each response failure_modecategorical, choice

    If `response` falls short of `task.criteria`, how? Choose `none` if it does not.

    wrong answerincompleteunsupported claimformat violationrefusednone
  • Prefer preferencecategorical, choice

    Considering `task.prompt`, `task.criteria` and both responses in `responses`, which response is the better answer?

    ABtie
buildHow each stage is built, as JSON
1 reducer Grade each response
One request per response. correct is a probability against the task’s own acceptance criteria, which are in the state. quality is an ordinal over your five-level rubric. failure_mode names how it went wrong, or none. Grading A and B in separate requests keeps the grades independent; the model never sees the other response here.

stage definition

{
  "id": "grade",
  "input": "source:responses",
  "kind": "evaluate",
  "model": "jev",
  "name": "Grade each response",
  "questions": {
    "correct": {
      "instructions": "Does `response` satisfy every acceptance criterion listed in `task.criteria`?",
      "type": "noul"
    },
    "failure_mode": {
      "criteria": {
        "format_violation": "The required output format is not followed",
        "incomplete": "Part of the task is missing",
        "none": "It meets the criteria",
        "refused": "It declined or deflected",
        "unsupported_claim": "It asserts something the task material does not support",
        "wrong_answer": "The substantive answer is incorrect"
      },
      "instructions": "If `response` falls short of `task.criteria`, how? Choose `none` if it does not.",
      "type": "choice"
    },
    "quality": {
      "criteria": [
        "Unusable",
        "Poor",
        "Acceptable",
        "Good",
        "Excellent"
      ],
      "instructions": "Using `rubric` as the scale, how good is `response` as an answer to `task.prompt`?",
      "type": "score"
    }
  },
  "state_path": []
}
2 group Group by task
Deterministic. The two graded responses for each task become one unit, with their grades attached. The preference reducer sees both, plus the task.

stage definition

{
  "id": "by_task",
  "input": "grade",
  "key": [
    "task_id"
  ],
  "kind": "group",
  "name": "Group by task"
}
3 reducer Prefer
One request per task with both responses in the state. preference is a choice among A, B and tie, returned as a distribution, so a 0.52 to 0.48 split looks different from 0.90 to 0.05. Replaying this stage with a different judge model gives you a second preference column on the same receipts.

stage definition

{
  "id": "prefer",
  "input": "by_task",
  "kind": "evaluate",
  "model": "jev",
  "name": "Prefer",
  "questions": {
    "preference": {
      "criteria": {
        "A": "The response labelled A",
        "B": "The response labelled B",
        "tie": "They are equally good or equally bad"
      },
      "instructions": "Considering `task.prompt`, `task.criteria` and both responses in `responses`, which response is the better answer?",
      "type": "choice"
    }
  },
  "state_path": []
}
4 definition The whole workflow, as saved

Saving publishes an immutable version. A run freezes this definition, the snapshot IDs and each reducer’s model profile, so the trace can always show exactly what ran. Edits create a new version; old runs keep theirs.

jevreduce.local.v1

{
  "name": "Grading model outputs, and comparing models by replay",
  "schema_version": "jevreduce.local.v1",
  "sources": {
    "responses": "snap_eval_responses_v1"
  },
  "stages": [
    {
      "id": "grade",
      "input": "source:responses",
      "kind": "evaluate",
      "model": "jev",
      "name": "Grade each response",
      "questions": {
        "correct": {
          "instructions": "Does `response` satisfy every acceptance criterion listed in `task.criteria`?",
          "type": "noul"
        },
        "failure_mode": {
          "criteria": {
            "format_violation": "The required output format is not followed",
            "incomplete": "Part of the task is missing",
            "none": "It meets the criteria",
            "refused": "It declined or deflected",
            "unsupported_claim": "It asserts something the task material does not support",
            "wrong_answer": "The substantive answer is incorrect"
          },
          "instructions": "If `response` falls short of `task.criteria`, how? Choose `none` if it does not.",
          "type": "choice"
        },
        "quality": {
          "criteria": [
            "Unusable",
            "Poor",
            "Acceptable",
            "Good",
            "Excellent"
          ],
          "instructions": "Using `rubric` as the scale, how good is `response` as an answer to `task.prompt`?",
          "type": "score"
        }
      },
      "state_path": []
    },
    {
      "id": "by_task",
      "input": "grade",
      "key": [
        "task_id"
      ],
      "kind": "group",
      "name": "Group by task"
    },
    {
      "id": "prefer",
      "input": "by_task",
      "kind": "evaluate",
      "model": "jev",
      "name": "Prefer",
      "questions": {
        "preference": {
          "criteria": {
            "A": "The response labelled A",
            "B": "The response labelled B",
            "tie": "They are equally good or equally bad"
          },
          "instructions": "Considering `task.prompt`, `task.criteria` and both responses in `responses`, which response is the better answer?",
          "type": "choice"
        }
      },
      "state_path": []
    }
  ]
}
limitsWhat it will not do
  • A judge has an error rate. Compare with human labels.
  • Adversarial text can sway a judge.
  • Two judges agreeing does not make them right.
  • No critique text. A failure mode is a name, not a paragraph.
  • Deterministic checks belong in code, not in a question.
relatedRelated pages

Try it on your own records.