evalcore 2.4.0__tar.gz → 2.4.1__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.
Files changed (76) hide show
  1. {evalcore-2.4.0 → evalcore-2.4.1}/CHANGELOG.md +32 -1
  2. {evalcore-2.4.0 → evalcore-2.4.1}/PKG-INFO +1 -1
  3. {evalcore-2.4.0 → evalcore-2.4.1}/pyproject.toml +1 -1
  4. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/judge.py +18 -4
  5. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_judge.py +33 -2
  6. {evalcore-2.4.0 → evalcore-2.4.1}/uv.lock +1 -1
  7. {evalcore-2.4.0 → evalcore-2.4.1}/.github/workflows/ci.yml +0 -0
  8. {evalcore-2.4.0 → evalcore-2.4.1}/.github/workflows/publish.yml +0 -0
  9. {evalcore-2.4.0 → evalcore-2.4.1}/.gitignore +0 -0
  10. {evalcore-2.4.0 → evalcore-2.4.1}/.pre-commit-config.yaml +0 -0
  11. {evalcore-2.4.0 → evalcore-2.4.1}/LICENSE +0 -0
  12. {evalcore-2.4.0 → evalcore-2.4.1}/README.md +0 -0
  13. {evalcore-2.4.0 → evalcore-2.4.1}/docs/design.md +0 -0
  14. {evalcore-2.4.0 → evalcore-2.4.1}/examples/__init__.py +0 -0
  15. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/README.md +0 -0
  16. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/__init__.py +0 -0
  17. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/adapter.py +0 -0
  18. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/datasets/support_reply/v1/cases/angry_complaint.yaml +0 -0
  19. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/datasets/support_reply/v1/cases/billing_question.yaml +0 -0
  20. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/datasets/support_reply/v1/cases/double_charge.yaml +0 -0
  21. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/datasets/support_reply/v1/cases/refund_request.yaml +0 -0
  22. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/fixtures/support_reply_judge.yaml +0 -0
  23. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/fixtures/support_reply_pairwise.yaml +0 -0
  24. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/fixtures/support_reply_replay.yaml +0 -0
  25. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/graders.py +0 -0
  26. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/run_eval.py +0 -0
  27. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/suite.yaml +0 -0
  28. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/tests/__init__.py +0 -0
  29. {evalcore-2.4.0 → evalcore-2.4.1}/examples/quickstart/tests/test_quickstart.py +0 -0
  30. {evalcore-2.4.0 → evalcore-2.4.1}/justfile +0 -0
  31. {evalcore-2.4.0 → evalcore-2.4.1}/pyrightconfig.json +0 -0
  32. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/__init__.py +0 -0
  33. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/adapters/__init__.py +0 -0
  34. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/adapters/base.py +0 -0
  35. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/adapters/env.py +0 -0
  36. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/adapters/http.py +0 -0
  37. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/adapters/replay.py +0 -0
  38. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/cli.py +0 -0
  39. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/compare.py +0 -0
  40. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/errors.py +0 -0
  41. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/__init__.py +0 -0
  42. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/base.py +0 -0
  43. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/classification.py +0 -0
  44. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/deterministic.py +0 -0
  45. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/graders/numeric.py +0 -0
  46. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/loader.py +0 -0
  47. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/models.py +0 -0
  48. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/pairwise.py +0 -0
  49. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/py.typed +0 -0
  50. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/rating.py +0 -0
  51. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/refs.py +0 -0
  52. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/report.py +0 -0
  53. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/reporters/__init__.py +0 -0
  54. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/reporters/base.py +0 -0
  55. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/reporters/html.py +0 -0
  56. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/reporters/markdown.py +0 -0
  57. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/retry.py +0 -0
  58. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/runner.py +0 -0
  59. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/store.py +0 -0
  60. {evalcore-2.4.0 → evalcore-2.4.1}/src/evalcore/sweep.py +0 -0
  61. {evalcore-2.4.0 → evalcore-2.4.1}/tests/__init__.py +0 -0
  62. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_adapters.py +0 -0
  63. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_cli.py +0 -0
  64. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_edge_cases.py +0 -0
  65. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_judge_extra.py +0 -0
  66. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_live_clients.py +0 -0
  67. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_pairwise_extra.py +0 -0
  68. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_rating.py +0 -0
  69. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_rating_server.py +0 -0
  70. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_reporters.py +0 -0
  71. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_retry.py +0 -0
  72. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_runner.py +0 -0
  73. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_store.py +0 -0
  74. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_sweep_pairwise.py +0 -0
  75. {evalcore-2.4.0 → evalcore-2.4.1}/tests/test_unit.py +0 -0
  76. {evalcore-2.4.0 → evalcore-2.4.1}/uv.toml +0 -0
@@ -6,6 +6,36 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.4.1] - 2026-08-09
10
+
11
+ The judge's identity is now in its provenance pin.
12
+
13
+ Shipped as a patch. Both changes below alter a published value, so the 1.0.0
14
+ policy would call this a minor at least; it goes out as 2.4.1 as a deliberate
15
+ exception, because the old pin was answering a provenance question wrongly
16
+ and the sooner it stops the fewer runs are affected.
17
+
18
+ ### Fixed
19
+ - `Scorecard.judge_version` includes the judge's model:
20
+ `anthropic:claude-sonnet-4-6@v1` rather than `judge@v1`. It was
21
+ `key@judge_version`, so swapping the judge's model while leaving the
22
+ declared `judge_version` alone produced a byte-identical pin - and any
23
+ provenance check reading that field passed a comparison against a baseline
24
+ scored by a different model. The model is the thing most likely to change
25
+ and the thing a declared version is most likely to miss.
26
+
27
+ ### Changed
28
+ - A single judge's key defaults to its provider (`anthropic`) instead of the
29
+ literal `judge`. A panel already defaulted to `key or provider`, so the
30
+ one-judge case was the odd one out, and `judge` named nothing the `grader`
31
+ column did not already say. It appears in the `judges.name` column.
32
+
33
+ **Upgrading:** every suite with a judge re-baselines once, because
34
+ `judge_version` is part of what identifies a comparable run - which is the
35
+ intended behaviour, just paid all at once. Queries filtering
36
+ `judges.name = 'judge'` need the provider instead. Set `key:` on a `judges:`
37
+ entry to pin a name of your own.
38
+
9
39
  ## [2.4.0] - 2026-08-09
10
40
 
11
41
  A run without a baseline can now say whether it passed.
@@ -248,7 +278,8 @@ by semantic versioning: a breaking change to either means a 2.0.
248
278
  rating + ranking with judge agreement, Markdown/HTML reporters, JSON +
249
279
  column-store outbox, and content-hash provenance.
250
280
 
251
- [Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.0...HEAD
281
+ [Unreleased]: https://github.com/scottpmiller/evalcore/compare/2.4.1...HEAD
282
+ [2.4.1]: https://github.com/scottpmiller/evalcore/compare/2.4.0...2.4.1
252
283
  [2.4.0]: https://github.com/scottpmiller/evalcore/compare/2.3.0...2.4.0
253
284
  [2.3.0]: https://github.com/scottpmiller/evalcore/compare/2.2.0...2.3.0
254
285
  [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.0
3
+ Version: 2.4.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "evalcore"
3
- version = "2.4.0"
3
+ version = "2.4.1"
4
4
  description = "A generic, consumer-agnostic evaluation engine for prompt, model, and API outputs."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -358,7 +358,11 @@ class RubricJudge:
358
358
  else:
359
359
  self.judges = [
360
360
  {
361
- 'key': 'judge',
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 panel
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"]}' for j in self.judges
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:
@@ -125,7 +125,8 @@ class RubricJudgeTests(unittest.TestCase):
125
125
  replay_path='f.yaml',
126
126
  judge_version='v3',
127
127
  )
128
- self.assertEqual(single.judge_version, 'judge@v3')
128
+ # No model configured (replay-only), so the pin is key@version.
129
+ self.assertEqual(single.judge_version, 'anthropic@v3')
129
130
  panel = judge.RubricJudge(
130
131
  content_ref='output.text',
131
132
  dimensions=DIMENSIONS,
@@ -139,7 +140,37 @@ class RubricJudgeTests(unittest.TestCase):
139
140
  },
140
141
  ],
141
142
  )
142
- self.assertEqual(panel.judge_version, 'claude@v2,gpt@v5')
143
+ self.assertEqual(panel.judge_version, 'claude:m@v2,gpt:openai:g@v5')
144
+
145
+ def test_the_model_is_in_the_pin(self):
146
+ """Swapping the judge's model has to move the pin.
147
+
148
+ Without this, a model swap that left `judge_version` alone produced
149
+ a byte-identical pin, so a comparison against a baseline scored by a
150
+ different model passed every provenance check reading this field.
151
+ """
152
+
153
+ def _judge(model):
154
+ return judge.RubricJudge(
155
+ content_ref='output.text',
156
+ dimensions=DIMENSIONS,
157
+ model=model,
158
+ judge_version='v1',
159
+ )
160
+
161
+ before = _judge('claude-sonnet-4-6').judge_version
162
+ after = _judge('claude-opus-4-1').judge_version
163
+ self.assertEqual(before, 'anthropic:claude-sonnet-4-6@v1')
164
+ self.assertNotEqual(before, after)
165
+
166
+ def test_a_single_judge_names_itself_by_provider(self):
167
+ """Not the literal 'judge', which carried no information."""
168
+ single = judge.RubricJudge(
169
+ content_ref='output.text',
170
+ dimensions=DIMENSIONS,
171
+ replay_path='f.yaml',
172
+ )
173
+ self.assertEqual(single.judges[0]['key'], 'anthropic')
143
174
 
144
175
  def test_transient_client_error_retried_via_set_retry(self):
145
176
  class _RateLimit(Exception):
@@ -188,7 +188,7 @@ wheels = [
188
188
 
189
189
  [[package]]
190
190
  name = "evalcore"
191
- version = "2.4.0"
191
+ version = "2.4.1"
192
192
  source = { editable = "." }
193
193
  dependencies = [
194
194
  { name = "pydantic" },
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