tuieval 0.2.0.dev90__tar.gz → 0.2.0.dev94__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.dev90 → tuieval-0.2.0.dev94}/CHANGELOG.md +3 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/PKG-INFO +1 -1
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/models.md +17 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/_version.py +2 -2
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/builtin.toml +1 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/cli.py +1 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/client.py +24 -5
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/engine.py +3 -1
- tuieval-0.2.0.dev94/src/tuieval/insights.py +538 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/run_evals.py +37 -1
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/tui.py +94 -1
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/tests/mock_server.py +17 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/tests/test_tuieval.py +195 -1
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/.gitignore +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/LICENSE +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/README.md +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/RELEASING.md +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/README.md +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/favicon.ico +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/readme-header.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/social-preview.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-1024.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-128.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-16.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-16.svg +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-256.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-32.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-48.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-512.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-64.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon-animated.svg +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/brand/tuieval-icon.svg +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/tui-answer-reasoning.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/tui-run-reasoning.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/images/tui-setup.png +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/docs/writing-packs.md +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/pyproject.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/__init__.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/__main__.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/compare.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/doctor.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/export.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/__init__.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/answer.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/code.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/rag.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/reply.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/graders/tool_call.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/logs.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/machines.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/onboard.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/packs.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/profiles.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/pta.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/remove.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/scaffold.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/selftest.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/models.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/answer/pack.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/answer/system.txt +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/code/pack.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/code/system.txt +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/code/tests.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/rag/pack.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/rag/system.txt +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/reply/pack.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/reply/system.txt +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/tune.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/verdict.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/watch_proxy.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/workspace.py +0 -0
- {tuieval-0.2.0.dev90 → tuieval-0.2.0.dev94}/src/tuieval/yamlout.py +0 -0
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Results → Insights (also `tuieval insights`): charts with what to do next: every model against a reference (95% CI), the best model per use case, fine-tunes against their base (`base` in models.toml), why models fail, accuracy against tokens or time, question health, and the Certify runs that would settle the strongest models' verdicts. `[defaults] reference` sets the default reference.
|
|
6
|
+
- A server that stops sending tokens mid-answer for 5 minutes (`stall_timeout_ms`) is a server error: retried, never counted against the model. Keep-alive pings no longer stretch a request past `request_timeout_ms`.
|
|
7
|
+
- The run screen's queue shows what each running model is doing: waiting, thinking or answering with ~tokens so far, and how long it's been quiet, so a model you aren't streaming doesn't look stuck.
|
|
5
8
|
- Results, Per question: the side-by-side summary shows any number of models (it stopped at 8).
|
|
6
9
|
- `max_tokens` in pack.toml sets one pack's output budget; changing it reruns only that pack. Models whose context or endpoint can't fit it are skipped for that pack.
|
|
7
10
|
- The run screen's queue keeps the running model in view, unless you scroll it yourself (scrolling back to it follows again).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tuieval
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.dev94
|
|
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
|
|
@@ -40,6 +40,7 @@ In the TUI, `a` adds a model and `m` scans your model folders. Each model is one
|
|
|
40
40
|
| `max_context` (other servers) | the context the server provides; packs whose prompts need more are skipped |
|
|
41
41
|
| `kv_type` (llama) | e.g. `"q8_0"`: a quantized KV cache, which makes it a different model (give it its own label) |
|
|
42
42
|
| `tools = false` | packs that need tool calling are skipped |
|
|
43
|
+
| `base` | for a fine-tune: the label of its base model, compared in Results → Insights |
|
|
43
44
|
|
|
44
45
|
Results record the settings each model actually ran with, and the results screen notes any differences between models.
|
|
45
46
|
|
|
@@ -109,6 +110,20 @@ The P and T bars are as long as the tokens and time used: the model that used th
|
|
|
109
110
|
|
|
110
111
|
Models are compared on the questions all of them answered, so one that skipped some never looks faster. Server and connection errors aren't answers: a model with only those is listed as having none. A model with fewer than half as many answers as the most-answered one is left out (and listed), or it would shrink the shared questions for everyone. Pick the models and pack to compare in the TUI, or use `--only` and `--packs`: models you pick are always compared.
|
|
111
112
|
|
|
113
|
+
## Insights
|
|
114
|
+
|
|
115
|
+
Results → **Insights** (also `tuieval insights`) turns the results into charts, each with plain lines on what to do:
|
|
116
|
+
|
|
117
|
+
1. **Who's worth keeping**: every model against a reference model, paired on the questions both answered: the difference in accuracy points with its 95% CI, and better, tie or worse. Pick the reference in the tab or with `--reference`; set a default with `reference = "<label>"` under `[defaults]` in `models.toml`. Without one, the most accurate model is the reference.
|
|
118
|
+
2. **Best model per use case**: a heatmap of accuracy per model and use case (pack group); ★ marks the best among models that answered most of it.
|
|
119
|
+
3. **Fine-tunes against their base model**: for each model with `base = "<label>"` in its `[[models]]` block, the difference per pack and the question categories (a test's `category`) it gained or lost most.
|
|
120
|
+
4. **Why models fail**: each model's answers split into right, wrong, cut off at max_tokens, no answer or bad format, code that crashed or hung, and critical failures.
|
|
121
|
+
5. **Accuracy against cost**: accuracy against median tokens (or seconds) per question, over the questions every model answered; green models are the ones no other model beats on both. With hosted and local models together, the best local trade-offs get their own line.
|
|
122
|
+
6. **Question health**: questions every model fails (check them), every model passes (they don't separate models), and the ones that split models most.
|
|
123
|
+
7. **Next runs**: the Certify runs that would settle the open verdicts of the strongest models, with their time and command.
|
|
124
|
+
|
|
125
|
+
Models are the ones picked with **Models** (shared with the other tabs), else every model with at least half as many answers as the most-answered one. Server and connection errors aren't answers.
|
|
126
|
+
|
|
112
127
|
## Tuning
|
|
113
128
|
|
|
114
129
|
`tuieval tune <model>` (or `t` on the setup screen, for the ticked models) finds the fastest speed flags for a model on this machine:
|
|
@@ -191,6 +206,8 @@ A workspace's `models.toml` only needs what differs from these. `[defaults]` fil
|
|
|
191
206
|
[defaults]
|
|
192
207
|
repeat = 3
|
|
193
208
|
request_timeout_ms = 1800000 # per request; long thinking runs need it
|
|
209
|
+
stall_timeout_ms = 300000 # no tokens this long once answering began: the server stalled (retried, not counted)
|
|
210
|
+
# reference = "<label>" # the model Results → Insights compares against by default
|
|
194
211
|
ready_timeout_s = 1200 # how long a model may take to load before giving up
|
|
195
212
|
model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
|
|
196
213
|
|
|
@@ -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.dev94'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'dev94')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -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
|
|
|
@@ -15,6 +15,7 @@ Start here
|
|
|
15
15
|
Day to day
|
|
16
16
|
tuieval compare [...] scorecards (--speed, --pairwise, --reference M, --failures)
|
|
17
17
|
tuieval pta the PTA index: parsimony (tokens), time and accuracy per model, as bars
|
|
18
|
+
tuieval insights charts with what to do next: vs a reference, per use case, why models fail, …
|
|
18
19
|
tuieval report the verdicts with their evidence, as a markdown file
|
|
19
20
|
tuieval history every verdict change, newest first
|
|
20
21
|
tuieval items tests that don't separate models or look broken
|
|
@@ -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
|
|
@@ -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()
|