Skip to main content

How stable are natural-language unit tests for LLM evals? An empirical robustness study and mitigation library.

Project description

flakyjudge

PyPI CI License: MIT

How stable are natural-language unit tests for LLM evals?

Teams increasingly evaluate LLM outputs with natural-language unit tests — discrete assertions like "Does the response cite the refund window?" scored by a judge model (LMUnit, TICK, checklist evals). This project measures the reliability of that paradigm: if you reword the assertion without changing its meaning, does the verdict change?

Rewording a unit test flips 16 of 78 verdicts (gpt-4o)

📄 Tech report: report/report.md — methods, all results, practical guidance, limitations.

Experimental design frozen in PREREGISTRATION.md. Five judges across three families: gpt-4o, gpt-4o-mini, claude-sonnet-4-6, claude-haiku-4-5, llama-3.1-8b. ~24,000 scored triples, $19 total spend.

Findings

  • Rewording a unit test flips its pass/fail verdict on 14–25% of items (sonnet 25%, gpt-4o & haiku 20.5%, mini 14.1%) — 5–20× each judge's identical-input resampling noise floor (0.8–4%).

  • Criterion wording acts as a hidden decision threshold: flips concentrate ~3–7× on items whose scores sit near the pass/fail cut (29–40%) vs clear verdicts (5–13%).

    Flip rates vs noise floor and controls

  • "Stability" without validity is insensitivity — the controls catch it: llama-8b looks most stable under paraphrase (5% flips) but barely reacts to deliberately meaning-changed criteria either (1–3% vs Claude's 47–54%) — it isn't robust, it isn't reading the criterion. Every real judge shifts 3–4× more on controls than on true paraphrases.

    Which rewordings move the score

  • The classic verbosity bias disappears under criterion-anchored judging, in every family: content-matched 1.8× padding produces no significant positive drift for any judge; gpt-4o rewards concision (+0.23, p=0.03) and Claude trends mildly negative. The length halo documented for holistic/pairwise judging does not survive decomposition.

  • The logprob-weighted scoring trick is a small-model story: it buys +0.01–0.02 Spearman for GPT-4o-class judges (CIs cross zero) but +0.23 for llama-8b (CI [+0.17, +0.29]) — rescuing it from ρ≈0.13 to ρ≈0.43. Expectation-scoring is what makes small judges usable.

  • Validity and stability don't come together: claude-sonnet is the most human-aligned judge (ρ=0.69 vs the 0.56 human-agreement ceiling) AND the most paraphrase-flippy (25%) — and the least format-compliant (~10% of calls ignore the bare-digit instruction; OpenAI judges: 0%).

  • Rubrics make judges harsher, not more valid: appending the FLASK rubric+reference shifts scores −0.25 to −0.29 with ~no correlation gain.

What this is

An empirical robustness study across 5 judge models in 3 families (GPT-4o, GPT-4o-mini, Claude Sonnet 4.6, Claude Haiku 4.5, Llama-3.1-8B) on FLASK and BiGGen-Bench items, plus a small library implementing the measurement tools and mitigations:

  • E1 — judge-human correlation anchor; logprob-weighted vs. direct scoring
  • E2 — identical-input noise floor (resampling + field-order stability)
  • E3headline: paraphrase sensitivity of the unit-test criterion itself
  • E4 — verbosity bias under criterion-anchored judging

Related work measures judge robustness at the response level (Judge Reliability Harness) and template level; this study targets the criterion level — the load-bearing assumption of the unit-test paradigm (LMUnit).

Use the mitigation: pip install flakyjudge

Our headline finding is that single-shot verdicts silently depend on how you worded the assertion. ensemble_score() makes that visible: it scores your criterion plus n auto-generated meaning-preserving rewordings and reports the spread.

from flakyjudge import ensemble_score

r = ensemble_score(
    query="What is your refund policy?",
    response="You can return items within 30 days for a full refund.",
    unit_test="Does the response state the refund time window?",
    judge="gpt-4o-mini",        # or gpt-4o / claude-sonnet / claude-haiku,
    n_paraphrases=4,            #    or any flakyjudge.JudgeSpec
)
r.score    # 5.00 (mean across wordings; logprob-weighted where available)
r.stable   # True -> no wording flips the verdict
r.margin   # 2.50 (distance from threshold; <1 means borderline: distrust it)

Use it for gating/CI evals where a silent wording flip matters; responses are cached in ~/.flakyjudge/ so repeat runs are free.

Reproducibility

Every raw API response (including logprobs) is committed in a content-addressed cache, so all results and figures regenerate with zero API keys and zero dollars:

git clone https://github.com/ShivaSankeerth/flakyjudge && cd flakyjudge
uv sync
make cache-unpack   # inflate the committed API-response cache (19MB gz)
make figures

Development

uv sync --extra dev
make test    # pytest: metrics vs published worked examples, cache, providers
make lint    # ruff

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

flakyjudge-0.1.1.tar.gz (23.2 kB view details)

Uploaded Source

Built Distribution

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

flakyjudge-0.1.1-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file flakyjudge-0.1.1.tar.gz.

File metadata

  • Download URL: flakyjudge-0.1.1.tar.gz
  • Upload date:
  • Size: 23.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for flakyjudge-0.1.1.tar.gz
Algorithm Hash digest
SHA256 b551df075b4827662e0248d31cbada6b48992ac248e57fe3a833ed9fa7963ab9
MD5 3c0cc3702ccb06f33bcbcd54b325ac9e
BLAKE2b-256 582b8971cca60a52ad2adfd510ac11a07a6a4e2f4d4e126cb1ac4743c80b8901

See more details on using hashes here.

File details

Details for the file flakyjudge-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: flakyjudge-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 25.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for flakyjudge-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2f1c68bdcf3b54fc55d454f0df1434d926d22bf38ce9279289db1085af3c160e
MD5 6c64b20b328d2f28c4d4aba2c2fb2e1f
BLAKE2b-256 2e8d5cbff4106db418278cf5339dde6d2ff181f5fb95f13a0393c5ae9a893b7d

See more details on using hashes here.

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