tuieval 0.2.0.dev75__tar.gz → 0.2.0.dev79__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.
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/CHANGELOG.md +2 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/PKG-INFO +3 -1
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/README.md +2 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/writing-packs.md +3 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/cli.py +1 -1
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/compare.py +58 -1
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/engine.py +45 -6
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/packs.py +3 -1
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/run_evals.py +4 -1
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/tui.py +11 -3
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/verdict.py +23 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/tests/test_tuieval.py +69 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/.gitignore +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/LICENSE +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/docs/models.md +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/pta.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/yamlout.py +0 -0
- {tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/tests/mock_server.py +0 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Early stop: `tuieval run --stop-early`, **Stop a pack once it fails** in Setup, or `stop_early = true` in a pack.toml ends a pack once its FAIL is certain (a critical failure, or an accuracy or truncation bar out of reach), asking first the questions models fail most. Verdicts also say FAIL when a partial run can no longer pass.
|
|
6
|
+
- `tuieval compare --reference <model>`: every other model paired against one on the questions both answered, per pack and overall: the difference with a 95% CI, better/tie/worse, critical failures and tokens vs the reference. `--packs` limits any compare to some packs.
|
|
5
7
|
- Fix: a question description or grader reason containing `[` no longer crashes the run screen.
|
|
6
8
|
- Retest asks per pack before it starts: from zero, continue or leave out. It now really starts from zero, and replaced smoke results go to history/.
|
|
7
9
|
- Retest: the setup checkbox "Rerun existing results" is now "Retest (ask again, even if already answered)", and `tuieval run --retest` is the same (`--force` still works).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev79
|
|
4
4
|
Summary: Evaluate local and frontier LLMs on your own questions: accuracy, speed, tokens and PASS/FAIL verdicts, in the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ashe-wb/tuieval
|
|
6
6
|
Project-URL: Issues, https://github.com/ashe-wb/tuieval/issues
|
|
@@ -160,6 +160,7 @@ tuieval verdict # PASS / FAIL / INCONCLUSIVE per m
|
|
|
160
160
|
tuieval report # the same with evidence, as a markdown file
|
|
161
161
|
tuieval history # every verdict change over time
|
|
162
162
|
tuieval compare --speed --pairwise --failures # scorecards
|
|
163
|
+
tuieval compare --reference <model> --packs X # each model vs one: better, tie or worse
|
|
163
164
|
tuieval selftest # check every test's reference and wrong answers
|
|
164
165
|
tuieval items # tests that don't separate models or look broken
|
|
165
166
|
tuieval logs # follow the server log of the model running now
|
|
@@ -179,6 +180,7 @@ tuieval help
|
|
|
179
180
|
- **Start with Production readiness.** A single critical failure already means FAIL, whatever the accuracy: a model that breaks a hard rule once in 300 answers will do it in production. The Failures tab and `tuieval report` list exactly which answers failed.
|
|
180
181
|
- **INCONCLUSIVE is not "nearly passed".** It says what's missing (usually a Certify run, or more trials).
|
|
181
182
|
- **Trust `tuieval compare --pairwise` over raw percentages.** Differences of one or two tests are usually noise.
|
|
183
|
+
- **Comparing variants of one model (fine-tunes, quantizations)?** `tuieval compare --reference <base>` pairs each against the base on the same questions and says better, tie or worse. Keep the server, quantization and settings the same, or they'll show up as differences too.
|
|
182
184
|
- **Read `trunc` before accuracy.** A model that runs out of tokens isn't wrong; it's thinking too long for the budget.
|
|
183
185
|
- **Watch TTFT for interactive use.** A model that is 5% more accurate but takes 3 s longer to start answering may be the worse choice.
|
|
184
186
|
|
|
@@ -134,6 +134,7 @@ tuieval verdict # PASS / FAIL / INCONCLUSIVE per m
|
|
|
134
134
|
tuieval report # the same with evidence, as a markdown file
|
|
135
135
|
tuieval history # every verdict change over time
|
|
136
136
|
tuieval compare --speed --pairwise --failures # scorecards
|
|
137
|
+
tuieval compare --reference <model> --packs X # each model vs one: better, tie or worse
|
|
137
138
|
tuieval selftest # check every test's reference and wrong answers
|
|
138
139
|
tuieval items # tests that don't separate models or look broken
|
|
139
140
|
tuieval logs # follow the server log of the model running now
|
|
@@ -153,6 +154,7 @@ tuieval help
|
|
|
153
154
|
- **Start with Production readiness.** A single critical failure already means FAIL, whatever the accuracy: a model that breaks a hard rule once in 300 answers will do it in production. The Failures tab and `tuieval report` list exactly which answers failed.
|
|
154
155
|
- **INCONCLUSIVE is not "nearly passed".** It says what's missing (usually a Certify run, or more trials).
|
|
155
156
|
- **Trust `tuieval compare --pairwise` over raw percentages.** Differences of one or two tests are usually noise.
|
|
157
|
+
- **Comparing variants of one model (fine-tunes, quantizations)?** `tuieval compare --reference <base>` pairs each against the base on the same questions and says better, tie or worse. Keep the server, quantization and settings the same, or they'll show up as differences too.
|
|
156
158
|
- **Read `trunc` before accuracy.** A model that runs out of tokens isn't wrong; it's thinking too long for the budget.
|
|
157
159
|
- **Watch TTFT for interactive use.** A model that is 5% more accurate but takes 3 s longer to start answering may be the worse choice.
|
|
158
160
|
|
|
@@ -34,6 +34,7 @@ needs = ["vision"] # vision | tools | long_context | <python modul
|
|
|
34
34
|
order = 30 # position in the list
|
|
35
35
|
tests = ["tests.yaml"] # optional; default: tests.* first, then every other .yaml/.csv file
|
|
36
36
|
screen = 20 # tests in a Screen run (spread across categories, one variant per group)
|
|
37
|
+
stop_early = true # stop a run of this pack once its FAIL is certain (default false; see below)
|
|
37
38
|
|
|
38
39
|
[certify]
|
|
39
40
|
repeat = 3 # repeats in a Certify run (default: models.toml [defaults] repeat)
|
|
@@ -48,6 +49,8 @@ min_consistency = 0.95 # share of variant groups where every variant p
|
|
|
48
49
|
|
|
49
50
|
`group` doubles as the **use case** in the readiness verdicts: all packs in a group must PASS for the use case to PASS. For example, a "Coding" use case could be a `coding` pack plus a `coding-sql` pack.
|
|
50
51
|
|
|
52
|
+
`stop_early` suits packs a weak model fails quickly, like a regression set of routine cases: a run stops the pack at the first critical failure, or once the accuracy or truncation bar is out of reach even if every remaining answer passes, saves what it has (the verdict is FAIL) and moves on. It asks first the questions models have failed most often. `tuieval run --stop-early` (or **Stop a pack once it fails** in Setup) does this for every pack in the run. A later Certify fills in the rest.
|
|
53
|
+
|
|
51
54
|
`needs` entries other than `vision`, `tools` and `long_context` name Python modules the pack's grading needs (for example `pandas`, when your hidden tests use it). The pack is skipped, with a note, where that module isn't installed in tuieval's Python environment.
|
|
52
55
|
|
|
53
56
|
## Tests (YAML)
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2, 0, '
|
|
21
|
+
__version__ = version = '0.2.0.dev79'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev79')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -13,7 +13,7 @@ Start here
|
|
|
13
13
|
tuieval verdict which model is ready for what, and what to run next
|
|
14
14
|
|
|
15
15
|
Day to day
|
|
16
|
-
tuieval compare [...] scorecards (--speed, --pairwise, --failures)
|
|
16
|
+
tuieval compare [...] scorecards (--speed, --pairwise, --reference M, --failures)
|
|
17
17
|
tuieval pta the PTA index: parsimony (tokens), time and accuracy per model, as bars
|
|
18
18
|
tuieval report the verdicts with their evidence, as a markdown file
|
|
19
19
|
tuieval history every verdict change, newest first
|
|
@@ -5,6 +5,8 @@ Usage:
|
|
|
5
5
|
tuieval compare --speed # speed & tokens table (TTFT, tok/s, tokens, memory)
|
|
6
6
|
tuieval compare --machine m3max-64gb # speed on another machine (measured there, or projected)
|
|
7
7
|
tuieval compare --pairwise --failures # is each difference real? which tests failed?
|
|
8
|
+
tuieval compare --reference MODEL # every model paired against one: better, tie or worse
|
|
9
|
+
tuieval compare --packs coding,rag # only these packs (works with every option)
|
|
8
10
|
tuieval compare results/some-model/*.json # specific files
|
|
9
11
|
"""
|
|
10
12
|
import collections
|
|
@@ -409,6 +411,49 @@ def pairwise(rows, rng=None):
|
|
|
409
411
|
return out
|
|
410
412
|
|
|
411
413
|
|
|
414
|
+
def vs_reference(rows, ref, rng=None):
|
|
415
|
+
"""Every other model against one reference model, paired on the questions both answered:
|
|
416
|
+
[{model, pack ("all" = every pack together), n, diff, lo, hi, verdict, crit, ref_crit, tokens}].
|
|
417
|
+
|
|
418
|
+
diff is the mean over shared questions of (pass rate - the reference's pass rate), with a 95%
|
|
419
|
+
bootstrap CI; verdict is "better" or "worse" when the CI excludes 0, else "tie". crit/ref_crit
|
|
420
|
+
count critical failures on those questions; tokens is the median of per-question token ratios
|
|
421
|
+
(median tokens over repeats, model / reference), so 0.5 means half the tokens."""
|
|
422
|
+
rng = rng or random.Random(0)
|
|
423
|
+
by = collections.defaultdict(lambda: collections.defaultdict(list)) # model -> test -> rows
|
|
424
|
+
suite = {}
|
|
425
|
+
for r in rows:
|
|
426
|
+
by[r["model"]][r["test"]].append(r)
|
|
427
|
+
suite[r["test"]] = r["suite"]
|
|
428
|
+
if ref not in by:
|
|
429
|
+
return []
|
|
430
|
+
out = []
|
|
431
|
+
for m in sorted(set(by) - {ref}):
|
|
432
|
+
shared = sorted(set(by[m]) & set(by[ref]))
|
|
433
|
+
for pack in sorted({suite[t] for t in shared}) + ["all"]:
|
|
434
|
+
tests = [t for t in shared if pack in ("all", suite[t])]
|
|
435
|
+
if not tests:
|
|
436
|
+
continue
|
|
437
|
+
rate = lambda model, t: statistics.fmean(r["ok"] for r in by[model][t])
|
|
438
|
+
diffs = [rate(m, t) - rate(ref, t) for t in tests]
|
|
439
|
+
lo, hi = bootstrap_ci(diffs, rng)
|
|
440
|
+
crit = lambda model: sum(r.get("severity") == "critical" for t in tests for r in by[model][t])
|
|
441
|
+
ratios = [a / b for t in tests
|
|
442
|
+
if (a := median(r["tokens"] for r in by[m][t])) and (b := median(r["tokens"] for r in by[ref][t]))]
|
|
443
|
+
out.append({"model": m, "pack": pack, "n": len(tests), "diff": statistics.fmean(diffs), "lo": lo, "hi": hi,
|
|
444
|
+
"verdict": "better" if lo > 0 else "worse" if hi < 0 else "tie",
|
|
445
|
+
"crit": crit(m), "ref_crit": crit(ref), "tokens": median(ratios)})
|
|
446
|
+
return out
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def vs_reference_table(results, ref):
|
|
450
|
+
header = ["model", "pack", "questions", f"vs {ref}", "95% CI", "verdict", "critical (vs ref)", "tokens vs ref"]
|
|
451
|
+
table = [[x["model"], x["pack"], x["n"], f"{100 * x['diff']:+.1f} pts", f"{100 * x['lo']:+.1f} to {100 * x['hi']:+.1f}",
|
|
452
|
+
x["verdict"].upper() if x["pack"] == "all" else x["verdict"], f"{x['crit']} ({x['ref_crit']})",
|
|
453
|
+
_fmt(x["tokens"], ".2f") + ("×" if x["tokens"] is not None else "")] for x in results]
|
|
454
|
+
return header, table
|
|
455
|
+
|
|
456
|
+
|
|
412
457
|
def failures(rows):
|
|
413
458
|
return [(r["model"], r.get("name", r["test"]), ("TRUNCATED " if r["truncated"] and not r["reason"].startswith("TRUNCATED")
|
|
414
459
|
else "") + r["reason"], r.get("difficulty", "unrated"))
|
|
@@ -499,10 +544,22 @@ def main(argv):
|
|
|
499
544
|
flags = {a for a in argv if a.startswith("--")}
|
|
500
545
|
log_dir = argv[argv.index("--logs") + 1] if "--logs" in argv else None
|
|
501
546
|
machine_arg = argv[argv.index("--machine") + 1] if "--machine" in argv else None
|
|
502
|
-
|
|
547
|
+
ref = argv[argv.index("--reference") + 1] if "--reference" in argv else None
|
|
548
|
+
only_packs = argv[argv.index("--packs") + 1].split(",") if "--packs" in argv else None
|
|
549
|
+
values = (log_dir, machine_arg, ref, ",".join(only_packs) if only_packs else None)
|
|
550
|
+
paths = [a for a in argv if not a.startswith("--") and a not in values] or default_paths()
|
|
551
|
+
if only_packs:
|
|
552
|
+
paths = [p for p in paths if os.path.basename(p)[:-len(".json")] in only_packs]
|
|
503
553
|
if not paths:
|
|
504
554
|
sys.exit(__doc__ + "\n(no results found in results/<model>/<pack>.json: run some evals first)")
|
|
505
555
|
rows, infos = load(paths)
|
|
556
|
+
if ref:
|
|
557
|
+
if ref not in {r["model"] for r in rows}:
|
|
558
|
+
sys.exit(f"no results for reference model {ref!r}" + (f" in {', '.join(only_packs)}" if only_packs else ""))
|
|
559
|
+
print(f"Paired against {ref} on the questions both answered (pass rate per question, repeats averaged;"
|
|
560
|
+
"\n95% bootstrap CI over questions; BETTER/WORSE only when the CI excludes 0):")
|
|
561
|
+
print_table(*vs_reference_table(vs_reference(rows, ref, random.Random(0)), ref))
|
|
562
|
+
return
|
|
506
563
|
rng = random.Random(0)
|
|
507
564
|
from . import packs as packs_mod
|
|
508
565
|
packs_mod.load_packs(errors=[]) # loads the workspace's graders, and their Scorecard columns
|
|
@@ -28,6 +28,7 @@ interrupted run resumes from its .partial.jsonl, request by request.
|
|
|
28
28
|
Events are delivered as on_event(kind, **data), from the engine thread.
|
|
29
29
|
"""
|
|
30
30
|
import base64
|
|
31
|
+
import collections
|
|
31
32
|
import contextlib
|
|
32
33
|
import dataclasses
|
|
33
34
|
import fcntl
|
|
@@ -438,6 +439,8 @@ class Job:
|
|
|
438
439
|
finished: float = 0.0
|
|
439
440
|
resumed: int = 0 # answers already done when this sitting started
|
|
440
441
|
earlier: list = dataclasses.field(default_factory=list) # earlier sittings (see Engine._sittings)
|
|
442
|
+
stop_early: bool = False # stop once the pack's FAIL is certain (verdict.fail_certain)
|
|
443
|
+
first: list = dataclasses.field(default_factory=list) # test ids round 1 asks first (most failed)
|
|
441
444
|
|
|
442
445
|
@property
|
|
443
446
|
def label(self):
|
|
@@ -1152,14 +1155,15 @@ class Engine:
|
|
|
1152
1155
|
return not current and header_matches(head, pack.fingerprint, settings)
|
|
1153
1156
|
|
|
1154
1157
|
# ---- planning
|
|
1155
|
-
def plan(self, labels, pack_names, repeat=None, tier="screen", force=False, tests=None):
|
|
1158
|
+
def plan(self, labels, pack_names, repeat=None, tier="screen", force=False, tests=None, stop_early=None):
|
|
1156
1159
|
"""Jobs for models x packs. tier: smoke (3 tests), screen (a spread sample), certify (all
|
|
1157
1160
|
tests). repeat: times each question is asked; None = the tier default (1 for smoke and
|
|
1158
1161
|
screen, the pack's certification repeats for certify). tests: {pack: [test ids]} runs just
|
|
1159
1162
|
those tests of a pack instead of the tier's sample (unknown ids raise PackError). Earlier
|
|
1160
1163
|
matching results are kept and only missing answers are run, unless force; force with
|
|
1161
1164
|
picked tests reruns those and keeps the pack's other answers. force is True, False, or the
|
|
1162
|
-
keys ("label/pack") of the jobs to retest, the others resuming.
|
|
1165
|
+
keys ("label/pack") of the jobs to retest, the others resuming. stop_early: stop each pack
|
|
1166
|
+
once its FAIL is certain, asking first what models fail most; None = each pack's stop_early."""
|
|
1163
1167
|
if tier not in TIERS:
|
|
1164
1168
|
raise ValueError(f"tier must be one of {TIERS}")
|
|
1165
1169
|
jobs = []
|
|
@@ -1173,7 +1177,10 @@ class Engine:
|
|
|
1173
1177
|
job = Job(m, pack, reps, pack.pick(picked) if picked else pack.select(tier),
|
|
1174
1178
|
self.result_path(label, name, tier), effective_sampling(self.cfg, m), tier=tier,
|
|
1175
1179
|
merge=not retest or bool(picked), redo=retest and bool(picked), fresh=retest,
|
|
1176
|
-
settings=self.settings(m)
|
|
1180
|
+
settings=self.settings(m),
|
|
1181
|
+
stop_early=pack.stop_early if stop_early is None else bool(stop_early))
|
|
1182
|
+
if job.stop_early:
|
|
1183
|
+
job.first = self.most_failed_first(pack)
|
|
1177
1184
|
sv = self.serving(m)
|
|
1178
1185
|
status = self.result_status(label, name, tier)
|
|
1179
1186
|
job.discards = self._discards(job)
|
|
@@ -2005,13 +2012,30 @@ class Engine:
|
|
|
2005
2012
|
|
|
2006
2013
|
@staticmethod
|
|
2007
2014
|
def round_order(job, rep):
|
|
2008
|
-
"""The tests in the order round `rep` asks them: the pack's order first
|
|
2009
|
-
shuffle per round (the same on every run, so resuming
|
|
2015
|
+
"""The tests in the order round `rep` asks them: the pack's order first (with an early stop,
|
|
2016
|
+
the most failed first), then a fixed shuffle per round (the same on every run, so resuming
|
|
2017
|
+
and reruns ask in the same order)."""
|
|
2010
2018
|
tests = list(job.tests)
|
|
2011
2019
|
if rep:
|
|
2012
2020
|
random.Random(f"{job.pack.name}:{rep}").shuffle(tests)
|
|
2021
|
+
elif job.first:
|
|
2022
|
+
rank = {tid: i for i, tid in enumerate(job.first)}
|
|
2023
|
+
tests.sort(key=lambda t: rank.get(t["id"], len(rank)))
|
|
2013
2024
|
return tests
|
|
2014
2025
|
|
|
2026
|
+
def most_failed_first(self, pack):
|
|
2027
|
+
"""The pack's test ids, the ones models failed most often first (critical failures count
|
|
2028
|
+
double), from every model's stored results; ties keep the pack's order."""
|
|
2029
|
+
fails, seen = collections.Counter(), collections.Counter()
|
|
2030
|
+
for m in self.cfg["models"]:
|
|
2031
|
+
for r in self.records(m["label"], pack.name):
|
|
2032
|
+
if client.server_error_row(r):
|
|
2033
|
+
continue
|
|
2034
|
+
seen[r["test"]] += 1
|
|
2035
|
+
fails[r["test"]] += (not r["pass"]) + (r.get("severity") == "critical")
|
|
2036
|
+
order = {t["id"]: i for i, t in enumerate(pack.tests)}
|
|
2037
|
+
return sorted(order, key=lambda tid: (-(fails[tid] / seen[tid] if seen[tid] else 0), order[tid]))
|
|
2038
|
+
|
|
2015
2039
|
# ---- sittings: a pack run over several sessions (stopped and resumed) keeps its total time
|
|
2016
2040
|
SITTING_KEYS = ("load_s", "peak_rss_mb", "gpu_peak_gb", "vram_peak_gb", "kernel_share_max", "machine",
|
|
2017
2041
|
"loaded_alongside")
|
|
@@ -2143,7 +2167,10 @@ class Engine:
|
|
|
2143
2167
|
# a hosted API has no /tokenize to split reasoning from answer tokens (it reports the split)
|
|
2144
2168
|
tokenize_url = None if self.cfg["servers"][job.model["server"]].get("api_key_env") else base_url
|
|
2145
2169
|
warned_reuse = False
|
|
2170
|
+
stopped = self._stop_reason(job, mine)
|
|
2146
2171
|
for rep in range(job.repeat):
|
|
2172
|
+
if stopped:
|
|
2173
|
+
break
|
|
2147
2174
|
for test in self.round_order(job, rep):
|
|
2148
2175
|
if (test["id"], rep) in done_keys:
|
|
2149
2176
|
continue
|
|
@@ -2237,6 +2264,10 @@ class Engine:
|
|
|
2237
2264
|
job.passed += bool(g["pass"])
|
|
2238
2265
|
job.failed += not g["pass"]
|
|
2239
2266
|
self.emit("request_done", job=job, record=rec)
|
|
2267
|
+
mine.append(rec)
|
|
2268
|
+
stopped = self._stop_reason(job, mine)
|
|
2269
|
+
if stopped:
|
|
2270
|
+
break
|
|
2240
2271
|
job.status, job.finished = "done", time.time()
|
|
2241
2272
|
merged = {(r["test"], r["repeat"]): r for r in kept}
|
|
2242
2273
|
merged.update({(r["test"], r["repeat"]): r for r in new})
|
|
@@ -2246,9 +2277,17 @@ class Engine:
|
|
|
2246
2277
|
os.remove(partial)
|
|
2247
2278
|
with contextlib.suppress(OSError):
|
|
2248
2279
|
os.remove(self._sittings_path(job))
|
|
2249
|
-
job.note = f"{job.passed}/{job.done} passed"
|
|
2280
|
+
job.note = f"{job.passed}/{job.done} passed" + (f"; stopped early, FAIL is certain: {stopped}" if stopped else "")
|
|
2250
2281
|
self.emit("job_done", job=job)
|
|
2251
2282
|
|
|
2283
|
+
def _stop_reason(self, job, rows):
|
|
2284
|
+
"""Why the job stops early now (verdict.fail_certain), or None."""
|
|
2285
|
+
if not job.stop_early:
|
|
2286
|
+
return None
|
|
2287
|
+
from . import verdict
|
|
2288
|
+
return verdict.fail_certain(job.pack, [r for r in rows if not client.server_error_row(r)],
|
|
2289
|
+
self.cfg["defaults"]["repeat"])
|
|
2290
|
+
|
|
2252
2291
|
def _partial_to_history(self, job, keep=False):
|
|
2253
2292
|
"""Keep an unfinished run that is set aside in history/; keep: copy it (some of its answers stay)."""
|
|
2254
2293
|
partial = job.out_path + ".partial.jsonl"
|
|
@@ -49,6 +49,7 @@ class Pack:
|
|
|
49
49
|
gate: dict # release criteria (see verdict.py)
|
|
50
50
|
screen: int # tests in a screening run
|
|
51
51
|
certify_repeat: int # repeats in a certification run (0 = models.toml default)
|
|
52
|
+
stop_early: bool = False # stop the pack once its FAIL is certain (pack.toml stop_early)
|
|
52
53
|
|
|
53
54
|
@property
|
|
54
55
|
def modules(self):
|
|
@@ -221,7 +222,8 @@ def load_pack(path):
|
|
|
221
222
|
system=system, needs=list(needs), tools=tools, tests=tests,
|
|
222
223
|
fingerprint=_fingerprint(system, tools, meta.get("grader", "answer"), tests, images),
|
|
223
224
|
gate=gate, screen=int(meta.get("screen", 10)),
|
|
224
|
-
certify_repeat=int(meta.get("certify", {}).get("repeat", 0))
|
|
225
|
+
certify_repeat=int(meta.get("certify", {}).get("repeat", 0)),
|
|
226
|
+
stop_early=bool(meta.get("stop_early", False)))
|
|
225
227
|
|
|
226
228
|
|
|
227
229
|
def load_packs(packs_dir=None, errors=None):
|
|
@@ -745,6 +745,9 @@ def main(argv=None):
|
|
|
745
745
|
"pack in --packs, else pack:id (and --packs defaults to those packs)")
|
|
746
746
|
p.add_argument("--retest", "--force", action="store_true", dest="force",
|
|
747
747
|
help="ask again even if already answered: the packs, or just the --tests (earlier answers go to history/)")
|
|
748
|
+
p.add_argument("--stop-early", action="store_true",
|
|
749
|
+
help="stop each pack once its FAIL is certain (a critical failure, or a bar out of reach), "
|
|
750
|
+
"asking first what models fail most (default: each pack's stop_early)")
|
|
748
751
|
p.add_argument("--repeat", type=int, help="times each question is asked, any tier (default: 1 for smoke and "
|
|
749
752
|
"screen, the pack's certification count for certify)")
|
|
750
753
|
p.add_argument("--tier", choices=engine.TIERS, default="screen",
|
|
@@ -800,7 +803,7 @@ def main(argv=None):
|
|
|
800
803
|
if args.tests:
|
|
801
804
|
tests, pack_names = parse_tests(args.tests, pack_names if (args.packs or args.preset) else None, e.packs)
|
|
802
805
|
try:
|
|
803
|
-
jobs = e.plan(labels, pack_names, args.repeat, args.tier, args.force, tests)
|
|
806
|
+
jobs = e.plan(labels, pack_names, args.repeat, args.tier, args.force, tests, args.stop_early or None)
|
|
804
807
|
except packs_mod.PackError as err:
|
|
805
808
|
sys.exit(str(err))
|
|
806
809
|
|
|
@@ -390,6 +390,10 @@ picked with e, if any). Without it, answered questions are skipped. Before anyth
|
|
|
390
390
|
the packs with earlier answers and lets you choose for each: Zero (ask everything again), Continue
|
|
391
391
|
(keep what's answered, ask the rest) or Leave out. Replaced answers go to history/, never deleted.
|
|
392
392
|
|
|
393
|
+
[b]Stop a pack once it fails[/b]: stop each pack as soon as its FAIL is certain (a critical failure, or
|
|
394
|
+
an accuracy or truncation bar out of reach) and move on, asking first the questions models fail most.
|
|
395
|
+
Packs with stop_early = true in pack.toml always do this. A later Certify fills in the rest.
|
|
396
|
+
|
|
393
397
|
[b]Keys[/b]
|
|
394
398
|
s start (with a run going: add to it, or queue it) r results
|
|
395
399
|
a add a model (a GGUF, a model id, openrouter:<id>) m scan model folders for new GGUFs
|
|
@@ -652,6 +656,8 @@ class SetupScreen(Screen):
|
|
|
652
656
|
yield Label("Models at a time")
|
|
653
657
|
yield Input("", id="parallel", type="integer", max_length=1, placeholder="1")
|
|
654
658
|
yield Checkbox("Retest (ask again, even if already answered)", id="force")
|
|
659
|
+
with Horizontal(id="options2"):
|
|
660
|
+
yield Checkbox("Stop a pack once it fails (a critical failure, or a bar out of reach)", id="stop-early")
|
|
655
661
|
yield Static(id="estimate")
|
|
656
662
|
with Horizontal(id="buttons"):
|
|
657
663
|
yield Button("Start [s]", id="start", variant="success")
|
|
@@ -1009,6 +1015,8 @@ class SetupScreen(Screen):
|
|
|
1009
1015
|
self.query_one(f"#tier-{tier}", RadioButton).value = True
|
|
1010
1016
|
if "force" in sel:
|
|
1011
1017
|
self.query_one("#force", Checkbox).value = bool(sel["force"])
|
|
1018
|
+
if "stop_early" in sel:
|
|
1019
|
+
self.query_one("#stop-early", Checkbox).value = bool(sel["stop_early"])
|
|
1012
1020
|
self.query_one("#filter", Input).value = ""
|
|
1013
1021
|
self.refresh_models()
|
|
1014
1022
|
if not quiet:
|
|
@@ -1043,7 +1051,7 @@ class SetupScreen(Screen):
|
|
|
1043
1051
|
return
|
|
1044
1052
|
picks = self.current_picks(suites)
|
|
1045
1053
|
sel = {"models": labels, "packs": suites, "repeat": repeat, "tier": tier, "force": force, "tests": picks,
|
|
1046
|
-
"parallel": self.parallel()}
|
|
1054
|
+
"parallel": self.parallel(), "stop_early": self.query_one("#stop-early", Checkbox).value}
|
|
1047
1055
|
e = self.app.engine
|
|
1048
1056
|
jobs = e.plan(labels, suites, repeat, tier, True, picks)
|
|
1049
1057
|
if not any(j.status == "waiting" for j in jobs):
|
|
@@ -1573,7 +1581,7 @@ class QueuedRun:
|
|
|
1573
1581
|
"""(jobs, None), or (None, why it can't run) now."""
|
|
1574
1582
|
sel = self.sel
|
|
1575
1583
|
jobs = [j for j in eng.plan(sel["models"], sel["packs"], sel["repeat"], sel["tier"], self.retest,
|
|
1576
|
-
self.tests(eng)) if j.key not in self.leave]
|
|
1584
|
+
self.tests(eng), sel.get("stop_early") or None) if j.key not in self.leave]
|
|
1577
1585
|
waiting = [j for j in jobs if j.status == "waiting"]
|
|
1578
1586
|
if not waiting:
|
|
1579
1587
|
return None, "every pack left is already done"
|
|
@@ -3076,7 +3084,7 @@ class EvalsApp(App):
|
|
|
3076
3084
|
#tier-row Label { padding: 1 1 0 0; }
|
|
3077
3085
|
#tier-row RadioSet { layout: horizontal; width: auto; height: auto; }
|
|
3078
3086
|
#tier-row RadioButton { width: auto; margin-right: 2; }
|
|
3079
|
-
#options { height: 3; padding: 0 1; align-vertical: middle; }
|
|
3087
|
+
#options, #options2 { height: 3; padding: 0 1; align-vertical: middle; }
|
|
3080
3088
|
#options Label { padding: 1 1 0 0; }
|
|
3081
3089
|
#options Input { width: 12; }
|
|
3082
3090
|
#options #parallel { width: 8; }
|
|
@@ -73,6 +73,26 @@ def consistency(records):
|
|
|
73
73
|
return ok / len(full), len(full)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def fail_certain(pack, records, repeat_default=3):
|
|
77
|
+
"""Why this pack's verdict is FAIL whatever the remaining answers of its certification say,
|
|
78
|
+
or None: a critical failure, or a bar out of reach even if every answer still to come passes
|
|
79
|
+
(accuracy) or none of them is cut off (truncation). What an early stop checks after each answer."""
|
|
80
|
+
crit = next((r for r in records if r.get("severity") == "critical"), None)
|
|
81
|
+
if crit:
|
|
82
|
+
return f"critical failure ({crit['test']})"
|
|
83
|
+
n = len(records)
|
|
84
|
+
total = max(n, len(pack.tests) * (pack.certify_repeat or repeat_default))
|
|
85
|
+
passed = sum(bool(r["pass"]) for r in records)
|
|
86
|
+
bar = pack.gate.get("min_accuracy", 0.8)
|
|
87
|
+
if n and wilson(passed + total - n, total)[1] < bar:
|
|
88
|
+
return (f"{n - passed} wrong of {n}: under the {100 * bar:.0f}% bar even if all "
|
|
89
|
+
f"{total - n} answers left pass")
|
|
90
|
+
cut = sum(r.get("finish") == "length" for r in records)
|
|
91
|
+
if "max_truncation" in pack.gate and cut / total > pack.gate["max_truncation"]:
|
|
92
|
+
return f"{cut} answers cut off by max_tokens: over the {100 * pack.gate['max_truncation']:g}% limit for the pack"
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
76
96
|
def evaluate(pack, records, repeat_default=3, speed_gate=False):
|
|
77
97
|
"""Quality verdict for one model on one pack. `records` are result rows from
|
|
78
98
|
results/<label>/<pack>.json. The p90 time limit is judged per machine by latency() unless
|
|
@@ -112,6 +132,9 @@ def evaluate(pack, records, repeat_default=3, speed_gate=False):
|
|
|
112
132
|
if hi < min_acc:
|
|
113
133
|
fails.append(f"accuracy {100 * passed / n:.1f}% (95% CI {100 * lo:.0f}-{100 * hi:.0f}%) is below "
|
|
114
134
|
f"the {100 * min_acc:.0f}% bar")
|
|
135
|
+
certain = fail_certain(pack, records, repeat_default)
|
|
136
|
+
if certain and not crit and not fails: # a run stopped early (or a partial one) that can't pass any more
|
|
137
|
+
fails.append(certain)
|
|
115
138
|
if "min_consistency" in gate and cons is not None and certified and cons < gate["min_consistency"]:
|
|
116
139
|
fails.append(f"only {100 * cons:.0f}% of {groups} rephrased cases answered consistently "
|
|
117
140
|
f"(needs {100 * gate['min_consistency']:.0f}%)")
|
|
@@ -151,6 +151,52 @@ class Starters(unittest.TestCase):
|
|
|
151
151
|
self.assertIn("separate the models", tuieval(self.ws, "compare", "--speed").stdout)
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
class EarlyStop(unittest.TestCase):
|
|
155
|
+
"""--stop-early ends a pack once its FAIL is certain, and asks what models fail most first."""
|
|
156
|
+
|
|
157
|
+
def test_wrong_model_stops_and_good_model_finishes(self):
|
|
158
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
159
|
+
ws = os.path.join(tmp, "ws")
|
|
160
|
+
tuieval(tmp, "init", ws)
|
|
161
|
+
tuieval(ws, "new-pack", "demo", "--grader", "answer")
|
|
162
|
+
packs = os.path.join(ws, "packs")
|
|
163
|
+
good, bad = Mock(packs, "oracle"), Mock(packs, "wrong")
|
|
164
|
+
try:
|
|
165
|
+
with open(os.path.join(ws, "models.toml"), "a") as f:
|
|
166
|
+
f.write(f'\n[servers.good]\nurl = "http://127.0.0.1:{good.port}"\n'
|
|
167
|
+
f'\n[servers.bad]\nurl = "http://127.0.0.1:{bad.port}"\n')
|
|
168
|
+
tuieval(ws, "add", "mock", "--server", "good", "--label", "good-mock")
|
|
169
|
+
tuieval(ws, "add", "mock", "--server", "bad", "--label", "bad-mock")
|
|
170
|
+
out = tuieval(ws, "run", "--tier", "certify", "--stop-early").stdout
|
|
171
|
+
finally:
|
|
172
|
+
good.stop()
|
|
173
|
+
bad.stop()
|
|
174
|
+
self.assertIn("bad-mock/demo: done", out)
|
|
175
|
+
self.assertIn("stopped early, FAIL is certain", out)
|
|
176
|
+
bad_res = json.loads(read(os.path.join(ws, "results", "bad-mock", "demo.json")))["results"]
|
|
177
|
+
good_res = json.loads(read(os.path.join(ws, "results", "good-mock", "demo.json")))["results"]
|
|
178
|
+
self.assertEqual(len(good_res), 18)
|
|
179
|
+
self.assertLess(len(bad_res), 18)
|
|
180
|
+
self.assertIn("bad-mock: not ready for", tuieval(ws, "verdict").stdout.split("Details")[0])
|
|
181
|
+
# bad-mock's failures now come first for the next model with an early stop
|
|
182
|
+
code = ("from tuieval import engine; e = engine.Engine(); p = e.packs['demo']; "
|
|
183
|
+
"first = e.most_failed_first(p); fails = {r['test'] for r in e.records('bad-mock', 'demo') "
|
|
184
|
+
"if not r['pass']}; print(set(first[:len(fails)]) == fails)")
|
|
185
|
+
self.assertEqual(subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
|
|
186
|
+
env=dict(os.environ, TUIEVAL_HOME=ws)).stdout.strip(), "True")
|
|
187
|
+
|
|
188
|
+
def test_fail_certain(self):
|
|
189
|
+
from tuieval import verdict
|
|
190
|
+
pack = types.SimpleNamespace(tests=[{"id": str(i)} for i in range(10)], certify_repeat=3,
|
|
191
|
+
gate={"min_accuracy": 0.8, "max_truncation": 0.05})
|
|
192
|
+
row = lambda ok, **kw: {"test": "0", "pass": ok, **kw}
|
|
193
|
+
self.assertIsNone(verdict.fail_certain(pack, [row(False)] * 2)) # 28 of 30 could still pass
|
|
194
|
+
self.assertIsNone(verdict.fail_certain(pack, [row(False)] * 9)) # best case 21/30: CI reaches 83%
|
|
195
|
+
self.assertIn("under the 80% bar", verdict.fail_certain(pack, [row(False)] * 12))
|
|
196
|
+
self.assertIn("critical failure", verdict.fail_certain(pack, [row(False, severity="critical")]))
|
|
197
|
+
self.assertIn("cut off", verdict.fail_certain(pack, [row(True, finish="length")] * 2)) # 2 of 30 > 5%
|
|
198
|
+
|
|
199
|
+
|
|
154
200
|
class WorkspaceGraders(unittest.TestCase):
|
|
155
201
|
"""A workspace's graders/ and a pack's grader.py are loaded, with their critical flag."""
|
|
156
202
|
|
|
@@ -209,6 +255,29 @@ class Units(unittest.TestCase):
|
|
|
209
255
|
self.assertEqual(compare.settings_notes(infos),
|
|
210
256
|
["Settings differ: effort medium on 4 models; penalty 1.5 on j."])
|
|
211
257
|
|
|
258
|
+
def test_vs_reference_pairs_on_shared_questions(self):
|
|
259
|
+
from tuieval import compare
|
|
260
|
+
row = lambda m, pack, t, ok, tokens=100, sev=None, rep=0: {
|
|
261
|
+
"model": m, "suite": pack, "test": f"{pack}: {t}", "ok": ok, "tokens": tokens, "severity": sev, "repeat": rep}
|
|
262
|
+
rows = [row("ref", "a", f"q{i}", i < 10) for i in range(20)] # ref: 10/20 on a
|
|
263
|
+
rows += [row("good", "a", f"q{i}", True, tokens=50) for i in range(20)] # good: 20/20, half the tokens
|
|
264
|
+
rows += [row("same", "a", f"q{i}", i < 10) for i in range(20)]
|
|
265
|
+
rows += [row("bad", "a", f"q{i}", False, sev="critical" if i == 0 else None) for i in range(20)]
|
|
266
|
+
rows += [row("ref", "b", "x", True), row("good", "b", "x", False), row("good", "b", "x", True, rep=1)]
|
|
267
|
+
rows += [row("good", "b", "only-good", True)] # not shared: ignored
|
|
268
|
+
res = {(x["model"], x["pack"]): x for x in compare.vs_reference(rows, "ref")}
|
|
269
|
+
self.assertEqual(res["good", "a"]["verdict"], "better")
|
|
270
|
+
self.assertAlmostEqual(res["good", "a"]["diff"], 0.5)
|
|
271
|
+
self.assertEqual(res["good", "a"]["tokens"], 0.5)
|
|
272
|
+
self.assertEqual(res["same", "a"]["verdict"], "tie")
|
|
273
|
+
self.assertEqual((res["bad", "a"]["verdict"], res["bad", "a"]["crit"], res["bad", "a"]["ref_crit"]), ("worse", 1, 0))
|
|
274
|
+
self.assertEqual(res["good", "b"]["n"], 1)
|
|
275
|
+
self.assertAlmostEqual(res["good", "b"]["diff"], -0.5) # repeats averaged
|
|
276
|
+
self.assertEqual(res["good", "all"]["n"], 21)
|
|
277
|
+
self.assertEqual(compare.vs_reference(rows, "nobody"), [])
|
|
278
|
+
header, table = compare.vs_reference_table([res["good", "all"]], "ref")
|
|
279
|
+
self.assertEqual(table[0][5], "BETTER")
|
|
280
|
+
|
|
212
281
|
def test_eta_goes_pack_by_pack(self):
|
|
213
282
|
from tuieval.tui import RunScreen
|
|
214
283
|
job = lambda key, label, total, done=0, status="waiting": types.SimpleNamespace(
|
|
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
|
{tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev75 → tuieval-0.2.0.dev79}/src/tuieval/templates/packs/tool_call/tools.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|