Skip to main content

What Youโ€™ll Build

A progression of custom judges: a lambda check, a reusable deterministic judge, a composed AI judge, an LLM judge with full control, and a RAG-specific judge. Youโ€™ll wire them into a jury alongside built-in judges. This tutorial builds on Build an Evaluation Pipeline. You should be comfortable with JudgmentContext, Judgment, and SimpleJury before starting.

Step 1: Lambda Judge

The simplest custom judge is a lambda โ€” three lines:
Judge is a @FunctionalInterface with a single method: judge(JudgmentContext) -> Judgment. Any lambda or method reference that matches this signature is a judge. Lambda judges work well for one-off checks. For production judges, extend a base class to get metadata and structured checks.

Step 2: Extend DeterministicJudge

For a judge youโ€™ll reuse, extend DeterministicJudge. It implements JudgeWithMetadata so infrastructure code (logging, metrics, verdict reporting) can discover the judgeโ€™s name and type.
Key patterns:
  • Constructor calls super(name, description) โ€” this becomes metadata() for logging and verdicts
  • Checks provide granular sub-assertions โ€” on failure, you can distinguish โ€œfile not foundโ€ from โ€œfile found but annotation missingโ€
  • Judgment.error() handles unexpected failures without crashing the jury
Usage:

Step 3: Add Metadata to Lambda Judges

If you prefer lambdas but still want metadata, use Judges.named():
When to use what:

Step 4: Build an AI Judge with ModelBackedJudge

For semantic criteria that canโ€™t be checked from files or commands, you need an AI-backed judge. ModelBackedJudge composes three parts โ€” a prompt template, a model backend, and a response classifier โ€” into a judge without subclassing.
The three components are independently swappable: Available template variables: {{goal}}, {{output}}, {{workspace}}, {{status}}, {{metadata.*}}.
ModelBackedJudge lives in the agent-judge-ai-core module, which has zero external dependencies. The actual AI backend arrives through a JudgeModel implementation from a bridge module (agent-judge-llm or agent-judge-agent-client).
You can also load templates from the classpath for reuse across judges:
When to use ModelBackedJudge vs. LLMJudge: Use ModelBackedJudge as the default. Reach for LLMJudge when you need full control over prompt construction or response parsing.

Step 5: Write a Custom LLM Judge

For full control over prompt construction and response parsing, extend LLMJudge. It uses the template method pattern โ€” you implement two methods:
  1. buildPrompt() โ€” construct the evaluation prompt from the context
  2. parseResponse() โ€” parse the LLMโ€™s response into a Judgment
The base class handles the LLM call.
Usage:
LLM judges require the agent-judge-llm module and Spring AI. Always handle parse failures gracefully โ€” Judgment.error() prevents one broken response from crashing the jury.
Prefer deterministic judges when the criterion can be checked from files, commands, or structured metadata. Use LLM judges when the criterion is semantic and cannot be expressed reliably as code.

Step 6: Wire Custom Judges into a Jury

Combine your custom judges with built-in ones:
The WeightedAverageStrategy normalizes all scores to [0, 1] and computes a weighted average. Build success (weight 2.0) matters more than file existence (weight 1.0), and quality (weight 1.5) falls in between.

Step 7: Write a Custom RAG Judge

RAG judges follow the same LLMJudge pattern but use the RagContext helper to extract the (question, context, answer) triple from metadata. Hereโ€™s a custom judge that evaluates answer completeness โ€” did the answer address all parts of the question?
The key patterns for RAG judges:
  • Use RagContext.question(), RagContext.context(), RagContext.answer() to extract the triple
  • Return ABSTAIN when required inputs are missing โ€” this prevents misleading verdicts
  • Use the (?mi)^\s*Answer:\s*(YES|NO) regex pattern for reliable LLM response parsing
Supply RAG metadata when building the context:
RagContext.question() falls back to context.goal() and RagContext.answer() falls back to context.agentOutput(), but the explicit metadata keys are the recommended convention. LangChain4jโ€™s sources are also available as a fallback for RagContext.context().

What You Built

The progression: lambda (quick checks) -> DeterministicJudge (production checks with metadata and sub-assertions) -> ModelBackedJudge (composed AI judge, no subclassing) -> LLMJudge (full control over prompt and parsing) -> RAG judge (LLMJudge with the RagContext metadata convention).

Whatโ€™s Next

Built-in Judges

Catalog of built-in judges โ€” see whatโ€™s already available before writing custom judges

Jury System

CascadedJury for tiered evaluation, voting strategy configuration