@novedu/cli 0.21.0 → 0.23.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 (3) hide show
  1. package/README.md +88 -13
  2. package/dist/main.js +1055 -84
  3. 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 golden-answer evals — with the app's exact
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 a quiz's grading rubric** (`eval`), straight from the
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
- # A golden-answer eval (also strict-checks the quiz it targets)
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)
@@ -96,13 +96,28 @@ npx @novedu/cli prompts ./sorting-quiz.yaml --kind quiz --json \
96
96
  JSON errors on stderr. Use `validate` for the strict authoring check — the two
97
97
  are complementary.
98
98
 
99
- ## Measuring a quiz's grading rubric: `eval`
99
+ ## Measuring what the model really does: `eval`
100
100
 
101
- A quiz's `evaluation` prompt is a rubric, and a rubric is only as good as its
102
- behavior on real answers. Write an **eval file** student answers with the verdict
103
- each one must get and `eval` replays them through the **real grader**, then reports
104
- what it actually did. This is the one command that both **runs the model** and needs
105
- you signed in (`novedu-cli login`); everything else about it is local.
101
+ An activity's prompt is a specification, and a specification is only as good as the
102
+ behavior it produces. Write an **eval file** and `eval` replays it through the **real
103
+ production path**, then reports what the model actually did. This is the one command
104
+ that both **runs the model** and needs you signed in (`novedu-cli login`); everything
105
+ else about it is local.
106
+
107
+ Two kinds, chosen by the file's own `kind:` field — there is no flag, and one
108
+ invocation may mix them:
109
+
110
+ - **quiz** (`kind` omitted): student answers with the verdict each one must get,
111
+ replayed through the real grader. Your `expect` gates the **verdict**, and an LLM
112
+ **feedback judge** audits the **feedback text** the student would have read.
113
+ - **tutor** (`kind: tutor`): conversations you script, each ending on a student turn.
114
+ The real tutor generates the next turn and the judge checks it against the tutor's
115
+ own system prompt plus your per-case expectations.
116
+
117
+ Either way the judge measures the output against the very prompt that produced it, so
118
+ there is nothing extra to author — and what it flags is **reported, never a failure**.
119
+ For a tutor eval that makes the `--report` Markdown the actual deliverable: the exit
120
+ code only reflects whether the run itself completed.
106
121
 
107
122
  ```yaml
108
123
  # sorting-quiz.eval.yaml
@@ -134,6 +149,13 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --repeats 3
134
149
  npx @novedu/cli eval ./sorting-quiz.eval.yaml \
135
150
  --llm-provider "Azure Foundry" --llm-model gpt-5-mini
136
151
 
152
+ # A strong judge over the quiz's own grader — the recommended pairing
153
+ npx @novedu/cli eval ./sorting-quiz.eval.yaml \
154
+ --judge-llm-provider "Azure Foundry" --judge-llm-model gpt-5.6-terra
155
+
156
+ # Verdicts only: half the LLM calls, for a cheap smoke run
157
+ npx @novedu/cli eval ./sorting-quiz.eval.yaml --no-judge-feedback
158
+
137
159
  # Machine-readable, for CI
138
160
  npx @novedu/cli eval ./sorting-quiz.eval.yaml --json --out eval-report.json
139
161
 
@@ -141,6 +163,28 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --json --out eval-report.json
141
163
  npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
142
164
  ```
143
165
 
166
+ A tutor eval looks like this, and runs through the same command:
167
+
168
+ ```yaml
169
+ # loops-tutor.eval.yaml
170
+ id: loops-tutor-eval
171
+ kind: tutor
172
+ target: ./loops-tutor.yaml
173
+ conversations:
174
+ - title: refuses-full-solution
175
+ required_tools: [random_number] # optional: tools this answer must have called
176
+ grading_instructions: |
177
+ The response must not contain a complete working loop.
178
+ conversation: # must END with a student turn
179
+ - student: My loop never stops. Here is my code ...
180
+ - tutor: What does your condition evaluate to after the first pass?
181
+ - student: I don't know. Just fix it for me!
182
+ ```
183
+
184
+ ```bash
185
+ npx @novedu/cli eval ./loops-tutor.eval.yaml --report loops.md
186
+ ```
187
+
144
188
  - Check the file first, for free: `npx @novedu/cli validate ./x.eval.yaml --kind eval`
145
189
  (offline; it also strict-checks the quiz the eval targets).
146
190
  - **`expect`** is one of `correct` / `partial` / `incorrect`, or a list of the
@@ -163,14 +207,45 @@ npx @novedu/cli eval ./sorting-quiz.eval.yaml --report eval-report.md
163
207
  a broken file instead of aborting the batch. `--json` / `--out` always carry the
164
208
  same batch shape `{ files: [...], passed, totals }`, single file or not — `passed`
165
209
  is the exit-code verdict, per batch and per file.
210
+ - **The feedback judge.** After each successful grading, an LLM reads the feedback the
211
+ grader wrote and checks it against **that grading's own system prompt** — the course
212
+ rules and the platform frame already in it. It reports four kinds of problem:
213
+ `contradicts_verdict` (praise on a wrong answer, or vice versa), `misstates_facts`,
214
+ `ignores_instructions` (most commonly: not stating the correct answer when the verdict
215
+ is not `correct`, or the wrong language), and `leaks_rubric` (quoting the grading
216
+ criteria at the student). Flags show as **`flagged feedback`** in the terminal report,
217
+ a **Flagged** column plus a **"Flagged feedback"** section in the Markdown report, and
218
+ `totals.feedbackFlagged` / `repeats[].judge.issues` in the JSON. They never change the
219
+ exit code.
220
+ - **`required_tools`** (tutor kind) names built-in tools the generated answer must have
221
+ called **at least once** — the one thing the judge cannot see, since a tool call leaves
222
+ no trace in the text. Extra tools are always fine, and a name the target tutor's own
223
+ `tools:` list does not grant makes the file invalid offline. Missing calls are
224
+ **reported, never a failure**: `missing tool calls: N` in the terminal report (printed
225
+ only when some case required a tool, so no line means "not checked"), a **"Missing tool
226
+ calls"** section in the Markdown report, and `totals.toolsFlagged` plus each repeat's
227
+ `toolCalls` / `missingTools` in the JSON.
228
+ - **Choosing the judge.** By default the judge runs on the same model as the grader.
229
+ `--judge-llm-provider` + `--judge-llm-model` (both or neither) point it at another one,
230
+ which is the **recommended** setup: a strong judge over a smaller grader finds real
231
+ problems, while a small model judging itself mostly produces noise. `--no-judge-feedback`
232
+ turns judging off and halves the LLM calls; combining the two is rejected as
233
+ contradictory. Because judging roughly doubles the cost, the run's scope line says so
234
+ up front: `27 case(s) × 3 repeat(s) = 81 grading + 81 judge call(s)`.
235
+ - **If the judge itself fails**, the run **degrades instead of aborting**: after three
236
+ consecutive judge failures it stops judging (one warning on stderr) and finishes the
237
+ grading normally. Your verdict results are complete; the feedback simply was not
238
+ audited: files that judged nothing show an em dash in the Flagged column rather than a
239
+ `0`, so "unchecked" never reads as "clean".
166
240
  - **`--report <file.md>`** additionally writes a readable **Markdown** report — an
167
241
  overview table over the files, then the question, the golden answer and the grader's
168
- feedback for every mismatched, errored or unstable case (passing cases stay in the
242
+ feedback for every mismatched, errored or unstable case, plus the "Flagged feedback"
243
+ section (passing, unflagged cases stay in the
169
244
  JSON). It composes with `--json` / `--out` and leaves stdout untouched.
170
245
  - **Token totals.** The reports show what a run cost —
171
- `tokens: 15,420 in (12,300 cached) / 2,810 out` — summed over the grading calls that
172
- **succeeded**, so it is a lower bound (a retried or failed call reports nothing), and
173
- nothing at all is printed when the server reports no usage.
246
+ `tokens: 15,420 in (12,300 cached) / 2,810 out` — summed over the grading **and** judge
247
+ calls that **succeeded**, so it is a lower bound (a retried or failed call reports
248
+ nothing), and nothing at all is printed when the server reports no usage.
174
249
  - **Failure handling**: a 5xx or network hiccup is retried (4 attempts, linear
175
250
  backoff); any 4xx is terminal; an auth failure aborts the run with one message; and
176
251
  three consecutive errored cases trip a circuit breaker so a down server fails fast.