Causal Judge Evaluation - Unbiased LLM evaluation framework
Project description
CJE - Causal Judge Evaluation
Your LLM judge scores are noisy and nebulous. CJE calibrates them to what actually matters.
We ran 16,000+ tests on Chatbot Arena data. Without calibration, 95% confidence intervals captured the true value 0% of the time. With CJE: 99% ranking accuracy using just 5% oracle labels, at 14× lower cost.
Quick Start
pip install cje-eval
# Optional (for plotting):
pip install "cje-eval[viz]"
from cje import analyze_dataset
# Compare policies on the same evaluation prompts
# Structure: { policy_name: [samples] }
# Each sample needs: prompt_id, judge_score
# Optional: oracle_label (human ground truth) on 5-25% of samples
results = analyze_dataset(
fresh_draws_data={
"gpt-4o": [
{"prompt_id": "eval_001", "judge_score": 0.85, "oracle_label": 0.9},
{"prompt_id": "eval_002", "judge_score": 0.72, "oracle_label": 0.7},
{"prompt_id": "eval_003", "judge_score": 0.68},
{"prompt_id": "eval_004", "judge_score": 0.79},
],
"claude-sonnet": [
{"prompt_id": "eval_001", "judge_score": 0.78, "oracle_label": 0.82},
{"prompt_id": "eval_002", "judge_score": 0.81, "oracle_label": 0.79},
{"prompt_id": "eval_003", "judge_score": 0.75},
{"prompt_id": "eval_004", "judge_score": 0.83},
],
}
)
# Or from files: analyze_dataset(fresh_draws_dir="responses/")
# Optional: plotting requires matplotlib (pip install "cje-eval[viz]")
results.plot_estimates(save_path="ranking.png")
CJE learns the judge→oracle mapping from the labeled samples and applies it everywhere.
Label Compatibility
CJE automatically handles different label scales without manual preprocessing:
# 0-100 scores work automatically
results = analyze_dataset(
fresh_draws_data={
"gpt-4o": [
{"prompt_id": "1", "judge_score": 85, "oracle_label": 78}, # 0-100 scale
{"prompt_id": "2", "judge_score": 72, "oracle_label": 65},
],
}
)
# Results are returned in YOUR scale (0-100), not [0,1]
print(results.estimates[0]) # → 73.5 (not 0.735)
Supported: [0,1], 0-100, Likert 1-5, or any bounded range. If values are already in [0,1], no transformation is applied.
Why You Need This
LLM-as-judge gives you rankings. CJE gives you certainty.
Without calibration, you know prompt A scored higher than B—but you don't know:
- Is the difference real or noise?
- How big is the improvement, actually?
- Have I tested enough samples?
- Will this hold next week?
CJE answers all of these. Label 5% of samples with your oracle (human raters, latest SOTA model or AI agent, downstream metric). CJE learns the calibration and applies it everywhere—giving you trustworthy magnitudes, valid confidence intervals, and drift detection.
The result: Make decisions faster, spend less on labeling, and defend your conclusions with real statistics.
The Results
We tested on 5,000 Chatbot Arena prompts with GPT-5 as the oracle (ground truth) and GPT-4.1-nano as the cheap judge:
CJE achieves 99% ranking accuracy using only 5% oracle labels—matching full-oracle performance at 14× lower cost.
Label ~250 samples with your oracle (human raters, downstream KPIs, expensive model). CJE learns the judge→oracle mapping and applies it to everything else. Without calibration, error bars contained the true value 0% of the time. With CJE: ~95%.
Already using an expensive model for evals? Switch to a 10-30× cheaper judge + CJE calibration. Same accuracy, fraction of the inference cost.
Example output with simulated data (not real model benchmarks)
Read the full Arena Experiment →
Monitoring Calibration Over Time
Calibration can drift. Periodically verify it still holds with a small probe:
from cje import analyze_dataset
from cje.diagnostics import audit_transportability
# results.calibrator is automatically fitted during analysis
results = analyze_dataset(fresh_draws_dir="responses/")
# Check if calibration still works on this week's data (50+ oracle labels)
diag = audit_transportability(results.calibrator, this_week_samples)
print(diag.summary())
# Status: PASS | Samples: 48 | Mean error: +0.007 (CI: -0.05 to +0.06)
PASS means your calibration is still valid. FAIL means something changed — investigate or recalibrate.
Try It Now
Open the interactive tutorial in Google Colab →
Walk through a complete example: compare policies, check if calibration transfers, inspect what's fooling the judge, and monitor drift over time. No setup required.
Documentation
Planning sample sizes? Use pilot data to optimize your evaluation budget:
Video Walkthroughs
- CJE Technical Walkthrough — Pipeline deep dive: calibration, evaluation, and transport auditing
- CJE in 3 Minutes — Quick intro: why raw judge scores mislead and how CJE fixes it
Technical Guides
- Calibration Methods — AutoCal-R, isotonic regression, two-stage
- Diagnostics System — Uncertainty quantification, transportability
- Estimators — Direct, IPS, DR implementations
- Interface/API —
analyze_datasetimplementation
Bridges (Promptfoo / TruLens / LangSmith / OpenCompass → CJE)
Note: these bridge converters are repo scripts (they are not installed with
pip install cje-eval). Clone the repo to use them:git clone https://github.com/cimo-labs/cje.git cd cje
If you already run evals in Promptfoo, TruLens, LangSmith, or OpenCompass, you can convert those outputs into CJE’s fresh_draws_data format.
# Promptfoo
python3 scripts/cje_bridges/convert.py promptfoo results.json \
--out cje_fresh_draws_data.json \
--label-template oracle_label_template.csv
# TruLens (install first: pip install trulens)
python3 scripts/cje_bridges/convert.py trulens \
--database-url sqlite:///default.sqlite \
--judge-col "Answer Relevance" \
--out cje_fresh_draws_data.json \
--label-template oracle_label_template.csv
# LangSmith (install first: pip install langsmith; set LANGSMITH_API_KEY)
python3 scripts/cje_bridges/convert.py langsmith \
--project "my_model_a_project" \
--project "my_model_b_project" \
--feedback-key "correctness" \
--out cje_fresh_draws_data.json \
--label-template oracle_label_template.csv
# OpenCompass (LLM-as-judge; run OpenCompass with --dump-eval-details)
python3 scripts/cje_bridges/convert.py opencompass path/to/opencompass_results.json \
--out cje_fresh_draws_data.json \
--label-template oracle_label_template.csv
After you label an oracle slice, re-run the converter to populate oracle_label:
- Promptfoo/TruLens/OpenCompass: pass
--oracle-labels <your_labeled_csv_or_jsonl> - LangSmith: if labels are stored in LangSmith as feedback, pass
--oracle-feedback-key <key>
See: scripts/cje_bridges/README.md
Examples & Data
- Examples Folder — Working code samples
- Arena Sample Data — Real-world test data
Development
git clone https://github.com/cimo-labs/cje.git
cd cje && poetry install && make test
Support
Citation
If you use CJE in your research, please cite:
@misc{landesberg2025causaljudgeevaluationcalibrated,
title={Causal Judge Evaluation: Calibrated Surrogate Metrics for LLM Systems},
author={Eddie Landesberg},
year={2025},
eprint={2512.11150},
archivePrefix={arXiv},
primaryClass={stat.ME},
url={https://arxiv.org/abs/2512.11150},
}
License
MIT — See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cje_eval-0.2.22.tar.gz.
File metadata
- Download URL: cje_eval-0.2.22.tar.gz
- Upload date:
- Size: 354.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa7d363e45f16e25954be1e830eb91a66ec29b2b58b4493bd724f3f2d3751497
|
|
| MD5 |
e83668c24af6a8d979846e75c423d912
|
|
| BLAKE2b-256 |
231e82f1efb583a5eaa6ec312c297bc6252a2b967a0294fb648610f68acc7f7d
|
Provenance
The following attestation bundles were made for cje_eval-0.2.22.tar.gz:
Publisher:
publish.yml on cimo-labs/cje
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cje_eval-0.2.22.tar.gz -
Subject digest:
fa7d363e45f16e25954be1e830eb91a66ec29b2b58b4493bd724f3f2d3751497 - Sigstore transparency entry: 909247314
- Sigstore integration time:
-
Permalink:
cimo-labs/cje@3c2fe7413eb74d43d5bd3bd7a0c3fa6282191776 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cimo-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2fe7413eb74d43d5bd3bd7a0c3fa6282191776 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file cje_eval-0.2.22-py3-none-any.whl.
File metadata
- Download URL: cje_eval-0.2.22-py3-none-any.whl
- Upload date:
- Size: 414.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3d0731c1f9043e76d97084c863a091bc0244dfc09b2367ff4c550852f08753a
|
|
| MD5 |
8b92e6dc1699e4b78ec5f673e4b481c1
|
|
| BLAKE2b-256 |
2986c7c0bec238a6e74161f3af1f172948a09b23a6d745dbaa309dfd19157182
|
Provenance
The following attestation bundles were made for cje_eval-0.2.22-py3-none-any.whl:
Publisher:
publish.yml on cimo-labs/cje
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cje_eval-0.2.22-py3-none-any.whl -
Subject digest:
f3d0731c1f9043e76d97084c863a091bc0244dfc09b2367ff4c550852f08753a - Sigstore transparency entry: 909247319
- Sigstore integration time:
-
Permalink:
cimo-labs/cje@3c2fe7413eb74d43d5bd3bd7a0c3fa6282191776 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cimo-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3c2fe7413eb74d43d5bd3bd7a0c3fa6282191776 -
Trigger Event:
workflow_dispatch
-
Statement type: