Skip to main content

A CI trust gate for LLM judges: agreement statistics against human labels, bias probes, and a label budget calculator

Project description

judgegate

A CI trust gate for LLM judges.

CI PyPI Python 3.11+ License: MIT

You gate releases on an LLM judge's scores. Has anyone checked the judge?

Most teams adopt LLM-as-judge, write a grading prompt, eyeball a few verdicts, and start trusting it in CI. From then on every ship or block decision inherits whatever biases and noise the judge carries. judgegate measures the judge itself against your human labels and refuses to let an unreliable judge gate your pipeline.

judgegate verify trusting a judge

Verdict Exit code Meaning
TRUSTED 0 The judge agrees with your humans well enough to gate, and no probe failed.
UNTRUSTED 1 The judge disagrees too much, or a bias probe failed outright.
INCONCLUSIVE 2 Not enough human labels to certify or reject, reported with the label count that would decide it.

Operational failures (bad files, bad flags, endpoint errors) exit with code 3, so a pipeline can always distinguish "the gate decided" from "the gate broke".

judgegate is pure Python, works with any OpenAI-compatible endpoint, caches every judge call in SQLite so reruns are free, sends nothing anywhere except to the judge endpoint you configure, and runs fully offline when your labels file already contains the judge's answers.

Install

pip install judgegate

Sixty second tour

The repository ships example data, so every command below works on a fresh clone with no API key.

1. Verify a judge. Agreement is measured with Cohen's kappa (chance corrected, unlike raw percent agreement) and a bootstrap confidence interval, then compared against your trust threshold:

judgegate verify examples/labels.jsonl --config examples/judge.yaml

That is the TRUSTED screenshot above. Against a judge that flips verdicts too often, the same command refuses:

judgegate verify rejecting a noisy judge

2. Ask the label budget question. Human labels are the most expensive part of judge validation. This command turns "how many do we need" from a guess into arithmetic:

judgegate power --labels examples/labels.jsonl --config examples/judge.yaml

judgegate power output

3. Stop labeling early. Sequential mode replays your labels through always-valid stopping boundaries and shows where the labeling effort could have stopped:

judgegate sequential examples/labels.jsonl --config examples/judge.yaml

judgegate sequential stopping early

4. Pre-flight any labels file with judgegate validate labels.jsonl.

The labels file

One JSON object per line. Either score-style grading or pairwise comparison:

{"id": "q-001", "input": "What is a mutex?", "output": "A lock that...", "human_label": "pass", "judge_label": "pass"}
{"id": "q-002", "input": "Capital of France?", "output": "Lyon.", "human_label": "fail", "judge_label": "pass"}

human_label comes from your annotators. judge_label is optional: when every row has one (exported from your existing eval logs), judgegate runs completely offline. When missing, judgegate calls the judge configured in judge.yaml and caches every response. Pairwise judges use output_a and output_b with labels a and b. Full schema in docs/labels-format.md.

The probe battery

Agreement alone can hide systematic bias, so verify also runs probes, each with an effect size, confidence interval, and configurable tolerance:

  • stability: rerun the judge on identical inputs and count changed verdicts
  • position: swap the two candidates of a pairwise judge and check the verdict follows the content, not the slot
  • verbosity: fully offline check for whether output length sways the judge relative to humans
  • format: strip markdown decoration and check the verdicts hold

A failed probe forces UNTRUSTED regardless of how good the kappa looks, because a biased judge with high average agreement is still exploitable. judgegate probe runs the battery on its own.

Use it as a pull request gate

jobs:
  judge-gate:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v7
      - uses: yashchimata/judgegate@v0.1.0
        with:
          labels: evals/judge-labels.jsonl
          config: evals/judge.yaml

The action posts a sticky comment with the verdict, kappa error bar, and probe table, updates it in place on every push, and fails the check only on UNTRUSTED (or on INCONCLUSIVE if you set fail-on-inconclusive: true). Details in docs/github-action.md.

See it live: this repository gates its own example judge on every pull request; the open demo pull requests show a real TRUSTED comment and a real UNTRUSTED with a failed check.

Configuration

Everything lives in one judge.yaml:

judge:
  endpoint: https://api.openai.com/v1
  model: gpt-4o-mini
  api_key_env: OPENAI_API_KEY
  temperature: 0.0
  prompt: |
    You are grading whether an answer is factually correct.
    Question: {input}
    Answer: {output}
    Respond with JSON only: {"label": "pass"} or {"label": "fail"}.
  extraction:
    method: json_field
    field: label

labels:
  values: [fail, pass]

gate:
  min_kappa: 0.6
  alpha: 0.05
  power: 0.8

min_kappa is the heart of the policy. Kappa of 0.6 is a common bar for substantial agreement; where to set yours depends on what the judge gates. The full reference, including probe tolerances and weighted kappa for ordinal labels, is in docs/configuration.md.

How the statistics work

Short version: Cohen's kappa (or weighted kappa for ordinal labels) measured against human labels, a bias-corrected bootstrap confidence interval resampled at the item level, a decision rule on the interval rather than the point estimate, simulation-based power analysis for the label budget, and a mixture SPRT for the sequential mode. The long version, with the reasoning behind each choice and the known limitations, is in docs/methodology.md. The test suite includes calibration checks that verify interval coverage and certification rates against synthetic judges with known true agreement.

Works well with statgate

judgegate validates the judge; statgate turns eval scores into statistically calibrated ship or block decisions. Together they cover both halves of trusting an eval pipeline: first prove the judge deserves the job, then gate changes on what it reports.

Python API

from pathlib import Path

from judgegate import Verdict, evaluate_gate, load_config, load_examples
from judgegate.judge import resolve_judge_labels

config = load_config(Path("judge.yaml"))
examples = load_examples(Path("labels.jsonl"))
labels = resolve_judge_labels(examples, config, client=None)
report = evaluate_gate(examples, labels, config)

print(report.verdict, report.agreement.kappa, report.labels_needed)
if report.verdict is not Verdict.TRUSTED:
    raise SystemExit(report.exit_code)

Development

git clone https://github.com/yashchimata/judgegate
cd judgegate
python -m venv .venv && . .venv/bin/activate   # .venv\Scripts\activate on Windows
pip install -e ".[dev]"
pytest
ruff check src tests scripts
mypy src

Contributions are welcome. See CONTRIBUTING.md and the good first issues.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

judgegate-0.1.0.tar.gz (60.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

judgegate-0.1.0-py3-none-any.whl (50.4 kB view details)

Uploaded Python 3

File details

Details for the file judgegate-0.1.0.tar.gz.

File metadata

  • Download URL: judgegate-0.1.0.tar.gz
  • Upload date:
  • Size: 60.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for judgegate-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c79d01fe5170161475c09a5af49dff1442ad7be075568a9b0b05edbee08928b2
MD5 06202de7a23a9c6b0c9fc646493abc0e
BLAKE2b-256 bd6ca343e62f7a63cd84f80e43095d0e2716d36d012b8d0c20fd5b07629a0216

See more details on using hashes here.

Provenance

The following attestation bundles were made for judgegate-0.1.0.tar.gz:

Publisher: release.yml on yashchimata/judgegate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file judgegate-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: judgegate-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 50.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for judgegate-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e8d61720b8e5f3f305758a766875cdbfbfd30243b87e146529a3eef8e774fae0
MD5 335a9cf58339403b7c5f8d0fd8c37ce9
BLAKE2b-256 898c775d93de5cdbc0ec7a25060ac4982fc4bf9e6fb69d2cc003c63c40ee82e9

See more details on using hashes here.

Provenance

The following attestation bundles were made for judgegate-0.1.0-py3-none-any.whl:

Publisher: release.yml on yashchimata/judgegate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page