reliability-lab 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.
- reliability_lab-0.1.0/.gitattributes +2 -0
- reliability_lab-0.1.0/.github/workflows/ci.yml +50 -0
- reliability_lab-0.1.0/.github/workflows/release.yml +65 -0
- reliability_lab-0.1.0/.gitignore +28 -0
- reliability_lab-0.1.0/.python-version +1 -0
- reliability_lab-0.1.0/CHANGELOG.md +41 -0
- reliability_lab-0.1.0/LICENSE +28 -0
- reliability_lab-0.1.0/PKG-INFO +178 -0
- reliability_lab-0.1.0/README.md +148 -0
- reliability_lab-0.1.0/docs/design/framing-v2.md +323 -0
- reliability_lab-0.1.0/docs/releasing.md +32 -0
- reliability_lab-0.1.0/examples/llm_judge.py +87 -0
- reliability_lab-0.1.0/examples/quickstart.py +58 -0
- reliability_lab-0.1.0/examples/splits.py +101 -0
- reliability_lab-0.1.0/pyproject.toml +107 -0
- reliability_lab-0.1.0/src/reliability_lab/__init__.py +68 -0
- reliability_lab-0.1.0/src/reliability_lab/_version.py +1 -0
- reliability_lab-0.1.0/src/reliability_lab/alpha.py +290 -0
- reliability_lab-0.1.0/src/reliability_lab/data.py +294 -0
- reliability_lab-0.1.0/src/reliability_lab/design.py +308 -0
- reliability_lab-0.1.0/src/reliability_lab/exceptions.py +47 -0
- reliability_lab-0.1.0/src/reliability_lab/inference.py +724 -0
- reliability_lab-0.1.0/src/reliability_lab/llm.py +560 -0
- reliability_lab-0.1.0/src/reliability_lab/py.typed +0 -0
- reliability_lab-0.1.0/src/reliability_lab/report.py +779 -0
- reliability_lab-0.1.0/src/reliability_lab/split.py +338 -0
- reliability_lab-0.1.0/src/reliability_lab/study.py +1034 -0
- reliability_lab-0.1.0/tests/fixtures/__init__.py +0 -0
- reliability_lab-0.1.0/tests/fixtures/krippendorff2011.py +52 -0
- reliability_lab-0.1.0/tests/fixtures/llm_logs.py +181 -0
- reliability_lab-0.1.0/tests/helpers.py +57 -0
- reliability_lab-0.1.0/tests/naive_bootstrap.py +90 -0
- reliability_lab-0.1.0/tests/reference.py +73 -0
- reliability_lab-0.1.0/tests/test_alpha.py +146 -0
- reliability_lab-0.1.0/tests/test_bca.py +83 -0
- reliability_lab-0.1.0/tests/test_compare.py +136 -0
- reliability_lab-0.1.0/tests/test_data.py +106 -0
- reliability_lab-0.1.0/tests/test_design.py +218 -0
- reliability_lab-0.1.0/tests/test_inference_units.py +227 -0
- reliability_lab-0.1.0/tests/test_kalpha.py +133 -0
- reliability_lab-0.1.0/tests/test_llm_end_to_end.py +281 -0
- reliability_lab-0.1.0/tests/test_parity.py +95 -0
- reliability_lab-0.1.0/tests/test_properties.py +130 -0
- reliability_lab-0.1.0/tests/test_report.py +75 -0
- reliability_lab-0.1.0/tests/test_report_io.py +172 -0
- reliability_lab-0.1.0/tests/test_retries.py +123 -0
- reliability_lab-0.1.0/tests/test_smoke.py +6 -0
- reliability_lab-0.1.0/tests/test_split.py +361 -0
- reliability_lab-0.1.0/tests/test_status_policies.py +200 -0
- reliability_lab-0.1.0/tests/test_trace.py +292 -0
- reliability_lab-0.1.0/uv.lock +1473 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: ${{ matrix.os }} / py${{ matrix.python-version }}
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
16
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Install
|
|
23
|
+
run: uv sync --all-groups --locked
|
|
24
|
+
- name: Lint
|
|
25
|
+
run: uv run ruff check
|
|
26
|
+
- name: Format
|
|
27
|
+
run: uv run ruff format --check
|
|
28
|
+
- name: Type-check
|
|
29
|
+
run: uv run mypy
|
|
30
|
+
- name: Test
|
|
31
|
+
run: uv run pytest
|
|
32
|
+
|
|
33
|
+
# irrCAC pins scipy==1.12.0, below this package's floor (1.15, for the bootstrap's
|
|
34
|
+
# rng keyword). Its parity test only needs the point estimates, so it runs in a
|
|
35
|
+
# throwaway environment with the package installed without its dependencies.
|
|
36
|
+
parity-irrcac:
|
|
37
|
+
name: irrCAC parity (isolated)
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v5
|
|
41
|
+
- uses: astral-sh/setup-uv@v6
|
|
42
|
+
- name: Build the isolated environment
|
|
43
|
+
run: |
|
|
44
|
+
uv venv .venv-irrcac --python 3.12
|
|
45
|
+
uv pip install --python .venv-irrcac irrcac pytest hypothesis
|
|
46
|
+
uv pip install --python .venv-irrcac --no-deps .
|
|
47
|
+
- name: Run irrCAC parity
|
|
48
|
+
run: >-
|
|
49
|
+
.venv-irrcac/bin/python -m pytest tests/test_parity.py -m parity -k irrcac
|
|
50
|
+
-p no:cacheprovider
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes on a version tag (v0.1.0a1, v0.1.0, ...) with PyPI trusted publishing:
|
|
4
|
+
# no API tokens are stored. See docs/releasing.md for the one-time setup.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build and check the distributions
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v5
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
- name: Check the tag matches the package version
|
|
17
|
+
run: |
|
|
18
|
+
version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/reliability_lab/_version.py)
|
|
19
|
+
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
|
|
20
|
+
echo "tag $GITHUB_REF_NAME does not match package version $version" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
- name: Build
|
|
24
|
+
run: uv build
|
|
25
|
+
- name: Check metadata
|
|
26
|
+
run: uvx twine check --strict dist/*
|
|
27
|
+
- uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: dist
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish-testpypi:
|
|
33
|
+
name: Publish to TestPyPI
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: testpypi
|
|
38
|
+
url: https://test.pypi.org/p/reliability-lab
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
47
|
+
with:
|
|
48
|
+
repository-url: https://test.pypi.org/legacy/
|
|
49
|
+
|
|
50
|
+
publish-pypi:
|
|
51
|
+
name: Publish to PyPI
|
|
52
|
+
needs: publish-testpypi
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
# Give this environment a required reviewer to approve each release by hand.
|
|
55
|
+
environment:
|
|
56
|
+
name: pypi
|
|
57
|
+
url: https://pypi.org/p/reliability-lab
|
|
58
|
+
permissions:
|
|
59
|
+
id-token: write
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.mypy_cache/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.hypothesis/
|
|
8
|
+
|
|
9
|
+
# Environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Build artefacts
|
|
14
|
+
build/
|
|
15
|
+
dist/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
|
|
18
|
+
# Coverage
|
|
19
|
+
.coverage
|
|
20
|
+
.coverage.*
|
|
21
|
+
htmlcov/
|
|
22
|
+
coverage.xml
|
|
23
|
+
|
|
24
|
+
# Editors / OS
|
|
25
|
+
.vscode/
|
|
26
|
+
.idea/
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
LLM judge workflows.
|
|
8
|
+
|
|
9
|
+
- `TraceSidecar`: one row per judge configuration (`condition_id`). It records provider, prompt hash, rubric and parser versions, temperature, seed, determinism, candidate order, request time and retry policy.
|
|
10
|
+
- Each study checks its trace against its design. Configurations that differ in more than the design compares raise a `TraceWarning`; missing conditions or fields become provenance gaps. Comparisons report the model and trace fields that changed between the arms (`configuration_diff`).
|
|
11
|
+
- The single-configuration designs (repeatability, prompt robustness, order robustness) can hold several judge configurations for `compare()`. Each configuration is checked on its own, and `alpha()` refuses to pool them.
|
|
12
|
+
- `order_robustness` design for pairwise judges whose candidates are shown in swapped orders.
|
|
13
|
+
- Diagnostics on every result (`AlphaResult.diagnostics`):
|
|
14
|
+
- `coder_agreement` (pairwise exact agreement);
|
|
15
|
+
- `run_agreement` (repeatability);
|
|
16
|
+
- `order_swap` (reversal rate, and position bias with a Wilson interval).
|
|
17
|
+
- Retries: a new `attempt` field in `RatingFrame.from_long`. Retried calls must be declared with `retries="last"` or `retries="first_observed"`. Attempts are grouped by call (unit, rater, model, condition, run), so repeated runs are never treated as retries. Coverage reports attempt-level and final error rates.
|
|
18
|
+
- Provenance records the trace rows of the conditions in use and the retries declaration.
|
|
19
|
+
|
|
20
|
+
## 0.1.0a1
|
|
21
|
+
|
|
22
|
+
First public release.
|
|
23
|
+
|
|
24
|
+
- Project scaffold: packaging, BSD-3-Clause licence, CI on Linux, Windows and macOS.
|
|
25
|
+
- Core engine: `RatingFrame` long-form schema with rating status, `CoderSpec` and design labels, Krippendorff's alpha (nominal, ordinal, interval, ratio, custom distances) with white-box diagnostics, and missing-data policies (`as_missing`, `as_category`, `sensitivity`).
|
|
26
|
+
- Bootstrap inference: `AlphaResult.bootstrap(scheme=...)` with a required scheme.
|
|
27
|
+
- `units` resamples units, or groups, optionally within strata, and recomputes D_e per replicate.
|
|
28
|
+
- `kalpha` follows Krippendorff's algorithm, resampling pairs of values with D_e fixed.
|
|
29
|
+
- Percentile intervals are the default; BCa is opt-in and flagged experimental.
|
|
30
|
+
- Failed replicates are counted and reported, the seed is always recorded, and an optional `alpha_min` reports how many replicates fall below it.
|
|
31
|
+
- Paired comparisons: `ReliabilityStudy.compare(a=..., b=...)` places the interval on Δα = α_b − α_a, using the same resampled units for both configurations.
|
|
32
|
+
- Group-safe splits:
|
|
33
|
+
- `ReliabilityStudy.holdout()` and `cv()` take any scikit-learn style splitter. The splitter sees one sample per group, so groups and repeated LLM calls never cross partitions.
|
|
34
|
+
- When a group's units span several strata, `group_stratum` must be given.
|
|
35
|
+
- `SplitManifest` saves partition ids, the splitter, realised sizes and fingerprints; `apply_manifest()` reapplies them to a later log.
|
|
36
|
+
- `assert_no_leakage()` checks that few-shot examples come from the dev partition.
|
|
37
|
+
- Reports:
|
|
38
|
+
- versioned JSON (`to_json`) with provenance for every result type: software versions, ratings fingerprint, declarations, split and known gaps;
|
|
39
|
+
- tidy tables (`tables`) and Parquet output (`to_parquet`, with the `parquet` extra).
|
|
40
|
+
- Tag-triggered release workflow using PyPI trusted publishing.
|
|
41
|
+
- New exceptions and warnings: `BootstrapWarning`, `LeakageError`. The scipy floor is now 1.15.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Sathapana Chawananon
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: reliability-lab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Study-design library for the reliability of LLM and human judgments, built on Krippendorff's alpha.
|
|
5
|
+
Project-URL: Repository, https://github.com/Sathapana/reliability-lab
|
|
6
|
+
Project-URL: Issues, https://github.com/Sathapana/reliability-lab/issues
|
|
7
|
+
Author: Sathapana Chawananon
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: alpha,annotation,evaluation,inter-rater reliability,krippendorff,llm-as-a-judge
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: numpy>=1.26
|
|
23
|
+
Requires-Dist: pandas>=2.1
|
|
24
|
+
Requires-Dist: scipy>=1.15
|
|
25
|
+
Provides-Extra: parquet
|
|
26
|
+
Requires-Dist: pyarrow>=15; extra == 'parquet'
|
|
27
|
+
Provides-Extra: sklearn
|
|
28
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'sklearn'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# reliability-lab
|
|
32
|
+
|
|
33
|
+
**Status: alpha (0.1.0).** The API may still change between releases.
|
|
34
|
+
|
|
35
|
+
reliability-lab is a study-design library for measuring the reliability of LLM and human judgments. It is built on Krippendorff's alpha. Alpha implementations already exist in Python. This package adds the workflow around the coefficient:
|
|
36
|
+
|
|
37
|
+
- a validated long-form ratings schema in which every rating has an explicit status (`observed`, `abstained`, `inapplicable`, `parse_error`, `provider_error`);
|
|
38
|
+
- declared measurement semantics: level, value domain, coder definition and design label;
|
|
39
|
+
- white-box output: observed and expected disagreement, coincidence matrices and per-unit contributions;
|
|
40
|
+
- explicit missing-data policies, because LLM failures are rarely unrelated to the item;
|
|
41
|
+
- bootstrap intervals by a scheme you name: whole units or groups resampled (`scheme="units"`), or Krippendorff's KALPHA algorithm (`scheme="kalpha"`);
|
|
42
|
+
- paired comparisons of two configurations on the same units, with the interval on the difference;
|
|
43
|
+
- group-safe train/test splits that work with any scikit-learn splitter, with a saved manifest that reproduces the split;
|
|
44
|
+
- versioned JSON and Parquet reports with provenance;
|
|
45
|
+
- LLM judge workflows: a trace sidecar of judge configurations, declared retry handling, and evidence specific to each design (run agreement, order swaps and position bias).
|
|
46
|
+
|
|
47
|
+
Mixed human-LLM panels and coverage simulations for the bootstrap schemes are next on the roadmap ([design document](docs/design/framing-v2.md)).
|
|
48
|
+
|
|
49
|
+
> **Reliability is not validity.** Coders can agree consistently and still be wrong in the same way, for example because of a flawed rubric or a bias they share. When results come from different designs (repeatability, inter-model agreement, human panels), they are reported as different quantities and are never pooled.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install "reliability-lab[sklearn,parquet]"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The extras are optional. `sklearn` provides the splitters, although any object with a scikit-learn style `split` method works; `parquet` enables `to_parquet`. For development, run `uv sync` and then `uv run pytest`.
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import pandas as pd
|
|
63
|
+
from reliability_lab import CoderSpec, RatingFrame, ReliabilityStudy
|
|
64
|
+
|
|
65
|
+
df = pd.DataFrame(
|
|
66
|
+
{
|
|
67
|
+
"item": ["u1", "u1", "u2", "u2", "u3", "u3"],
|
|
68
|
+
"annotator": ["ann", "bob", "ann", "bob", "ann", "bob"],
|
|
69
|
+
"label": ["pos", "pos", "neg", "pos", "neg", "neg"],
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
ratings = RatingFrame.from_long(df, unit="item", rater="annotator", value="label")
|
|
74
|
+
study = ReliabilityStudy(
|
|
75
|
+
ratings,
|
|
76
|
+
measurement="nominal",
|
|
77
|
+
values=["neg", "pos"],
|
|
78
|
+
coders=CoderSpec(by="rater_id"),
|
|
79
|
+
design="human_panel",
|
|
80
|
+
)
|
|
81
|
+
result = study.alpha()
|
|
82
|
+
print(result.summary())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Intervals and comparisons
|
|
86
|
+
|
|
87
|
+
Every interval names its scheme, because the two schemes answer different questions. `scheme` is a required argument.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
boot = result.bootstrap(scheme="units", n_resamples=2000, random_state=7)
|
|
91
|
+
print(boot.summary()) # 95% percentile intervals for every missing-data policy
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `scheme="units"` resamples whole units, and whole groups when a `group` column is declared. It can also resample within declared strata (`strata=True`), and it recomputes the expected disagreement in every replicate. `method="bca"` is available as an experimental option.
|
|
95
|
+
- `scheme="kalpha"` reproduces Krippendorff's KALPHA algorithm. It resamples pairs of values and holds the expected disagreement fixed.
|
|
96
|
+
|
|
97
|
+
To compare two configurations of a judge, for example two prompts, select each one's rows. Both must cover the same units. Every replicate resamples the same units for both, and the interval is on the difference:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
diff = study.compare(
|
|
101
|
+
a={"condition_id": "prompt_v1"},
|
|
102
|
+
b={"condition_id": "prompt_v2"},
|
|
103
|
+
n_resamples=2000,
|
|
104
|
+
random_state=7,
|
|
105
|
+
)
|
|
106
|
+
print(diff.summary()) # alpha_b - alpha_a with a paired interval
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Splits and reports
|
|
110
|
+
|
|
111
|
+
Teams tune the judge (prompt, few-shot examples, rubric) and should report reliability on units the tuning never saw. So split before tuning, freeze the judge, then estimate on the test partition.
|
|
112
|
+
|
|
113
|
+
When groups are declared, the splitter sees one sample per group (a conversation, say), so groups and repeated LLM calls never cross partitions:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from sklearn.model_selection import StratifiedGroupKFold
|
|
117
|
+
|
|
118
|
+
dev, test = study.holdout(
|
|
119
|
+
StratifiedGroupKFold(n_splits=4, shuffle=True, random_state=7),
|
|
120
|
+
group_stratum="majority", # needed only when a group's units span several strata
|
|
121
|
+
)
|
|
122
|
+
test.save_manifest("split.json") # the ids of every partition, not just the seed
|
|
123
|
+
|
|
124
|
+
# ... tune and freeze the judge on dev ...
|
|
125
|
+
|
|
126
|
+
result = test.alpha()
|
|
127
|
+
result.to_json("alpha_test.json") # strict JSON with provenance
|
|
128
|
+
result.to_parquet("alpha_test/") # tidy tables
|
|
129
|
+
|
|
130
|
+
dev2, test2 = later_study.apply_manifest("split.json") # same units, new judge runs
|
|
131
|
+
test.manifest.assert_no_leakage(few_shot_units) # raises LeakageError
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See [`examples/splits.py`](examples/splits.py) for a full run. Releases are described in [docs/releasing.md](docs/releasing.md).
|
|
135
|
+
|
|
136
|
+
## LLM judge workflows
|
|
137
|
+
|
|
138
|
+
A `condition_id` names a frozen judge configuration. A trace sidecar records what each configuration was: provider, prompt hash, rubric and parser versions, temperature, seed, and candidate order for pairwise judges. With it, a study checks that its coders differ only in what the design compares. For example, if prompt-robustness conditions also differ in temperature, it raises a `TraceWarning`. Whatever the trace leaves out is listed among the provenance gaps.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from reliability_lab import TraceSidecar
|
|
142
|
+
|
|
143
|
+
trace = TraceSidecar.from_frame(
|
|
144
|
+
configs,
|
|
145
|
+
condition="condition_id",
|
|
146
|
+
prompt_hash="prompt_sha",
|
|
147
|
+
temperature="temperature",
|
|
148
|
+
candidate_order="order", # "A,B" / "B,A"
|
|
149
|
+
)
|
|
150
|
+
study = ReliabilityStudy(
|
|
151
|
+
ratings, # RatingFrame.from_long(..., attempt="attempt")
|
|
152
|
+
measurement="nominal",
|
|
153
|
+
values=["A", "tie", "B"], # labels name the winning candidate, not its position
|
|
154
|
+
coders="condition_id",
|
|
155
|
+
design="order_robustness",
|
|
156
|
+
trace=trace,
|
|
157
|
+
retries="last", # required when calls were retried
|
|
158
|
+
)
|
|
159
|
+
result = study.alpha()
|
|
160
|
+
result.diagnostics["order_swap"].values # reversal rate, P(first shown wins) with a Wilson interval
|
|
161
|
+
result.trace_findings # confounds and caveats from the trace
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Design | Coders are | Evidence beside alpha |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `repeatability` | repeated runs of one configuration (`run_id`) | run agreement: units where every run agrees |
|
|
167
|
+
| `prompt_robustness` | prompt conditions of one model | pairwise agreement between conditions |
|
|
168
|
+
| `inter_model` | different models | pairwise agreement; family and configuration checks |
|
|
169
|
+
| `order_robustness` | candidate orders of one pairwise judge | reversal rate and position bias |
|
|
170
|
+
| configuration change | two configurations, via `study.compare(a=..., b=...)` | paired interval on the difference; the model and trace fields that changed |
|
|
171
|
+
|
|
172
|
+
The single-configuration designs (repeatability, prompt robustness, order robustness) can hold several configurations to compare, such as an old and a new judge. Each configuration is checked on its own. `alpha()` refuses to pool them; `study.compare(a={"condition_id": "old"}, b={"condition_id": "new"})` reports each one's alpha and diagnostics, with a paired interval on the difference.
|
|
173
|
+
|
|
174
|
+
Retried calls are never resolved silently. Number the attempts with an `attempt` column, then declare `retries="last"` or `retries="first_observed"`. Coverage then reports error rates both per attempt and for the attempts that count. See [`examples/llm_judge.py`](examples/llm_judge.py).
|
|
175
|
+
|
|
176
|
+
## Licence
|
|
177
|
+
|
|
178
|
+
BSD-3-Clause. Alpha is implemented independently from Krippendorff's papers. The GPL-licensed `krippendorff` package is used only in the test suite, as a parity check.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# reliability-lab
|
|
2
|
+
|
|
3
|
+
**Status: alpha (0.1.0).** The API may still change between releases.
|
|
4
|
+
|
|
5
|
+
reliability-lab is a study-design library for measuring the reliability of LLM and human judgments. It is built on Krippendorff's alpha. Alpha implementations already exist in Python. This package adds the workflow around the coefficient:
|
|
6
|
+
|
|
7
|
+
- a validated long-form ratings schema in which every rating has an explicit status (`observed`, `abstained`, `inapplicable`, `parse_error`, `provider_error`);
|
|
8
|
+
- declared measurement semantics: level, value domain, coder definition and design label;
|
|
9
|
+
- white-box output: observed and expected disagreement, coincidence matrices and per-unit contributions;
|
|
10
|
+
- explicit missing-data policies, because LLM failures are rarely unrelated to the item;
|
|
11
|
+
- bootstrap intervals by a scheme you name: whole units or groups resampled (`scheme="units"`), or Krippendorff's KALPHA algorithm (`scheme="kalpha"`);
|
|
12
|
+
- paired comparisons of two configurations on the same units, with the interval on the difference;
|
|
13
|
+
- group-safe train/test splits that work with any scikit-learn splitter, with a saved manifest that reproduces the split;
|
|
14
|
+
- versioned JSON and Parquet reports with provenance;
|
|
15
|
+
- LLM judge workflows: a trace sidecar of judge configurations, declared retry handling, and evidence specific to each design (run agreement, order swaps and position bias).
|
|
16
|
+
|
|
17
|
+
Mixed human-LLM panels and coverage simulations for the bootstrap schemes are next on the roadmap ([design document](docs/design/framing-v2.md)).
|
|
18
|
+
|
|
19
|
+
> **Reliability is not validity.** Coders can agree consistently and still be wrong in the same way, for example because of a flawed rubric or a bias they share. When results come from different designs (repeatability, inter-model agreement, human panels), they are reported as different quantities and are never pooled.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "reliability-lab[sklearn,parquet]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The extras are optional. `sklearn` provides the splitters, although any object with a scikit-learn style `split` method works; `parquet` enables `to_parquet`. For development, run `uv sync` and then `uv run pytest`.
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pandas as pd
|
|
33
|
+
from reliability_lab import CoderSpec, RatingFrame, ReliabilityStudy
|
|
34
|
+
|
|
35
|
+
df = pd.DataFrame(
|
|
36
|
+
{
|
|
37
|
+
"item": ["u1", "u1", "u2", "u2", "u3", "u3"],
|
|
38
|
+
"annotator": ["ann", "bob", "ann", "bob", "ann", "bob"],
|
|
39
|
+
"label": ["pos", "pos", "neg", "pos", "neg", "neg"],
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
ratings = RatingFrame.from_long(df, unit="item", rater="annotator", value="label")
|
|
44
|
+
study = ReliabilityStudy(
|
|
45
|
+
ratings,
|
|
46
|
+
measurement="nominal",
|
|
47
|
+
values=["neg", "pos"],
|
|
48
|
+
coders=CoderSpec(by="rater_id"),
|
|
49
|
+
design="human_panel",
|
|
50
|
+
)
|
|
51
|
+
result = study.alpha()
|
|
52
|
+
print(result.summary())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Intervals and comparisons
|
|
56
|
+
|
|
57
|
+
Every interval names its scheme, because the two schemes answer different questions. `scheme` is a required argument.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
boot = result.bootstrap(scheme="units", n_resamples=2000, random_state=7)
|
|
61
|
+
print(boot.summary()) # 95% percentile intervals for every missing-data policy
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- `scheme="units"` resamples whole units, and whole groups when a `group` column is declared. It can also resample within declared strata (`strata=True`), and it recomputes the expected disagreement in every replicate. `method="bca"` is available as an experimental option.
|
|
65
|
+
- `scheme="kalpha"` reproduces Krippendorff's KALPHA algorithm. It resamples pairs of values and holds the expected disagreement fixed.
|
|
66
|
+
|
|
67
|
+
To compare two configurations of a judge, for example two prompts, select each one's rows. Both must cover the same units. Every replicate resamples the same units for both, and the interval is on the difference:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
diff = study.compare(
|
|
71
|
+
a={"condition_id": "prompt_v1"},
|
|
72
|
+
b={"condition_id": "prompt_v2"},
|
|
73
|
+
n_resamples=2000,
|
|
74
|
+
random_state=7,
|
|
75
|
+
)
|
|
76
|
+
print(diff.summary()) # alpha_b - alpha_a with a paired interval
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Splits and reports
|
|
80
|
+
|
|
81
|
+
Teams tune the judge (prompt, few-shot examples, rubric) and should report reliability on units the tuning never saw. So split before tuning, freeze the judge, then estimate on the test partition.
|
|
82
|
+
|
|
83
|
+
When groups are declared, the splitter sees one sample per group (a conversation, say), so groups and repeated LLM calls never cross partitions:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from sklearn.model_selection import StratifiedGroupKFold
|
|
87
|
+
|
|
88
|
+
dev, test = study.holdout(
|
|
89
|
+
StratifiedGroupKFold(n_splits=4, shuffle=True, random_state=7),
|
|
90
|
+
group_stratum="majority", # needed only when a group's units span several strata
|
|
91
|
+
)
|
|
92
|
+
test.save_manifest("split.json") # the ids of every partition, not just the seed
|
|
93
|
+
|
|
94
|
+
# ... tune and freeze the judge on dev ...
|
|
95
|
+
|
|
96
|
+
result = test.alpha()
|
|
97
|
+
result.to_json("alpha_test.json") # strict JSON with provenance
|
|
98
|
+
result.to_parquet("alpha_test/") # tidy tables
|
|
99
|
+
|
|
100
|
+
dev2, test2 = later_study.apply_manifest("split.json") # same units, new judge runs
|
|
101
|
+
test.manifest.assert_no_leakage(few_shot_units) # raises LeakageError
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
See [`examples/splits.py`](examples/splits.py) for a full run. Releases are described in [docs/releasing.md](docs/releasing.md).
|
|
105
|
+
|
|
106
|
+
## LLM judge workflows
|
|
107
|
+
|
|
108
|
+
A `condition_id` names a frozen judge configuration. A trace sidecar records what each configuration was: provider, prompt hash, rubric and parser versions, temperature, seed, and candidate order for pairwise judges. With it, a study checks that its coders differ only in what the design compares. For example, if prompt-robustness conditions also differ in temperature, it raises a `TraceWarning`. Whatever the trace leaves out is listed among the provenance gaps.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from reliability_lab import TraceSidecar
|
|
112
|
+
|
|
113
|
+
trace = TraceSidecar.from_frame(
|
|
114
|
+
configs,
|
|
115
|
+
condition="condition_id",
|
|
116
|
+
prompt_hash="prompt_sha",
|
|
117
|
+
temperature="temperature",
|
|
118
|
+
candidate_order="order", # "A,B" / "B,A"
|
|
119
|
+
)
|
|
120
|
+
study = ReliabilityStudy(
|
|
121
|
+
ratings, # RatingFrame.from_long(..., attempt="attempt")
|
|
122
|
+
measurement="nominal",
|
|
123
|
+
values=["A", "tie", "B"], # labels name the winning candidate, not its position
|
|
124
|
+
coders="condition_id",
|
|
125
|
+
design="order_robustness",
|
|
126
|
+
trace=trace,
|
|
127
|
+
retries="last", # required when calls were retried
|
|
128
|
+
)
|
|
129
|
+
result = study.alpha()
|
|
130
|
+
result.diagnostics["order_swap"].values # reversal rate, P(first shown wins) with a Wilson interval
|
|
131
|
+
result.trace_findings # confounds and caveats from the trace
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Design | Coders are | Evidence beside alpha |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `repeatability` | repeated runs of one configuration (`run_id`) | run agreement: units where every run agrees |
|
|
137
|
+
| `prompt_robustness` | prompt conditions of one model | pairwise agreement between conditions |
|
|
138
|
+
| `inter_model` | different models | pairwise agreement; family and configuration checks |
|
|
139
|
+
| `order_robustness` | candidate orders of one pairwise judge | reversal rate and position bias |
|
|
140
|
+
| configuration change | two configurations, via `study.compare(a=..., b=...)` | paired interval on the difference; the model and trace fields that changed |
|
|
141
|
+
|
|
142
|
+
The single-configuration designs (repeatability, prompt robustness, order robustness) can hold several configurations to compare, such as an old and a new judge. Each configuration is checked on its own. `alpha()` refuses to pool them; `study.compare(a={"condition_id": "old"}, b={"condition_id": "new"})` reports each one's alpha and diagnostics, with a paired interval on the difference.
|
|
143
|
+
|
|
144
|
+
Retried calls are never resolved silently. Number the attempts with an `attempt` column, then declare `retries="last"` or `retries="first_observed"`. Coverage then reports error rates both per attempt and for the attempts that count. See [`examples/llm_judge.py`](examples/llm_judge.py).
|
|
145
|
+
|
|
146
|
+
## Licence
|
|
147
|
+
|
|
148
|
+
BSD-3-Clause. Alpha is implemented independently from Krippendorff's papers. The GPL-licensed `krippendorff` package is used only in the test suite, as a parity check.
|