Skip to main content

Panel-based reliability annotation for preference data: measure inter-judge agreement (IJA) with a panel of LLM judges and export TRL-ready DPO data with reliability signals attached.

Project description

Twelve Angry LLMs

Panel-based reliability annotation for preference data.

Modern preference datasets are built by asking a single strong judge to score or rank candidate responses, then collapsing its verdict into (chosen, rejected) pairs for DPO or reward-model training. That collapse throws away any notion of how contestable each label was.

twelve-angry-llms recovers that signal: it runs a panel of diverse LLM judges over each (prompt, K responses) datapoint and measures how much the judges agree with each other — the inter-judge agreement (IJA), computed as average pairwise Kendall's τ-b over the judges' rankings. High IJA means the label is trustworthy; low IJA means it is shaky and likely to inject noise into training. The result exports straight into TRL's (prompt, chosen, rejected) schema with the reliability signals attached, so you can filter, down-weight, or soft-label your data and run your existing training setup unchanged.

License: MIT Code style: ruff

Installation

pip install twelve-angry-llms          # core: OpenRouter + any OpenAI-compatible provider
pip install "twelve-angry-llms[data]"  # + raw UltraFeedback / Nectar loaders

Quickstart

The easiest way to run a cross-family panel is a single OpenRouter key — one account, hundreds of models:

from twelve_angry_llms import (
    Judge, Panel, PreferenceDatapoint, ScoringProtocol,
    OpenRouterClient, to_records, to_jsonl,
)

openrouter = OpenRouterClient()  # reads OPENROUTER_API_KEY
panel = Panel([
    Judge(model="openai/gpt-4o-2024-08-06", client=openrouter),
    Judge(model="anthropic/claude-sonnet-4.5", client=openrouter),
    Judge(model="qwen/qwen-2.5-72b-instruct", client=openrouter),
])

datapoints = [
    PreferenceDatapoint(
        prompt="What causes tides?",
        responses=(
            "The gravitational pull of the moon and sun.",
            "Mostly wind patterns over the ocean.",
            "The moon's gravity, with a smaller solar contribution.",
        ),
    ),
]

results = panel.annotate_sync(datapoints, ScoringProtocol())
print(results[0].ija)              # panel agreement on this datapoint, in [-1, 1]
print(panel.diagnostics(results))  # Krippendorff's alpha, judge-judge correlations

to_jsonl(to_records(results), "annotated.jsonl")  # TRL schema + IJA columns

Judges aren't tied to OpenRouter: OpenAICompatibleClient (which OpenRouterClient is a preconfigured version of) speaks to any OpenAI-compatible endpoint — OpenAI directly, Together, Groq, a local vLLM or Ollama server, ... — via its base_url. Keys are read from environment variables; pass api_key_env to point at a different variable per provider.

from twelve_angry_llms import OpenAICompatibleClient

openai = OpenAICompatibleClient()  # OPENAI_API_KEY
local = OpenAICompatibleClient(base_url="http://localhost:11434/v1", api_key="-")

Each exported record carries:

column meaning
prompt, chosen, rejected the standard TRL/DPO interface
prompt_ija panel agreement over all K candidates (Kendall's τ-b)
pair_agreement fraction of judges preferring chosen over rejected — usable directly as a soft label for conservative DPO / soft-label methods
judge_values raw per-judge scores or rankings

Two elicitation protocols

  • ScoringProtocol — every judge scores each response 1–5 against a shared rubric (the UltraFeedback style). Also yields per-judge score margins for free.
  • RankingProtocol — every judge orders all K responses best-to-worst in one pass (the Nectar style).

Both parse into per-response utilities, so IJA and all exports work identically. Judges receiving one shared guideline per protocol is the point: any disagreement then reflects the judges, not prompt wording.

Command line

tal annotate --input data.jsonl --panel panel.yaml --output annotated.jsonl
tal export   --input annotated.jsonl --output pairs.jsonl

data.jsonl rows need prompt and responses; panel.yaml declares the judges and (optionally) a response cache:

metric: kendall
cache: .tal/cache.sqlite
judges:
  - model: openai/gpt-4o-2024-08-06
    provider: openrouter                 # the default
  - model: anthropic/claude-sonnet-4.5
    provider: openrouter
  - model: qwen/qwen-2.5-72b-instruct
    provider: openrouter
  - model: llama3.1:70b                 # any OpenAI-compatible endpoint also works
    provider: openai
    base_url: http://localhost:11434/v1

With the data extra you can annotate the raw (non-binarized) datasets directly: tal annotate --dataset ultrafeedback --limit 1000 ....

Reproducible OpenRouter runs

One caveat with aggregators: they load-balance open-weight models across hosts with different quantizations, which can silently change judge behavior between runs. Pin the routing with extra_body (merged into every request, and part of the response-cache key):

  - model: qwen/qwen-2.5-72b-instruct
    provider: openrouter
    extra_body:
      provider: {order: [together], allow_fallbacks: false, quantizations: [fp16]}

Use exact-versioned model slugs (not ~latest aliases) for the same reason. See research/configs/panel.example.yaml for a fully pinned five-judge panel.

Reproducibility

  • Temperature defaults to 0 everywhere.
  • CachedClient / the cache: key stores every raw response in SQLite, keyed on (model, sampling settings, messages) — re-runs never re-bill.
  • Clients track token usage (client.usage) so runs can report cost.

The research

This library is the instrument for an ongoing study of IJA as a label-reliability signal — hypotheses, judging protocol, and experimental phases live in research/RESEARCH_PLAN.md. Working title: "Twelve Angry LLMs: Judge Agreement as a Label-Reliability Signal for Preference Data."

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

twelve_angry_llms-0.2.0.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

twelve_angry_llms-0.2.0-py3-none-any.whl (27.0 kB view details)

Uploaded Python 3

File details

Details for the file twelve_angry_llms-0.2.0.tar.gz.

File metadata

  • Download URL: twelve_angry_llms-0.2.0.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.10

File hashes

Hashes for twelve_angry_llms-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1f7103a5b4c3e8f343cd6a7fd5fef126eee0e1cdef826a2d21471fd006a2e143
MD5 e8c9effd7f96318024e23536476a66cd
BLAKE2b-256 bfa31f109a6d86363ae2d6d95674d761292b08d9c3be75b452daa0aa800fce8f

See more details on using hashes here.

File details

Details for the file twelve_angry_llms-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for twelve_angry_llms-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cbfb6cd345fd3566781e933b34c492addc39c1dbf8d24abbed9cd6a7010f623f
MD5 e8e2b9a8dbfc6533336b7988e16f8dfb
BLAKE2b-256 56bd8b534e6c72167d7827022bafb3177458e04841524a588ce97150fdd4406d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page