Skip to main content

Creating Judges

A judge in Evalm8 scores a single aspect of quality. You can define two kinds, LLM judges and deterministic judges.

→ To wire judges into evaluations, see Building Evals.

Judge Types at a Glance​

LLM JudgeDeterministic Judge
Type"llm""code"
How it worksSends a prompt to an LLM and parses a structured verdictRuns a Python snippet in a sandboxed environment
Best forSubjective reasoning: coherence, relevance, hallucinationDeterministic checks: schema validation, regex, exact match
Originbespoke (fixed prompt) or fine_tuned (learns from annotations)bespoke

Creating a Judge in the UI​

Go to Evaluation → Judges and click Create Judge.

Judge builder

The judge name becomes its slug ID (e.g. Correctness JUDGE → correctness-judge), which evals and pipelines reference.

Common Fields​

Both judge types share:

FieldRequiredDescription
nameYesUnique name. Becomes the judge's slug ID.
typeYes"llm" or "code"
inputsYesList of input fields the judge expects (at least one).
modeNoScoring mode and scale. Defaults to pointwise with integer 1–5.

Declaring Inputs​

Every judge declares the variables it expects to receive:

"inputs": [
{
"name": "response",
"target_type": "string",
"description": "The LLM's raw output text"
},
{
"name": "reference",
"target_type": "string",
"description": "The expected correct answer"
}
]
PropertyRequiredDescription
nameYesVariable name your code or prompt template references.
target_typeNostring, integer, float, boolean, dict, or list. Defaults to string. Evalm8 coerces each value to this type.
descriptionNoHuman-readable description. Shown in the UI and used as context for LLM judges.
annotation_templateNoHTML template for rendering this field in the annotation/review interface.

A judge never extracts data from the trace itself. A pipeline (or the default heuristic pipeline) extracts values from the trace and maps them to these declared input names.

LLM Judges​

An LLM judge uses a prompt template and a language model to reason about quality.

{
"type": "llm",
"name": "Correctness JUDGE",
"inputs": [
{ "name": "query", "target_type": "string", "description": "The prompt sent to the model." },
{ "name": "response", "target_type": "string", "description": "The model response to evaluate." }
],
"template": "You are an expert evaluator assessing the correctness of a model response. Score 1 to 5, then explain briefly.\n\nQuery: {{query}}\nResponse: {{response}}",
"model": "google/gemini-2.5-flash",
"mode": {
"type": "pointwise",
"scale": { "type": "integer", "min": 1, "max": 5 }
}
}
FieldRequiredDescription
templateYesThe prompt sent to the LLM. Reference inputs with {{variable_name}}.
modelNoModel provider URI (e.g. google/gemini-2.5-flash). Uses platform default if omitted.
mode.scaleNoThe scoring scale the LLM should use. Defaults to integer 1–5.
Model providers

Before using a model for LLM judges, register it under Integrations → Model Providers.

Model providers

Bespoke vs Fine-Tuned​

  • bespoke: a fixed prompt template. Scores are consistent but do not adapt to your domain without manual prompt editing.
  • fine_tuned: starts from the same template and learns from your golden dataset annotations through the refinement workflow. With each refinement cycle, its scores move closer to human judgment.

Deterministic Judges​

A deterministic judge runs a Python snippet in a sandboxed environment. Use it for checks that don't need LLM reasoning.

{
"type": "code",
"name": "json_schema_validator",
"inputs": [
{ "name": "response", "target_type": "string" },
{ "name": "schema", "target_type": "dict" }
],
"code": "try:\n data = json.loads(response)\n jsonschema.validate(data, schema)\n score = 1.0\n reason = \"Valid JSON conforming to schema\"\nexcept json.JSONDecodeError:\n score = 0.0\n reason = \"Output is not valid JSON\"\nexcept jsonschema.ValidationError as e:\n score = 0.0\n reason = f\"Schema violation: {e.message}\""
}

Deterministic judge inputs​

Your snippet runs with each declared input already available as a local variable. If you declared an input named response, write response in your code. You don't need to unpack anything.

Your snippet must set:

  • score (numeric): the judge's verdict. It defaults to 0.0 if not set.
  • reason (string, optional): an explanation. If you omit it, Evalm8 uses a generic fallback.

For ranking judges, set rank (a list of integers) instead of score.

Available in the Sandbox​

Pre-imported modules (no import needed):

ModuleTypical use
mathmath.log, math.sqrt, math.ceil
reRegex matching and substitution
jsonParse/dump JSON strings
jsonschemaValidate dicts against JSON Schema
datetimeDate/time construction and arithmetic
collectionsCounter, defaultdict, OrderedDict
itertoolschain, product, combinations
functoolsreduce, partial
hashlibmd5, sha256
statisticsmean, median, stdev
copycopy.copy, copy.deepcopy

Built-in functions: int, float, str, bool, list, dict, set, tuple, len, sum, max, min, round, sorted, range, enumerate, zip, map, filter, any, all, isinstance, abs, print (for debugging), and standard exception types.

Not available: File I/O, networking, system access, or any module not listed above.

Error Handling​

If your snippet raises an unhandled exception, Evalm8 reports the error with the offending line number. Use try/except to catch errors and set a score yourself:

try:
parsed = json.loads(response)
score = 1.0 if "name" in parsed else 0.0
reason = "Has 'name' key" if score else "Missing 'name' key"
except json.JSONDecodeError:
score = 0.0
reason = "Not valid JSON"

Deterministic Judge Examples​

Regex Pattern Gate​

pattern = r"\b\d{4}-\d{2}-\d{2}\b"
matches = re.findall(pattern, response)
score = 1.0 if matches else 0.0
reason = f"Found {len(matches)} date(s)" if matches else "No ISO-8601 date found"

Word-Overlap Similarity​

ref_tokens = set(reference.lower().split())
res_tokens = set(response.lower().split())
overlap = len(ref_tokens & res_tokens) / len(ref_tokens) if ref_tokens else 1.0
score = round(overlap, 3)
reason = f"Token overlap: {score}"

Exact Match with Normalisation​

normalise = lambda s: re.sub(r'\s+', ' ', s.strip().lower())
score = 1.0 if normalise(response) == normalise(expected) else 0.0
reason = "Exact match" if score else "Does not match expected output"

Length Validation​

length = len(response.strip())
if length < 10:
score = 0.0
reason = "Response too short"
elif length > 2000:
score = 0.5
reason = "Excessively long"
else:
score = 1.0
reason = f"Acceptable length ({length} chars)"

JSON Schema Validation (with config)​

This judge receives schema via judge configuration on the eval binding, so you can reuse it across dimensions:

try:
data = json.loads(response)
jsonschema.validate(data, schema)
score = 1.0
reason = "Response conforms to the required schema"
except json.JSONDecodeError:
score = 0.0
reason = "Response is not valid JSON"
except jsonschema.ValidationError as e:
score = 0.0
reason = f"Schema violation: {e.message}"

Next steps​

GuideCovers
Core ConceptsTraces, Rubrics, Dimensions, and the evaluation flow
Authoring PipelinesHow to extract trace data and route it to judges
Building EvalsWiring judges to rubric dimensions, configuration, the default pipeline
WorkflowsDatasets, annotation, and judge refinement