Skip to content

feat: multi-sample judging with stability metadata and noise-aware quality gate - #53

Open
brainsparker wants to merge 1 commit into
mainfrom
feature/2026-08-26-judge-stability
Open

feat: multi-sample judging with stability metadata and noise-aware quality gate#53
brainsparker wants to merge 1 commit into
mainfrom
feature/2026-08-26-judge-stability

Conversation

@brainsparker

Copy link
Copy Markdown
Owner

What this does

LLM-as-judge scores are noisy. The same judge model, prompt, and response can score 4 on one run and 3 on the next, even at temperature 0.3. On a single sample, that noise is indistinguishable from a real quality change, so --fail-under gates flap and teams stop trusting red builds.

This PR adds multi-sample judging and a noise-aware quality gate:

  • judge.samples config field and --judge-samples N CLI override (1 to 10, default 1): run the judge N times per response, concurrently.
  • Aggregation: the reported score is the median of the samples (robust to a single outlier). Every JudgeScore now carries sample_scores, score_mean, and score_stdev, which flow into the JSON export automatically.
  • Resilience: failed judge samples are logged and dropped as long as one succeeds, so a transient judge API error no longer sinks the case.
  • Run summary: per-model average judge stdev plus a count of unstable cases (stdev of 1.0 or more, meaning samples disagreed by a full point).
  • --noise-aware-gate: lowers each model's --fail-under threshold by its average per-case judge stdev. A model fails only when its score drops below the gate by more than the observed judge noise. The gate output prints the effective threshold and noise margin for each failing model, so the decision is auditable in the CI log.

Usage

promptlens run config.yaml --judge-samples 3 --fail-under 3.5 --noise-aware-gate

or in config:

judge:
  samples: 3

Why now (market rationale)

Judge flakiness is the most complained-about failure mode in the eval category right now, and nobody serves it in the lightweight OSS tier:

DeepEval, promptfoo, and RAGAS all issue a single judge call per case by default. PromptLens shipping variance-aware judging for free, wired directly into its existing CI gate, is a clean differentiator that extends the CI-native story from the recent --fail-under and compare --fail-on-regression work.

Runner-up candidates considered today: an MCP 2026-07-28 conformance checker command (timely, spec is under a month old, but a bigger surface to get right), and delta-gating against saved baselines (real need, but partially covered by the existing promptlens compare --fail-on-regression).

Implementation notes

  • New module promptlens/judges/stability.py holds aggregation (aggregate_judge_scores) and orchestration (sample_judge_scores). It works against BaseJudge, so any future judge implementation gets multi-sampling for free. LLMJudge is untouched.
  • Explanation and criteria scores come from the representative sample (the one closest to the median), with a stability note appended when samples disagree.
  • Backward compatible: samples defaults to 1, new JudgeScore fields have defaults, _check_fail_under keeps its existing shape (existing junit gate tests pass unchanged).
  • Uses stdlib statistics only, no new dependencies.
  • Cost note: each extra sample adds one judge API call per test case per model; documented in the README.

Test status

  • 27 new tests in tests/test_judge_stability.py covering aggregation math (median, outlier robustness, rounding, clamping), sampling orchestration (partial and total failure paths, call counts via a stub judge), config validation bounds, get_average_judge_stdev, and the noise-aware gate.
  • Full suite: 110 passed (83 existing plus 27 new), verified locally on Python 3.9.
  • CLI smoke-tested: --dry-run with judge.samples: 3, --judge-samples 5 override, and --noise-aware-gate without --fail-under exits 1 with a clear message.

PR opened by Daily Feature Builder. Run log: Hyperagent thread cmta3rei4004x08adr92set04.

…ality gate

LLM-as-judge scores are noisy: the same judge, prompt, and response can
score differently across runs, which makes single-sample CI gates flap.
This adds judge.samples / --judge-samples to run the judge k times per
response, aggregates to the median with mean/stdev/sample_scores attached
to JudgeScore, surfaces per-model stability in the run summary, and adds
--noise-aware-gate so --fail-under fails a model only when its average
score drops below the gate by more than the observed judge noise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant