Skip to content

Commit 0cc6ba5

Browse files
authored
Merge pull request #24 from avalyset/feat/cross-judge-experiment
feat: CrossJudgeExperiment for cross-version judge stability analysis
2 parents 9ec7ada + 3bf6533 commit 0cc6ba5

4 files changed

Lines changed: 964 additions & 0 deletions

File tree

examples/cross_judge_example.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
Cross-judge stability analysis example.
3+
4+
Demonstrates CrossJudgeExperiment: running the same subject model through two
5+
judge versions to measure whether the judge upgrade shifts severity ratings.
6+
7+
Motivated by the finding that judge-model version can materially shift modal
8+
severity on identical subject responses — including safety-relevant scenarios —
9+
without any change to the subject model itself.
10+
11+
Usage
12+
-----
13+
python examples/cross_judge_example.py
14+
15+
Set ANTHROPIC_API_KEY before running. Results are cached under
16+
`cross_judge_results/` so the run is resumable if interrupted.
17+
"""
18+
19+
import asyncio
20+
import os
21+
22+
from simpleaudit import CrossJudgeExperiment, compare_judges
23+
24+
25+
SUBJECT_MODELS = [
26+
{
27+
"model": "claude-haiku-4-5-20251001",
28+
"provider": "anthropic",
29+
"label": "haiku-4.5",
30+
},
31+
]
32+
33+
JUDGE_MODELS = [
34+
{"model": "claude-opus-4-7", "provider": "anthropic"},
35+
{"model": "claude-opus-4-8", "provider": "anthropic"},
36+
]
37+
38+
39+
async def main() -> None:
40+
if not os.environ.get("ANTHROPIC_API_KEY"):
41+
raise EnvironmentError("ANTHROPIC_API_KEY is not set.")
42+
43+
exp = CrossJudgeExperiment(
44+
models=SUBJECT_MODELS,
45+
judge_models=JUDGE_MODELS,
46+
n_repetitions=3,
47+
save_dir="cross_judge_results",
48+
show_progress=True,
49+
)
50+
51+
print(f"Judges: {exp.judge_labels}")
52+
print("Running cross-judge experiment (results cached under cross_judge_results/)...\n")
53+
54+
results = await exp.run_async(
55+
scenarios="nav_aap",
56+
language="Norwegian",
57+
max_turns=3,
58+
)
59+
60+
# --- Score summary ---------------------------------------------------------
61+
print("=== Score summary (mean across 3 runs) ===")
62+
summary = results.score_summary()
63+
for subject, per_judge in summary.items():
64+
print(f"\n Subject: {subject}")
65+
for judge_label, stats in per_judge.items():
66+
print(
67+
f" {judge_label:20s} mean={stats['mean']:.1f} "
68+
f"std={stats['std']:.2f} cv={stats['cv']:.1%} "
69+
f"n={stats['n_runs']}"
70+
)
71+
72+
# --- Severity shifts -------------------------------------------------------
73+
print("\n=== Cross-judge severity shifts (haiku-4.5 / nav_aap) ===")
74+
shifts = results.severity_shifts("haiku-4.5")
75+
shifted = [s for s in shifts if s["shifted"]]
76+
print(f" {len(shifted)} of {len(shifts)} scenarios shifted modal severity.\n")
77+
for entry in shifted:
78+
direction = entry.get("direction", 0)
79+
arrow = "→ stricter" if direction > 0 else "→ more lenient"
80+
judges = list(entry["modals"].keys())
81+
modal_a = entry["modals"][judges[0]]
82+
modal_b = entry["modals"][judges[1]]
83+
print(
84+
f" {entry['scenario']:<45s} "
85+
f"{judges[0]}: {modal_a:<8s} {judges[1]}: {modal_b:<8s} {arrow}"
86+
)
87+
88+
# --- compare_judges utility (post-hoc comparison) -------------------------
89+
print("\n=== compare_judges utility ===")
90+
cmp = compare_judges(
91+
results["opus-4-7"],
92+
results["opus-4-8"],
93+
subject_label="haiku-4.5",
94+
label_a="opus-4-7",
95+
label_b="opus-4-8",
96+
)
97+
print(
98+
f" score_delta (4.8 − 4.7): {cmp['score_delta']:+.2f} "
99+
f"cv_delta: {cmp['cv_delta']:+.4f} "
100+
f"n_shifted: {cmp['n_shifted']}/{cmp['n_total']}"
101+
)
102+
103+
104+
if __name__ == "__main__":
105+
asyncio.run(main())

simpleaudit/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from .judges import get_judge, list_judge_configs
3030
from .experiment import AuditExperiment
3131
from .repeated_results import RepeatedExperimentResults, ModelStabilityReport
32+
from .cross_judge import CrossJudgeExperiment, CrossJudgeResults, compare_judges
3233

3334
__all__ = [
3435
"ModelAuditor",
@@ -41,5 +42,8 @@
4142
"AuditExperiment",
4243
"RepeatedExperimentResults",
4344
"ModelStabilityReport",
45+
"CrossJudgeExperiment",
46+
"CrossJudgeResults",
47+
"compare_judges",
4448
]
4549

0 commit comments

Comments
 (0)