tuieval 0.2.0.dev89__py3-none-any.whl → 0.2.0.dev92__py3-none-any.whl
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/_version.py +2 -2
- tuieval/builtin.toml +1 -0
- tuieval/client.py +24 -5
- tuieval/engine.py +3 -1
- tuieval/tui.py +33 -4
- {tuieval-0.2.0.dev89.dist-info → tuieval-0.2.0.dev92.dist-info}/METADATA +1 -1
- {tuieval-0.2.0.dev89.dist-info → tuieval-0.2.0.dev92.dist-info}/RECORD +10 -10
- {tuieval-0.2.0.dev89.dist-info → tuieval-0.2.0.dev92.dist-info}/WHEEL +0 -0
- {tuieval-0.2.0.dev89.dist-info → tuieval-0.2.0.dev92.dist-info}/entry_points.txt +0 -0
- {tuieval-0.2.0.dev89.dist-info → tuieval-0.2.0.dev92.dist-info}/licenses/LICENSE +0 -0
tuieval/_version.py
CHANGED
|
@@ -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.dev92'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev92')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
tuieval/builtin.toml
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[defaults]
|
|
6
6
|
repeat = 3
|
|
7
7
|
request_timeout_ms = 1800000 # per request; long thinking runs need it
|
|
8
|
+
stall_timeout_ms = 300000 # no tokens this long once answering began: the server stalled (retried, not counted)
|
|
8
9
|
ready_timeout_s = 1200 # how long a model may take to load before giving up
|
|
9
10
|
model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
|
|
10
11
|
|
tuieval/client.py
CHANGED
|
@@ -99,10 +99,17 @@ def count_tokens(base_url, text):
|
|
|
99
99
|
return None
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
def
|
|
102
|
+
def _stalled(stall_s):
|
|
103
|
+
return f"server error: stalled, no tokens for {stall_s:.0f} s after it started answering"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def stream_chat(base_url, body, on_delta=None, timeout=1800, stream=None, headers=None, stall_s=None):
|
|
103
107
|
"""POST {base_url}/v1/chat/completions with streaming. Returns a dict (see module docstring).
|
|
104
108
|
|
|
105
109
|
on_delta(kind, text) is called for "reasoning" and "answer" text as it arrives.
|
|
110
|
+
timeout bounds the whole request, keep-alive comments (OpenRouter's ": OPENROUTER PROCESSING")
|
|
111
|
+
included; running out counts against the model, like any slow answer. stall_s: once tokens have
|
|
112
|
+
started, this long without one is the server stalling (a server error, never the model's answer).
|
|
106
113
|
"""
|
|
107
114
|
stream = stream or Stream()
|
|
108
115
|
body = dict(body, stream=True, stream_options={"include_usage": True})
|
|
@@ -111,7 +118,7 @@ def stream_chat(base_url, body, on_delta=None, timeout=1800, stream=None, header
|
|
|
111
118
|
out = {"answer": "", "reasoning": "", "tool_calls": [], "finish": None, "usage": {}, "timings": {},
|
|
112
119
|
"ttft_s": None, "total_s": None, "error": None}
|
|
113
120
|
reasoning, answer, tools = [], [], {}
|
|
114
|
-
start = time.time()
|
|
121
|
+
start = last = time.time()
|
|
115
122
|
try:
|
|
116
123
|
conn.request("POST", prefix + "/v1/chat/completions", body=json.dumps(body),
|
|
117
124
|
headers={"Content-Type": "application/json", "Authorization": "Bearer none", **(headers or {})})
|
|
@@ -123,6 +130,13 @@ def stream_chat(base_url, body, on_delta=None, timeout=1800, stream=None, header
|
|
|
123
130
|
out["error"] = f"server returned HTTP {r.status}: {r.read()[:300].decode('utf-8', 'replace')}"
|
|
124
131
|
return out
|
|
125
132
|
for raw in r:
|
|
133
|
+
now = time.time()
|
|
134
|
+
if now - start > timeout:
|
|
135
|
+
out["error"] = f"connection error: timed out after {timeout:.0f} s in all"
|
|
136
|
+
break
|
|
137
|
+
if stall_s and out["ttft_s"] is not None and now - last > stall_s:
|
|
138
|
+
out["error"] = _stalled(stall_s)
|
|
139
|
+
break
|
|
126
140
|
line = raw.decode("utf-8", "replace").strip()
|
|
127
141
|
if not line.startswith("data:"):
|
|
128
142
|
continue
|
|
@@ -147,8 +161,12 @@ def stream_chat(base_url, body, on_delta=None, timeout=1800, stream=None, header
|
|
|
147
161
|
fn = tc.get("function") or {}
|
|
148
162
|
slot["name"] += fn.get("name") or ""
|
|
149
163
|
slot["arguments"] += fn.get("arguments") or ""
|
|
150
|
-
if
|
|
151
|
-
|
|
164
|
+
if think or text or delta.get("tool_calls"):
|
|
165
|
+
last = time.time()
|
|
166
|
+
if out["ttft_s"] is None:
|
|
167
|
+
out["ttft_s"] = last - start
|
|
168
|
+
if stall_s: # a server that goes silent mid-answer: don't wait out the whole timeout
|
|
169
|
+
stream.sock.settimeout(min(stall_s, timeout))
|
|
152
170
|
if think:
|
|
153
171
|
reasoning.append(think)
|
|
154
172
|
on_delta and on_delta("reasoning", think)
|
|
@@ -158,7 +176,8 @@ def stream_chat(base_url, body, on_delta=None, timeout=1800, stream=None, header
|
|
|
158
176
|
except (OSError, http.client.HTTPException, ValueError) as e:
|
|
159
177
|
if stream.cancelled:
|
|
160
178
|
raise Cancelled() from None
|
|
161
|
-
out["
|
|
179
|
+
stalled = stall_s and out["ttft_s"] is not None and isinstance(e, TimeoutError)
|
|
180
|
+
out["error"] = _stalled(stall_s) if stalled else f"connection error: {e!r}"
|
|
162
181
|
finally:
|
|
163
182
|
conn.close()
|
|
164
183
|
out["total_s"] = time.time() - start
|
tuieval/engine.py
CHANGED
|
@@ -2229,6 +2229,7 @@ class Engine:
|
|
|
2229
2229
|
self.emit("job_started", job=job, resumed=len(done_keys),
|
|
2230
2230
|
earlier_s=sum(s.get("wall_s") or 0 for s in job.earlier), sittings=len(job.earlier))
|
|
2231
2231
|
timeout = self.cfg["defaults"]["request_timeout_ms"] / 1000
|
|
2232
|
+
stall_s = (self.cfg["defaults"].get("stall_timeout_ms") or 0) / 1000 or None
|
|
2232
2233
|
headers = self.request_headers(job.model)
|
|
2233
2234
|
# a hosted API has no /tokenize to split reasoning from answer tokens (it reports the split)
|
|
2234
2235
|
tokenize_url = None if self.cfg["servers"][job.model["server"]].get("api_key_env") else base_url
|
|
@@ -2253,7 +2254,8 @@ class Engine:
|
|
|
2253
2254
|
body = self._body(job, test, rep)
|
|
2254
2255
|
res = client.stream_chat(base_url, body,
|
|
2255
2256
|
lambda kind, text: self.emit("delta", stream=kind, text=text, label=job.label),
|
|
2256
|
-
timeout=timeout, stream=self._stream, headers=headers
|
|
2257
|
+
timeout=timeout, stream=self._stream, headers=headers,
|
|
2258
|
+
stall_s=stall_s)
|
|
2257
2259
|
except client.Cancelled:
|
|
2258
2260
|
self._check()
|
|
2259
2261
|
raise Cancelled()
|
tuieval/tui.py
CHANGED
|
@@ -2104,6 +2104,7 @@ class RunScreen(Screen):
|
|
|
2104
2104
|
self.follow = True
|
|
2105
2105
|
self.buf = {"reasoning": [], "answer": []}
|
|
2106
2106
|
self.buf_lock = threading.Lock()
|
|
2107
|
+
self.req_live = {} # model label -> its request now: phase, ~tokens, started, last token
|
|
2107
2108
|
self.started = time.time()
|
|
2108
2109
|
self.req_started = None
|
|
2109
2110
|
self.req_tokens = 0
|
|
@@ -2235,7 +2236,20 @@ class RunScreen(Screen):
|
|
|
2235
2236
|
return # the app is shutting down; the UI thread may be waiting on the engine
|
|
2236
2237
|
if kind in ("request_started", "model_loading") and self.watch not in self.active():
|
|
2237
2238
|
self.watch = d["job"].label if "job" in d else d["label"] # follow the next model streaming
|
|
2239
|
+
if kind in ("request_started", "server_retry"): # here, not in handle(): deltas follow at once
|
|
2240
|
+
with self.buf_lock:
|
|
2241
|
+
self.req_live[d["job"].label if "job" in d else d["label"]] = {
|
|
2242
|
+
"phase": "waiting", "tokens": 0, "started": time.time(), "last": None}
|
|
2243
|
+
elif kind == "request_done":
|
|
2244
|
+
with self.buf_lock:
|
|
2245
|
+
self.req_live.pop(d["job"].label, None)
|
|
2238
2246
|
if kind == "delta": # very frequent: buffer, flushed every 100 ms on the UI thread
|
|
2247
|
+
with self.buf_lock:
|
|
2248
|
+
lv = self.req_live.get(d.get("label"))
|
|
2249
|
+
if lv:
|
|
2250
|
+
lv["phase"] = "thinking" if d["stream"] == "reasoning" else "answering"
|
|
2251
|
+
lv["tokens"] += max(1, len(d["text"]) // 4)
|
|
2252
|
+
lv["last"] = time.time()
|
|
2239
2253
|
if d.get("label", self.watch) != self.watch:
|
|
2240
2254
|
return # another model running alongside: v switches to it
|
|
2241
2255
|
with self.buf_lock:
|
|
@@ -2439,7 +2453,8 @@ class RunScreen(Screen):
|
|
|
2439
2453
|
elapsed = (j.finished or time.time()) - j.started if j.started else 0
|
|
2440
2454
|
elapsed += sum(s.get("wall_s") or 0 for s in j.earlier) # a resumed pack: its earlier sittings too
|
|
2441
2455
|
# update_width: columns start at their header's width; "12" under ✓ would otherwise show as "1"
|
|
2442
|
-
|
|
2456
|
+
status = f"[{style}]{j.status}[/{style}]" + (self.live_note(j.label) if j.status == "running" else "")
|
|
2457
|
+
for col, value in (("status", status),
|
|
2443
2458
|
("progress", f"{j.done}/{j.total}" if j.status != "skipped" or j.done else ""),
|
|
2444
2459
|
("pass", f"[green]{j.passed}[/green]" if j.passed else ""),
|
|
2445
2460
|
("fail", f"[red]{j.failed}[/red]" if j.failed else ""),
|
|
@@ -2447,6 +2462,21 @@ class RunScreen(Screen):
|
|
|
2447
2462
|
("note", short_note(j.note))):
|
|
2448
2463
|
q.update_cell(j.key, col, value, update_width=True)
|
|
2449
2464
|
|
|
2465
|
+
def live_note(self, label):
|
|
2466
|
+
"""What a running model's request is doing now, so a slow model doesn't look stuck: waiting for
|
|
2467
|
+
its first token, or thinking/answering with ~tokens so far, and how long it's been quiet."""
|
|
2468
|
+
with self.buf_lock:
|
|
2469
|
+
lv = dict(self.req_live.get(label) or {})
|
|
2470
|
+
if not lv:
|
|
2471
|
+
return ""
|
|
2472
|
+
now = time.time()
|
|
2473
|
+
if lv["last"] is None:
|
|
2474
|
+
return f" [dim]· waiting {fmt_secs(now - lv['started'])}[/dim]"
|
|
2475
|
+
note = f" [dim]· {lv['phase']} ~{lv['tokens']:,} tok[/dim]"
|
|
2476
|
+
quiet = now - lv["last"]
|
|
2477
|
+
return note + (f" [yellow]· quiet {fmt_secs(quiet)}[/yellow]" if quiet >= self.QUIET_S else "")
|
|
2478
|
+
|
|
2479
|
+
QUIET_S = 30 # a running model's row says how long it's been without a token after this long
|
|
2450
2480
|
LIVE_ANSWERS = 5 # a pack's own times this session take over from its history after this many
|
|
2451
2481
|
|
|
2452
2482
|
def eta_seconds(self):
|
|
@@ -2983,10 +3013,9 @@ class ResultsScreen(Screen):
|
|
|
2983
3013
|
table.clear(columns=True)
|
|
2984
3014
|
title = self.query_one("#pq-compare-title", Static)
|
|
2985
3015
|
shared = [t for t in tests if all(m in t["cells"] for m in models)]
|
|
2986
|
-
if len(models) < 2
|
|
3016
|
+
if len(models) < 2:
|
|
2987
3017
|
table.display = False
|
|
2988
|
-
title.update("
|
|
2989
|
-
if len(models) > 8 else "")
|
|
3018
|
+
title.update("")
|
|
2990
3019
|
return
|
|
2991
3020
|
table.display = True
|
|
2992
3021
|
if not shared:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev92
|
|
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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
tuieval/__init__.py,sha256=myVXVX7htbwezW6k3x_FuKgn1RSZ2WcTaDEMByGn6Wg,312
|
|
2
2
|
tuieval/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
-
tuieval/_version.py,sha256=
|
|
4
|
-
tuieval/builtin.toml,sha256=
|
|
3
|
+
tuieval/_version.py,sha256=LTyNMvKP3YQzCLuW_fhXY3Ipvz3I7zhbkkf5Q0Fzxqs,535
|
|
4
|
+
tuieval/builtin.toml,sha256=Nwu7O0qFzcrDAxXO_VZtDPvjVwxZ29OtYOW0BPihZGs,4410
|
|
5
5
|
tuieval/cli.py,sha256=VwiaU1F9bZtU8C-gEowJq58kPeYQY4t6SY_HwrV-qgc,4702
|
|
6
|
-
tuieval/client.py,sha256=
|
|
6
|
+
tuieval/client.py,sha256=wtACBVsngtWopWWhIc4bB6mEUEi31dvvDePN-PjmXbM,11122
|
|
7
7
|
tuieval/compare.py,sha256=uXU_Yzf4zDSHoupHQrTmBhsfc8wCyI1nMaZQN81ybiA,30522
|
|
8
8
|
tuieval/doctor.py,sha256=j2MdDORstsAGPnRmpsnxau1_fR6sTc_oEFGM5zugTJU,7246
|
|
9
|
-
tuieval/engine.py,sha256=
|
|
9
|
+
tuieval/engine.py,sha256=96GWzKzMQ_KaG6wXrIuBiKWFCcblXWL9cQs89QYgj7Y,125256
|
|
10
10
|
tuieval/export.py,sha256=dbHQgL_ubWtIPM1sLzAew_gETnrDJyd0zgqVA5vkpXM,9657
|
|
11
11
|
tuieval/logs.py,sha256=01gZK6tf53dIThaIrs1bEsz9eqKYVlcxpyaLnSPTVxo,8127
|
|
12
12
|
tuieval/machines.py,sha256=t9tu6CZeXZON4EtOVnEwP4VtaynpVLf9Cz8TNjf7jtY,20124
|
|
@@ -18,7 +18,7 @@ tuieval/remove.py,sha256=Vwh43nm9lVb9U9aN-h6CMYbkTwdR3UWfZObwweF72n0,5157
|
|
|
18
18
|
tuieval/run_evals.py,sha256=FU3bvu3jt9ZWg3YLsXNINbdeClXMioyc4zQ4yEhmBQs,47855
|
|
19
19
|
tuieval/scaffold.py,sha256=YBOqniJpf_GtKKAFF3_OHxxMGbmTWdFjcClGcOnrrKE,12130
|
|
20
20
|
tuieval/selftest.py,sha256=nExkrOidgymlmvkxImEBfrQG5iTUN5WuRMIKtqusCCs,9835
|
|
21
|
-
tuieval/tui.py,sha256=
|
|
21
|
+
tuieval/tui.py,sha256=a3ZANtRLq9bBSIdxRj2Ksgj8S5lF8shSMNBxpIzH6rM,178622
|
|
22
22
|
tuieval/tune.py,sha256=azxxthgJeag8JyoQUgaRxq6oI1XvEgzb9vkszjJZH7w,39645
|
|
23
23
|
tuieval/verdict.py,sha256=3SM8qrVdWNIFDENagaR5ZPBYEnjsBtkYB9WS5TDVssU,20517
|
|
24
24
|
tuieval/watch_proxy.py,sha256=bnDO1i6n6yUs1hODoUWcMppJMGPavoQqs6aNYrIxpT8,11416
|
|
@@ -47,8 +47,8 @@ tuieval/templates/packs/tool_call/pack.toml,sha256=n4bx0lGJHSmW41uVqhfIXJ2BWZpmT
|
|
|
47
47
|
tuieval/templates/packs/tool_call/system.txt,sha256=mKxu-lB-0YjLHXdgeFJa8x2C180OvcNz5cHv2N5Bg3U,254
|
|
48
48
|
tuieval/templates/packs/tool_call/tests.yaml,sha256=Nv7c4syrzJ9cU8EBKSK_1ZSFx9V02aCjW4-2TBCPT7s,1859
|
|
49
49
|
tuieval/templates/packs/tool_call/tools.yaml,sha256=HgxCkh0On2VwbHSsjAGM8tNCK7ZtKLGrs7lMCn3Fprs,1048
|
|
50
|
-
tuieval-0.2.0.
|
|
51
|
-
tuieval-0.2.0.
|
|
52
|
-
tuieval-0.2.0.
|
|
53
|
-
tuieval-0.2.0.
|
|
54
|
-
tuieval-0.2.0.
|
|
50
|
+
tuieval-0.2.0.dev92.dist-info/METADATA,sha256=1zUki84oRDfw_gL4VqsvwMsDMxV35smy8dZheMab-RI,14580
|
|
51
|
+
tuieval-0.2.0.dev92.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
52
|
+
tuieval-0.2.0.dev92.dist-info/entry_points.txt,sha256=7eIhi1GpFxBP2OLJV02Nn8LwsNuO512eKOGt1IBbj04,45
|
|
53
|
+
tuieval-0.2.0.dev92.dist-info/licenses/LICENSE,sha256=7wfRgGtBGH1aXhZkIn47oUznqKC18kEPMvUVgol7Ffc,1077
|
|
54
|
+
tuieval-0.2.0.dev92.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|