Judging¶
The judge turns each answer into a score on your rubric. That score is what CAFE attributes back to your factors, so the judge is load-bearing — this page covers how to configure it, swap it, and check it.
The LLM judge¶
cafe.LLMJudge scores answers with any LiteLLM-supported model using a
research-grounded prompt preset:
judge = cafe.LLMJudge(
model="ollama_cloud/gpt-oss:20b", # any LiteLLM model id
preset="reference_qa", # the prompt style (below)
temperature=0.0, # deterministic by default
)
Pass it to the study, alongside a rubric:
Presets¶
The preset chooses the prompt scaffold:
| Preset | Use it when | Reference? |
|---|---|---|
reference_qa (default) |
you have a gold answer to grade against | required |
single_answer |
open-ended quality, no gold answer | not used |
criterion |
grade against a stated criterion (faithfulness, safety, …) | not used |
reference_qa reads a reference from each dataset item; give your inputs a
"reference" key for it ({"text": ..., "reference": ...}). The others are
reference-free.
Auditing every verdict¶
Each judge call is stored in full — the exact prompt sent and the raw model response
sit behind every score, so you can audit or debug any grade. CAFE also asks for a
structured (JSON) verdict where the model supports it and falls back to a GRADE: <n>
line otherwise (structured="auto", the default; force with True/False).
Rubrics¶
A rubric fixes the scale answers are scored on — and the scale decides which statistical model runs (see Interpreting results). Six are built in:
| Rubric | Scale | Type |
|---|---|---|
CORRECTNESS_0_3 |
0–3 correctness | ordinal |
ANSWER_QUALITY_1_5 |
1–5 quality | ordinal |
FAITHFULNESS_1_5 |
1–5 grounding | ordinal |
RELEVANCE_1_5 |
1–5 relevance | ordinal |
HELPFULNESS_0_10 |
0–10 helpfulness | numeric |
CORRECT_PASS_FAIL |
pass / fail | binary |
Define your own by listing its levels and scale type:
tone = cafe.Rubric(
name="tone",
scale_type=cafe.ScaleType.ordinal,
levels=[
cafe.Level(0, "off", "Wrong register or rude."),
cafe.Level(1, "ok", "Acceptable but flat."),
cafe.Level(2, "great", "Warm, on-brand, appropriate."),
],
instruction="Judge the TONE of the answer, ignoring factual correctness.",
)
Judge self-consistency¶
An LLM judge can score the same answer differently on repeat calls. Set
judge_replications to have it grade each answer several times, then inspect the
spread:
study = cafe.Study(..., judge=judge, judge_replications=3)
result = study.evaluate()
print(result.judge_stability().show()) # per-answer std dev + a summary
Repeated passes are averaged before the factor models (so they do not masquerade as extra data), but the raw spread is kept as a diagnostic. Note that repetitions only tell you something at a non-zero judge temperature — a deterministic judge will not vary.
Generate once, judge many ways¶
Generating answers is the expensive part. rejudge() re-scores the same answers
with a different judge, rubric, or repetition count — nothing is regenerated:
result = study.evaluate()
cheaper = result.rejudge(cafe.LLMJudge(model="ollama_cloud/gpt-oss:20b"))
binary = result.rejudge(judge, rubric=cafe.rubrics.CORRECT_PASS_FAIL)
noisy = result.rejudge(judge, repetitions=3)
This is also the basis for judge ↔ judge agreement:
A custom (non-LLM) judge¶
A judge is anything with a model attribute and an async score() returning a
JudgeOutput. This lets you plug in a deterministic scorer, an EM/F1 metric, or an
external service — no API key required:
class KeywordJudge:
model = "keyword-match"
async def score(self, rubric, question, answer, reference=None):
v = 3 if reference and reference.lower() in str(answer).lower() else 0
return cafe.JudgeOutput(value=v, value_numeric=v, reasoning="",
prompt="", raw_response=str(v))
study = cafe.Study(..., rubric=cafe.rubrics.CORRECTNESS_0_3, judge=KeywordJudge())
The full walkthrough — presets, custom rubrics, rejudging, and stability — is in the Judging modes notebook.