evaluatorq¶
evaluatorq async ¶
evaluatorq(
name: str,
params: EvaluatorParams | dict[str, Any] | None = None,
*,
data: DatasetIdInput
| ExperimentInput
| Sequence[Awaitable[DataPoint] | DataPointInput]
| None = None,
jobs: list[Job] | None = None,
evaluators: list[Evaluator] | None = None,
datapoint_parallelism: int | None = None,
llm_parallelism: int | None = None,
parallelism: int | None = None,
print_results: bool = True,
description: str | None = None,
path: str | None = None,
inference: bool = True,
single_trace: bool = False,
) -> EvaluatorqResult
Run an evaluation with the given parameters.
Can be called with either a params dict/object or keyword arguments:
# Using keyword arguments (recommended):
await evaluatorq("name", data=[...], jobs=[...], datapoint_parallelism=5)
# Using a dict:
await evaluatorq("name", {"data": [...], "jobs": [...], "datapoint_parallelism": 5})
# Using EvaluatorParams:
await evaluatorq("name", EvaluatorParams(data=[...], jobs=[...]))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name | str | Name of the evaluation run | required |
params | EvaluatorParams | dict[str, Any] | None | Optional EvaluatorParams instance or dict with all parameters. | None |
data | DatasetIdInput | ExperimentInput | Sequence[Awaitable[DataPoint] | DataPointInput] | None | The data to evaluate. A DatasetIdInput to fetch from Orq platform, an ExperimentInput to replay an experiment's recorded responses (requires inference=False), or a list of DataPoint instances/awaitables. | None |
jobs | list[Job] | None | The jobs to run on the data. | None |
evaluators | list[Evaluator] | None | The evaluators to use. If not provided, only jobs will run. | None |
datapoint_parallelism | int | None | Task concurrency, applied at two levels: datapoints run at most | None |
parallelism | int | None | Deprecated alias for | None |
llm_parallelism | int | None | Ceiling on in-flight LLM requests for the whole run, counted per request rather than per task, so it holds however the datapoint/job/evaluator/jury fan-out nests. Unbounded by default. This is the knob to set against a provider concurrency limit; | None |
print_results | bool | Whether to print results table to console. Defaults to True. | True |
description | str | None | Optional description for the evaluation run. | None |
path | str | None | Optional path (e.g. "MyProject/MyFolder") to place the experiment in a specific project and folder on the Orq platform. | None |
inference | bool | When True (default) jobs run to generate responses. When False, generation is skipped and evaluators score the pre-recorded response in each row's | True |
single_trace | bool | Group every row under one | False |
Returns:
| Type | Description |
|---|---|
EvaluatorqResult | List of DataPointResult objects |
Raises:
| Type | Description |
|---|---|
ValidationError | If parameters fail validation. |
ValueError | If neither params nor required kwargs are provided. |
Example
from evaluatorq import DataPoint, EvaluationResult, evaluatorq, job
@job("uppercase")
async def uppercase_job(data: DataPoint, row: int):
return data.inputs["text"].upper()
async def matches_expected(params):
return EvaluationResult(value=1 if params["output"] == params["data"].expected_output else 0)
await evaluatorq(
"uppercase-eval",
data=[DataPoint(inputs={"text": "hi"}, expected_output="HI")],
jobs=[uppercase_job],
evaluators=[{"name": "matches-expected", "scorer": matches_expected}],
)
deployment async ¶
deployment(
key: str,
inputs: dict[str, object] | None = None,
context: dict[str, object] | None = None,
metadata: dict[str, object] | None = None,
thread: ThreadConfig | None = None,
messages: list[MessageDict] | None = None,
) -> DeploymentResponse
Invoke an Orq deployment and return the response content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key | str | The deployment key (name) | required |
inputs | dict[str, object] | None | Input variables for the deployment template | None |
context | dict[str, object] | None | Context attributes for routing | None |
metadata | dict[str, object] | None | Metadata to attach to the request | None |
thread | ThreadConfig | None | Thread configuration for conversation tracking. Must include 'id' key. | None |
messages | list[MessageDict] | None | Chat messages for conversational deployments | None |
Returns:
| Type | Description |
|---|---|
DeploymentResponse | DeploymentResponse with content and raw response |
Example
# Simple invocation
response = await deployment("my-deployment")
print(response.content)
# With inputs
response = await deployment("summarizer", inputs={"text": "Long text..."})
# With messages for chat-style deployments
response = await deployment("chatbot", messages=[
{"role": "user", "content": "Hello!"}
])
# With thread tracking
response = await deployment("assistant",
inputs={"query": "What is AI?"},
thread={"id": "conversation-123"}
)
llm_jury ¶
llm_jury(
*,
name: str,
criteria: str | None = None,
prompt: str | None = None,
system_prompt: str | None = None,
judges: list[str] | None = None,
model: str | None = None,
repetitions: int = 1,
assignment: Literal['all', 'cyclic'] = 'all',
replacement_judges: list[str] | None = None,
min_successful_judges: int = 1,
verdict_kind: Literal['categorical', 'numeric'] = 'categorical',
labels: list[str] | None = None,
passing_labels: list[str] | None = None,
aggregator: AggregatorSpec | None = None,
threshold: float = 0.5,
score_range: tuple[float, float] = (0.0, 1.0),
tie_break: TieBreak | None = None,
structured_output: bool = True,
temperature: float | None = None,
max_tokens: int = 8000,
timeout_ms: int = 90000,
extra_kwargs: dict[str, Any] | None = None,
client: Any = None,
) -> Evaluator
Build a jury (or single-judge) LLM evaluator for evaluators=[...].
Verdict modes¶
The judge's verdict type and the passed (pass/fail) field are decided by verdict_kind together with labels. verdict_kind is not inferred from labels — it defaults to "categorical" and you pick the mode explicitly. There are three modes:
| how you configure it | judge returns | passed is |
|---|---|---|
boolean mode — verdict_kind="categorical" (default), labels=None | a JSON boolean true/false | the boolean itself |
labeled mode — verdict_kind="categorical" with labels=[...] | one of labels (a string) | verdict in passing_labels (None if no passing_labels given) |
numeric mode — verdict_kind="numeric" | a float in score_range | score >= threshold |
Notes
- For a yes/no judge, use boolean mode (the default — just omit
labels).passedis populated automatically; you do not needpassing_labels. labelsandpassing_labelsare valid only forverdict_kind="categorical"; passing them with"numeric"raisesValueError.- In labeled mode,
passing_labelsmust be a subset oflabels. If you omit it, the verdict is still recorded butpassedisNone(no pass/fail, so no pass-rate to aggregate).passing_labelsrequireslabels— it is rejected in boolean mode (which derives pass/fail from the verdict directly). labelsmust be strings. NativeTrue/Falseare not labels — use boolean mode for that.-
aggregatorpicks the panel consensus rule and must match the verdict kind (a mismatch raisesValueError): -
categorical:
"mode"(default — most common; plurality ties go totie_break) or"majority"(strict >50%, else inconclusive). - numeric:
"mean_std"(default — mean verdict; std reported instatson a conclusive verdict),"median","min", or"max". - a custom
Callable[[list[JuryVote]], bool | float | str | None]for either kind (returnNonefor "no consensus" / inconclusive). The same numeric keyword also collapses a single judge'srepetitions. -
assignmentpicks how judges are allocated across datapoints: -
"all"(default): every judge scores every datapoint — per-item consensus at K times single-judge cost. "cyclic": round-robin (CyclicJudge, arXiv:2603.01865) — each datapoint is scored by exactly one judge, cycling through the panel so every judge covers an equal share. Panel-relative judge bias cancels in expectation over the run at single-judge cost; per-item verdicts are single-judge opinions, so use it for benchmark/run-level scores, not when each individual verdict must be trustworthy. Per-itemstatsandraw_agreementareNone: one vote has no cross-judge agreement to report. The rotation runs over the deduplicated panel, so listing a judge twice to up-weight it only works under"all".repetitionsstill applies to the single assigned judge — N calls to one judge, never N judges. Insideevaluatorq()the assignment is keyed on the dataset row (datapointigoes to judgei % len(panel)), deterministic at anyparallelismand across evaluator reuse. A direct scorer call carries no row and rotates in arrival order on a cursor that lives on the evaluator — only the equal share balance is guaranteed there. Shuffle the dataset first if its order is meaningful. Requiresmin_successful_judges=1; a failed item degrades to inconclusive unlessreplacement_judgesis set.
Examples¶
Boolean — "is the answer correct?" (verdict_kind="categorical", the default):
Labeled categorical (verdict_kind="categorical"):
llm_jury(
name="grade",
criteria="Grade the answer.",
labels=["correct", "partially_correct", "incorrect"],
passing_labels=["correct", "partially_correct"],
)
Numeric (verdict_kind="numeric"):
EvaluatorQ Python - An evaluation framework for LLM applications.
job ¶
job(
name: str, fn: Callable[[DataPoint, int], Awaitable[Output] | Output] | None = None
) -> Job | Callable[[Callable[[DataPoint, int], Awaitable[Output] | Output]], Job]
Helper function/decorator to create a named job that ensures the job name is preserved even when errors occur during execution.
This wrapper:
- Automatically formats the return value as {"name": ..., "output": ...}
- Attaches the job name to errors for better error tracking
- Can be used as a decorator (@job("name")) or function (job("name", fn))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name | str | The name of the job | required |
fn | Callable[[DataPoint, int], Awaitable[Output] | Output] | None | The job function that returns the output (optional when used as decorator) | None |
Returns:
| Type | Description |
|---|---|
Job | Callable[[Callable[[DataPoint, int], Awaitable[Output] | Output]], Job] | A Job function that always includes the job name |
Example
# As a decorator:
@job("text-analyzer")
async def analyze_text(data: DataPoint, row: int):
return {"length": len(data.inputs["text"])}
# As a function wrapper:
my_job = job("my-job", async_function)
# With lambda for simple cases:
uppercase_job = job("uppercase", lambda data, row: data.inputs["text"].upper())
DataPoint ¶
Bases: BaseModel
A data point for evaluation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs | The inputs to pass to the job. | required | |
expected_output | The expected output of the data point. Used for evaluation and comparing the output of the job. | required |
Example
Evaluator ¶
EvaluationResult ¶
EvaluationResultCell ¶
Bases: BaseModel
string_contains_evaluator ¶
string_contains_evaluator(
*, case_insensitive: bool = True, name: str = 'string-contains'
) -> Evaluator
Creates an evaluator that checks if the output contains the expected output. Uses the data.expected_output from the dataset to compare against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
case_insensitive | bool | Whether the comparison should be case-insensitive | True |
name | str | Optional name for the evaluator | 'string-contains' |
Returns:
| Type | Description |
|---|---|
Evaluator | An Evaluator that checks if output contains expected output |
DatasetIdInput ¶
Bases: BaseModel
Input for fetching a dataset from Orq platform.
invoke async ¶
invoke(
key: str,
inputs: dict[str, object] | None = None,
context: dict[str, object] | None = None,
metadata: dict[str, object] | None = None,
thread: ThreadConfig | None = None,
messages: list[MessageDict] | None = None,
) -> str
Invoke an Orq deployment and return just the text content. This is a convenience wrapper around deployment() for simple use cases.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key | str | The deployment key (name) | required |
inputs | dict[str, object] | None | Input variables for the deployment template | None |
context | dict[str, object] | None | Context attributes for routing | None |
metadata | dict[str, object] | None | Metadata to attach to the request | None |
thread | ThreadConfig | None | Thread configuration for conversation tracking. Must include 'id' key. | None |
messages | list[MessageDict] | None | Chat messages for conversational deployments | None |
Returns:
| Type | Description |
|---|---|
str | The text content of the response |
llm_jury_pairwise ¶
llm_jury_pairwise(
*,
judges: list[str] | None = None,
model: str | None = None,
criteria: str | None = None,
prompt: str | None = None,
system_prompt: str | None = None,
swap: bool = True,
repetitions: int = 1,
assignment: Literal['all', 'cyclic'] = 'all',
replacement_judges: list[str] | None = None,
min_successful_judges: int = 1,
max_tokens: int = 8000,
timeout_ms: int = 90000,
temperature: float | None = None,
structured_output: bool = True,
extra_kwargs: dict[str, Any] | None = None,
client: Any = None,
max_concurrency: int | None = None,
) -> PairwiseComparator
Build a pairwise (A-vs-B) LLM jury that reuses the shared panel machinery.
Judges compare two responses and pick a winner ('A'/'B'/'tie'). With swap on (default) each judge is run in both orderings to correct for position bias (see ADR-24). Panel/orchestration params mirror llm_jury. prompt overrides the built-in Mustache-style template (which exposes the response_a.*/response_b.* namespace via _side_to_namespace); leave it None to use the default. Returns a PairwiseComparator; call compare per A/B pair, and roll many comparisons up with evaluatorq.pairwise.build_report.
max_concurrency caps TOTAL in-flight judge LLM calls across all concurrently running compare calls on the returned comparator (each pair fans out judges x orderings x repetitions). None (default) keeps the fan-out unbounded.
assignment="cyclic" (CyclicJudge, arXiv:2603.01865) gives each comparison exactly one judge, cycling through the panel so every judge covers an equal share of the run. Judge bias cancels in expectation over many comparisons at single-judge cost, and the assigned judge still runs both orderings when swap is on. Per-pair winners are single-judge opinions — roll them up with build_report and read run-level rates. The rotation runs over the deduplicated panel (duplicate judge entries add weight only under "all"), repetitions still applies to the single assigned judge, and the cursor lives on the comparator: a reused comparator continues where the previous run stopped, so exact balance holds per freshly built comparator, not per run. Shuffle your pairs first if their order is meaningful. Requires min_successful_judges=1.
Usage:
from evaluatorq import llm_jury_pairwise
comparator = llm_jury_pairwise(
criteria="The answer is accurate, complete, and directly addresses the question.",
judges=["anthropic/claude-sonnet-4-6", "openai/gpt-5.4-mini"],
)
comparison = await comparator.compare(
question="What is the capital of France?",
response_a="The capital of France is Paris.",
response_b="The capital of France is Berlin.",
)
print(comparison.winner)
AgentResponse ¶
Bases: BaseModel
Structured response from a target agent as an ordered list of output messages.
Each item in output is a TextOutputItem, ToolCallOutputItem, or ReasoningOutputItem, preserving the order in which they were produced. Item type discriminators align with the OpenResponses intermediate data format (output_text, function_call, reasoning).
Accessors
.text — all TextOutputItem contents concatenated, or "" if none .tool_calls — list of ToolCallOutputItem filtered from output in order
tool_calls property ¶
Return the tool call items from .output in order.
from_openresponses classmethod ¶
Build a full AgentResponse from a Responses API response object.
Single parse path for the OpenResponses wire format. Populates output + usage + model + finish_reason + response_id.
usage is None when the response carries no usage block — callers must distinguish "no usage reported" from "zero tokens used" so cost reports stay honest. usage.calls is intentionally left at 0: this is a pure parse; per-call-site accounting (calls=1 / +1) is applied by callers to the returned object's usage.
BTFit ¶
Bases: BaseModel
Result of a Bradley-Terry family fit.
reliability ¶
1 / sigma for a judge - the paper's unsupervised reliability signal.
BTSigmaAggregation ¶
Bases: BaseModel
Reliability-weighted rollup of a pairwise run (BT-sigma, arXiv:2602.16610).
Fitted unsupervised on the run's own reconciled votes: hard BT-sigma jointly infers the A-vs-B skill gap and a discriminator per judge, then re-derives each comparison's winner as a reliability-weighted vote instead of uniform plurality. Down-weights noisy judges; needs no labels. The weight depends on the path: on the pooled two-item fit it is 1/sigma; on a repetition run (repetition_consistency non-empty and at quorum) it is instead max(shrunk_consistency, 0.05), since within-datapoint consistency is the better reliability signal there.
Two caveats of the two-item collapse, both handled here:
- With only two items a judge's sigma is pinned by its own vote distribution, so a judge whose decisive votes are unanimous gets an arbitrarily small sigma that measures one-sidedness (e.g. position or verbosity bias), not reliability. Such judges are excluded from the weighting — they vote with a neutral weight, their sigma is dropped from
judge_sigmas, andfit_warningsnames them. p_a_beats_b/skill_gapcome from the pooled global fit, whilewinnerscome from the per-comparison weighted vote. These are two different estimators and need not agree; readp_a_beats_bas the run-level headline andwinnersas the per-row calls.
DataPointDict ¶
Bases: _DataPointDictRequired
Dict representation of a DataPoint for type checking.
DataPointInput module-attribute ¶
Type alias for DataPoint that accepts both model instances and dicts.
DataPointResult ¶
Bases: BaseModel
DeploymentResponse dataclass ¶
Response from a deployment invocation.
EvaluationResultCellValue module-attribute ¶
EvaluatorParams ¶
Bases: BaseModel
Parameters for running an evaluation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data | The data to evaluate. A DatasetIdInput to fetch from Orq platform, an ExperimentInput to replay an experiment's recorded responses (requires inference=False), or a list of DataPoint instances/awaitables. | required | |
jobs | The jobs to run on the data. | required | |
evaluators | The evaluators to use. If not provided, only jobs will run. | required | |
datapoint_parallelism | Number of datapoints to process in parallel. Defaults to 10; set to 1 for sequential execution. Accepts the former name | required | |
llm_parallelism | Ceiling on in-flight LLM requests for the whole run, counted per request rather than per task. Unbounded by default. Use this against a provider concurrency limit — one datapoint can issue many requests, so the datapoint count cannot be sized against one. | required | |
print_results | Whether to print results table to console. Defaults to True. Also accepts "print" as an alias. | required | |
description | Optional description for the evaluation run. | required | |
path | Optional path (e.g. "MyProject/MyFolder") to place the experiment in a specific project and folder on the Orq platform. | required | |
single_trace | Group every row of the run under one | required |
inference class-attribute instance-attribute ¶
When False, skip generation and evaluate the pre-recorded response in each row's messages column instead of running jobs.
single_trace class-attribute instance-attribute ¶
When True, open one evaluatorq.run span for the whole run so every row shares a trace. Default False keeps each row's orq.job as its own root.
EvaluatorScore ¶
Bases: BaseModel
EvaluatorqResult module-attribute ¶
Type alias for evaluation results
ExperimentInput ¶
Bases: BaseModel
Input for sourcing pre-recorded responses from an Orq experiment.
Used with inference=False to re-run evaluators against the responses an earlier experiment already produced, without regenerating them.
experiment_id instance-attribute ¶
The experiment ID to load responses from. Read it off the experiment URL in the Orq UI (/experiments/<experiment_id>). The API refers to experiments as "spreadsheets", so you will also see this ID in /v2/spreadsheets/<id> routes.
run_id class-attribute instance-attribute ¶
A specific run ID (a "manifest" in the API). When omitted, the latest run is used. Every execution of an experiment creates a new run; open it from the experiment's run history to read its ID from the URL.
Job module-attribute ¶
Job function type - returns a dict with 'name' and 'output' keys
JobResult ¶
Bases: BaseModel
JobReturn ¶
Bases: TypedDict
Job return structure
JudgeStats ¶
Bases: BaseModel
Per-judge behaviour rolled up across a set of comparisons.
JudgedComparison ¶
Bases: BaseModel
One judge's preference on one item pair, in a fixed (a, b) frame.
p_a is the judge's preference probability that item_a beats item_b. Callers that only have categorical votes map them as A -> 1.0, B -> 0.0, tie -> 0.5. Position-bias symmetrisation (paper Eq. 4) is the caller's job: build p_a from both orderings, e.g. via the pairwise module's swap/reconcile machinery, before fitting.
MessageDict ¶
Bases: TypedDict
Chat message structure compatible with Orq SDK.
Output module-attribute ¶
Output type alias
PairwiseComparator ¶
A configured pairwise LLM jury. Call compare on an A/B pair.
compare async ¶
Run the panel over one A-vs-B comparison and return the reconciled verdict.
A side that carries a target-level error is never judged — comparing against a failed generation would score noise as a preference.
PairwiseComparison ¶
Bases: BaseModel
The panel's result for a single A-vs-B comparison.
PairwiseReport ¶
Bases: BaseModel
Cross-comparison rollup of a pairwise run.
PairwiseVote ¶
Bases: BaseModel
One judge's reconciled verdict for a single A-vs-B comparison.
RepetitionObservation ¶
Bases: BaseModel
One raw repetition pass of one judge in one ordering, canonicalized.
verdict is in the ORIGINAL A/B orientation regardless of ordering: a 'B' returned under the swapped ordering is recorded as 'A' here, so the observations are directly comparable across orderings. None is a pass that produced no usable verdict: a genuine abstention, an error, or an off-contract value. The per-vote repetition_failures count says how many of those Nones were errors or off-contract (as opposed to clean abstentions); those two lower reliability while an abstention does not (RES-1251). Nothing is silently collapsed.
Scorer module-attribute ¶
ScorerParameter ¶
Bases: TypedDict
Parameters passed to a scorer function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data | The data point being evaluated. | required | |
output | The output produced by the job for the data point. | required | |
row | Zero-based dataset index of the data point. Present when the scorer runs inside | required |
ThreadConfig ¶
Bases: TypedDict
Thread configuration for conversation tracking.
bt_sigma_aggregation ¶
Fit hard BT-sigma over a run's reconciled votes and re-derive winners.
The two "items" are the run's A and B sides; every decisive reconciled vote is one comparison between them. Reconciliation has already symmetrised position bias (both orderings per judge), satisfying the model's commutativity requirement. Categorical votes make hard BT-sigma the natural variant, which is also the paper's most robust one under inconsistency.
Identical votes are collapsed into weighted records before the fit (a judge has at most three distinct judgements here: A, B, tie), so the fit cost stays flat no matter how many comparisons the run holds.
Judges whose decisive votes are unanimous are excluded from the reliability weighting: in the two-item collapse their sigma measures one-sidedness, not reliability (see BTSigmaAggregation). They vote with the median weight of the remaining judges (or uniformly when no judge remains), and fit_warnings names them.
build_report ¶
build_report(
comparisons: Sequence[PairwiseComparison], *, aggregation: str = 'plurality'
) -> PairwiseReport
Roll a set of pairwise comparisons up into headline and per-judge metrics.
aggregation='plurality' (default) keeps the existing uniform plurality consensus. aggregation='bt-sigma' additionally fits hard BT-sigma over the run and attaches the reliability-weighted rollup (report.bt_sigma) plus each judge's discriminator (JudgeStats.sigma); the headline plurality rates are unchanged so the two aggregations stay comparable.
exact_match_evaluator ¶
Creates an evaluator that checks if the output exactly matches the expected output. Uses the data.expected_output from the dataset to compare against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
case_insensitive | bool | Whether the comparison should be case-insensitive | False |
name | str | Optional name for the evaluator | 'exact-match' |
Returns:
| Type | Description |
|---|---|
Evaluator | An Evaluator that checks if output exactly matches expected output |
fit_bt ¶
fit_bt(
comparisons: Sequence[JudgedComparison],
*,
judge_sigma: bool = True,
hard: bool = False,
) -> BTFit
Fit the Bradley-Terry family by MLE on the given comparisons.
judge_sigma adds the per-judge discriminator (BT-sigma). hard binarizes preferences first (hard BT / hard BT-sigma) - the paper's most robust variant when judges are highly inconsistent.
Degradations (recorded in warnings, never raised):
- a single judge with
judge_sigma=Truefalls back to plain BT - a lone sigma is absorbed into the skill scale and carries no information (paper section 3.3); - a tiny ridge keeps disconnected graphs / perfect separation finite.
repetition_consistency ¶
Per-judge within-datapoint repetition consistency, in [0, 1] (RES-1251).
The unit of analysis is one (judge, comparison, ordering) group: repeated passes of the SAME prompt. A group needs >= 2 decisive repetitions; its consistency is the mean pairwise agreement among them, then discounted by the share of that vote's passes that errored or came back off-contract (repetition_failures) so a flaky judge scores below a clean one; a genuine abstention is not counted as a failure and does not penalise. A comparison contributes at most ONE observation per judge (its groups averaged), so repetition count never multiplies a judge's evidence, and different datapoints are never compared to each other - which is exactly why this is interpretable as judge reliability where the global two-item fit was not.
Empty dict when no judge has any qualifying group (e.g. repetitions=1 or a legacy run without observations).
This is the SHRUNK reliability weight, not raw self-agreement: at the recommended R=2 a group's agreement is only 0.0 or 1.0, so a judge with one lucky group would otherwise carry the same weight as a judge with many. Each judge's mean is shrunk toward the panel mean by its evidence count (empirical-Bayes, _SHRINKAGE_PSEUDO_OBS pseudo-observations), so thin evidence is pulled to neutral and cannot dominate a run. A perfectly self-consistent judge therefore reads below 1.0 unless the whole panel is; repetition_consistency_raw publishes the un-shrunk mean beside it (RES-1251).
repetition_consistency_raw ¶
Per-judge RAW within-datapoint self-agreement mean (RES-1251).
1.0 = a judge that always agreed with itself on repeated passes of the same prompt. This is PURE agreement, not failure-adjusted and not shrunk: a judge that agreed with itself on every completed pass reads 1.0 even if some passes failed (the failure discount and the shrinkage both live in the repetition_consistency weight, not here). Published beside that weight so the raw signal stays visible. Empty dict when no judge has a qualifying group.
run_pairwise async ¶
run_pairwise(
*,
judge_fn: PairwiseJudgeFn,
panel: Sequence[str],
response_a: Any,
response_b: Any,
swap: bool = True,
repetitions: int = 1,
replacement_judges: Sequence[str] | None = None,
min_successful_judges: int = 1,
propagate_errors: bool = False,
max_concurrency: int | Semaphore | None = None,
) -> PairwiseComparison
Run a panel over one A-vs-B comparison and reconcile it into a verdict.
Each judge runs through the shared run_jury. When swap is on (default) every judge is also run with A and B exchanged; the two verdicts are un-swapped and passed through reconcile_pair, so a judge that just follows slot order abstains and is recorded as a flip.
Both orderings run concurrently. Replacement judges are promoted at the pair level — a stand-in is run in both orderings so it can cast a real reconciled vote, and a primary that fails in either ordering is what it stands in for. The winner is the plurality of the reconciled votes, or 'inconclusive' when fewer than min_successful_judges cast a decisive reconciled vote.
max_concurrency caps how many judge calls run at once across the whole comparison (judges x orderings x repetitions, replacements included). Pass an existing asyncio.Semaphore to share one budget across several comparisons. None (default) keeps the fan-out unbounded.
Tracing: the whole comparison is ONE orq.pairwise_jury span (RES-985). Each ordering drives _run_jury_core rather than run_jury so it doesn't mint a second jury span whose aggregates describe half a comparison; every judge span hangs off this one, tagged judge.label_swapped.