judgejitter 0.1.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.
- judgejitter-0.1.0/.gitignore +15 -0
- judgejitter-0.1.0/CHANGELOG.md +11 -0
- judgejitter-0.1.0/LICENSE +21 -0
- judgejitter-0.1.0/PKG-INFO +184 -0
- judgejitter-0.1.0/README.md +149 -0
- judgejitter-0.1.0/pyproject.toml +65 -0
- judgejitter-0.1.0/src/judgejitter/__init__.py +3 -0
- judgejitter-0.1.0/src/judgejitter/__main__.py +3 -0
- judgejitter-0.1.0/src/judgejitter/cli.py +413 -0
- judgejitter-0.1.0/src/judgejitter/compare.py +83 -0
- judgejitter-0.1.0/src/judgejitter/items.py +184 -0
- judgejitter-0.1.0/src/judgejitter/pricing.py +59 -0
- judgejitter-0.1.0/src/judgejitter/prompt.py +122 -0
- judgejitter-0.1.0/src/judgejitter/report.py +309 -0
- judgejitter-0.1.0/src/judgejitter/runner.py +331 -0
- judgejitter-0.1.0/src/judgejitter/stats.py +374 -0
- judgejitter-0.1.0/src/judgejitter/store.py +263 -0
- judgejitter-0.1.0/src/judgejitter/templates/report.html.j2 +262 -0
- judgejitter-0.1.0/tests/conftest.py +68 -0
- judgejitter-0.1.0/tests/test_cli.py +87 -0
- judgejitter-0.1.0/tests/test_items_prompt.py +130 -0
- judgejitter-0.1.0/tests/test_run_report.py +154 -0
- judgejitter-0.1.0/tests/test_stats.py +189 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-11-01)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `jj run`: K judge calls per item on the Claude API, concurrent, with a cost cap, a dry run, structured or free-text verdicts, and every call stored in DuckDB.
|
|
8
|
+
- `jj resume`: finish a stopped run and retry failed calls.
|
|
9
|
+
- `jj report`: one self-contained HTML report and a JSON file: Fleiss' kappa or ICC(1,1), flip rates, a flip-rate histogram, single-run versus majority-vote intervals, the recommended calls per item, and the 20 least stable items.
|
|
10
|
+
- `jj compare`: paired comparison of two runs with an exact McNemar test and the list of changed items.
|
|
11
|
+
- Input from JSONL, CSV or a promptfoo results file.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ishan Yash
|
|
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,184 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: judgejitter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Re-run your LLM-as-judge eval K times and see how stable the verdicts are, which items flip, and how many repeats you need.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ishanyash/judgejitter
|
|
6
|
+
Project-URL: Source, https://github.com/ishanyash/judgejitter
|
|
7
|
+
Project-URL: Issues, https://github.com/ishanyash/judgejitter/issues
|
|
8
|
+
Project-URL: Sample report, https://ishanyash.github.io/judgejitter/
|
|
9
|
+
Author: Ishan Yash
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: anthropic,claude,evals,evaluation,kappa,llm,llm-as-judge,reliability
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: anthropic<3,>=2.0
|
|
26
|
+
Requires-Dist: duckdb>=1.1
|
|
27
|
+
Requires-Dist: jinja2>=3.1
|
|
28
|
+
Requires-Dist: numpy>=1.26
|
|
29
|
+
Requires-Dist: scipy>=1.11
|
|
30
|
+
Requires-Dist: typer>=0.12
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# JudgeJitter
|
|
37
|
+
|
|
38
|
+
**Re-run your LLM-as-judge eval K times and see how stable the verdicts are, which items flip, and how many repeats you need for a score you can trust.**
|
|
39
|
+
|
|
40
|
+
[](https://ishanyash.github.io/judgejitter/)
|
|
41
|
+
|
|
42
|
+
Live sample report: **https://ishanyash.github.io/judgejitter/**
|
|
43
|
+
|
|
44
|
+
## Why now
|
|
45
|
+
|
|
46
|
+
- Claude API version `2026-10-21` (21 October 2026) rejects `temperature`, `top_p` and `top_k`. Requests run at the default temperature of 1.0, and a judge that relied on `temperature: 0` loses that unless it stays on `2023-06-01` ([migration guide](https://platform.claude.com/docs/en/api/migration-2026-10-21)).
|
|
47
|
+
- Claude Haiku 5.5 (7 October 2026), a common cheap judge, already returns errors for sampling parameters ([release notes](https://platform.claude.com/docs/en/release-notes/overview)).
|
|
48
|
+
- So the question every team with an LLM judge now has: if I run the same eval twice, how far does the score move? JudgeJitter measures it.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install judgejitter
|
|
54
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
55
|
+
|
|
56
|
+
# See the plan, the first request and a rough cost. Makes no calls.
|
|
57
|
+
jj run items.jsonl --judge judge.txt --dry-run
|
|
58
|
+
|
|
59
|
+
# Judge every item 5 times, stop before $2 is spent.
|
|
60
|
+
jj run items.jsonl --judge judge.txt -k 5 --max-cost 2
|
|
61
|
+
|
|
62
|
+
# One self-contained HTML page, plus the numbers as JSON.
|
|
63
|
+
jj report --out report.html --json stats.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`items.jsonl` has one item per line:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{"id": "q1", "input": "What happens if you crack your knuckles a lot?", "output": "You will get arthritis.", "reference": "Nothing in particular happens."}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
CSV with the same columns works too, and so does a promptfoo results file (`promptfoo eval -o results.json`). `reference` is optional.
|
|
73
|
+
|
|
74
|
+
`judge.txt` is your judge prompt with placeholders:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
Question: {{ input }}
|
|
78
|
+
Reference answer: {{ reference }}
|
|
79
|
+
Candidate answer: {{ output }}
|
|
80
|
+
|
|
81
|
+
PASS if the candidate is true and agrees with the reference. FAIL otherwise.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The judge returns PASS or FAIL by default. Use `--scale 1-5` for a 1 to 5 score (a pass is 4 or more; change it with `--pass-threshold`).
|
|
85
|
+
|
|
86
|
+
## What you get
|
|
87
|
+
|
|
88
|
+
- **Self-agreement**: Fleiss' kappa across the K calls (ICC(1,1) for 1 to 5 scores).
|
|
89
|
+
- **Flip rate per item**: the share of an item's calls that disagree with its majority, as a histogram and as a list of the 20 least stable items, with one reason the judge gave for each side.
|
|
90
|
+
- **Single run versus majority vote**: the pass rate you would get if you reran the judge on the same items, with a 95% interval, for 1 call per item and for a majority vote of 3, 5, 7 and more.
|
|
91
|
+
- **Recommended K**: the smallest odd number of calls per item that keeps that interval within 2 points (`--target` to change).
|
|
92
|
+
- **Item sampling**: the interval when the items are resampled too, so you can see the part that more calls cannot fix.
|
|
93
|
+
- **Cost**: tokens and dollars per run, from the token counts the API returns.
|
|
94
|
+
|
|
95
|
+
Every call is kept in a DuckDB file (`jj.duckdb`), so you can query it:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
duckdb jj.duckdb "select item_id, list(verdict) from verdicts group by 1 having count(distinct verdict) > 1"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Compare two runs
|
|
102
|
+
|
|
103
|
+
Run the same items twice with different settings, then compare per-item majority verdicts with an exact McNemar test:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
jj run items.jsonl -j judge.txt --model claude-sonnet-4-6 --api-version 2023-06-01 --temperature 0 --name old
|
|
107
|
+
jj run items.jsonl -j judge.txt --model claude-sonnet-4-6 --name new
|
|
108
|
+
jj compare old new
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
It prints how many items went PASS to FAIL and FAIL to PASS, the change in pass rate with a 95% interval, the p-value, and the items whose verdict changed. Models without a built-in price show cost as unknown; add `--price-in` and `--price-out` (dollars per million tokens).
|
|
112
|
+
|
|
113
|
+
## Commands
|
|
114
|
+
|
|
115
|
+
| Command | What it does |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `jj run ITEMS --judge PROMPT` | Calls the judge K times per item and stores every verdict. `--k`, `--model`, `--concurrency`, `--max-cost`, `--dry-run`, `--limit`, `--scale`, `--effort`, `--thinking`, `--api-version`, `--temperature`, `--text` |
|
|
118
|
+
| `jj resume [RUN]` | Finishes a run that stopped at the cost cap and retries failed calls |
|
|
119
|
+
| `jj report [RUN]` | Writes the HTML report (`--out`) and the numbers (`--json`) |
|
|
120
|
+
| `jj compare RUN_A RUN_B` | Paired comparison of two runs on the same items |
|
|
121
|
+
| `jj runs` | Lists the runs in the database |
|
|
122
|
+
| `jj models` | Lists the models with a built-in price |
|
|
123
|
+
|
|
124
|
+
A run can be named with `--name` and referred to by name, by id, by an id prefix, or as `latest` (the default).
|
|
125
|
+
|
|
126
|
+
## How it works
|
|
127
|
+
|
|
128
|
+
1. **Calls.** Each item's prompt is sent K times, concurrently, with no sampling parameters, so the judge runs at the model's default. The default model is `claude-haiku-5-5` with the SDK's API version (`2026-10-21`). Replies use structured output (a JSON schema with a short reason and the verdict) so every reply can be read; `--text` parses free text instead, taking the last PASS/FAIL or score in the reply. Refusals, empty replies and errors are kept as failed calls, not verdicts. Calls go out repeat by repeat, so a run stopped by `--max-cost` still has whole repeats.
|
|
129
|
+
2. **Agreement.** Fleiss' kappa treats the K calls as raters. For scores, the one-way intraclass correlation fits, because repeated calls are interchangeable rather than distinct raters.
|
|
130
|
+
3. **Rerun intervals.** Each item's chance of a pass on one call is its observed share of passing calls. JudgeJitter simulates 4,000 reruns on the same items: it draws k calls per item, takes the majority, and averages. The middle 95% of those pass rates is the interval.
|
|
131
|
+
4. **Compare.** Items are paired by id. The exact McNemar test uses the items that changed between PASS and FAIL; the interval on the change comes from a paired bootstrap.
|
|
132
|
+
|
|
133
|
+
## Sample results
|
|
134
|
+
|
|
135
|
+
The sample judges 200 [TruthfulQA](https://github.com/sylinrl/TruthfulQA) questions (Apache-2.0). Each item has one candidate answer, picked at random from the dataset's correct or incorrect answers, and the dataset's best answer as the reference. The judge prompt is [examples/truthfulqa/judge.txt](examples/truthfulqa/judge.txt) and the items are built by [examples/truthfulqa/make_items.py](examples/truthfulqa/make_items.py).
|
|
136
|
+
|
|
137
|
+
<!-- sample-numbers:start -->
|
|
138
|
+
Measured 2026-11-01, run `20261101-175206-c137`: `claude-haiku-5-5` as judge, API version 2026-10-21, no sampling parameters (model default), structured JSON output, 200 TruthfulQA items x 5 calls.
|
|
139
|
+
|
|
140
|
+
| Measure | Result |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| Calls | 1,000 (0 failed or unreadable) |
|
|
143
|
+
| Fleiss' kappa (self-agreement) | 0.953 |
|
|
144
|
+
| Items with all 5 verdicts the same | 190 of 200 (95.0%) |
|
|
145
|
+
| Items that flipped at least once | 10 of 200 (5.0%) |
|
|
146
|
+
| Mean flip rate | 1.3% |
|
|
147
|
+
| The 5 single runs scored | 45.0%, 44.5%, 43.5%, 44.5%, 44.0% |
|
|
148
|
+
| Single run, 95% of reruns | 43.0% to 45.5% (2.5 points) |
|
|
149
|
+
| Majority of 5, 95% of reruns | 43.5% to 45.5% (2.0 points) |
|
|
150
|
+
| Calls per item for a 2-point interval | 5 |
|
|
151
|
+
| With item sampling (200 items), majority of 5 | 37.5% to 51.5% (14.0 points) |
|
|
152
|
+
| Tokens | 426,565 in, 98,489 out |
|
|
153
|
+
| Cost | $0.0919 |
|
|
154
|
+
|
|
155
|
+
Cost is the token counts the API returned, priced at list price. The raw numbers are in [sample/stats.json](sample/stats.json).
|
|
156
|
+
<!-- sample-numbers:end -->
|
|
157
|
+
|
|
158
|
+
## Known limits
|
|
159
|
+
|
|
160
|
+
- **Self-agreement is not accuracy.** A judge can agree with itself every time and still be wrong. To check a judge against human grades, use a tool such as [judgekeeper](https://pypi.org/project/judgekeeper/).
|
|
161
|
+
- **K=5 misses rare flips.** An item that flips once in 20 calls shows no flip in 5 calls about three times in four. Items with no observed flips are treated as stable, so the rerun intervals are a lower bound on the noise.
|
|
162
|
+
- **Kappa drops when nearly every verdict is the same** (the prevalence effect). Read it next to the flip rate.
|
|
163
|
+
- **Majority vote needs an odd K.** With an even K, the report votes with K-1 calls.
|
|
164
|
+
- **Claude only** in v0.1. The dry-run cost is a rough guess; the cost after a run uses the API's token counts and list prices checked on 1 November 2026.
|
|
165
|
+
- **Structured output adds a schema** to each request. Use `--text` to judge with your prompt exactly as it is.
|
|
166
|
+
- **promptfoo files**: the `results.results` and `results.outputs` shapes are read. Open an issue with a sample if yours is different.
|
|
167
|
+
- **The `jj` command name** is also used by Jujutsu. If you have both, call `judgejitter` instead; it is the same program.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pip install -e ".[dev]"
|
|
173
|
+
pytest
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The statistics are tested against known values: the worked example on Wikipedia's Fleiss' kappa page, the Shrout and Fleiss (1979) ICC table, and hand-worked kappa and exact McNemar cases.
|
|
177
|
+
|
|
178
|
+
## Licence
|
|
179
|
+
|
|
180
|
+
MIT. See [LICENSE](LICENSE).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
Built by [Ishan Yash](https://ishanyash.com).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# JudgeJitter
|
|
2
|
+
|
|
3
|
+
**Re-run your LLM-as-judge eval K times and see how stable the verdicts are, which items flip, and how many repeats you need for a score you can trust.**
|
|
4
|
+
|
|
5
|
+
[](https://ishanyash.github.io/judgejitter/)
|
|
6
|
+
|
|
7
|
+
Live sample report: **https://ishanyash.github.io/judgejitter/**
|
|
8
|
+
|
|
9
|
+
## Why now
|
|
10
|
+
|
|
11
|
+
- Claude API version `2026-10-21` (21 October 2026) rejects `temperature`, `top_p` and `top_k`. Requests run at the default temperature of 1.0, and a judge that relied on `temperature: 0` loses that unless it stays on `2023-06-01` ([migration guide](https://platform.claude.com/docs/en/api/migration-2026-10-21)).
|
|
12
|
+
- Claude Haiku 5.5 (7 October 2026), a common cheap judge, already returns errors for sampling parameters ([release notes](https://platform.claude.com/docs/en/release-notes/overview)).
|
|
13
|
+
- So the question every team with an LLM judge now has: if I run the same eval twice, how far does the score move? JudgeJitter measures it.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install judgejitter
|
|
19
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
20
|
+
|
|
21
|
+
# See the plan, the first request and a rough cost. Makes no calls.
|
|
22
|
+
jj run items.jsonl --judge judge.txt --dry-run
|
|
23
|
+
|
|
24
|
+
# Judge every item 5 times, stop before $2 is spent.
|
|
25
|
+
jj run items.jsonl --judge judge.txt -k 5 --max-cost 2
|
|
26
|
+
|
|
27
|
+
# One self-contained HTML page, plus the numbers as JSON.
|
|
28
|
+
jj report --out report.html --json stats.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`items.jsonl` has one item per line:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{"id": "q1", "input": "What happens if you crack your knuckles a lot?", "output": "You will get arthritis.", "reference": "Nothing in particular happens."}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
CSV with the same columns works too, and so does a promptfoo results file (`promptfoo eval -o results.json`). `reference` is optional.
|
|
38
|
+
|
|
39
|
+
`judge.txt` is your judge prompt with placeholders:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Question: {{ input }}
|
|
43
|
+
Reference answer: {{ reference }}
|
|
44
|
+
Candidate answer: {{ output }}
|
|
45
|
+
|
|
46
|
+
PASS if the candidate is true and agrees with the reference. FAIL otherwise.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The judge returns PASS or FAIL by default. Use `--scale 1-5` for a 1 to 5 score (a pass is 4 or more; change it with `--pass-threshold`).
|
|
50
|
+
|
|
51
|
+
## What you get
|
|
52
|
+
|
|
53
|
+
- **Self-agreement**: Fleiss' kappa across the K calls (ICC(1,1) for 1 to 5 scores).
|
|
54
|
+
- **Flip rate per item**: the share of an item's calls that disagree with its majority, as a histogram and as a list of the 20 least stable items, with one reason the judge gave for each side.
|
|
55
|
+
- **Single run versus majority vote**: the pass rate you would get if you reran the judge on the same items, with a 95% interval, for 1 call per item and for a majority vote of 3, 5, 7 and more.
|
|
56
|
+
- **Recommended K**: the smallest odd number of calls per item that keeps that interval within 2 points (`--target` to change).
|
|
57
|
+
- **Item sampling**: the interval when the items are resampled too, so you can see the part that more calls cannot fix.
|
|
58
|
+
- **Cost**: tokens and dollars per run, from the token counts the API returns.
|
|
59
|
+
|
|
60
|
+
Every call is kept in a DuckDB file (`jj.duckdb`), so you can query it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
duckdb jj.duckdb "select item_id, list(verdict) from verdicts group by 1 having count(distinct verdict) > 1"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Compare two runs
|
|
67
|
+
|
|
68
|
+
Run the same items twice with different settings, then compare per-item majority verdicts with an exact McNemar test:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
jj run items.jsonl -j judge.txt --model claude-sonnet-4-6 --api-version 2023-06-01 --temperature 0 --name old
|
|
72
|
+
jj run items.jsonl -j judge.txt --model claude-sonnet-4-6 --name new
|
|
73
|
+
jj compare old new
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It prints how many items went PASS to FAIL and FAIL to PASS, the change in pass rate with a 95% interval, the p-value, and the items whose verdict changed. Models without a built-in price show cost as unknown; add `--price-in` and `--price-out` (dollars per million tokens).
|
|
77
|
+
|
|
78
|
+
## Commands
|
|
79
|
+
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `jj run ITEMS --judge PROMPT` | Calls the judge K times per item and stores every verdict. `--k`, `--model`, `--concurrency`, `--max-cost`, `--dry-run`, `--limit`, `--scale`, `--effort`, `--thinking`, `--api-version`, `--temperature`, `--text` |
|
|
83
|
+
| `jj resume [RUN]` | Finishes a run that stopped at the cost cap and retries failed calls |
|
|
84
|
+
| `jj report [RUN]` | Writes the HTML report (`--out`) and the numbers (`--json`) |
|
|
85
|
+
| `jj compare RUN_A RUN_B` | Paired comparison of two runs on the same items |
|
|
86
|
+
| `jj runs` | Lists the runs in the database |
|
|
87
|
+
| `jj models` | Lists the models with a built-in price |
|
|
88
|
+
|
|
89
|
+
A run can be named with `--name` and referred to by name, by id, by an id prefix, or as `latest` (the default).
|
|
90
|
+
|
|
91
|
+
## How it works
|
|
92
|
+
|
|
93
|
+
1. **Calls.** Each item's prompt is sent K times, concurrently, with no sampling parameters, so the judge runs at the model's default. The default model is `claude-haiku-5-5` with the SDK's API version (`2026-10-21`). Replies use structured output (a JSON schema with a short reason and the verdict) so every reply can be read; `--text` parses free text instead, taking the last PASS/FAIL or score in the reply. Refusals, empty replies and errors are kept as failed calls, not verdicts. Calls go out repeat by repeat, so a run stopped by `--max-cost` still has whole repeats.
|
|
94
|
+
2. **Agreement.** Fleiss' kappa treats the K calls as raters. For scores, the one-way intraclass correlation fits, because repeated calls are interchangeable rather than distinct raters.
|
|
95
|
+
3. **Rerun intervals.** Each item's chance of a pass on one call is its observed share of passing calls. JudgeJitter simulates 4,000 reruns on the same items: it draws k calls per item, takes the majority, and averages. The middle 95% of those pass rates is the interval.
|
|
96
|
+
4. **Compare.** Items are paired by id. The exact McNemar test uses the items that changed between PASS and FAIL; the interval on the change comes from a paired bootstrap.
|
|
97
|
+
|
|
98
|
+
## Sample results
|
|
99
|
+
|
|
100
|
+
The sample judges 200 [TruthfulQA](https://github.com/sylinrl/TruthfulQA) questions (Apache-2.0). Each item has one candidate answer, picked at random from the dataset's correct or incorrect answers, and the dataset's best answer as the reference. The judge prompt is [examples/truthfulqa/judge.txt](examples/truthfulqa/judge.txt) and the items are built by [examples/truthfulqa/make_items.py](examples/truthfulqa/make_items.py).
|
|
101
|
+
|
|
102
|
+
<!-- sample-numbers:start -->
|
|
103
|
+
Measured 2026-11-01, run `20261101-175206-c137`: `claude-haiku-5-5` as judge, API version 2026-10-21, no sampling parameters (model default), structured JSON output, 200 TruthfulQA items x 5 calls.
|
|
104
|
+
|
|
105
|
+
| Measure | Result |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| Calls | 1,000 (0 failed or unreadable) |
|
|
108
|
+
| Fleiss' kappa (self-agreement) | 0.953 |
|
|
109
|
+
| Items with all 5 verdicts the same | 190 of 200 (95.0%) |
|
|
110
|
+
| Items that flipped at least once | 10 of 200 (5.0%) |
|
|
111
|
+
| Mean flip rate | 1.3% |
|
|
112
|
+
| The 5 single runs scored | 45.0%, 44.5%, 43.5%, 44.5%, 44.0% |
|
|
113
|
+
| Single run, 95% of reruns | 43.0% to 45.5% (2.5 points) |
|
|
114
|
+
| Majority of 5, 95% of reruns | 43.5% to 45.5% (2.0 points) |
|
|
115
|
+
| Calls per item for a 2-point interval | 5 |
|
|
116
|
+
| With item sampling (200 items), majority of 5 | 37.5% to 51.5% (14.0 points) |
|
|
117
|
+
| Tokens | 426,565 in, 98,489 out |
|
|
118
|
+
| Cost | $0.0919 |
|
|
119
|
+
|
|
120
|
+
Cost is the token counts the API returned, priced at list price. The raw numbers are in [sample/stats.json](sample/stats.json).
|
|
121
|
+
<!-- sample-numbers:end -->
|
|
122
|
+
|
|
123
|
+
## Known limits
|
|
124
|
+
|
|
125
|
+
- **Self-agreement is not accuracy.** A judge can agree with itself every time and still be wrong. To check a judge against human grades, use a tool such as [judgekeeper](https://pypi.org/project/judgekeeper/).
|
|
126
|
+
- **K=5 misses rare flips.** An item that flips once in 20 calls shows no flip in 5 calls about three times in four. Items with no observed flips are treated as stable, so the rerun intervals are a lower bound on the noise.
|
|
127
|
+
- **Kappa drops when nearly every verdict is the same** (the prevalence effect). Read it next to the flip rate.
|
|
128
|
+
- **Majority vote needs an odd K.** With an even K, the report votes with K-1 calls.
|
|
129
|
+
- **Claude only** in v0.1. The dry-run cost is a rough guess; the cost after a run uses the API's token counts and list prices checked on 1 November 2026.
|
|
130
|
+
- **Structured output adds a schema** to each request. Use `--text` to judge with your prompt exactly as it is.
|
|
131
|
+
- **promptfoo files**: the `results.results` and `results.outputs` shapes are read. Open an issue with a sample if yours is different.
|
|
132
|
+
- **The `jj` command name** is also used by Jujutsu. If you have both, call `judgejitter` instead; it is the same program.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install -e ".[dev]"
|
|
138
|
+
pytest
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The statistics are tested against known values: the worked example on Wikipedia's Fleiss' kappa page, the Shrout and Fleiss (1979) ICC table, and hand-worked kappa and exact McNemar cases.
|
|
142
|
+
|
|
143
|
+
## Licence
|
|
144
|
+
|
|
145
|
+
MIT. See [LICENSE](LICENSE).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
Built by [Ishan Yash](https://ishanyash.com).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "judgejitter"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Re-run your LLM-as-judge eval K times and see how stable the verdicts are, which items flip, and how many repeats you need."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Ishan Yash" }]
|
|
14
|
+
keywords = ["llm", "llm-as-judge", "evals", "evaluation", "claude", "anthropic", "reliability", "kappa"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Software Development :: Testing",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"anthropic>=2.0,<3",
|
|
30
|
+
"duckdb>=1.1",
|
|
31
|
+
"jinja2>=3.1",
|
|
32
|
+
"numpy>=1.26",
|
|
33
|
+
"scipy>=1.11",
|
|
34
|
+
"typer>=0.12",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
jj = "judgejitter.cli:main"
|
|
42
|
+
judgejitter = "judgejitter.cli:main"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/ishanyash/judgejitter"
|
|
46
|
+
Source = "https://github.com/ishanyash/judgejitter"
|
|
47
|
+
Issues = "https://github.com/ishanyash/judgejitter/issues"
|
|
48
|
+
"Sample report" = "https://ishanyash.github.io/judgejitter/"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["src/judgejitter"]
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
include = ["src", "tests", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
line-length = 120
|
|
61
|
+
target-version = "py311"
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint]
|
|
64
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
65
|
+
ignore = ["E501", "B008", "UP042"]
|