evalcore 2.4.0__tar.gz → 2.4.2__tar.gz
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.
- {evalcore-2.4.0 → evalcore-2.4.2}/CHANGELOG.md +67 -1
- {evalcore-2.4.0 → evalcore-2.4.2}/PKG-INFO +1 -1
- {evalcore-2.4.0 → evalcore-2.4.2}/pyproject.toml +1 -1
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/judge.py +39 -5
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_judge.py +89 -9
- {evalcore-2.4.0 → evalcore-2.4.2}/uv.lock +1 -1
- {evalcore-2.4.0 → evalcore-2.4.2}/.github/workflows/ci.yml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/.github/workflows/publish.yml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/.gitignore +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/.pre-commit-config.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/LICENSE +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/README.md +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/docs/design.md +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/README.md +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/adapter.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/angry_complaint.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/billing_question.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/double_charge.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/refund_request.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_judge.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_pairwise.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_replay.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/graders.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/run_eval.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/suite.yaml +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/tests/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/examples/quickstart/tests/test_quickstart.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/justfile +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/pyrightconfig.json +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/adapters/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/adapters/base.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/adapters/env.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/adapters/http.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/adapters/replay.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/cli.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/compare.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/errors.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/base.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/classification.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/deterministic.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/graders/numeric.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/loader.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/models.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/pairwise.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/py.typed +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/rating.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/refs.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/report.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/reporters/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/reporters/base.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/reporters/html.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/reporters/markdown.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/retry.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/runner.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/store.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/src/evalcore/sweep.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/__init__.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_adapters.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_cli.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_edge_cases.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_judge_extra.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_live_clients.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_pairwise_extra.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_rating.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_rating_server.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_reporters.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_retry.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_runner.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_store.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_sweep_pairwise.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/tests/test_unit.py +0 -0
- {evalcore-2.4.0 → evalcore-2.4.2}/uv.toml +0 -0
|
@@ -6,6 +6,70 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.4.2] - 2026-08-09
|
|
10
|
+
|
|
11
|
+
Every `llm_as_judge` row now describes what it measures.
|
|
12
|
+
|
|
13
|
+
Shipped as a patch. It changes what published rows contain, so the 1.0.0
|
|
14
|
+
policy would call it a minor; it goes out as 2.4.2 as a deliberate exception,
|
|
15
|
+
alongside 2.4.1's judge-pin fix that it completes. No schema change - the
|
|
16
|
+
columns already exist and were empty.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- A judge's dimension row (`<grader>.<dimension>`) carried no judge
|
|
20
|
+
information at all: no `judges.*`, and `judge_scale` of 0. So `0.400` on a
|
|
21
|
+
row could not say who scored it, on what scale, or that the raw number was
|
|
22
|
+
2 out of 5 - the scale lived only on `<grader>.overall`, and recovering the
|
|
23
|
+
raw point meant joining back to it.
|
|
24
|
+
|
|
25
|
+
Each dimension row now carries the verdict for the dimension it measures:
|
|
26
|
+
`judges.name`, `judges.version`, `judges.rationale`, `judge_scale`, a
|
|
27
|
+
`judges.points` **scoped to that dimension**, and a `judges.score` that is
|
|
28
|
+
that judge's number for it.
|
|
29
|
+
|
|
30
|
+
`<grader>.overall` is unchanged and still carries the full points map.
|
|
31
|
+
|
|
32
|
+
This establishes one invariant across every judge row, dimension rows
|
|
33
|
+
included: `value == mean(judges.score)`. With one judge they are equal;
|
|
34
|
+
with a panel, `value` is the mean and `judges.score` shows the spread - so
|
|
35
|
+
per-dimension disagreement is readable from the dimension row instead of
|
|
36
|
+
by unpacking the map on `.overall`.
|
|
37
|
+
|
|
38
|
+
**Upgrading:** `notEmpty(judges.name)` now matches every judge row rather
|
|
39
|
+
than only `<grader>.overall`, so a six-case run goes from 12 judged rows to
|
|
40
|
+
48. Anything aggregating over judges must filter to `.overall` or it
|
|
41
|
+
multiplies by the dimension count. That is a change in results, not an error.
|
|
42
|
+
|
|
43
|
+
## [2.4.1] - 2026-08-09
|
|
44
|
+
|
|
45
|
+
The judge's identity is now in its provenance pin.
|
|
46
|
+
|
|
47
|
+
Shipped as a patch. Both changes below alter a published value, so the 1.0.0
|
|
48
|
+
policy would call this a minor at least; it goes out as 2.4.1 as a deliberate
|
|
49
|
+
exception, because the old pin was answering a provenance question wrongly
|
|
50
|
+
and the sooner it stops the fewer runs are affected.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- `Scorecard.judge_version` includes the judge's model:
|
|
54
|
+
`anthropic:claude-sonnet-4-6@v1` rather than `judge@v1`. It was
|
|
55
|
+
`key@judge_version`, so swapping the judge's model while leaving the
|
|
56
|
+
declared `judge_version` alone produced a byte-identical pin - and any
|
|
57
|
+
provenance check reading that field passed a comparison against a baseline
|
|
58
|
+
scored by a different model. The model is the thing most likely to change
|
|
59
|
+
and the thing a declared version is most likely to miss.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- A single judge's key defaults to its provider (`anthropic`) instead of the
|
|
63
|
+
literal `judge`. A panel already defaulted to `key or provider`, so the
|
|
64
|
+
one-judge case was the odd one out, and `judge` named nothing the `grader`
|
|
65
|
+
column did not already say. It appears in the `judges.name` column.
|
|
66
|
+
|
|
67
|
+
**Upgrading:** every suite with a judge re-baselines once, because
|
|
68
|
+
`judge_version` is part of what identifies a comparable run - which is the
|
|
69
|
+
intended behaviour, just paid all at once. Queries filtering
|
|
70
|
+
`judges.name = 'judge'` need the provider instead. Set `key:` on a `judges:`
|
|
71
|
+
entry to pin a name of your own.
|
|
72
|
+
|
|
9
73
|
## [2.4.0] - 2026-08-09
|
|
10
74
|
|
|
11
75
|
A run without a baseline can now say whether it passed.
|
|
@@ -248,7 +312,9 @@ by semantic versioning: a breaking change to either means a 2.0.
|
|
|
248
312
|
rating + ranking with judge agreement, Markdown/HTML reporters, JSON +
|
|
249
313
|
column-store outbox, and content-hash provenance.
|
|
250
314
|
|
|
251
|
-
[Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.
|
|
315
|
+
[Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.2...HEAD
|
|
316
|
+
[2.4.2]: https://github.com/scottpmiller/evalcore/compare/2.4.1...2.4.2
|
|
317
|
+
[2.4.1]: https://github.com/scottpmiller/evalcore/compare/2.4.0...2.4.1
|
|
252
318
|
[2.4.0]: https://github.com/scottpmiller/evalcore/compare/2.3.0...2.4.0
|
|
253
319
|
[2.3.0]: https://github.com/scottpmiller/evalcore/compare/2.2.0...2.3.0
|
|
254
320
|
[2.2.0]: https://github.com/scottpmiller/evalcore/compare/2.1.0...2.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evalcore
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: A generic, consumer-agnostic evaluation engine for prompt, model, and API outputs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/scottpmiller/evalcore
|
|
6
6
|
Project-URL: Repository, https://github.com/scottpmiller/evalcore
|
|
@@ -358,7 +358,11 @@ class RubricJudge:
|
|
|
358
358
|
else:
|
|
359
359
|
self.judges = [
|
|
360
360
|
{
|
|
361
|
-
'
|
|
361
|
+
# The provider, not a literal 'judge'. A single judge is
|
|
362
|
+
# a panel of one, so it names itself the same way a panel
|
|
363
|
+
# member does; 'judge' carried no information and made
|
|
364
|
+
# the one-judge case the odd one out.
|
|
365
|
+
'key': 'anthropic',
|
|
362
366
|
'provider': 'anthropic',
|
|
363
367
|
'model': _env_expand(model),
|
|
364
368
|
'api_key_env': None,
|
|
@@ -394,17 +398,27 @@ class RubricJudge:
|
|
|
394
398
|
|
|
395
399
|
@property
|
|
396
400
|
def judge_version(self) -> str:
|
|
397
|
-
"""Provenance pin: ``key@version`` per configured judge (a
|
|
398
|
-
joins them, comma-separated).
|
|
401
|
+
"""Provenance pin: ``key:model@version`` per configured judge (a
|
|
402
|
+
panel joins them, comma-separated).
|
|
399
403
|
|
|
400
404
|
The runner reads this onto ``Scorecard.judge_version`` so a judge
|
|
401
405
|
model / prompt / scale change surfaces as a re-baseline event rather
|
|
402
406
|
than hiding in each score's ``detail``. Uses the configured judges,
|
|
403
407
|
not the mode-filtered active set, so the pin is stable across
|
|
404
408
|
environments.
|
|
409
|
+
|
|
410
|
+
The model is in the pin because it is the thing most likely to
|
|
411
|
+
change and the thing a declared version is most likely to miss.
|
|
412
|
+
Without it, swapping the judge's model while leaving
|
|
413
|
+
``judge_version`` alone produced a byte-identical pin - so a
|
|
414
|
+
comparison against a baseline scored by a different model passed
|
|
415
|
+
every provenance check that reads this field.
|
|
405
416
|
"""
|
|
406
417
|
return ','.join(
|
|
407
|
-
f'{j["key"]}@{j["judge_version"]}'
|
|
418
|
+
f'{j["key"]}:{j["model"]}@{j["judge_version"]}'
|
|
419
|
+
if j['model']
|
|
420
|
+
else f'{j["key"]}@{j["judge_version"]}'
|
|
421
|
+
for j in self.judges
|
|
408
422
|
)
|
|
409
423
|
|
|
410
424
|
def set_mode(self, mode: str) -> None:
|
|
@@ -565,6 +579,14 @@ class RubricJudge:
|
|
|
565
579
|
|
|
566
580
|
# Panel mean per dimension (identical to the single judge's value
|
|
567
581
|
# when there is only one).
|
|
582
|
+
#
|
|
583
|
+
# Each dimension row carries the verdict for the dimension it
|
|
584
|
+
# measures, not the whole one: points scoped to this key, and a score
|
|
585
|
+
# that is this judge's number for it. That makes `value ==
|
|
586
|
+
# mean(judges.score)` hold on every judge row, so per-dimension
|
|
587
|
+
# disagreement is readable without unpacking the map on `.overall`.
|
|
588
|
+
# Carrying the full map here would put two irrelevant numbers on a
|
|
589
|
+
# row and invite reading the wrong one.
|
|
568
590
|
panel_dims: list[float] = []
|
|
569
591
|
for key, _ in self.dimensions:
|
|
570
592
|
per_judge = [
|
|
@@ -574,7 +596,19 @@ class RubricJudge:
|
|
|
574
596
|
value = sum(present) / len(present) if present else None
|
|
575
597
|
if value is not None:
|
|
576
598
|
panel_dims.append(value)
|
|
577
|
-
|
|
599
|
+
dim_details = [
|
|
600
|
+
models.JudgeDetail(
|
|
601
|
+
key=judge['key'],
|
|
602
|
+
version=judge['judge_version'],
|
|
603
|
+
rationale=rationales.get(judge['key']),
|
|
604
|
+
points={key: raw.get(judge['key'], {}).get(key)},
|
|
605
|
+
overall=self._normalize(
|
|
606
|
+
raw.get(judge['key'], {}).get(key)
|
|
607
|
+
),
|
|
608
|
+
)
|
|
609
|
+
for judge in active
|
|
610
|
+
]
|
|
611
|
+
out.append(score(f'{self.name}.{key}', value, judges=dim_details))
|
|
578
612
|
|
|
579
613
|
overall = sum(panel_dims) / len(panel_dims) if panel_dims else None
|
|
580
614
|
out.append(
|
|
@@ -59,13 +59,62 @@ class RubricJudgeTests(unittest.TestCase):
|
|
|
59
59
|
# raw 1..scale points are retained, not just the normalized mean
|
|
60
60
|
self.assertEqual(jd.points, {'clarity': 5, 'specificity': 4})
|
|
61
61
|
self.assertAlmostEqual(jd.overall, 0.9)
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
# the overall score carries the whole breakdown
|
|
63
|
+
self.assertEqual(jd.points, {'clarity': 5, 'specificity': 4})
|
|
64
|
+
|
|
65
|
+
def test_a_dimension_row_carries_its_own_verdict(self):
|
|
66
|
+
"""Every judge row describes what that row measures.
|
|
67
|
+
|
|
68
|
+
The dimension row used to carry nothing, so `0.8` alone could not say
|
|
69
|
+
who scored it, on what scale, or that the raw number was 4.
|
|
70
|
+
"""
|
|
71
|
+
grader = self._grader(
|
|
72
|
+
{
|
|
73
|
+
'Great subject': {
|
|
74
|
+
'scores': {'clarity': 5, 'specificity': 4},
|
|
75
|
+
'rationale': 'Clear and specific enough.',
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
scores = _grade(
|
|
80
|
+
grader,
|
|
81
|
+
models.Case(id='c1'),
|
|
82
|
+
models.Output(fields={'text': 'Great subject'}),
|
|
83
|
+
)
|
|
84
|
+
dim = next(s for s in scores if s.metric == 'llm_judge.specificity')
|
|
85
|
+
self.assertEqual(len(dim.judges), 1)
|
|
86
|
+
jd = dim.judges[0]
|
|
87
|
+
# Scoped: only the point this row is about.
|
|
88
|
+
self.assertEqual(jd.points, {'specificity': 4})
|
|
89
|
+
self.assertAlmostEqual(jd.overall, 0.8)
|
|
90
|
+
self.assertEqual(jd.rationale, 'Clear and specific enough.')
|
|
91
|
+
|
|
92
|
+
def test_value_is_the_mean_of_the_judge_scores(self):
|
|
93
|
+
"""The invariant that holds on every judge row, dimensions included:
|
|
94
|
+
value == mean(judges.score)."""
|
|
95
|
+
grader = self._grader(
|
|
96
|
+
{'X': {'scores': {'clarity': 5, 'specificity': 2}}}
|
|
97
|
+
)
|
|
98
|
+
scores = _grade(
|
|
99
|
+
grader, models.Case(id='c'), models.Output(fields={'text': 'X'})
|
|
100
|
+
)
|
|
101
|
+
judged = [s for s in scores if s.judges and s.value is not None]
|
|
102
|
+
self.assertTrue(judged)
|
|
103
|
+
for s in judged:
|
|
104
|
+
mean = sum(j.overall for j in s.judges) / len(s.judges)
|
|
105
|
+
self.assertAlmostEqual(s.value, mean, msg=s.metric)
|
|
106
|
+
|
|
107
|
+
def test_the_overall_row_keeps_the_full_map(self):
|
|
108
|
+
grader = self._grader(
|
|
109
|
+
{'X': {'scores': {'clarity': 5, 'specificity': 4}}}
|
|
110
|
+
)
|
|
111
|
+
scores = _grade(
|
|
112
|
+
grader, models.Case(id='c'), models.Output(fields={'text': 'X'})
|
|
113
|
+
)
|
|
114
|
+
overall = next(s for s in scores if s.metric == 'llm_judge.overall')
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
overall.judges[0].points, {'clarity': 5, 'specificity': 4}
|
|
117
|
+
)
|
|
69
118
|
|
|
70
119
|
def test_different_content_gets_different_scores(self):
|
|
71
120
|
grader = self._grader(
|
|
@@ -125,7 +174,8 @@ class RubricJudgeTests(unittest.TestCase):
|
|
|
125
174
|
replay_path='f.yaml',
|
|
126
175
|
judge_version='v3',
|
|
127
176
|
)
|
|
128
|
-
|
|
177
|
+
# No model configured (replay-only), so the pin is key@version.
|
|
178
|
+
self.assertEqual(single.judge_version, 'anthropic@v3')
|
|
129
179
|
panel = judge.RubricJudge(
|
|
130
180
|
content_ref='output.text',
|
|
131
181
|
dimensions=DIMENSIONS,
|
|
@@ -139,7 +189,37 @@ class RubricJudgeTests(unittest.TestCase):
|
|
|
139
189
|
},
|
|
140
190
|
],
|
|
141
191
|
)
|
|
142
|
-
self.assertEqual(panel.judge_version, 'claude@v2,gpt@v5')
|
|
192
|
+
self.assertEqual(panel.judge_version, 'claude:m@v2,gpt:openai:g@v5')
|
|
193
|
+
|
|
194
|
+
def test_the_model_is_in_the_pin(self):
|
|
195
|
+
"""Swapping the judge's model has to move the pin.
|
|
196
|
+
|
|
197
|
+
Without this, a model swap that left `judge_version` alone produced
|
|
198
|
+
a byte-identical pin, so a comparison against a baseline scored by a
|
|
199
|
+
different model passed every provenance check reading this field.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def _judge(model):
|
|
203
|
+
return judge.RubricJudge(
|
|
204
|
+
content_ref='output.text',
|
|
205
|
+
dimensions=DIMENSIONS,
|
|
206
|
+
model=model,
|
|
207
|
+
judge_version='v1',
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
before = _judge('claude-sonnet-4-6').judge_version
|
|
211
|
+
after = _judge('claude-opus-4-1').judge_version
|
|
212
|
+
self.assertEqual(before, 'anthropic:claude-sonnet-4-6@v1')
|
|
213
|
+
self.assertNotEqual(before, after)
|
|
214
|
+
|
|
215
|
+
def test_a_single_judge_names_itself_by_provider(self):
|
|
216
|
+
"""Not the literal 'judge', which carried no information."""
|
|
217
|
+
single = judge.RubricJudge(
|
|
218
|
+
content_ref='output.text',
|
|
219
|
+
dimensions=DIMENSIONS,
|
|
220
|
+
replay_path='f.yaml',
|
|
221
|
+
)
|
|
222
|
+
self.assertEqual(single.judges[0]['key'], 'anthropic')
|
|
143
223
|
|
|
144
224
|
def test_transient_client_error_retried_via_set_retry(self):
|
|
145
225
|
class _RateLimit(Exception):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|