judgekit 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. judgekit-0.2.0/LICENSE +21 -0
  2. judgekit-0.2.0/PKG-INFO +240 -0
  3. judgekit-0.2.0/README.md +208 -0
  4. judgekit-0.2.0/pyproject.toml +71 -0
  5. judgekit-0.2.0/setup.cfg +4 -0
  6. judgekit-0.2.0/src/judgekit/__init__.py +21 -0
  7. judgekit-0.2.0/src/judgekit/agreement.py +178 -0
  8. judgekit-0.2.0/src/judgekit/audit.py +448 -0
  9. judgekit-0.2.0/src/judgekit/bias.py +361 -0
  10. judgekit-0.2.0/src/judgekit/bootstrap.py +67 -0
  11. judgekit-0.2.0/src/judgekit/calibration.py +95 -0
  12. judgekit-0.2.0/src/judgekit/cli.py +89 -0
  13. judgekit-0.2.0/src/judgekit/consistency.py +90 -0
  14. judgekit-0.2.0/src/judgekit/demo.py +135 -0
  15. judgekit-0.2.0/src/judgekit/io.py +74 -0
  16. judgekit-0.2.0/src/judgekit/py.typed +0 -0
  17. judgekit-0.2.0/src/judgekit/report.py +176 -0
  18. judgekit-0.2.0/src/judgekit/result.py +66 -0
  19. judgekit-0.2.0/src/judgekit/schema.py +115 -0
  20. judgekit-0.2.0/src/judgekit/synthetic.py +272 -0
  21. judgekit-0.2.0/src/judgekit.egg-info/PKG-INFO +240 -0
  22. judgekit-0.2.0/src/judgekit.egg-info/SOURCES.txt +33 -0
  23. judgekit-0.2.0/src/judgekit.egg-info/dependency_links.txt +1 -0
  24. judgekit-0.2.0/src/judgekit.egg-info/entry_points.txt +2 -0
  25. judgekit-0.2.0/src/judgekit.egg-info/requires.txt +12 -0
  26. judgekit-0.2.0/src/judgekit.egg-info/top_level.txt +1 -0
  27. judgekit-0.2.0/tests/test_agreement.py +101 -0
  28. judgekit-0.2.0/tests/test_bias.py +176 -0
  29. judgekit-0.2.0/tests/test_calibration_bootstrap.py +99 -0
  30. judgekit-0.2.0/tests/test_cli.py +80 -0
  31. judgekit-0.2.0/tests/test_consistency.py +54 -0
  32. judgekit-0.2.0/tests/test_report.py +84 -0
  33. judgekit-0.2.0/tests/test_schema_io.py +86 -0
  34. judgekit-0.2.0/tests/test_skips.py +94 -0
  35. judgekit-0.2.0/tests/test_synthetic_recovery.py +176 -0
judgekit-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hadi Mohammadi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: judgekit
3
+ Version: 0.2.0
4
+ Summary: Audit an LLM judge before you trust it: agreement, bias probes, calibration and consistency, with bootstrap confidence intervals.
5
+ Author: Hadi Mohammadi
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mohammadi-hadi/judgekit
8
+ Project-URL: Issues, https://github.com/mohammadi-hadi/judgekit/issues
9
+ Keywords: llm-as-judge,llm-evaluation,calibration,bias,inter-rater-agreement
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy>=1.26
21
+ Requires-Dist: pydantic>=2.5
22
+ Requires-Dist: matplotlib>=3.8
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8; extra == "dev"
25
+ Requires-Dist: ruff>=0.4; extra == "dev"
26
+ Requires-Dist: mypy>=1.10; extra == "dev"
27
+ Requires-Dist: scikit-learn>=1.4; extra == "dev"
28
+ Requires-Dist: scipy>=1.12; extra == "dev"
29
+ Requires-Dist: krippendorff>=0.6; extra == "dev"
30
+ Requires-Dist: statsmodels>=0.14; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # judgekit
34
+
35
+ Audit an LLM judge before you trust it.
36
+
37
+ [![ci](https://github.com/mohammadi-hadi/judgekit/actions/workflows/ci.yml/badge.svg)](https://github.com/mohammadi-hadi/judgekit/actions/workflows/ci.yml)
38
+
39
+ An LLM judge is a model too. It can prefer whichever answer came first, reward
40
+ padding, favor its own outputs, hug the middle of the scale, and say "90%
41
+ sure" while being right 70% of the time — all while agreeing with your
42
+ annotators often enough to look fine. judgekit reads a JSONL of verdicts and
43
+ measures each of these failure modes with a bootstrap confidence interval,
44
+ then flags whatever leaves the range an innocent judge could occupy.
45
+
46
+ ## The audit in one table
47
+
48
+ Six synthetic judges, five implanted defects. Bold values are flags; each lands
49
+ exactly where the defect was implanted, and the clean judge's row carries none.
50
+
51
+ <!-- judgekit:demo -->
52
+ | judge | implanted defect | kappa_qw | position | verbosity | self-pref | spread | ECE | flags |
53
+ |---|---|---|---|---|---|---|---|---|
54
+ | clean | nothing | 0.96 | 0.50 | -0.03 | -0.04 | 1.02 | 0.03 | none |
55
+ | first-picker | takes the presented-first candidate 35% of the time | - | **0.67** | - | - | - | - | position preference |
56
+ | length-lover | adds standardized length to perceived quality | 0.80 | 0.49 | **0.82** | -0.14 | 1.00 | - | verbosity bias, verbosity bias (pairwise) |
57
+ | self-server | adds one scale point when the candidate is its own model | 0.89 | - | 0.05 | **0.76** | 0.97 | - | self-preference |
58
+ | middler | halves every departure from the scale midpoint | 0.76 | - | -0.03 | 0.00 | **0.63** | - | central tendency |
59
+ | overconfident | sharpens every claimed probability on the logit scale | - | - | - | - | - | **0.11** | expected calibration error |
60
+ <!-- /judgekit:demo -->
61
+
62
+ The row to sit with is `length-lover`: quadratic-weighted kappa with humans of
63
+ 0.80 — a number most eval writeups would celebrate — while carrying a verbosity
64
+ bias of +0.82. Agreement is not an audit.
65
+
66
+ `make demo` regenerates this table, the [full report](results/report.md) and
67
+ the figures from fixed seeds; CI rebuilds them from pinned dependencies and
68
+ fails if a committed number differs from what the code produces.
69
+
70
+ ![position preference](results/figures/position.png)
71
+
72
+ ![reliability](results/figures/reliability.png)
73
+
74
+ ## Two real judges, audited
75
+
76
+ [`examples/audit_ollama.py`](examples/audit_ollama.py) builds a small
77
+ adversarial arithmetic benchmark — one answer provably correct by
78
+ construction, and half the time the wrong answer is the long, worked-through
79
+ one — then asks a local Ollama model to judge every pair in both presentation
80
+ orders. Verdicts and full reports are committed under
81
+ [examples/results](examples/results/).
82
+
83
+ | judge (temperature 0) | truth agreement | position preference | verbosity excess | flips under swap | decisive on identical pairs |
84
+ |---|---|---|---|---|---|
85
+ | qwen2.5:14b | 0.667 | **0.768** (flag) | +0.131 | 0.568 | 0.200 |
86
+ | llama3.1:8b | 0.443 | 0.576 | **+0.483** (flag) | 0.171 | 1.000 |
87
+
88
+ Same task, two different failure modes. The 14B judge follows slot order:
89
+ presented-first wins 76.8% of decisive verdicts and swapping the order flips
90
+ 56.8% of matched pairs. The 8B judge grades effort instead, picking the longer
91
+ answer 48 points more often than correctness warrants, agreeing with ground
92
+ truth less often than a coin flip, and never once declaring a tie between two
93
+ identical answers. n=105 pairs, adversarial by construction — an illustration
94
+ of what the probes see, not a model leaderboard.
95
+
96
+ ## Install
97
+
98
+ ```
99
+ pip install judgekit
100
+ ```
101
+
102
+ Or the latest development version straight from GitHub:
103
+
104
+ ```
105
+ pip install git+https://github.com/mohammadi-hadi/judgekit
106
+ ```
107
+
108
+ Python 3.11+. Runtime dependencies: numpy, pydantic, matplotlib.
109
+
110
+ ## Quickstart
111
+
112
+ Write one JSON object per verdict. Three kinds exist, and every check switches
113
+ on automatically when the fields it needs are present:
114
+
115
+ ```json
116
+ {"kind": "graded", "item_id": "q101", "score": 4, "candidate_len": 512, "candidate_model": "gpt-4o"}
117
+ {"kind": "binary", "item_id": "q102", "label": true, "confidence": 0.9}
118
+ {"kind": "pairwise", "item_id": "q103", "choice": "a", "swapped": true, "a_len": 210, "b_len": 480}
119
+ ```
120
+
121
+ The reference file uses the same records (`judge_id: "human"`), aligned on
122
+ `item_id`. For pairwise verdicts, `choice` is always in canonical space —
123
+ `"a"` means canonical candidate A won regardless of which slot it was shown
124
+ in — and `swapped: true` records that the pair was presented in reversed
125
+ order. Judging every pair in both orders is what makes the position probes
126
+ assumption-free.
127
+
128
+ ```python
129
+ from judgekit import load_verdicts, run_audit
130
+
131
+ audit = run_audit(
132
+ load_verdicts("judge.jsonl"),
133
+ load_verdicts("human.jsonl"),
134
+ judge_model="gpt-4o", # enables the self-preference probe
135
+ )
136
+ for flag in audit.flags:
137
+ print(flag.name, "--", flag.detail)
138
+ ```
139
+
140
+ Or from the shell, with an exit code CI can gate on:
141
+
142
+ ```
143
+ judgekit report judge.jsonl --human human.jsonl --out audit --fail-on-flags
144
+ ```
145
+
146
+ Alongside `report.md` the audit writes `report.json` for pipelines that gate
147
+ on specific numbers. Any check that could not run is listed in the report with
148
+ the exact fields to log to enable it — a skip describes the log file, not the
149
+ judge.
150
+
151
+ ## What it checks
152
+
153
+ | check | question it answers | needs |
154
+ |---|---|---|
155
+ | position preference | does the judge favor the first slot? | pairwise verdicts, both orders |
156
+ | swap flip rate | how often does order alone decide? | both orders per pair |
157
+ | identical-pair decisiveness | verdicts between identical texts | pairs with `meta.identical` |
158
+ | verbosity bias | length preference at fixed quality | scores, `candidate_len`, human scores |
159
+ | self-preference | generosity toward its own model | `candidate_model`, human scores |
160
+ | central tendency | compression toward the scale middle | graded + human scores |
161
+ | calibration (ECE, Brier) | are confidences frequencies? | binary verdicts with `confidence` |
162
+ | re-judgment unanimity | sampling stability | repeats via `sample_index` |
163
+ | agreement (kappa, rho, exact) | does it match the reference at all? | judge + human verdicts |
164
+
165
+ ## How the numbers are defended
166
+
167
+ - **Cross-checked implementations.** Cohen's kappa (all weightings) matches
168
+ scikit-learn, Krippendorff's alpha matches the krippendorff package, and
169
+ Spearman matches scipy to 1e-10 or better in the test suite. The library
170
+ itself depends on none of them.
171
+ - **Exact identities.** The Brier score must equal reliability − resolution +
172
+ uncertainty from its Murphy decomposition at machine precision; the tests
173
+ assert it.
174
+ - **Validation by implantation.** The synthetic judges have defect dials, and
175
+ the tests require each probe to recover its dial — a 0.4 position override
176
+ must read as 0.7 first-slot preference — to stay monotone in it, and to stay
177
+ silent on the clean judge, including a world where longer answers genuinely
178
+ are better and the raw length correlation would convict an honest judge.
179
+ - **Drift-checked results.** Every number above and in
180
+ [results/report.md](results/report.md) is regenerated by CI from pinned
181
+ dependencies and diffed against the committed copy.
182
+
183
+ ## Design notes
184
+
185
+ - **Innocent bands, not point nulls.** With enough data a spread ratio of 1.04
186
+ excludes 1.0 while meaning nothing. Each triggering probe declares the band a
187
+ defect-free judge could plausibly occupy — position 0.45–0.55, correlations
188
+ ±0.10, self-preference ±0.20 scale points, spread 0.90–1.10, ECE ≤ 0.05 —
189
+ and flags only when the whole 95% interval leaves it. The bands are opinions,
190
+ stated in one place ([`bias.py`](src/judgekit/bias.py)) and easy to disagree
191
+ with.
192
+ - **Item-level bootstrap.** Re-judgments and swapped presentations of an item
193
+ are resampled together; treating correlated rows as independent evidence
194
+ would shrink every interval.
195
+ - **Bias triggers, instability informs.** A fair-but-noisy judge flips
196
+ genuinely close pairs under swap, so the flip rate and identical-pair
197
+ decisiveness are reported without flagging; systematic preference is what
198
+ `position preference` triggers on.
199
+ - **Verbosity is a partial correlation.** Longer answers are often genuinely
200
+ better, so score-vs-length is confounded with quality; the probe rank-partials
201
+ the human score out. When length never varies independently of quality the
202
+ answer is nan, because no verdict is the honest verdict.
203
+ - **Deterministic to the digit.** Seeded PCG64 resampling, no BLAS in any
204
+ statistic's path, and a pinned drift environment: the same verdicts give the
205
+ same report on any machine.
206
+
207
+ ## Limitations
208
+
209
+ - An audit is bounded by what was logged. No lengths, no verbosity probe; no
210
+ both-order presentations, weaker position probe; no re-judgments, no
211
+ stability number.
212
+ - The human reference is treated as ground truth. Where annotators are noisy,
213
+ judge-human disagreement is not all the judge's fault — with multiple raters,
214
+ measure the human ceiling with `agreement.krippendorff_alpha` first.
215
+ - The innocent bands are judgment calls, not derivations.
216
+ - Implantation shows the probes detect the mechanisms simulated; real judges
217
+ can fail in ways not simulated here.
218
+ - Verbosity control is linear in ranks of the human score; residual
219
+ confounding remains possible.
220
+
221
+ ## Related work
222
+
223
+ The failure modes measured here were documented by, among others: MT-Bench's
224
+ position, verbosity and self-enhancement analysis ([Zheng et al., 2023](https://arxiv.org/abs/2306.05685)),
225
+ position bias specifically ([Wang et al., 2023](https://arxiv.org/abs/2305.17926)),
226
+ adversarial judge evaluation ([Zeng et al., 2024](https://arxiv.org/abs/2310.07641)),
227
+ self-recognition and self-preference ([Panickssery et al., 2024](https://arxiv.org/abs/2404.13076)),
228
+ rubric-based judging ([Liu et al., 2023](https://arxiv.org/abs/2303.16634)) and
229
+ the LLM-as-a-judge survey ([Gu et al., 2024](https://arxiv.org/abs/2411.15594)).
230
+ judgekit's contribution is packaging the checks as one auditable tool with
231
+ uncertainty on every number and validation by implantation.
232
+
233
+ Companion projects: [trajectory-judge](https://github.com/mohammadi-hadi/trajectory-judge)
234
+ measures what outcome-only judges miss on agent trajectories;
235
+ [EvalMORAAL](https://github.com/mohammadi-hadi/EvalMORAAL) applies
236
+ chain-of-thought judging to moral alignment across 20 models.
237
+
238
+ ## License
239
+
240
+ MIT
@@ -0,0 +1,208 @@
1
+ # judgekit
2
+
3
+ Audit an LLM judge before you trust it.
4
+
5
+ [![ci](https://github.com/mohammadi-hadi/judgekit/actions/workflows/ci.yml/badge.svg)](https://github.com/mohammadi-hadi/judgekit/actions/workflows/ci.yml)
6
+
7
+ An LLM judge is a model too. It can prefer whichever answer came first, reward
8
+ padding, favor its own outputs, hug the middle of the scale, and say "90%
9
+ sure" while being right 70% of the time — all while agreeing with your
10
+ annotators often enough to look fine. judgekit reads a JSONL of verdicts and
11
+ measures each of these failure modes with a bootstrap confidence interval,
12
+ then flags whatever leaves the range an innocent judge could occupy.
13
+
14
+ ## The audit in one table
15
+
16
+ Six synthetic judges, five implanted defects. Bold values are flags; each lands
17
+ exactly where the defect was implanted, and the clean judge's row carries none.
18
+
19
+ <!-- judgekit:demo -->
20
+ | judge | implanted defect | kappa_qw | position | verbosity | self-pref | spread | ECE | flags |
21
+ |---|---|---|---|---|---|---|---|---|
22
+ | clean | nothing | 0.96 | 0.50 | -0.03 | -0.04 | 1.02 | 0.03 | none |
23
+ | first-picker | takes the presented-first candidate 35% of the time | - | **0.67** | - | - | - | - | position preference |
24
+ | length-lover | adds standardized length to perceived quality | 0.80 | 0.49 | **0.82** | -0.14 | 1.00 | - | verbosity bias, verbosity bias (pairwise) |
25
+ | self-server | adds one scale point when the candidate is its own model | 0.89 | - | 0.05 | **0.76** | 0.97 | - | self-preference |
26
+ | middler | halves every departure from the scale midpoint | 0.76 | - | -0.03 | 0.00 | **0.63** | - | central tendency |
27
+ | overconfident | sharpens every claimed probability on the logit scale | - | - | - | - | - | **0.11** | expected calibration error |
28
+ <!-- /judgekit:demo -->
29
+
30
+ The row to sit with is `length-lover`: quadratic-weighted kappa with humans of
31
+ 0.80 — a number most eval writeups would celebrate — while carrying a verbosity
32
+ bias of +0.82. Agreement is not an audit.
33
+
34
+ `make demo` regenerates this table, the [full report](results/report.md) and
35
+ the figures from fixed seeds; CI rebuilds them from pinned dependencies and
36
+ fails if a committed number differs from what the code produces.
37
+
38
+ ![position preference](results/figures/position.png)
39
+
40
+ ![reliability](results/figures/reliability.png)
41
+
42
+ ## Two real judges, audited
43
+
44
+ [`examples/audit_ollama.py`](examples/audit_ollama.py) builds a small
45
+ adversarial arithmetic benchmark — one answer provably correct by
46
+ construction, and half the time the wrong answer is the long, worked-through
47
+ one — then asks a local Ollama model to judge every pair in both presentation
48
+ orders. Verdicts and full reports are committed under
49
+ [examples/results](examples/results/).
50
+
51
+ | judge (temperature 0) | truth agreement | position preference | verbosity excess | flips under swap | decisive on identical pairs |
52
+ |---|---|---|---|---|---|
53
+ | qwen2.5:14b | 0.667 | **0.768** (flag) | +0.131 | 0.568 | 0.200 |
54
+ | llama3.1:8b | 0.443 | 0.576 | **+0.483** (flag) | 0.171 | 1.000 |
55
+
56
+ Same task, two different failure modes. The 14B judge follows slot order:
57
+ presented-first wins 76.8% of decisive verdicts and swapping the order flips
58
+ 56.8% of matched pairs. The 8B judge grades effort instead, picking the longer
59
+ answer 48 points more often than correctness warrants, agreeing with ground
60
+ truth less often than a coin flip, and never once declaring a tie between two
61
+ identical answers. n=105 pairs, adversarial by construction — an illustration
62
+ of what the probes see, not a model leaderboard.
63
+
64
+ ## Install
65
+
66
+ ```
67
+ pip install judgekit
68
+ ```
69
+
70
+ Or the latest development version straight from GitHub:
71
+
72
+ ```
73
+ pip install git+https://github.com/mohammadi-hadi/judgekit
74
+ ```
75
+
76
+ Python 3.11+. Runtime dependencies: numpy, pydantic, matplotlib.
77
+
78
+ ## Quickstart
79
+
80
+ Write one JSON object per verdict. Three kinds exist, and every check switches
81
+ on automatically when the fields it needs are present:
82
+
83
+ ```json
84
+ {"kind": "graded", "item_id": "q101", "score": 4, "candidate_len": 512, "candidate_model": "gpt-4o"}
85
+ {"kind": "binary", "item_id": "q102", "label": true, "confidence": 0.9}
86
+ {"kind": "pairwise", "item_id": "q103", "choice": "a", "swapped": true, "a_len": 210, "b_len": 480}
87
+ ```
88
+
89
+ The reference file uses the same records (`judge_id: "human"`), aligned on
90
+ `item_id`. For pairwise verdicts, `choice` is always in canonical space —
91
+ `"a"` means canonical candidate A won regardless of which slot it was shown
92
+ in — and `swapped: true` records that the pair was presented in reversed
93
+ order. Judging every pair in both orders is what makes the position probes
94
+ assumption-free.
95
+
96
+ ```python
97
+ from judgekit import load_verdicts, run_audit
98
+
99
+ audit = run_audit(
100
+ load_verdicts("judge.jsonl"),
101
+ load_verdicts("human.jsonl"),
102
+ judge_model="gpt-4o", # enables the self-preference probe
103
+ )
104
+ for flag in audit.flags:
105
+ print(flag.name, "--", flag.detail)
106
+ ```
107
+
108
+ Or from the shell, with an exit code CI can gate on:
109
+
110
+ ```
111
+ judgekit report judge.jsonl --human human.jsonl --out audit --fail-on-flags
112
+ ```
113
+
114
+ Alongside `report.md` the audit writes `report.json` for pipelines that gate
115
+ on specific numbers. Any check that could not run is listed in the report with
116
+ the exact fields to log to enable it — a skip describes the log file, not the
117
+ judge.
118
+
119
+ ## What it checks
120
+
121
+ | check | question it answers | needs |
122
+ |---|---|---|
123
+ | position preference | does the judge favor the first slot? | pairwise verdicts, both orders |
124
+ | swap flip rate | how often does order alone decide? | both orders per pair |
125
+ | identical-pair decisiveness | verdicts between identical texts | pairs with `meta.identical` |
126
+ | verbosity bias | length preference at fixed quality | scores, `candidate_len`, human scores |
127
+ | self-preference | generosity toward its own model | `candidate_model`, human scores |
128
+ | central tendency | compression toward the scale middle | graded + human scores |
129
+ | calibration (ECE, Brier) | are confidences frequencies? | binary verdicts with `confidence` |
130
+ | re-judgment unanimity | sampling stability | repeats via `sample_index` |
131
+ | agreement (kappa, rho, exact) | does it match the reference at all? | judge + human verdicts |
132
+
133
+ ## How the numbers are defended
134
+
135
+ - **Cross-checked implementations.** Cohen's kappa (all weightings) matches
136
+ scikit-learn, Krippendorff's alpha matches the krippendorff package, and
137
+ Spearman matches scipy to 1e-10 or better in the test suite. The library
138
+ itself depends on none of them.
139
+ - **Exact identities.** The Brier score must equal reliability − resolution +
140
+ uncertainty from its Murphy decomposition at machine precision; the tests
141
+ assert it.
142
+ - **Validation by implantation.** The synthetic judges have defect dials, and
143
+ the tests require each probe to recover its dial — a 0.4 position override
144
+ must read as 0.7 first-slot preference — to stay monotone in it, and to stay
145
+ silent on the clean judge, including a world where longer answers genuinely
146
+ are better and the raw length correlation would convict an honest judge.
147
+ - **Drift-checked results.** Every number above and in
148
+ [results/report.md](results/report.md) is regenerated by CI from pinned
149
+ dependencies and diffed against the committed copy.
150
+
151
+ ## Design notes
152
+
153
+ - **Innocent bands, not point nulls.** With enough data a spread ratio of 1.04
154
+ excludes 1.0 while meaning nothing. Each triggering probe declares the band a
155
+ defect-free judge could plausibly occupy — position 0.45–0.55, correlations
156
+ ±0.10, self-preference ±0.20 scale points, spread 0.90–1.10, ECE ≤ 0.05 —
157
+ and flags only when the whole 95% interval leaves it. The bands are opinions,
158
+ stated in one place ([`bias.py`](src/judgekit/bias.py)) and easy to disagree
159
+ with.
160
+ - **Item-level bootstrap.** Re-judgments and swapped presentations of an item
161
+ are resampled together; treating correlated rows as independent evidence
162
+ would shrink every interval.
163
+ - **Bias triggers, instability informs.** A fair-but-noisy judge flips
164
+ genuinely close pairs under swap, so the flip rate and identical-pair
165
+ decisiveness are reported without flagging; systematic preference is what
166
+ `position preference` triggers on.
167
+ - **Verbosity is a partial correlation.** Longer answers are often genuinely
168
+ better, so score-vs-length is confounded with quality; the probe rank-partials
169
+ the human score out. When length never varies independently of quality the
170
+ answer is nan, because no verdict is the honest verdict.
171
+ - **Deterministic to the digit.** Seeded PCG64 resampling, no BLAS in any
172
+ statistic's path, and a pinned drift environment: the same verdicts give the
173
+ same report on any machine.
174
+
175
+ ## Limitations
176
+
177
+ - An audit is bounded by what was logged. No lengths, no verbosity probe; no
178
+ both-order presentations, weaker position probe; no re-judgments, no
179
+ stability number.
180
+ - The human reference is treated as ground truth. Where annotators are noisy,
181
+ judge-human disagreement is not all the judge's fault — with multiple raters,
182
+ measure the human ceiling with `agreement.krippendorff_alpha` first.
183
+ - The innocent bands are judgment calls, not derivations.
184
+ - Implantation shows the probes detect the mechanisms simulated; real judges
185
+ can fail in ways not simulated here.
186
+ - Verbosity control is linear in ranks of the human score; residual
187
+ confounding remains possible.
188
+
189
+ ## Related work
190
+
191
+ The failure modes measured here were documented by, among others: MT-Bench's
192
+ position, verbosity and self-enhancement analysis ([Zheng et al., 2023](https://arxiv.org/abs/2306.05685)),
193
+ position bias specifically ([Wang et al., 2023](https://arxiv.org/abs/2305.17926)),
194
+ adversarial judge evaluation ([Zeng et al., 2024](https://arxiv.org/abs/2310.07641)),
195
+ self-recognition and self-preference ([Panickssery et al., 2024](https://arxiv.org/abs/2404.13076)),
196
+ rubric-based judging ([Liu et al., 2023](https://arxiv.org/abs/2303.16634)) and
197
+ the LLM-as-a-judge survey ([Gu et al., 2024](https://arxiv.org/abs/2411.15594)).
198
+ judgekit's contribution is packaging the checks as one auditable tool with
199
+ uncertainty on every number and validation by implantation.
200
+
201
+ Companion projects: [trajectory-judge](https://github.com/mohammadi-hadi/trajectory-judge)
202
+ measures what outcome-only judges miss on agent trajectories;
203
+ [EvalMORAAL](https://github.com/mohammadi-hadi/EvalMORAAL) applies
204
+ chain-of-thought judging to moral alignment across 20 models.
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "judgekit"
7
+ version = "0.2.0"
8
+ description = "Audit an LLM judge before you trust it: agreement, bias probes, calibration and consistency, with bootstrap confidence intervals."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Hadi Mohammadi" }]
12
+ requires-python = ">=3.11"
13
+ dependencies = [
14
+ "numpy>=1.26",
15
+ "pydantic>=2.5",
16
+ "matplotlib>=3.8",
17
+ ]
18
+ keywords = ["llm-as-judge", "llm-evaluation", "calibration", "bias", "inter-rater-agreement"]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/mohammadi-hadi/judgekit"
31
+ Issues = "https://github.com/mohammadi-hadi/judgekit/issues"
32
+
33
+ [project.scripts]
34
+ judgekit = "judgekit.cli:main"
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8",
39
+ "ruff>=0.4",
40
+ "mypy>=1.10",
41
+ "scikit-learn>=1.4",
42
+ "scipy>=1.12",
43
+ "krippendorff>=0.6",
44
+ "statsmodels>=0.14",
45
+ ]
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.setuptools.package-data]
51
+ judgekit = ["py.typed"]
52
+
53
+ [tool.ruff]
54
+ line-length = 100
55
+ src = ["src", "tests"]
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "I", "W", "UP", "B", "SIM", "RUF"]
59
+
60
+ [tool.mypy]
61
+ strict = true
62
+ files = ["src"]
63
+ plugins = ["pydantic.mypy"]
64
+
65
+ [[tool.mypy.overrides]]
66
+ module = "matplotlib.*"
67
+ ignore_missing_imports = true
68
+
69
+ [tool.pytest.ini_options]
70
+ addopts = "-q"
71
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ """judgekit: audit an LLM judge before you trust it."""
2
+
3
+ from judgekit.audit import AuditReport, run_audit
4
+ from judgekit.io import dump_verdicts, load_verdicts
5
+ from judgekit.result import ProbeResult, SkippedProbe
6
+ from judgekit.schema import BinaryVerdict, GradedVerdict, PairwiseVerdict
7
+
8
+ __version__ = "0.2.0"
9
+
10
+ __all__ = [
11
+ "AuditReport",
12
+ "BinaryVerdict",
13
+ "GradedVerdict",
14
+ "PairwiseVerdict",
15
+ "ProbeResult",
16
+ "SkippedProbe",
17
+ "__version__",
18
+ "dump_verdicts",
19
+ "load_verdicts",
20
+ "run_audit",
21
+ ]