tuieval 0.2.0.dev141__tar.gz → 0.2.0.dev143__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.dev141 → tuieval-0.2.0.dev143}/CHANGELOG.md +1 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/models.md +1 -1
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/pta.py +72 -41
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/run_evals.py +5 -1
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/tui.py +15 -2
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/tests/test_tuieval.py +28 -2
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/.gitignore +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/LICENSE +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/README.md +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/agent-packs.md +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/agents.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/cli.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/compare.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/dates.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/engine.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/insights.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/yamlout.py +0 -0
- {tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/tests/mock_server.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- PTA index: models scoring below 85% are left out by default (listed with their score), unless every model is. The **Score ≥** dropdown (`tuieval pta --min-score N`) sets the minimum, Any score shows all; picked models are always shown.
|
|
5
6
|
- Per question → Comparison: a **Pass rate** column beside Passed, the share of answers right with repeats included (shown when some model has repeats).
|
|
6
7
|
- Repeats (Setup, `tuieval run --repeat N`) add N rounds to the saved answers: ×1 run with 2 gives ×3, not ×2. Retest, a blank count and presets still mean the total.
|
|
7
8
|
- Answers stuck in a loop (one short unit repeated over 2000 chars, in the reasoning or the answer) are stopped as they stream and failed as `LOOP`, instead of running to max_tokens. `[defaults] loop_stop_chars` sets the length; 0 turns it off.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev143
|
|
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
|
|
@@ -111,7 +111,7 @@ The **PTA index** puts parsimony (tokens), time and accuracy side by side, as a
|
|
|
111
111
|
|
|
112
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.
|
|
113
113
|
|
|
114
|
-
Models are compared on the questions all of them answered, so one that skipped some never looks faster. Server and connection errors aren't answers: a model with only those is listed as having none. A model with fewer than half as many answers as the most-answered one is left out (and listed), or it would shrink the shared questions for everyone. Pick the models and pack to compare in the TUI, or use `--only` and `--packs`: models you pick are always compared.
|
|
114
|
+
Models are compared on the questions all of them answered, so one that skipped some never looks faster. Server and connection errors aren't answers: a model with only those is listed as having none. A model with fewer than half as many answers as the most-answered one is left out (and listed), or it would shrink the shared questions for everyone. So is one scoring below 85% (listed with its score), unless every model does: the rest are then compared again, on the questions they all answered. The **Score ≥** dropdown in the tab (or `tuieval pta --min-score N`) sets that minimum; **Any score** (`0`) shows every model. Pick the models and pack to compare in the TUI, or use `--only` and `--packs`: models you pick are always compared.
|
|
115
115
|
|
|
116
116
|
## Insights
|
|
117
117
|
|
|
@@ -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.dev143'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev143')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -21,7 +21,8 @@ it varies). A ×1 model's accuracy rests on one answer per question, so it is ro
|
|
|
21
21
|
Models are compared on the questions all of them answered, so one that skipped some never looks
|
|
22
22
|
faster. Server and connection errors aren't answers. By default a model with fewer than half as many
|
|
23
23
|
answers as the most-answered model is left out (listed, so it can be picked), or it would shrink the
|
|
24
|
-
shared questions for everyone
|
|
24
|
+
shared questions for everyone, and so is one that scores below MIN_SCORE (also listed), unless every
|
|
25
|
+
model does. There's no combined number: the three stay side by side, and verdicts
|
|
25
26
|
and critical failures stay where they are (verdict.py).
|
|
26
27
|
"""
|
|
27
28
|
import math
|
|
@@ -31,6 +32,8 @@ from rich.markup import escape as rich_escape
|
|
|
31
32
|
from . import compare
|
|
32
33
|
|
|
33
34
|
MIN_SHARE = 0.5 # fewer answers than this share of the most-answered model's: left out by default
|
|
35
|
+
MIN_SCORE = 85 # a score (partial credit, %) below this: left out by default, unless every model's is
|
|
36
|
+
MIN_SCORES = (0, 50, 70, 80, 85, 90, 95) # the choices offered (0: every score)
|
|
34
37
|
POINTS_PER_DOUBLING = 15
|
|
35
38
|
|
|
36
39
|
|
|
@@ -55,13 +58,14 @@ def memory_gb(infos):
|
|
|
55
58
|
SORTS = ("accuracy", "score")
|
|
56
59
|
|
|
57
60
|
|
|
58
|
-
def index(rows, models=None, memory=None, sort="accuracy"):
|
|
61
|
+
def index(rows, models=None, memory=None, sort="accuracy", min_score=MIN_SCORE):
|
|
59
62
|
"""{"questions": n shared, "models": [{model, P, T, A, S, cut, cut_pct, tokens, total_s, passed, answers, alongside,
|
|
60
63
|
fewest, fastest, tps, ttft, memory_gb, repeats: (fewest, most) answers per question}],
|
|
61
|
-
"left_out": [(model, questions answered)], "
|
|
62
|
-
most-answered model}, best accuracy (sort="accuracy") or score (sort="score") first. S: the mean
|
|
64
|
+
"left_out": [(model, questions answered)], "low_score": [(model, score %) left out below MIN_SCORE],
|
|
65
|
+
"no_answers": [model], "most": questions answered by the most-answered model}, best accuracy (sort="accuracy") or score (sort="score") first. S: the mean
|
|
63
66
|
score (partial credit) in %. models: the ones to compare (default: every model in rows
|
|
64
|
-
with enough answers; models named here are compared
|
|
67
|
+
with enough answers and a score of at least min_score, 0 for any; models named here are compared
|
|
68
|
+
whatever their count and score). memory: memory_gb()."""
|
|
65
69
|
no_answers = sorted({r["model"] for r in rows} - {r["model"] for r in rows if not r.get("error")})
|
|
66
70
|
rows = [r for r in rows if not r.get("error")] # a server or connection error isn't an answer
|
|
67
71
|
tests = compare.per_question(rows)
|
|
@@ -70,47 +74,70 @@ score (partial credit) in %. models: the ones to compare (default: every model i
|
|
|
70
74
|
for m in t["cells"]:
|
|
71
75
|
counts[m] = counts.get(m, 0) + 1
|
|
72
76
|
most = max(counts.values(), default=0)
|
|
77
|
+
picked = models
|
|
73
78
|
if models:
|
|
74
79
|
no_answers = [m for m in no_answers if m in models]
|
|
75
80
|
models, left_out = sorted(m for m in models if m in counts), []
|
|
76
81
|
else:
|
|
77
82
|
models = sorted(m for m in counts if counts[m] >= MIN_SHARE * most)
|
|
78
83
|
left_out = sorted(((m, n) for m, n in counts.items() if m not in models), key=lambda x: (-x[1], x[0]))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
x["
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
84
|
+
|
|
85
|
+
def compare_on(models):
|
|
86
|
+
shared = [t for t in tests if models and all(m in t["cells"] for m in models)]
|
|
87
|
+
out = []
|
|
88
|
+
for m in models:
|
|
89
|
+
cells = [t["cells"][m] for t in shared]
|
|
90
|
+
answers = sum(c["n"] for c in cells)
|
|
91
|
+
out.append({"model": m, "tokens": sum(c["tokens"] or 0 for c in cells),
|
|
92
|
+
"total_s": sum(c["secs"] or 0 for c in cells),
|
|
93
|
+
"passed": sum(c["passed"] for c in cells), "answers": answers,
|
|
94
|
+
"A": 100 * sum(c["passed"] for c in cells) / answers if answers else None,
|
|
95
|
+
"S": 100 * sum(c["score"] * c["n"] for c in cells) / answers if answers else None,
|
|
96
|
+
# failed only because max_tokens ran out (overthinking, not wrong yet)
|
|
97
|
+
"cut": sum(c["truncated"] for c in cells),
|
|
98
|
+
"cut_pct": 100 * sum(c["truncated"] for c in cells) / answers if answers else None,
|
|
99
|
+
# ran past the max_tokens sent: the provider didn't stop it, so it had more room than asked
|
|
100
|
+
"over": sum(c.get("over", 0) for c in cells),
|
|
101
|
+
# generation speed and time to first token over the same answers (medians)
|
|
102
|
+
"tps": compare.median(r.get("gen_tps") for c in cells for r in c["rows"]),
|
|
103
|
+
"ttft": compare.median(r.get("ttft") for c in cells for r in c["rows"]),
|
|
104
|
+
"memory_gb": (memory or {}).get(m),
|
|
105
|
+
"repeats": (min(c["n"] for c in cells), max(c["n"] for c in cells)) if cells else None,
|
|
106
|
+
# answers that ran while other models were served shared the machine's speed
|
|
107
|
+
"alongside": any(r.get("alongside") for c in cells for r in c["rows"])})
|
|
108
|
+
fastest = min((x["total_s"] for x in out if x["total_s"] > 0), default=None)
|
|
109
|
+
fewest = min((x["tokens"] for x in out if x["tokens"] > 0), default=None)
|
|
110
|
+
for x in out:
|
|
111
|
+
x["P"], x["T"] = score(x["tokens"], fewest), score(x["total_s"], fastest)
|
|
112
|
+
# shown on the bars: how many times the leanest / fastest model's total (1.0 = the best)
|
|
113
|
+
x["tokens_x"] = x["tokens"] / fewest if fewest and x["tokens"] else None
|
|
114
|
+
x["time_x"] = x["total_s"] / fastest if fastest and x["total_s"] else None
|
|
115
|
+
x["fewest"] = bool(fewest) and x["tokens"] == fewest
|
|
116
|
+
x["fastest"] = bool(fastest) and x["total_s"] == fastest
|
|
117
|
+
return shared, out
|
|
118
|
+
|
|
119
|
+
shared, out = compare_on(models)
|
|
120
|
+
low_score = []
|
|
121
|
+
if not picked and min_score:
|
|
122
|
+
# by default, models scoring below min_score are left out too (unless every model is), and the rest
|
|
123
|
+
# compared again: without them the shared questions can grow, and scores move, so until none is below
|
|
124
|
+
while True:
|
|
125
|
+
low = [x for x in out if _score(x) < min_score]
|
|
126
|
+
if not low or len(low) == len(out):
|
|
127
|
+
break
|
|
128
|
+
low_score += [(x["model"], _score(x)) for x in low]
|
|
129
|
+
models = [m for m in models if m not in {x["model"] for x in low}]
|
|
130
|
+
shared, out = compare_on(models)
|
|
131
|
+
low_score.sort(key=lambda x: (-x[1], x[0]))
|
|
110
132
|
first, then = ("S", "A") if sort == "score" else ("A", "S")
|
|
111
133
|
out.sort(key=lambda x: (-(x[first] or 0), -(x[then] or 0), -(x["T"] or 0), x["model"]))
|
|
112
|
-
return {"questions": len(shared), "models": out, "left_out": left_out, "
|
|
113
|
-
"most": most}
|
|
134
|
+
return {"questions": len(shared), "models": out, "left_out": left_out, "low_score": low_score,
|
|
135
|
+
"min_score": min_score, "no_answers": no_answers, "most": most}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _score(x):
|
|
139
|
+
"""A model's score (partial credit, %), or its accuracy when there's none."""
|
|
140
|
+
return x["S"] if x.get("S") is not None else (x["A"] or 0)
|
|
114
141
|
|
|
115
142
|
|
|
116
143
|
# (letter, name, which way is better, what the bar measures, the number beside it, colour). P and T bars
|
|
@@ -200,13 +227,17 @@ def bars(result, width=20, markup=True, click=None):
|
|
|
200
227
|
|
|
201
228
|
|
|
202
229
|
def left_out_lines(result):
|
|
203
|
-
"""Plain sentences on the models not compared (too few answers, or only server errors) and on
|
|
230
|
+
"""Plain sentences on the models not compared (too few answers, a low score, or only server errors) and on
|
|
204
231
|
answers that shared the machine with other models."""
|
|
205
232
|
out = []
|
|
206
233
|
if result["left_out"]:
|
|
207
234
|
out.append("Left out (fewer than half as many answers as the most-answered model; pick them under "
|
|
208
235
|
"Models or --only to include them): "
|
|
209
236
|
+ ", ".join(f"{m} ({n} of {result['most']})" for m, n in result["left_out"]))
|
|
237
|
+
if result.get("low_score"):
|
|
238
|
+
out.append(f"Left out (scored below {result.get('min_score', MIN_SCORE):g}%; lower the minimum score, or pick "
|
|
239
|
+
"them under Models or --only, to include them): "
|
|
240
|
+
+ ", ".join(f"{m} ({compare.pct(v / 100)})" for m, v in result["low_score"]))
|
|
210
241
|
if result["no_answers"]:
|
|
211
242
|
out.append("No answers, only server errors: " + ", ".join(result["no_answers"]))
|
|
212
243
|
shared = [x["model"] for x in result["models"] if x["alongside"]]
|
|
@@ -271,14 +302,14 @@ def scope_note(result, models):
|
|
|
271
302
|
if any(x.get("over") for x in result["models"]) else ""))
|
|
272
303
|
|
|
273
304
|
|
|
274
|
-
def for_engine(e, labels=None, packs=None, results_dir=None, sort="accuracy"):
|
|
305
|
+
def for_engine(e, labels=None, packs=None, results_dir=None, sort="accuracy", min_score=MIN_SCORE):
|
|
275
306
|
"""(result, models) from a workspace's finished results (results on their way out excluded)."""
|
|
276
307
|
paths = [p for p in compare.default_paths(results_dir or e.results_dir) if not e.superseded(p)]
|
|
277
308
|
rows, infos = compare.load(paths) if paths else ([], [])
|
|
278
309
|
if packs:
|
|
279
310
|
rows = [r for r in rows if r["suite"] in packs]
|
|
280
311
|
models = sorted({r["model"] for r in rows} & set(labels) if labels else {r["model"] for r in rows})
|
|
281
|
-
return index(rows, models if labels else None, memory_gb(infos), sort), models
|
|
312
|
+
return index(rows, models if labels else None, memory_gb(infos), sort, min_score), models
|
|
282
313
|
|
|
283
314
|
|
|
284
315
|
def markdown(result, models):
|
|
@@ -405,6 +405,9 @@ def cmd_pta(argv):
|
|
|
405
405
|
p.add_argument("--width", type=int, default=20, help="bar width in characters (default 20)")
|
|
406
406
|
p.add_argument("--sort", choices=("accuracy", "score"), default="accuracy",
|
|
407
407
|
help="order models by answers right (default) or by score (partial credit)")
|
|
408
|
+
p.add_argument("--min-score", type=float, default=None,
|
|
409
|
+
help="leave out models scoring below this %% (default 85; 0 shows every model; "
|
|
410
|
+
"models named with --only are always shown)")
|
|
408
411
|
p.add_argument("--models")
|
|
409
412
|
p.add_argument("--results-dir")
|
|
410
413
|
a = p.parse_args(argv)
|
|
@@ -413,7 +416,8 @@ def cmd_pta(argv):
|
|
|
413
416
|
e = engine.Engine(models_path=a.models, results_dir=a.results_dir)
|
|
414
417
|
labels = [x.strip() for x in a.only.split(",")] if a.only else None
|
|
415
418
|
packs = [x.strip() for x in a.packs.split(",")] if a.packs else None
|
|
416
|
-
result, models = pta.for_engine(e, labels, packs, sort=a.sort
|
|
419
|
+
result, models = pta.for_engine(e, labels, packs, sort=a.sort,
|
|
420
|
+
min_score=pta.MIN_SCORE if a.min_score is None else a.min_score)
|
|
417
421
|
out = Console(highlight=False)
|
|
418
422
|
if not models:
|
|
419
423
|
sys.exit("no finished results to compare" + (" for those models or packs" if labels or packs else ""))
|
|
@@ -2934,6 +2934,7 @@ class ResultsScreen(Screen):
|
|
|
2934
2934
|
self.smoke_dir = smoke_dir
|
|
2935
2935
|
|
|
2936
2936
|
def compose(self) -> ComposeResult:
|
|
2937
|
+
from . import pta
|
|
2937
2938
|
yield Header()
|
|
2938
2939
|
with Horizontal(id="results-top"):
|
|
2939
2940
|
yield Static(id="results-info")
|
|
@@ -2945,6 +2946,9 @@ class ResultsScreen(Screen):
|
|
|
2945
2946
|
yield Select([("All packs", "")], id="pta-pack", allow_blank=False, value="")
|
|
2946
2947
|
yield Select([("By accuracy", "accuracy"), ("By score", "score")], id="pta-sort",
|
|
2947
2948
|
allow_blank=False, value="accuracy")
|
|
2949
|
+
yield Select([("Any score", 0)] + [(f"Score ≥ {n}%", n) for n in pta.MIN_SCORES if n],
|
|
2950
|
+
id="pta-min-score", allow_blank=False,
|
|
2951
|
+
value=getattr(self.app, "pta_min_score", pta.MIN_SCORE))
|
|
2948
2952
|
yield Button("Models: all", id="pta-models")
|
|
2949
2953
|
yield Static("[dim]P parsimony (tokens): tokens used, reasoning and answer. T time: total time. "
|
|
2950
2954
|
"A accuracy: answers right. Verdicts and critical failures are in Production "
|
|
@@ -3275,6 +3279,13 @@ class ResultsScreen(Screen):
|
|
|
3275
3279
|
out = insights.panels(rows, models, e.packs, ref, bases, by, engine=e if main else None)
|
|
3276
3280
|
body.update("\n\n".join(insights.render(p, i) for i, p in enumerate(out, 1)))
|
|
3277
3281
|
|
|
3282
|
+
@on(Select.Changed, "#pta-min-score")
|
|
3283
|
+
def pta_min_score_changed(self, event):
|
|
3284
|
+
"""The minimum score: models below it are left out of the PTA index unless picked (kept for the session)."""
|
|
3285
|
+
self.app.pta_min_score = event.value
|
|
3286
|
+
if getattr(self, "rows", None) is not None:
|
|
3287
|
+
self.fill_pta()
|
|
3288
|
+
|
|
3278
3289
|
@on(Select.Changed, "#pta-sort")
|
|
3279
3290
|
def pta_sort_changed(self):
|
|
3280
3291
|
"""The Sort dropdown orders the bars and the table, taking over from a column sorted by a click."""
|
|
@@ -3351,9 +3362,10 @@ class ResultsScreen(Screen):
|
|
|
3351
3362
|
self.query_one("#pta-bars", Static).update("")
|
|
3352
3363
|
self.query_one("#pta-left-out", Static).update("")
|
|
3353
3364
|
return
|
|
3354
|
-
# none picked: every model with enough answers (pta.index leaves out the rest, and says so)
|
|
3365
|
+
# none picked: every model with enough answers and score (pta.index leaves out the rest, and says so)
|
|
3355
3366
|
result = pta.index(rows, sorted(chosen) or None, pta.memory_gb(self.infos),
|
|
3356
|
-
self.query_one("#pta-sort", Select).value or "accuracy"
|
|
3367
|
+
self.query_one("#pta-sort", Select).value or "accuracy",
|
|
3368
|
+
self.query_one("#pta-min-score", Select).value or 0)
|
|
3357
3369
|
self.query_one("#pta-note", Static).update(pta.scope_note(result, sorted(present)))
|
|
3358
3370
|
self.pta_result = result
|
|
3359
3371
|
removed = [m for m in self.pta_removed_now() if m in present]
|
|
@@ -3700,6 +3712,7 @@ class EvalsApp(App):
|
|
|
3700
3712
|
#ins-body { height: 1fr; }
|
|
3701
3713
|
#pta-controls Select { width: 28; margin-right: 1; }
|
|
3702
3714
|
#pta-controls #pta-sort { width: 18; }
|
|
3715
|
+
#pta-controls #pta-min-score { width: 18; }
|
|
3703
3716
|
#pta-bars { height: auto; padding: 1 0 0 0; }
|
|
3704
3717
|
#pta { width: 1fr; height: auto; margin-top: 1; }
|
|
3705
3718
|
#pta-left-out { height: auto; padding: 1 0 0 0; }
|
|
@@ -1691,7 +1691,7 @@ class PTAIndex(unittest.TestCase):
|
|
|
1691
1691
|
rows = [row("fast", "q1", True, 2), row("fast", "q2", False, 2),
|
|
1692
1692
|
row("slow", "q1", True, 8), row("slow", "q2", True, 4), row("slow", "q2", True, 6, repeat=1),
|
|
1693
1693
|
row("slow", "q3", True, 100)] # only slow answered q3: not compared
|
|
1694
|
-
res = pta.index(rows)
|
|
1694
|
+
res = pta.index(rows, min_score=0) # fast scores 50%: kept
|
|
1695
1695
|
self.assertEqual(res["questions"], 2)
|
|
1696
1696
|
by = {x["model"]: x for x in res["models"]}
|
|
1697
1697
|
self.assertEqual(by["fast"]["total_s"], 4)
|
|
@@ -1777,6 +1777,30 @@ class PTAIndex(unittest.TestCase):
|
|
|
1777
1777
|
picked = pta.index(rows, ["a", "few"]) # picked by hand: compared anyway
|
|
1778
1778
|
self.assertEqual((picked["questions"], picked["left_out"]), (1, []))
|
|
1779
1779
|
|
|
1780
|
+
def test_low_scores_left_out_by_default(self):
|
|
1781
|
+
from tuieval import pta
|
|
1782
|
+
|
|
1783
|
+
def row(model, test, ok):
|
|
1784
|
+
return {"model": model, "suite": "p", "test": f"p: {test}", "ok": ok, "score": 1.0 if ok else 0.0,
|
|
1785
|
+
"latency": 1000, "tokens": 10, "truncated": False, "gen_tps": None, "repeat": 0}
|
|
1786
|
+
rows = ([row("good", f"q{i}", True) for i in range(10)]
|
|
1787
|
+
+ [row("ok", f"q{i}", i != 0) for i in range(10)] # 90%
|
|
1788
|
+
+ [row("weak", f"q{i}", i < 5) for i in range(8)]) # 62.5%, skipped q8 and q9
|
|
1789
|
+
res = pta.index(rows)
|
|
1790
|
+
self.assertEqual([x["model"] for x in res["models"]], ["good", "ok"])
|
|
1791
|
+
self.assertEqual(res["questions"], 10) # compared again without it: q8 and q9 count now
|
|
1792
|
+
self.assertEqual(res["low_score"], [("weak", 62.5)])
|
|
1793
|
+
self.assertIn("scored below 85%", "\n".join(pta.left_out_lines(res)))
|
|
1794
|
+
self.assertIn("weak (62.5%)", "\n".join(pta.left_out_lines(res)))
|
|
1795
|
+
picked = pta.index(rows, ["good", "weak"]) # picked by hand: shown whatever the score
|
|
1796
|
+
self.assertEqual(([x["model"] for x in picked["models"]], picked["low_score"]), (["good", "weak"], []))
|
|
1797
|
+
low = [r for r in rows if r["model"] != "good"]
|
|
1798
|
+
self.assertEqual(pta.index([r for r in low if r["model"] == "weak"])["low_score"], []) # all low: kept
|
|
1799
|
+
self.assertEqual([x["model"] for x in pta.index(low)["models"]], ["ok"])
|
|
1800
|
+
self.assertEqual([x["model"] for x in pta.index(rows, min_score=0)["models"]], ["good", "ok", "weak"])
|
|
1801
|
+
self.assertEqual([x["model"] for x in pta.index(rows, min_score=95)["models"]], ["good"])
|
|
1802
|
+
self.assertIn("scored below 95%", "\n".join(pta.left_out_lines(pta.index(rows, min_score=95))))
|
|
1803
|
+
|
|
1780
1804
|
def test_results_sort_values(self):
|
|
1781
1805
|
from tuieval.tui import diff_badge, sort_cell, sort_key, sort_value
|
|
1782
1806
|
got = [sort_value(c) for c in ("1,234 ★", "3.1k", "2.4×", "4m10s", "1h02m", "12.5s", diff_badge("hard"),
|
|
@@ -1964,7 +1988,8 @@ class PTAIndex(unittest.TestCase):
|
|
|
1964
1988
|
finally:
|
|
1965
1989
|
right.stop()
|
|
1966
1990
|
wrong.stop()
|
|
1967
|
-
|
|
1991
|
+
self.assertIn("scored below 85%", tuieval(ws, "pta").stdout) # hosted (0%) left out by default
|
|
1992
|
+
out = tuieval(ws, "pta", "--min-score", "0").stdout
|
|
1968
1993
|
self.assertIn("Compared on the 4 questions all 2 model(s) answered", out)
|
|
1969
1994
|
bars = {l.split()[0]: l for l in out.splitlines() if "█" in l}
|
|
1970
1995
|
self.assertEqual((bars["good"].count("1.0×"), "100%" in bars["good"]), (2, True))
|
|
@@ -1983,6 +2008,7 @@ class PTAIndex(unittest.TestCase):
|
|
|
1983
2008
|
app = EvalsApp({})
|
|
1984
2009
|
async with app.run_test(size=(180, 60)) as pilot:
|
|
1985
2010
|
await pilot.pause()
|
|
2011
|
+
app.pta_min_score = 0 # hosted scores 0%
|
|
1986
2012
|
app.push_screen(ResultsScreen())
|
|
1987
2013
|
await pilot.pause(0.5)
|
|
1988
2014
|
t = app.screen.query_one("#pta", DataTable)
|
|
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
|
|
File without changes
|
{tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/pack.toml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev141 → tuieval-0.2.0.dev143}/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
|