tuieval 0.2.0.dev126__tar.gz → 0.2.0.dev129__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.dev126 → tuieval-0.2.0.dev129}/CHANGELOG.md +1 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/compare.py +11 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/tui.py +47 -9
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/tests/mock_server.py +6 -1
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/tests/test_tuieval.py +84 -2
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/.gitignore +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/LICENSE +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/README.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/agent-packs.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/models.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/agents.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/cli.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/dates.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/engine.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/insights.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/pta.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/run_evals.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/yamlout.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- 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.
|
|
5
6
|
- 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).
|
|
6
7
|
- Agent packs (`kind = "agent"`): each test is a small project with a task, worked on by a command-line agent you define in `[agents]`, using the model tuieval serves, and graded by the task's visible and hidden tests. Results show the agent's stats, diff and transcript; `tuieval compare` adds an Agent tasks table; `tuieval selftest` checks each task's reference and wrong fixes. See docs/agent-packs.md.
|
|
7
8
|
- Answers that used more tokens than the `max_tokens` sent (a provider that didn't stop them) are marked over budget: `(n over budget)` in the PTA index, an **over budget** column, `over` in Per question and a note in the answer view. `[defaults] over_budget = "fail"` fails them instead. Works on stored results.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev129
|
|
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.dev129'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev129')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -390,6 +390,17 @@ def with_models(test, models):
|
|
|
390
390
|
return dict(test, cells=cells, spread=_spread(cells))
|
|
391
391
|
|
|
392
392
|
|
|
393
|
+
def complete_models(rows, suite, test_ids=None):
|
|
394
|
+
"""The models whose rows answer every question of a pack: test_ids (the pack's current tests),
|
|
395
|
+
or every question of it seen in the rows. Partial rows (a pack still running) don't count."""
|
|
396
|
+
rows = [r for r in rows if r["suite"] == suite and not r.get("partial")]
|
|
397
|
+
wanted = {f"{suite}: {t}" for t in test_ids} if test_ids is not None else {r["test"] for r in rows}
|
|
398
|
+
answered = {}
|
|
399
|
+
for r in rows:
|
|
400
|
+
answered.setdefault(r["model"], set()).add(r["test"])
|
|
401
|
+
return sorted(m for m, tests in answered.items() if wanted and wanted <= tests)
|
|
402
|
+
|
|
403
|
+
|
|
393
404
|
def compare_models(tests, models):
|
|
394
405
|
"""Per model over per_question entries that all `models` answered: passes, token and time
|
|
395
406
|
totals/medians (totals add each question's median over repeats, so repeats don't inflate them), questions where it used the fewest tokens / was fastest (among passing
|
|
@@ -2963,6 +2963,9 @@ class ResultsScreen(Screen):
|
|
|
2963
2963
|
self.rows, self.infos, self.partial_rows, self.superseded = [], [], [], set()
|
|
2964
2964
|
if not hasattr(self.app, "pq_models"):
|
|
2965
2965
|
self.app.pq_models = set()
|
|
2966
|
+
if getattr(self.app, "pack_pick", None): # the pack lists open on All packs again
|
|
2967
|
+
self.app.pq_models = self.app.pack_pick["before"]
|
|
2968
|
+
self.app.pack_pick = None
|
|
2966
2969
|
if not hasattr(self.app, "result_sorts"):
|
|
2967
2970
|
self.app.result_sorts = {} # table id -> (column name, descending), kept for this session
|
|
2968
2971
|
self.fill()
|
|
@@ -3054,7 +3057,7 @@ class ResultsScreen(Screen):
|
|
|
3054
3057
|
for i, (model, test, reason, level) in enumerate(compare.failures(rows)):
|
|
3055
3058
|
when = dates.day(dates.answered_at(self.fail_rows[i].get("raw") or {}))
|
|
3056
3059
|
f.add_row(model, diff_badge(level), rich_escape(test), when, rich_escape(reason[:130]), key=str(i))
|
|
3057
|
-
self.query_one("#fail-models", Button).label =
|
|
3060
|
+
self.query_one("#fail-models", Button).label = self.models_label(chosen)
|
|
3058
3061
|
n = len(self.fail_rows)
|
|
3059
3062
|
self.query_one("#fail-count", Static).update(
|
|
3060
3063
|
f"{n} failure{'s' * (n != 1)}" + (f" of {len(chosen)} model{'s' * (len(chosen) != 1)}" if chosen else ""))
|
|
@@ -3072,7 +3075,6 @@ class ResultsScreen(Screen):
|
|
|
3072
3075
|
self.app.push_screen(AnswerScreen(keep, keep.index(entries[i]), what="failure"))
|
|
3073
3076
|
|
|
3074
3077
|
# -- per question: tokens and time for every model on the same question
|
|
3075
|
-
@on(Select.Changed, "#pq-pack")
|
|
3076
3078
|
@on(Select.Changed, "#pq-level")
|
|
3077
3079
|
@on(Checkbox.Changed, "#pq-partial")
|
|
3078
3080
|
@on(Checkbox.Changed, "#pq-shared")
|
|
@@ -3214,7 +3216,7 @@ class ResultsScreen(Screen):
|
|
|
3214
3216
|
finally:
|
|
3215
3217
|
self.filling_insights = False
|
|
3216
3218
|
chosen = {m for m in self.app.pq_models if m in every}
|
|
3217
|
-
self.query_one("#ins-models", Button).label =
|
|
3219
|
+
self.query_one("#ins-models", Button).label = self.models_label(chosen)
|
|
3218
3220
|
main = os.path.abspath(self.results_dir or e.results_dir) == os.path.abspath(e.results_dir)
|
|
3219
3221
|
bases = {m["label"]: m["base"] for m in e.cfg["models"] if m.get("base")}
|
|
3220
3222
|
by = self.query_one("#ins-cost", Select).value or "tokens"
|
|
@@ -3222,9 +3224,46 @@ class ResultsScreen(Screen):
|
|
|
3222
3224
|
body.update("\n\n".join(insights.render(p, i) for i, p in enumerate(out, 1)))
|
|
3223
3225
|
|
|
3224
3226
|
@on(Select.Changed, "#pta-pack")
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3227
|
+
@on(Select.Changed, "#pq-pack")
|
|
3228
|
+
def pack_changed(self, event):
|
|
3229
|
+
"""A pack picked (PTA index or Per question, kept the same in both): compare the models that
|
|
3230
|
+
answered every question of it (the Models button can change that); All packs: back to the
|
|
3231
|
+
models picked before."""
|
|
3232
|
+
if getattr(self, "rows", None) is None:
|
|
3233
|
+
return
|
|
3234
|
+
e, pack = self.app.engine, event.value or ""
|
|
3235
|
+
for other, values in (("#pta-pack", "pta_pack_values"), ("#pq-pack", "pq_pack_values")):
|
|
3236
|
+
sel = self.query_one(other, Select)
|
|
3237
|
+
if sel is not event.select and sel.value != pack and pack in getattr(self, values, [""]):
|
|
3238
|
+
with sel.prevent(Select.Changed):
|
|
3239
|
+
sel.value = pack
|
|
3240
|
+
shared = self.query_one("#pq-shared", Checkbox)
|
|
3241
|
+
p = self.app.pack_pick or {"before": set(self.app.pq_models), "shared": shared.value}
|
|
3242
|
+
before = p["before"]
|
|
3243
|
+
if not pack:
|
|
3244
|
+
self.app.pack_pick = None
|
|
3245
|
+
with shared.prevent(Checkbox.Changed): # set_models may have ticked it
|
|
3246
|
+
shared.value = p["shared"]
|
|
3247
|
+
self.set_models(before)
|
|
3248
|
+
return
|
|
3249
|
+
rows = [r for r in self.rows if r.get("path") not in self.superseded]
|
|
3250
|
+
complete = set(compare.complete_models(rows, pack, [t["id"] for t in e.packs[pack].tests]
|
|
3251
|
+
if pack in e.packs else None))
|
|
3252
|
+
self.app.pack_pick = {"pack": pack, "models": complete, "before": before, "shared": p["shared"]}
|
|
3253
|
+
if not complete:
|
|
3254
|
+
self.notify(f"No model has answered every question of {self.pack_label(pack)} yet: "
|
|
3255
|
+
"the models picked before are kept.", severity="warning", timeout=8)
|
|
3256
|
+
self.set_models(complete or before)
|
|
3257
|
+
|
|
3258
|
+
def pack_label(self, pack):
|
|
3259
|
+
return self.app.engine.packs[pack].label if pack in self.app.engine.packs else pack
|
|
3260
|
+
|
|
3261
|
+
def models_label(self, chosen):
|
|
3262
|
+
"""The Models buttons' text: the models a pack pick chose say so."""
|
|
3263
|
+
p = self.app.pack_pick
|
|
3264
|
+
if chosen and p and chosen == p["models"]:
|
|
3265
|
+
return f"Models: {len(chosen)} complete on {self.pack_label(p['pack'])}"
|
|
3266
|
+
return f"Models: {len(chosen)} picked" if chosen else "Models: all"
|
|
3228
3267
|
|
|
3229
3268
|
def fill_pta(self):
|
|
3230
3269
|
"""The PTA index (pta.py) of the picked models (Models button, shared with Per question) on
|
|
@@ -3245,7 +3284,7 @@ class ResultsScreen(Screen):
|
|
|
3245
3284
|
rows = [r for r in rows if r["suite"] == sel.value]
|
|
3246
3285
|
present = {r["model"] for r in rows}
|
|
3247
3286
|
chosen = {m for m in self.app.pq_models if m in present}
|
|
3248
|
-
self.query_one("#pta-models", Button).label =
|
|
3287
|
+
self.query_one("#pta-models", Button).label = self.models_label(chosen)
|
|
3249
3288
|
t = self.query_one("#pta", DataTable)
|
|
3250
3289
|
t.clear(columns=True)
|
|
3251
3290
|
if not present:
|
|
@@ -3300,8 +3339,7 @@ class ResultsScreen(Screen):
|
|
|
3300
3339
|
all_tests = compare.per_question(rows)
|
|
3301
3340
|
self.pq_all_tests = all_tests
|
|
3302
3341
|
chosen = {m for m in self.app.pq_models if any(m in t["cells"] for t in all_tests)}
|
|
3303
|
-
self.query_one("#pq-models", Button).label = (
|
|
3304
|
-
f"Models: {len(chosen)} picked" if chosen else "Models: all")
|
|
3342
|
+
self.query_one("#pq-models", Button).label = self.models_label(chosen)
|
|
3305
3343
|
tests = []
|
|
3306
3344
|
for t in all_tests:
|
|
3307
3345
|
if (pack and t["suite"] != pack) or (level and t["difficulty"] != level):
|
|
@@ -56,6 +56,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
56
56
|
answers, mode, model, delay, max_output = {}, "oracle", "mock", 0.0, None
|
|
57
57
|
stall, stall_before, silent = 0.0, 0.0, False
|
|
58
58
|
die_after = None
|
|
59
|
+
hold, answered = None, 0 # while the hold file exists, answers after the first wait (a run that stays going)
|
|
59
60
|
|
|
60
61
|
def log_message(self, *a):
|
|
61
62
|
pass
|
|
@@ -120,6 +121,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
120
121
|
self.wfile.flush()
|
|
121
122
|
time.sleep(0.2)
|
|
122
123
|
|
|
124
|
+
while self.hold and Handler.answered and os.path.exists(self.hold):
|
|
125
|
+
wait(0.2)
|
|
123
126
|
wait(self.stall_before)
|
|
124
127
|
send({"choices": [{"delta": {"reasoning_content": "Thinking it over. "}}]})
|
|
125
128
|
wait(self.stall)
|
|
@@ -136,6 +139,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
136
139
|
"prompt_per_second": 500.0, "cache_n": 0}})
|
|
137
140
|
die_if_due()
|
|
138
141
|
self.wfile.write(b"data: [DONE]\n\n")
|
|
142
|
+
Handler.answered += 1
|
|
139
143
|
|
|
140
144
|
|
|
141
145
|
class Server(ThreadingHTTPServer):
|
|
@@ -157,11 +161,12 @@ def main(argv=None):
|
|
|
157
161
|
p.add_argument("--stall-before", type=float, default=0.0)
|
|
158
162
|
p.add_argument("--silent", action="store_true")
|
|
159
163
|
p.add_argument("--die-after", type=int)
|
|
164
|
+
p.add_argument("--hold", help="while this file exists, every answer after the first waits")
|
|
160
165
|
a, _ = p.parse_known_args(argv) # other flags (e.g. speed knobs under test) are accepted and ignored
|
|
161
166
|
Handler.answers, Handler.mode, Handler.model, Handler.delay = load_answers(a.packs), a.mode, a.model, a.delay
|
|
162
167
|
Handler.max_output = a.max_output
|
|
163
168
|
Handler.stall, Handler.stall_before, Handler.silent = a.stall, a.stall_before, a.silent
|
|
164
|
-
Handler.die_after = a.die_after
|
|
169
|
+
Handler.die_after, Handler.hold = a.die_after, a.hold
|
|
165
170
|
srv = Server(("127.0.0.1", a.port), Handler)
|
|
166
171
|
print(f"mock server on 127.0.0.1:{a.port} ({a.mode}, {len(Handler.answers)} known questions)", flush=True)
|
|
167
172
|
try:
|
|
@@ -1412,8 +1412,12 @@ class AddToRun(unittest.TestCase):
|
|
|
1412
1412
|
|
|
1413
1413
|
def test_tui_never_queues_the_same_thing_twice(self):
|
|
1414
1414
|
"""Start while a run is going never adds or queues a model and pack already running or queued."""
|
|
1415
|
+
hold = os.path.join(self.tmp.name, "hold") # the first run stays going until every pick is made
|
|
1416
|
+
write(hold, "")
|
|
1417
|
+
path = os.path.join(self.ws, "models.toml")
|
|
1418
|
+
write(path, read(path).replace('"--delay", "0.2"', f'"--delay", "0.2", "--hold", {json.dumps(hold)}'))
|
|
1415
1419
|
code = textwrap.dedent("""
|
|
1416
|
-
import asyncio, json, time
|
|
1420
|
+
import asyncio, json, os, time
|
|
1417
1421
|
from tuieval.tui import EvalsApp, RunScreen, SetupScreen, ChoiceScreen
|
|
1418
1422
|
from textual.widgets import SelectionList
|
|
1419
1423
|
async def pick(app, pilot, models, packs=("apps",)):
|
|
@@ -1459,6 +1463,7 @@ class AddToRun(unittest.TestCase):
|
|
|
1459
1463
|
if isinstance(s, ChoiceScreen):
|
|
1460
1464
|
s.dismiss("queue")
|
|
1461
1465
|
await pilot.pause()
|
|
1466
|
+
os.remove(os.environ["HOLD"])
|
|
1462
1467
|
deadline = time.time() + 300 # the queued runs start one after another: wait for all three
|
|
1463
1468
|
while time.time() < deadline:
|
|
1464
1469
|
await pilot.pause(0.2)
|
|
@@ -1469,7 +1474,7 @@ class AddToRun(unittest.TestCase):
|
|
|
1469
1474
|
asyncio.run(go())
|
|
1470
1475
|
""")
|
|
1471
1476
|
p = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, timeout=600,
|
|
1472
|
-
env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""))
|
|
1477
|
+
env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS="", HOLD=hold))
|
|
1473
1478
|
out = json.loads(p.stdout.strip().splitlines()[-1]) if p.stdout.strip() else p.stderr
|
|
1474
1479
|
self.assertIsInstance(out, dict, out)
|
|
1475
1480
|
self.assertEqual(out["same"], [False, 0])
|
|
@@ -1651,6 +1656,83 @@ class PTAIndex(unittest.TestCase):
|
|
|
1651
1656
|
self.assertEqual(sorted(cells, key=sort_key), ["10/20", "3/4", "4/4", "abc", "-"])
|
|
1652
1657
|
self.assertEqual(sorted(cells, key=lambda c: sort_key(c, True)), ["4/4", "3/4", "10/20", "abc", "-"])
|
|
1653
1658
|
|
|
1659
|
+
def test_complete_models(self):
|
|
1660
|
+
from tuieval import compare
|
|
1661
|
+
rows = [{"model": m, "suite": s, "test": f"{s}: {t}", **({"partial": True} if m == "running" else {})}
|
|
1662
|
+
for m, s, ts in (("full", "p", "abc"), ("half", "p", "ab"), ("running", "p", "abc"), ("full", "o", "a"))
|
|
1663
|
+
for t in ts]
|
|
1664
|
+
self.assertEqual(compare.complete_models(rows, "p", ["a", "b", "c"]), ["full"]) # half misses c; running is ◐
|
|
1665
|
+
self.assertEqual(compare.complete_models(rows, "p", ["a", "b"]), ["full", "half"])
|
|
1666
|
+
self.assertEqual(compare.complete_models(rows, "p", ["a", "b", "c", "d"]), []) # a question added since
|
|
1667
|
+
self.assertEqual(compare.complete_models(rows, "p"), ["full"]) # pack gone: questions seen
|
|
1668
|
+
self.assertEqual(compare.complete_models(rows, "x", []), [])
|
|
1669
|
+
|
|
1670
|
+
def test_results_pack_picks_the_models_that_answered_all_of_it(self):
|
|
1671
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
1672
|
+
ws = os.path.join(tmp, "ws")
|
|
1673
|
+
tuieval(tmp, "init", ws)
|
|
1674
|
+
for name, ids in (("apps", "abcd"), ("other", "ab")):
|
|
1675
|
+
d = os.path.join(ws, "packs", name)
|
|
1676
|
+
os.makedirs(d)
|
|
1677
|
+
write(os.path.join(d, "pack.toml"), f'label = "{name.title()}"\n[certify]\nrepeat = 1\n')
|
|
1678
|
+
write(os.path.join(d, "tests.yaml"), "".join(
|
|
1679
|
+
f"- {{id: {t}, input: question {t}, expected: 1, reference: 'ANSWER: 1', wrong: ['ANSWER: 2'], "
|
|
1680
|
+
"difficulty: easy}\n" for t in ids))
|
|
1681
|
+
mock = Mock(os.path.join(ws, "packs"), "oracle")
|
|
1682
|
+
try:
|
|
1683
|
+
with open(os.path.join(ws, "models.toml"), "a") as f:
|
|
1684
|
+
f.write(f'\n[servers.here]\nurl = "http://127.0.0.1:{mock.port}"\n')
|
|
1685
|
+
for label in ("full", "half", "solo"):
|
|
1686
|
+
tuieval(ws, "add", "mock", "--server", "here", "--label", label)
|
|
1687
|
+
tuieval(ws, "run", "--tier", "certify", "--only", "full")
|
|
1688
|
+
tuieval(ws, "run", "--tier", "certify", "--only", "half", "--packs", "apps", "--tests", "a,b")
|
|
1689
|
+
tuieval(ws, "run", "--tier", "certify", "--only", "solo", "--packs", "other")
|
|
1690
|
+
finally:
|
|
1691
|
+
mock.stop()
|
|
1692
|
+
code = textwrap.dedent("""
|
|
1693
|
+
import asyncio
|
|
1694
|
+
from tuieval.tui import EvalsApp, ResultsScreen
|
|
1695
|
+
from textual.widgets import Button, DataTable, Select
|
|
1696
|
+
async def go():
|
|
1697
|
+
app = EvalsApp({})
|
|
1698
|
+
async with app.run_test(size=(180, 50)) as pilot:
|
|
1699
|
+
await pilot.pause()
|
|
1700
|
+
app.pq_models = {"half"} # picked by hand before
|
|
1701
|
+
app.push_screen(ResultsScreen())
|
|
1702
|
+
await pilot.pause(0.5)
|
|
1703
|
+
s = app.screen
|
|
1704
|
+
def state():
|
|
1705
|
+
pq = s.query_one("#per-question", DataTable)
|
|
1706
|
+
cols = [str(c.label) for c in pq.ordered_columns][len(s.PQ_FIXED):]
|
|
1707
|
+
return (sorted(app.pq_models), s.query_one("#pta-pack", Select).value,
|
|
1708
|
+
str(s.query_one("#pq-models", Button).label), cols, s.query_one("#pq-shared").value)
|
|
1709
|
+
s.query_one("#pq-pack", Select).value = "apps"
|
|
1710
|
+
await pilot.pause(0.3)
|
|
1711
|
+
print(state())
|
|
1712
|
+
s.query_one("#pta-pack", Select).value = "other"
|
|
1713
|
+
await pilot.pause(0.3)
|
|
1714
|
+
print(state())
|
|
1715
|
+
s.query_one("#pta-pack", Select).value = ""
|
|
1716
|
+
await pilot.pause(0.3)
|
|
1717
|
+
print(state())
|
|
1718
|
+
app.pq_models = set() # none picked: All packs gives every model again
|
|
1719
|
+
s.set_models(set())
|
|
1720
|
+
await pilot.pause(0.3)
|
|
1721
|
+
for pack in ("apps", ""):
|
|
1722
|
+
s.query_one("#pq-pack", Select).value = pack
|
|
1723
|
+
await pilot.pause(0.3)
|
|
1724
|
+
print(state())
|
|
1725
|
+
asyncio.run(go())
|
|
1726
|
+
""")
|
|
1727
|
+
out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
|
|
1728
|
+
env=dict(os.environ, TUIEVAL_HOME=ws, TUIEVAL_DETECT_PORTS=""), timeout=60)
|
|
1729
|
+
self.assertEqual(out.stdout.strip().splitlines(), [
|
|
1730
|
+
"(['full'], 'apps', 'Models: 1 complete on Apps', ['full'], False)",
|
|
1731
|
+
"(['full', 'solo'], 'other', 'Models: 2 complete on Other', ['full', 'solo'], False)",
|
|
1732
|
+
"(['half'], '', 'Models: 1 picked', ['half'], False)",
|
|
1733
|
+
"(['full'], 'apps', 'Models: 1 complete on Apps', ['full'], True)", # none picked before: ticked
|
|
1734
|
+
"([], '', 'Models: all', ['full', 'half', 'solo'], False)"], out.stderr[-2000:])
|
|
1735
|
+
|
|
1654
1736
|
def test_cli_report_and_tui(self):
|
|
1655
1737
|
with tempfile.TemporaryDirectory() as tmp:
|
|
1656
1738
|
ws = os.path.join(tmp, "ws")
|
|
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
|
|
File without changes
|
|
File without changes
|
{tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/pack.toml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev126 → tuieval-0.2.0.dev129}/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
|