evals-lab 0.1.3 → 0.2.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install -g evals-lab
13
13
  evals-lab
14
14
  ```
15
15
 
16
- Then open <http://127.0.0.1:8080>. A new lab opens on two demo datasets, each
16
+ Then open <http://localhost:8080>. A new lab opens on two demo datasets, each
17
17
  with its images and a starter pipeline. The first start takes a little longer
18
18
  than later ones while it installs them.
19
19
 
@@ -61,12 +61,106 @@ evals-lab [--port <n>] [--host <address>] [--data-dir <dir>]
61
61
  | `--data-dir` | `DATA_DIR` | the directory above |
62
62
  | | `LAB_PASSWORD` | none |
63
63
  | | `OLLAMA_URL` | `http://127.0.0.1:11434` |
64
+ | | `M365_CLIENT_ID`, `M365_TENANT` | none; see Power Automate below |
64
65
  | | `PYTHON` | `py -3`, `python` or `python3`, whichever is found |
65
66
 
66
67
  `OLLAMA_URL` is only the connection a new lab starts with. Every other
67
68
  connection — an OpenAI-compatible endpoint, Anthropic, llama.cpp — is a Setup
68
69
  profile you add in the page.
69
70
 
71
+ ## Power Automate (Microsoft 365)
72
+
73
+ The lab can read a Power Automate cloud flow, and its run history, from your
74
+ organisation's Microsoft 365 tenant, then grade the flow's HTTP step against
75
+ the same model and prompt. The sign-in stays in your browser tab: the lab's
76
+ server never receives a Microsoft token.
77
+
78
+ It needs a work or school account. Microsoft retired cloud flows for personal
79
+ accounts (outlook.com, hotmail.com) in July 2025, so a personal account cannot
80
+ be used. For a small business, a trial or a Microsoft 365 Developer Program
81
+ tenant, follow the same steps as a company.
82
+
83
+ ### Register an app in your tenant
84
+
85
+ An administrator of the tenant registers it once, at
86
+ <https://entra.microsoft.com>:
87
+
88
+ 1. **App registrations › New registration.**
89
+ - Supported account types: *Accounts in this organizational directory only*.
90
+ - Redirect URI: platform **Single-page application**, value `http://localhost/`.
91
+ Microsoft ignores the port on a `localhost` redirect, so this covers
92
+ `--port` too. A lab served on another address needs that address here
93
+ as well, over `https`.
94
+ 2. **API permissions › Add a permission › APIs my organization uses.** Search
95
+ for *Microsoft Flow Service* and add the delegated permission
96
+ **Flows.Read.All**. If the service isn't listed, open
97
+ <https://make.powerautomate.com> once, then search again.
98
+ 3. **Grant admin consent** for the tenant.
99
+ 4. No client secret or certificate is needed: the lab signs in as a public
100
+ client.
101
+
102
+ ### Tell the lab about it
103
+
104
+ In the lab, open **Setup › Connections**, then use the Microsoft 365 row's
105
+ menu › **Configure…**. Enter:
106
+
107
+ | | |
108
+ |---|---|
109
+ | Client ID | the app's *Application (client) ID* |
110
+ | Tenant | the *Directory (tenant) ID*, or one of the tenant's domains |
111
+
112
+ Fill in Tenant for an app registered as above. Left empty, it means any
113
+ organisation's tenant, which only a multitenant app accepts: Microsoft
114
+ refuses the sign-in of a single-tenant app with `AADSTS50194`.
115
+
116
+ Both are saved as you type, and neither is secret. **Sign in** from the same
117
+ menu, then add a Source in the Sources tab with **Add source › Power Automate
118
+ workflow**.
119
+
120
+ To set the app for the whole machine instead, start the lab with
121
+ `M365_CLIENT_ID` and `M365_TENANT` set. Setup then shows those values and
122
+ can't change them.
123
+
124
+ Open the lab at `localhost`, the address `evals-lab` prints. A sign-in begun
125
+ at `127.0.0.1` reloads the page at `localhost`, the address registered above,
126
+ and carries on from there. Anything open in the tab, such as an Add source
127
+ dialog, does not carry over.
128
+
129
+ ## Google Drive
130
+
131
+ The lab can sign in to Google Drive, so a Source can read files from a Drive
132
+ folder. The sign-in is held by the lab's server, on your machine, and it can
133
+ reach only the files you pick: the lab asks for `drive.file` access, never
134
+ your whole Drive.
135
+
136
+ Open **Setup › Connections**, then use the Google Drive row's menu ›
137
+ **Sign in**. If the row says *Not Configured*, give the lab a Google app
138
+ first, once. These steps are provisional: they haven't yet been tried
139
+ against a real Google project, and the Drive Source that uses the sign-in
140
+ is still to come.
141
+
142
+ 1. At <https://console.cloud.google.com>, make a project. Enable the
143
+ **Google Drive API** and the **Google Picker API**.
144
+ 2. **OAuth consent screen:** choose *External*. Give the app a name and your
145
+ email, add the scope `.../auth/drive.file`, then **Publish app**. It needs
146
+ no review from Google, because that scope is non-sensitive. Left in
147
+ *Testing*, a sign-in lapses after 7 days.
148
+ 3. **Credentials › Create credentials › OAuth client ID**, of type
149
+ **Desktop app**. Download its JSON file.
150
+ 4. **Credentials › Create credentials › API key.** Restrict it to the Google
151
+ Picker API.
152
+ 5. In the lab, use the Google Drive row's menu › **Configure…**, then
153
+ **Load client file…**, and choose the file from step 3. Paste the key
154
+ from step 4 into **API key**.
155
+
156
+ A Desktop app needs no redirect registered: Google sends the browser back to
157
+ any port on this machine. A lab you reach at its own address, from another
158
+ machine, needs a *Web application* client instead. Choose that type in
159
+ Configure…, then register the **Redirect URI** it shows with the client.
160
+
161
+ Everything saves as you type. The client's secret is kept by the lab and
162
+ never shown again. The window says only that one is saved.
163
+
70
164
  ## Reaching it from another machine
71
165
 
72
166
  By default only your own machine can reach the lab. That is deliberate: the
package/bin/evals-lab.js CHANGED
@@ -53,6 +53,13 @@ function isLoopback(host) {
53
53
  return host === "localhost" || /^127(?:\.\d{1,3}){3}$/.test(host);
54
54
  }
55
55
 
56
+ // The address to open the lab at. Loopback reads as localhost: it is the one
57
+ // http address Entra takes as a sign-in redirect from its portal, on any port,
58
+ // so a Microsoft 365 sign-in works from the address printed (npm/README.md).
59
+ function advertised(host, port) {
60
+ return `http://${host === "127.0.0.1" ? "localhost" : host}:${port}`;
61
+ }
62
+
56
63
  // What the command line and the environment ask for, or an Error saying what
57
64
  // is wrong with it.
58
65
  function options(argv, env = process.env, platform = process.platform, home = os.homedir()) {
@@ -191,11 +198,12 @@ async function main() {
191
198
  process.on("SIGTERM", () => { stopping = true; child.kill("SIGTERM"); });
192
199
 
193
200
  const address = `http://${o.host}:${o.port}`;
201
+ const open = advertised(o.host, o.port);
194
202
  // A first start installs the demo pack before it listens, which takes longer
195
203
  // than any later one.
196
204
  for (let i = 0; i < 1200; i++) {
197
205
  if (await answers(`${address}/healthz`)) {
198
- console.log(`\nevals-lab ${version()} is running at ${address}\n data: ${o.dataDir}\n stop: Ctrl+C`);
206
+ console.log(`\nevals-lab ${version()} is running at ${open}\n data: ${o.dataDir}\n stop: Ctrl+C`);
199
207
  return;
200
208
  }
201
209
  await new Promise(r => setTimeout(r, 250));
@@ -204,4 +212,4 @@ async function main() {
204
212
  }
205
213
 
206
214
  if (require.main === module) main();
207
- module.exports = { dataDir, isLoopback, options, pythonCandidates, findPython, atLeast, PYTHON_FLOOR };
215
+ module.exports = { dataDir, isLoopback, advertised, options, pythonCandidates, findPython, atLeast, PYTHON_FLOOR };
package/lab/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3 (2026.10.01-289)
1
+ 0.2.0 (2026.10.02-323)
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 9,
2
+ "version": 10,
3
3
  "id": "demo-1",
4
4
  "name": "Demo 1",
5
5
  "jobs": [
@@ -21,38 +21,46 @@
21
21
  }
22
22
  },
23
23
  {
24
- "type": "prompt",
25
- "withImage": true,
26
- "tokenMappings": []
24
+ "type": "attachImage"
27
25
  },
28
26
  {
29
27
  "type": "readReply",
30
28
  "out": {
31
29
  "kind": "list",
32
30
  "parse": "csv",
33
- "preamble": false,
34
- "modifiers": [
35
- {
36
- "type": "tidy"
37
- },
38
- {
39
- "type": "dedupe"
40
- },
41
- {
42
- "type": "keep-first",
43
- "n": 12
44
- },
45
- {
46
- "type": "case",
47
- "mode": "LOWER"
48
- }
49
- ]
31
+ "preamble": false
32
+ }
33
+ },
34
+ {
35
+ "type": "modifier",
36
+ "modifier": {
37
+ "type": "tidy"
38
+ }
39
+ },
40
+ {
41
+ "type": "modifier",
42
+ "modifier": {
43
+ "type": "dedupe"
44
+ }
45
+ },
46
+ {
47
+ "type": "modifier",
48
+ "modifier": {
49
+ "type": "keep-first",
50
+ "n": 12
51
+ }
52
+ },
53
+ {
54
+ "type": "modifier",
55
+ "modifier": {
56
+ "type": "case",
57
+ "mode": "LOWER"
50
58
  }
51
59
  }
52
60
  ]
53
61
  }
54
62
  ],
55
- "scenarios": [
63
+ "targets": [
56
64
  {
57
65
  "id": "s1",
58
66
  "name": "",
@@ -60,8 +68,9 @@
60
68
  "id": "vision",
61
69
  "name": "Vision"
62
70
  },
63
- "stages": [
71
+ "steps": [
64
72
  {
73
+ "type": "prompt",
65
74
  "prompt": "List the things you can see in this image, as a comma-separated list of short items (one to three words each), most important first. Up to 12 items."
66
75
  }
67
76
  ]
@@ -73,8 +82,9 @@
73
82
  "id": "vision",
74
83
  "name": "Vision"
75
84
  },
76
- "stages": [
85
+ "steps": [
77
86
  {
87
+ "type": "prompt",
78
88
  "prompt": "Name what is in this image. Reply with a comma-separated list only: up to 12 short items, most important first."
79
89
  }
80
90
  ]
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 9,
2
+ "version": 10,
3
3
  "id": "demo-2",
4
4
  "name": "Demo 2",
5
5
  "jobs": [
@@ -21,38 +21,46 @@
21
21
  }
22
22
  },
23
23
  {
24
- "type": "prompt",
25
- "withImage": true,
26
- "tokenMappings": []
24
+ "type": "attachImage"
27
25
  },
28
26
  {
29
27
  "type": "readReply",
30
28
  "out": {
31
29
  "kind": "list",
32
30
  "parse": "csv",
33
- "preamble": false,
34
- "modifiers": [
35
- {
36
- "type": "tidy"
37
- },
38
- {
39
- "type": "dedupe"
40
- },
41
- {
42
- "type": "keep-first",
43
- "n": 12
44
- },
45
- {
46
- "type": "case",
47
- "mode": "LOWER"
48
- }
49
- ]
31
+ "preamble": false
32
+ }
33
+ },
34
+ {
35
+ "type": "modifier",
36
+ "modifier": {
37
+ "type": "tidy"
38
+ }
39
+ },
40
+ {
41
+ "type": "modifier",
42
+ "modifier": {
43
+ "type": "dedupe"
44
+ }
45
+ },
46
+ {
47
+ "type": "modifier",
48
+ "modifier": {
49
+ "type": "keep-first",
50
+ "n": 12
51
+ }
52
+ },
53
+ {
54
+ "type": "modifier",
55
+ "modifier": {
56
+ "type": "case",
57
+ "mode": "LOWER"
50
58
  }
51
59
  }
52
60
  ]
53
61
  }
54
62
  ],
55
- "scenarios": [
63
+ "targets": [
56
64
  {
57
65
  "id": "s1",
58
66
  "name": "",
@@ -60,8 +68,9 @@
60
68
  "id": "vision",
61
69
  "name": "Vision"
62
70
  },
63
- "stages": [
71
+ "steps": [
64
72
  {
73
+ "type": "prompt",
65
74
  "prompt": "List the things you can see in this image, as a comma-separated list of short items (one to three words each), most important first. Up to 12 items."
66
75
  }
67
76
  ]
@@ -73,8 +82,9 @@
73
82
  "id": "vision",
74
83
  "name": "Vision"
75
84
  },
76
- "stages": [
85
+ "steps": [
77
86
  {
87
+ "type": "prompt",
78
88
  "prompt": "Name what is in this image. Reply with a comma-separated list only: up to 12 short items, most important first."
79
89
  }
80
90
  ]