evalcore 2.4.1__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.1 → evalcore-2.4.2}/CHANGELOG.md +36 -1
- {evalcore-2.4.1 → evalcore-2.4.2}/PKG-INFO +1 -1
- {evalcore-2.4.1 → evalcore-2.4.2}/pyproject.toml +1 -1
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/judge.py +21 -1
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_judge.py +56 -7
- {evalcore-2.4.1 → evalcore-2.4.2}/uv.lock +1 -1
- {evalcore-2.4.1 → evalcore-2.4.2}/.github/workflows/ci.yml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/.github/workflows/publish.yml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/.gitignore +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/.pre-commit-config.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/LICENSE +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/README.md +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/docs/design.md +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/README.md +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/adapter.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/angry_complaint.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/billing_question.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/double_charge.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/datasets/support_reply/v1/cases/refund_request.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_judge.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_pairwise.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/fixtures/support_reply_replay.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/graders.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/run_eval.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/suite.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/tests/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/examples/quickstart/tests/test_quickstart.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/justfile +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/pyrightconfig.json +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/adapters/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/adapters/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/adapters/env.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/adapters/http.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/adapters/replay.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/cli.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/compare.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/errors.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/classification.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/deterministic.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/graders/numeric.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/loader.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/models.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/pairwise.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/py.typed +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/rating.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/refs.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/report.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/reporters/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/reporters/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/reporters/html.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/reporters/markdown.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/retry.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/runner.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/store.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/src/evalcore/sweep.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_adapters.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_cli.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_edge_cases.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_judge_extra.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_live_clients.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_pairwise_extra.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_rating.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_rating_server.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_reporters.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_retry.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_runner.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_store.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_sweep_pairwise.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/tests/test_unit.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.2}/uv.toml +0 -0
|
@@ -6,6 +6,40 @@ 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
|
+
|
|
9
43
|
## [2.4.1] - 2026-08-09
|
|
10
44
|
|
|
11
45
|
The judge's identity is now in its provenance pin.
|
|
@@ -278,7 +312,8 @@ by semantic versioning: a breaking change to either means a 2.0.
|
|
|
278
312
|
rating + ranking with judge agreement, Markdown/HTML reporters, JSON +
|
|
279
313
|
column-store outbox, and content-hash provenance.
|
|
280
314
|
|
|
281
|
-
[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
|
|
282
317
|
[2.4.1]: https://github.com/scottpmiller/evalcore/compare/2.4.0...2.4.1
|
|
283
318
|
[2.4.0]: https://github.com/scottpmiller/evalcore/compare/2.3.0...2.4.0
|
|
284
319
|
[2.3.0]: https://github.com/scottpmiller/evalcore/compare/2.2.0...2.3.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
|
|
@@ -579,6 +579,14 @@ class RubricJudge:
|
|
|
579
579
|
|
|
580
580
|
# Panel mean per dimension (identical to the single judge's value
|
|
581
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.
|
|
582
590
|
panel_dims: list[float] = []
|
|
583
591
|
for key, _ in self.dimensions:
|
|
584
592
|
per_judge = [
|
|
@@ -588,7 +596,19 @@ class RubricJudge:
|
|
|
588
596
|
value = sum(present) / len(present) if present else None
|
|
589
597
|
if value is not None:
|
|
590
598
|
panel_dims.append(value)
|
|
591
|
-
|
|
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))
|
|
592
612
|
|
|
593
613
|
overall = sum(panel_dims) / len(panel_dims) if panel_dims else None
|
|
594
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(
|
|
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
|