tuieval 0.2.0.dev131__tar.gz → 0.2.0.dev133__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.dev131 → tuieval-0.2.0.dev133}/CHANGELOG.md +1 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/models.md +1 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/compare.py +6 -3
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/engine.py +8 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/pta.py +28 -7
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/tui.py +5 -2
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/tests/test_tuieval.py +16 -8
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/.gitignore +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/LICENSE +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/README.md +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/agent-packs.md +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/agents.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/cli.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/dates.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/insights.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/run_evals.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/yamlout.py +0 -0
- {tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/tests/mock_server.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Repeats: the PTA index, `tuieval compare` and Setup show how many times each model answered each question (`×3`, `×1-3`), so single-run results stand out.
|
|
5
6
|
- Partial credit: the PTA index shows each model's score (e.g. 5 of 6 checks passed is 83%) beside accuracy, and can be ordered by it (By score, `tuieval pta --sort score`). Why models fail splits failures into near miss, partly right and wrong; Failures shows each one's kind and checks and filters by kind; Per question cells show `✗ 5/6`. Verdicts are unchanged.
|
|
6
7
|
- Results: picking a pack (PTA index or Per question) compares the models that answered all of it; the Models button can change that, and All packs goes back to the earlier pick.
|
|
7
8
|
- Fix: a request that runs out of time says `time limit: timed out after N s`, not `connection error`, and `tuieval compare` counts it against the model like the run does (it was left out as an error).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev133
|
|
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
|
|
@@ -106,6 +106,7 @@ The **PTA index** puts parsimony (tokens), time and accuracy side by side, as a
|
|
|
106
106
|
- **T time**: the total time to answer those questions, as how many times the fastest model's, e.g. `28×` (★ in **time/answer**).
|
|
107
107
|
- **A accuracy**: the share of answers to those questions that passed. Answers cut off at `max_tokens` count as wrong, but show in orange after the green, with `(n% truncated)` beside the number and a **truncated** column in the table: those failures are a model out of tokens (overthinking), not a wrong answer yet. A pack's own `max_tokens` gives it more room.
|
|
108
108
|
- **Score**: partial credit, averaged over the same answers. Graders that can tell how close an answer came give it: the code grader the share of checks passed (5 of 6 is 83%, where accuracy counts it wrong), agent packs the share of hidden tests, the tool-call and RAG graders part marks. It shows beside the accuracy when the two differ, and in a **score** column. Order the models by it with the **By score** dropdown in the tab or `tuieval pta --sort score`; verdicts still use accuracy only.
|
|
109
|
+
- **Repeats**: beside each name, how many times the model answered each question compared (`×3`, or `×1-3` when it varies), also a **repeats** column here and in `tuieval compare`. A `×1` model (in yellow) answered each question once, so its accuracy is rougher: one answer more or less right moves it by 100 / questions points. Setup shows the same beside each pack in a model's results (`✓coding×3`).
|
|
109
110
|
- **Over budget**: some providers don't stop a model at `max_tokens` (thinking goes on past it), so the answer isn't cut off but the model had more room than the others. Answers that used more tokens than were sent as `max_tokens` show as `(n over budget)` beside the accuracy, an **over budget** column, `over` in Results → Per question and a note in the answer view. They're only flagged; `over_budget = "fail"` in `[defaults]` counts them as failed instead (stored results aren't changed, so switching back restores them).
|
|
110
111
|
|
|
111
112
|
The P and T bars are as long as the tokens and time used: the model that used the most fills its bar, the others in proportion (at least one block), so **shorter is better**. The A bar is the share of answers right, so **longer is better**; it's full only when every answer passed. Each heading says which way is better.
|
|
@@ -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.dev133'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev133')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -248,12 +248,14 @@ def scorecard(rows, rng=None):
|
|
|
248
248
|
suites = sorted({r["suite"] for r in rows})
|
|
249
249
|
extra = dict(graders.COLUMNS) # columns your graders add (graders.scorecard_column)
|
|
250
250
|
over = any(r.get("over_budget") for r in rows) # a column only when some answer ran past max_tokens
|
|
251
|
-
header = ["model"] + suites + ["overall", "95% CI", "mean score", "all repeats", *extra, "errors", "trunc",
|
|
251
|
+
header = ["model", "repeats"] + suites + ["overall", "95% CI", "mean score", "all repeats", *extra, "errors", "trunc",
|
|
252
252
|
*(["over budget"] if over else []), "rep"]
|
|
253
253
|
table = []
|
|
254
254
|
for m in models:
|
|
255
255
|
mine = [r for r in rows if r["model"] == m]
|
|
256
|
-
|
|
256
|
+
per_test = collections.Counter(r["test"] for r in mine) # answers per question: the repeats
|
|
257
|
+
few, most = min(per_test.values()), max(per_test.values())
|
|
258
|
+
line = [m, f"×{few}" if few == most else f"×{few}-{most}"]
|
|
257
259
|
for s in suites:
|
|
258
260
|
sub = [r for r in mine if r["suite"] == s]
|
|
259
261
|
line.append(f"{sum(r['ok'] for r in sub)}/{len(sub)}" if sub else "-")
|
|
@@ -678,7 +680,8 @@ def main(argv):
|
|
|
678
680
|
for note in settings_notes(infos):
|
|
679
681
|
print("NOTE:", note)
|
|
680
682
|
print_table(*scorecard(rows, rng))
|
|
681
|
-
print("\
|
|
683
|
+
print("\nrepeats = answers per question (×1: one answer each, the roughest accuracy).")
|
|
684
|
+
print("mean score = average score, with partial credit; all repeats = tests passed on every repeat.")
|
|
682
685
|
print("trunc = hit max_tokens; rep = output ends in a degenerate repeated pattern.")
|
|
683
686
|
header, table = by_difficulty(rows)
|
|
684
687
|
if any(r.get("difficulty") in LEVELS for r in rows):
|
|
@@ -989,6 +989,14 @@ class Engine:
|
|
|
989
989
|
need = {(t["id"], r) for t in pack.tests for r in range(self.certify_repeat(pack))}
|
|
990
990
|
return "certified" if need <= keys else "screened"
|
|
991
991
|
|
|
992
|
+
def result_repeats(self, label, pack_name, tier="certify"):
|
|
993
|
+
"""How many times each answered question of a stored result was asked: (fewest, most), or None."""
|
|
994
|
+
data = self._result(self.result_path(label, pack_name, tier))
|
|
995
|
+
if not data or not data["results"]:
|
|
996
|
+
return None
|
|
997
|
+
per_test = collections.Counter(r["test"] for r in data["results"])
|
|
998
|
+
return min(per_test.values()), max(per_test.values())
|
|
999
|
+
|
|
992
1000
|
def _partial_lines(self, path):
|
|
993
1001
|
"""(header, answers) of an unfinished run's .partial.jsonl, cached like _result;
|
|
994
1002
|
(None, []) if there is none."""
|
|
@@ -15,6 +15,9 @@ the others in proportion, at least one block), so shorter is better; the A bar:
|
|
|
15
15
|
P and T also carry a 0-100 score (100 for the best, POINTS_PER_DOUBLING points less for every
|
|
16
16
|
doubling), used to order models with the same accuracy.
|
|
17
17
|
|
|
18
|
+
Beside each name, the repeats: how many times it answered each of those questions ("×3", or "×1-3" when
|
|
19
|
+
it varies). A ×1 model's accuracy rests on one answer per question, so it is rougher than a ×3 one's.
|
|
20
|
+
|
|
18
21
|
Models are compared on the questions all of them answered, so one that skipped some never looks
|
|
19
22
|
faster. Server and connection errors aren't answers. By default a model with fewer than half as many
|
|
20
23
|
answers as the most-answered model is left out (listed, so it can be picked), or it would shrink the
|
|
@@ -54,7 +57,7 @@ SORTS = ("accuracy", "score")
|
|
|
54
57
|
|
|
55
58
|
def index(rows, models=None, memory=None, sort="accuracy"):
|
|
56
59
|
"""{"questions": n shared, "models": [{model, P, T, A, S, cut, cut_pct, tokens, total_s, passed, answers, alongside,
|
|
57
|
-
fewest, fastest, tps, ttft, memory_gb}],
|
|
60
|
+
fewest, fastest, tps, ttft, memory_gb, repeats: (fewest, most) answers per question}],
|
|
58
61
|
"left_out": [(model, questions answered)], "no_answers": [model], "most": questions answered by the
|
|
59
62
|
most-answered model}, best accuracy (sort="accuracy") or score (sort="score") first. S: the mean
|
|
60
63
|
score (partial credit) in %. models: the ones to compare (default: every model in rows
|
|
@@ -92,6 +95,7 @@ score (partial credit) in %. models: the ones to compare (default: every model i
|
|
|
92
95
|
"tps": compare.median(r.get("gen_tps") for c in cells for r in c["rows"]),
|
|
93
96
|
"ttft": compare.median(r.get("ttft") for c in cells for r in c["rows"]),
|
|
94
97
|
"memory_gb": (memory or {}).get(m),
|
|
98
|
+
"repeats": (min(c["n"] for c in cells), max(c["n"] for c in cells)) if cells else None,
|
|
95
99
|
# answers that ran while other models were served shared the machine's speed
|
|
96
100
|
"alongside": any(r.get("alongside") for c in cells for r in c["rows"])})
|
|
97
101
|
fastest = min((x["total_s"] for x in out if x["total_s"] > 0), default=None)
|
|
@@ -116,6 +120,14 @@ LETTERS = (("P", "parsimony (tokens)", "▼ shorter is better", "tokens", "token
|
|
|
116
120
|
("A", "accuracy", "▲ longer is better", "A", "A", "green"))
|
|
117
121
|
|
|
118
122
|
|
|
123
|
+
def repeats(x):
|
|
124
|
+
"""How many times a model answered each question compared: "×3", "×1-3", or "" when unknown."""
|
|
125
|
+
r = x.get("repeats")
|
|
126
|
+
if not r:
|
|
127
|
+
return ""
|
|
128
|
+
return f"×{r[0]}" if r[0] == r[1] else f"×{r[0]}-{r[1]}"
|
|
129
|
+
|
|
130
|
+
|
|
119
131
|
def times(v):
|
|
120
132
|
"""A multiple of the best: "1.0×", "2.4×", "28×"."""
|
|
121
133
|
return "-" if v is None else f"{v:.1f}×" if v < 9.95 else f"{v:.0f}×"
|
|
@@ -138,6 +150,8 @@ def bars(result, width=20, markup=True, click=None):
|
|
|
138
150
|
if not result["models"]:
|
|
139
151
|
return []
|
|
140
152
|
name_w = max(len(x["model"]) for x in result["models"])
|
|
153
|
+
rep_w = max(len(repeats(x)) for x in result["models"])
|
|
154
|
+
name_w += rep_w + 1 if rep_w else 0 # the repeats sit after the name, in its column
|
|
141
155
|
# each column: the bar, a space, "100%" or "9.8×" (6), then a gap; at least as wide as its heading
|
|
142
156
|
cell_w = max(width + 9, *(len(t) + 2 for k, name, better, *_ in LETTERS for t in (f"{k} {name}", better)))
|
|
143
157
|
pad = " " * (name_w + 2)
|
|
@@ -173,11 +187,14 @@ def bars(result, width=20, markup=True, click=None):
|
|
|
173
187
|
bar = (f"[{color}]{bar}[/]" + (f"[#ff8c00]{'█' * part}[/]" if part else "") + f"[grey37]{rest}[/]"
|
|
174
188
|
if markup else bar + cut_bar + rest)
|
|
175
189
|
cells.append(f"{bar} {num:>6}" + " " * max(0, cell_w - width - 1 - max(6, len(num))))
|
|
176
|
-
|
|
190
|
+
tag = repeats(x)
|
|
191
|
+
name = f"{x['model'] + (' ' + tag if tag else ''):<{name_w}}"
|
|
177
192
|
if markup:
|
|
178
|
-
pad_w = name_w - len(x["model"])
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
pad_w = name_w - len(x["model"]) - (len(tag) + 1 if tag else 0)
|
|
194
|
+
# a single answer per question in yellow: its accuracy is the roughest
|
|
195
|
+
tag = f" [{'yellow' if x['repeats'][1] == 1 else 'dim'}]{tag}[/]" if tag else ""
|
|
196
|
+
name = ((f"[@click={click}({row})]{rich_escape(x['model'])}[/]" if click else rich_escape(x["model"]))
|
|
197
|
+
+ tag + " " * pad_w)
|
|
181
198
|
lines.append((f"{name} " + "".join(cells)).rstrip())
|
|
182
199
|
return lines
|
|
183
200
|
|
|
@@ -202,9 +219,9 @@ def table(result):
|
|
|
202
219
|
"""(header, rows) for a plain table of the index."""
|
|
203
220
|
n = result["questions"] or 1
|
|
204
221
|
over = any(x.get("over") for x in result["models"]) # a column only when some answer ran past max_tokens
|
|
205
|
-
header = ["model", "tokens/answer", "time/answer", "answers right", "score", "truncated", *(["over budget"] if over else []),
|
|
222
|
+
header = ["model", "repeats", "tokens/answer", "time/answer", "answers right", "score", "truncated", *(["over budget"] if over else []),
|
|
206
223
|
"tok/s", "TTFT s", "memory GB"]
|
|
207
|
-
rows = [[x["model"], f"{round(x['tokens'] / n):,}" + (" ★" if x.get("fewest") else ""),
|
|
224
|
+
rows = [[x["model"], repeats(x) or "-", f"{round(x['tokens'] / n):,}" + (" ★" if x.get("fewest") else ""),
|
|
208
225
|
fmt_secs(x["total_s"] / n) + (" ★" if x.get("fastest") else ""),
|
|
209
226
|
f"{x['passed']}/{x['answers']}", "-" if x.get("S") is None else compare.pct(x["S"] / 100),
|
|
210
227
|
f"{x.get('cut') or 0}/{x['answers']}",
|
|
@@ -243,6 +260,10 @@ def scope_note(result, models):
|
|
|
243
260
|
"P parsimony (tokens) and T time: bars as long as the tokens / time used (shorter is better), "
|
|
244
261
|
"and how many times the leanest / fastest model's (1.0×, ★ in the table). A accuracy: answers right "
|
|
245
262
|
"(longer is better); score: partial credit, e.g. 5 of 6 checks passed is 83% (shown when it differs)"
|
|
263
|
+
+ (f"; ×n beside a name: answers per question. ×1 models answered each question once, so one "
|
|
264
|
+
f"answer more or less right moves their accuracy by {100 / result['questions']:.0f} points; "
|
|
265
|
+
"repeats steady it" if len({x.get("repeats") for x in result["models"]}) > 1
|
|
266
|
+
or any(x.get("repeats") and x["repeats"][1] == 1 for x in result["models"]) else "")
|
|
246
267
|
+ ("; orange, (n% truncated): answers cut off at max_tokens. They count as wrong, but the model ran "
|
|
247
268
|
"out of tokens rather than answering wrong." if any(x.get("cut") for x in result["models"]) else ".")
|
|
248
269
|
+ (" Over budget: answers that used more tokens than the max_tokens sent, because the provider didn't "
|
|
@@ -937,7 +937,10 @@ class SetupScreen(Screen):
|
|
|
937
937
|
for n in e.packs:
|
|
938
938
|
st = e.result_status(label, n)
|
|
939
939
|
if st:
|
|
940
|
-
|
|
940
|
+
reps = e.result_repeats(label, n) if st in ("certified", "screened") else None
|
|
941
|
+
times = ("" if not reps else f"[dim]×{reps[0]}[/dim]" if reps[0] == reps[1]
|
|
942
|
+
else f"[dim]×{reps[0]}-{reps[1]}[/dim]")
|
|
943
|
+
packs.append(f"{mark.get(st, '[yellow]~[/yellow]')}{n}{times}")
|
|
941
944
|
if st.startswith("outdated"):
|
|
942
945
|
why.setdefault(st.removeprefix("outdated: "), []).append(n)
|
|
943
946
|
if why: # each reason once, not once per pack
|
|
@@ -951,7 +954,7 @@ class SetupScreen(Screen):
|
|
|
951
954
|
hidden = (f" [cyan]{' '.join(tags)}[/cyan]" if tags else "") + hidden
|
|
952
955
|
detail.update(f"[b]{label}[/b] [dim]{m['server']} · {m.get('model') or ''}[/dim]"
|
|
953
956
|
f"{serving_badge(e, m, not e.has_results())}{hidden}\n{verdicts}\n"
|
|
954
|
-
+ ("[dim]results (✓ certified ◐ screened ~ outdated … partial):[/dim] " + " ".join(packs)
|
|
957
|
+
+ ("[dim]results (✓ certified ◐ screened ~ outdated … partial; ×n answers per question):[/dim] " + " ".join(packs)
|
|
955
958
|
if packs else "[dim]no results yet[/dim]"))
|
|
956
959
|
|
|
957
960
|
@on(Checkbox.Changed, "#show-hidden")
|
|
@@ -502,6 +502,9 @@ class PickTests(unittest.TestCase):
|
|
|
502
502
|
self.assertIn("done earlier", out) # nothing missing: not re-asked
|
|
503
503
|
tuieval(self.ws, "run", "--tests", "apps:d", "--tier", "certify")
|
|
504
504
|
self.assertEqual(self.status(), "certified")
|
|
505
|
+
code = "from tuieval import engine; print(engine.Engine().result_repeats('m', 'apps'))"
|
|
506
|
+
self.assertEqual(subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, # ✓×1 on Setup
|
|
507
|
+
env=dict(os.environ, TUIEVAL_HOME=self.ws)).stdout.strip(), "(1, 1)")
|
|
505
508
|
|
|
506
509
|
self.server("wrong")
|
|
507
510
|
tuieval(self.ws, "run", "--tests", "apps:b", "--force")
|
|
@@ -1555,7 +1558,7 @@ class PTAIndex(unittest.TestCase):
|
|
|
1555
1558
|
scale, and accuracy, all over the questions every compared model answered."""
|
|
1556
1559
|
|
|
1557
1560
|
def test_index_compares_shared_questions(self):
|
|
1558
|
-
from tuieval import pta
|
|
1561
|
+
from tuieval import compare, pta
|
|
1559
1562
|
|
|
1560
1563
|
def row(model, test, ok, secs, repeat=0):
|
|
1561
1564
|
return {"model": model, "suite": "p", "test": f"p: {test}", "ok": ok, "latency": secs * 1000,
|
|
@@ -1574,9 +1577,9 @@ class PTAIndex(unittest.TestCase):
|
|
|
1574
1577
|
self.assertEqual((by["slow"]["P"], by["fast"]["P"]), (100, 70)) # 4x the tokens: two doublings
|
|
1575
1578
|
self.assertEqual((pta.score(2, 1), pta.score(1000, 1)), (85, 0))
|
|
1576
1579
|
header, table = pta.table(res)
|
|
1577
|
-
self.assertEqual(header[1:
|
|
1580
|
+
self.assertEqual(header[1:5], ["repeats", "tokens/answer", "time/answer", "answers right"])
|
|
1578
1581
|
cells = {r[0]: r for r in table}
|
|
1579
|
-
self.assertEqual((cells["slow"][
|
|
1582
|
+
self.assertEqual((cells["slow"][2], cells["fast"][2:4]), ("10 ★", ["40", "2.0s ★"])) # per answer
|
|
1580
1583
|
bars = pta.bars(res, 10, markup=False)
|
|
1581
1584
|
self.assertEqual(bars[0].split(), ["P", "parsimony", "(tokens)", "T", "time", "A", "accuracy"])
|
|
1582
1585
|
self.assertEqual(bars[1].split(), ["▼", "shorter", "is", "better"] * 2 + ["▲", "longer", "is", "better"])
|
|
@@ -1585,8 +1588,13 @@ class PTAIndex(unittest.TestCase):
|
|
|
1585
1588
|
(fast.index("██████████"), fast.index("███░░░░░░░"))) # its heading
|
|
1586
1589
|
# P and T bars as long as tokens and time (shorter is better): fast used 4x the tokens of slow but
|
|
1587
1590
|
# took 4 s against 13 s, so it has the longest token bar and the shortest time bar
|
|
1588
|
-
self.assertEqual(fast.split(), ["fast", "██████████", "4.0×", "███░░░░░░░", "1.0×", "█████░░░░░", "50.0%"])
|
|
1589
|
-
self.assertEqual(slow.split()[1:
|
|
1591
|
+
self.assertEqual(fast.split(), ["fast", "×1", "██████████", "4.0×", "███░░░░░░░", "1.0×", "█████░░░░░", "50.0%"])
|
|
1592
|
+
self.assertEqual(slow.split()[1:6], ["×1-2", "██░░░░░░░░", "1.0×", "██████████", "3.2×"]) # ×: answers per question
|
|
1593
|
+
self.assertEqual((cells["slow"][1], cells["fast"][1]), ("×1-2", "×1"))
|
|
1594
|
+
self.assertIn("×1 models answered each question once", pta.scope_note(res, ["slow", "fast"]))
|
|
1595
|
+
full = [dict(r, score=float(r["ok"]), error=False, output="") for r in rows]
|
|
1596
|
+
header, table = compare.scorecard(full)
|
|
1597
|
+
self.assertEqual({r[0]: r[header.index("repeats")] for r in table}, {"slow": "×1-2", "fast": "×1"})
|
|
1590
1598
|
self.assertEqual((pta.times(1), pta.times(9.84), pta.times(27.9)), ("1.0×", "9.8×", "28×"))
|
|
1591
1599
|
self.assertEqual((by["fast"]["A"], by["slow"]["A"]), (50, 100))
|
|
1592
1600
|
self.assertEqual([x["model"] for x in res["models"]], ["slow", "fast"]) # best accuracy first
|
|
@@ -1618,8 +1626,8 @@ class PTAIndex(unittest.TestCase):
|
|
|
1618
1626
|
self.assertEqual(bars["plain"][-2:], ["██████░░░░", "60.0%"]) # none truncated: nothing beside it
|
|
1619
1627
|
self.assertIn("[#ff8c00]██[/]", pta.bars(res, 10)[2] + pta.bars(res, 10)[3])
|
|
1620
1628
|
header, table = pta.table(res)
|
|
1621
|
-
self.assertEqual(header[
|
|
1622
|
-
self.assertEqual({r[0]: r[
|
|
1629
|
+
self.assertEqual(header[5:7], ["score", "truncated"])
|
|
1630
|
+
self.assertEqual({r[0]: r[6] for r in table}, {"thinker": "2/10", "plain": "0/10"})
|
|
1623
1631
|
self.assertIn("truncated", pta.scope_note(res, ["thinker", "plain"]))
|
|
1624
1632
|
self.assertNotIn("truncated", pta.scope_note(pta.index(rows[10:]), ["plain"]))
|
|
1625
1633
|
|
|
@@ -1949,7 +1957,7 @@ class PTAIndex(unittest.TestCase):
|
|
|
1949
1957
|
self.assertGreaterEqual(len(got["opened"]), 2)
|
|
1950
1958
|
self.assertEqual(got["opened"], {m: m for m in got["opened"]}) # a cell opens its column's model
|
|
1951
1959
|
self.assertEqual(got["tab"], "tab-pta") # Results opens on the PTA index
|
|
1952
|
-
self.assertEqual([(r[0], r[
|
|
1960
|
+
self.assertEqual([(r[0], r[4]) for r in got["rows"]][0], ("good", "4/4"))
|
|
1953
1961
|
s = got["sorts"] # a click on a column name sorts by it, a second click reverses
|
|
1954
1962
|
self.assertEqual((s["up"], s["down"], s["kept"]), (["hosted", "good"], ["good", "hosted"], ["good", "hosted"]))
|
|
1955
1963
|
self.assertIn("answers right ▼", s["labels"])
|
|
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
|
{tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/pack.toml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev131 → tuieval-0.2.0.dev133}/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
|
|
File without changes
|