auditkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. auditkit/README.md +99 -0
  2. auditkit/__init__.py +177 -0
  3. auditkit/__main__.py +3 -0
  4. auditkit/_bootstrap.py +77 -0
  5. auditkit/_identity_guard.py +99 -0
  6. auditkit/adapter.py +264 -0
  7. auditkit/annotator.py +339 -0
  8. auditkit/api.py +502 -0
  9. auditkit/assets/auditkit_logo.png +0 -0
  10. auditkit/cache.py +47 -0
  11. auditkit/cli.py +417 -0
  12. auditkit/comparison.py +563 -0
  13. auditkit/diff.py +265 -0
  14. auditkit/errors.py +54 -0
  15. auditkit/evaluator.py +20 -0
  16. auditkit/experiment.py +145 -0
  17. auditkit/hf_publish.py +262 -0
  18. auditkit/lmeval_engine.py +550 -0
  19. auditkit/loaders.py +121 -0
  20. auditkit/logs.py +18 -0
  21. auditkit/metric.py +199 -0
  22. auditkit/metrics/README.md +15 -0
  23. auditkit/metrics/__init__.py +0 -0
  24. auditkit/metrics/code.py +222 -0
  25. auditkit/metrics/embedding.py +131 -0
  26. auditkit/metrics/encoder_judge.py +423 -0
  27. auditkit/metrics/generation.py +331 -0
  28. auditkit/metrics/guard.py +412 -0
  29. auditkit/metrics/hallucination.py +45 -0
  30. auditkit/metrics/judge.py +547 -0
  31. auditkit/metrics/pairwise.py +153 -0
  32. auditkit/metrics/perf.py +53 -0
  33. auditkit/metrics/rag.py +149 -0
  34. auditkit/metrics/security.py +64 -0
  35. auditkit/metrics/toxicity.py +238 -0
  36. auditkit/model/README.md +16 -0
  37. auditkit/model/__init__.py +485 -0
  38. auditkit/model/anthropic.py +94 -0
  39. auditkit/model/api_gen.py +133 -0
  40. auditkit/model/groq_gen.py +121 -0
  41. auditkit/model/hf_gen.py +385 -0
  42. auditkit/model/lexsi.py +155 -0
  43. auditkit/model/litellm_gen.py +65 -0
  44. auditkit/model/openai.py +90 -0
  45. auditkit/model/openrouter_gen.py +152 -0
  46. auditkit/model/vllm_gen.py +316 -0
  47. auditkit/model_compare.py +655 -0
  48. auditkit/redteam/README.md +9 -0
  49. auditkit/redteam/__init__.py +26 -0
  50. auditkit/redteam/detector.py +37 -0
  51. auditkit/redteam/detectors/README.md +5 -0
  52. auditkit/redteam/detectors/builtin.py +126 -0
  53. auditkit/redteam/probe.py +39 -0
  54. auditkit/redteam/probes/README.md +5 -0
  55. auditkit/redteam/probes/builtin.py +85 -0
  56. auditkit/redteam/runner.py +206 -0
  57. auditkit/registry.py +65 -0
  58. auditkit/report.py +278 -0
  59. auditkit/report_format.py +52 -0
  60. auditkit/router.py +54 -0
  61. auditkit/runner.py +575 -0
  62. auditkit/runspec.py +159 -0
  63. auditkit/sample.py +40 -0
  64. auditkit/scenario.py +88 -0
  65. auditkit/scenarios/README.md +10 -0
  66. auditkit/scenarios/__init__.py +4 -0
  67. auditkit/scenarios/arc.py +33 -0
  68. auditkit/scenarios/gsm8k.py +32 -0
  69. auditkit/scenarios/hellaswag.py +33 -0
  70. auditkit/scenarios/humaneval.py +32 -0
  71. auditkit/scenarios/mmlu.py +34 -0
  72. auditkit/scenarios/truthfulqa.py +33 -0
  73. auditkit/score.py +165 -0
  74. auditkit/scorers.py +117 -0
  75. auditkit/scoring.py +79 -0
  76. auditkit/types.py +69 -0
  77. auditkit-1.0.0.dist-info/METADATA +396 -0
  78. auditkit-1.0.0.dist-info/RECORD +81 -0
  79. auditkit-1.0.0.dist-info/WHEEL +4 -0
  80. auditkit-1.0.0.dist-info/entry_points.txt +2 -0
  81. auditkit-1.0.0.dist-info/licenses/LICENSE.md +92 -0
@@ -0,0 +1,423 @@
1
+ """LLM-as-judge via a real encoder classifier — classification, not generation.
2
+
3
+ Every judge in ``metrics/judge.py`` (``LLMJudge``, ``GEval``, ...) needs a
4
+ *generative* model: it asks a second LLM to produce free text, then parses a
5
+ verdict out of that text with a regex/marker. :class:`EncoderJudge` is a
6
+ genuinely different mechanism: any `AutoModelForSequenceClassification`
7
+ encoder — BERT, RoBERTa, DeBERTa, ALBERT, ELECTRA, XLNet, MPNet, and any
8
+ other architecture Hugging Face's ``Auto*`` classes cover — used directly as
9
+ the judge. It never generates anything; it classifies a (candidate,
10
+ reference) pair in one forward pass and reads the verdict straight off the
11
+ model's own output distribution, with no free-text parsing at all.
12
+
13
+ **Templating for an encoder** works differently than a chat/prompt template:
14
+ encoder classifiers built for pair-input tasks (NLI, similarity, cross-encoder
15
+ ranking, ...) take exactly *two* text spans and encode them together via the
16
+ tokenizer's native pair-sequence support (``tokenizer(text, text_pair)`` →
17
+ ``[CLS] text [SEP] text_pair [SEP]`` for BERT-style models, with an
18
+ equivalent for every other encoder architecture) — there is no chat-template
19
+ string to write by hand. ``text_template=``/``text_pair_template=`` here
20
+ just decide *which rendered fields* fill those two slots; the actual
21
+ pair-encoding is handled by ``transformers``' own pipeline, not by manually
22
+ concatenating a literal ``"[SEP]"`` into one string (which does not
23
+ reliably match what any given tokenizer's real special-token handling would
24
+ produce — see ``FactualConsistency`` in ``hallucination.py`` for the
25
+ older, less reliable version of that mistake).
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any, Literal, Optional
31
+
32
+ from auditkit.metric import Metric
33
+ from auditkit.sample import Sample
34
+ from auditkit.score import Score
35
+ from auditkit.types import Direction, ScoreKind
36
+ from auditkit.registry import METRICS
37
+ from auditkit.errors import ExtraNotInstalled
38
+
39
+ # Label names commonly used across NLI/entailment/similarity-style encoder
40
+ # classifiers, for auto-detecting a sensible label_map when the checkpoint's
41
+ # own config.id2label uses real words. Deliberately conservative -- a
42
+ # checkpoint with meaningless labels (LABEL_0/LABEL_1/LABEL_2, the actual
43
+ # default for a lot of fine-tuned classifiers) can't be guessed at all, and
44
+ # EncoderJudge raises rather than fabricating a mapping in that case.
45
+ _POSITIVE_LABEL_NAMES = frozenset({
46
+ "entailment", "entail", "positive", "pos", "yes", "true", "consistent",
47
+ "supported", "match", "duplicate", "similar", "1", "label_1",
48
+ })
49
+ _NEGATIVE_LABEL_NAMES = frozenset({
50
+ "contradiction", "contradict", "negative", "neg", "no", "false",
51
+ "inconsistent", "unsupported", "mismatch", "not_duplicate",
52
+ "dissimilar", "0", "label_0",
53
+ })
54
+ _NEUTRAL_LABEL_NAMES = frozenset({"neutral", "unrelated"})
55
+
56
+
57
+ def _resolve_device(device: Optional[str]) -> int | str:
58
+ """Best available device for transformers.pipeline(device=...), which
59
+ wants an int (-1=cpu, 0=cuda:0, ...) or a device string -- unlike
60
+ HFGenModel's backend, which takes a plain device string throughout.
61
+ """
62
+ if device is not None:
63
+ return device
64
+ import torch
65
+ if torch.cuda.is_available():
66
+ return 0
67
+ if torch.backends.mps.is_available():
68
+ return "mps"
69
+ return -1
70
+
71
+
72
+ @METRICS.register("encoder_judge")
73
+ class EncoderJudge(Metric):
74
+ """LLM-as-judge backed by a real encoder classifier, not a prompted LLM.
75
+
76
+ Works with **any** `AutoModelForSequenceClassification`-compatible
77
+ checkpoint — BERT, RoBERTa, DeBERTa, ALBERT, ELECTRA, XLNet, MPNet, and
78
+ so on; genericity comes for free from ``transformers``' own ``Auto*``
79
+ classes, no architecture-specific code here.
80
+
81
+ Parameters
82
+ ----------
83
+ model_name
84
+ Any encoder sequence-classification checkpoint
85
+ (``"microsoft/deberta-base-mnli"``, ``"roberta-large-mnli"``,
86
+ ``"textattack/bert-base-uncased-MNLI"``, a fine-tuned similarity/
87
+ toxicity/sentiment classifier, ...).
88
+ text_template, text_pair_template
89
+ Placeholder templates (``{input}``/``{output}``/``{target}``,
90
+ alias ``{expected}``/``{context}``, same field set as
91
+ :class:`~auditkit.metrics.judge.LLMJudge`) rendered into the two
92
+ text spans handed to the tokenizer's real pair-sequence encoding.
93
+ Default: candidate output as the first span, reference target as
94
+ the second (the standard NLI-as-judge setup — "does the output
95
+ entail the target"). Set ``text_pair_template=None`` for a
96
+ single-sequence classifier (e.g. a standalone toxicity/quality
97
+ classifier with no reference to compare against).
98
+ label_map
99
+ ``{label_name_or_index: score}`` — maps the checkpoint's own
100
+ output labels to a 0–1 score. Keys match case-insensitively against
101
+ ``model.config.id2label`` values, or against the numeric index
102
+ (as ``int`` or the literal string a generic checkpoint uses, e.g.
103
+ ``"LABEL_1"``). If omitted, tries to auto-detect a sensible
104
+ mapping from common NLI/sentiment-style label names (entailment/
105
+ positive/yes → 1.0, contradiction/negative/no → 0.0, neutral →
106
+ 0.5) — **raises** rather than guessing when the checkpoint's
107
+ labels aren't recognizable (e.g. bare ``LABEL_0``/``LABEL_1``),
108
+ since fabricating a mapping for unknown labels would silently
109
+ misjudge every single sample.
110
+ aggregation
111
+ ``"probability"`` (default): the score is the softmax-weighted sum
112
+ of every label's probability times its mapped score — a
113
+ continuous value reflecting the classifier's actual confidence,
114
+ not just its single best guess (e.g. an entailment probability of
115
+ 0.85 scores 0.85, not a rounded 1.0). ``"argmax"``: score is just
116
+ the mapped value of the single highest-probability label (a
117
+ cruder, binary-flavored pass/fail).
118
+ device
119
+ ``None`` (default) auto-detects CUDA/MPS/CPU, same as every other
120
+ real local backend in this repo. Pass an explicit
121
+ ``transformers.pipeline``-style device otherwise.
122
+
123
+ The returned :class:`Score` always carries ``reason`` (the predicted
124
+ label + its probability) and ``metadata`` (the full label→probability
125
+ distribution), so you can inspect exactly what the classifier saw and
126
+ decided, the same transparency :class:`~auditkit.metrics.judge.LLMJudge`
127
+ gives via its raw judge-model reply.
128
+
129
+ **Two real gotchas, confirmed live across multiple checkpoints:**
130
+
131
+ - **A generic checkpoint's label *order* is not standardized, even for
132
+ the nominally same task.** Two real MNLI-finetuned checkpoints tested
133
+ against each other disagreed: ``howey/electra-base-mnli`` uses
134
+ ``LABEL_0=entailment, LABEL_1=neutral, LABEL_2=contradiction``, while
135
+ a naively-assumed "standard" order (0=contradiction, 1=entailment,
136
+ 2=neutral, matching several other real checkpoints) is exactly
137
+ backwards for it. There is no way to know a generic checkpoint's real
138
+ label semantics without checking — this is exactly why
139
+ auto-detection refuses to guess for labels like ``LABEL_0``/
140
+ ``LABEL_1`` rather than assuming any particular index convention.
141
+ Determine the real order empirically (a couple of known
142
+ obviously-true/obviously-false example pairs, checking which index
143
+ lights up) before trusting a ``label_map`` you haven't verified.
144
+ - **``aggregation="probability"`` assumes single-label, mutually-
145
+ exclusive classification** (NLI/entailment, sentiment — probabilities
146
+ across all labels sum to ~1, so the weighted sum is a genuine soft
147
+ interpolation between verdicts). For an independent **multi-label**
148
+ classifier (e.g. `unitary/toxic-bert`, sigmoid outputs, several
149
+ labels can be true at once), mapping a label to ``0.0`` means "this
150
+ label contributes nothing to the score" — **not** "the complement of
151
+ this label." There is no way to express "1 − P(toxic)" through the
152
+ weighted-sum formula alone; if you want that framing specifically,
153
+ use a dedicated metric built for it (e.g. this repo's own
154
+ `ToxicityScore`) rather than `EncoderJudge`.
155
+ """
156
+
157
+ name = "encoder_judge"
158
+ kind = ScoreKind.JUDGE
159
+ direction = Direction.MAXIMIZE
160
+ is_deterministic = True
161
+ required_fields = frozenset({"target"})
162
+
163
+ def __init__(
164
+ self,
165
+ model_name: str = "microsoft/deberta-base-mnli",
166
+ *,
167
+ text_template: str = "{output}",
168
+ text_pair_template: Optional[str] = "{target}",
169
+ label_map: Optional[dict[Any, float]] = None,
170
+ aggregation: Literal["probability", "argmax"] = "probability",
171
+ device: Optional[str] = None,
172
+ name: str = "encoder_judge",
173
+ ) -> None:
174
+ self._model_name = model_name
175
+ self.text_template = text_template
176
+ self.text_pair_template = text_pair_template
177
+ self._label_map_arg = dict(label_map) if label_map else None
178
+ if aggregation not in ("probability", "argmax"):
179
+ raise ValueError(f"EncoderJudge: aggregation must be 'probability' or 'argmax', got {aggregation!r}")
180
+ self.aggregation = aggregation
181
+ self._device = device
182
+ self.name = name
183
+ self._pipe = None
184
+ self._label_map: Optional[dict[str, float]] = None # resolved lazily, needs id2label
185
+
186
+ def identity(self) -> dict:
187
+ return {
188
+ "name": self.name,
189
+ "model_name": self._model_name,
190
+ "text_template": self.text_template,
191
+ "text_pair_template": self.text_pair_template,
192
+ "label_map": self._label_map_arg,
193
+ "aggregation": self.aggregation,
194
+ }
195
+
196
+ # -- model / label-map resolution --------------------------------------
197
+
198
+ def _ensure_pipeline(self) -> None:
199
+ if self._pipe is not None:
200
+ return
201
+ try:
202
+ from transformers import pipeline
203
+ except ImportError:
204
+ raise ExtraNotInstalled("transformers", "pip install auditkit[transformers]") from None
205
+ self._pipe = pipeline(
206
+ "text-classification",
207
+ model=self._model_name,
208
+ device=_resolve_device(self._device),
209
+ top_k=None, # return every label's probability, not just the top one
210
+ )
211
+ self._label_map = self._resolve_label_map()
212
+
213
+ def _resolve_label_map(self) -> dict[str, float]:
214
+ id2label: dict[int, str] = dict(self._pipe.model.config.id2label)
215
+ if self._label_map_arg is not None:
216
+ return self._normalize_explicit_label_map(self._label_map_arg, id2label)
217
+ return self._auto_detect_label_map(id2label)
218
+
219
+ @staticmethod
220
+ def _normalize_explicit_label_map(raw: dict[Any, float], id2label: dict[int, str]) -> dict[str, float]:
221
+ """Resolve a user-supplied label_map (keyed by label name, case-
222
+ insensitive, or by numeric index) against the real label strings
223
+ the pipeline will actually emit."""
224
+ by_lower_name = {v.lower(): v for v in id2label.values()}
225
+ resolved: dict[str, float] = {}
226
+ for key, value in raw.items():
227
+ if isinstance(key, int) and key in id2label:
228
+ resolved[id2label[key]] = value
229
+ continue
230
+ key_str = str(key).lower()
231
+ if key_str in by_lower_name:
232
+ resolved[by_lower_name[key_str]] = value
233
+ continue
234
+ # Bare numeric string ("0", "1") against index-derived generic
235
+ # labels (LABEL_0, LABEL_1) that id2label itself may already use.
236
+ if key_str.isdigit() and int(key_str) in id2label:
237
+ resolved[id2label[int(key_str)]] = value
238
+ continue
239
+ raise ValueError(
240
+ f"EncoderJudge: label_map key {key!r} doesn't match any of this "
241
+ f"checkpoint's real labels {list(id2label.values())!r} (by name, "
242
+ f"case-insensitive, or by index)."
243
+ )
244
+ return resolved
245
+
246
+ @staticmethod
247
+ def _auto_detect_label_map(id2label: dict[int, str]) -> dict[str, float]:
248
+ detected: dict[str, float] = {}
249
+ for label in id2label.values():
250
+ norm = label.lower()
251
+ if norm in _POSITIVE_LABEL_NAMES:
252
+ detected[label] = 1.0
253
+ elif norm in _NEGATIVE_LABEL_NAMES:
254
+ detected[label] = 0.0
255
+ elif norm in _NEUTRAL_LABEL_NAMES:
256
+ detected[label] = 0.5
257
+ if len(detected) < len(id2label):
258
+ raise ValueError(
259
+ f"EncoderJudge: couldn't auto-detect a label_map for this "
260
+ f"checkpoint's labels {list(id2label.values())!r} -- pass "
261
+ f"label_map= explicitly, e.g. label_map={{{list(id2label.values())[0]!r}: 1.0, ...}}. "
262
+ f"Auto-detection only recognizes common NLI/sentiment-style "
263
+ f"names (entailment/positive/yes -> 1.0, contradiction/"
264
+ f"negative/no -> 0.0, neutral -> 0.5); a checkpoint with "
265
+ f"generic labels (LABEL_0/LABEL_1/...) can't be guessed "
266
+ f"without fabricating what they mean."
267
+ )
268
+ return detected
269
+
270
+ # -- templating ----------------------------------------------------------
271
+
272
+ def _render(self, template: str, sample: Sample, output: str) -> str:
273
+ fields = {
274
+ "input": getattr(sample, "input_text", None) or str(sample.input),
275
+ "output": output,
276
+ "expected": sample.target or "",
277
+ "target": sample.target or "",
278
+ "context": "\n".join(sample.retrieval_context or []),
279
+ }
280
+ for k, v in (sample.metadata or {}).items():
281
+ fields.setdefault(k, v)
282
+ text = template
283
+ for key, val in fields.items():
284
+ text = text.replace("{" + key + "}", str(val))
285
+ return text
286
+
287
+ # -- Metric API ------------------------------------------------------------
288
+
289
+ def score(self, sample: Sample, output: str, context: Any = None) -> Score:
290
+ self._ensure_pipeline()
291
+ text = self._render(self.text_template, sample, output)
292
+ if self.text_pair_template is not None:
293
+ text_pair = self._render(self.text_pair_template, sample, output)
294
+ pipeline_input = {"text": text, "text_pair": text_pair}
295
+ else:
296
+ pipeline_input = text
297
+ # truncation=True is required, not optional: BERT/RoBERTa/ELECTRA-
298
+ # style checkpoints use absolute position embeddings with a hard
299
+ # 512-token limit and crash outright (RuntimeError: tensor size
300
+ # mismatch) on longer combined input -- confirmed live with two
301
+ # long real paragraphs on a BERT checkpoint. DeBERTa (relative
302
+ # position embeddings, no hard limit -- its tokenizer even reports
303
+ # model_max_length as the classic "unbounded" sentinel value)
304
+ # doesn't strictly need this, but truncation=True is confirmed
305
+ # harmless for it too.
306
+ results = self._pipe(pipeline_input, truncation=True)
307
+ # transformers' pipeline has an inconsistent return shape depending
308
+ # on input form: a bare string is treated as "a batch of one" and
309
+ # comes back nested ([[{label,score}, ...]]), while a single
310
+ # {"text":..., "text_pair":...} dict comes back flat
311
+ # ([{label,score}, ...]) -- confirmed live, both forms used here
312
+ # depending on whether text_pair_template is set. Normalize either
313
+ # way to a flat list of per-label dicts.
314
+ if results and isinstance(results[0], list):
315
+ results = results[0]
316
+ probs = {r["label"]: r["score"] for r in results}
317
+ predicted_label = max(results, key=lambda r: r["score"])["label"]
318
+ predicted_prob = probs[predicted_label]
319
+
320
+ if self.aggregation == "argmax":
321
+ value = self._label_map.get(predicted_label, 0.0)
322
+ else:
323
+ value = sum(probs.get(label, 0.0) * mapped for label, mapped in self._label_map.items())
324
+
325
+ return Score(
326
+ name=self.name,
327
+ value=value,
328
+ kind=self.kind,
329
+ reason=f"predicted={predicted_label!r} probability={predicted_prob:.3f} aggregation={self.aggregation}",
330
+ metadata={"probabilities": probs, "predicted_label": predicted_label},
331
+ )
332
+
333
+
334
+ # ---------------------------------------------------------------------------
335
+ # Prebuilt judges — ready-to-use EncoderJudge subclasses for 6 real,
336
+ # live-verified checkpoints spanning 5 architecture families (see
337
+ # docs/encoder_judge.md's checkpoint table for the full verification
338
+ # detail). Every constructor argument still has the exact same meaning and
339
+ # is fully overridable -- these only supply the *correct, checkpoint-
340
+ # specific defaults* (model_name, label_map, template shape) so using one
341
+ # doesn't require knowing that checkpoint's real label semantics up front,
342
+ # mirroring how Factuality/ClosedQA/Relevance are LLMJudge with a
343
+ # pre-filled prompt/choices, not a different mechanism.
344
+ # ---------------------------------------------------------------------------
345
+
346
+ @METRICS.register("factuality_encoder_judge")
347
+ class FactualityEncoderJudge(EncoderJudge):
348
+ """Factual-consistency-as-judge via DeBERTa (v1) --
349
+ ``microsoft/deberta-base-mnli``. Real label names
350
+ (ENTAILMENT/NEUTRAL/CONTRADICTION); label_map is auto-detected
351
+ correctly with no configuration needed.
352
+
353
+ The one kept from what was originally three near-identical NLI
354
+ prebuilts (DeBERTa v1/v3, RoBERTa) -- all three used checkpoints with
355
+ real, auto-detectable labels, so they added a class name but no real
356
+ behavior difference over passing ``model_name=`` directly. DeBERTa v1
357
+ is the strongest of the three in practice: relative position
358
+ embeddings (no hard input-length limit, unlike RoBERTa/BERT/ELECTRA's
359
+ 512-token absolute limit) and empirically more confident/discriminative
360
+ than DeBERTa-v3 on ambiguous pairs (see the removed DebertaV3NLIJudge's
361
+ old docstring: 0.33 vs ~0.001 for the same "unrelated sentences" test).
362
+ Named for its actual use case (checking whether a claim/answer is
363
+ factually consistent with a reference), mirroring ``Factuality`` on
364
+ ``LLMJudge`` -- same use case, two different judge mechanisms."""
365
+
366
+ def __init__(
367
+ self,
368
+ model_name: str = "microsoft/deberta-base-mnli",
369
+ *,
370
+ text_template: str = "{output}",
371
+ text_pair_template: Optional[str] = "{target}",
372
+ label_map: Optional[dict[Any, float]] = None,
373
+ aggregation: Literal["probability", "argmax"] = "probability",
374
+ device: Optional[str] = None,
375
+ name: str = "factuality_encoder_judge",
376
+ ) -> None:
377
+ super().__init__(
378
+ model_name=model_name, text_template=text_template, text_pair_template=text_pair_template,
379
+ label_map=label_map, aggregation=aggregation, device=device, name=name,
380
+ )
381
+
382
+
383
+ @METRICS.register("sentiment_encoder_judge")
384
+ class SentimentEncoderJudge(EncoderJudge):
385
+ """Sentiment-as-judge via DistilBERT --
386
+ ``distilbert-base-uncased-finetuned-sst-2-english``. The only
387
+ remaining prebuilt with a genuinely different *task* from
388
+ :class:`FactualityEncoderJudge` (2-way sentiment, not NLI entailment)
389
+ and single-sequence (no reference needed) rather than pair mode --
390
+ demonstrates ``text_pair_template=None`` as a prebuilt, not just a
391
+ base-class option. Judges ``output`` alone; ``target`` is unused.
392
+
393
+ Originally shipped alongside ``BertNLIJudge``/``ElectraNLIJudge``,
394
+ both since removed: both did the exact same task as
395
+ ``FactualityEncoderJudge`` (entailment/factual-consistency) on
396
+ checkpoints whose only distinguishing feature was an unrecoverable,
397
+ empirically-verified generic label order (``textattack/bert-base-
398
+ uncased-MNLI``: ``LABEL_0=contradiction, LABEL_1=entailment,
399
+ LABEL_2=neutral``; ``howey/electra-base-mnli``: the OPPOSITE order,
400
+ ``LABEL_0=entailment, LABEL_1=neutral, LABEL_2=contradiction``) --
401
+ real, checkpoint-specific knowledge worth documenting (see
402
+ ``docs/encoder_judge.md``), but not a distinct enough task to justify
403
+ two more classes with no behavior difference from
404
+ ``FactualityEncoderJudge`` beyond which checkpoint they pin."""
405
+
406
+ required_fields = frozenset()
407
+
408
+ def __init__(
409
+ self,
410
+ model_name: str = "distilbert-base-uncased-finetuned-sst-2-english",
411
+ *,
412
+ text_template: str = "{output}",
413
+ text_pair_template: Optional[str] = None,
414
+ label_map: Optional[dict[Any, float]] = None,
415
+ aggregation: Literal["probability", "argmax"] = "probability",
416
+ device: Optional[str] = None,
417
+ name: str = "sentiment_encoder_judge",
418
+ ) -> None:
419
+ super().__init__(
420
+ model_name=model_name, text_template=text_template, text_pair_template=text_pair_template,
421
+ label_map=label_map if label_map is not None else {"POSITIVE": 1.0, "NEGATIVE": 0.0},
422
+ aggregation=aggregation, device=device, name=name,
423
+ )