evals-lab 0.11.0 → 0.13.0

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 (62) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +4 -4
  3. package/bin/evals-lab.js +9 -1
  4. package/bin/lab-file.js +97 -0
  5. package/bin/run.js +1 -1
  6. package/lab/VERSION +1 -1
  7. package/lab/demo/CREDITS.md +37 -3
  8. package/lab/demo/datasets/demo-1.json +2 -2
  9. package/lab/demo/datasets/demo-2.json +2 -2
  10. package/lab/demo/datasets/sentiment.json +383 -0
  11. package/lab/demo/datasets/weather-alerts.json +61 -0
  12. package/lab/demo/datasets/wordings.json +39 -0
  13. package/lab/demo/manifest.json +3 -3
  14. package/lab/demo/pipelines/demo-1.json +4 -4
  15. package/lab/demo/pipelines/demo-2.json +4 -4
  16. package/lab/demo/pipelines/sentiment.json +65 -0
  17. package/lab/demo/pipelines/weather-alerts.json +66 -0
  18. package/lab/demo/pipelines/wordings.json +104 -0
  19. package/lab/demo/sources/Demo 1/source.json +3 -0
  20. package/lab/demo/sources/Demo 2/source.json +3 -0
  21. package/lab/demo/sources/sentiment/bakery-croissant.txt +1 -0
  22. package/lab/demo/sources/sentiment/bus-timetable.txt +1 -0
  23. package/lab/demo/sources/sentiment/cafe-coffee.txt +1 -0
  24. package/lab/demo/sources/sentiment/concert-night.txt +1 -0
  25. package/lab/demo/sources/sentiment/film-sequel.txt +1 -0
  26. package/lab/demo/sources/sentiment/garden-tomatoes.txt +1 -0
  27. package/lab/demo/sources/sentiment/gym-contract.txt +1 -0
  28. package/lab/demo/sources/sentiment/headphones-review.txt +1 -0
  29. package/lab/demo/sources/sentiment/hotel-room.txt +1 -0
  30. package/lab/demo/sources/sentiment/library-hours.txt +1 -0
  31. package/lab/demo/sources/sentiment/meeting-minutes.txt +1 -0
  32. package/lab/demo/sources/sentiment/parcel-lost.txt +1 -0
  33. package/lab/demo/sources/sentiment/phone-update.txt +1 -0
  34. package/lab/demo/sources/sentiment/puppy-home.txt +1 -0
  35. package/lab/demo/sources/sentiment/recipe-note.txt +1 -0
  36. package/lab/demo/sources/sentiment/road-closure.txt +1 -0
  37. package/lab/demo/sources/sentiment/source.json +3 -0
  38. package/lab/demo/sources/sentiment/teacher-thanks.txt +1 -0
  39. package/lab/demo/sources/sentiment/train-delay.txt +1 -0
  40. package/lab/demo/sources/sentiment/volunteer-day.txt +1 -0
  41. package/lab/demo/sources/sentiment/weather-report.txt +1 -0
  42. package/lab/demo/sources/weather-alerts/https-api.weather.gov-alerts-types.txt +116 -0
  43. package/lab/demo/sources/weather-alerts/source.json +8 -0
  44. package/lab/evals-core.mjs +246 -57
  45. package/lab/flows/wdl.mjs +19 -0
  46. package/lab/kinds/list.mjs +5 -2
  47. package/lab/metrics/builtin.mjs +34 -7
  48. package/lab/run-evals.js +30 -11
  49. package/lab/server.py +614 -75
  50. package/lab/web/dist/assets/gallery-DpaqOg9Z.js +5 -0
  51. package/lab/web/dist/assets/main-BSw-jyYD.css +1 -0
  52. package/lab/web/dist/assets/main-CrB8wPZn.js +17 -0
  53. package/lab/web/dist/assets/tokens-CrjDMCbz.js +60 -0
  54. package/lab/web/dist/assets/tokens-DDOONQon.css +1 -0
  55. package/lab/web/dist/gallery.html +3 -3
  56. package/lab/web/dist/index.html +4 -4
  57. package/package.json +1 -1
  58. package/lab/web/dist/assets/gallery-DKy8QK3o.js +0 -5
  59. package/lab/web/dist/assets/main-BGmSu7aS.css +0 -1
  60. package/lab/web/dist/assets/main-DdGON-yl.js +0 -16
  61. package/lab/web/dist/assets/tokens-BmytrwxE.js +0 -60
  62. package/lab/web/dist/assets/tokens-DBN7I0ru.css +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,102 @@ Newest first. Read a release's **Upgrade notes** before installing it: an
5
5
  upgrade can rewrite what the lab keeps in your data directory, and an older
6
6
  version cannot always read it back.
7
7
 
8
+ ## 0.13.0
9
+
10
+ ### Upgrade notes
11
+
12
+ - Pipelines and datasets keep their versions (17 and 9), so nothing in your
13
+ data directory is rewritten. A pipeline that uses the new Refused metric
14
+ needs 0.13.0 to grade it.
15
+
16
+ ### Added
17
+
18
+ - A Refused metric, for a case that expects the target's call to be refused.
19
+ - A PDF item goes to the model whole, as a document; a connection that can't
20
+ read one is refused before the run.
21
+ - AI Connections and Connection Profiles select several rows to remove at
22
+ once, with one Undo; Connection Profiles show their connection.
23
+ - Copy or move… on an eval group's, a filter set's and a Source's own page.
24
+
25
+ ### Changed
26
+
27
+ - One date format everywhere, and Home lists recent runs.
28
+ - Dropdowns that name things read A–Z; Model, Environment and Flow open the
29
+ Switcher.
30
+ - Empty values read as a short parenthetical, such as (No grader), and
31
+ explanations are shorter.
32
+ - A run's jobs sit in one Jobs group, with Add job inside it.
33
+ - A Supplied Source takes several files in one Upload.
34
+ - Enter in a dialog's field presses its main button; in Run settings it
35
+ leaves the field.
36
+ - Stop reads Stopping… until the run ends, and History can stop a run.
37
+
38
+ ### Fixed
39
+
40
+ - An errored result fails on its error and is not graded.
41
+ - Metrics and the grader read the reply after its Pre-format filters, and
42
+ Results shows that reply.
43
+ - A linked group whose grader is gone can't run, and Results says why a
44
+ graded metric never ran; an off-scale grader score is the grader's error.
45
+ - An Edited target whose expression doesn't parse is refused before the run.
46
+ - Pin names the newest version the server holds.
47
+ - Demo targets answer on a connection the workspace can use.
48
+ - Loop repeats a live capture, and the Comment stays with its pipeline.
49
+ - A Re-sync that needed a Microsoft sign-in runs once the page is back.
50
+ - A dynamic Source's fetch names the lab as its User-Agent.
51
+ - Emptying the trash leaves a connection shared only with the purged
52
+ workspace shared with none, not All; a lab import's rollback leaves
53
+ another import's connection intact.
54
+ - A stored Echo connection is no longer listed beside the built-in Echo
55
+ target.
56
+
57
+ ## 0.12.0
58
+
59
+ ### Upgrade notes
60
+
61
+ - **Copy your data directory before upgrading** (see "User data" in the
62
+ README). A pipeline 0.12.0 saves is pipeline version 17, which 0.11.x
63
+ cannot read. To go back, reinstall 0.11.0 and restore the copy.
64
+ - An HTTP Request step no longer picks an API of its own: it uses its job's
65
+ flow step and connection. A step that named a different API keeps the
66
+ request it would have sent, and a request in a job with no flow step
67
+ becomes a Prompt keeping its words. Pipelines read as version 17, and are
68
+ saved as it at their next edit.
69
+
70
+ ### Added
71
+
72
+ - `evals-lab export --lab <url>` writes a lab's settings to a file:
73
+ workspaces, connections with who each is shared with, profiles and the
74
+ grader, never a key. `--workspace <slug>` exports one workspace's.
75
+ `evals-lab import <file> --lab <url>` adds what the lab lacks, giving each
76
+ connection the key in `$EVALSLAB_API_KEY_<SLUG>`, and sends keys only to
77
+ an https or loopback lab.
78
+ - `LAB_IMPORT=<file>` imports a lab file each time the lab starts.
79
+ - A new lab opens on five demos: describe images (twice), classify
80
+ sentiment, compare wordings, and sort weather alerts from a live HTTP GET
81
+ Source. A pack can now carry a dynamic Source.
82
+ - Create test… on a dynamic Source's Calls makes a dataset that is graded
83
+ and ready to run.
84
+
85
+ ### Changed
86
+
87
+ - A new lab starts in about half the time.
88
+ - Names, slugs and model ids get the full width of their field, the tables
89
+ in one dialog share one label column, and Undo is amber and labelled.
90
+ - A dynamic Source names its provider once, and the grader picker says
91
+ Connection.
92
+
93
+ ### Fixed
94
+
95
+ - An API call Source sends an HTTP endpoint connection's key in the header
96
+ the connection names, not always `Authorization: Bearer`.
97
+ - An API call Source can use only a connection shared with its workspace.
98
+ - A run is refused when two connections take their key from the same
99
+ variable, on a re-run too.
100
+ - Seeding from production reads a gateway flow's replies in its
101
+ connection's request format.
102
+ - The Runs stop keeps its icon on Home.
103
+
8
104
  ## 0.11.0
9
105
 
10
106
  ### Upgrade notes
package/README.md CHANGED
@@ -45,8 +45,8 @@ npm install -g evals-lab
45
45
  evals-lab
46
46
  ```
47
47
 
48
- Open <http://localhost:8080>. A new lab starts with two demo datasets and
49
- their pipelines. Note the first start takes longer while it installs them. Without
48
+ Open <http://localhost:8080>. A new lab starts with five demos, each a dataset
49
+ and its pipeline, which run once you add an AI Connection in Setup. Note the first start takes longer while it installs them. Without
50
50
  installing: `npx evals-lab`.
51
51
 
52
52
  | Needs | Windows | Linux |
@@ -164,7 +164,7 @@ CI can run a lab's own pipeline there instead, so the run shows in that lab's
164
164
  History and its keys never leave it:
165
165
 
166
166
  ```bash
167
- LAB_PASSWORD=... evals-lab run --lab https://lab.example --pipeline "Demo 1" --wait
167
+ LAB_PASSWORD=... evals-lab run --lab https://lab.example --pipeline "Demo: describe images" --wait
168
168
  ```
169
169
 
170
170
  ```text
@@ -249,5 +249,5 @@ Copyright 2026 Grainbox Limited, under the
249
249
  [PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0)
250
250
  (`LICENSE.md`): you and your company may use and change it for your own
251
251
  internal business, but not give it, or a changed copy, to anyone else, sell
252
- it, or offer it as a service. The demo images are public domain; each is
252
+ it, or offer it as a service. The demo files are public domain; each is
253
253
  credited in `lab/demo/CREDITS.md`.
package/bin/evals-lab.js CHANGED
@@ -30,6 +30,8 @@ const LOCAL_OLLAMA = "http://127.0.0.1:11434";
30
30
  const USAGE = `Usage: evals-lab [options]
31
31
  evals-lab run <bundle> [options] (evals-lab run --help)
32
32
  evals-lab run --lab <url> --pipeline <name> [--wait] [options]
33
+ evals-lab export --lab <url> [--workspace <slug>] [--out <file>]
34
+ evals-lab import <file> --lab <url> (evals-lab import --help)
33
35
 
34
36
  --port <n> the port to listen on. Default: $PORT, else 8080.
35
37
  --host <address> the address to listen on. Default: $LAB_HOST, else
@@ -43,7 +45,9 @@ Environment: LAB_PASSWORD puts a password in front of the lab, OLLAMA_URL is
43
45
  the connection a new lab starts with, and PYTHON names the Python to run.
44
46
  SOURCE_ALLOW_LOCAL_FILES=1 and SOURCE_ALLOW_LOCAL_COMMANDS=1 let a Source read a
45
47
  file or run a command on this machine; both are off unless set.
46
- SOURCE_LOCAL_ROOT=<dir> lets a file Source read only under that directory.`;
48
+ SOURCE_LOCAL_ROOT=<dir> lets a file Source read only under that directory.
49
+ LAB_IMPORT=<file> imports a lab file each time the lab starts, each
50
+ connection's key from $EVALSLAB_API_KEY_<SLUG>.`;
47
51
 
48
52
  // Where a lab keeps its store when nobody said: the platform's per-user data
49
53
  // directory, so it survives the package being upgraded or reinstalled.
@@ -159,6 +163,10 @@ function fail(message) {
159
163
  }
160
164
 
161
165
  async function main() {
166
+ if (process.argv[2] === "export" || process.argv[2] === "import") {
167
+ process.exitCode = await require("./lab-file.js").labFile(process.argv[2], process.argv.slice(3));
168
+ return;
169
+ }
162
170
  if (process.argv[2] === "run") {
163
171
  process.exitCode = await require("./run.js").run(process.argv.slice(3), { lab: LAB });
164
172
  return;
@@ -0,0 +1,97 @@
1
+ // `evals-lab export` and `evals-lab import` (docs/lab-export.md): a running
2
+ // lab's settings -- its AI Connections, Connection Profiles, grader and
3
+ // workspaces -- as a lab file, and a lab file into a lab, adding what it
4
+ // lacks. Setup › Workspaces › Data does the same through the same two routes.
5
+ //
6
+ // THE KEYS. A lab file holds none, and the lab serves none. `import` sends
7
+ // each connection's from $EVALSLAB_API_KEY_<SLUG>, as a CI run reads it, and
8
+ // only to an https or loopback lab: anything else would put the key on the
9
+ // wire in the clear.
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { labApi, Refused } = require("./run.js");
13
+
14
+ const EXIT = { broken: 3 };
15
+
16
+ const USAGE = `Usage: evals-lab export --lab <url> [--workspace <slug>] [--out <file>]
17
+ evals-lab import <file> --lab <url>
18
+
19
+ --lab <url> the running lab. LAB_PASSWORD is sent when it is set.
20
+ --workspace <slug> export only the workspace at /w/<slug>. Default: every one.
21
+ --out <file> where the export goes. Default: stdout.
22
+
23
+ A lab file holds no key. import gives each connection the key in
24
+ $EVALSLAB_API_KEY_<SLUG> when it is set, and sends keys only to an https or
25
+ loopback lab. An import only adds: what the lab has already is left as it is.`;
26
+
27
+ function options(argv, verb) {
28
+ const o = {};
29
+ const value = { "--lab": "lab", "--workspace": "workspace", "--out": "out" };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ const a = argv[i];
32
+ if (a === "--help" || a === "-h") o.help = true;
33
+ else if (value[a]) {
34
+ if (argv[i + 1] === undefined) throw new Refused(`${a} needs a value`, true);
35
+ o[value[a]] = argv[++i];
36
+ } else if (a.startsWith("-")) throw new Refused(`no option ${a}`, true);
37
+ else if (verb === "import" && o.file === undefined) o.file = a;
38
+ else throw new Refused(`one lab file, not ${a} as well`, true);
39
+ }
40
+ if (o.help) return o;
41
+ if (o.lab == null) throw new Refused("name the lab with --lab", true);
42
+ if (!/^https?:\/\/[^/]/i.test(o.lab)) throw new Refused(`--lab ${o.lab} is not an http or https address`, true);
43
+ if (verb === "import") {
44
+ if (o.file == null) throw new Refused("name the lab file to import", true);
45
+ if (o.workspace != null || o.out != null) throw new Refused("--workspace and --out are export's", true);
46
+ }
47
+ return o;
48
+ }
49
+
50
+ const keyVar = (id, slug) => "EVALSLAB_API_KEY_"
51
+ + String(slug || id).toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
52
+
53
+ // Loopback or https: where a key may go.
54
+ function keySafe(base) {
55
+ const u = new URL(base);
56
+ return u.protocol === "https:" || ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname);
57
+ }
58
+
59
+ async function labFile(verb, argv, { env = process.env, stdout = process.stdout, stderr = process.stderr } = {}) {
60
+ const say = text => stderr.write(text + "\n");
61
+ try {
62
+ const o = options(argv, verb);
63
+ if (o.help) { stdout.write(USAGE + "\n"); return 0; }
64
+ const base = o.lab.replace(/\/+$/, "");
65
+ const api = labApi(base, env);
66
+ if (verb === "export") {
67
+ const out = await api("GET", "/api/lab-export" + (o.workspace ? `?workspace=${encodeURIComponent(o.workspace)}` : ""));
68
+ const text = JSON.stringify(out, null, 2) + "\n";
69
+ if (o.out) fs.writeFileSync(path.resolve(o.out), text);
70
+ else stdout.write(text);
71
+ if (o.out) say(`${out.connections.length} connections, ${out.workspaces.length} workspaces to ${o.out}`);
72
+ return 0;
73
+ }
74
+ let lab;
75
+ try { lab = JSON.parse(fs.readFileSync(path.resolve(o.file), "utf8")); }
76
+ catch (e) { throw new Refused(`${o.file} is not a lab file: ${e.message}`); }
77
+ const keys = {};
78
+ for (const c of Array.isArray(lab?.connections) ? lab.connections : []) {
79
+ const name = keyVar(c?.id, c?.slug);
80
+ if (env[name]) keys[name] = env[name];
81
+ }
82
+ if (Object.keys(keys).length && !keySafe(base)) {
83
+ throw new Refused(`${base} is neither https nor loopback, so no key goes to it: `
84
+ + `unset ${Object.keys(keys).join(", ")} to import without them`);
85
+ }
86
+ const { said } = await api("POST", "/api/lab-import", { lab, keys });
87
+ for (const line of said) say(line);
88
+ return 0;
89
+ } catch (e) {
90
+ if (!(e instanceof Refused)) throw e;
91
+ say(`evals-lab ${verb}: ${e.message}`);
92
+ if (e.usage) say(`\n${USAGE}`);
93
+ return EXIT.broken;
94
+ }
95
+ }
96
+
97
+ module.exports = { labFile, options, keyVar, keySafe, USAGE };
package/bin/run.js CHANGED
@@ -573,4 +573,4 @@ async function run(argv, { lab, env = process.env, stdout = process.stdout, stde
573
573
  }
574
574
  }
575
575
 
576
- module.exports = { run, runOptions, USAGE };
576
+ module.exports = { run, runOptions, USAGE, labApi, Refused };
package/lab/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.0 (2026.10.11-671)
1
+ 0.13.0 (2026.10.13-794)
@@ -1,9 +1,13 @@
1
1
  # Credits
2
2
 
3
- Every file in `images/`, where it came from and under what terms. All
3
+ Every file in `sources/`, where it came from and under what terms. All
4
4
  need no attribution; the sources are listed so each one can be traced.
5
- Files were resized to about 3 megapixels and their metadata stripped;
6
- the panorama is a crop, and the PNG and TIFF are conversions.
5
+
6
+ ## Images
7
+
8
+ The images of `Demo 1/` and `Demo 2/`. Files were resized to about 3
9
+ megapixels and their metadata stripped; the panorama is a crop, and the PNG
10
+ and TIFF are conversions.
7
11
 
8
12
  | File | Source | Author | Licence |
9
13
  |---|---|---|---|
@@ -90,3 +94,33 @@ the panorama is a crop, and the PNG and TIFF are conversions.
90
94
  | `whiteboard-meeting-notes.jpg` | https://www.flickr.com/photo.gne?id=186873704 | | Public domain |
91
95
  | `whiteboard-messages.jpg` | https://www.flickr.com/photo.gne?id=23162308061 | | Public domain |
92
96
  | `wooden-house-fence.jpg` | https://www.flickr.com/photo.gne?id=26595748497 | | Public domain |
97
+
98
+ ## Texts
99
+
100
+ The short texts of `sentiment/`, written for this pack, and the alert
101
+ types of `weather-alerts/`, fetched from the National Weather Service's API
102
+ on 2026-10-11. A work of the US federal government is in the public domain.
103
+
104
+ | File | Source | Author | Licence |
105
+ |---|---|---|---|
106
+ | `bakery-croissant.txt` | Written for this pack | Evals Lab | Public domain |
107
+ | `bus-timetable.txt` | Written for this pack | Evals Lab | Public domain |
108
+ | `cafe-coffee.txt` | Written for this pack | Evals Lab | Public domain |
109
+ | `concert-night.txt` | Written for this pack | Evals Lab | Public domain |
110
+ | `film-sequel.txt` | Written for this pack | Evals Lab | Public domain |
111
+ | `garden-tomatoes.txt` | Written for this pack | Evals Lab | Public domain |
112
+ | `gym-contract.txt` | Written for this pack | Evals Lab | Public domain |
113
+ | `headphones-review.txt` | Written for this pack | Evals Lab | Public domain |
114
+ | `hotel-room.txt` | Written for this pack | Evals Lab | Public domain |
115
+ | `library-hours.txt` | Written for this pack | Evals Lab | Public domain |
116
+ | `meeting-minutes.txt` | Written for this pack | Evals Lab | Public domain |
117
+ | `parcel-lost.txt` | Written for this pack | Evals Lab | Public domain |
118
+ | `phone-update.txt` | Written for this pack | Evals Lab | Public domain |
119
+ | `puppy-home.txt` | Written for this pack | Evals Lab | Public domain |
120
+ | `recipe-note.txt` | Written for this pack | Evals Lab | Public domain |
121
+ | `road-closure.txt` | Written for this pack | Evals Lab | Public domain |
122
+ | `teacher-thanks.txt` | Written for this pack | Evals Lab | Public domain |
123
+ | `train-delay.txt` | Written for this pack | Evals Lab | Public domain |
124
+ | `volunteer-day.txt` | Written for this pack | Evals Lab | Public domain |
125
+ | `weather-report.txt` | Written for this pack | Evals Lab | Public domain |
126
+ | `https-api.weather.gov-alerts-types.txt` | https://api.weather.gov/alerts/types | National Weather Service | Public domain |
@@ -2,12 +2,12 @@
2
2
  "format": "evals-lab/dataset",
3
3
  "version": 6,
4
4
  "dataset": {
5
- "name": "Demo 1",
5
+ "name": "Demo: describe images",
6
6
  "body": {
7
7
  "version": 6,
8
8
  "source": {
9
9
  "id": "Demo 1",
10
- "name": "Demo 1"
10
+ "name": "Demo: describe images"
11
11
  },
12
12
  "cases": [
13
13
  {
@@ -2,12 +2,12 @@
2
2
  "format": "evals-lab/dataset",
3
3
  "version": 6,
4
4
  "dataset": {
5
- "name": "Demo 2",
5
+ "name": "Demo: describe images 2",
6
6
  "body": {
7
7
  "version": 6,
8
8
  "source": {
9
9
  "id": "Demo 2",
10
- "name": "Demo 2"
10
+ "name": "Demo: describe images 2"
11
11
  },
12
12
  "cases": [
13
13
  {