@axlsdk/eval 0.17.8 → 0.18.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 +74 -7
- package/dist/{chunk-NPKIP4SB.js → chunk-BWBX5YVQ.js} +245 -164
- package/dist/chunk-BWBX5YVQ.js.map +1 -0
- package/dist/cli.cjs +537 -214
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +305 -52
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +327 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -18
- package/dist/index.d.ts +199 -18
- package/dist/index.js +91 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-NPKIP4SB.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
2
|
+
import { Provider, Effort, AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
3
3
|
|
|
4
4
|
type DatasetItem<TInput, TAnnotations = undefined> = {
|
|
5
5
|
input: TInput;
|
|
6
6
|
annotations?: TAnnotations;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* What to do when a dataset item's `annotations` contain keys that are absent
|
|
10
|
+
* from the `annotations` schema.
|
|
11
|
+
*
|
|
12
|
+
* Zod object schemas strip unknown keys by default, so an annotation field
|
|
13
|
+
* that isn't declared in the schema is silently dropped before it ever reaches
|
|
14
|
+
* a scorer. A scorer that reads such a field sees `undefined` and quietly
|
|
15
|
+
* degrades to a no-op — the eval still "passes", so the bug is invisible. This
|
|
16
|
+
* is especially easy to hit with file-based datasets, where the JSON is never
|
|
17
|
+
* type-checked.
|
|
18
|
+
*
|
|
19
|
+
* - `'warn'` (default) — log a single `console.warn` per dataset listing the
|
|
20
|
+
* dropped key paths. Non-breaking; surfaces the contract violation.
|
|
21
|
+
* - `'error'` — throw, listing the dropped key paths. The strict path for CI.
|
|
22
|
+
* - `'ignore'` — preserve the silent-strip behavior (skips detection entirely).
|
|
23
|
+
*
|
|
24
|
+
* Only `annotations` are checked, not `input`: stripped input fields flow to
|
|
25
|
+
* the workflow where they surface, whereas stripped annotations vanish into a
|
|
26
|
+
* silent scorer.
|
|
27
|
+
*/
|
|
28
|
+
type ExtraKeyPolicy = 'warn' | 'error' | 'ignore';
|
|
8
29
|
type DatasetConfig<TInput extends z.ZodType, TAnnotations extends z.ZodType = z.ZodType> = {
|
|
9
30
|
name: string;
|
|
10
31
|
schema: TInput;
|
|
@@ -13,11 +34,28 @@ type DatasetConfig<TInput extends z.ZodType, TAnnotations extends z.ZodType = z.
|
|
|
13
34
|
file?: string;
|
|
14
35
|
/** Base directory for resolving relative file paths. Defaults to cwd. */
|
|
15
36
|
basePath?: string;
|
|
37
|
+
/**
|
|
38
|
+
* How to handle annotation keys not present in the `annotations` schema
|
|
39
|
+
* (Zod strips them by default). Defaults to `'warn'`. See {@link ExtraKeyPolicy}.
|
|
40
|
+
*/
|
|
41
|
+
onExtraAnnotationKeys?: ExtraKeyPolicy;
|
|
16
42
|
};
|
|
17
43
|
type Dataset<TInput = unknown, TAnnotations = unknown> = {
|
|
18
44
|
readonly name: string;
|
|
19
45
|
readonly schema: z.ZodType;
|
|
20
46
|
readonly annotationsSchema?: z.ZodType;
|
|
47
|
+
/**
|
|
48
|
+
* Annotation key paths the schema dropped during the most recent
|
|
49
|
+
* `getItems()` call (empty when none were dropped, or when
|
|
50
|
+
* `onExtraAnnotationKeys: 'ignore'`). Populated as a side effect of
|
|
51
|
+
* `getItems()` — read it synchronously right after awaiting, on the same
|
|
52
|
+
* instance; a later `getItems()` overwrites it (calling `getItems()`
|
|
53
|
+
* concurrently on one shared dataset is unsupported). The eval runner folds
|
|
54
|
+
* this into `EvalResult.metadata.droppedAnnotationKeys` so any consumer (the
|
|
55
|
+
* CLI logs it; UIs can render it) sees the same signal. Optional so
|
|
56
|
+
* hand-rolled `Dataset`-shaped objects (test harnesses) remain valid.
|
|
57
|
+
*/
|
|
58
|
+
readonly droppedAnnotationKeys?: readonly string[];
|
|
21
59
|
getItems(): Promise<DatasetItem<TInput, TAnnotations>[]>;
|
|
22
60
|
};
|
|
23
61
|
declare function dataset<TInput extends z.ZodType, TAnnotations extends z.ZodType>(config: DatasetConfig<TInput, TAnnotations>): Dataset<z.infer<TInput>, z.infer<TAnnotations>>;
|
|
@@ -34,24 +72,11 @@ declare function normalizeScorerResult(result: number | ScorerResult): ScorerRes
|
|
|
34
72
|
type ScorerContext = {
|
|
35
73
|
/** Resolve a provider:model URI to a provider instance and model name. */
|
|
36
74
|
resolveProvider: (modelUri: string) => {
|
|
37
|
-
provider:
|
|
38
|
-
chat(messages: {
|
|
39
|
-
role: string;
|
|
40
|
-
content: string;
|
|
41
|
-
}[], options: {
|
|
42
|
-
model: string;
|
|
43
|
-
temperature?: number;
|
|
44
|
-
responseFormat?: {
|
|
45
|
-
type: string;
|
|
46
|
-
json_schema?: unknown;
|
|
47
|
-
};
|
|
48
|
-
}): Promise<{
|
|
49
|
-
content: string;
|
|
50
|
-
cost?: number;
|
|
51
|
-
}>;
|
|
52
|
-
};
|
|
75
|
+
provider: Provider;
|
|
53
76
|
model: string;
|
|
54
77
|
};
|
|
78
|
+
/** Abort signal forwarded from the eval runner; scorers should pass it into provider calls. */
|
|
79
|
+
signal?: AbortSignal;
|
|
55
80
|
};
|
|
56
81
|
type ScorerFn<TOutput, TInput, TAnnotations> = (output: TOutput, input: TInput, annotations?: TAnnotations) => number | ScorerResult;
|
|
57
82
|
type ScorerConfig<TOutput = unknown, TInput = unknown, TAnnotations = unknown> = {
|
|
@@ -76,7 +101,22 @@ type LlmScorerConfig = {
|
|
|
76
101
|
score: number;
|
|
77
102
|
[key: string]: unknown;
|
|
78
103
|
}>;
|
|
104
|
+
/** Sampling temperature. Default: 0.2 for deterministic judging. */
|
|
79
105
|
temperature?: number;
|
|
106
|
+
/** Cap on response tokens. Falls back to provider default if unset. */
|
|
107
|
+
maxTokens?: number;
|
|
108
|
+
/** Reasoning effort — `'high'` or `'max'` materially improves judge calibration on
|
|
109
|
+
* reasoning-capable models (gpt-5.x, Opus 4.6+, Gemini 3.x). See `Effort` for
|
|
110
|
+
* provider-specific mapping. */
|
|
111
|
+
effort?: Effort;
|
|
112
|
+
/** Precise thinking-token budget override. See `ChatOptions.thinkingBudget`. */
|
|
113
|
+
thinkingBudget?: number;
|
|
114
|
+
/** Surface reasoning summaries in the judge's response (Gemini + OpenAI Responses). */
|
|
115
|
+
includeThoughts?: boolean;
|
|
116
|
+
/** Stop sequences forwarded to the provider. */
|
|
117
|
+
stop?: string[];
|
|
118
|
+
/** Provider-specific escape hatch — merged last into the raw API request. */
|
|
119
|
+
providerOptions?: Record<string, unknown>;
|
|
80
120
|
};
|
|
81
121
|
declare function llmScorer(config: LlmScorerConfig): Scorer;
|
|
82
122
|
|
|
@@ -84,10 +124,53 @@ type EvalConfig = {
|
|
|
84
124
|
workflow: string;
|
|
85
125
|
dataset: Dataset<unknown, unknown>;
|
|
86
126
|
scorers: Scorer<unknown, unknown, unknown>[];
|
|
127
|
+
/** Item-level worker-pool size (how many dataset items run in parallel). Default 5. */
|
|
87
128
|
concurrency?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Per-item scorer fan-out — how many of an item's scorers run concurrently.
|
|
131
|
+
* Default 5 (matches `concurrency`; scorers are parallel-by-default). The
|
|
132
|
+
* worst-case number of concurrent scorer calls is `concurrency × scorerConcurrency`,
|
|
133
|
+
* so lower `concurrency` if a rate-limited judge model needs a tighter ceiling.
|
|
134
|
+
*/
|
|
135
|
+
scorerConcurrency?: number;
|
|
88
136
|
budget?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Opt-in source-side trust gate. When set (0–1), `runEval` flags the run as
|
|
139
|
+
* `summary.degraded` if any scorer's failure rate exceeds tolerance, and the
|
|
140
|
+
* CLI exits non-zero. Enforcement is **type-aware**: deterministic scorers
|
|
141
|
+
* tolerate ZERO failures (a deterministic scorer that throws is a bug, not
|
|
142
|
+
* noise), while LLM scorers use this rate (a flaky judge / rate-limit storm).
|
|
143
|
+
*
|
|
144
|
+
* Failure rate = `failed / (scored + failed)` per scorer, where `scored` is
|
|
145
|
+
* the number of items that produced a valid numeric score and `failed` is the
|
|
146
|
+
* number whose scorer ran and threw / returned out-of-range. `0` means "any
|
|
147
|
+
* LLM failure degrades the run". Invalid values (non-finite, <0, >1) are
|
|
148
|
+
* ignored with a `console.warn`.
|
|
149
|
+
*
|
|
150
|
+
* Distinct from the gate-side `--max-scorer-error-rate` compare flag: this one
|
|
151
|
+
* fires at run/produce time (catch a thinned sample before it's saved), the
|
|
152
|
+
* other at consume/gate time (refuse to certify a thinned baseline/candidate).
|
|
153
|
+
*/
|
|
154
|
+
failOnScorerErrorRate?: number;
|
|
89
155
|
metadata?: Record<string, unknown>;
|
|
90
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* One scorer whose failure rate tripped `EvalConfig.failOnScorerErrorRate`.
|
|
159
|
+
* Surfaced on `EvalSummary.degraded` so consumers (CLI exit code, Studio
|
|
160
|
+
* banner) can refuse to trust the mean without re-deriving the rate.
|
|
161
|
+
*/
|
|
162
|
+
type DegradedScorer = {
|
|
163
|
+
scorer: string;
|
|
164
|
+
/** Observed failure rate `failed / (scored + failed)` (0 when nothing ran). */
|
|
165
|
+
rate: number;
|
|
166
|
+
/** The tolerance that was exceeded (0 for deterministic scorers). */
|
|
167
|
+
limit: number;
|
|
168
|
+
type: 'llm' | 'deterministic';
|
|
169
|
+
/** Items that produced a valid numeric score. */
|
|
170
|
+
scored: number;
|
|
171
|
+
/** Items whose scorer ran and failed (threw / out-of-range). */
|
|
172
|
+
failed: number;
|
|
173
|
+
};
|
|
91
174
|
type EvalResult = {
|
|
92
175
|
id: string;
|
|
93
176
|
/**
|
|
@@ -106,6 +189,13 @@ type EvalResult = {
|
|
|
106
189
|
* that need a single "primary" workflow should use `workflows[0]`.
|
|
107
190
|
* - `scorerTypes: Record<string, 'llm' | 'deterministic'>`
|
|
108
191
|
* - `runGroupId?: string`, `runIndex?: number` for multi-run groups
|
|
192
|
+
* - `droppedAnnotationKeys?: string[]` — annotation key paths the dataset
|
|
193
|
+
* schema stripped (present only when non-empty). Mirrors the dataset's
|
|
194
|
+
* `console.warn`; lets any consumer surface the same signal.
|
|
195
|
+
* - `scorerFiltered?: boolean` + `scorersRun?: string[]` — set when the CLI
|
|
196
|
+
* `--scorers` flag ran a subset of scorers. A guard so a filtered run isn't
|
|
197
|
+
* silently used as a full baseline (`compare` warns / refuses to gate on it).
|
|
198
|
+
* Distinct from the multi-run "partial" concept (see {@link EvalComparisonPartial}).
|
|
109
199
|
*/
|
|
110
200
|
metadata: Record<string, unknown>;
|
|
111
201
|
timestamp: string;
|
|
@@ -148,6 +238,22 @@ type EvalSummary = {
|
|
|
148
238
|
max: number;
|
|
149
239
|
p50: number;
|
|
150
240
|
p95: number;
|
|
241
|
+
/**
|
|
242
|
+
* Number of items that produced a valid numeric score — the sample size
|
|
243
|
+
* the `mean` actually covers. Optional so pre-existing artifacts (and
|
|
244
|
+
* hand-rolled summaries) stay valid; absent ⇒ recompute from `items`.
|
|
245
|
+
* Named `scored` (NOT `n`) to avoid collision with
|
|
246
|
+
* `EvalComparison.scorers[].n` (paired-diff count).
|
|
247
|
+
*/
|
|
248
|
+
scored?: number;
|
|
249
|
+
/**
|
|
250
|
+
* Number of items whose scorer RAN and failed (threw or returned
|
|
251
|
+
* out-of-range). Distinct from items skipped by cancellation, which land
|
|
252
|
+
* in neither bucket — so `scored + failed` is the honest "attempted"
|
|
253
|
+
* denominator and may be `<` the eligible item count. A non-zero `failed`
|
|
254
|
+
* means the `mean` was computed over a thinned sample.
|
|
255
|
+
*/
|
|
256
|
+
failed?: number;
|
|
151
257
|
}>;
|
|
152
258
|
timing?: {
|
|
153
259
|
mean: number;
|
|
@@ -156,6 +262,14 @@ type EvalSummary = {
|
|
|
156
262
|
p50: number;
|
|
157
263
|
p95: number;
|
|
158
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* Populated by `runEval` ONLY when `EvalConfig.failOnScorerErrorRate` is set
|
|
267
|
+
* and one or more scorers exceeded tolerance. `runEval` never throws on this
|
|
268
|
+
* — it returns the (still-useful) result with this flag set, and the CLI /
|
|
269
|
+
* consumer decides whether to fail. Absent / empty ⇒ no degradation gate
|
|
270
|
+
* tripped (either not configured or all scorers within tolerance).
|
|
271
|
+
*/
|
|
272
|
+
degraded?: DegradedScorer[];
|
|
159
273
|
};
|
|
160
274
|
/**
|
|
161
275
|
* Sample-size context for one side of a comparison.
|
|
@@ -214,6 +328,18 @@ type EvalComparison = {
|
|
|
214
328
|
pImprovement?: number;
|
|
215
329
|
/** Number of paired item differences used for CI computation. */
|
|
216
330
|
n?: number;
|
|
331
|
+
/**
|
|
332
|
+
* Per-side scorer success/failure counts over the SAME truncated pool the
|
|
333
|
+
* means and CI are computed from (not a separately-recomputed raw set), so
|
|
334
|
+
* a gate reads numbers consistent with what the table displays. Raw counts
|
|
335
|
+
* (consumer divides) keep this truncation-consistent. A non-zero
|
|
336
|
+
* `*Failed` means that side's mean rests on a thinned sample — the gate-side
|
|
337
|
+
* `--max-scorer-error-rate` flag refuses to certify when it's over tolerance.
|
|
338
|
+
*/
|
|
339
|
+
baselineScored?: number;
|
|
340
|
+
baselineFailed?: number;
|
|
341
|
+
candidateScored?: number;
|
|
342
|
+
candidateFailed?: number;
|
|
217
343
|
}>;
|
|
218
344
|
timing?: {
|
|
219
345
|
baselineMean: number;
|
|
@@ -295,6 +421,48 @@ declare function runEval(config: EvalConfig, executeWorkflow: (input: unknown, r
|
|
|
295
421
|
}>, runtime: AxlRuntime, options?: RunEvalOptions): Promise<EvalResult>;
|
|
296
422
|
|
|
297
423
|
declare function evalCompare(baseline: EvalResult | EvalResult[], candidate: EvalResult | EvalResult[], options?: EvalCompareOptions): EvalComparison;
|
|
424
|
+
/**
|
|
425
|
+
* Decide whether a comparison should be REFUSED under a gate-side scorer
|
|
426
|
+
* failure-rate limit (`axl-eval compare --max-scorer-error-rate`). Pure and
|
|
427
|
+
* testable — returns a human-readable refusal reason, or `null` to allow.
|
|
428
|
+
*
|
|
429
|
+
* Type-aware via the baseline's `scorerTypes` metadata: a scorer EXPLICITLY
|
|
430
|
+
* marked `'deterministic'` tolerates ZERO failures (a deterministic throw is a
|
|
431
|
+
* bug); everything else — including an unknown/absent type on a pre-`scorerTypes`
|
|
432
|
+
* artifact — uses the LLM `limit`. Defaulting unknown types to LLM (the
|
|
433
|
+
* permissive tier) is deliberate: classifying an old artifact's scorers as
|
|
434
|
+
* deterministic would hard-fail them on the first flake, contradicting the
|
|
435
|
+
* `--max-scorer-error-rate <n>` the user passed. Scorer NAMES are identical
|
|
436
|
+
* across sides (enforced by `evalCompare`), so reading types from the baseline
|
|
437
|
+
* is sufficient.
|
|
438
|
+
*
|
|
439
|
+
* Also refuses a scorer that produced ZERO scored items on a side — a mean over
|
|
440
|
+
* an empty sample can't be certified. Counts come from the same truncated pool
|
|
441
|
+
* `evalCompare` used for the means/CI (`baseline/candidateScored/Failed`).
|
|
442
|
+
*/
|
|
443
|
+
declare function evaluateScorerErrorRateGate(comparison: EvalComparison, maxScorerErrorRate: number): string | null;
|
|
444
|
+
|
|
445
|
+
/** A scorer's failure-rate verdict against a tolerance — see {@link evaluateScorerTolerance}. */
|
|
446
|
+
type ScorerToleranceVerdict = {
|
|
447
|
+
/** `scored + failed` — the attempted denominator (excludes cancelled/never-run). */
|
|
448
|
+
attempted: number;
|
|
449
|
+
/** `failed / attempted`, or `0` when nothing was attempted. */
|
|
450
|
+
rate: number;
|
|
451
|
+
/** Over tolerance AND something was actually attempted. */
|
|
452
|
+
exceeds: boolean;
|
|
453
|
+
/** Nothing was attempted (`attempted === 0`) — no basis to certify a rate. */
|
|
454
|
+
zeroSample: boolean;
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* Single source of truth for the type-aware failure-rate decision, shared by the
|
|
458
|
+
* source-side gate (`runEval` → `summary.degraded`) and the gate-side compare
|
|
459
|
+
* gate (`evalCompare` consumers). Deterministic scorers tolerate ZERO failures
|
|
460
|
+
* (a deterministic throw is a bug, not noise); LLM scorers tolerate up to `limit`
|
|
461
|
+
* (`failed / attempted`). `exceeds` is never true when nothing was attempted —
|
|
462
|
+
* callers decide their own policy for `zeroSample` (the runner skips it; the
|
|
463
|
+
* compare gate refuses to certify a zero-sample scorer).
|
|
464
|
+
*/
|
|
465
|
+
declare function evaluateScorerTolerance(scored: number, failed: number, type: 'llm' | 'deterministic', limit: number): ScorerToleranceVerdict;
|
|
298
466
|
|
|
299
467
|
type BootstrapCIResult = {
|
|
300
468
|
/** Lower bound of the confidence interval. */
|
|
@@ -321,7 +489,16 @@ declare function pairedBootstrapCI(differences: number[], options?: {
|
|
|
321
489
|
}): BootstrapCIResult;
|
|
322
490
|
|
|
323
491
|
type RescoreOptions = {
|
|
492
|
+
/** Item-level worker-pool size (how many saved items rescore in parallel). Default 5. */
|
|
324
493
|
concurrency?: number;
|
|
494
|
+
/** Per-item scorer fan-out (how many scorers run concurrently within one item).
|
|
495
|
+
* Default 5 — matches `EvalConfig.scorerConcurrency`. Worst-case concurrent
|
|
496
|
+
* judge calls is `concurrency × scorerConcurrency`. */
|
|
497
|
+
scorerConcurrency?: number;
|
|
498
|
+
/** Abort signal forwarded into ScorerContext so in-flight LLM scorer calls can
|
|
499
|
+
* be cancelled mid-flight. Also checked between items to short-circuit
|
|
500
|
+
* remaining work. Mirrors `RunEvalOptions.signal`. */
|
|
501
|
+
signal?: AbortSignal;
|
|
325
502
|
};
|
|
326
503
|
/**
|
|
327
504
|
* Re-run scorers on the saved outputs of an existing eval result.
|
|
@@ -347,6 +524,10 @@ type MultiRunSummary = {
|
|
|
347
524
|
std: number;
|
|
348
525
|
min: number;
|
|
349
526
|
max: number;
|
|
527
|
+
/** Total valid scores summed across all runs in the group. */
|
|
528
|
+
scored?: number;
|
|
529
|
+
/** Total ran-and-failed scorer invocations summed across all runs. */
|
|
530
|
+
failed?: number;
|
|
350
531
|
}>;
|
|
351
532
|
timing?: {
|
|
352
533
|
mean: number;
|
|
@@ -358,4 +539,4 @@ type MultiRunSummary = {
|
|
|
358
539
|
*/
|
|
359
540
|
declare function aggregateRuns(runs: EvalResult[]): MultiRunSummary;
|
|
360
541
|
|
|
361
|
-
export { type BootstrapCIResult, type Dataset, type DatasetConfig, type DatasetItem, type EvalCompareOptions, type EvalComparison, type EvalComparisonPartial, type EvalConfig, type EvalImprovement, type EvalItem, type EvalProgressEvent, type EvalRegression, type EvalResult, type EvalSummary, type LlmScorerConfig, type MultiRunSummary, type RescoreOptions, type RunEvalOptions, type Scorer, type ScorerConfig, type ScorerContext, type ScorerDetail, type ScorerFn, type ScorerResult, aggregateRuns, dataset, defineEval, evalCompare, llmScorer, normalizeScorerResult, pairedBootstrapCI, rescore, runEval, scorer };
|
|
542
|
+
export { type BootstrapCIResult, type Dataset, type DatasetConfig, type DatasetItem, type DegradedScorer, type EvalCompareOptions, type EvalComparison, type EvalComparisonPartial, type EvalConfig, type EvalImprovement, type EvalItem, type EvalProgressEvent, type EvalRegression, type EvalResult, type EvalSummary, type ExtraKeyPolicy, type LlmScorerConfig, type MultiRunSummary, type RescoreOptions, type RunEvalOptions, type Scorer, type ScorerConfig, type ScorerContext, type ScorerDetail, type ScorerFn, type ScorerResult, type ScorerToleranceVerdict, aggregateRuns, dataset, defineEval, evalCompare, evaluateScorerErrorRateGate, evaluateScorerTolerance, llmScorer, normalizeScorerResult, pairedBootstrapCI, rescore, runEval, scorer };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
2
|
+
import { Provider, Effort, AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
3
3
|
|
|
4
4
|
type DatasetItem<TInput, TAnnotations = undefined> = {
|
|
5
5
|
input: TInput;
|
|
6
6
|
annotations?: TAnnotations;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* What to do when a dataset item's `annotations` contain keys that are absent
|
|
10
|
+
* from the `annotations` schema.
|
|
11
|
+
*
|
|
12
|
+
* Zod object schemas strip unknown keys by default, so an annotation field
|
|
13
|
+
* that isn't declared in the schema is silently dropped before it ever reaches
|
|
14
|
+
* a scorer. A scorer that reads such a field sees `undefined` and quietly
|
|
15
|
+
* degrades to a no-op — the eval still "passes", so the bug is invisible. This
|
|
16
|
+
* is especially easy to hit with file-based datasets, where the JSON is never
|
|
17
|
+
* type-checked.
|
|
18
|
+
*
|
|
19
|
+
* - `'warn'` (default) — log a single `console.warn` per dataset listing the
|
|
20
|
+
* dropped key paths. Non-breaking; surfaces the contract violation.
|
|
21
|
+
* - `'error'` — throw, listing the dropped key paths. The strict path for CI.
|
|
22
|
+
* - `'ignore'` — preserve the silent-strip behavior (skips detection entirely).
|
|
23
|
+
*
|
|
24
|
+
* Only `annotations` are checked, not `input`: stripped input fields flow to
|
|
25
|
+
* the workflow where they surface, whereas stripped annotations vanish into a
|
|
26
|
+
* silent scorer.
|
|
27
|
+
*/
|
|
28
|
+
type ExtraKeyPolicy = 'warn' | 'error' | 'ignore';
|
|
8
29
|
type DatasetConfig<TInput extends z.ZodType, TAnnotations extends z.ZodType = z.ZodType> = {
|
|
9
30
|
name: string;
|
|
10
31
|
schema: TInput;
|
|
@@ -13,11 +34,28 @@ type DatasetConfig<TInput extends z.ZodType, TAnnotations extends z.ZodType = z.
|
|
|
13
34
|
file?: string;
|
|
14
35
|
/** Base directory for resolving relative file paths. Defaults to cwd. */
|
|
15
36
|
basePath?: string;
|
|
37
|
+
/**
|
|
38
|
+
* How to handle annotation keys not present in the `annotations` schema
|
|
39
|
+
* (Zod strips them by default). Defaults to `'warn'`. See {@link ExtraKeyPolicy}.
|
|
40
|
+
*/
|
|
41
|
+
onExtraAnnotationKeys?: ExtraKeyPolicy;
|
|
16
42
|
};
|
|
17
43
|
type Dataset<TInput = unknown, TAnnotations = unknown> = {
|
|
18
44
|
readonly name: string;
|
|
19
45
|
readonly schema: z.ZodType;
|
|
20
46
|
readonly annotationsSchema?: z.ZodType;
|
|
47
|
+
/**
|
|
48
|
+
* Annotation key paths the schema dropped during the most recent
|
|
49
|
+
* `getItems()` call (empty when none were dropped, or when
|
|
50
|
+
* `onExtraAnnotationKeys: 'ignore'`). Populated as a side effect of
|
|
51
|
+
* `getItems()` — read it synchronously right after awaiting, on the same
|
|
52
|
+
* instance; a later `getItems()` overwrites it (calling `getItems()`
|
|
53
|
+
* concurrently on one shared dataset is unsupported). The eval runner folds
|
|
54
|
+
* this into `EvalResult.metadata.droppedAnnotationKeys` so any consumer (the
|
|
55
|
+
* CLI logs it; UIs can render it) sees the same signal. Optional so
|
|
56
|
+
* hand-rolled `Dataset`-shaped objects (test harnesses) remain valid.
|
|
57
|
+
*/
|
|
58
|
+
readonly droppedAnnotationKeys?: readonly string[];
|
|
21
59
|
getItems(): Promise<DatasetItem<TInput, TAnnotations>[]>;
|
|
22
60
|
};
|
|
23
61
|
declare function dataset<TInput extends z.ZodType, TAnnotations extends z.ZodType>(config: DatasetConfig<TInput, TAnnotations>): Dataset<z.infer<TInput>, z.infer<TAnnotations>>;
|
|
@@ -34,24 +72,11 @@ declare function normalizeScorerResult(result: number | ScorerResult): ScorerRes
|
|
|
34
72
|
type ScorerContext = {
|
|
35
73
|
/** Resolve a provider:model URI to a provider instance and model name. */
|
|
36
74
|
resolveProvider: (modelUri: string) => {
|
|
37
|
-
provider:
|
|
38
|
-
chat(messages: {
|
|
39
|
-
role: string;
|
|
40
|
-
content: string;
|
|
41
|
-
}[], options: {
|
|
42
|
-
model: string;
|
|
43
|
-
temperature?: number;
|
|
44
|
-
responseFormat?: {
|
|
45
|
-
type: string;
|
|
46
|
-
json_schema?: unknown;
|
|
47
|
-
};
|
|
48
|
-
}): Promise<{
|
|
49
|
-
content: string;
|
|
50
|
-
cost?: number;
|
|
51
|
-
}>;
|
|
52
|
-
};
|
|
75
|
+
provider: Provider;
|
|
53
76
|
model: string;
|
|
54
77
|
};
|
|
78
|
+
/** Abort signal forwarded from the eval runner; scorers should pass it into provider calls. */
|
|
79
|
+
signal?: AbortSignal;
|
|
55
80
|
};
|
|
56
81
|
type ScorerFn<TOutput, TInput, TAnnotations> = (output: TOutput, input: TInput, annotations?: TAnnotations) => number | ScorerResult;
|
|
57
82
|
type ScorerConfig<TOutput = unknown, TInput = unknown, TAnnotations = unknown> = {
|
|
@@ -76,7 +101,22 @@ type LlmScorerConfig = {
|
|
|
76
101
|
score: number;
|
|
77
102
|
[key: string]: unknown;
|
|
78
103
|
}>;
|
|
104
|
+
/** Sampling temperature. Default: 0.2 for deterministic judging. */
|
|
79
105
|
temperature?: number;
|
|
106
|
+
/** Cap on response tokens. Falls back to provider default if unset. */
|
|
107
|
+
maxTokens?: number;
|
|
108
|
+
/** Reasoning effort — `'high'` or `'max'` materially improves judge calibration on
|
|
109
|
+
* reasoning-capable models (gpt-5.x, Opus 4.6+, Gemini 3.x). See `Effort` for
|
|
110
|
+
* provider-specific mapping. */
|
|
111
|
+
effort?: Effort;
|
|
112
|
+
/** Precise thinking-token budget override. See `ChatOptions.thinkingBudget`. */
|
|
113
|
+
thinkingBudget?: number;
|
|
114
|
+
/** Surface reasoning summaries in the judge's response (Gemini + OpenAI Responses). */
|
|
115
|
+
includeThoughts?: boolean;
|
|
116
|
+
/** Stop sequences forwarded to the provider. */
|
|
117
|
+
stop?: string[];
|
|
118
|
+
/** Provider-specific escape hatch — merged last into the raw API request. */
|
|
119
|
+
providerOptions?: Record<string, unknown>;
|
|
80
120
|
};
|
|
81
121
|
declare function llmScorer(config: LlmScorerConfig): Scorer;
|
|
82
122
|
|
|
@@ -84,10 +124,53 @@ type EvalConfig = {
|
|
|
84
124
|
workflow: string;
|
|
85
125
|
dataset: Dataset<unknown, unknown>;
|
|
86
126
|
scorers: Scorer<unknown, unknown, unknown>[];
|
|
127
|
+
/** Item-level worker-pool size (how many dataset items run in parallel). Default 5. */
|
|
87
128
|
concurrency?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Per-item scorer fan-out — how many of an item's scorers run concurrently.
|
|
131
|
+
* Default 5 (matches `concurrency`; scorers are parallel-by-default). The
|
|
132
|
+
* worst-case number of concurrent scorer calls is `concurrency × scorerConcurrency`,
|
|
133
|
+
* so lower `concurrency` if a rate-limited judge model needs a tighter ceiling.
|
|
134
|
+
*/
|
|
135
|
+
scorerConcurrency?: number;
|
|
88
136
|
budget?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Opt-in source-side trust gate. When set (0–1), `runEval` flags the run as
|
|
139
|
+
* `summary.degraded` if any scorer's failure rate exceeds tolerance, and the
|
|
140
|
+
* CLI exits non-zero. Enforcement is **type-aware**: deterministic scorers
|
|
141
|
+
* tolerate ZERO failures (a deterministic scorer that throws is a bug, not
|
|
142
|
+
* noise), while LLM scorers use this rate (a flaky judge / rate-limit storm).
|
|
143
|
+
*
|
|
144
|
+
* Failure rate = `failed / (scored + failed)` per scorer, where `scored` is
|
|
145
|
+
* the number of items that produced a valid numeric score and `failed` is the
|
|
146
|
+
* number whose scorer ran and threw / returned out-of-range. `0` means "any
|
|
147
|
+
* LLM failure degrades the run". Invalid values (non-finite, <0, >1) are
|
|
148
|
+
* ignored with a `console.warn`.
|
|
149
|
+
*
|
|
150
|
+
* Distinct from the gate-side `--max-scorer-error-rate` compare flag: this one
|
|
151
|
+
* fires at run/produce time (catch a thinned sample before it's saved), the
|
|
152
|
+
* other at consume/gate time (refuse to certify a thinned baseline/candidate).
|
|
153
|
+
*/
|
|
154
|
+
failOnScorerErrorRate?: number;
|
|
89
155
|
metadata?: Record<string, unknown>;
|
|
90
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* One scorer whose failure rate tripped `EvalConfig.failOnScorerErrorRate`.
|
|
159
|
+
* Surfaced on `EvalSummary.degraded` so consumers (CLI exit code, Studio
|
|
160
|
+
* banner) can refuse to trust the mean without re-deriving the rate.
|
|
161
|
+
*/
|
|
162
|
+
type DegradedScorer = {
|
|
163
|
+
scorer: string;
|
|
164
|
+
/** Observed failure rate `failed / (scored + failed)` (0 when nothing ran). */
|
|
165
|
+
rate: number;
|
|
166
|
+
/** The tolerance that was exceeded (0 for deterministic scorers). */
|
|
167
|
+
limit: number;
|
|
168
|
+
type: 'llm' | 'deterministic';
|
|
169
|
+
/** Items that produced a valid numeric score. */
|
|
170
|
+
scored: number;
|
|
171
|
+
/** Items whose scorer ran and failed (threw / out-of-range). */
|
|
172
|
+
failed: number;
|
|
173
|
+
};
|
|
91
174
|
type EvalResult = {
|
|
92
175
|
id: string;
|
|
93
176
|
/**
|
|
@@ -106,6 +189,13 @@ type EvalResult = {
|
|
|
106
189
|
* that need a single "primary" workflow should use `workflows[0]`.
|
|
107
190
|
* - `scorerTypes: Record<string, 'llm' | 'deterministic'>`
|
|
108
191
|
* - `runGroupId?: string`, `runIndex?: number` for multi-run groups
|
|
192
|
+
* - `droppedAnnotationKeys?: string[]` — annotation key paths the dataset
|
|
193
|
+
* schema stripped (present only when non-empty). Mirrors the dataset's
|
|
194
|
+
* `console.warn`; lets any consumer surface the same signal.
|
|
195
|
+
* - `scorerFiltered?: boolean` + `scorersRun?: string[]` — set when the CLI
|
|
196
|
+
* `--scorers` flag ran a subset of scorers. A guard so a filtered run isn't
|
|
197
|
+
* silently used as a full baseline (`compare` warns / refuses to gate on it).
|
|
198
|
+
* Distinct from the multi-run "partial" concept (see {@link EvalComparisonPartial}).
|
|
109
199
|
*/
|
|
110
200
|
metadata: Record<string, unknown>;
|
|
111
201
|
timestamp: string;
|
|
@@ -148,6 +238,22 @@ type EvalSummary = {
|
|
|
148
238
|
max: number;
|
|
149
239
|
p50: number;
|
|
150
240
|
p95: number;
|
|
241
|
+
/**
|
|
242
|
+
* Number of items that produced a valid numeric score — the sample size
|
|
243
|
+
* the `mean` actually covers. Optional so pre-existing artifacts (and
|
|
244
|
+
* hand-rolled summaries) stay valid; absent ⇒ recompute from `items`.
|
|
245
|
+
* Named `scored` (NOT `n`) to avoid collision with
|
|
246
|
+
* `EvalComparison.scorers[].n` (paired-diff count).
|
|
247
|
+
*/
|
|
248
|
+
scored?: number;
|
|
249
|
+
/**
|
|
250
|
+
* Number of items whose scorer RAN and failed (threw or returned
|
|
251
|
+
* out-of-range). Distinct from items skipped by cancellation, which land
|
|
252
|
+
* in neither bucket — so `scored + failed` is the honest "attempted"
|
|
253
|
+
* denominator and may be `<` the eligible item count. A non-zero `failed`
|
|
254
|
+
* means the `mean` was computed over a thinned sample.
|
|
255
|
+
*/
|
|
256
|
+
failed?: number;
|
|
151
257
|
}>;
|
|
152
258
|
timing?: {
|
|
153
259
|
mean: number;
|
|
@@ -156,6 +262,14 @@ type EvalSummary = {
|
|
|
156
262
|
p50: number;
|
|
157
263
|
p95: number;
|
|
158
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* Populated by `runEval` ONLY when `EvalConfig.failOnScorerErrorRate` is set
|
|
267
|
+
* and one or more scorers exceeded tolerance. `runEval` never throws on this
|
|
268
|
+
* — it returns the (still-useful) result with this flag set, and the CLI /
|
|
269
|
+
* consumer decides whether to fail. Absent / empty ⇒ no degradation gate
|
|
270
|
+
* tripped (either not configured or all scorers within tolerance).
|
|
271
|
+
*/
|
|
272
|
+
degraded?: DegradedScorer[];
|
|
159
273
|
};
|
|
160
274
|
/**
|
|
161
275
|
* Sample-size context for one side of a comparison.
|
|
@@ -214,6 +328,18 @@ type EvalComparison = {
|
|
|
214
328
|
pImprovement?: number;
|
|
215
329
|
/** Number of paired item differences used for CI computation. */
|
|
216
330
|
n?: number;
|
|
331
|
+
/**
|
|
332
|
+
* Per-side scorer success/failure counts over the SAME truncated pool the
|
|
333
|
+
* means and CI are computed from (not a separately-recomputed raw set), so
|
|
334
|
+
* a gate reads numbers consistent with what the table displays. Raw counts
|
|
335
|
+
* (consumer divides) keep this truncation-consistent. A non-zero
|
|
336
|
+
* `*Failed` means that side's mean rests on a thinned sample — the gate-side
|
|
337
|
+
* `--max-scorer-error-rate` flag refuses to certify when it's over tolerance.
|
|
338
|
+
*/
|
|
339
|
+
baselineScored?: number;
|
|
340
|
+
baselineFailed?: number;
|
|
341
|
+
candidateScored?: number;
|
|
342
|
+
candidateFailed?: number;
|
|
217
343
|
}>;
|
|
218
344
|
timing?: {
|
|
219
345
|
baselineMean: number;
|
|
@@ -295,6 +421,48 @@ declare function runEval(config: EvalConfig, executeWorkflow: (input: unknown, r
|
|
|
295
421
|
}>, runtime: AxlRuntime, options?: RunEvalOptions): Promise<EvalResult>;
|
|
296
422
|
|
|
297
423
|
declare function evalCompare(baseline: EvalResult | EvalResult[], candidate: EvalResult | EvalResult[], options?: EvalCompareOptions): EvalComparison;
|
|
424
|
+
/**
|
|
425
|
+
* Decide whether a comparison should be REFUSED under a gate-side scorer
|
|
426
|
+
* failure-rate limit (`axl-eval compare --max-scorer-error-rate`). Pure and
|
|
427
|
+
* testable — returns a human-readable refusal reason, or `null` to allow.
|
|
428
|
+
*
|
|
429
|
+
* Type-aware via the baseline's `scorerTypes` metadata: a scorer EXPLICITLY
|
|
430
|
+
* marked `'deterministic'` tolerates ZERO failures (a deterministic throw is a
|
|
431
|
+
* bug); everything else — including an unknown/absent type on a pre-`scorerTypes`
|
|
432
|
+
* artifact — uses the LLM `limit`. Defaulting unknown types to LLM (the
|
|
433
|
+
* permissive tier) is deliberate: classifying an old artifact's scorers as
|
|
434
|
+
* deterministic would hard-fail them on the first flake, contradicting the
|
|
435
|
+
* `--max-scorer-error-rate <n>` the user passed. Scorer NAMES are identical
|
|
436
|
+
* across sides (enforced by `evalCompare`), so reading types from the baseline
|
|
437
|
+
* is sufficient.
|
|
438
|
+
*
|
|
439
|
+
* Also refuses a scorer that produced ZERO scored items on a side — a mean over
|
|
440
|
+
* an empty sample can't be certified. Counts come from the same truncated pool
|
|
441
|
+
* `evalCompare` used for the means/CI (`baseline/candidateScored/Failed`).
|
|
442
|
+
*/
|
|
443
|
+
declare function evaluateScorerErrorRateGate(comparison: EvalComparison, maxScorerErrorRate: number): string | null;
|
|
444
|
+
|
|
445
|
+
/** A scorer's failure-rate verdict against a tolerance — see {@link evaluateScorerTolerance}. */
|
|
446
|
+
type ScorerToleranceVerdict = {
|
|
447
|
+
/** `scored + failed` — the attempted denominator (excludes cancelled/never-run). */
|
|
448
|
+
attempted: number;
|
|
449
|
+
/** `failed / attempted`, or `0` when nothing was attempted. */
|
|
450
|
+
rate: number;
|
|
451
|
+
/** Over tolerance AND something was actually attempted. */
|
|
452
|
+
exceeds: boolean;
|
|
453
|
+
/** Nothing was attempted (`attempted === 0`) — no basis to certify a rate. */
|
|
454
|
+
zeroSample: boolean;
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* Single source of truth for the type-aware failure-rate decision, shared by the
|
|
458
|
+
* source-side gate (`runEval` → `summary.degraded`) and the gate-side compare
|
|
459
|
+
* gate (`evalCompare` consumers). Deterministic scorers tolerate ZERO failures
|
|
460
|
+
* (a deterministic throw is a bug, not noise); LLM scorers tolerate up to `limit`
|
|
461
|
+
* (`failed / attempted`). `exceeds` is never true when nothing was attempted —
|
|
462
|
+
* callers decide their own policy for `zeroSample` (the runner skips it; the
|
|
463
|
+
* compare gate refuses to certify a zero-sample scorer).
|
|
464
|
+
*/
|
|
465
|
+
declare function evaluateScorerTolerance(scored: number, failed: number, type: 'llm' | 'deterministic', limit: number): ScorerToleranceVerdict;
|
|
298
466
|
|
|
299
467
|
type BootstrapCIResult = {
|
|
300
468
|
/** Lower bound of the confidence interval. */
|
|
@@ -321,7 +489,16 @@ declare function pairedBootstrapCI(differences: number[], options?: {
|
|
|
321
489
|
}): BootstrapCIResult;
|
|
322
490
|
|
|
323
491
|
type RescoreOptions = {
|
|
492
|
+
/** Item-level worker-pool size (how many saved items rescore in parallel). Default 5. */
|
|
324
493
|
concurrency?: number;
|
|
494
|
+
/** Per-item scorer fan-out (how many scorers run concurrently within one item).
|
|
495
|
+
* Default 5 — matches `EvalConfig.scorerConcurrency`. Worst-case concurrent
|
|
496
|
+
* judge calls is `concurrency × scorerConcurrency`. */
|
|
497
|
+
scorerConcurrency?: number;
|
|
498
|
+
/** Abort signal forwarded into ScorerContext so in-flight LLM scorer calls can
|
|
499
|
+
* be cancelled mid-flight. Also checked between items to short-circuit
|
|
500
|
+
* remaining work. Mirrors `RunEvalOptions.signal`. */
|
|
501
|
+
signal?: AbortSignal;
|
|
325
502
|
};
|
|
326
503
|
/**
|
|
327
504
|
* Re-run scorers on the saved outputs of an existing eval result.
|
|
@@ -347,6 +524,10 @@ type MultiRunSummary = {
|
|
|
347
524
|
std: number;
|
|
348
525
|
min: number;
|
|
349
526
|
max: number;
|
|
527
|
+
/** Total valid scores summed across all runs in the group. */
|
|
528
|
+
scored?: number;
|
|
529
|
+
/** Total ran-and-failed scorer invocations summed across all runs. */
|
|
530
|
+
failed?: number;
|
|
350
531
|
}>;
|
|
351
532
|
timing?: {
|
|
352
533
|
mean: number;
|
|
@@ -358,4 +539,4 @@ type MultiRunSummary = {
|
|
|
358
539
|
*/
|
|
359
540
|
declare function aggregateRuns(runs: EvalResult[]): MultiRunSummary;
|
|
360
541
|
|
|
361
|
-
export { type BootstrapCIResult, type Dataset, type DatasetConfig, type DatasetItem, type EvalCompareOptions, type EvalComparison, type EvalComparisonPartial, type EvalConfig, type EvalImprovement, type EvalItem, type EvalProgressEvent, type EvalRegression, type EvalResult, type EvalSummary, type LlmScorerConfig, type MultiRunSummary, type RescoreOptions, type RunEvalOptions, type Scorer, type ScorerConfig, type ScorerContext, type ScorerDetail, type ScorerFn, type ScorerResult, aggregateRuns, dataset, defineEval, evalCompare, llmScorer, normalizeScorerResult, pairedBootstrapCI, rescore, runEval, scorer };
|
|
542
|
+
export { type BootstrapCIResult, type Dataset, type DatasetConfig, type DatasetItem, type DegradedScorer, type EvalCompareOptions, type EvalComparison, type EvalComparisonPartial, type EvalConfig, type EvalImprovement, type EvalItem, type EvalProgressEvent, type EvalRegression, type EvalResult, type EvalSummary, type ExtraKeyPolicy, type LlmScorerConfig, type MultiRunSummary, type RescoreOptions, type RunEvalOptions, type Scorer, type ScorerConfig, type ScorerContext, type ScorerDetail, type ScorerFn, type ScorerResult, type ScorerToleranceVerdict, aggregateRuns, dataset, defineEval, evalCompare, evaluateScorerErrorRateGate, evaluateScorerTolerance, llmScorer, normalizeScorerResult, pairedBootstrapCI, rescore, runEval, scorer };
|