tuieval 0.2.0.dev139__tar.gz → 0.2.0.dev141__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 (82) hide show
  1. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/CHANGELOG.md +1 -0
  2. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/PKG-INFO +1 -1
  3. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/_version.py +2 -2
  4. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/compare.py +3 -1
  5. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/tui.py +8 -4
  6. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/tests/test_tuieval.py +46 -0
  7. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/.gitignore +0 -0
  8. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/LICENSE +0 -0
  9. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/README.md +0 -0
  10. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/RELEASING.md +0 -0
  11. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/agent-packs.md +0 -0
  12. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/README.md +0 -0
  13. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/favicon.ico +0 -0
  14. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/readme-header.png +0 -0
  15. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/social-preview.png +0 -0
  16. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-1024.png +0 -0
  17. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-128.png +0 -0
  18. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-16.png +0 -0
  19. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-16.svg +0 -0
  20. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-256.png +0 -0
  21. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-32.png +0 -0
  22. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-48.png +0 -0
  23. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-512.png +0 -0
  24. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-64.png +0 -0
  25. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  26. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/brand/tuieval-icon.svg +0 -0
  27. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/tui-answer-reasoning.png +0 -0
  28. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/tui-run-reasoning.png +0 -0
  29. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/images/tui-setup.png +0 -0
  30. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/models.md +0 -0
  31. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/docs/writing-packs.md +0 -0
  32. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/pyproject.toml +0 -0
  33. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/__init__.py +0 -0
  34. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/__main__.py +0 -0
  35. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/agents.py +0 -0
  36. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/builtin.toml +0 -0
  37. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/cli.py +0 -0
  38. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/client.py +0 -0
  39. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/dates.py +0 -0
  40. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/doctor.py +0 -0
  41. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/engine.py +0 -0
  42. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/export.py +0 -0
  43. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/__init__.py +0 -0
  44. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/answer.py +0 -0
  45. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/code.py +0 -0
  46. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/rag.py +0 -0
  47. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/reply.py +0 -0
  48. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/graders/tool_call.py +0 -0
  49. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/insights.py +0 -0
  50. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/logs.py +0 -0
  51. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/machines.py +0 -0
  52. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/onboard.py +0 -0
  53. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/packs.py +0 -0
  54. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/profiles.py +0 -0
  55. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/pta.py +0 -0
  56. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/remove.py +0 -0
  57. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/run_evals.py +0 -0
  58. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/scaffold.py +0 -0
  59. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/selftest.py +0 -0
  60. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/models.toml +0 -0
  61. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  62. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/answer/system.txt +0 -0
  63. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  64. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/code/pack.toml +0 -0
  65. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/code/system.txt +0 -0
  66. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  67. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  68. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/rag/system.txt +0 -0
  69. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  70. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  71. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/reply/system.txt +0 -0
  72. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  73. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  74. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  75. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  76. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  77. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/tune.py +0 -0
  78. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/verdict.py +0 -0
  79. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/watch_proxy.py +0 -0
  80. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/workspace.py +0 -0
  81. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/src/tuieval/yamlout.py +0 -0
  82. {tuieval-0.2.0.dev139 → tuieval-0.2.0.dev141}/tests/mock_server.py +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Per question → Comparison: a **Pass rate** column beside Passed, the share of answers right with repeats included (shown when some model has repeats).
5
6
  - Repeats (Setup, `tuieval run --repeat N`) add N rounds to the saved answers: ×1 run with 2 gives ×3, not ×2. Retest, a blank count and presets still mean the total.
6
7
  - Answers stuck in a loop (one short unit repeated over 2000 chars, in the reasoning or the answer) are stopped as they stream and failed as `LOOP`, instead of running to max_tokens. `[defaults] loop_stop_chars` sets the length; 0 turns it off.
7
8
  - Repeats: the PTA index, Per question, `tuieval compare` and Setup show how many times each model answered each question (`×3`, `×1-3`), so single-run results stand out.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev139
3
+ Version: 0.2.0.dev141
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.dev139'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev139')
21
+ __version__ = version = '0.2.0.dev141'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev141')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -420,7 +420,8 @@ def complete_models(rows, suite, test_ids=None):
420
420
  def compare_models(tests, models):
421
421
  """Per model over per_question entries that all `models` answered: passes, token and time
422
422
  totals/medians (totals add each question's median over repeats, so repeats don't inflate them), questions where it used the fewest tokens / was fastest (among passing
423
- answers for tokens; ties count for each), answers cut off, mean score (partial credit)."""
423
+ answers for tokens; ties count for each), answers cut off, mean score (partial credit),
424
+ pass rate (answers right with repeats, each question weighing the same)."""
424
425
  out = []
425
426
  for m in models:
426
427
  cs = [t["cells"][m] for t in tests]
@@ -438,6 +439,7 @@ def compare_models(tests, models):
438
439
  rs = [r for c in cs for r in c["rows"]]
439
440
  out.append({"model": m, "n": len(cs), "passed": passed, "total_tokens": total,
440
441
  "score": statistics.fmean(c["score"] for c in cs) if cs else None,
442
+ "pass_rate": statistics.fmean(c["passed"] / c["n"] for c in cs) if cs else None,
441
443
  "avg_pp_tps": _rate([(r.get("prompt_tokens"), r["prompt_tps"]) for r in rs]),
442
444
  "avg_gen_tps": _rate([(r["tokens"], r["gen_tps"]) for r in rs]),
443
445
  "med_tokens": median(c["tokens"] for c in cs), "med_secs": median(c["secs"] for c in cs),
@@ -3487,10 +3487,10 @@ class ResultsScreen(Screen):
3487
3487
  self.pq_summary_args = (tests, models) # to refit on resize
3488
3488
  rows = compare.compare_models(shared, models)
3489
3489
  # short headings so every column fits; the line above the table says what each one is
3490
- header = ("Model", "Time", "Passed", "Med tok", "Tokens", "Tok/✓", "Med s", "Prompt t/s", "Gen t/s",
3491
- "Fewest", "Fastest", "Cut off")
3490
+ header = ("Model", "Time", "Passed", "Pass rate", "Med tok", "Tokens", "Tok/✓", "Med s", "Prompt t/s",
3491
+ "Gen t/s", "Fewest", "Fastest", "Cut off")
3492
3492
  best = {k: max((r[k] for r in rows if r[k] is not None), default=None)
3493
- for k in ("passed", "fewest", "fastest", "avg_pp_tps", "avg_gen_tps")}
3493
+ for k in ("passed", "pass_rate", "fewest", "fastest", "avg_pp_tps", "avg_gen_tps")}
3494
3494
  low = {k: min(r[k] for r in rows if r[k] is not None) if any(r[k] is not None for r in rows) else None
3495
3495
  for k in ("med_tokens", "total_tokens", "tokens_per_pass", "med_secs", "total_secs")}
3496
3496
 
@@ -3505,6 +3505,7 @@ class ResultsScreen(Screen):
3505
3505
  hi(tt, ("-" if not tt else f"{tt:.1f}s" if tt < 60 else fmt_secs(tt)), "total_secs", "min"),
3506
3506
  hi(r["passed"], f"{r['passed']}/{r['n']}", "passed", "max")
3507
3507
  + (f" [dim]{r['score']:.0%}[/dim]" if r["passed"] < r["n"] and r["score"] else ""),
3508
+ hi(r["pass_rate"], f"{r['pass_rate']:.0%}", "pass_rate", "max"),
3508
3509
  hi(r["med_tokens"], f"{r['med_tokens']:,.0f}" if r["med_tokens"] else "-", "med_tokens", "min"),
3509
3510
  hi(r["total_tokens"], f"{r['total_tokens']:,.0f}", "total_tokens", "min"),
3510
3511
  hi(r["tokens_per_pass"], f"{r['tokens_per_pass']:,.0f}" if r["tokens_per_pass"] else "-",
@@ -3517,6 +3518,9 @@ class ResultsScreen(Screen):
3517
3518
  hi(r["fastest"], str(r["fastest"]), "fastest", "max"),
3518
3519
  (f"[red]{r['cut']}[/red]" if r["cut"] else "0")
3519
3520
  + (f" [yellow]+{r['over']} over[/yellow]" if r.get("over") else "")])
3521
+ if all(t["cells"][m]["n"] == 1 for t in shared for m in models): # one answer each: Passed says it all
3522
+ i = header.index("Pass rate")
3523
+ header, cells = header[:i] + header[i + 1:], [c[:i] + c[i + 1:] for c in cells]
3520
3524
  table.add_columns(*header)
3521
3525
  tag_w = max(Text.from_markup(tag).cell_len + 1 for tag in tags.values())
3522
3526
  cells = fit_first_column(header, cells, (self.size.width or self.app.size.width) - 3 - tag_w,
@@ -3537,7 +3541,7 @@ class ResultsScreen(Screen):
3537
3541
  "shared the machine's speed: compare their times with care.[/yellow]")
3538
3542
  title.update(line + " [dim]Time / Tokens: each question's median, added up. Med tok / Med s: median "
3539
3543
  "per answer. Tok/✓: tokens per answer right. Prompt t/s / Gen t/s: all tokens over all the time "
3540
- "they took (cached prompts left out). Passed: questions right on every repeat, then the score (partial credit, e.g. checks passed) when some failed. Fewest / Fastest: questions won; ties count for both. "
3544
+ "they took (cached prompts left out). Passed: questions right on every repeat, then the score (partial credit, e.g. checks passed) when some failed. Pass rate (with repeats): answers right, repeats included (each question weighs the same). Fewest / Fastest: questions won; ties count for both. "
3541
3545
  "Cut off: answers that hit max_tokens. ×n beside a name: answers per question.[/dim]")
3542
3546
 
3543
3547
  @staticmethod
@@ -2451,6 +2451,52 @@ class FirstRun(unittest.TestCase):
2451
2451
  "['once [yellow]×1[/]', 'thrice [dim]×3[/]']", # Comparison: only the questions both answered
2452
2452
  ], out.stderr[-2000:])
2453
2453
 
2454
+ def test_pass_rate_counts_every_answer(self):
2455
+ from tuieval import compare
2456
+ row = {"suite": "p", "latency": 1000, "tokens": 10, "truncated": False, "gen_tps": 20.0, "prompt_tps": None}
2457
+ oks = {"a": {"q0": [True, True], "q1": [True, False], "q2": [False, False, False, True]}, # ×2, ×2, ×4
2458
+ "b": {"q0": [True], "q1": [False], "q2": [False]},
2459
+ "c": {"q0": [False], "q1": [False], "q2": [False]}}
2460
+ rows = [dict(row, model=m, test=f"p: {q}", ok=ok, repeat=r)
2461
+ for m, qs in oks.items() for q, v in qs.items() for r, ok in enumerate(v)]
2462
+ out = {r["model"]: r for r in compare.compare_models(compare.per_question(rows), list(oks))}
2463
+ self.assertEqual(out["a"]["passed"], 1) # strict: right on every repeat
2464
+ self.assertAlmostEqual(out["a"]["pass_rate"], (1 + 0.5 + 0.25) / 3) # each question weighs the same
2465
+ self.assertAlmostEqual(out["b"]["pass_rate"], 1 / 3) # ×1: the same as passed / questions
2466
+ self.assertEqual(out["c"]["pass_rate"], 0)
2467
+
2468
+ def test_comparison_shows_pass_rate(self):
2469
+ tuieval(self.ws, "add", "my-model", "--server", "local")
2470
+ code = textwrap.dedent("""
2471
+ import asyncio
2472
+ from rich.text import Text
2473
+ from tuieval import compare
2474
+ from tuieval.tui import EvalsApp, ResultsScreen
2475
+ from textual.widgets import DataTable, TabbedContent
2476
+ oks = {"steady": [[True, True], [True, True]], "shaky": [[True, True], [True, False]]}
2477
+ rows = [{"model": m, "suite": "p", "test": f"p: q{q}", "ok": ok, "latency": 1000, "tokens": 10,
2478
+ "truncated": False, "gen_tps": 20.0, "prompt_tps": None, "repeat": r}
2479
+ for m, qs in oks.items() for q, v in enumerate(qs) for r, ok in enumerate(v)]
2480
+ async def go():
2481
+ app = EvalsApp({})
2482
+ async with app.run_test(size=(160, 40)) as pilot:
2483
+ await pilot.pause()
2484
+ app.push_screen(ResultsScreen())
2485
+ await pilot.pause(0.5)
2486
+ app.screen.query_one(TabbedContent).active = "tab-per-question"
2487
+ await pilot.pause(0.3)
2488
+ app.screen.fill_pq_summary(compare.per_question(rows), list(oks))
2489
+ await pilot.pause(0.3)
2490
+ t = app.screen.query_one("#pq-summary", DataTable)
2491
+ i = [str(c.label) for c in t.ordered_columns].index("Pass rate")
2492
+ print(str(t.ordered_columns[i - 1].label), [t.get_row_at(n)[i] for n in range(t.row_count)])
2493
+ asyncio.run(go())
2494
+ """)
2495
+ out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
2496
+ env=dict(os.environ, TUIEVAL_HOME=self.ws, TUIEVAL_DETECT_PORTS=""), timeout=60)
2497
+ # right after Passed; the best in bold green
2498
+ self.assertEqual(out.stdout.strip(), "Passed ['[bold green]100%[/bold green]', '75%']", out.stderr[-2000:])
2499
+
2454
2500
  def test_results_open_on_pta_index(self):
2455
2501
  tuieval(self.ws, "add", "my-model", "--server", "local")
2456
2502
  code = textwrap.dedent("""
File without changes
File without changes