evalcore 0.2.0__tar.gz → 0.3.0__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-0.3.0/CHANGELOG.md +77 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/PKG-INFO +1 -1
- {evalcore-0.2.0 → evalcore-0.3.0}/docs/design.md +20 -20
- {evalcore-0.2.0 → evalcore-0.3.0}/pyproject.toml +1 -1
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/cli.py +36 -19
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/models.py +6 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/runner.py +26 -2
- evalcore-0.3.0/src/evalcore/store.py +511 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_runner.py +34 -0
- evalcore-0.3.0/tests/test_store.py +376 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/uv.lock +1 -1
- evalcore-0.2.0/CHANGELOG.md +0 -35
- evalcore-0.2.0/src/evalcore/store.py +0 -286
- evalcore-0.2.0/tests/test_store.py +0 -173
- {evalcore-0.2.0 → evalcore-0.3.0}/.github/workflows/ci.yml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/.github/workflows/publish.yml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/.gitignore +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/.pre-commit-config.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/LICENSE +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/README.md +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/README.md +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/adapter.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/datasets/support_reply/v1/cases/angry_complaint.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/datasets/support_reply/v1/cases/billing_question.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/datasets/support_reply/v1/cases/double_charge.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/datasets/support_reply/v1/cases/refund_request.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/fixtures/support_reply_judge.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/fixtures/support_reply_pairwise.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/fixtures/support_reply_replay.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/graders.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/run_eval.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/suite.yaml +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/tests/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/examples/quickstart/tests/test_quickstart.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/justfile +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/pyrightconfig.json +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/adapters/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/adapters/base.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/adapters/env.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/adapters/http.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/adapters/replay.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/compare.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/errors.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/base.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/classification.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/deterministic.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/judge.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/graders/numeric.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/loader.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/pairwise.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/py.typed +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/rating.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/refs.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/report.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/reporters/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/reporters/base.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/reporters/html.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/reporters/markdown.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/retry.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/src/evalcore/sweep.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/__init__.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_adapters.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_cli.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_edge_cases.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_judge.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_judge_extra.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_live_clients.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_pairwise_extra.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_rating.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_rating_server.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_reporters.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_retry.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_sweep_pairwise.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/tests/test_unit.py +0 -0
- {evalcore-0.2.0 → evalcore-0.3.0}/uv.toml +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-07-28
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Breaking:** the results-store outbox is now one feed at
|
|
13
|
+
`(run, case, sample, grader, metric)` grain, targeting a single flat
|
|
14
|
+
`evaluation_scores` table with the run trend and the invocation grain as
|
|
15
|
+
plain views over it. `store.scorecard_rows` and
|
|
16
|
+
`JsonlOutboxExporter.export` are removed; use `store.score_rows` and
|
|
17
|
+
`export_scores`. `--export-scores` is now an alias for `--export`.
|
|
18
|
+
- **Breaking:** a missing measurement is emitted as `null` rather than the
|
|
19
|
+
`(value=0, has_value=false)` sentinel pair, and the `has_value` / `has_stdev`
|
|
20
|
+
companion keys are gone. The store columns are `Nullable`, and a real `0.0` is
|
|
21
|
+
a meaningful score.
|
|
22
|
+
- **Breaking:** outbox row keys are the store column names, so `project` is
|
|
23
|
+
emitted as `application`, `mode` as `adapter_mode`, `created_at` as
|
|
24
|
+
`timestamp`, and `Output.error` as `is_error` plus `error_text`.
|
|
25
|
+
- **Breaking:** outbox rows no longer carry `model_id` or `prompt_version`. Both
|
|
26
|
+
are `variant.knobs.get(...)` projections and travel inside `variant_knobs`.
|
|
27
|
+
- **Breaking:** `run_id` is now a dashed UUIDv7 rather than `uuid4().hex`, so it
|
|
28
|
+
parses as a ClickHouse `UUID` and carries its own creation time. Uses
|
|
29
|
+
`uuid.uuid7()` on 3.14 and an RFC 9562 implementation on 3.11 through 3.13.
|
|
30
|
+
- Outbox rows now carry the gate: `gate_verdict`, `gate_win`,
|
|
31
|
+
`baseline_run_id`, `baseline_variant`, `win_baseline`, `win_candidate`,
|
|
32
|
+
`win_delta` and `gate_summary` at run grain, plus `win`, `guardrail` and
|
|
33
|
+
`guardrail_gap` on the metric each one refers to. Pass the `Comparison` to
|
|
34
|
+
`score_rows`; without it a run reads as ungated.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- `RunResult.aggregate_scores`, retaining the `kind='aggregate'` scores so a
|
|
38
|
+
store row keeps the grader that emitted them and what it reported. The
|
|
39
|
+
scorecard kept only their values.
|
|
40
|
+
- `store.grader_lookups`, mapping grader names to a category and to a judge
|
|
41
|
+
scale from a suite's grader specs, since a `Score` carries neither.
|
|
42
|
+
- Three outbox row shapes that previously had no representation: an aggregate
|
|
43
|
+
metric (no `case_id`), an invocation that failed before any grader ran (no
|
|
44
|
+
`grader` or `metric`), and a metric a guardrail or the win metric names but
|
|
45
|
+
never scored (null value).
|
|
46
|
+
|
|
47
|
+
## [0.2.0] - 2026-07-16
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- **Breaking:** the import package and CLI are now `evalcore` (were `evalkit`).
|
|
51
|
+
Update `import evalkit` to `import evalcore` and the `evalkit` command to
|
|
52
|
+
`evalcore`. The distribution name (`evalcore`) is unchanged.
|
|
53
|
+
- Lowered the minimum Python to **3.11** (was 3.14).
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- `evalcore.__version__`.
|
|
57
|
+
- Public `evalcore.adapters.expand_env` for `${VAR}` expansion in custom
|
|
58
|
+
adapters (replaces the private `adapters._env` module).
|
|
59
|
+
- Exception hierarchy: `EvalcoreError` (base) and `ConfigError` (also a
|
|
60
|
+
`ValueError`, so existing handlers keep working).
|
|
61
|
+
- Top-level convenience entry points: `load_suite`, `load_cases`, `run_suite`,
|
|
62
|
+
`run_suite_sync`.
|
|
63
|
+
- HTML rendering for `sweep` and `pairwise` reports, and `--report` /
|
|
64
|
+
`--report-out` on those CLI commands.
|
|
65
|
+
|
|
66
|
+
## [0.1.0] - 2026-07-16
|
|
67
|
+
|
|
68
|
+
- Initial public release: adapters (http/replay), graders (deterministic,
|
|
69
|
+
numeric, classification, LLM judge + panel), runner (N-sampling, concurrency,
|
|
70
|
+
retries, checkpoint/resume), compare/gate, sweep, pairwise, blind human
|
|
71
|
+
rating + ranking with judge agreement, Markdown/HTML reporters, JSON +
|
|
72
|
+
column-store outbox, and content-hash provenance.
|
|
73
|
+
|
|
74
|
+
[Unreleased]: https://github.com/scottpmiller/evalcore/compare/0.3.0...HEAD
|
|
75
|
+
[0.3.0]: https://github.com/scottpmiller/evalcore/compare/0.2.0...0.3.0
|
|
76
|
+
[0.2.0]: https://github.com/scottpmiller/evalcore/compare/0.1.0...0.2.0
|
|
77
|
+
[0.1.0]: https://github.com/scottpmiller/evalcore/releases/tag/0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evalcore
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -20,7 +20,7 @@ adapter + datasets + graders config," not "fork the framework." If it leaks,
|
|
|
20
20
|
every new consumer reopens the core.
|
|
21
21
|
|
|
22
22
|
This shape is well-trodden - promptfoo, OpenAI Evals, Braintrust, and LangSmith
|
|
23
|
-
all converge on the same pipeline (case
|
|
23
|
+
all converge on the same pipeline (case -> target -> graders -> comparison ->
|
|
24
24
|
gate). evalcore is not inventing the abstraction; it is drawing the line so the
|
|
25
25
|
engine stays reusable across unrelated systems.
|
|
26
26
|
|
|
@@ -28,21 +28,21 @@ engine stays reusable across unrelated systems.
|
|
|
28
28
|
|
|
29
29
|
| Concern | **Engine** | **Consumer** |
|
|
30
30
|
|---|---|---|
|
|
31
|
-
| Case / suite schema + loading |
|
|
32
|
-
| Runner: sampling (N), concurrency, live/replay modes |
|
|
33
|
-
| Target-adapter protocol + built-ins (HTTP, replay) |
|
|
34
|
-
| Variant model (baseline vs candidate knobs) |
|
|
35
|
-
| Grader protocol + registry |
|
|
36
|
-
| Generic graders: deterministic, LLM-judge, classification, numeric |
|
|
37
|
-
| Comparison / regression engine (win metric, guardrails) |
|
|
38
|
-
| Gate decision (comparison
|
|
39
|
-
| Results store + row schema / outbox |
|
|
40
|
-
| Reporters (Markdown / HTML) |
|
|
41
|
-
| Datasets (cases + labels) |
|
|
42
|
-
| Judge **rubrics** |
|
|
43
|
-
| Custom graders / adapters |
|
|
44
|
-
| Thresholds / guardrail config |
|
|
45
|
-
| Which suite runs on which change trigger |
|
|
31
|
+
| Case / suite schema + loading | yes | uses it |
|
|
32
|
+
| Runner: sampling (N), concurrency, live/replay modes | yes | picks mode/config |
|
|
33
|
+
| Target-adapter protocol + built-ins (HTTP, replay) | yes | configures/extends |
|
|
34
|
+
| Variant model (baseline vs candidate knobs) | yes opaque dict | defines its knobs |
|
|
35
|
+
| Grader protocol + registry | yes | registers its graders |
|
|
36
|
+
| Generic graders: deterministic, LLM-judge, classification, numeric | yes | configures + writes custom |
|
|
37
|
+
| Comparison / regression engine (win metric, guardrails) | yes | sets thresholds |
|
|
38
|
+
| Gate decision (comparison -> pass/warn/fail) | yes | sets policy |
|
|
39
|
+
| Results store + row schema / outbox | yes | gets a `project` namespace |
|
|
40
|
+
| Reporters (Markdown / HTML) | yes | gets them free |
|
|
41
|
+
| Datasets (cases + labels) | no | yes owns |
|
|
42
|
+
| Judge **rubrics** | no | yes owns |
|
|
43
|
+
| Custom graders / adapters | no | yes owns |
|
|
44
|
+
| Thresholds / guardrail config | no | yes owns |
|
|
45
|
+
| Which suite runs on which change trigger | no | yes owns |
|
|
46
46
|
|
|
47
47
|
Everything in the left column is consumer-agnostic. The right column is what a
|
|
48
48
|
new team writes.
|
|
@@ -66,12 +66,12 @@ Eight concepts, none of which mention any particular system:
|
|
|
66
66
|
own for anything else.
|
|
67
67
|
- **Grader** - `grade(case, output) -> [Score]` where
|
|
68
68
|
`Score = {metric, value, passed?, detail}`. Registry-based; tiered
|
|
69
|
-
(deterministic
|
|
70
|
-
- **Run** - execute `suite × variant` over all cases × N samples
|
|
69
|
+
(deterministic -> numeric -> classification -> LLM judge).
|
|
70
|
+
- **Run** - execute `suite × variant` over all cases × N samples -> outputs +
|
|
71
71
|
scores, persisted as a `RunResult`.
|
|
72
72
|
- **Scorecard** - aggregated scores for one run (mean/stdev per metric).
|
|
73
|
-
- **Comparison + Gate** - candidate scorecard vs baseline
|
|
74
|
-
checks, win metric
|
|
73
|
+
- **Comparison + Gate** - candidate scorecard vs baseline -> deltas, guardrail
|
|
74
|
+
checks, win metric -> `pass | warn | fail`.
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
77
|
class TargetAdapter(typing.Protocol):
|
|
@@ -160,19 +160,28 @@ def _cmd_gate(args: argparse.Namespace) -> int:
|
|
|
160
160
|
)
|
|
161
161
|
_emit_report(args, reporters.wrap_document(rep, body))
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
163
|
+
# One feed now, so --export and --export-scores name the same rows;
|
|
164
|
+
# --export-scores is kept as an alias for existing invocations.
|
|
165
|
+
outboxes = dict.fromkeys(
|
|
166
|
+
path for path in (args.export, args.export_scores) if path
|
|
167
|
+
)
|
|
168
|
+
if outboxes:
|
|
169
|
+
types, scales = store.grader_lookups(suite.graders)
|
|
170
|
+
for outbox in outboxes:
|
|
171
|
+
exporter = store.JsonlOutboxExporter(outbox)
|
|
172
|
+
# The baseline half of a pair is not itself gated, so it carries no
|
|
173
|
+
# comparison; the candidate carries the verdict and the guardrails.
|
|
174
|
+
rows = exporter.export_scores(
|
|
175
|
+
baseline, grader_types=types, judge_scales=scales
|
|
176
|
+
)
|
|
177
|
+
rows += exporter.export_scores(
|
|
178
|
+
candidate,
|
|
179
|
+
result,
|
|
180
|
+
baseline_run_id=baseline.run_id,
|
|
181
|
+
grader_types=types,
|
|
182
|
+
judge_scales=scales,
|
|
175
183
|
)
|
|
184
|
+
print(f'\nexported {rows} store rows -> {outbox}', file=sys.stderr)
|
|
176
185
|
|
|
177
186
|
return 0 if result.verdict != 'fail' else 1
|
|
178
187
|
|
|
@@ -201,12 +210,16 @@ def _cmd_sweep(args: argparse.Namespace) -> int:
|
|
|
201
210
|
args, reporters.wrap_document(rep, reporters.render_sweep(rep, result))
|
|
202
211
|
)
|
|
203
212
|
if args.export:
|
|
213
|
+
types, scales = store.grader_lookups(suite.graders)
|
|
204
214
|
exporter = store.JsonlOutboxExporter(args.export)
|
|
205
|
-
|
|
206
|
-
exporter.
|
|
215
|
+
rows = sum(
|
|
216
|
+
exporter.export_scores(
|
|
217
|
+
run, grader_types=types, judge_scales=scales
|
|
218
|
+
)
|
|
219
|
+
for run in runs
|
|
220
|
+
)
|
|
207
221
|
print(
|
|
208
|
-
f'\nexported {
|
|
209
|
-
file=sys.stderr,
|
|
222
|
+
f'\nexported {rows} store rows -> {args.export}', file=sys.stderr
|
|
210
223
|
)
|
|
211
224
|
return 0
|
|
212
225
|
|
|
@@ -438,11 +451,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
438
451
|
help='grader mode when it differs from --mode '
|
|
439
452
|
'(e.g. live judges on replayed data)',
|
|
440
453
|
)
|
|
441
|
-
gate.add_argument(
|
|
454
|
+
gate.add_argument(
|
|
455
|
+
'--export', help='append results-store rows to a JSONL outbox'
|
|
456
|
+
)
|
|
442
457
|
gate.add_argument(
|
|
443
458
|
'--export-scores',
|
|
444
459
|
dest='export_scores',
|
|
445
|
-
help='
|
|
460
|
+
help='alias for --export, kept for existing invocations',
|
|
446
461
|
)
|
|
447
462
|
gate.add_argument(
|
|
448
463
|
'--revision', help='opaque provenance id stamped on both scorecards'
|
|
@@ -474,7 +489,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
474
489
|
help='grader mode when it differs from --mode '
|
|
475
490
|
'(e.g. live judges on replayed data)',
|
|
476
491
|
)
|
|
477
|
-
sweep.add_argument(
|
|
492
|
+
sweep.add_argument(
|
|
493
|
+
'--export', help='append results-store rows to a JSONL outbox'
|
|
494
|
+
)
|
|
478
495
|
sweep.add_argument('--revision')
|
|
479
496
|
sweep.add_argument(
|
|
480
497
|
'--report',
|
|
@@ -162,11 +162,17 @@ class RunResult(pydantic.BaseModel):
|
|
|
162
162
|
one entry per (case, sample) with the full output, artifacts, and
|
|
163
163
|
per-case scores. Persisting it is what makes transcript review,
|
|
164
164
|
human rating, and judge-agreement analysis possible after the fact.
|
|
165
|
+
|
|
166
|
+
``aggregate_scores`` holds the ``kind='aggregate'`` scores computed once
|
|
167
|
+
over the whole run. The scorecard keeps their values but not which grader
|
|
168
|
+
emitted them or what it reported, so they are retained here too - a
|
|
169
|
+
results-store row needs the grader attribution.
|
|
165
170
|
"""
|
|
166
171
|
|
|
167
172
|
run_id: str
|
|
168
173
|
scorecard: Scorecard
|
|
169
174
|
results: list[CaseResult] = pydantic.Field(default_factory=list)
|
|
175
|
+
aggregate_scores: list[Score] = pydantic.Field(default_factory=list)
|
|
170
176
|
|
|
171
177
|
|
|
172
178
|
class Rating(pydantic.BaseModel):
|
|
@@ -13,7 +13,9 @@ concurrent calls.
|
|
|
13
13
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import inspect
|
|
16
|
+
import secrets
|
|
16
17
|
import statistics
|
|
18
|
+
import time
|
|
17
19
|
import uuid
|
|
18
20
|
|
|
19
21
|
from evalcore import loader, models, store
|
|
@@ -47,6 +49,25 @@ async def _invoke_with_retry(
|
|
|
47
49
|
attempt += 1
|
|
48
50
|
|
|
49
51
|
|
|
52
|
+
def _uuid7() -> uuid.UUID:
|
|
53
|
+
"""A UUIDv7 (RFC 9562): 48-bit millisecond timestamp, then random.
|
|
54
|
+
|
|
55
|
+
Time-ordered, so a run id carries its own creation time and ids sort in
|
|
56
|
+
the order the runs happened. ``uuid.uuid7`` is 3.14; this is the fallback
|
|
57
|
+
for 3.11 through 3.13.
|
|
58
|
+
"""
|
|
59
|
+
if hasattr(uuid, 'uuid7'):
|
|
60
|
+
return uuid.uuid7()
|
|
61
|
+
ms = int(time.time() * 1000) & 0xFFFFFFFFFFFF
|
|
62
|
+
return uuid.UUID(
|
|
63
|
+
int=(ms << 80)
|
|
64
|
+
| (0x7 << 76)
|
|
65
|
+
| (secrets.randbits(12) << 64)
|
|
66
|
+
| (0b10 << 62)
|
|
67
|
+
| secrets.randbits(62)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
50
71
|
def _aggregate_metrics(
|
|
51
72
|
results: list[models.CaseResult], agg_scores: list[models.Score]
|
|
52
73
|
) -> dict[str, models.MetricValue]:
|
|
@@ -140,7 +161,7 @@ async def run_suite(
|
|
|
140
161
|
|
|
141
162
|
cases = loader.load_cases(suite.dataset)
|
|
142
163
|
dataset_hash = loader.dataset_hash(cases)
|
|
143
|
-
run_id =
|
|
164
|
+
run_id = str(_uuid7())
|
|
144
165
|
|
|
145
166
|
# Resume: reuse already-completed (case, sample) results from a checkpoint
|
|
146
167
|
# of the *same* eval; otherwise start (or restart) the checkpoint fresh.
|
|
@@ -272,7 +293,10 @@ async def run_suite(
|
|
|
272
293
|
metrics=_aggregate_metrics(results, agg_scores),
|
|
273
294
|
)
|
|
274
295
|
return models.RunResult(
|
|
275
|
-
run_id=run_id,
|
|
296
|
+
run_id=run_id,
|
|
297
|
+
scorecard=scorecard,
|
|
298
|
+
results=results,
|
|
299
|
+
aggregate_scores=agg_scores,
|
|
276
300
|
)
|
|
277
301
|
|
|
278
302
|
|