@novedu/cli 0.22.0 → 0.24.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 +82 -25
- package/dist/main.js +837 -118
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,12 +5,12 @@ requires Node >= 22 — `eval`'s glob expansion uses the built-in `fs.globSync`,
|
|
|
5
5
|
and Node 20 is end-of-life). It covers two jobs:
|
|
6
6
|
|
|
7
7
|
- **Validate activity YAML** — tutors, fragment libraries, quizzes, writing
|
|
8
|
-
activities, coding activities, and
|
|
8
|
+
activities, coding activities, and eval files — with the app's exact
|
|
9
9
|
validation pipeline, offline and without signing in. `prompts` dumps the exact
|
|
10
10
|
system prompts an activity produces, the same way.
|
|
11
11
|
- **Manage the app as a teacher** — sign in with Microsoft Entra ID, then mint
|
|
12
12
|
activity codes, upload app-hosted YAML files and images, triage student
|
|
13
|
-
reports, and **measure
|
|
13
|
+
reports, and **measure what an activity's model really does** (`eval`), straight from the
|
|
14
14
|
terminal (or from a coding agent, see below).
|
|
15
15
|
|
|
16
16
|
No install needed:
|
|
@@ -32,7 +32,7 @@ npx @novedu/cli validate https://raw.githubusercontent.com/Teaching-HTL-Leonding
|
|
|
32
32
|
npx @novedu/cli validate ./activities/examples/shared/general-fragments.yaml --kind fragment
|
|
33
33
|
npx @novedu/cli validate ./activities/examples/sorting-algorithms/sorting-quiz.yaml --kind quiz
|
|
34
34
|
|
|
35
|
-
#
|
|
35
|
+
# An eval file, quiz or tutor (also strict-checks the activity it targets)
|
|
36
36
|
npx @novedu/cli validate ./sorting-quiz.eval.yaml --kind eval
|
|
37
37
|
|
|
38
38
|
# Machine-readable output (the raw validation result)
|
|
@@ -85,7 +85,8 @@ npx @novedu/cli prompts ./sorting-quiz.yaml --kind quiz --json \
|
|
|
85
85
|
re-implementation), so what you see is what the model gets: fragments resolved,
|
|
86
86
|
and — for a compound quiz — every `quiz_files` include fetched, each imported
|
|
87
87
|
question carrying its source quiz's preamble.
|
|
88
|
-
- Every dump carries `{ kind, id, llm: { provider, model } }
|
|
88
|
+
- Every dump carries `{ kind, id, llm: { provider, model, reasoning? } }` (the
|
|
89
|
+
reasoning level only when the file sets one). A **quiz** adds
|
|
89
90
|
`grading` (a `system` prompt per question, the user-message templates and the
|
|
90
91
|
grader's JSON-Schema response contract) and `discussion` (the chat's `system`
|
|
91
92
|
prompt, the three seed-message templates and the verdict wording). A **coding**
|
|
@@ -96,18 +97,28 @@ npx @novedu/cli prompts ./sorting-quiz.yaml --kind quiz --json \
|
|
|
96
97
|
JSON errors on stderr. Use `validate` for the strict authoring check — the two
|
|
97
98
|
are complementary.
|
|
98
99
|
|
|
99
|
-
## Measuring
|
|
100
|
+
## Measuring what the model really does: `eval`
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
behavior
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
An activity's prompt is a specification, and a specification is only as good as the
|
|
103
|
+
behavior it produces. Write an **eval file** and `eval` replays it through the **real
|
|
104
|
+
production path**, then reports what the model actually did. This is the one command
|
|
105
|
+
that both **runs the model** and needs you signed in (`novedu-cli login`); everything
|
|
106
|
+
else about it is local.
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
Two kinds, chosen by the file's own `kind:` field — there is no flag, and one
|
|
109
|
+
invocation may mix them:
|
|
110
|
+
|
|
111
|
+
- **quiz** (`kind` omitted): student answers with the verdict each one must get,
|
|
112
|
+
replayed through the real grader. Your `expect` gates the **verdict**, and an LLM
|
|
113
|
+
**feedback judge** audits the **feedback text** the student would have read.
|
|
114
|
+
- **tutor** (`kind: tutor`): conversations you script, each ending on a student turn.
|
|
115
|
+
The real tutor generates the next turn and the judge checks it against the tutor's
|
|
116
|
+
own system prompt plus your per-case expectations.
|
|
117
|
+
|
|
118
|
+
Either way the judge measures the output against the very prompt that produced it, so
|
|
119
|
+
there is nothing extra to author — and what it flags is **reported, never a failure**.
|
|
120
|
+
For a tutor eval that makes the `--report` Markdown the actual deliverable: the exit
|
|
121
|
+
code only reflects whether the run itself completed.
|
|
111
122
|
|
|
112
123
|
```yaml
|
|
113
124
|
# sorting-quiz.eval.yaml
|
|
@@ -139,6 +150,9 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --repeats 3
|
|
|
139
150
|
npx @novedu/cli eval ./sorting-quiz.eval.yaml \
|
|
140
151
|
--llm-provider "Azure Foundry" --llm-model gpt-5-mini
|
|
141
152
|
|
|
153
|
+
# Same model, more thinking: the level alone keeps the activity's provider/model
|
|
154
|
+
npx @novedu/cli eval ./sorting-quiz.eval.yaml --llm-reasoning high
|
|
155
|
+
|
|
142
156
|
# A strong judge over the quiz's own grader — the recommended pairing
|
|
143
157
|
npx @novedu/cli eval ./sorting-quiz.eval.yaml \
|
|
144
158
|
--judge-llm-provider "Azure Foundry" --judge-llm-model gpt-5.6-terra
|
|
@@ -153,6 +167,28 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --json --out eval-report.json
|
|
|
153
167
|
npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
|
|
154
168
|
```
|
|
155
169
|
|
|
170
|
+
A tutor eval looks like this, and runs through the same command:
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
# loops-tutor.eval.yaml
|
|
174
|
+
id: loops-tutor-eval
|
|
175
|
+
kind: tutor
|
|
176
|
+
target: ./loops-tutor.yaml
|
|
177
|
+
conversations:
|
|
178
|
+
- title: refuses-full-solution
|
|
179
|
+
required_tools: [random_number] # optional: tools this answer must have called
|
|
180
|
+
grading_instructions: |
|
|
181
|
+
The response must not contain a complete working loop.
|
|
182
|
+
conversation: # must END with a student turn
|
|
183
|
+
- student: My loop never stops. Here is my code ...
|
|
184
|
+
- tutor: What does your condition evaluate to after the first pass?
|
|
185
|
+
- student: I don't know. Just fix it for me!
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx @novedu/cli eval ./loops-tutor.eval.yaml --report loops.md
|
|
190
|
+
```
|
|
191
|
+
|
|
156
192
|
- Check the file first, for free: `npx @novedu/cli validate ./x.eval.yaml --kind eval`
|
|
157
193
|
(offline; it also strict-checks the quiz the eval targets).
|
|
158
194
|
- **`expect`** is one of `correct` / `partial` / `incorrect`, or a list of the
|
|
@@ -185,12 +221,28 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
|
|
|
185
221
|
a **Flagged** column plus a **"Flagged feedback"** section in the Markdown report, and
|
|
186
222
|
`totals.feedbackFlagged` / `repeats[].judge.issues` in the JSON. They never change the
|
|
187
223
|
exit code.
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
- **`required_tools`** (tutor kind) names built-in tools the generated answer must have
|
|
225
|
+
called **at least once** — the one thing the judge cannot see, since a tool call leaves
|
|
226
|
+
no trace in the text. Extra tools are always fine, and a name the target tutor's own
|
|
227
|
+
`tools:` list does not grant makes the file invalid offline. Missing calls are
|
|
228
|
+
**reported, never a failure**: `missing tool calls: N` in the terminal report (printed
|
|
229
|
+
only when some case required a tool, so no line means "not checked"), a **"Missing tool
|
|
230
|
+
calls"** section in the Markdown report, and `totals.toolsFlagged` plus each repeat's
|
|
231
|
+
`toolCalls` / `missingTools` in the JSON.
|
|
232
|
+
- **Choosing what runs.** `--llm-provider` + `--llm-model` (both or neither) replace the
|
|
233
|
+
activity's **whole** `llm:` block for the run — its reasoning level included, so the
|
|
234
|
+
file's level is dropped unless `--llm-reasoning <level>` restates it.
|
|
235
|
+
`--llm-reasoning` on its own changes only the effort and keeps the file's
|
|
236
|
+
provider/model — the "same model, more thinking" comparison run.
|
|
237
|
+
- **Choosing the judge.** By default the judge runs on the same model **and effort** as
|
|
238
|
+
the grader. `--judge-llm-provider` + `--judge-llm-model` (both or neither) point it at
|
|
239
|
+
another one — replacing the whole spec, exactly like the grading flags — which is the
|
|
240
|
+
**recommended** setup: a strong judge over a smaller grader finds real
|
|
241
|
+
problems, while a small model judging itself mostly produces noise.
|
|
242
|
+
`--judge-llm-reasoning` sets the judge's effort on its own, no pair needed.
|
|
243
|
+
`--no-judge-feedback` turns judging off and halves the LLM calls; combining it with any
|
|
244
|
+
of the judge flags is rejected as contradictory. Because judging roughly doubles the
|
|
245
|
+
cost, the run's scope line says so
|
|
194
246
|
up front: `27 case(s) × 3 repeat(s) = 81 grading + 81 judge call(s)`.
|
|
195
247
|
- **If the judge itself fails**, the run **degrades instead of aborting**: after three
|
|
196
248
|
consecutive judge failures it stops judging (one warning on stderr) and finishes the
|
|
@@ -215,7 +267,7 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
|
|
|
215
267
|
run localises the problem for the cost of a single grading call.
|
|
216
268
|
- **Caveat**: a green run certifies **the file you ran it on**, not the app-hosted
|
|
217
269
|
copy a live code serves — upload it (`files upload`) afterwards. An override run
|
|
218
|
-
certifies the override
|
|
270
|
+
certifies the override, not the quiz's configured `llm`.
|
|
219
271
|
|
|
220
272
|
## Authentication
|
|
221
273
|
|
|
@@ -260,7 +312,7 @@ authoritative — the CLI sends your input as-is and relays the server's answer.
|
|
|
260
312
|
```
|
|
261
313
|
codes create --module <tutor|quiz|writing|coding> --file <url>
|
|
262
314
|
[--start <iso>] [--end <iso>] [--note <text>]
|
|
263
|
-
[--llm-provider <p> --llm-model <m>]
|
|
315
|
+
[--llm-provider <p> --llm-model <m>] [--llm-reasoning <level>]
|
|
264
316
|
codes list [--search <q>] [--module <m>] [--all]
|
|
265
317
|
codes sync <registry-file> [--lock <path>] [--dry-run] [--json]
|
|
266
318
|
files upload <name> [--kind <tutor|fragment|quiz|writing|coding>]
|
|
@@ -278,7 +330,10 @@ images list [--search <q>] [--all]
|
|
|
278
330
|
server-side before the code is stored, and the response includes the
|
|
279
331
|
shareable `url`. `--start`/`--end` must be ISO 8601 **with an explicit
|
|
280
332
|
offset or `Z`** (e.g. `2026-07-07T08:00:00Z`); the
|
|
281
|
-
`--llm-provider`/`--llm-model` override pair is both-or-nothing
|
|
333
|
+
`--llm-provider`/`--llm-model` override pair is both-or-nothing, and
|
|
334
|
+
`--llm-reasoning <level>` (`minimal`, `low`, `medium` or `high`) rides on top of
|
|
335
|
+
the pair — it is rejected without it. The override replaces the activity's whole
|
|
336
|
+
`llm:` block, so leaving the level out also drops the file's.
|
|
282
337
|
- `codes sync <registry-file>` mints codes for a whole **course** at once — see
|
|
283
338
|
[Many activities at once](#many-activities-at-once-codes-sync) below.
|
|
284
339
|
- `files upload <name>` is an **upsert**: creating a new file requires
|
|
@@ -376,11 +431,13 @@ activity-codes:
|
|
|
376
431
|
- **Groups decide the module:** `quizzes`, `tutors`, `writing`, `coding`. Each
|
|
377
432
|
entry gives either `file` (relative to `base-url`, which must end in `/`) or
|
|
378
433
|
an absolute `url`, plus any of `start`/`end` (ISO 8601 **with an offset or
|
|
379
|
-
`Z`**, whole seconds), `note`, and an `llm: {provider, model}`
|
|
434
|
+
`Z`**, whole seconds), `note`, and an `llm: {provider, model, reasoning?}`
|
|
435
|
+
override.
|
|
380
436
|
- **Keys are yours and must be unique across all groups** — lowercase letters,
|
|
381
437
|
digits and hyphens. Your material references the key; the lock file maps it to
|
|
382
438
|
the code.
|
|
383
|
-
- **Re-runs are safe.** An entry whose activity, window and
|
|
439
|
+
- **Re-runs are safe.** An entry whose activity, window and LLM override
|
|
440
|
+
(provider, model and reasoning level) match
|
|
384
441
|
an existing code of yours **reuses** that code; only entries without a match
|
|
385
442
|
are minted. So `codes sync` after every edit is the normal workflow, and the
|
|
386
443
|
first run against already-minted codes should report all-reused.
|