tuieval 0.2.0.dev55__tar.gz → 0.2.0.dev56__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.dev55 → tuieval-0.2.0.dev56}/CHANGELOG.md +1 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/engine.py +17 -7
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/tui.py +35 -3
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/tests/test_tuieval.py +21 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/.gitignore +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/LICENSE +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/README.md +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/models.md +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/cli.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/compare.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/pta.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/run_evals.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/yamlout.py +0 -0
- {tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/tests/mock_server.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- The run's ETA adds up each pack's answers left × that model's time per answer there (its history, then its own times once it has 5 answers), instead of extrapolating from the first few answers.
|
|
5
6
|
- Faster start on Macs: the GPU core count comes from `ioreg` instead of `system_profiler`, which took seconds on some Macs.
|
|
6
7
|
- A model a server won't serve (HTTP 401-404, e.g. OpenRouter's "No endpoints found") is never judged on it: those answers don't count, and the model stops at once instead of failing every question.
|
|
7
8
|
- PTA index: parsimony (tokens used) and speed (time), each 100 for the best and 15 points less per doubling, and accuracy per model, as bars: the first Results tab (where `r` lands), `tuieval pta` and the report. Server errors aren't answers, and models with few answers are left out unless picked.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev56
|
|
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
|
|
@@ -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.dev56'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev56')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -987,16 +987,16 @@ class Engine:
|
|
|
987
987
|
del per_pack[name]
|
|
988
988
|
return {l: p for l, p in out.items() if p}
|
|
989
989
|
|
|
990
|
-
def
|
|
991
|
-
"""(
|
|
990
|
+
def seconds_per_answer(self, jobs, waiting_only=True):
|
|
991
|
+
"""({job key: expected seconds per answer}, borrowed, guessed, thin) for the waiting jobs.
|
|
992
992
|
A pack the model has answered uses its own median time there. Otherwise the median of
|
|
993
993
|
other models' times on that pack, scaled by how much slower or faster this model is than
|
|
994
|
-
them on packs both have answered.
|
|
994
|
+
them on packs both have answered. The sets say which estimates are rough (see estimate_seconds)."""
|
|
995
995
|
times = self.answer_times()
|
|
996
996
|
med = {l: {p: statistics.median(ts) for p, ts in pp.items()} for l, pp in times.items()}
|
|
997
|
-
|
|
997
|
+
spr_of, borrowed, guessed, thin = {}, set(), set(), set()
|
|
998
998
|
for j in jobs:
|
|
999
|
-
if j.status != "waiting":
|
|
999
|
+
if j.status in ("done", "skipped", "failed") or (waiting_only and j.status != "waiting"):
|
|
1000
1000
|
continue
|
|
1001
1001
|
own = med.get(j.label, {})
|
|
1002
1002
|
ref = {p: statistics.median(v[p] for l, v in med.items() if p in v and l != j.label)
|
|
@@ -1013,8 +1013,18 @@ class Engine:
|
|
|
1013
1013
|
else:
|
|
1014
1014
|
spr = statistics.median(own.values()) if own else DEFAULT_SECONDS_PER_REQUEST
|
|
1015
1015
|
guessed.add((j.key, bool(own)))
|
|
1016
|
-
|
|
1017
|
-
|
|
1016
|
+
spr_of[j.key] = spr
|
|
1017
|
+
return spr_of, borrowed, guessed, thin
|
|
1018
|
+
|
|
1019
|
+
def estimate_seconds(self, jobs):
|
|
1020
|
+
"""(seconds, notes): expected run time for the waiting jobs, estimated per pack
|
|
1021
|
+
(seconds_per_answer). notes lists what made the estimate rough ([] = solid)."""
|
|
1022
|
+
spr_of, borrowed, guessed, thin = self.seconds_per_answer(jobs)
|
|
1023
|
+
total = 0.0
|
|
1024
|
+
for j in jobs:
|
|
1025
|
+
if j.key in spr_of:
|
|
1026
|
+
done = len(self._done_keys(j)) if j.merge else 0
|
|
1027
|
+
total += max(0, j.total - done) * spr_of[j.key]
|
|
1018
1028
|
notes = []
|
|
1019
1029
|
if borrowed:
|
|
1020
1030
|
factors = sorted({f for _, f, _ in borrowed if f is not None})
|
|
@@ -1855,6 +1855,8 @@ class RunScreen(Screen):
|
|
|
1855
1855
|
self.current = ""
|
|
1856
1856
|
self.req_first = None
|
|
1857
1857
|
self.stats = []
|
|
1858
|
+
self.spr, self.rough = None, set() # job key -> expected seconds per answer (ETA), rough ones
|
|
1859
|
+
self.live = {} # job key -> seconds per answer in this session
|
|
1858
1860
|
self.mem = {}
|
|
1859
1861
|
self.outcome = "" # "Finished" or "Cancelled" once the queue is done
|
|
1860
1862
|
self.ended = None
|
|
@@ -2037,6 +2039,8 @@ class RunScreen(Screen):
|
|
|
2037
2039
|
+ (f" [red]copy of repeat {r['identical_to_repeat'] + 1}[/red]"
|
|
2038
2040
|
if r.get("identical_to_repeat") is not None else ""))
|
|
2039
2041
|
self.stats.append(r)
|
|
2042
|
+
if r.get("total_s"):
|
|
2043
|
+
self.live.setdefault(job.key, []).append(r["total_s"])
|
|
2040
2044
|
self.recent_entries[row_key] = {"model": job.label, "pack": job.pack.name, "record": r,
|
|
2041
2045
|
"pack_fp": job.pack.fingerprint}
|
|
2042
2046
|
if recent.row_count > 500:
|
|
@@ -2187,6 +2191,34 @@ class RunScreen(Screen):
|
|
|
2187
2191
|
("note", short_note(j.note))):
|
|
2188
2192
|
q.update_cell(j.key, col, value, update_width=True)
|
|
2189
2193
|
|
|
2194
|
+
LIVE_ANSWERS = 5 # a pack's own times this session take over from its history after this many
|
|
2195
|
+
|
|
2196
|
+
def eta_seconds(self):
|
|
2197
|
+
"""(seconds, rough): time left, pack by pack: answers left × seconds per answer. A pack's own
|
|
2198
|
+
average this session once it has LIVE_ANSWERS of them, before that its history (Engine.
|
|
2199
|
+
seconds_per_answer: this model's past times there, or other models' scaled). Divided by the
|
|
2200
|
+
models running side by side. rough: some packs have no history of their own yet."""
|
|
2201
|
+
new = [j for j in self.jobs if self.spr is None or (j.key not in self.spr and j.status not in
|
|
2202
|
+
("done", "skipped", "failed"))]
|
|
2203
|
+
if new: # at the start, and for evals added to the run
|
|
2204
|
+
spr, borrowed, guessed, _ = self.app.engine.seconds_per_answer(new, waiting_only=False)
|
|
2205
|
+
self.spr = {**(self.spr or {}), **spr}
|
|
2206
|
+
self.rough |= {k for k, *_ in borrowed} | {k for k, _ in guessed}
|
|
2207
|
+
left, rough, models = 0.0, False, set()
|
|
2208
|
+
for j in self.jobs:
|
|
2209
|
+
if j.status in ("done", "skipped", "failed") or j.done >= j.total:
|
|
2210
|
+
continue
|
|
2211
|
+
live = self.live.get(j.key, [])
|
|
2212
|
+
if len(live) >= self.LIVE_ANSWERS:
|
|
2213
|
+
spr = sum(live) / len(live)
|
|
2214
|
+
else:
|
|
2215
|
+
spr = self.spr.get(j.key, engine.DEFAULT_SECONDS_PER_REQUEST)
|
|
2216
|
+
rough |= j.key in self.rough
|
|
2217
|
+
left += (j.total - j.done) * spr
|
|
2218
|
+
models.add(j.label)
|
|
2219
|
+
lanes = max(1, min(self.parallel or self.app.engine.parallel_models(), len(models)))
|
|
2220
|
+
return left / lanes, rough
|
|
2221
|
+
|
|
2190
2222
|
def update_overall(self):
|
|
2191
2223
|
done = sum(j.done for j in self.jobs)
|
|
2192
2224
|
passed = sum(j.passed for j in self.jobs)
|
|
@@ -2194,9 +2226,9 @@ class RunScreen(Screen):
|
|
|
2194
2226
|
self.query_one("#bar", ProgressBar).update(progress=done)
|
|
2195
2227
|
elapsed = time.time() - self.started
|
|
2196
2228
|
eta = ""
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
eta = f" · ETA {
|
|
2229
|
+
if self.running and self.total > done:
|
|
2230
|
+
left, rough = self.eta_seconds()
|
|
2231
|
+
eta = f" · ETA {'~' if rough else ''}{fmt_secs(left)}"
|
|
2200
2232
|
pct = f"{100 * passed / (passed + failed):.0f}%" if passed + failed else "-"
|
|
2201
2233
|
recent = self.stats[-50:]
|
|
2202
2234
|
tps = [r["gen_tps"] for r in recent if r.get("gen_tps")]
|
|
@@ -12,6 +12,7 @@ import sys
|
|
|
12
12
|
import tempfile
|
|
13
13
|
import textwrap
|
|
14
14
|
import threading
|
|
15
|
+
import types
|
|
15
16
|
import time
|
|
16
17
|
import unittest
|
|
17
18
|
import urllib.request
|
|
@@ -199,6 +200,26 @@ class WorkspaceGraders(unittest.TestCase):
|
|
|
199
200
|
|
|
200
201
|
|
|
201
202
|
class Units(unittest.TestCase):
|
|
203
|
+
def test_eta_goes_pack_by_pack(self):
|
|
204
|
+
from tuieval.tui import RunScreen
|
|
205
|
+
job = lambda key, label, total, done=0, status="waiting": types.SimpleNamespace(
|
|
206
|
+
key=key, label=label, total=total, done=done, status=status)
|
|
207
|
+
run = RunScreen.__new__(RunScreen)
|
|
208
|
+
run.parallel, run.rough = 1, {"local/new"}
|
|
209
|
+
run.jobs = [job("hosted/a", "hosted", 100, 100, "done"), job("local/a", "local", 200, 4, "running"),
|
|
210
|
+
job("local/new", "local", 50)]
|
|
211
|
+
run.spr = {"local/a": 60.0, "local/new": 30.0} # from history
|
|
212
|
+
run.live = {"local/a": [900.0, 800.0, 700.0, 1000.0]} # its first answers are the slow ones
|
|
213
|
+
left, rough = run.eta_seconds()
|
|
214
|
+
self.assertEqual((left, rough), (196 * 60 + 50 * 30, True)) # history, not 4 slow answers: ~3.7h, not ~47h
|
|
215
|
+
run.live["local/a"].append(600.0) # 5 answers: its own average takes over
|
|
216
|
+
run.jobs[1].done = 5
|
|
217
|
+
self.assertEqual(run.eta_seconds()[0], 195 * 800 + 50 * 30)
|
|
218
|
+
run.parallel = 2
|
|
219
|
+
run.jobs.append(job("other/a", "other", 10))
|
|
220
|
+
run.spr["other/a"] = 10.0
|
|
221
|
+
self.assertEqual(run.eta_seconds()[0], (195 * 800 + 50 * 30 + 100) / 2) # two models at a time
|
|
222
|
+
|
|
202
223
|
def test_server_errors_never_judge_the_model(self):
|
|
203
224
|
from tuieval import client
|
|
204
225
|
no_endpoints = 'server returned HTTP 404: {"error":{"message":"No endpoints found for some/model."}}'
|
|
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.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev55 → tuieval-0.2.0.dev56}/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
|