tuieval 0.2.0.dev94__tar.gz → 0.2.0.dev96__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.
Files changed (79) hide show
  1. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/CHANGELOG.md +1 -0
  2. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/PKG-INFO +1 -1
  3. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/models.md +1 -1
  4. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/_version.py +2 -2
  5. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/pta.py +21 -8
  6. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/tests/test_tuieval.py +23 -0
  7. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/.gitignore +0 -0
  8. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/LICENSE +0 -0
  9. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/README.md +0 -0
  10. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/RELEASING.md +0 -0
  11. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/README.md +0 -0
  12. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/favicon.ico +0 -0
  13. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/readme-header.png +0 -0
  14. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/social-preview.png +0 -0
  15. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-1024.png +0 -0
  16. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-128.png +0 -0
  17. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-16.png +0 -0
  18. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-16.svg +0 -0
  19. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-256.png +0 -0
  20. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-32.png +0 -0
  21. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-48.png +0 -0
  22. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-512.png +0 -0
  23. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-64.png +0 -0
  24. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  25. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/brand/tuieval-icon.svg +0 -0
  26. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/tui-answer-reasoning.png +0 -0
  27. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/tui-run-reasoning.png +0 -0
  28. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/images/tui-setup.png +0 -0
  29. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/docs/writing-packs.md +0 -0
  30. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/pyproject.toml +0 -0
  31. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/__init__.py +0 -0
  32. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/__main__.py +0 -0
  33. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/builtin.toml +0 -0
  34. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/cli.py +0 -0
  35. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/client.py +0 -0
  36. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/compare.py +0 -0
  37. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/doctor.py +0 -0
  38. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/engine.py +0 -0
  39. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/export.py +0 -0
  40. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/__init__.py +0 -0
  41. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/answer.py +0 -0
  42. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/code.py +0 -0
  43. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/rag.py +0 -0
  44. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/reply.py +0 -0
  45. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/graders/tool_call.py +0 -0
  46. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/insights.py +0 -0
  47. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/logs.py +0 -0
  48. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/machines.py +0 -0
  49. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/onboard.py +0 -0
  50. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/packs.py +0 -0
  51. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/profiles.py +0 -0
  52. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/remove.py +0 -0
  53. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/run_evals.py +0 -0
  54. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/scaffold.py +0 -0
  55. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/selftest.py +0 -0
  56. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/models.toml +0 -0
  57. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  58. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/answer/system.txt +0 -0
  59. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  60. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/code/pack.toml +0 -0
  61. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/code/system.txt +0 -0
  62. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  63. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  64. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/rag/system.txt +0 -0
  65. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  66. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  67. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/reply/system.txt +0 -0
  68. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  69. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  70. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  71. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  72. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  73. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/tui.py +0 -0
  74. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/tune.py +0 -0
  75. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/verdict.py +0 -0
  76. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/watch_proxy.py +0 -0
  77. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/workspace.py +0 -0
  78. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/src/tuieval/yamlout.py +0 -0
  79. {tuieval-0.2.0.dev94 → tuieval-0.2.0.dev96}/tests/mock_server.py +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - PTA index: the accuracy bar shows answers cut off at max_tokens in orange, with `✂ n%` beside it and a **cut off** column in the table, so overthinking stands apart from wrong answers.
5
6
  - 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
7
  - 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
8
  - 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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev94
3
+ Version: 0.2.0.dev96
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
@@ -104,7 +104,7 @@ The **PTA index** puts parsimony (tokens), time and accuracy side by side, as a
104
104
 
105
105
  - **P parsimony (tokens)**: the tokens used, reasoning and answer, on every question compared (each question at its median over repeats). Beside the bar: how many times the leanest model's, e.g. `2.4×` (`1.0×` is the leanest, ★ in the table's **tokens/answer**). Unlike time it doesn't depend on your machine: it's how much a model says to get to an answer, which is what you pay for on an API and what fills its context.
106
106
  - **T time**: the total time to answer those questions, as how many times the fastest model's, e.g. `28×` (★ in **time/answer**).
107
- - **A accuracy**: the share of answers to those questions that passed.
107
+ - **A accuracy**: the share of answers to those questions that passed. Answers cut off at `max_tokens` count as wrong, but show in orange after the green, with `✂ n%` beside the number and a **cut off** column in the table: those failures are a model out of tokens (overthinking), not a wrong answer yet. A pack's own `max_tokens` gives it more room.
108
108
 
109
109
  The P and T bars are as long as the tokens and time used: the model that used the most fills its bar, the others in proportion (at least one block), so **shorter is better**. The A bar is the share of answers right, so **longer is better**; it's full only when every answer passed. Each heading says which way is better.
110
110
 
@@ -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.dev94'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev94')
21
+ __version__ = version = '0.2.0.dev96'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev96')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -47,7 +47,7 @@ def memory_gb(infos):
47
47
 
48
48
 
49
49
  def index(rows, models=None, memory=None):
50
- """{"questions": n shared, "models": [{model, P, T, A, tokens, total_s, passed, answers, alongside,
50
+ """{"questions": n shared, "models": [{model, P, T, A, cut, cut_pct, tokens, total_s, passed, answers, alongside,
51
51
  fewest, fastest, tps, ttft, memory_gb}],
52
52
  "left_out": [(model, questions answered)], "no_answers": [model], "most": questions answered by the
53
53
  most-answered model}, best accuracy first. models: the ones to compare (default: every model in rows
@@ -75,6 +75,9 @@ def index(rows, models=None, memory=None):
75
75
  "total_s": sum(c["secs"] or 0 for c in cells),
76
76
  "passed": sum(c["passed"] for c in cells), "answers": answers,
77
77
  "A": 100 * sum(c["passed"] for c in cells) / answers if answers else None,
78
+ # failed only because max_tokens ran out (overthinking, not wrong yet)
79
+ "cut": sum(c["truncated"] for c in cells),
80
+ "cut_pct": 100 * sum(c["truncated"] for c in cells) / answers if answers else None,
78
81
  # generation speed and time to first token over the same answers (medians)
79
82
  "tps": compare.median(r.get("gen_tps") for c in cells for r in c["rows"]),
80
83
  "ttft": compare.median(r.get("ttft") for c in cells for r in c["rows"]),
@@ -118,7 +121,8 @@ def bars(result, width=20, markup=True):
118
121
  """Lines with a row per model and a bar each for P, T and A: the at-a-glance view. The P and T bars
119
122
  are proportional to tokens and time (the longest is the model that used most; shorter is better),
120
123
  with how many times the leanest / fastest model's beside them; the A bar is the share of answers
121
- right (longer is better, full only when every answer passed). markup=False gives plain text."""
124
+ right (longer is better, full only when every answer passed), then the share cut off at max_tokens in
125
+ orange (▓ in plain text) and "✂ n%" beside it. markup=False gives plain text."""
122
126
  if not result["models"]:
123
127
  return []
124
128
  name_w = max(len(x["model"]) for x in result["models"])
@@ -138,12 +142,18 @@ def bars(result, width=20, markup=True):
138
142
  if v is not None and v < 100: # full only when every answer passed
139
143
  full = min(full, width - 1)
140
144
  num = "-" if v is None else compare.pct(v / 100)
145
+ cut = x.get("cut_pct") or 0
146
+ part = min(width - full, max(1, round(width * cut / 100))) if cut else 0
147
+ if cut: # after the accuracy, which stays aligned with the other rows'
148
+ num = f"{num:>6} ✂ {compare.pct(cut / 100)}"
141
149
  else:
142
150
  full = _blocks(width, x.get(measure), most[measure])
143
151
  num = times(x.get(shown))
144
- bar, rest = "█" * full, "░" * (width - full)
145
- bar = f"[{color}]{bar}[/][grey37]{rest}[/]" if markup else bar + rest
146
- cells.append(f"{bar} {num:>6}" + " " * (cell_w - width - 7))
152
+ part = 0
153
+ bar, cut_bar, rest = "█" * full, "▓" * part, "░" * (width - full - part)
154
+ bar = (f"[{color}]{bar}[/]" + (f"[#ff8c00]{'█' * part}[/]" if part else "") + f"[grey37]{rest}[/]"
155
+ if markup else bar + cut_bar + rest)
156
+ cells.append(f"{bar} {num:>6}" + " " * max(0, cell_w - width - 1 - max(6, len(num))))
147
157
  name = f"{x['model']:<{name_w}}"
148
158
  lines.append((f"{rich_escape(name) if markup else name} " + "".join(cells)).rstrip())
149
159
  return lines
@@ -168,10 +178,11 @@ def left_out_lines(result):
168
178
  def table(result):
169
179
  """(header, rows) for a plain table of the index."""
170
180
  n = result["questions"] or 1
171
- header = ["model", "tokens/answer", "time/answer", "answers right", "tok/s", "TTFT s", "memory GB"]
181
+ header = ["model", "tokens/answer", "time/answer", "answers right", "cut off", "tok/s", "TTFT s", "memory GB"]
172
182
  rows = [[x["model"], f"{round(x['tokens'] / n):,}" + (" ★" if x.get("fewest") else ""),
173
183
  fmt_secs(x["total_s"] / n) + (" ★" if x.get("fastest") else ""),
174
- f"{x['passed']}/{x['answers']}", _num(x.get("tps"), ".0f"), _num(x.get("ttft"), ".1f"),
184
+ f"{x['passed']}/{x['answers']}", f"{x.get('cut') or 0}/{x['answers']}",
185
+ _num(x.get("tps"), ".0f"), _num(x.get("ttft"), ".1f"),
175
186
  _num(x.get("memory_gb"), ".1f")] for x in result["models"]]
176
187
  return header, rows
177
188
 
@@ -204,7 +215,9 @@ def scope_note(result, models):
204
215
  return (f"Compared on the {result['questions']} questions all {len(result['models'])} model(s) answered. "
205
216
  "P parsimony (tokens) and T time: bars as long as the tokens / time used (shorter is better), "
206
217
  "and how many times the leanest / fastest model's (1.0×, ★ in the table). A accuracy: answers right "
207
- "(longer is better).")
218
+ "(longer is better)"
219
+ + ("; orange ✂: answers cut off at max_tokens, out of tokens rather than wrong (they count as wrong)."
220
+ if any(x.get("cut") for x in result["models"]) else "."))
208
221
 
209
222
 
210
223
  def for_engine(e, labels=None, packs=None, results_dir=None):
@@ -1264,6 +1264,29 @@ class PTAIndex(unittest.TestCase):
1264
1264
  self.assertEqual((two_wrong[-2], two_wrong[-1]), ("█████████░", "99.6%")) # not a full bar
1265
1265
  self.assertEqual((perfect[-2], perfect[-1]), ("██████████", "100%"))
1266
1266
 
1267
+ def test_cut_off_answers_show_in_the_accuracy_bar(self):
1268
+ from tuieval import pta
1269
+
1270
+ def row(model, test, ok, truncated=False):
1271
+ return {"model": model, "suite": "p", "test": f"p: {test}", "ok": ok, "latency": 1000,
1272
+ "tokens": 10, "truncated": truncated, "gen_tps": None, "repeat": 0}
1273
+ rows = ([row("thinker", f"q{i}", i < 6) for i in range(8)]
1274
+ + [row("thinker", f"q{i}", False, truncated=True) for i in (8, 9)] # out of tokens
1275
+ + [row("plain", f"q{i}", i < 6) for i in range(10)])
1276
+ res = pta.index(rows)
1277
+ by = {x["model"]: x for x in res["models"]}
1278
+ self.assertEqual((by["thinker"]["A"], by["thinker"]["cut"], by["thinker"]["cut_pct"]), (60, 2, 20))
1279
+ self.assertEqual((by["plain"]["cut"], by["plain"]["cut_pct"]), (0, 0))
1280
+ bars = {line.split()[0]: line.split() for line in pta.bars(res, 10, markup=False)[2:]}
1281
+ self.assertEqual(bars["thinker"][-4:], ["██████▓▓░░", "60.0%", "✂", "20.0%"]) # right, cut off, wrong
1282
+ self.assertEqual(bars["plain"][-2:], ["██████░░░░", "60.0%"]) # no cut-offs: no ✂
1283
+ self.assertIn("[#ff8c00]██[/]", pta.bars(res, 10)[2] + pta.bars(res, 10)[3])
1284
+ header, table = pta.table(res)
1285
+ self.assertEqual(header[4], "cut off")
1286
+ self.assertEqual({r[0]: r[4] for r in table}, {"thinker": "2/10", "plain": "0/10"})
1287
+ self.assertIn("cut off at max_tokens", pta.scope_note(res, ["thinker", "plain"]))
1288
+ self.assertNotIn("cut off", pta.scope_note(pta.index(rows[10:]), ["plain"]))
1289
+
1267
1290
  def test_server_errors_and_models_with_few_answers(self):
1268
1291
  from tuieval import pta
1269
1292
 
File without changes
File without changes
File without changes