evalcore 2.4.1__tar.gz → 2.4.3__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.3}/CHANGELOG.md +65 -1
- {evalcore-2.4.1 → evalcore-2.4.3}/PKG-INFO +3 -3
- {evalcore-2.4.1 → evalcore-2.4.3}/README.md +1 -1
- {evalcore-2.4.1 → evalcore-2.4.3}/pyproject.toml +1 -1
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/judge.py +24 -5
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/pairwise.py +2 -3
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_judge.py +56 -7
- {evalcore-2.4.1 → evalcore-2.4.3}/uv.lock +1 -1
- {evalcore-2.4.1 → evalcore-2.4.3}/.github/workflows/ci.yml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/.github/workflows/publish.yml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/.gitignore +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/.pre-commit-config.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/LICENSE +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/docs/design.md +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/README.md +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/adapter.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/datasets/support_reply/v1/cases/angry_complaint.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/datasets/support_reply/v1/cases/billing_question.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/datasets/support_reply/v1/cases/double_charge.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/datasets/support_reply/v1/cases/refund_request.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/fixtures/support_reply_judge.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/fixtures/support_reply_pairwise.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/fixtures/support_reply_replay.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/graders.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/run_eval.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/suite.yaml +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/tests/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/examples/quickstart/tests/test_quickstart.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/justfile +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/pyrightconfig.json +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/adapters/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/adapters/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/adapters/env.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/adapters/http.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/adapters/replay.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/cli.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/compare.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/errors.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/classification.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/deterministic.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/graders/numeric.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/loader.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/models.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/py.typed +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/rating.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/refs.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/report.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/reporters/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/reporters/base.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/reporters/html.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/reporters/markdown.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/retry.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/runner.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/store.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/src/evalcore/sweep.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/__init__.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_adapters.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_cli.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_edge_cases.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_judge_extra.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_live_clients.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_pairwise_extra.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_rating.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_rating_server.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_reporters.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_retry.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_runner.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_store.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_sweep_pairwise.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/tests/test_unit.py +0 -0
- {evalcore-2.4.1 → evalcore-2.4.3}/uv.toml +0 -0
|
@@ -6,6 +6,68 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.4.3] - 2026-08-11
|
|
10
|
+
|
|
11
|
+
Live Anthropic judges work on current Claude models again.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- The Anthropic judge and pairwise clients no longer send `temperature=0`.
|
|
15
|
+
`temperature` (with `top_p`/`top_k`) was removed from the Claude request
|
|
16
|
+
surface at Opus 4.7, and sending it at all is a 400 there and on every model
|
|
17
|
+
after it - so a judge or a pairwise comparison pinned to `claude-opus-4-7`,
|
|
18
|
+
`claude-opus-4-8`, `claude-opus-5`, `claude-sonnet-5` or `claude-fable-5`
|
|
19
|
+
failed every call. The OpenAI clients still send it; that API still takes it.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Live judge `max_tokens` defaults are now 8192 (from 1024 on the rubric
|
|
23
|
+
judge, 512 on pairwise). Thinking is on by default from Opus 5 and Sonnet 5
|
|
24
|
+
onward and `max_tokens` bounds thinking plus reply together, so a
|
|
25
|
+
1024-token budget could be spent on reasoning before the forced tool call
|
|
26
|
+
landed - which surfaced as error scores rather than as an error. It is a
|
|
27
|
+
ceiling, not a spend: a model that does not think generates the same handful
|
|
28
|
+
of tokens it did before.
|
|
29
|
+
|
|
30
|
+
**Upgrading:** a judge on a model that still accepts `temperature` (Sonnet
|
|
31
|
+
4.6, Opus 4.6, the 4.5 line and older) now samples at the API default instead
|
|
32
|
+
of 0, so its scores are no longer pinned run to run - expect more variance in
|
|
33
|
+
a rubric dimension or a win-rate than before, and re-baseline if a gate sits
|
|
34
|
+
close to its threshold. A judge on a thinking model also now bills thinking
|
|
35
|
+
tokens on every call. Pass `max_tokens=` to a client to keep the old budget.
|
|
36
|
+
|
|
37
|
+
## [2.4.2] - 2026-08-09
|
|
38
|
+
|
|
39
|
+
Every `llm_as_judge` row now describes what it measures.
|
|
40
|
+
|
|
41
|
+
Shipped as a patch. It changes what published rows contain, so the 1.0.0
|
|
42
|
+
policy would call it a minor; it goes out as 2.4.2 as a deliberate exception,
|
|
43
|
+
alongside 2.4.1's judge-pin fix that it completes. No schema change - the
|
|
44
|
+
columns already exist and were empty.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- A judge's dimension row (`<grader>.<dimension>`) carried no judge
|
|
48
|
+
information at all: no `judges.*`, and `judge_scale` of 0. So `0.400` on a
|
|
49
|
+
row could not say who scored it, on what scale, or that the raw number was
|
|
50
|
+
2 out of 5 - the scale lived only on `<grader>.overall`, and recovering the
|
|
51
|
+
raw point meant joining back to it.
|
|
52
|
+
|
|
53
|
+
Each dimension row now carries the verdict for the dimension it measures:
|
|
54
|
+
`judges.name`, `judges.version`, `judges.rationale`, `judge_scale`, a
|
|
55
|
+
`judges.points` **scoped to that dimension**, and a `judges.score` that is
|
|
56
|
+
that judge's number for it.
|
|
57
|
+
|
|
58
|
+
`<grader>.overall` is unchanged and still carries the full points map.
|
|
59
|
+
|
|
60
|
+
This establishes one invariant across every judge row, dimension rows
|
|
61
|
+
included: `value == mean(judges.score)`. With one judge they are equal;
|
|
62
|
+
with a panel, `value` is the mean and `judges.score` shows the spread - so
|
|
63
|
+
per-dimension disagreement is readable from the dimension row instead of
|
|
64
|
+
by unpacking the map on `.overall`.
|
|
65
|
+
|
|
66
|
+
**Upgrading:** `notEmpty(judges.name)` now matches every judge row rather
|
|
67
|
+
than only `<grader>.overall`, so a six-case run goes from 12 judged rows to
|
|
68
|
+
48. Anything aggregating over judges must filter to `.overall` or it
|
|
69
|
+
multiplies by the dimension count. That is a change in results, not an error.
|
|
70
|
+
|
|
9
71
|
## [2.4.1] - 2026-08-09
|
|
10
72
|
|
|
11
73
|
The judge's identity is now in its provenance pin.
|
|
@@ -278,7 +340,9 @@ by semantic versioning: a breaking change to either means a 2.0.
|
|
|
278
340
|
rating + ranking with judge agreement, Markdown/HTML reporters, JSON +
|
|
279
341
|
column-store outbox, and content-hash provenance.
|
|
280
342
|
|
|
281
|
-
[Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.
|
|
343
|
+
[Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.3...HEAD
|
|
344
|
+
[2.4.3]: https://github.com/scottpmiller/evalcore/compare/2.4.2...2.4.3
|
|
345
|
+
[2.4.2]: https://github.com/scottpmiller/evalcore/compare/2.4.1...2.4.2
|
|
282
346
|
[2.4.1]: https://github.com/scottpmiller/evalcore/compare/2.4.0...2.4.1
|
|
283
347
|
[2.4.0]: https://github.com/scottpmiller/evalcore/compare/2.3.0...2.4.0
|
|
284
348
|
[2.3.0]: https://github.com/scottpmiller/evalcore/compare/2.2.0...2.3.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: evalcore
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.3
|
|
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
|
|
@@ -327,7 +327,7 @@ degrade to `null`. `compare`'s guardrails and a `win_metric` with
|
|
|
327
327
|
`generation_cost` or `tool_error_rate` alongside quality judges.
|
|
328
328
|
|
|
329
329
|
The judge runs live (`AnthropicJudgeClient` forced tool call, or
|
|
330
|
-
`OpenAIJudgeClient` `json_schema`
|
|
330
|
+
`OpenAIJudgeClient` `json_schema` at temperature 0 - needing the `judge`
|
|
331
331
|
extra plus `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`) or offline
|
|
332
332
|
(`ReplayJudgeClient`), chosen by the run mode like the adapter. Each
|
|
333
333
|
dimension becomes a metric `<name>.<key>` plus a `<name>.overall` mean.
|
|
@@ -295,7 +295,7 @@ degrade to `null`. `compare`'s guardrails and a `win_metric` with
|
|
|
295
295
|
`generation_cost` or `tool_error_rate` alongside quality judges.
|
|
296
296
|
|
|
297
297
|
The judge runs live (`AnthropicJudgeClient` forced tool call, or
|
|
298
|
-
`OpenAIJudgeClient` `json_schema`
|
|
298
|
+
`OpenAIJudgeClient` `json_schema` at temperature 0 - needing the `judge`
|
|
299
299
|
extra plus `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`) or offline
|
|
300
300
|
(`ReplayJudgeClient`), chosen by the run mode like the adapter. Each
|
|
301
301
|
dimension becomes a metric `<name>.<key>` plus a `<name>.overall` mean.
|
|
@@ -104,7 +104,7 @@ class AnthropicJudgeClient:
|
|
|
104
104
|
self,
|
|
105
105
|
model: str,
|
|
106
106
|
api_key_env: str = 'ANTHROPIC_API_KEY',
|
|
107
|
-
max_tokens: int =
|
|
107
|
+
max_tokens: int = 8192,
|
|
108
108
|
timeout: float = 30.0,
|
|
109
109
|
):
|
|
110
110
|
self.model = model
|
|
@@ -156,7 +156,6 @@ class AnthropicJudgeClient:
|
|
|
156
156
|
response = await client.messages.create(
|
|
157
157
|
model=self.model,
|
|
158
158
|
max_tokens=self.max_tokens,
|
|
159
|
-
temperature=0,
|
|
160
159
|
timeout=self.timeout,
|
|
161
160
|
system=system,
|
|
162
161
|
tools=[tool],
|
|
@@ -179,7 +178,7 @@ class OpenAIJudgeClient:
|
|
|
179
178
|
self,
|
|
180
179
|
model: str,
|
|
181
180
|
api_key_env: str = 'OPENAI_API_KEY',
|
|
182
|
-
max_tokens: int =
|
|
181
|
+
max_tokens: int = 8192,
|
|
183
182
|
timeout: float = 30.0,
|
|
184
183
|
):
|
|
185
184
|
# Accept a 'provider:model' id (e.g. 'openai:gpt-4o'); SDK wants bare.
|
|
@@ -326,7 +325,7 @@ class RubricJudge:
|
|
|
326
325
|
judge_version: str = 'v1',
|
|
327
326
|
replay_path: str | None = None,
|
|
328
327
|
client: JudgeClient | None = None,
|
|
329
|
-
max_tokens: int =
|
|
328
|
+
max_tokens: int = 8192,
|
|
330
329
|
):
|
|
331
330
|
self.name = name
|
|
332
331
|
self.content_ref = content_ref
|
|
@@ -579,6 +578,14 @@ class RubricJudge:
|
|
|
579
578
|
|
|
580
579
|
# Panel mean per dimension (identical to the single judge's value
|
|
581
580
|
# when there is only one).
|
|
581
|
+
#
|
|
582
|
+
# Each dimension row carries the verdict for the dimension it
|
|
583
|
+
# measures, not the whole one: points scoped to this key, and a score
|
|
584
|
+
# that is this judge's number for it. That makes `value ==
|
|
585
|
+
# mean(judges.score)` hold on every judge row, so per-dimension
|
|
586
|
+
# disagreement is readable without unpacking the map on `.overall`.
|
|
587
|
+
# Carrying the full map here would put two irrelevant numbers on a
|
|
588
|
+
# row and invite reading the wrong one.
|
|
582
589
|
panel_dims: list[float] = []
|
|
583
590
|
for key, _ in self.dimensions:
|
|
584
591
|
per_judge = [
|
|
@@ -588,7 +595,19 @@ class RubricJudge:
|
|
|
588
595
|
value = sum(present) / len(present) if present else None
|
|
589
596
|
if value is not None:
|
|
590
597
|
panel_dims.append(value)
|
|
591
|
-
|
|
598
|
+
dim_details = [
|
|
599
|
+
models.JudgeDetail(
|
|
600
|
+
key=judge['key'],
|
|
601
|
+
version=judge['judge_version'],
|
|
602
|
+
rationale=rationales.get(judge['key']),
|
|
603
|
+
points={key: raw.get(judge['key'], {}).get(key)},
|
|
604
|
+
overall=self._normalize(
|
|
605
|
+
raw.get(judge['key'], {}).get(key)
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
for judge in active
|
|
609
|
+
]
|
|
610
|
+
out.append(score(f'{self.name}.{key}', value, judges=dim_details))
|
|
592
611
|
|
|
593
612
|
overall = sum(panel_dims) / len(panel_dims) if panel_dims else None
|
|
594
613
|
out.append(
|
|
@@ -70,7 +70,7 @@ class AnthropicPairwiseClient:
|
|
|
70
70
|
self,
|
|
71
71
|
model: str,
|
|
72
72
|
api_key_env: str = 'ANTHROPIC_API_KEY',
|
|
73
|
-
max_tokens: int =
|
|
73
|
+
max_tokens: int = 8192,
|
|
74
74
|
timeout: float = 30.0,
|
|
75
75
|
):
|
|
76
76
|
self.model = model
|
|
@@ -88,7 +88,6 @@ class AnthropicPairwiseClient:
|
|
|
88
88
|
response = await client.messages.create(
|
|
89
89
|
model=self.model,
|
|
90
90
|
max_tokens=self.max_tokens,
|
|
91
|
-
temperature=0,
|
|
92
91
|
timeout=self.timeout,
|
|
93
92
|
system=system,
|
|
94
93
|
tools=[tool],
|
|
@@ -111,7 +110,7 @@ class OpenAIPairwiseClient:
|
|
|
111
110
|
self,
|
|
112
111
|
model: str,
|
|
113
112
|
api_key_env: str = 'OPENAI_API_KEY',
|
|
114
|
-
max_tokens: int =
|
|
113
|
+
max_tokens: int = 8192,
|
|
115
114
|
timeout: float = 30.0,
|
|
116
115
|
):
|
|
117
116
|
self.model = model.split(':', 1)[1] if ':' in model else model
|
|
@@ -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
|