tuieval 0.2.0.dev72__tar.gz → 0.2.0.dev74__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.dev72 → tuieval-0.2.0.dev74}/CHANGELOG.md +1 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/engine.py +48 -10
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/run_evals.py +4 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/tui.py +174 -35
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/tests/test_tuieval.py +154 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/.gitignore +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/LICENSE +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/README.md +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/models.md +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/builtin.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/cli.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/client.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/compare.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/pta.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/yamlout.py +0 -0
- {tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/tests/mock_server.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Retest asks per pack before it starts: from zero, continue or leave out. It now really starts from zero, and replaced smoke results go to history/.
|
|
5
6
|
- Retest: the setup checkbox "Rerun existing results" is now "Retest (ask again, even if already answered)", and `tuieval run --retest` is the same (`--force` still works).
|
|
6
7
|
- `tuieval logs` follows the server log of the model running now and moves on with the run; `logs live` shows each answer as it finishes, `--list` every log.
|
|
7
8
|
- Results tables sort by a click on a column name; a second click reverses. Numbers, times and levels sort by value; empty cells stay last.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev74
|
|
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.dev74'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev74')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -427,6 +427,7 @@ class Job:
|
|
|
427
427
|
settings: dict = dataclasses.field(default_factory=dict) # sampling + serving identity (fingerprinted)
|
|
428
428
|
merge: bool = True # keep matching earlier results and only run what's missing
|
|
429
429
|
redo: bool = False # rerun the picked tests, keeping the pack's other answers
|
|
430
|
+
fresh: bool = False # retest: an unfinished run's matching answers are set aside too, not resumed
|
|
430
431
|
status: str = "waiting" # waiting, loading, running, done, failed, skipped
|
|
431
432
|
note: str = ""
|
|
432
433
|
discards: list = dataclasses.field(default_factory=list) # [(what, answers, why)] set aside on start
|
|
@@ -1059,6 +1060,8 @@ class Engine:
|
|
|
1059
1060
|
lines = [json.loads(l) for l in f if l.strip()]
|
|
1060
1061
|
if lines and self._header_ok(lines[0], job):
|
|
1061
1062
|
keys |= {(r["test"], r["repeat"]) for r in lines[1:] if not client.server_error_row(r)}
|
|
1063
|
+
if job.fresh: # a retest asks these again
|
|
1064
|
+
keys = {k for k in keys if k[0] not in self._redo_ids(job)}
|
|
1062
1065
|
return keys & wanted
|
|
1063
1066
|
|
|
1064
1067
|
def _stored(self, job):
|
|
@@ -1119,6 +1122,20 @@ class Engine:
|
|
|
1119
1122
|
out.append(("unfinished", len(rows), why))
|
|
1120
1123
|
return out
|
|
1121
1124
|
|
|
1125
|
+
def set_aside(self, job):
|
|
1126
|
+
"""(finished, unfinished): earlier answers a retest of this job asks again, so they move to
|
|
1127
|
+
history/ and stop counting: in its result file and its unfinished run, under the job's
|
|
1128
|
+
questions and settings (only the picked tests' when it redoes those)."""
|
|
1129
|
+
redo = self._redo_ids(job)
|
|
1130
|
+
data = self._result(job.out_path)
|
|
1131
|
+
finished = 0
|
|
1132
|
+
if data and data["pack"]["fingerprint"] == job.pack.fingerprint and settings_match(data, job.settings):
|
|
1133
|
+
finished = sum(r["test"] in redo for r in data["results"])
|
|
1134
|
+
head, rows = self._partial_lines(job.out_path + ".partial.jsonl")
|
|
1135
|
+
unfinished = sum(r["test"] in redo and not client.server_error_row(r) for r in rows) \
|
|
1136
|
+
if self._header_ok(head, job) else 0
|
|
1137
|
+
return finished, unfinished
|
|
1138
|
+
|
|
1122
1139
|
def superseded(self, path):
|
|
1123
1140
|
"""Is this result file about to be replaced? True when it no longer matches the current
|
|
1124
1141
|
questions or settings and an unfinished run of the same pack does."""
|
|
@@ -1141,7 +1158,8 @@ class Engine:
|
|
|
1141
1158
|
screen, the pack's certification repeats for certify). tests: {pack: [test ids]} runs just
|
|
1142
1159
|
those tests of a pack instead of the tier's sample (unknown ids raise PackError). Earlier
|
|
1143
1160
|
matching results are kept and only missing answers are run, unless force; force with
|
|
1144
|
-
picked tests reruns those and keeps the pack's other answers.
|
|
1161
|
+
picked tests reruns those and keeps the pack's other answers. force is True, False, or the
|
|
1162
|
+
keys ("label/pack") of the jobs to retest, the others resuming."""
|
|
1145
1163
|
if tier not in TIERS:
|
|
1146
1164
|
raise ValueError(f"tier must be one of {TIERS}")
|
|
1147
1165
|
jobs = []
|
|
@@ -1151,9 +1169,11 @@ class Engine:
|
|
|
1151
1169
|
pack = self.packs[name]
|
|
1152
1170
|
reps = repeat or self.default_repeat(pack, tier)
|
|
1153
1171
|
picked = (tests or {}).get(name)
|
|
1172
|
+
retest = force if isinstance(force, bool) else f"{label}/{name}" in force
|
|
1154
1173
|
job = Job(m, pack, reps, pack.pick(picked) if picked else pack.select(tier),
|
|
1155
1174
|
self.result_path(label, name, tier), effective_sampling(self.cfg, m), tier=tier,
|
|
1156
|
-
merge=not
|
|
1175
|
+
merge=not retest or bool(picked), redo=retest and bool(picked), fresh=retest,
|
|
1176
|
+
settings=self.settings(m))
|
|
1157
1177
|
sv = self.serving(m)
|
|
1158
1178
|
status = self.result_status(label, name, tier)
|
|
1159
1179
|
job.discards = self._discards(job)
|
|
@@ -1171,11 +1191,11 @@ class Engine:
|
|
|
1171
1191
|
job.status, job.note = "skipped", f"{', '.join(missing)} not installed in tuieval's Python"
|
|
1172
1192
|
elif status and status.startswith("outdated"):
|
|
1173
1193
|
job.merge, job.redo, job.note = False, False, status.split(": ", 1)[-1] + ", rerunning"
|
|
1174
|
-
if not
|
|
1194
|
+
if not retest: # a rerun under the new settings already under way resumes
|
|
1175
1195
|
self._fill_progress(job)
|
|
1176
1196
|
if job.done:
|
|
1177
1197
|
job.note += f" ({job.done} of {job.total} done)"
|
|
1178
|
-
elif not
|
|
1198
|
+
elif not retest:
|
|
1179
1199
|
self._fill_progress(job)
|
|
1180
1200
|
if job.done >= job.total:
|
|
1181
1201
|
job.status, job.note = "done", "done earlier"
|
|
@@ -2092,11 +2112,16 @@ class Engine:
|
|
|
2092
2112
|
lines = [json.loads(l) for l in f if l.strip()]
|
|
2093
2113
|
if lines and self._header_ok(lines[0], job):
|
|
2094
2114
|
new = [r for r in lines[1:] if not client.server_error_row(r)]
|
|
2115
|
+
if job.fresh: # a retest asks again what the unfinished run already answered
|
|
2116
|
+
stay = [r for r in new if r["test"] not in self._redo_ids(job)]
|
|
2117
|
+
if len(stay) < len(new):
|
|
2118
|
+
self._partial_to_history(job, keep=bool(stay))
|
|
2119
|
+
new = stay
|
|
2120
|
+
if stay:
|
|
2121
|
+
with open(partial, "w") as f:
|
|
2122
|
+
f.write("".join(json.dumps(l) + "\n" for l in [lines[0]] + stay))
|
|
2095
2123
|
else: # other questions or settings: kept in history/, not deleted
|
|
2096
|
-
|
|
2097
|
-
os.makedirs(hist, exist_ok=True)
|
|
2098
|
-
stamp = time.strftime("%Y%m%d-%H%M%S", time.localtime(os.path.getmtime(partial)))
|
|
2099
|
-
os.replace(partial, os.path.join(hist, f"{job.pack.name}-{stamp}.partial.jsonl"))
|
|
2124
|
+
self._partial_to_history(job)
|
|
2100
2125
|
if not os.path.isfile(partial):
|
|
2101
2126
|
with contextlib.suppress(OSError): # a fresh start: sittings of a discarded partial don't count
|
|
2102
2127
|
os.remove(self._sittings_path(job))
|
|
@@ -2224,10 +2249,23 @@ class Engine:
|
|
|
2224
2249
|
job.note = f"{job.passed}/{job.done} passed"
|
|
2225
2250
|
self.emit("job_done", job=job)
|
|
2226
2251
|
|
|
2252
|
+
def _partial_to_history(self, job, keep=False):
|
|
2253
|
+
"""Keep an unfinished run that is set aside in history/; keep: copy it (some of its answers stay)."""
|
|
2254
|
+
partial = job.out_path + ".partial.jsonl"
|
|
2255
|
+
hist = os.path.join(os.path.dirname(job.out_path), "history")
|
|
2256
|
+
os.makedirs(hist, exist_ok=True)
|
|
2257
|
+
stamp = time.strftime("%Y%m%d-%H%M%S", time.localtime(os.path.getmtime(partial)))
|
|
2258
|
+
(shutil.copy2 if keep else os.replace)(partial, os.path.join(hist, f"{job.pack.name}-{stamp}.partial.jsonl"))
|
|
2259
|
+
|
|
2260
|
+
@staticmethod
|
|
2261
|
+
def _redo_ids(job):
|
|
2262
|
+
"""The tests whose earlier answers a retest replaces: the picked ones, or all of them."""
|
|
2263
|
+
return {t["id"] for t in job.tests} if job.redo else {t["id"] for t in job.pack.tests}
|
|
2264
|
+
|
|
2227
2265
|
def _archive(self, job, keep=False):
|
|
2228
2266
|
"""Keep a result file that is about to be replaced (rerun or outdated) in history/; keep:
|
|
2229
|
-
copy it (some of its answers stay in the new file)."""
|
|
2230
|
-
if
|
|
2267
|
+
copy it (some of its answers stay in the new file). Smoke results too: nothing is deleted."""
|
|
2268
|
+
if not os.path.isfile(job.out_path):
|
|
2231
2269
|
return
|
|
2232
2270
|
hist = os.path.join(os.path.dirname(job.out_path), "history")
|
|
2233
2271
|
os.makedirs(hist, exist_ok=True)
|
|
@@ -823,6 +823,10 @@ def main(argv=None):
|
|
|
823
823
|
say(f"{j.key}: skipping ({j.note})")
|
|
824
824
|
elif j.status == "done":
|
|
825
825
|
say(f"{j.key}: done earlier ({j.passed}/{j.done} passed)")
|
|
826
|
+
elif j.fresh and any(e.set_aside(j)):
|
|
827
|
+
finished, unfinished = e.set_aside(j)
|
|
828
|
+
what = " and ".join(f"{n} {w}" for n, w in ((finished, "finished"), (unfinished, "unfinished")) if n)
|
|
829
|
+
say(f"{j.key}: retest from zero; its {what} answers move to history/")
|
|
826
830
|
|
|
827
831
|
def stop(*_):
|
|
828
832
|
say("stopping…", RED)
|
|
@@ -386,7 +386,9 @@ HELP = {
|
|
|
386
386
|
parallel_models in models.toml, default 1). Answers note the models they ran alongside.
|
|
387
387
|
|
|
388
388
|
[b]Retest[/b]: ask the ticked questions again even if they're already answered (only the tests
|
|
389
|
-
picked with e, if any). Without it, answered questions are skipped.
|
|
389
|
+
picked with e, if any). Without it, answered questions are skipped. Before anything moves, Start lists
|
|
390
|
+
the packs with earlier answers and lets you choose for each: Zero (ask everything again), Continue
|
|
391
|
+
(keep what's answered, ask the rest) or Leave out. Replaced answers go to history/, never deleted.
|
|
390
392
|
|
|
391
393
|
[b]Keys[/b]
|
|
392
394
|
s start (with a run going: add to it, or queue it) r results
|
|
@@ -961,7 +963,9 @@ class SetupScreen(Screen):
|
|
|
961
963
|
if not run:
|
|
962
964
|
text += "\n[yellow]Nothing selected can run (see skipping).[/yellow]"
|
|
963
965
|
elif rerun:
|
|
964
|
-
text += "\n[yellow]Everything selected has results; Start
|
|
966
|
+
text += "\n[yellow]Everything selected has results; Start asks which packs to ask again.[/yellow]"
|
|
967
|
+
if force:
|
|
968
|
+
text += "\n[yellow]Retest: Start lists the packs that would start from zero and lets you choose.[/yellow]"
|
|
965
969
|
resumed = [j for j in run if "done earlier" in j.note]
|
|
966
970
|
if resumed:
|
|
967
971
|
text += f" [yellow]{len(resumed)} pack(s) continue from earlier results[/yellow]"
|
|
@@ -1040,32 +1044,32 @@ class SetupScreen(Screen):
|
|
|
1040
1044
|
picks = self.current_picks(suites)
|
|
1041
1045
|
sel = {"models": labels, "packs": suites, "repeat": repeat, "tier": tier, "force": force, "tests": picks,
|
|
1042
1046
|
"parallel": self.parallel()}
|
|
1043
|
-
|
|
1044
|
-
|
|
1047
|
+
e = self.app.engine
|
|
1048
|
+
jobs = e.plan(labels, suites, repeat, tier, True, picks)
|
|
1045
1049
|
if not any(j.status == "waiting" for j in jobs):
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
jobs = self.app.engine.plan(labels, suites, repeat, tier, True, picks)
|
|
1049
|
-
if not any(j.status == "waiting" for j in jobs):
|
|
1050
|
-
reasons = sorted({j.note for j in jobs if j.status == "skipped"})
|
|
1051
|
-
self.notify(f"Nothing selected can run: {'; '.join(reasons)}", severity="warning")
|
|
1052
|
-
return
|
|
1053
|
-
rerun = True
|
|
1054
|
-
self.app.save_state(sel)
|
|
1055
|
-
lost = [(j, what, n, why) for j in jobs if j.status == "waiting" for what, n, why in j.discards]
|
|
1056
|
-
item = QueuedRun(sel, rerun, {(j.key, what) for j, what, _, _ in lost}, jobs, self.app.engine)
|
|
1057
|
-
if not lost:
|
|
1058
|
-
self.app.start_or_queue(item)
|
|
1050
|
+
reasons = sorted({j.note for j in jobs if j.status == "skipped"})
|
|
1051
|
+
self.notify(f"Nothing selected can run: {'; '.join(reasons)}", severity="warning")
|
|
1059
1052
|
return
|
|
1053
|
+
self.app.save_state(sel)
|
|
1054
|
+
# Packs whose earlier answers a start would set aside (Retest, everything already done, or
|
|
1055
|
+
# other questions or settings): the user picks per pack before anything moves.
|
|
1056
|
+
rows, rerun = retest_rows(e, labels, suites, repeat, tier, picks, force)
|
|
1060
1057
|
|
|
1061
|
-
def
|
|
1062
|
-
if
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1058
|
+
def go(choices):
|
|
1059
|
+
if choices is None:
|
|
1060
|
+
return
|
|
1061
|
+
item = QueuedRun(sel, {k for k, c in choices.items() if c == "zero"},
|
|
1062
|
+
{k for k, c in choices.items() if c == "leave"},
|
|
1063
|
+
{(r.key, what) for r in rows if choices.get(r.key) == "zero" for what, _, _ in r.base.discards},
|
|
1064
|
+
e)
|
|
1065
|
+
if not item.jobs:
|
|
1066
|
+
self.notify(f"Nothing to run: {item.problem}.", severity="warning")
|
|
1067
|
+
return
|
|
1068
|
+
self.app.start_or_queue(item)
|
|
1069
|
+
if rows:
|
|
1070
|
+
self.app.push_screen(RetestScreen(rows, force, rerun), go)
|
|
1071
|
+
else:
|
|
1072
|
+
go({})
|
|
1069
1073
|
|
|
1070
1074
|
def action_tune(self):
|
|
1071
1075
|
e = self.app.engine
|
|
@@ -1540,14 +1544,16 @@ def run_title(tier, models, packs):
|
|
|
1540
1544
|
|
|
1541
1545
|
class QueuedRun:
|
|
1542
1546
|
"""A run waiting its turn. It's planned again when it starts, so it picks up what the runs before
|
|
1543
|
-
it finished; it never sets aside answers that weren't confirmed when it was queued.
|
|
1547
|
+
it finished; it never sets aside answers that weren't confirmed when it was queued. retest: the
|
|
1548
|
+
job keys to ask again from zero; leave: the ones left out."""
|
|
1544
1549
|
kind = "Run"
|
|
1545
1550
|
|
|
1546
|
-
def __init__(self, sel,
|
|
1547
|
-
self.sel, self.
|
|
1551
|
+
def __init__(self, sel, retest, leave, accepted, eng):
|
|
1552
|
+
self.sel, self.retest, self.leave, self.accepted = sel, retest, leave, accepted
|
|
1548
1553
|
self.queued = time.time()
|
|
1554
|
+
self.jobs, self.problem = self.plan(eng)
|
|
1555
|
+
jobs = self.jobs or []
|
|
1549
1556
|
self.title = run_title(sel["tier"], {j.label for j in jobs}, {j.pack.label for j in jobs})
|
|
1550
|
-
self.jobs = jobs
|
|
1551
1557
|
self.est = eng.estimate_seconds(jobs)[0]
|
|
1552
1558
|
|
|
1553
1559
|
def tests(self, eng):
|
|
@@ -1566,11 +1572,11 @@ class QueuedRun:
|
|
|
1566
1572
|
def plan(self, eng):
|
|
1567
1573
|
"""(jobs, None), or (None, why it can't run) now."""
|
|
1568
1574
|
sel = self.sel
|
|
1569
|
-
jobs = eng.plan(sel["models"], sel["packs"], sel["repeat"], sel["tier"],
|
|
1570
|
-
|
|
1575
|
+
jobs = [j for j in eng.plan(sel["models"], sel["packs"], sel["repeat"], sel["tier"], self.retest,
|
|
1576
|
+
self.tests(eng)) if j.key not in self.leave]
|
|
1571
1577
|
waiting = [j for j in jobs if j.status == "waiting"]
|
|
1572
1578
|
if not waiting:
|
|
1573
|
-
return None, "
|
|
1579
|
+
return None, "every pack left is already done"
|
|
1574
1580
|
new = sorted({j.key for j in waiting for what, _, _ in j.discards if (j.key, what) not in self.accepted})
|
|
1575
1581
|
if new:
|
|
1576
1582
|
return None, (f"it would now start over {', '.join(new)}, which wasn't confirmed when it was queued. "
|
|
@@ -1621,6 +1627,140 @@ class ChoiceScreen(ModalScreen):
|
|
|
1621
1627
|
self.dismiss(None)
|
|
1622
1628
|
|
|
1623
1629
|
|
|
1630
|
+
class RetestRow:
|
|
1631
|
+
"""A pack whose earlier answers a start could set aside. base: its job resuming; zero: from zero."""
|
|
1632
|
+
|
|
1633
|
+
def __init__(self, base, zero, finished, unfinished, choices, choice):
|
|
1634
|
+
self.base, self.zero, self.finished, self.unfinished = base, zero, finished, unfinished
|
|
1635
|
+
self.choices, self.choice = choices, choice
|
|
1636
|
+
self.key = base.key
|
|
1637
|
+
|
|
1638
|
+
def earlier(self):
|
|
1639
|
+
if self.base.discards:
|
|
1640
|
+
return ", ".join(f"{n} {what}" for what, n, _ in self.base.discards)
|
|
1641
|
+
parts = [f"{self.finished} finished"] * bool(self.finished) + \
|
|
1642
|
+
[f"[yellow]{self.unfinished} unfinished[/yellow]"] * bool(self.unfinished)
|
|
1643
|
+
return ", ".join(parts)
|
|
1644
|
+
|
|
1645
|
+
def happens(self):
|
|
1646
|
+
b = self.base
|
|
1647
|
+
if self.choice == "leave":
|
|
1648
|
+
return "not run now"
|
|
1649
|
+
if self.choice == "continue":
|
|
1650
|
+
return "skipped: already done" if b.status == "done" else f"keeps {b.done}, asks the other {b.total - b.done}"
|
|
1651
|
+
if b.discards:
|
|
1652
|
+
why = "; ".join(sorted({why for _, _, why in b.discards}))
|
|
1653
|
+
return f"[yellow]{why}[/yellow]; {sum(n for _, n, _ in b.discards)} → history/"
|
|
1654
|
+
what = "picked tests asked again" if self.zero.redo else "asked again"
|
|
1655
|
+
return f"{what}; {self.finished + self.unfinished} → history/"
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
CHOICE_TEXT = {"zero": "[b red]Zero[/b red]", "continue": "[b green]Continue[/b green]", "leave": "[dim]Leave out[/dim]"}
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
def retest_rows(eng, labels, packs, repeat, tier, picks, force):
|
|
1662
|
+
"""(rows, rerun): the packs Start would set earlier answers aside for, or could. rerun: Retest is
|
|
1663
|
+
off but everything selected is done, so Start runs it again. A pack whose questions or settings
|
|
1664
|
+
changed can't continue (Zero or Leave out); with Retest every pack with answers starts at Zero,
|
|
1665
|
+
without it a pack that can resume continues."""
|
|
1666
|
+
base = eng.plan(labels, packs, repeat, tier, False, picks)
|
|
1667
|
+
zero = {j.key: j for j in eng.plan(labels, packs, repeat, tier, True, picks)}
|
|
1668
|
+
rerun = not force and not any(j.status == "waiting" for j in base)
|
|
1669
|
+
rows = []
|
|
1670
|
+
for b in base:
|
|
1671
|
+
z = zero[b.key]
|
|
1672
|
+
if z.status != "waiting":
|
|
1673
|
+
continue
|
|
1674
|
+
finished, unfinished = eng.set_aside(z)
|
|
1675
|
+
if b.discards:
|
|
1676
|
+
rows.append(RetestRow(b, z, finished, unfinished, ["zero", "leave"], "zero"))
|
|
1677
|
+
elif (force or rerun) and (finished or unfinished):
|
|
1678
|
+
rows.append(RetestRow(b, z, finished, unfinished, ["zero", "continue"],
|
|
1679
|
+
"zero" if force or b.status == "done" else "continue"))
|
|
1680
|
+
return rows, rerun
|
|
1681
|
+
|
|
1682
|
+
|
|
1683
|
+
class RetestScreen(ModalScreen):
|
|
1684
|
+
"""Start's confirm when earlier answers would be set aside: a choice per model · pack. Dismisses
|
|
1685
|
+
with {job key: "zero" | "continue" | "leave"}, or None on cancel."""
|
|
1686
|
+
BINDINGS = [Binding("space", "toggle", "Toggle"), Binding("a", "all('zero')", "All from zero"),
|
|
1687
|
+
Binding("c", "all('continue')", "All continue"), Binding("escape", "cancel", "Cancel")]
|
|
1688
|
+
|
|
1689
|
+
def __init__(self, rows, force, rerun):
|
|
1690
|
+
super().__init__()
|
|
1691
|
+
self.rows, self.force, self.rerun = rows, force, rerun
|
|
1692
|
+
|
|
1693
|
+
def compose(self) -> ComposeResult:
|
|
1694
|
+
if self.force:
|
|
1695
|
+
title = ("[b]Retest: choose what each pack does.[/b] Zero asks every question again; Continue keeps "
|
|
1696
|
+
"what's answered and asks the rest.")
|
|
1697
|
+
elif self.rerun:
|
|
1698
|
+
title = ("[b]Everything selected has results.[/b] Choose which packs to ask again (Zero) and which "
|
|
1699
|
+
"to keep (Continue).")
|
|
1700
|
+
else:
|
|
1701
|
+
title = ("[b]These packs can't continue:[/b] their earlier answers ran with other questions or "
|
|
1702
|
+
"settings. Start them from zero, or leave them out (put the setting back in models.toml "
|
|
1703
|
+
"to resume).")
|
|
1704
|
+
with Vertical(id="dialog", classes="wide"):
|
|
1705
|
+
yield Static(title)
|
|
1706
|
+
yield DataTable(id="choices", cursor_type="row", zebra_stripes=True)
|
|
1707
|
+
yield Static("[dim]space toggles the highlighted row · a all from zero · c all continue · "
|
|
1708
|
+
"Zero: earlier answers go to results/<model>/history/ (kept, not counted)[/dim]")
|
|
1709
|
+
with Horizontal(classes="dialog-buttons"):
|
|
1710
|
+
yield Button(self.start_label(), id="start", variant="primary")
|
|
1711
|
+
yield Button("Cancel", id="cancel")
|
|
1712
|
+
|
|
1713
|
+
def on_mount(self):
|
|
1714
|
+
t = self.query_one(DataTable)
|
|
1715
|
+
t.add_column("Choice", key="choice", width=10)
|
|
1716
|
+
t.add_column("Model", key="model")
|
|
1717
|
+
t.add_column("Pack", key="pack")
|
|
1718
|
+
t.add_column("Earlier answers", key="earlier")
|
|
1719
|
+
t.add_column("What happens", key="happens")
|
|
1720
|
+
for r in self.rows:
|
|
1721
|
+
t.add_row(CHOICE_TEXT[r.choice], r.base.label, r.base.pack.label, r.earlier(), r.happens(), key=r.key)
|
|
1722
|
+
self.update_start()
|
|
1723
|
+
t.focus()
|
|
1724
|
+
|
|
1725
|
+
def redraw(self, r):
|
|
1726
|
+
t = self.query_one(DataTable)
|
|
1727
|
+
t.update_cell(r.key, "choice", CHOICE_TEXT[r.choice])
|
|
1728
|
+
t.update_cell(r.key, "happens", r.happens(), update_width=True)
|
|
1729
|
+
self.update_start()
|
|
1730
|
+
|
|
1731
|
+
def start_label(self):
|
|
1732
|
+
n = collections.Counter(r.choice for r in self.rows)
|
|
1733
|
+
parts = [f"{n[c]} {w}" for c, w in (("zero", "from zero"), ("continue", "continue"), ("leave", "left out"))
|
|
1734
|
+
if n[c]]
|
|
1735
|
+
return f"Start ({', '.join(parts)})"
|
|
1736
|
+
|
|
1737
|
+
def update_start(self):
|
|
1738
|
+
button = self.query_one("#start", Button)
|
|
1739
|
+
button.label = self.start_label()
|
|
1740
|
+
button.refresh(layout=True)
|
|
1741
|
+
|
|
1742
|
+
def action_toggle(self):
|
|
1743
|
+
t = self.query_one(DataTable)
|
|
1744
|
+
if not self.rows:
|
|
1745
|
+
return
|
|
1746
|
+
r = self.rows[t.cursor_row]
|
|
1747
|
+
r.choice = r.choices[(r.choices.index(r.choice) + 1) % len(r.choices)]
|
|
1748
|
+
self.redraw(r)
|
|
1749
|
+
|
|
1750
|
+
def action_all(self, choice):
|
|
1751
|
+
for r in self.rows:
|
|
1752
|
+
if choice in r.choices:
|
|
1753
|
+
r.choice = choice
|
|
1754
|
+
self.redraw(r)
|
|
1755
|
+
|
|
1756
|
+
def action_cancel(self):
|
|
1757
|
+
self.dismiss(None)
|
|
1758
|
+
|
|
1759
|
+
@on(Button.Pressed)
|
|
1760
|
+
def answer(self, event):
|
|
1761
|
+
self.dismiss({r.key: r.choice for r in self.rows} if event.button.id == "start" else None)
|
|
1762
|
+
|
|
1763
|
+
|
|
1624
1764
|
class ConfirmScreen(ModalScreen):
|
|
1625
1765
|
def __init__(self, question):
|
|
1626
1766
|
super().__init__()
|
|
@@ -2955,7 +3095,8 @@ class EvalsApp(App):
|
|
|
2955
3095
|
#reasoning { color: $text-muted; }
|
|
2956
3096
|
StreamView { height: 1fr; border: none; padding: 0; }
|
|
2957
3097
|
#tabs { height: 12; }
|
|
2958
|
-
AddModelScreen, ConfirmScreen { align: center middle; }
|
|
3098
|
+
AddModelScreen, ConfirmScreen, RetestScreen { align: center middle; }
|
|
3099
|
+
RetestScreen #start { width: auto; }
|
|
2959
3100
|
SelectionList > .selection-list--button, SelectionList > .selection-list--button-highlighted {
|
|
2960
3101
|
color: $panel; background: $panel;
|
|
2961
3102
|
}
|
|
@@ -3094,8 +3235,6 @@ class EvalsApp(App):
|
|
|
3094
3235
|
if problem:
|
|
3095
3236
|
self.notify(f"Can't start {item.title}: {problem}", severity="warning", timeout=15)
|
|
3096
3237
|
return
|
|
3097
|
-
if item.kind == "Run" and item.rerun:
|
|
3098
|
-
self.notify("Everything selected had results; running it again.")
|
|
3099
3238
|
self.add_session(screen)
|
|
3100
3239
|
return
|
|
3101
3240
|
run = self.active_session
|
|
@@ -411,6 +411,160 @@ class PickTests(unittest.TestCase):
|
|
|
411
411
|
self.assertEqual(len(self.rows("other")), 1) # no pick: the Screen sample (screen = 1)
|
|
412
412
|
|
|
413
413
|
|
|
414
|
+
class Retest(unittest.TestCase):
|
|
415
|
+
"""Retest starts from zero (an unfinished run's answers too), keeps every replaced answer in
|
|
416
|
+
history/ (smoke too), and the TUI asks per pack before anything moves."""
|
|
417
|
+
|
|
418
|
+
def setUp(self):
|
|
419
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
420
|
+
self.ws = os.path.join(self.tmp.name, "ws")
|
|
421
|
+
tuieval(self.tmp.name, "init", self.ws)
|
|
422
|
+
for name in ("apps", "other"):
|
|
423
|
+
d = os.path.join(self.ws, "packs", name)
|
|
424
|
+
os.makedirs(d)
|
|
425
|
+
write(os.path.join(d, "pack.toml"), f'label = "{name}"\nscreen = 4\n[certify]\nrepeat = 1\n'
|
|
426
|
+
'[gate]\nmin_accuracy = 0.1\n')
|
|
427
|
+
write(os.path.join(d, "tests.yaml"), "".join(
|
|
428
|
+
f"- {{id: {t}, input: {name} question {t}, expected: 1, reference: 'ANSWER: 1', wrong: ['ANSWER: 2'], "
|
|
429
|
+
"difficulty: easy}\n" for t in "abcd"))
|
|
430
|
+
self.port = free_port()
|
|
431
|
+
with open(os.path.join(self.ws, "models.toml"), "a") as f:
|
|
432
|
+
f.write(f'\n[servers.m]\nurl = "http://127.0.0.1:{self.port}"\n')
|
|
433
|
+
self.mock = Mock(os.path.join(self.ws, "packs"), "oracle", port=self.port)
|
|
434
|
+
tuieval(self.ws, "add", "mock", "--server", "m", "--label", "m")
|
|
435
|
+
tuieval(self.ws, "run", "--tier", "certify")
|
|
436
|
+
self.mock.stop()
|
|
437
|
+
self.mock = Mock(os.path.join(self.ws, "packs"), "wrong", port=self.port) # retested answers fail
|
|
438
|
+
|
|
439
|
+
def tearDown(self):
|
|
440
|
+
self.mock.stop()
|
|
441
|
+
self.tmp.cleanup()
|
|
442
|
+
|
|
443
|
+
def path(self, pack, smoke=False):
|
|
444
|
+
return os.path.join(self.ws, "results", *(["smoke"] if smoke else []), "m", f"{pack}.json")
|
|
445
|
+
|
|
446
|
+
def passes(self, pack):
|
|
447
|
+
return {r["test"]: r["pass"] for r in json.loads(read(self.path(pack)))["results"]}
|
|
448
|
+
|
|
449
|
+
def unfinished(self, pack, tests):
|
|
450
|
+
"""Turn a pack's finished answers for these tests into an unfinished run of the same settings."""
|
|
451
|
+
from tuieval import engine
|
|
452
|
+
e = engine.Engine(root=self.ws)
|
|
453
|
+
j = e.plan(["m"], [pack], None, "certify", False, {})[0]
|
|
454
|
+
rows = [r for r in json.loads(read(j.out_path))["results"] if r["test"] in tests]
|
|
455
|
+
write(j.out_path + ".partial.jsonl", "".join(json.dumps(l) + "\n" for l in [e._partial_header(j)] + rows))
|
|
456
|
+
os.remove(j.out_path)
|
|
457
|
+
|
|
458
|
+
def history(self, smoke=False):
|
|
459
|
+
d = os.path.join(self.ws, "results", *(["smoke"] if smoke else []), "m", "history")
|
|
460
|
+
return sorted(os.listdir(d)) if os.path.isdir(d) else []
|
|
461
|
+
|
|
462
|
+
def test_cli_retest_starts_from_zero(self):
|
|
463
|
+
self.unfinished("apps", "ab")
|
|
464
|
+
out = tuieval(self.ws, "run", "--tier", "certify", "--packs", "apps", "--retest").stdout
|
|
465
|
+
self.assertIn("m/apps: retest from zero; its 2 unfinished answers move to history/", out)
|
|
466
|
+
self.assertNotIn("resuming", out)
|
|
467
|
+
self.assertEqual(self.passes("apps"), dict.fromkeys("abcd", False)) # all asked again
|
|
468
|
+
self.assertTrue(any(f.endswith(".partial.jsonl") for f in self.history()))
|
|
469
|
+
|
|
470
|
+
def test_picked_retest_keeps_the_rest_of_an_unfinished_run(self):
|
|
471
|
+
self.unfinished("apps", "abc")
|
|
472
|
+
tuieval(self.ws, "run", "--tier", "certify", "--tests", "apps:b", "--retest")
|
|
473
|
+
partial = self.path("apps") + ".partial.jsonl"
|
|
474
|
+
self.assertFalse(os.path.exists(partial))
|
|
475
|
+
self.assertEqual(self.passes("apps"), {"a": True, "b": False, "c": True}) # only b asked again
|
|
476
|
+
self.assertTrue(any(f.endswith(".partial.jsonl") for f in self.history())) # a copy, b included
|
|
477
|
+
|
|
478
|
+
def test_smoke_retest_keeps_history(self):
|
|
479
|
+
tuieval(self.ws, "run", "--tier", "smoke", "--packs", "apps")
|
|
480
|
+
tuieval(self.ws, "run", "--tier", "smoke", "--packs", "apps", "--retest")
|
|
481
|
+
self.assertEqual(len(self.history(smoke=True)), 1)
|
|
482
|
+
self.assertEqual(tuieval(self.ws, "list").returncode, 0)
|
|
483
|
+
|
|
484
|
+
def tui(self, steps):
|
|
485
|
+
"""Drive Setup with apps and other ticked for model m (Certify); steps runs against the screen."""
|
|
486
|
+
code = textwrap.dedent("""
|
|
487
|
+
import asyncio, json, time
|
|
488
|
+
from tuieval.tui import EvalsApp, RetestScreen, RunScreen
|
|
489
|
+
from textual.widgets import SelectionList, Checkbox
|
|
490
|
+
async def go():
|
|
491
|
+
app = EvalsApp({})
|
|
492
|
+
async with app.run_test(size=(160, 50)) as pilot:
|
|
493
|
+
await pilot.pause()
|
|
494
|
+
s = app.screen
|
|
495
|
+
for p in ("apps", "other"):
|
|
496
|
+
s.query_one("#suites", SelectionList).select(p)
|
|
497
|
+
s.selected_models = {"m"}
|
|
498
|
+
s.refresh_models()
|
|
499
|
+
s.query_one("#tier-certify").value = True
|
|
500
|
+
out = {}
|
|
501
|
+
%s
|
|
502
|
+
deadline = time.time() + 120
|
|
503
|
+
while time.time() < deadline and (app.queue or (isinstance(app.active_session, RunScreen)
|
|
504
|
+
and app.active_session.running)):
|
|
505
|
+
await pilot.pause(0.2)
|
|
506
|
+
out["sessions"] = len(app.sessions)
|
|
507
|
+
print(json.dumps(out))
|
|
508
|
+
asyncio.run(go())
|
|
509
|
+
""") % textwrap.indent(textwrap.dedent(steps), " " * 8)
|
|
510
|
+
p = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, timeout=300,
|
|
511
|
+
env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""))
|
|
512
|
+
out = json.loads(p.stdout.strip().splitlines()[-1]) if p.stdout.strip() else p.stderr
|
|
513
|
+
self.assertIsInstance(out, dict, out)
|
|
514
|
+
return out
|
|
515
|
+
|
|
516
|
+
def test_tui_asks_per_pack(self):
|
|
517
|
+
self.unfinished("other", "ab")
|
|
518
|
+
out = self.tui("""
|
|
519
|
+
s.query_one("#force", Checkbox).value = True
|
|
520
|
+
await pilot.pause()
|
|
521
|
+
s.action_start()
|
|
522
|
+
await pilot.pause()
|
|
523
|
+
d = app.screen
|
|
524
|
+
out["asked"] = isinstance(d, RetestScreen)
|
|
525
|
+
out["rows"] = [(r.key, r.choice, r.finished, r.unfinished) for r in d.rows]
|
|
526
|
+
await pilot.press("down", "space") # other: Continue
|
|
527
|
+
out["button"] = str(d.query_one("#start").label)
|
|
528
|
+
await pilot.press("escape") # cancel: nothing runs
|
|
529
|
+
await pilot.pause()
|
|
530
|
+
out["after_cancel"] = len(app.sessions)
|
|
531
|
+
s.action_start()
|
|
532
|
+
await pilot.pause()
|
|
533
|
+
await pilot.press("down", "space")
|
|
534
|
+
await pilot.pause()
|
|
535
|
+
app.screen.query_one("#start").press()
|
|
536
|
+
await pilot.pause()
|
|
537
|
+
""")
|
|
538
|
+
self.assertTrue(out["asked"])
|
|
539
|
+
self.assertEqual(out["rows"], [["m/apps", "zero", 4, 0], ["m/other", "zero", 0, 2]])
|
|
540
|
+
self.assertEqual(out["button"], "Start (1 from zero, 1 continue)")
|
|
541
|
+
self.assertEqual(out["after_cancel"], 0)
|
|
542
|
+
self.assertEqual(out["sessions"], 1)
|
|
543
|
+
self.assertEqual(self.passes("apps"), dict.fromkeys("abcd", False)) # from zero
|
|
544
|
+
self.assertEqual(self.passes("other"), {"a": True, "b": True, "c": False, "d": False}) # continued
|
|
545
|
+
|
|
546
|
+
def test_tui_everything_done_or_settings_changed(self):
|
|
547
|
+
models = os.path.join(self.ws, "models.toml")
|
|
548
|
+
write(models, read(models).replace("temperature = 0.7", "temperature = 0.6"))
|
|
549
|
+
out = self.tui("""
|
|
550
|
+
s.query_one("#suites", SelectionList).deselect("other")
|
|
551
|
+
await pilot.pause()
|
|
552
|
+
s.action_start()
|
|
553
|
+
await pilot.pause()
|
|
554
|
+
d = app.screen
|
|
555
|
+
out["rows"] = [(r.key, r.choices, r.choice) for r in d.rows]
|
|
556
|
+
await pilot.press("space") # Leave out
|
|
557
|
+
await pilot.pause()
|
|
558
|
+
out["happens"] = d.rows[0].happens()
|
|
559
|
+
d.query_one("#start").press()
|
|
560
|
+
await pilot.pause()
|
|
561
|
+
""")
|
|
562
|
+
self.assertEqual(out["rows"], [["m/apps", ["zero", "leave"], "zero"]])
|
|
563
|
+
self.assertEqual(out["happens"], "not run now")
|
|
564
|
+
self.assertEqual(out["sessions"], 0) # nothing ran
|
|
565
|
+
self.assertEqual(self.passes("apps"), dict.fromkeys("abcd", True))
|
|
566
|
+
|
|
567
|
+
|
|
414
568
|
class Parallel(unittest.TestCase):
|
|
415
569
|
"""Several models at a time (parallel_models / --parallel): each runs in its own lane on its own
|
|
416
570
|
port, answers note the models they ran alongside, and the scheduler keeps the memory and
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/system.txt
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/src/tuieval/templates/packs/tool_call/tests.yaml
RENAMED
|
File without changes
|
{tuieval-0.2.0.dev72 → tuieval-0.2.0.dev74}/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
|