--- name: output-eval-judge-prompt description: Design effective LLM judge .prompt files for evaluators. Use when creating judgeVerdict/judgeScore/judgeLabel prompts, or when existing judges produce unreliable results. allowed-tools: [Read, Write, Edit] --- # Designing LLM Judge Prompts ## Overview An LLM judge evaluates workflow output for a **single, specific failure mode** identified during error analysis. This skill covers how to design the `.prompt` file that powers `judgeVerdict()`, `judgeScore()`, or `judgeLabel()` calls. For the file format basics, see `output-dev-prompt-file`. For error analysis, see `output-eval-error-analysis`. ## Prerequisites Before writing a judge prompt: 1. **Error analysis is complete** — You have identified the specific failure mode this judge targets (from `output-eval-error-analysis`) 2. **20+ labeled examples** — At least 20 pass and 20 fail traces for this failure mode, with `ground_truth` labels in dataset YAML files 3. **Code-based check ruled out** — Confirmed that `Verdict.*` helpers (contains, matches, gte, etc.) cannot reliably detect this failure ## The Four Components Every effective judge prompt has exactly four components. ### 1. Task and Criterion State the single failure mode being evaluated. Be specific and observable. **Good criteria (specific, observable):** - "Does the blog post maintain a formal tone throughout, or does it slip into casual language?" - "Does the output contain any URLs that are fabricated rather than drawn from the input?" - "Does the summary faithfully represent the source material without adding claims not present in the original?" **Bad criteria (vague, holistic):** - "Is this output high quality?" - "Rate the overall effectiveness of this response" - "How good is this content?" ### 2. Pass/Fail Definitions Define exactly what constitutes pass and fail. **Always binary** — no Likert scales, no 1-5 ratings, no "partially meets criteria." ``` PASS: The blog post uses formal language throughout. Professional vocabulary, complete sentences, no slang, no contractions, no first-person casual asides. FAIL: The blog post contains one or more instances of casual language: slang, contractions ("don't", "can't"), informal asides ("pretty cool", "super important"), or conversational filler ("honestly", "basically"). ``` Why binary: Likert scales create ambiguous boundaries (what's the difference between a 3 and a 4?). Binary forces precise definitions that LLMs can apply consistently and that you can validate against human labels. ### 3. Few-Shot Examples Include at least three labeled examples: one clear pass, one clear fail, and one borderline case. **Borderline examples are the most valuable** — they teach the judge where the decision boundary lies. Draw examples from your **training split only** (see `output-eval-validate-judge`). Never use dev or test examples as few-shot — that's data leakage. Each example must include: - The relevant input/output excerpt - A detailed critique explaining the reasoning - The verdict (pass or fail) ### 4. Structured Output Request JSON output with **critique before verdict**. This forces the judge to reason before deciding, which improves accuracy. ```json { "critique": "Detailed analysis of the output against the criterion...", "verdict": "pass" } ``` Always put `critique` first in the schema. If `verdict` comes first, the judge commits to a decision before reasoning. ## Full `.prompt` File Example A judge for the "tone mismatch" failure mode: ``` # tests/evals/judge_tone@v1.prompt --- provider: anthropic # current as of 2026-05-04 — run output-dev-model-selection for the latest model: claude-haiku-4-5-20251001 temperature: 0 maxOutputTokens: 1500 --- You are an evaluation judge. Your task is to determine whether a blog post maintains the requested tone throughout. ## Criterion Assess whether the blog post consistently uses the requested tone. A single paragraph that breaks tone is a failure. ## Definitions PASS: The blog post maintains the requested tone in every paragraph. Word choice, sentence structure, and rhetorical style all align with the requested tone. FAIL: The blog post contains one or more paragraphs where the tone shifts away from what was requested. Common failures include: - Formal request but casual language appears ("pretty cool", "super important", contractions) - Professional request but opinionated editorializing appears - Technical request but oversimplified explanations appear ## Examples ### Example 1: PASS Requested tone: formal Blog excerpt: "The implications of quantum computing for cryptographic security are substantial. Current encryption standards rely on the computational infeasibility of factoring large prime numbers, a guarantee that quantum algorithms may undermine." Critique: The excerpt uses professional vocabulary ("implications", "computational infeasibility"), complete sentences, no contractions, and maintains an academic register. Consistent formal tone throughout. Verdict: pass ### Example 2: FAIL Requested tone: formal Blog excerpt: "Quantum computing is basically going to break all our encryption. It's pretty wild when you think about it — everything we thought was secure might not be." Critique: The excerpt contains multiple casual markers: "basically", "pretty wild", contractions ("It's", "might not be"), and conversational filler ("when you think about it"). This directly violates the formal tone request. Verdict: fail ### Example 3: BORDERLINE (fail) Requested tone: formal Blog excerpt: "Quantum computing represents a paradigm shift in computational capability. The technology is incredibly promising, though it's important to note the current limitations in qubit stability and error correction." Critique: Mostly formal, but contains "incredibly promising" (informal intensifier) and "it's" (contraction). While the overall register is professional, these lapses break the formal tone requirement. Even minor inconsistencies constitute a failure. Verdict: fail ## Output Format Return a JSON object with exactly two fields: - "critique": A detailed analysis (3-5 sentences) citing specific evidence from the blog post - "verdict": Either "pass" or "fail" Requested tone: {{ requested_tone }} Blog title: {{ blog_title }} Blog post: {{ blog_post }} Evaluate whether this blog post consistently maintains the requested tone. ``` ## Wiring to `judgeVerdict()` After creating the `.prompt` file, wire it to an evaluator using `verify()` and `judgeVerdict()`: ```typescript // tests/evals/evaluators.ts import { verify, judgeVerdict } from '@outputai/evals'; import { z } from '@outputai/core'; import { blogInput, blogOutput } from './schemas.js'; export const checkTone = verify( { name: 'check_tone', input: blogInput, output: blogOutput }, async ({ input, output, context }) => judgeVerdict({ prompt: 'judge_tone@v1', variables: { requested_tone: String(context.ground_truth.expected_tone ?? input.tone ?? 'professional'), blog_title: output.title, blog_post: output.blog_post } }) ); ``` Then add it to the eval workflow: ```typescript // tests/evals/workflow.ts import { evalWorkflow } from '@outputai/evals'; import { checkTone } from './evaluators.js'; export default evalWorkflow({ name: 'blog_generator_eval', evals: [ { evaluator: checkTone, criticality: 'required', interpret: { type: 'verdict' } } ] }); ``` ## Choosing What Context to Pass Feed the judge only what it needs to evaluate the criterion. Extra context adds noise and cost. | Failure Mode | Required Variables | Not Needed | |-------------|-------------------|------------| | Tone mismatch | requested_tone, blog_post | topic, input constraints | | Off-topic drift | topic, blog_post | tone, length requirements | | Hallucinated claims | blog_post, source_material | topic, tone | | Faithfulness | summary, original_document | formatting requirements | | Missing requirements | requirements_list, blog_post | topic (unless relevant) | Use `context.ground_truth` for expected values that vary per dataset. Use `input.*` for values from the workflow input. Use `output.*` for the workflow output being evaluated. ## `judgeScore()` Variant Use `judgeScore()` when you need a numeric quality score rather than binary pass/fail. Apply the same four-component design. ### `.prompt` file for scoring ``` # tests/evals/judge_quality@v1.prompt --- provider: anthropic # current as of 2026-05-04 — run output-dev-model-selection for the latest model: claude-haiku-4-5-20251001 temperature: 0 maxOutputTokens: 1500 --- You are an evaluation judge. Score the overall writing quality of a blog post on a scale of 0.0 to 1.0. ## Scoring Criteria - 0.0-0.3: Major issues — incoherent, riddled with errors, or completely off-topic - 0.4-0.6: Mediocre — readable but has significant quality issues (poor structure, weak arguments, factual gaps) - 0.7-0.8: Good — well-structured, clear, minor issues only - 0.9-1.0: Excellent — polished, engaging, publication-ready ## Output Format Return a JSON object with: - "critique": Detailed analysis of quality strengths and weaknesses (3-5 sentences) - "score": A number between 0.0 and 1.0 Topic: {{ topic }} Blog title: {{ blog_title }} Blog post: {{ blog_post }} Score the writing quality of this blog post. ``` ### Wiring to `judgeScore()` ```typescript export const checkQuality = verify( { name: 'check_quality', input: blogInput, output: blogOutput }, async ({ input, output }) => judgeScore({ prompt: 'judge_quality@v1', variables: { topic: input.topic, blog_title: output.title, blog_post: output.blog_post } }) ); ``` In the eval workflow, use `interpret: { type: 'number' }` with thresholds: ```typescript { evaluator: checkQuality, criticality: 'required', interpret: { type: 'number', pass: 0.7, partial: 0.4 } } ``` ## `judgeLabel()` Variant Use `judgeLabel()` when you need classification into named categories. ```typescript export const checkToneLabel = verify( { name: 'check_tone_label', input: blogInput, output: blogOutput }, async ({ output }) => judgeLabel({ prompt: 'judge_tone_label@v1', variables: { blog_title: output.title, blog_post: output.blog_post } }) ); ``` In the eval workflow, use `interpret: { type: 'string' }` with label lists: ```typescript { evaluator: checkToneLabel, criticality: 'informational', interpret: { type: 'string', pass: ['professional', 'formal'], partial: ['casual'] } } ``` ## Model Selection > Run [`output-dev-model-selection`](../output-dev-model-selection/SKILL.md) to resolve each tier below to a current model ID. | Tier | When to Use | Cost | |------|-------------|------| | Smallest in family (`speed`/`cost` priority) | Default for most judges. Fast, cheap, good at following structured instructions. | Low | | Mid-tier (`balance` priority) | Complex reasoning required (faithfulness checking, multi-step logical analysis). | Medium | | Top-tier (`reasoning` priority) | Only if mid-tier fails validation. Rarely needed. | High | Always set `temperature: 0` for judges. Reproducibility matters more than creativity. **Escalation strategy:** start with the smallest tier. If the judge fails validation (TPR/TNR below 80%), move up one tier before rewriting the prompt — the model upgrade alone often fixes it. ## Anti-Patterns - **Vague criteria** ("Is this good?") — Target one specific, observable failure mode - **Holistic judges** ("Rate overall quality on 5 dimensions") — One judge per failure mode - **No few-shot examples** — Always include pass, fail, and borderline examples - **Likert scales** (1-5 ratings) — Use binary pass/fail for verdict judges; use 0.0-1.0 with defined bands for score judges - **Verdict before critique** — Put critique first in the JSON schema to force reasoning - **Skipping validation** — Always validate judges against human labels (`output-eval-validate-judge`) - **Kitchen-sink context** — Pass only the variables the judge needs for its criterion - **Few-shot from dev/test set** — Only use training-split examples to avoid data leakage ## Related Skills - `output-eval-error-analysis` — Identify the failure mode this judge targets - `output-dev-eval-testing` — Implementation reference for `verify()`, `judgeVerdict()`, `evalWorkflow()` - `output-dev-prompt-file` — `.prompt` file format, Liquid.js templating, provider configuration - `output-eval-validate-judge` — Validate this judge against human labels after writing it - `output-eval-dataset-design` — Generate diverse datasets for judge validation