evalstone 0.2.2__tar.gz → 0.2.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.
Files changed (57) hide show
  1. {evalstone-0.2.2 → evalstone-0.2.3}/CHANGELOG.md +16 -0
  2. {evalstone-0.2.2 → evalstone-0.2.3}/PKG-INFO +1 -1
  3. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/__init__.py +1 -1
  4. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/comparison.py +2 -1
  5. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/judge.py +10 -2
  6. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_judge.py +17 -0
  7. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_rag_and_comparison.py +8 -0
  8. {evalstone-0.2.2 → evalstone-0.2.3}/.gitignore +0 -0
  9. {evalstone-0.2.2 → evalstone-0.2.3}/LICENSE +0 -0
  10. {evalstone-0.2.2 → evalstone-0.2.3}/README.md +0 -0
  11. {evalstone-0.2.2 → evalstone-0.2.3}/justfile +0 -0
  12. {evalstone-0.2.2 → evalstone-0.2.3}/pyproject.toml +0 -0
  13. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/__main__.py +0 -0
  14. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/_git.py +0 -0
  15. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/_json.py +0 -0
  16. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/center.py +0 -0
  17. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/__init__.py +0 -0
  18. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/_base.py +0 -0
  19. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/deterministic.py +0 -0
  20. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/human.py +0 -0
  21. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/rag.py +0 -0
  22. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/safety.py +0 -0
  23. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/checks/trajectory.py +0 -0
  24. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/cli.py +0 -0
  25. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/config.py +0 -0
  26. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/dataset.py +0 -0
  27. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/gate.py +0 -0
  28. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/mcp_server.py +0 -0
  29. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/output.py +0 -0
  30. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/providers/__init__.py +0 -0
  31. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/providers/_http.py +0 -0
  32. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/providers/anthropic_.py +0 -0
  33. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/providers/gemini.py +0 -0
  34. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/providers/openai_compat.py +0 -0
  35. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/pytest_plugin.py +0 -0
  36. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/run.py +0 -0
  37. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/score.py +0 -0
  38. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/suite.py +0 -0
  39. {evalstone-0.2.2 → evalstone-0.2.3}/src/evalstone/target.py +0 -0
  40. {evalstone-0.2.2 → evalstone-0.2.3}/tests/conftest.py +0 -0
  41. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_action.py +0 -0
  42. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_center.py +0 -0
  43. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_cli.py +0 -0
  44. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_config.py +0 -0
  45. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_dataset.py +0 -0
  46. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_deterministic.py +0 -0
  47. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_docs.py +0 -0
  48. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_human.py +0 -0
  49. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_judge_switching.py +0 -0
  50. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_mcp.py +0 -0
  51. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_providers.py +0 -0
  52. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_pytest_plugin.py +0 -0
  53. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_safety.py +0 -0
  54. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_score_and_checks.py +0 -0
  55. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_suite_run_gate.py +0 -0
  56. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_target.py +0 -0
  57. {evalstone-0.2.2 → evalstone-0.2.3}/tests/test_trajectory.py +0 -0
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to `python/evalstone`. Semantic versioning; tags `lib-vX.Y.Z`.
4
4
 
5
+ ## 0.2.3 — 2026-10-14
6
+
7
+ Promote pins the model the verdicts were routed through, found on the demo project's first
8
+ real calibration.
9
+
10
+ - `judge` and `comparison.pairwise` record `raw.judge_model`: the `<provider>/<model>` the
11
+ verdict was routed through (for example `openrouter/anthropic/claude-haiku-5.5`), next to
12
+ `raw.model`, the id the provider reported (`anthropic/claude-haiku-5.5`). Verdicts served
13
+ from the cache carry it too.
14
+ - The center stamps only `raw.judge_model`, so the Judges page, calibrations and **Promote**
15
+ use the routed model. Before, it stamped `raw.model`, and Promote sent that
16
+ provider-reported id, which judge settings reject (`no provider named 'anthropic'`) or,
17
+ when a provider of that name existed, route elsewhere. Results pushed by an older library
18
+ are no longer stamped (a migration clears the old stamps): a version seen only from them
19
+ shows no model and cannot be promoted until a 0.2.3+ run pushes it.
20
+
5
21
  ## 0.2.2 — 2026-10-14
6
22
 
7
23
  Judge replies cut off by the token cap, found by the first run against a real
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evalstone
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: The plug-and-play evals layer for LLM-based projects: datasets, checks, suites, runs and gates.
5
5
  Project-URL: Homepage, https://evalstone.com
6
6
  Project-URL: Documentation, https://evalstone.com/docs
@@ -8,7 +8,7 @@ import types
8
8
  from importlib import import_module
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
- __version__ = "0.2.2"
11
+ __version__ = "0.2.3"
12
12
 
13
13
  # Exports resolve on first access (PEP 562) so that importing the package — which
14
14
  # the pytest plugin does in every pytest session on the interpreter — costs
@@ -115,7 +115,7 @@ def pairwise(
115
115
  cached = ctx.cache.get(cache_key) if ctx.cache is not None else None
116
116
  if cached is not None:
117
117
  # The judge verdict is cached; the baseline was still produced (its cost counts).
118
- raw = {**(cached.raw or {}), "cached": True}
118
+ raw = {**(cached.raw or {}), "judge_model": resolved, "cached": True}
119
119
  return cached.model_copy(
120
120
  update={"cost_usd": base.cost_usd, "latency_ms": base.latency_ms, "raw": raw}
121
121
  )
@@ -161,6 +161,7 @@ def pairwise(
161
161
  "points": points,
162
162
  "orders": len(points),
163
163
  "judge_version": version,
164
+ "judge_model": resolved,
164
165
  "unpriced": bool(unpriced),
165
166
  },
166
167
  )
@@ -179,7 +179,15 @@ def judge(
179
179
  reason=f"{n}/{scale}: {reason}" if reason else f"{n}/{scale}",
180
180
  cost_usd=cost,
181
181
  latency_ms=ms,
182
- raw={**raw, "score": n, "scale": scale, "judge_version": judge_version},
182
+ # judge_model = `<provider>/<model>` as routed (what Promote pins); set here so
183
+ # a verdict served from an older cache entry carries it too
184
+ raw={
185
+ **raw,
186
+ "score": n,
187
+ "scale": scale,
188
+ "judge_version": judge_version,
189
+ "judge_model": resolved,
190
+ },
183
191
  )
184
192
 
185
193
  # The cache holds the model's raw verdict (level + reason), keyed by the
@@ -218,7 +226,7 @@ def judge(
218
226
  n,
219
227
  reason,
220
228
  raw={
221
- "model": resp.model,
229
+ "model": resp.model, # what the provider reported (a bare id, maybe dated)
222
230
  "tokens": [resp.input_tokens, resp.output_tokens],
223
231
  "unpriced": resp.cost_usd is None,
224
232
  "verdict_reason": reason,
@@ -288,3 +288,20 @@ def test_judge_version_includes_the_resolved_default_model():
288
288
  b = chk(S, Output(value="Within 30 days."), ctx_with(prov, default="openai/judge-b"))
289
289
  assert a.raw["judge_version"] != b.raw["judge_version"]
290
290
  assert len(prov.calls) == 2 # different resolved model → different cache key
291
+
292
+
293
+ def test_verdict_records_the_routed_model_fresh_and_cached():
294
+ """`judge_model` is what Promote pins: `<provider>/<model>` as routed, not `resp.model`."""
295
+ from evalstone.score import Score
296
+
297
+ chk = judge("h", "r")
298
+ ctx = ctx_with(FakeProvider())
299
+ fresh = chk(S, Output(value="30 days"), ctx)
300
+ assert fresh.raw["judge_model"] == "openai/judge-model"
301
+ assert chk(S, Output(value="30 days"), ctx).raw["judge_model"] == "openai/judge-model"
302
+ # a cache entry written by an older library (no judge_model) still gets it on the way out
303
+ key, old = next(iter(ctx.cache._data.items()))
304
+ ctx.cache._data[key] = Score.model_validate(
305
+ {**old.model_dump(), "raw": {k: v for k, v in old.raw.items() if k != "judge_model"}}
306
+ )
307
+ assert chk(S, Output(value="30 days"), ctx).raw["judge_model"] == "openai/judge-model"
@@ -276,3 +276,11 @@ def test_pairwise_unparseable_second_order_keeps_every_call_cost():
276
276
  score = pairwise("b", "r", baseline={"s": "x"})(S, Output(value="y"), ctx(Half()))
277
277
  assert score.passed is False and "no JSON object" in score.error
278
278
  assert score.cost_usd == pytest.approx(0.002)
279
+
280
+
281
+ def test_pairwise_records_the_routed_model_fresh_and_cached():
282
+ chk = pairwise("b", "r", baseline={"s": "x"})
283
+ c = ctx(JudgeFake())
284
+ assert chk(S, Output(value="y"), c).raw["judge_model"] == "openai/judge"
285
+ cached = chk(S, Output(value="y"), c)
286
+ assert cached.raw["cached"] is True and cached.raw["judge_model"] == "openai/judge"
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