tuieval 0.2.0.dev14__tar.gz → 0.2.0.dev23__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 (76) hide show
  1. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/CHANGELOG.md +10 -0
  2. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/PKG-INFO +18 -7
  3. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/README.md +17 -6
  4. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/models.md +117 -2
  5. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/writing-packs.md +6 -0
  6. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/_version.py +2 -2
  7. tuieval-0.2.0.dev23/src/tuieval/builtin.toml +83 -0
  8. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/cli.py +21 -13
  9. tuieval-0.2.0.dev23/src/tuieval/doctor.py +131 -0
  10. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/engine.py +150 -4
  11. tuieval-0.2.0.dev23/src/tuieval/onboard.py +132 -0
  12. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/packs.py +8 -2
  13. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/run_evals.py +54 -4
  14. tuieval-0.2.0.dev23/src/tuieval/scaffold.py +243 -0
  15. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/selftest.py +11 -7
  16. tuieval-0.2.0.dev23/src/tuieval/templates/models.toml +23 -0
  17. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/tui.py +206 -20
  18. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/verdict.py +57 -0
  19. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/tests/test_tuieval.py +204 -2
  20. tuieval-0.2.0.dev14/src/tuieval/scaffold.py +0 -106
  21. tuieval-0.2.0.dev14/src/tuieval/templates/models.toml +0 -142
  22. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/.gitignore +0 -0
  23. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/LICENSE +0 -0
  24. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/RELEASING.md +0 -0
  25. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/README.md +0 -0
  26. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/favicon.ico +0 -0
  27. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/readme-header.png +0 -0
  28. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/social-preview.png +0 -0
  29. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-1024.png +0 -0
  30. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-128.png +0 -0
  31. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-16.png +0 -0
  32. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-16.svg +0 -0
  33. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-256.png +0 -0
  34. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-32.png +0 -0
  35. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-48.png +0 -0
  36. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-512.png +0 -0
  37. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-64.png +0 -0
  38. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  39. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/brand/tuieval-icon.svg +0 -0
  40. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/docs/images/tui-setup.png +0 -0
  41. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/pyproject.toml +0 -0
  42. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/__init__.py +0 -0
  43. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/__main__.py +0 -0
  44. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/client.py +0 -0
  45. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/compare.py +0 -0
  46. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/export.py +0 -0
  47. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/__init__.py +0 -0
  48. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/answer.py +0 -0
  49. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/code.py +0 -0
  50. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/rag.py +0 -0
  51. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/reply.py +0 -0
  52. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/graders/tool_call.py +0 -0
  53. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/machines.py +0 -0
  54. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/profiles.py +0 -0
  55. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/remove.py +0 -0
  56. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  57. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/answer/system.txt +0 -0
  58. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  59. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/code/pack.toml +0 -0
  60. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/code/system.txt +0 -0
  61. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  62. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  63. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/rag/system.txt +0 -0
  64. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  65. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  66. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/reply/system.txt +0 -0
  67. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  68. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  69. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  70. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  71. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  72. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/tune.py +0 -0
  73. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/watch_proxy.py +0 -0
  74. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/workspace.py +0 -0
  75. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/src/tuieval/yamlout.py +0 -0
  76. {tuieval-0.2.0.dev14 → tuieval-0.2.0.dev23}/tests/mock_server.py +0 -0
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - A new workspace's `models.toml` is 23 lines: the llama, local and OpenRouter servers and the defaults are built in (docs/models.md lists them in full; a server defined in `models.toml` replaces the built-in one with that name). Existing workspaces keep their settings; built-in servers they don't define become available, and adding a model still prefers the workspace's own servers.
6
+ - Less to take in at first: until something has run, the setup screen leaves out context sizes, tuning state and the Tune key; Presets and Hide are off the footer (`?` lists every key); `tuieval help` groups commands into Start here, Day to day and Advanced.
7
+ - Results lead with a plain summary per model ("ready for Support; not decided yet for Coding → run Certify to finish"), in the TUI, `tuieval verdict` (with the command to run next) and the report; the statistics follow.
8
+ - First packs without hand-written YAML: `tuieval new-pack <name> --from questions.csv` (question and answer columns) and `--about "<topic>"` (writes a prompt for a strong model to draft the tests). A test can take its prompt from a file (`input_file`). `tuieval selftest` errors say how to fix them.
9
+ - `tuieval init` in a terminal is a guided setup: it finds models on servers already running (LM Studio, Ollama, llama-server, vLLM), adds the ones you pick, creates a starter pack and runs a Smoke check (`--yes` for no questions).
10
+ - `tuieval add` without a model lists the models on running servers to pick from; `tuieval add <id>` finds the server that has it and adds that server to models.toml. The TUI's add dialog lists them too, with rarely needed options folded away.
11
+ - `?` on any TUI screen explains what it's for, its keys, and the words tuieval uses (tiers, verdicts, critical failures, gates).
12
+ - The setup screen is ready on first open: the only pack and model are ticked, the tier is Smoke until something has run, and ticked boxes are clearly marked (unticked ones are empty).
13
+ - First runs fail fast with the fix instead of hanging: a server that isn't running fails in seconds (it used to wait up to 20 minutes), a missing `llama-server` says how to install it, and `tuieval run` with no models says how to add one.
14
+ - `tuieval doctor` checks servers, models, packs and API keys, and says what to fix.
5
15
  - Run only some tests of a pack: `e` in the TUI, or `tuieval run --tests`. Runs add up; `--force` reruns just the picked tests. Presets keep the pick.
6
16
  - `tuieval tune` scores options on projected full-length answers (`[tune] answer_tokens`, default 1024), so generation speed counts as in real runs.
7
17
  - `tuieval tune` reports an option's memory cost instead of rejecting it; it rejects only on critical memory pressure or over `[tune] swap_limit_mb`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev14
3
+ Version: 0.2.0.dev23
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
@@ -43,12 +43,21 @@ Then you get an answer to the questions that matter:
43
43
 
44
44
  ```bash
45
45
  pipx install tuieval # or: uv tool install tuieval, or pip install tuieval (Python 3.11+)
46
- tuieval init my-evals && cd my-evals
47
- tuieval new-pack my-first-pack # a pack of example questions to edit
48
- tuieval add ~/models/Some-Model-Q4_K_M.gguf
49
- tuieval # open the TUI
46
+ tuieval init my-evals # guided: finds models already running (LM Studio, Ollama, vLLM…),
47
+ # adds a starter pack and runs a 3-question check
48
+ cd my-evals && tuieval # open the TUI (? explains any screen)
50
49
  ```
51
50
 
51
+ Whatever runs your models, the first steps are short:
52
+
53
+ | You have | Do this |
54
+ |---|---|
55
+ | **LM Studio, Ollama or vLLM** | Start its server and load a model, then `tuieval init my-evals`: it finds the model. Later: `tuieval add` lists new ones. |
56
+ | **GGUF files** | Install llama.cpp (`brew install llama.cpp`), then `tuieval add ~/models/Some-Model-Q4_K_M.gguf`. tuieval starts `llama-server` for it, sized to your machine. |
57
+ | **An OpenRouter key** | `export OPENROUTER_API_KEY=…`, then `tuieval run --tier smoke --only openrouter:<model id>`. |
58
+
59
+ Stuck? `tuieval doctor` checks servers, models, packs and keys, and says what to fix.
60
+
52
61
  `pip install tuieval` gets the latest release. Every change on `main` is also published as a dev build (`X.Y.0.devN`); get it with `pipx install --pip-args=--pre tuieval` or `pip install --pre tuieval`.
53
62
 
54
63
  Once you've added your own packs and models, the setup screen looks like this (packs on the left, models with a verdict code per use case on the right, the highlighted model's details below):
@@ -117,7 +126,8 @@ tuieval uses the current folder, or `--workspace DIR` / `TUIEVAL_HOME`. Keep it
117
126
  ```bash
118
127
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # llama.cpp (llama-server on PATH)
119
128
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision
120
- tuieval add qwen3:8b --server local # an already-running server (set its url)
129
+ tuieval add # pick from the models on running servers
130
+ tuieval add qwen3:8b # finds the running server that has it
121
131
  tuieval run --tier smoke --only openrouter:qwen/qwen3-32b # any OpenRouter model, no config needed
122
132
  ```
123
133
 
@@ -130,7 +140,7 @@ See **[docs/models.md](docs/models.md)** for servers, OpenRouter endpoint pinnin
130
140
  3. **Watch the run:** progress with ETA, live score, tok/s, TTFT and memory, the current test with reasoning and answer side by side, recent results with the grader's reason. `k` skips a model, `c` cancels (finished work is kept and resumes next time). Runs started while one is going are queued.
131
141
  4. **Press `r` for results:** production readiness, verdict history, scorecard, speed & tokens (★ marks models nothing beats on both accuracy and time), per question, is the difference real?, tests that separate models, by difficulty, failures, and test quality.
132
142
 
133
- Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model.
143
+ Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model. `?` explains any screen.
134
144
 
135
145
  ## Command line
136
146
 
@@ -139,6 +149,7 @@ tuieval run # Screen every model on every pack
139
149
  tuieval run --tier certify --only a,b --packs support-bot,coding
140
150
  tuieval run --packs coding --tests parse-dates,fix-bug # only these tests (or --tests coding:parse-dates)
141
151
  tuieval run --dry-run # the plan and server commands
152
+ tuieval doctor # check servers, models, packs and keys, and what to fix
142
153
  tuieval verdict # PASS / FAIL / INCONCLUSIVE per model and use case
143
154
  tuieval report # the same with evidence, as a markdown file
144
155
  tuieval history # every verdict change over time
@@ -17,12 +17,21 @@ Then you get an answer to the questions that matter:
17
17
 
18
18
  ```bash
19
19
  pipx install tuieval # or: uv tool install tuieval, or pip install tuieval (Python 3.11+)
20
- tuieval init my-evals && cd my-evals
21
- tuieval new-pack my-first-pack # a pack of example questions to edit
22
- tuieval add ~/models/Some-Model-Q4_K_M.gguf
23
- tuieval # open the TUI
20
+ tuieval init my-evals # guided: finds models already running (LM Studio, Ollama, vLLM…),
21
+ # adds a starter pack and runs a 3-question check
22
+ cd my-evals && tuieval # open the TUI (? explains any screen)
24
23
  ```
25
24
 
25
+ Whatever runs your models, the first steps are short:
26
+
27
+ | You have | Do this |
28
+ |---|---|
29
+ | **LM Studio, Ollama or vLLM** | Start its server and load a model, then `tuieval init my-evals`: it finds the model. Later: `tuieval add` lists new ones. |
30
+ | **GGUF files** | Install llama.cpp (`brew install llama.cpp`), then `tuieval add ~/models/Some-Model-Q4_K_M.gguf`. tuieval starts `llama-server` for it, sized to your machine. |
31
+ | **An OpenRouter key** | `export OPENROUTER_API_KEY=…`, then `tuieval run --tier smoke --only openrouter:<model id>`. |
32
+
33
+ Stuck? `tuieval doctor` checks servers, models, packs and keys, and says what to fix.
34
+
26
35
  `pip install tuieval` gets the latest release. Every change on `main` is also published as a dev build (`X.Y.0.devN`); get it with `pipx install --pip-args=--pre tuieval` or `pip install --pre tuieval`.
27
36
 
28
37
  Once you've added your own packs and models, the setup screen looks like this (packs on the left, models with a verdict code per use case on the right, the highlighted model's details below):
@@ -91,7 +100,8 @@ tuieval uses the current folder, or `--workspace DIR` / `TUIEVAL_HOME`. Keep it
91
100
  ```bash
92
101
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # llama.cpp (llama-server on PATH)
93
102
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision
94
- tuieval add qwen3:8b --server local # an already-running server (set its url)
103
+ tuieval add # pick from the models on running servers
104
+ tuieval add qwen3:8b # finds the running server that has it
95
105
  tuieval run --tier smoke --only openrouter:qwen/qwen3-32b # any OpenRouter model, no config needed
96
106
  ```
97
107
 
@@ -104,7 +114,7 @@ See **[docs/models.md](docs/models.md)** for servers, OpenRouter endpoint pinnin
104
114
  3. **Watch the run:** progress with ETA, live score, tok/s, TTFT and memory, the current test with reasoning and answer side by side, recent results with the grader's reason. `k` skips a model, `c` cancels (finished work is kept and resumes next time). Runs started while one is going are queued.
105
115
  4. **Press `r` for results:** production readiness, verdict history, scorecard, speed & tokens (★ marks models nothing beats on both accuracy and time), per question, is the difference real?, tests that separate models, by difficulty, failures, and test quality.
106
116
 
107
- Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model.
117
+ Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model. `?` explains any screen.
108
118
 
109
119
  ## Command line
110
120
 
@@ -113,6 +123,7 @@ tuieval run # Screen every model on every pack
113
123
  tuieval run --tier certify --only a,b --packs support-bot,coding
114
124
  tuieval run --packs coding --tests parse-dates,fix-bug # only these tests (or --tests coding:parse-dates)
115
125
  tuieval run --dry-run # the plan and server commands
126
+ tuieval doctor # check servers, models, packs and keys, and what to fix
116
127
  tuieval verdict # PASS / FAIL / INCONCLUSIVE per model and use case
117
128
  tuieval report # the same with evidence, as a markdown file
118
129
  tuieval history # every verdict change over time
@@ -1,6 +1,6 @@
1
1
  # Models, servers and machines
2
2
 
3
- Everything about models lives in your workspace's `models.toml`. `tuieval init` writes one with three servers ready to use:
3
+ Everything about models lives in your workspace's `models.toml`. `tuieval init` writes a short one; three servers are built in and ready to use (their full definitions are at the end of this page, under [Built-in servers and defaults](#built-in-servers-and-defaults)):
4
4
 
5
5
  | server | what it is |
6
6
  |---|---|
@@ -14,7 +14,8 @@ Everything about models lives in your workspace's `models.toml`. `tuieval init`
14
14
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # GGUF -> llama
15
15
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --no-think # same model, thinking off (label …-nothink)
16
16
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision model
17
- tuieval add qwen3:8b --server local # a model your running server serves
17
+ tuieval add # pick from the models on servers already running
18
+ tuieval add qwen3:8b # finds the running server that has it
18
19
  tuieval scan --add # every new GGUF under model_dirs
19
20
  tuieval list # models; hidden ones listed separately
20
21
  tuieval remove --hidden # clean up every hidden model
@@ -142,3 +143,117 @@ On Apple Silicon Macs, once the system's GPU allocations pass about half of RAM,
142
143
  | `api_key_env` | environment variable holding the API key |
143
144
  | `thinking_param` | `reasoning` to send `[sampling] enable_thinking` as OpenRouter's `reasoning.enabled` |
144
145
  | `headers` | extra HTTP headers |
146
+
147
+ ## Built-in servers and defaults
148
+
149
+ A workspace's `models.toml` only needs what differs from these. `[defaults]` fill in key by key. `[sampling]` applies only when `models.toml` has no `[sampling]` section at all, because sampling is part of every result's fingerprint. A server you define replaces the built-in one with the same name; to change one setting, copy the whole block into `models.toml` and edit it. `tuieval add` adds a `[servers.<name>]` block with just a `url` when it finds a model on a server that's already running.
150
+
151
+ ```toml
152
+ [defaults] fill in key by key; [sampling] only when models.toml has no [sampling] at all (it's part of
153
+ # every result's fingerprint); a server here is replaced whole by one of the same name in models.toml.
154
+
155
+ [defaults]
156
+ repeat = 3
157
+ request_timeout_ms = 1800000 # per request; long thinking runs need it
158
+ ready_timeout_s = 1200 # how long a model may take to load before giving up
159
+ model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
160
+
161
+ # Sent with every request. A model may override it (see `thinking` / `sampling` below); results
162
+ # record the settings each model actually ran with, and the scorecard shows any difference.
163
+ [sampling]
164
+ temperature = 0.7
165
+ top_p = 0.95
166
+ top_k = 20
167
+ min_p = 0.0
168
+ max_tokens = 16384
169
+ enable_thinking = true # test every model in the SAME mode
170
+
171
+ # llama.cpp's server (https://github.com/ggml-org/llama.cpp), started for each GGUF model.
172
+ [servers.llama]
173
+ # Output-affecting flags only. Anything here must be identical wherever results are compared,
174
+ # so it's part of each result's fingerprint. Speed-only flags live in `perf` and `tune` below.
175
+ cmd = [
176
+ "llama-server",
177
+ "-m", "{model}",
178
+ "--alias", "{served_name}",
179
+ "--host", "127.0.0.1", "--port", "{port}",
180
+ "--jinja",
181
+ "--metrics",
182
+ "-np", "1",
183
+ "-ctk", "{kv_type}", "-ctv", "{kv_type}",
184
+ "-c", "{ctx}",
185
+ "--reasoning-format", "auto",
186
+ ]
187
+ port = 8080
188
+ model_is_path = true # check the GGUF exists and fits before starting
189
+ vision_args = ["--mmproj", "{mmproj}"] # appended only for models that set mmproj
190
+ kv_type = "f16" # a model's own `kv_type` (e.g. "q8_0") makes it a different model entry
191
+ max_ctx = 65536 # upper bound; the fit check lowers it per machine (see machines.py)
192
+ version_cmd = ["llama-server", "--version"] # recorded with every result
193
+ # Added to every request. cache_prompt = false: each answer is computed from scratch, so a repeat of
194
+ # the same question reuses nothing from the last time it was asked (and its TTFT is honest).
195
+ # Answers record any prompt tokens the server reused.
196
+ request = { cache_prompt = false }
197
+
198
+ # Speed-only flags, always applied (not tuned).
199
+ perf = ["-ngl", "99"]
200
+
201
+ # Speed-only knobs `tuieval tune` tries, one at a time from the best so far. The first option of
202
+ # each knob is the default for models not tuned on this machine yet. Placeholders: {p} P-cores,
203
+ # {p_minus_2}, {all} all cores. Options that stop the server from starting are skipped.
204
+ # Check the flags against `llama-server --help` for your build.
205
+ [servers.llama.tune]
206
+ threads = [["-t", "{p}", "-tb", "{p}"], ["-t", "{p_minus_2}", "-tb", "{p}"], ["-t", "{all}", "-tb", "{all}"]]
207
+ ubatch = [["-ub", "512"], ["-ub", "256"], ["-ub", "1024"]]
208
+ batch = [["-b", "2048"], ["-b", "4096"]]
209
+ flash_attn = [["-fa", "on"], ["-fa", "off"]]
210
+
211
+ # A server that's already running (LM Studio, Ollama, vLLM, a remote box): give its URL and no cmd.
212
+ # Its models are added with `tuieval add <model id> --server local`.
213
+ [servers.local]
214
+ url = "http://127.0.0.1:1234" # LM Studio's default; Ollama is http://127.0.0.1:11434, vLLM :8000
215
+
216
+ # OpenRouter: ANY model it serves, with no [[models]] entry. Name it when you run:
217
+ # tuieval run --tier smoke --only openrouter:qwen/qwen3-235b-a22b-2507
218
+ # TUI: tick "+ openrouter: any model" in the model list, or press "a" and type openrouter:<model id>
219
+ # Results go to results/or-<id>/ and the model stays listed once it has results. A plain
220
+ # `tuieval run` never includes OpenRouter models (they cost money); name them.
221
+ # Needs OPENROUTER_API_KEY in the environment of the shell that starts tuieval.
222
+ # pin_endpoint: every answer of a model comes from one provider endpoint (the closest to the released
223
+ # weights), with no fallback, since providers run different quantizations.
224
+ # To keep providers that store or train on prompts (and could learn your tests) out entirely, add
225
+ # request = { provider = { data_collection = "deny" } }
226
+ [servers.openrouter]
227
+ url = "https://openrouter.ai/api/v1"
228
+ any_model = true
229
+ label_prefix = "or"
230
+ pin_endpoint = true
231
+ api_key_env = "OPENROUTER_API_KEY"
232
+ thinking_param = "reasoning" # [sampling] enable_thinking -> OpenRouter's reasoning.enabled
233
+ headers = { "X-Title" = "tuieval" }
234
+ ```
235
+
236
+ `[defaults] detect_ports` (default `[1234, 11434, 8080, 8000]`, the usual LM Studio, Ollama, llama-server and vLLM ports) is where `tuieval add`, `tuieval init` and `tuieval doctor` look for servers that are already running. `TUIEVAL_DETECT_PORTS` (comma-separated; empty means none) overrides it.
237
+
238
+ Optional sections:
239
+
240
+ ```toml
241
+ # Optional tuner settings.
242
+ # [tune]
243
+ # guard_similarity = 0.6 # greedy answers under a candidate flag must stay this similar to the defaults'
244
+ # request_timeout_s = 600 # a tuning request that takes longer fails that candidate
245
+ # warm_start = true # start from a tuned model with the same architecture and shapes (a fine-tune)
246
+ # warm_retest = ["spec", "ubatch"] # the knobs a warm start re-tries; the rest are inherited
247
+ # answer_tokens = 1024 # answer length options are scored for (projected objective)
248
+ # swap_limit_mb = 500 # reject options that swap more than this beyond the defaults (default: report only)
249
+
250
+ # Optional: llama-bench for the tuner's fast first stage (found on PATH as llama-bench otherwise).
251
+ # [bench]
252
+ # cmd = ["~/llama.cpp/build/bin/llama-bench"]
253
+
254
+ # Optional per-machine settings, by machine id (tuieval machines shows the ids).
255
+ # [machines.m3max-64gb]
256
+ # memory_headroom_gb = 3 # GPU memory kept free for macOS and other apps (default 4)
257
+ # gpu_residency_gb = 12 # GPU memory the driver keeps resident before churning (default: half of RAM)
258
+ ```
259
+
@@ -10,6 +10,11 @@ tuieval new-pack support-bot --grader reply # answer | rag | reply | tool_c
10
10
 
11
11
  Edit `packs/support-bot/tests.yaml`, replace the examples with your own questions, and run `tuieval selftest support-bot` after every change.
12
12
 
13
+ Two shortcuts to a first set of real questions:
14
+
15
+ - **From a spreadsheet:** `tuieval new-pack quiz --from questions.csv` makes one test per row from a `question` and an `answer` column (optional: `id`, `category`, `difficulty`, `tolerance`, and `wrong` with known-wrong answers separated by `|`). Numbers are checked with the tolerance, words case-insensitively, and `NOT_AVAILABLE` means the right answer is "the question doesn't say".
16
+ - **Drafted by a model:** `tuieval new-pack support-bot --grader reply --about "refund questions for a shoe store"` also writes `DRAFT-PROMPT.md`. Paste it into the strongest model you have, save the YAML it writes as `tests.yaml`, and run `tuieval selftest`: it catches drafted tests whose expected answer is wrong or whose grader can't tell a mistake from the right answer. Read the questions anyway; you know your domain better than the model.
17
+
13
18
  To try a different set of questions, copy a pack (`cp -r packs/support-bot packs/support-bot-v2`), edit the copy, and pick whichever you want in the TUI.
14
19
 
15
20
  ## Fingerprints
@@ -55,6 +60,7 @@ min_consistency = 0.95 # share of variant groups where every variant p
55
60
  input: | # the user message
56
61
  A customer bought shoes 20 days ago …
57
62
  image: images/receipt.png # optional: sent as an image (needs vision)
63
+ input_file: prompts/refund.txt # optional, instead of input: the user message from a file in the pack
58
64
  grader: answer # optional: override the pack's grader
59
65
  group: refund-window-2 # optional: variants of one case share a group (consistency gate)
60
66
  critical: true # optional: any failure of this test is critical (disqualifying)
@@ -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.dev14'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev14')
21
+ __version__ = version = '0.2.0.dev23'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev23')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,83 @@
1
+ # Built-in defaults and servers, used when a workspace's models.toml doesn't define them.
2
+ # [defaults] fill in key by key; [sampling] only when models.toml has no [sampling] at all (it's part of
3
+ # every result's fingerprint); a server here is replaced whole by one of the same name in models.toml.
4
+
5
+ [defaults]
6
+ repeat = 3
7
+ request_timeout_ms = 1800000 # per request; long thinking runs need it
8
+ ready_timeout_s = 1200 # how long a model may take to load before giving up
9
+ model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
10
+
11
+ # Sent with every request. A model may override it (see `thinking` / `sampling` below); results
12
+ # record the settings each model actually ran with, and the scorecard shows any difference.
13
+ [sampling]
14
+ temperature = 0.7
15
+ top_p = 0.95
16
+ top_k = 20
17
+ min_p = 0.0
18
+ max_tokens = 16384
19
+ enable_thinking = true # test every model in the SAME mode
20
+
21
+ # llama.cpp's server (https://github.com/ggml-org/llama.cpp), started for each GGUF model.
22
+ [servers.llama]
23
+ # Output-affecting flags only. Anything here must be identical wherever results are compared,
24
+ # so it's part of each result's fingerprint. Speed-only flags live in `perf` and `tune` below.
25
+ cmd = [
26
+ "llama-server",
27
+ "-m", "{model}",
28
+ "--alias", "{served_name}",
29
+ "--host", "127.0.0.1", "--port", "{port}",
30
+ "--jinja",
31
+ "--metrics",
32
+ "-np", "1",
33
+ "-ctk", "{kv_type}", "-ctv", "{kv_type}",
34
+ "-c", "{ctx}",
35
+ "--reasoning-format", "auto",
36
+ ]
37
+ port = 8080
38
+ model_is_path = true # check the GGUF exists and fits before starting
39
+ vision_args = ["--mmproj", "{mmproj}"] # appended only for models that set mmproj
40
+ kv_type = "f16" # a model's own `kv_type` (e.g. "q8_0") makes it a different model entry
41
+ max_ctx = 65536 # upper bound; the fit check lowers it per machine (see machines.py)
42
+ version_cmd = ["llama-server", "--version"] # recorded with every result
43
+ # Added to every request. cache_prompt = false: each answer is computed from scratch, so a repeat of
44
+ # the same question reuses nothing from the last time it was asked (and its TTFT is honest).
45
+ # Answers record any prompt tokens the server reused.
46
+ request = { cache_prompt = false }
47
+
48
+ # Speed-only flags, always applied (not tuned).
49
+ perf = ["-ngl", "99"]
50
+
51
+ # Speed-only knobs `tuieval tune` tries, one at a time from the best so far. The first option of
52
+ # each knob is the default for models not tuned on this machine yet. Placeholders: {p} P-cores,
53
+ # {p_minus_2}, {all} all cores. Options that stop the server from starting are skipped.
54
+ # Check the flags against `llama-server --help` for your build.
55
+ [servers.llama.tune]
56
+ threads = [["-t", "{p}", "-tb", "{p}"], ["-t", "{p_minus_2}", "-tb", "{p}"], ["-t", "{all}", "-tb", "{all}"]]
57
+ ubatch = [["-ub", "512"], ["-ub", "256"], ["-ub", "1024"]]
58
+ batch = [["-b", "2048"], ["-b", "4096"]]
59
+ flash_attn = [["-fa", "on"], ["-fa", "off"]]
60
+
61
+ # A server that's already running (LM Studio, Ollama, vLLM, a remote box): give its URL and no cmd.
62
+ # Its models are added with `tuieval add <model id> --server local`.
63
+ [servers.local]
64
+ url = "http://127.0.0.1:1234" # LM Studio's default; Ollama is http://127.0.0.1:11434, vLLM :8000
65
+
66
+ # OpenRouter: ANY model it serves, with no [[models]] entry. Name it when you run:
67
+ # tuieval run --tier smoke --only openrouter:qwen/qwen3-235b-a22b-2507
68
+ # TUI: tick "+ openrouter: any model" in the model list, or press "a" and type openrouter:<model id>
69
+ # Results go to results/or-<id>/ and the model stays listed once it has results. A plain
70
+ # `tuieval run` never includes OpenRouter models (they cost money); name them.
71
+ # Needs OPENROUTER_API_KEY in the environment of the shell that starts tuieval.
72
+ # pin_endpoint: every answer of a model comes from one provider endpoint (the closest to the released
73
+ # weights), with no fallback, since providers run different quantizations.
74
+ # To keep providers that store or train on prompts (and could learn your tests) out entirely, add
75
+ # request = { provider = { data_collection = "deny" } }
76
+ [servers.openrouter]
77
+ url = "https://openrouter.ai/api/v1"
78
+ any_model = true
79
+ label_prefix = "or"
80
+ pin_endpoint = true
81
+ api_key_env = "OPENROUTER_API_KEY"
82
+ thinking_param = "reasoning" # [sampling] enable_thinking -> OpenRouter's reasoning.enabled
83
+ headers = { "X-Title" = "tuieval" }
@@ -2,28 +2,33 @@
2
2
 
3
3
  Usage: tuieval [--workspace DIR] [command] [options]
4
4
 
5
- tuieval open the TUI
6
- tuieval init [DIR] create a workspace (models.toml, packs/, graders/)
7
- tuieval new-pack NAME a new pack from a starter template (--grader answer|rag|reply|tool_call|code)
8
- tuieval add PATH|ID register a model in models.toml (a GGUF, or a model id)
9
- tuieval scan list GGUFs in model_dirs that aren't in models.toml yet
10
- tuieval list the models tuieval knows, hidden ones separately (--packs: the packs)
11
- tuieval remove MODEL remove models, or every hidden one (--hidden); archived in removed/
5
+ Start here
6
+ tuieval init [DIR] create a workspace; in a terminal, a guided setup to a first result
7
+ tuieval open the TUI (? explains any screen)
8
+ tuieval doctor check servers, models, packs and API keys, and say what to fix
9
+ tuieval add [PATH|ID] add a model: a GGUF, a model id, or none to pick from running servers
10
+ tuieval new-pack NAME a new pack of example questions (--from CSV, --about TOPIC, --grader …)
11
+ tuieval selftest check every test's reference and wrong answers against its grader
12
12
  tuieval run [...] run evals from the command line (tuieval run --help)
13
+ tuieval verdict which model is ready for what, and what to run next
14
+
15
+ Day to day
13
16
  tuieval compare [...] scorecards (--speed, --pairwise, --failures)
14
- tuieval regrade re-score stored answers after changing a grader
15
- tuieval verdict production readiness (PASS/FAIL per model and use case)
16
- tuieval report the readiness verdicts with their evidence, as a markdown file
17
+ tuieval report the verdicts with their evidence, as a markdown file
17
18
  tuieval history every verdict change, newest first
18
- tuieval selftest check every test's reference and wrong answers against its grader
19
19
  tuieval items tests that don't separate models or look broken
20
20
  tuieval capture LOG --pack turn a real failure (logs/live/*.txt) into a new test
21
+ tuieval regrade re-score stored answers after changing a grader
22
+ tuieval list the models tuieval knows, hidden ones separately (--packs: the packs)
23
+ tuieval remove MODEL remove models, or every hidden one (--hidden); archived in removed/
24
+
25
+ Advanced
26
+ tuieval scan list GGUFs in model_dirs that aren't in models.toml yet
21
27
  tuieval machines this machine, other recorded machines, and fit and tuning per model
22
28
  tuieval tune MODEL find the fastest speed-only server flags for a model on this machine
23
29
  tuieval export pi MODEL serve a model in the pi coding agent with its tuned flags
24
30
  tuieval watch a proxy that shows the reasoning of any app using your server
25
- tuieval help this help
26
- tuieval --version
31
+ tuieval help · --version
27
32
 
28
33
  The workspace is --workspace DIR, else $TUIEVAL_HOME, else the current folder.
29
34
  """
@@ -67,6 +72,9 @@ def main(argv=None):
67
72
  if not workspace.is_workspace():
68
73
  sys.exit(f"{workspace.root()} isn't a tuieval workspace; run `tuieval init` first")
69
74
  return scaffold.cmd_new_pack(rest)
75
+ if cmd == "doctor":
76
+ from . import doctor
77
+ return doctor.cmd_doctor(rest)
70
78
  if cmd == "run":
71
79
  from . import run_evals
72
80
  return run_evals.main(rest)
@@ -0,0 +1,131 @@
1
+ """tuieval doctor: checks the setup and says what to fix (servers, models, packs, API keys).
2
+
3
+ tuieval doctor
4
+
5
+ Each line is ✓ (fine), ! (worth knowing) or ✗ (a run would fail), with the fix. Exits 1 if any ✗.
6
+ """
7
+ import argparse
8
+ import os
9
+ import platform
10
+ import sys
11
+
12
+ from . import engine
13
+ from . import workspace
14
+
15
+ OK, WARN, BAD = "✓", "!", "✗"
16
+ COLOR = {OK: "\033[32m", WARN: "\033[33m", BAD: "\033[31m"}
17
+
18
+
19
+ class Report:
20
+ def __init__(self):
21
+ self.bad = 0
22
+ self.color = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
23
+
24
+ def line(self, mark, text, fix=""):
25
+ self.bad += mark == BAD
26
+ m = f"{COLOR[mark]}{mark}\033[0m" if self.color else mark
27
+ print(f" {m} {text}" + (f"\n → {fix}" if fix else ""))
28
+
29
+ def head(self, text):
30
+ print(f"\n{text}")
31
+
32
+
33
+ def check(models_path=None):
34
+ """Print the report; returns the number of problems that would make a run fail."""
35
+ r = Report()
36
+ r.head("tuieval")
37
+ py = platform.python_version()
38
+ r.line(OK if sys.version_info >= (3, 11) else BAD, f"Python {py}", "" if sys.version_info >= (3, 11)
39
+ else "tuieval needs Python 3.11 or newer")
40
+ try:
41
+ e = engine.Engine(models_path=models_path)
42
+ except Exception as ex: # a broken models.toml: nothing else can be checked
43
+ r.line(BAD, f"models.toml in {workspace.root()}: {ex}", "fix the file (tuieval init makes a fresh one elsewhere)")
44
+ return r.bad
45
+ r.line(OK, f"workspace {e.root}")
46
+
47
+ r.head("Packs")
48
+ for err in e.pack_errors:
49
+ r.line(BAD, err, "fix the pack, then: tuieval selftest")
50
+ if e.packs:
51
+ tests = sum(len(p.tests) for p in e.packs.values())
52
+ r.line(OK, f"{len(e.packs)} pack(s), {tests} tests (tuieval selftest checks their answers)")
53
+ elif not e.pack_errors:
54
+ r.line(BAD, "no packs yet", "tuieval new-pack my-first-pack (a pack of example questions to edit)")
55
+
56
+ r.head("Servers")
57
+ found = engine.detect_servers(engine.detect_ports(e.cfg))
58
+ used = {m["server"] for m in e.cfg["models"]}
59
+ answering = {}
60
+ for name, server in e.cfg["servers"].items():
61
+ if server.get("any_model") or server.get("api_key_env"):
62
+ key = server.get("api_key_env")
63
+ if key and os.environ.get(key):
64
+ r.line(OK, f"{name}: {key} is set")
65
+ elif key:
66
+ r.line(WARN, f"{name}: {key} isn't set (needed only for {name} models)",
67
+ f"export {key}=… in the shell you start tuieval from")
68
+ continue
69
+ if server.get("cmd"):
70
+ missing = engine.missing_program([str(a) for a in server["cmd"]],
71
+ engine.expand(server["cwd"]) if server.get("cwd") else None)
72
+ if missing:
73
+ r.line(BAD if name in used else WARN,
74
+ f"{name}: {missing} isn't installed" + ("" if name in used else " (no model uses it yet)"),
75
+ engine.install_hint(missing))
76
+ else:
77
+ r.line(OK, f"{name}: {server['cmd'][0]} is installed; tuieval starts it for each model")
78
+ continue
79
+ url = server.get("url", "").rstrip("/").removesuffix("/v1")
80
+ ids = engine.probe_models(url) if url else None
81
+ answering[name] = ids
82
+ if ids is not None:
83
+ r.line(OK, f"{name}: {url} answers, serving {len(ids)} model(s)" + (f": {', '.join(ids[:5])}" if ids else ""))
84
+ else:
85
+ elsewhere = [f for f in found if f["url"] != url]
86
+ fix = (f"start the server at {url}, or "
87
+ + ("run `tuieval add` to add the models of the one running at " + ", ".join(
88
+ f"{f['url']}" + (f" ({f['usual']})" if f["usual"] else "") for f in elsewhere)
89
+ if elsewhere else engine.url_fix(e.cfg, name)))
90
+ r.line(BAD if name in used else WARN,
91
+ f"{name}: nothing answers at {url}" + ("" if name in used else " (no model uses it yet)"), fix)
92
+ known = {s.get("url", "").rstrip("/").removesuffix("/v1") for s in e.cfg["servers"].values()}
93
+ for f in found:
94
+ if f["url"] not in known:
95
+ r.line(WARN, f"found a server at {f['url']}" + (f" ({f['usual']}'s usual port)" if f["usual"] else "")
96
+ + f" serving {len(f['models'])} model(s), not in models.toml",
97
+ "add its models with: tuieval add")
98
+
99
+ r.head("Models")
100
+ if not e.cfg["models"]:
101
+ r.line(BAD, "no models yet", "tuieval add <GGUF path or model id>, or press a in the TUI")
102
+ for m in e.cfg["models"]:
103
+ server = e.cfg["servers"][m["server"]]
104
+ label = f"{m['label']} ({m['server']})"
105
+ if server.get("model_is_path"):
106
+ path = engine.expand(m["model"])
107
+ r.line(OK if os.path.exists(path) else BAD, f"{label}: {path}",
108
+ "" if os.path.exists(path) else "the file is gone; fix `model` in models.toml or tuieval remove it")
109
+ elif m["server"] in answering:
110
+ ids = answering[m["server"]]
111
+ if ids is None:
112
+ r.line(BAD, f"{label}: its server isn't running", "see Servers above")
113
+ elif engine.serves(ids, m["served_name"]):
114
+ r.line(OK, f"{label}: served")
115
+ else:
116
+ r.line(BAD, f"{label}: the server doesn't list {m['served_name']!r}",
117
+ f"load it in the server, or use one of: {', '.join(ids[:8]) or 'none listed'}")
118
+ else:
119
+ r.line(OK, label)
120
+
121
+ print("\n" + ("Ready: open the TUI with `tuieval`, or run `tuieval run --tier smoke`." if not r.bad
122
+ else f"{r.bad} problem(s) to fix before a run (✗ above)."))
123
+ return r.bad
124
+
125
+
126
+ def cmd_doctor(argv):
127
+ p = argparse.ArgumentParser(prog="tuieval doctor", description=__doc__,
128
+ formatter_class=argparse.RawDescriptionHelpFormatter)
129
+ p.add_argument("--models", default=None, help="default: the workspace's models.toml")
130
+ a = p.parse_args(argv)
131
+ sys.exit(1 if check(a.models) else 0)