@gaunt-sloth/batch 2.0.0-alpha.19 → 2.0.0-alpha.21

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 (39) hide show
  1. package/README.md +140 -0
  2. package/dist/bin.d.ts +13 -0
  3. package/dist/bin.js +28 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/deterministicChecks.d.ts +39 -7
  6. package/dist/deterministicChecks.js +120 -11
  7. package/dist/deterministicChecks.js.map +1 -1
  8. package/dist/evalOutput.d.ts +17 -4
  9. package/dist/evalOutput.js +40 -5
  10. package/dist/evalOutput.js.map +1 -1
  11. package/dist/evalRunner.d.ts +68 -15
  12. package/dist/evalRunner.js +309 -55
  13. package/dist/evalRunner.js.map +1 -1
  14. package/dist/evalSuite.d.ts +32 -11
  15. package/dist/evalSuite.js +487 -42
  16. package/dist/evalSuite.js.map +1 -1
  17. package/dist/evalTypes.d.ts +187 -15
  18. package/dist/index.d.ts +5 -1
  19. package/dist/index.js +6 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/pipelineCli.d.ts +102 -0
  22. package/dist/pipelineCli.js +348 -0
  23. package/dist/pipelineCli.js.map +1 -0
  24. package/dist/reporters/drive.d.ts +14 -0
  25. package/dist/reporters/drive.js +49 -0
  26. package/dist/reporters/drive.js.map +1 -0
  27. package/dist/reporters/registry.d.ts +11 -0
  28. package/dist/reporters/registry.js +26 -0
  29. package/dist/reporters/registry.js.map +1 -0
  30. package/dist/reporters/reporterTypes.d.ts +36 -0
  31. package/dist/reporters/reporterTypes.js +2 -0
  32. package/dist/reporters/reporterTypes.js.map +1 -0
  33. package/dist/reporters/textReporter.d.ts +10 -0
  34. package/dist/reporters/textReporter.js +50 -0
  35. package/dist/reporters/textReporter.js.map +1 -0
  36. package/dist/toolChecks.d.ts +21 -0
  37. package/dist/toolChecks.js +38 -0
  38. package/dist/toolChecks.js.map +1 -0
  39. package/package.json +6 -3
@@ -1,53 +1,225 @@
1
1
  import { runBatchMatrix } from '#src/BatchRunner.js';
2
2
  import { runDeterministicChecks } from '#src/deterministicChecks.js';
3
+ import { runToolCallChecks } from '#src/toolChecks.js';
4
+ /** A block applies to an identity when it names no identities (unscoped → all) or explicitly names
5
+ * this one. For the single-run pseudo-identity (`undefined`) only unscoped blocks apply — and the
6
+ * parser guarantees a no-identities suite has only unscoped blocks. */
7
+ function expectationAppliesTo(expectation, identity) {
8
+ if (!expectation.identities || expectation.identities.length === 0)
9
+ return true;
10
+ return identity !== undefined && expectation.identities.includes(identity);
11
+ }
3
12
  /**
4
- * Build one {@link MatrixCell} per case, `content` = the case's `prompt`. Unlike BATCH-1's
5
- * `buildMatrix` (a models × rows cross product), eval has exactly one cell per case — so cells are
6
- * built directly rather than via `buildMatrix`/`bindCellContent`, which would force an artificial
7
- * single-row/single-model matrix per case for no benefit (no `{{field}}` interpolation applies to
8
- * an eval case's literal `prompt`).
9
- *
10
- * `cell.id` is the case's own `id` (not a `cell-<m>-<r>` index) so per-case JSON output filenames
11
- * read naturally (`<case-id>.json` — see `#src/evalOutput.js`) and results are trivially traceable
12
- * back to the authored case. `parseEvalSuite` already rejects duplicate case ids, so this can't
13
- * collide.
13
+ * Reduce the suite to its (case × identity) units. A suite with no `identities` reduces to a single
14
+ * pseudo-identity (`undefined`) so the runner has ONE code path the whole point of the BATCH-12
15
+ * design: flat cases are just the one-identity, one-unscoped-block special case of the matrix.
14
16
  */
15
- function buildEvalCells(cases) {
16
- return cases.map((evalCase, index) => ({
17
- id: evalCase.id,
18
- modelIndex: 0,
19
- inputIndex: index,
20
- content: evalCase.prompt,
21
- }));
17
+ function buildUnits(suite) {
18
+ const identities = suite.identities && suite.identities.length > 0 ? suite.identities : [undefined];
19
+ const units = [];
20
+ for (const evalCase of suite.cases) {
21
+ for (const identity of identities) {
22
+ units.push({
23
+ cellId: identity === undefined ? evalCase.id : `${evalCase.id}__${identity}`,
24
+ evalCase,
25
+ identity,
26
+ // BATCH-12 Task 2: filter EACH turn's blocks for this identity (length 1 = single-turn).
27
+ applicablePerTurn: evalCase.turns.map((turn) => turn.expectations.filter((e) => expectationAppliesTo(e, identity))),
28
+ });
29
+ }
30
+ }
31
+ return units;
22
32
  }
23
33
  /**
24
- * Run every case in the suite through the SUT ({@link RunEvalSuiteOptions.runCell}, pooled via
25
- * BATCH-1's `runBatchMatrix`), then grade each answer with deterministic checks and (when the case
26
- * declares a rubric) the judge. A case PASSES iff its deterministic checks pass AND (it has no
27
- * judge rubric OR the judge's rate is at/above the case's `passThreshold`) ported from the field
28
- * user's proven harness semantics (`docs/batch-eval-user-requirements.md` Appendix A).
34
+ * Run every (case × identity) cell of the suite through the SUT ({@link RunCellFn}, pooled via
35
+ * BATCH-1's `runBatchMatrix`), then grade each answer with the applicable expectation blocks'
36
+ * deterministic checks, tool-trace checks, and (when a block declares a rubric) the judge. A cell
37
+ * PASSES iff EVERY applicable block passes ported from the field user's proven harness semantics
38
+ * (`docs/batch-eval-user-requirements.md` Appendix A), generalized to per-identity blocks.
39
+ *
40
+ * ONE concurrency pool: flat and matrix suites both normalize to {@link EvalUnit}s and ride the same
41
+ * `runBatchMatrix` pool; the only divergence is which `RunCellFn` a unit's identity resolves to.
42
+ *
43
+ * BATCH-12 Task 2 — a unit whose case has `turns.length > 1` is a MULTI-TURN conversation: it runs
44
+ * through the injected {@link RunConversationFn} seam (agent/tools built once, messages accumulated
45
+ * across turns) instead of {@link RunCellFn}, still inside the SAME pool, and is graded turn-by-turn
46
+ * by {@link gradeConversationUnit} — the cell PASSES iff EVERY turn's applicable blocks pass. A
47
+ * single-turn unit keeps the proven `runCell` + {@link gradeUnit} path byte-for-byte.
29
48
  */
30
49
  export async function runEvalSuite(suite, options) {
31
- const cells = buildEvalCells(suite.cases);
50
+ const units = buildUnits(suite);
51
+ // I1 — dispatch AND grading are keyed by the guaranteed-unique `inputIndex` (a unit's position in
52
+ // this array, threaded onto its `MatrixCell`/`CellResult`), NEVER by the composite `cellId`
53
+ // (`<caseId>__<identity>`). Both case ids and identity names permit `__`, so distinct cells can
54
+ // collapse to the same `cellId` (e.g. case `x` + identity `y__z` and case `x__y` + identity `z`
55
+ // both → `x__y__z`). A `cellId`-keyed Map would silently run/grade one cell under the WRONG
56
+ // identity and drop the other — the worst failure for an authorization matrix. `cellId` is kept
57
+ // for display and per-cell output filenames only (`evalOutput.ts` guards the filename collision).
58
+ const unitByInputIndex = new Map(units.map((unit, index) => [index, unit]));
59
+ // Resolve the RunCellFn for a unit's identity: the single `runCell` for the no-identities path,
60
+ // else the per-identity runCell the command built. A missing runCell is a wiring bug — surface it
61
+ // as a thrown error (caught by `runBatchMatrix` as a failed cell) rather than a silent skip.
62
+ const runCellFor = (identity) => {
63
+ if (identity === undefined) {
64
+ if (!options.runCell) {
65
+ throw new Error('runEvalSuite: no `runCell` provided for the single-run (no-identities) path.');
66
+ }
67
+ return options.runCell;
68
+ }
69
+ const identityRunCell = options.runCellByIdentity?.get(identity);
70
+ if (!identityRunCell) {
71
+ throw new Error(`runEvalSuite: no runCell provided for identity "${identity}".`);
72
+ }
73
+ return identityRunCell;
74
+ };
75
+ // BATCH-12 Task 2 — the MULTI-TURN counterpart to `runCellFor`: resolve the conversational seam
76
+ // for a unit's identity. Same missing-seam-is-a-wiring-bug discipline (throw → caught by
77
+ // `runBatchMatrix` as a failed cell → the whole conversation FAILs) rather than a silent skip.
78
+ const runConversationFor = (identity) => {
79
+ if (identity === undefined) {
80
+ if (!options.runConversation) {
81
+ throw new Error('runEvalSuite: no `runConversation` provided for the multi-turn (no-identities) path.');
82
+ }
83
+ return options.runConversation;
84
+ }
85
+ const identityRunConversation = options.runConversationByIdentity?.get(identity);
86
+ if (!identityRunConversation) {
87
+ throw new Error(`runEvalSuite: no runConversation provided for identity "${identity}".`);
88
+ }
89
+ return identityRunConversation;
90
+ };
91
+ const cells = units.map((unit, index) => ({
92
+ id: unit.cellId,
93
+ modelIndex: 0,
94
+ inputIndex: index,
95
+ // Single-turn dispatch reads `content`; the multi-turn branch reads the unit's turns directly
96
+ // (a MatrixCell has one `content`, so it can't carry a conversation) — turns[0].user is a
97
+ // harmless placeholder there.
98
+ content: unit.evalCase.turns[0].user,
99
+ }));
100
+ // BATCH-12 Task 2 — per-conversation per-turn outcomes, keyed by the unique `inputIndex` (a
101
+ // side-channel so a whole conversation still rides ONE `runBatchMatrix` pool with the single-turn
102
+ // cells — `CellRunOutcome` carries one answer, a conversation carries N). Only multi-turn cells
103
+ // populate this; single-turn cells grade straight from their `CellResult` as before.
104
+ const conversationOutcomes = new Map();
105
+ const dispatchRunCell = async (cell) => {
106
+ const unit = unitByInputIndex.get(cell.inputIndex);
107
+ // Single-turn: the proven `runSingleShot`-backed path, byte-for-byte (unchanged dispatch).
108
+ if (unit.evalCase.turns.length <= 1) {
109
+ return runCellFor(unit.identity)(cell);
110
+ }
111
+ // Multi-turn: run the whole conversation ONCE (agent/tools built once), stash the per-turn
112
+ // outcomes for grading, and report a synthetic pool outcome (`ok` iff every turn ran) so the
113
+ // pool's own bookkeeping is coherent — grading reads the stash, not this outcome's answer.
114
+ const outcomes = await runConversationFor(unit.identity)(unit.evalCase.turns.map((turn) => turn.user));
115
+ conversationOutcomes.set(cell.inputIndex, outcomes);
116
+ return { ok: outcomes.length > 0 && outcomes.every((o) => o.ok) };
117
+ };
32
118
  const cellResults = await runBatchMatrix(cells, {
33
- runCell: options.runCell,
119
+ runCell: dispatchRunCell,
34
120
  concurrency: options.concurrency,
35
121
  });
36
- const casesById = new Map(suite.cases.map((evalCase) => [evalCase.id, evalCase]));
37
122
  const results = [];
38
123
  for (const cellResult of cellResults) {
39
- const evalCase = casesById.get(cellResult.id);
40
- /* istanbul ignore next -- cell ids are derived 1:1 from case ids by buildEvalCells above */
41
- if (!evalCase)
124
+ const unit = unitByInputIndex.get(cellResult.inputIndex);
125
+ /* istanbul ignore next -- inputIndex is derived 1:1 from units (each cell's inputIndex is its
126
+ unit's array position, preserved through runBatchMatrix), so every result maps to one unit */
127
+ if (!unit)
42
128
  continue;
43
- results.push(await gradeCase(evalCase, cellResult, options.judge));
129
+ if (unit.evalCase.turns.length <= 1) {
130
+ results.push(await gradeUnit(unit, cellResult, options.judge));
131
+ }
132
+ else {
133
+ results.push(await gradeConversationUnit(unit, conversationOutcomes.get(cellResult.inputIndex), cellResult, options.judge));
134
+ }
44
135
  }
45
136
  const passed = results.filter((result) => result.verdict === 'PASS').length;
46
137
  return { total: results.length, passed, failed: results.length - passed, cases: results };
47
138
  }
48
- async function gradeCase(evalCase, cellResult, judge) {
139
+ /**
140
+ * BATCH-11 (#405 his #6) — classify a completed suite's {@link EvalSuiteSummary} into a distinct
141
+ * exit code so CI can tell a product regression from a broken harness. BATCH-12 counts matrix
142
+ * CELLS (one per case × identity), not cases:
143
+ *
144
+ * - `0` — every cell passed (unchanged contract).
145
+ * - `1` — the suite **ran** but ≥1 cell FAILED (assertion/judge below threshold). A *product*
146
+ * signal: real, gradeable results, some below the bar.
147
+ * - `2` — **harness error**: no gradeable results at all — an empty suite, or **every** cell's SUT
148
+ * run failed (`sutOk === false`). (The other harness errors that never reach a summary — suite
149
+ * load/parse error, config error, an unresolved identity precondition — are mapped to `2` by the
150
+ * caller (`evalCommand.ts`) in a try/catch.)
151
+ *
152
+ * Classification is anchored on `sutOk`, not the verdict: a cell that ran (`sutOk === true`) but
153
+ * whose judge errored (or whose answer failed a check) is a real result → exit `1`, never `2`. A
154
+ * *mix* of `sutOk:false` and `sutOk:true` cells therefore yields `1`.
155
+ */
156
+ export function classifyEvalExit(summary) {
157
+ // No gradeable results at all → harness/environment error, not a product signal.
158
+ if (summary.total === 0 || summary.cases.every((result) => !result.sutOk)) {
159
+ return 2;
160
+ }
161
+ // Ran and produced gradeable results, but at least one cell failed → product regression.
162
+ if (summary.failed > 0) {
163
+ return 1;
164
+ }
165
+ // Every cell passed.
166
+ return 0;
167
+ }
168
+ /**
169
+ * Grade ONE answer (+ its tool trace) against a set of APPLICABLE expectation blocks — the shared
170
+ * inner loop of both the single-turn {@link gradeUnit} and the multi-turn {@link gradeConversationUnit}.
171
+ * Runs each block's deterministic checks, tool-trace checks, and judge (when it declares a rubric),
172
+ * accumulating failure `reasons` (a passing block/judge appends nothing). `judgeOutcome` is the FIRST
173
+ * block that declared a judge, so a single-block case's `judge` field is exactly that block's outcome
174
+ * (back-compat). For a single applicable block this reproduces the pre-BATCH-12 grading byte-for-byte.
175
+ */
176
+ async function gradeApplicableBlocks(answer, tools, applicable, passThreshold, judge) {
177
+ const deterministicFailures = [];
178
+ const reasons = [];
179
+ let judgeOutcome;
180
+ for (const block of applicable) {
181
+ const checks = runDeterministicChecks(answer, block);
182
+ // Tool-trace assertions read the captured tool names, not the answer, so they are graded
183
+ // separately (see #src/toolChecks.js) and merged into `reasons` alongside the answer checks.
184
+ const toolFailures = runToolCallChecks(tools, block);
185
+ deterministicFailures.push(...checks.failures);
186
+ reasons.push(...checks.failures, ...toolFailures);
187
+ if (block.judgeRubric) {
188
+ let outcome;
189
+ if (!judge) {
190
+ outcome = { attempted: false, ok: false, error: 'No judge configured for this suite.' };
191
+ reasons.push('judge: not configured');
192
+ }
193
+ else {
194
+ outcome = await judge(answer, block.judgeRubric);
195
+ if (!outcome.ok || !outcome.verdict) {
196
+ reasons.push(`judge error: ${outcome.error ?? 'unknown error'}`);
197
+ }
198
+ else if (outcome.verdict.rate < passThreshold) {
199
+ reasons.push(`judge rate ${outcome.verdict.rate}/10 below threshold ${passThreshold}` +
200
+ (outcome.verdict.reason ? `: ${outcome.verdict.reason}` : ''));
201
+ }
202
+ }
203
+ if (judgeOutcome === undefined)
204
+ judgeOutcome = outcome;
205
+ }
206
+ }
207
+ return { reasons, deterministicFailures, judgeOutcome };
208
+ }
209
+ /**
210
+ * Grade one SINGLE-TURN (case × identity) cell: run its (single turn's) APPLICABLE expectation
211
+ * blocks' deterministic checks, tool-trace checks, and judge; the cell PASSES iff every applicable
212
+ * block passes. For a flat case (exactly one unscoped applicable block) this produces byte-for-byte
213
+ * the same `reasons`/`checks`/`judge`/`verdict` as the pre-BATCH-12 single-block grader.
214
+ */
215
+ async function gradeUnit(unit, cellResult, judge) {
216
+ const { evalCase, identity, applicablePerTurn } = unit;
217
+ const applicable = applicablePerTurn[0];
49
218
  const base = {
50
219
  id: evalCase.id,
220
+ // Omit `identity` entirely (rather than set it to `undefined`) for the no-identities path, so
221
+ // its `<id>.json` output stays byte-for-byte identical to before BATCH-12.
222
+ ...(identity !== undefined ? { identity } : {}),
51
223
  passThreshold: evalCase.passThreshold,
52
224
  answer: cellResult.answer,
53
225
  tokensInput: cellResult.tokensInput,
@@ -63,38 +235,120 @@ async function gradeCase(evalCase, cellResult, judge) {
63
235
  reasons: [cellResult.error ? `SUT run failed: ${cellResult.error}` : 'SUT run failed.'],
64
236
  };
65
237
  }
66
- const answer = cellResult.answer ?? '';
67
- const checks = runDeterministicChecks(answer, evalCase);
68
- const reasons = [...checks.failures];
69
- let judgeOutcome;
70
- let judgePassed = true;
71
- if (evalCase.judgeRubric) {
72
- if (!judge) {
73
- judgeOutcome = { attempted: false, ok: false, error: 'No judge configured for this suite.' };
74
- judgePassed = false;
75
- reasons.push('judge: not configured');
76
- }
77
- else {
78
- judgeOutcome = await judge(answer, evalCase.judgeRubric);
79
- if (!judgeOutcome.ok || !judgeOutcome.verdict) {
80
- judgePassed = false;
81
- reasons.push(`judge error: ${judgeOutcome.error ?? 'unknown error'}`);
82
- }
83
- else if (judgeOutcome.verdict.rate < evalCase.passThreshold) {
84
- judgePassed = false;
85
- reasons.push(`judge rate ${judgeOutcome.verdict.rate}/10 below threshold ${evalCase.passThreshold}` +
86
- (judgeOutcome.verdict.reason ? `: ${judgeOutcome.verdict.reason}` : ''));
87
- }
88
- }
238
+ // NO-SILENT-PASS backstop (M2): a cell with zero applicable blocks is a suite-AUTHORING error, not
239
+ // a product regression — nothing would grade this (case × identity). The parser rejects this
240
+ // statically when the suite declares identities, so this branch is only reachable if that guard is
241
+ // bypassed; when it is, THROW so the eval command's catch classifies it as a harness error
242
+ // (exit 2), never a product FAIL (exit 1) or, worse, a silent trivial pass. Throwing aborts the
243
+ // whole suite, which is acceptable precisely because this is unreachable under the parse-time guard.
244
+ if (applicable.length === 0) {
245
+ throw new Error(`no applicable expectation block for cell "${unit.cellId}" (identity ` +
246
+ `"${identity ?? '(default)'}") — nothing would grade this (case × identity), which is a ` +
247
+ 'suite-authoring error.');
89
248
  }
90
- const verdict = checks.passed && judgePassed ? 'PASS' : 'FAIL';
249
+ const answer = cellResult.answer ?? '';
250
+ const tools = cellResult.tools ?? [];
251
+ const { reasons, deterministicFailures, judgeOutcome } = await gradeApplicableBlocks(answer, tools, applicable, evalCase.passThreshold, judge);
252
+ // A cell PASSES iff nothing was recorded against it: every applicable block's deterministic
253
+ // checks, tool-trace checks, and judge all passed (a passing judge appends no reason). For a
254
+ // single applicable block this is exactly the pre-BATCH-12 verdict.
255
+ const verdict = reasons.length === 0 ? 'PASS' : 'FAIL';
91
256
  return {
92
257
  ...base,
93
258
  verdict,
94
259
  sutOk: true,
95
- checks,
260
+ checks: { passed: deterministicFailures.length === 0, failures: deterministicFailures },
96
261
  judge: judgeOutcome,
97
262
  reasons,
98
263
  };
99
264
  }
265
+ /**
266
+ * Grade one MULTI-TURN (case × identity) cell (BATCH-12 Task 2): grade EACH turn's answer + per-turn
267
+ * tool trace against THAT turn's applicable blocks, then roll them up. The cell PASSES iff EVERY turn
268
+ * PASSes; its top-level `reasons` are every turn's reasons each prefixed with the failing turn
269
+ * (`turn N: …`) so the summary/output pinpoints which turn broke, and the full per-turn breakdown is
270
+ * in `turns`.
271
+ *
272
+ * `outcomes` is what the conversational runner returned (one per turn it attempted). A SHORT array
273
+ * (the conversation aborted mid-way) or a MISSING one (`undefined` — the runner threw, so
274
+ * `runBatchMatrix` recorded a failed cell with no stash) fails the un-run turns with a clear reason.
275
+ * `sutOk` is TRUE iff at least one turn actually ran — so a totally-failed conversation is
276
+ * `sutOk:false` (exit-2-eligible, like a single-shot SUT failure) while a turn-1-ran/turn-2-failed
277
+ * cell is `sutOk:true`, a real product signal → exit 1.
278
+ */
279
+ async function gradeConversationUnit(unit, outcomes, cellResult, judge) {
280
+ const { evalCase, identity, applicablePerTurn } = unit;
281
+ const turnOutcomes = outcomes ?? [];
282
+ const turnResults = [];
283
+ const cellReasons = [];
284
+ let anyTurnRan = false;
285
+ for (let i = 0; i < evalCase.turns.length; i++) {
286
+ const turn = evalCase.turns[i];
287
+ const applicable = applicablePerTurn[i];
288
+ // NO-SILENT-PASS backstop (per turn × identity), mirroring gradeUnit's: unreachable under the
289
+ // parse-time guard, but if bypassed THROW so it's a harness error (exit 2), never a silent pass.
290
+ if (applicable.length === 0) {
291
+ throw new Error(`no applicable expectation block for cell "${unit.cellId}" (identity ` +
292
+ `"${identity ?? '(default)'}") turn ${i} — nothing would grade this (turn × identity), ` +
293
+ 'which is a suite-authoring error.');
294
+ }
295
+ const outcome = turnOutcomes[i];
296
+ const label = `turn ${i + 1}`;
297
+ if (!outcome || !outcome.ok) {
298
+ // This turn produced no answer: the conversation errored on it, or aborted before reaching it.
299
+ let detail;
300
+ if (outcome?.error)
301
+ detail = `SUT run failed: ${outcome.error}`;
302
+ else if (outcome)
303
+ detail = 'SUT run failed.';
304
+ else if (outcomes === undefined)
305
+ detail = `SUT run failed: ${cellResult.error ?? 'the conversation could not run.'}`;
306
+ else
307
+ detail = 'SUT run failed: the conversation ended before this turn.';
308
+ turnResults.push({
309
+ user: turn.user,
310
+ answer: outcome?.answer,
311
+ tokensInput: outcome?.tokensInput,
312
+ tokensOutput: outcome?.tokensOutput,
313
+ tools: outcome?.tools,
314
+ ok: false,
315
+ verdict: 'FAIL',
316
+ reasons: [detail],
317
+ });
318
+ cellReasons.push(`${label}: ${detail}`);
319
+ continue;
320
+ }
321
+ anyTurnRan = true;
322
+ const answer = outcome.answer ?? '';
323
+ const tools = outcome.tools ?? [];
324
+ const { reasons, deterministicFailures, judgeOutcome } = await gradeApplicableBlocks(answer, tools, applicable, evalCase.passThreshold, judge);
325
+ const turnVerdict = reasons.length === 0 ? 'PASS' : 'FAIL';
326
+ turnResults.push({
327
+ user: turn.user,
328
+ answer: outcome.answer,
329
+ tokensInput: outcome.tokensInput,
330
+ tokensOutput: outcome.tokensOutput,
331
+ tools: outcome.tools,
332
+ ok: true,
333
+ verdict: turnVerdict,
334
+ checks: { passed: deterministicFailures.length === 0, failures: deterministicFailures },
335
+ judge: judgeOutcome,
336
+ reasons,
337
+ });
338
+ for (const reason of reasons)
339
+ cellReasons.push(`${label}: ${reason}`);
340
+ }
341
+ const verdict = cellReasons.length === 0 ? 'PASS' : 'FAIL';
342
+ return {
343
+ id: evalCase.id,
344
+ ...(identity !== undefined ? { identity } : {}),
345
+ passThreshold: evalCase.passThreshold,
346
+ // A multi-turn cell has no single answer/tools/checks/judge — read the per-turn breakdown below.
347
+ durationMs: cellResult.durationMs,
348
+ verdict,
349
+ sutOk: anyTurnRan,
350
+ reasons: cellReasons,
351
+ turns: turnResults,
352
+ };
353
+ }
100
354
  //# sourceMappingURL=evalRunner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"evalRunner.js","sourceRoot":"","sources":["../src/evalRunner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"evalRunner.js","sourceRoot":"","sources":["../src/evalRunner.ts"],"names":[],"mappings":"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"}
@@ -2,21 +2,42 @@ import type { EvalSuite } from '#src/evalTypes.js';
2
2
  /**
3
3
  * Parse and validate an eval suite YAML document into a normalized {@link EvalSuite}.
4
4
  *
5
+ * Every case is normalized to ONE shape — `turns: [{ user, expectations: EvalExpectation[] }]` (Task
6
+ * 1: exactly one turn). A flat case is sugar for one unscoped expectation (applies to every
7
+ * identity); a matrix case's `expect:` array becomes the expectation list directly.
8
+ *
5
9
  * Rejects, with a clear message, at parse time (never silently no-ops or defers to run time):
6
10
  * - Malformed YAML.
7
11
  * - A suite shape that doesn't match {@link RawSuiteSchema} (missing/wrong-typed fields).
8
- * - `target.type` other than `"gth-agent"` — pluggable CLI/HTTP targets are out of scope for this
9
- * task (see the CLI surface doc's future `target: {type: cli|http}` sketch).
12
+ * - `target.type` other than `"gth-agent"`, `"adk-agent"`, or `"ag-ui"` other pluggable CLI/HTTP
13
+ * targets are out of scope.
10
14
  * - `target.profile` set to anything other than `"default"`/absent — a single suite-wide profile
11
- * switch is the same `--identities` direction the brief scopes out; this task's target is always
12
- * whatever profile `gth eval` itself was invoked under (`-i/--identity-profile`, if any).
13
- * - A case `id` containing anything other than alphanumerics, dashes, underscores, or dots (case
14
- * ids double as output filenames — see `#src/evalOutput.js` so a path separator or traversal
15
- * sequence like `../../etc/passwd` is rejected here, not sanitized).
16
- * - A duplicate case `id` (case ids double as output filenames see `#src/evalOutput.js`).
17
- * - A case with **no** deterministic checks (`must_contain`/`must_not_contain`/
18
- * `should_contain_any` all absent/empty) **and no** `judge` rubric: nothing would ever grade it,
19
- * which is a suite-authoring bug, not a case that trivially passes.
15
+ * switch is the `--identities` direction, replaced by the suite-level `identities` list.
16
+ * - A `"adk-agent"` (BATCH-14) target missing its `url`; an `adk-agent` suite that ALSO uses the
17
+ * `identities` matrix (per-identity gth configs are meaningless for an external agent); or an
18
+ * `adk-agent` suite that uses `must_call`/`must_not_call` (A2A does not expose the agent's tool
19
+ * trace, so grading a tool-call assertion is impossible — rejected rather than silently passed).
20
+ * - An `"ag-ui"` (BATCH-15) target missing its `url` or its `agent_id`; an `ag-ui` target carrying a
21
+ * `profile`; or an `ag-ui` suite that ALSO uses the `identities` matrix (per-identity gth configs
22
+ * are meaningless for an external agent). NOTE `must_call`/`must_not_call` ARE supported for an
23
+ * `ag-ui` target the AG-UI wire streams `TOOL_CALL_START`, so the tool trace is captured and
24
+ * graded (this is the key difference from `adk-agent`).
25
+ * - A case `id`, or a suite `identities` name, containing anything other than alphanumerics,
26
+ * dashes, underscores, or dots (both double as output filenames — path traversal is rejected).
27
+ * - A duplicate case `id`, or a duplicate `identities` entry.
28
+ * - A case declaring BOTH `prompt` and `turns:`, or NEITHER (a case is single- or multi-turn).
29
+ * - A single-turn case with a missing/blank `prompt`; a multi-turn turn with a missing/blank `user`;
30
+ * an empty `turns:` array.
31
+ * - A multi-turn case that ALSO declares case-level assertions or an `expect:` array (those live on
32
+ * each turn for a multi-turn case).
33
+ * - A case (or turn) declaring BOTH flat assertions AND an `expect:` array (one way per case/turn).
34
+ * - An `expect:` block referencing an identity the suite does not declare.
35
+ * - A (turn × identity) with no applicable expectation block (statically determinable → rejected
36
+ * here; the runner also guards it as a per-cell FAIL as a backstop).
37
+ * - A flat case or `expect:` block with no checks of any kind AND no `judge` rubric.
38
+ * - An invalid `must_match`/`must_not_match` regex, or a `json_path` entry not setting exactly one
39
+ * of `equals`/`contains`.
40
+ * - A `judge_profile` containing a path separator or `..`.
20
41
  *
21
42
  * @param yamlText Raw suite file content.
22
43
  * @param sourcePath Optional path, only used to make error messages more actionable.