compere

How it works

Two algorithms, one honest answer

compere separates two questions most tools conflate: which pair should I ask about next, and how do I score the answer? UCB1 handles the first, Elo the second.

The request flow

Entities
UCB1
picks next pair
Human/model votes
Leaderboard
Elo
updates ratings
Comparison result

Both steps read and write through SQLAlchemy — SQLite by default, PostgreSQL when configured.

Step 1 — UCB1 pair selection

The bottleneck in a comparison study is not the maths of scoring; it is deciding which pair a rater should look at next. Comparing every pair is quadratic and wasteful — most matchups are between an obvious winner and an obvious loser. compere frames pair selection as a multi-armed bandit and applies the Upper Confidence Bound (UCB1) rule (implemented in compere/modules/mab.py):

UCB(i) = win_rate(i) + c · sqrt(ln(N) / n_i)
  • win_rate(i) — the observed win rate of entity i (exploitation).
  • c — the exploration constant, default 1.414 (set UCB_EXPLORATION_CONSTANT).
  • N — total comparisons so far; n_i — comparisons involving entity i.

New entities get a large initial weight (UCB_UNEXPLORED_WEIGHT, default 1000.0) so they are surveyed before they are scored. The two highest-UCB entities are paired for the next comparison. The effect: votes concentrate on genuinely uncertain matchups, and you reach a stable ranking sub-quadratically.

Step 2 — Elo rating updates

Once a verdict comes in, compere updates ratings with standard Elo (implemented in compere/modules/rating.py) — the same formulation used in chess:

expected_a  = 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
new_rating_a = rating_a + K · (actual_a - expected_a)
  • K — update step size, default 32 (set ELO_K_FACTOR).
  • Initial rating1500 by default (set ELO_INITIAL_RATING).
  • actual_a — 1 if a won, 0 if it lost (draws split the difference).

Nothing fancier is claimed. Because the update is transparent, you can explain any rating change to a stakeholder in two sentences — which is exactly why Elo was chosen over a latent-variable model.

An alternative: similarity pairing

A second selection strategy lives in compere/modules/similarity.py and is exposed at /comparisons/next. Instead of maximising information gain, it pairs entities that are similar — useful when you specifically want to resolve close calls. It is a drop-in replacement for the UCB endpoint; the Elo layer does not change.

What compere deliberately leaves out

compere does not implement Bradley-Terry maximum-likelihood estimation, Thurstone Case V, TrueSkill, Glicko, or any neural ranker. Those models can be more statistically efficient, but they trade away interpretability. compere's bet is that for most applied ranking work — evals, RLHF, A/B, taste graphs — an interpretable UCB + Elo pipeline is the better tool.