@axlsdk/eval 0.23.3 → 0.24.1
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 +110 -9
- package/dist/chunk-HKEAPM4B.js +1799 -0
- package/dist/chunk-HKEAPM4B.js.map +1 -0
- package/dist/cli.cjs +1518 -537
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +277 -57
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1010 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +546 -10
- package/dist/index.d.ts +546 -10
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-AFUNZE7W.js +0 -966
- package/dist/chunk-AFUNZE7W.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Provider, Effort, AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
2
|
+
import { Provider, Effort, ArtifactManifest, CapturedRequestRecord, Accounting, AxlEvent, AxlRuntime } from '@axlsdk/axl';
|
|
3
3
|
|
|
4
4
|
type DatasetItem<TInput, TAnnotations = undefined> = {
|
|
5
5
|
input: TInput;
|
|
@@ -173,6 +173,193 @@ type LlmScorerConfig = {
|
|
|
173
173
|
};
|
|
174
174
|
declare function llmScorer(config: LlmScorerConfig): Scorer;
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The eval side of opt-in request capture: the JSONL codec, the manifest an
|
|
178
|
+
* `EvalResult` carries, and the bundle validation that keeps an imported
|
|
179
|
+
* artifact from naming anything but an artifact id.
|
|
180
|
+
*
|
|
181
|
+
* The division of labour with the core is deliberate. `@axlsdk/axl` owns the
|
|
182
|
+
* measurement-adjacent parts — snapshotting the request before dispatch,
|
|
183
|
+
* redaction, byte bounds, the non-blocking queue, and the artifact lifecycle —
|
|
184
|
+
* because all of those must hold for ANY consumer of `trackOutcome`, not just
|
|
185
|
+
* evals. This module owns what is eval-shaped: which run/case/scorer a record
|
|
186
|
+
* belongs to, how a run reports what it managed to capture, and what an
|
|
187
|
+
* exported bundle is allowed to contain.
|
|
188
|
+
*
|
|
189
|
+
* `RequestRecord` is the core record type by alias rather than by copy. Two
|
|
190
|
+
* hand-maintained declarations of a wire format drift, and the drift is
|
|
191
|
+
* invisible until someone tries to read last month's artifact.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/** One JSONL line of a captured-request artifact. Codec version 1. */
|
|
195
|
+
type RequestRecord = CapturedRequestRecord;
|
|
196
|
+
/**
|
|
197
|
+
* A pointer from an item or a scorer to one captured operation.
|
|
198
|
+
*
|
|
199
|
+
* `status` is the honest part: `'recorded'` is a complete request/response
|
|
200
|
+
* pair, `'start_only'` is a call that never came back (the one you most want
|
|
201
|
+
* to see), `'truncated'` is a record replaced by a stub because it exceeded the
|
|
202
|
+
* per-record byte bound, and `'omitted'` is an operation capture never reached.
|
|
203
|
+
*/
|
|
204
|
+
type OperationRef = {
|
|
205
|
+
operationId: string;
|
|
206
|
+
kind: 'chat' | 'stream';
|
|
207
|
+
turn?: number;
|
|
208
|
+
attempt?: number;
|
|
209
|
+
status: 'recorded' | 'start_only' | 'truncated' | 'omitted';
|
|
210
|
+
};
|
|
211
|
+
/** What an `EvalResult` says about its captured requests. */
|
|
212
|
+
type DiagnosticManifest = {
|
|
213
|
+
version: 1;
|
|
214
|
+
artifactId: string;
|
|
215
|
+
fidelity: 'runtime_request';
|
|
216
|
+
status: ArtifactManifest['status'];
|
|
217
|
+
reason?: string;
|
|
218
|
+
records: number;
|
|
219
|
+
bytes: number;
|
|
220
|
+
redaction: 'applied' | 'none';
|
|
221
|
+
expiresAt?: number;
|
|
222
|
+
};
|
|
223
|
+
/** Per-run capture limits, as `runEval`/`rescore` accept them. */
|
|
224
|
+
type CaptureRequestsOption = boolean | {
|
|
225
|
+
maxRecordBytes?: number;
|
|
226
|
+
maxRunBytes?: number;
|
|
227
|
+
maxQueueBytes?: number;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Default bytes a rescore may copy from the source run's artifact.
|
|
231
|
+
*
|
|
232
|
+
* The same ceiling as a run's own capture budget: a rescore's copied evidence
|
|
233
|
+
* should never be able to dwarf the evidence a fresh run is allowed to produce.
|
|
234
|
+
*/
|
|
235
|
+
declare const DEFAULT_COPY_MAX_BYTES: number;
|
|
236
|
+
/** Normalize the option into explicit limits, or `undefined` when capture is off. */
|
|
237
|
+
declare function resolveCaptureLimits(option: CaptureRequestsOption | undefined): {
|
|
238
|
+
maxRecordBytes?: number;
|
|
239
|
+
maxRunBytes?: number;
|
|
240
|
+
maxQueueBytes?: number;
|
|
241
|
+
} | undefined;
|
|
242
|
+
/** Build the result-level manifest from the store's finalized manifest. */
|
|
243
|
+
declare function toDiagnosticManifest(manifest: ArtifactManifest): DiagnosticManifest;
|
|
244
|
+
/** A manifest for a run whose capture could not be produced at all. */
|
|
245
|
+
declare function unavailableManifest(artifactId: string, reason: string): DiagnosticManifest;
|
|
246
|
+
/** Default ceiling on an imported `.requests.jsonl` sidecar. */
|
|
247
|
+
declare const DEFAULT_SIDECAR_MAX_BYTES: number;
|
|
248
|
+
type SidecarValidation = {
|
|
249
|
+
ok: true;
|
|
250
|
+
lines: string[];
|
|
251
|
+
bytes: number;
|
|
252
|
+
} | {
|
|
253
|
+
ok: false;
|
|
254
|
+
reason: string;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Validate an imported JSONL sidecar before a single byte of it is stored.
|
|
258
|
+
*
|
|
259
|
+
* Imports are the one place where a stranger's JSON reaches this subsystem, so
|
|
260
|
+
* the rules are deliberately narrow: every line must parse, must declare `v: 1`,
|
|
261
|
+
* and must carry the identity fields a reader indexes on. Anything else — a
|
|
262
|
+
* `file://` reference, a `path` field, a record for a codec version we cannot
|
|
263
|
+
* interpret — is refused rather than stored and puzzled over later.
|
|
264
|
+
*
|
|
265
|
+
* Note what is NOT here: nothing in a record can name a filesystem path or a
|
|
266
|
+
* URL to fetch, because the reader never dereferences anything. Records are
|
|
267
|
+
* re-staged under a NEW artifact id owned by the NEW history row, so an
|
|
268
|
+
* imported reference can only ever point at bytes this runtime wrote itself.
|
|
269
|
+
*/
|
|
270
|
+
declare function validateRequestSidecar(text: unknown, options?: {
|
|
271
|
+
maxBytes?: number;
|
|
272
|
+
maxRecordBytes?: number;
|
|
273
|
+
}): SidecarValidation;
|
|
274
|
+
/** Parse a validated sidecar into records. */
|
|
275
|
+
declare function parseRequestRecords(lines: readonly string[]): RequestRecord[];
|
|
276
|
+
/** Encode records back into a sidecar body. */
|
|
277
|
+
declare function serializeRequestRecords(records: readonly RequestRecord[]): string;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* How one dataset item ended. Absent only on artifacts written before 0.24.
|
|
281
|
+
*
|
|
282
|
+
* The four non-`completed` values are deliberately NOT interchangeable: a
|
|
283
|
+
* model/workflow failure, a caller cancellation, a case never started because
|
|
284
|
+
* the budget had closed, and a case stopped mid-flight by a denied operation
|
|
285
|
+
* are four different facts about the run, and collapsing them makes a
|
|
286
|
+
* budget-truncated run look like a broken model.
|
|
287
|
+
*/
|
|
288
|
+
type EvalItemOutcome =
|
|
289
|
+
/** Ran to completion (its scorers may still have been stopped). */
|
|
290
|
+
'completed'
|
|
291
|
+
/** The workflow threw — including a user `ctx.budget` block, which is user logic. */
|
|
292
|
+
| 'failed'
|
|
293
|
+
/** The caller's `AbortSignal` fired. */
|
|
294
|
+
| 'cancelled'
|
|
295
|
+
/** Never started: the run budget had already closed. No operations, no spend. */
|
|
296
|
+
| 'budget_skipped'
|
|
297
|
+
/** Started and charged, then a further operation was denied by the budget. */
|
|
298
|
+
| 'budget_interrupted';
|
|
299
|
+
/** How one scorer ended for one item. See {@link EvalItemOutcome} for the rationale. */
|
|
300
|
+
type ScorerOutcome =
|
|
301
|
+
/** Produced a valid numeric score. */
|
|
302
|
+
'scored'
|
|
303
|
+
/** Ran and threw, or returned an out-of-range score. */
|
|
304
|
+
| 'failed'
|
|
305
|
+
/** Its `applies` predicate returned `false` — deliberately not run. */
|
|
306
|
+
| 'skipped'
|
|
307
|
+
/** The caller's `AbortSignal` fired before or during the call. */
|
|
308
|
+
| 'cancelled'
|
|
309
|
+
/** A paid judge that was never started because the budget had closed. */
|
|
310
|
+
| 'budget_skipped'
|
|
311
|
+
/** Started, then hit a denied operation. */
|
|
312
|
+
| 'budget_interrupted';
|
|
313
|
+
/** The run budget's terminal state, persisted on {@link EvalAccounting}. */
|
|
314
|
+
type EvalBudgetStatus = {
|
|
315
|
+
/** The configured USD threshold. */
|
|
316
|
+
limit: number;
|
|
317
|
+
status: 'open' | 'closed';
|
|
318
|
+
/** Known spend observed under the controller — `accounting.knownCost`. */
|
|
319
|
+
knownSpend: number;
|
|
320
|
+
/**
|
|
321
|
+
* `max(0, knownSpend - limit)`. A threshold is not a reservation: work
|
|
322
|
+
* already dispatched when the limit was crossed still settles, so a run can
|
|
323
|
+
* legitimately end above its limit. This says by how much rather than
|
|
324
|
+
* clamping the reported total.
|
|
325
|
+
*/
|
|
326
|
+
knownOvershoot: number;
|
|
327
|
+
/** Which scheduling decision first observed the closure. */
|
|
328
|
+
closedBy?: 'case' | 'scorer' | 'operation';
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* A run's authoritative accounting: the core {@link Accounting} record plus the
|
|
332
|
+
* eval-specific context needed to read it honestly.
|
|
333
|
+
*/
|
|
334
|
+
type EvalAccounting = Accounting & {
|
|
335
|
+
/** `'run'` for `runEval`, `'rescore'` for `rescore` (judging-only spend). */
|
|
336
|
+
scope: 'run' | 'rescore';
|
|
337
|
+
/** Present exactly when a budget was configured. */
|
|
338
|
+
budget?: EvalBudgetStatus;
|
|
339
|
+
/**
|
|
340
|
+
* Rescore provenance: the source run and its UNMODIFIED generation
|
|
341
|
+
* accounting, or `null` when the source was a legacy artifact that carried
|
|
342
|
+
* none. The original spend is never added to the rescore's own total.
|
|
343
|
+
*/
|
|
344
|
+
source?: {
|
|
345
|
+
runId: string;
|
|
346
|
+
generation: Accounting | null;
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Legacy caller-reported values observed during the run. Inspection only —
|
|
350
|
+
* never summed into `knownCost`, which measures Axl-observed operations.
|
|
351
|
+
*/
|
|
352
|
+
callerReported?: {
|
|
353
|
+
costItems: number;
|
|
354
|
+
costTotal: number;
|
|
355
|
+
metadataItems: number;
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
/** Per-outcome counts for a run. Every key is present, including zeros. */
|
|
359
|
+
type EvalCoverage = {
|
|
360
|
+
items: Record<EvalItemOutcome, number>;
|
|
361
|
+
scorers: Record<string, Record<ScorerOutcome, number>>;
|
|
362
|
+
};
|
|
176
363
|
type EvalConfig = {
|
|
177
364
|
workflow: string;
|
|
178
365
|
dataset: Dataset<unknown, unknown>;
|
|
@@ -186,6 +373,17 @@ type EvalConfig = {
|
|
|
186
373
|
* so lower `concurrency` if a rate-limited judge model needs a tighter ceiling.
|
|
187
374
|
*/
|
|
188
375
|
scorerConcurrency?: number;
|
|
376
|
+
/**
|
|
377
|
+
* Known-spend threshold for the whole run, e.g. `'$1'`, `'1'`, `'0.50'`.
|
|
378
|
+
*
|
|
379
|
+
* Validated BEFORE the dataset is loaded — a malformed value throws
|
|
380
|
+
* `AxlError('INVALID_BUDGET')` before any item runs or any provider is
|
|
381
|
+
* called. Admission closes at `knownSpend >= limit` (so `'$0'` admits
|
|
382
|
+
* nothing), which stops new cases, new paid judges, and new instrumented
|
|
383
|
+
* operations inside cases that are already running. Work already dispatched
|
|
384
|
+
* settles and is still counted, so the final total can exceed the limit —
|
|
385
|
+
* `accounting.budget.knownOvershoot` says by how much.
|
|
386
|
+
*/
|
|
189
387
|
budget?: string;
|
|
190
388
|
/**
|
|
191
389
|
* Opt-in source-side trust gate. When set (0–1), `runEval` flags the run as
|
|
@@ -205,8 +403,45 @@ type EvalConfig = {
|
|
|
205
403
|
* other at consume/gate time (refuse to certify a thinned baseline/candidate).
|
|
206
404
|
*/
|
|
207
405
|
failOnScorerErrorRate?: number;
|
|
406
|
+
/**
|
|
407
|
+
* Source-side coverage gate, **on by default** at `0.05`. `runEval` records
|
|
408
|
+
* `summary.itemErrorRate` whenever an item failed, and marks it `exceeded`
|
|
409
|
+
* when more than this fraction of the attempted items failed; the CLI then
|
|
410
|
+
* exits non-zero. It exists because a run that lost most of its items to
|
|
411
|
+
* throttling or an incident otherwise exits clean and is scored over the
|
|
412
|
+
* survivors.
|
|
413
|
+
*
|
|
414
|
+
* Rate = `coverage.items.failed / (count − cancelled − budget_skipped −
|
|
415
|
+
* budget_interrupted)`: cancelled and budget-stopped items are reported by
|
|
416
|
+
* their own gates and never count twice. Fires on strictly `>`, so `1`
|
|
417
|
+
* disables it (the CLI flag `--max-item-error-rate` overrides this value).
|
|
418
|
+
* A run with nothing attempted never fires.
|
|
419
|
+
*
|
|
420
|
+
* Unlike `failOnScorerErrorRate`, an invalid value (non-number, non-finite,
|
|
421
|
+
* `< 0` or `> 1`) makes `runEval` THROW `AxlError('INVALID_ITEM_ERROR_RATE')`
|
|
422
|
+
* before the dataset loads: warning and skipping would silently switch off a
|
|
423
|
+
* default-on gate.
|
|
424
|
+
*/
|
|
425
|
+
failOnItemErrorRate?: number;
|
|
208
426
|
metadata?: Record<string, unknown>;
|
|
209
427
|
};
|
|
428
|
+
/**
|
|
429
|
+
* A run's item error rate against its `failOnItemErrorRate` limit, on
|
|
430
|
+
* `EvalSummary.itemErrorRate`. See {@link EvalConfig.failOnItemErrorRate} for
|
|
431
|
+
* the definition.
|
|
432
|
+
*/
|
|
433
|
+
type ItemErrorRate = {
|
|
434
|
+
/** Items whose workflow threw — `coverage.items.failed`. */
|
|
435
|
+
failed: number;
|
|
436
|
+
/** `count − cancelled − budget_skipped − budget_interrupted`. */
|
|
437
|
+
attempted: number;
|
|
438
|
+
/** `failed / attempted`; `0` when nothing was attempted. */
|
|
439
|
+
rate: number;
|
|
440
|
+
/** The limit in force for this run (config or CLI flag, default `0.05`). */
|
|
441
|
+
limit: number;
|
|
442
|
+
/** `rate > limit` with at least one attempted item. */
|
|
443
|
+
exceeded: boolean;
|
|
444
|
+
};
|
|
210
445
|
/**
|
|
211
446
|
* One scorer whose failure rate tripped `EvalConfig.failOnScorerErrorRate`.
|
|
212
447
|
* Surfaced on `EvalSummary.degraded` so consumers (CLI exit code, Studio
|
|
@@ -252,9 +487,32 @@ type EvalResult = {
|
|
|
252
487
|
*/
|
|
253
488
|
metadata: Record<string, unknown>;
|
|
254
489
|
timestamp: string;
|
|
490
|
+
/**
|
|
491
|
+
* Compatibility view of `accounting.knownCost` — the sum of settled, disjoint
|
|
492
|
+
* charges Axl observed. A caller-reported `cost` is NEVER part of it (read
|
|
493
|
+
* `accounting.callerReported` for those). Lower bound when `unpriced` is set.
|
|
494
|
+
*/
|
|
255
495
|
totalCost: number;
|
|
256
|
-
/** True when `totalCost` is a lower bound
|
|
496
|
+
/** True when `totalCost` is a lower bound — present iff `accounting.completeness !== 'complete'`. */
|
|
257
497
|
unpriced?: boolean;
|
|
498
|
+
/**
|
|
499
|
+
* The authoritative record `totalCost` / `unpriced` are derived from.
|
|
500
|
+
* Required on results this version produces; optional so a pre-0.24 artifact
|
|
501
|
+
* still types. Read it through `readAccounting()`, which reports an absent
|
|
502
|
+
* record as `'unverified'` instead of silently treating it as complete.
|
|
503
|
+
*/
|
|
504
|
+
accounting?: EvalAccounting;
|
|
505
|
+
/**
|
|
506
|
+
* What this run captured of the requests it submitted, when
|
|
507
|
+
* `captureRequests` was on. Present only for a capturing run — the default
|
|
508
|
+
* artifact carries no `diagnostics` key at all.
|
|
509
|
+
*
|
|
510
|
+
* It is a POINTER plus a self-assessment, never content: `artifactId` names
|
|
511
|
+
* bytes owned by the runtime that saved this result, and `status`/`reason`
|
|
512
|
+
* say whether those bytes are complete, truncated, interrupted or
|
|
513
|
+
* unavailable. Numeric results stay readable when the artifact is gone.
|
|
514
|
+
*/
|
|
515
|
+
diagnostics?: DiagnosticManifest;
|
|
258
516
|
duration: number;
|
|
259
517
|
items: EvalItem[];
|
|
260
518
|
summary: EvalSummary;
|
|
@@ -263,7 +521,24 @@ type ScorerDetail = {
|
|
|
263
521
|
score: number | null;
|
|
264
522
|
metadata?: Record<string, unknown>;
|
|
265
523
|
duration?: number;
|
|
524
|
+
/**
|
|
525
|
+
* Compatibility view of `accounting.knownCost` for this scorer. Falls back to
|
|
526
|
+
* a scorer-returned `cost` ONLY when the runtime measured nothing at all
|
|
527
|
+
* (no `trackOutcome`); a caller value never overrides a measured one and is
|
|
528
|
+
* never summed into any total.
|
|
529
|
+
*/
|
|
266
530
|
cost?: number;
|
|
531
|
+
/** How this scorer ended for this item. Absent only on pre-0.24 artifacts. */
|
|
532
|
+
outcome?: ScorerOutcome;
|
|
533
|
+
/** This scorer's own operations for this item (judging spend, plus any
|
|
534
|
+
* `externalOperation` it declared). Absent on pre-0.24 artifacts. */
|
|
535
|
+
accounting?: Accounting;
|
|
536
|
+
/** The captured operations this judge performed for this item. Present only
|
|
537
|
+
* under `captureRequests`; references only, resolved through the run's
|
|
538
|
+
* artifact. */
|
|
539
|
+
diagnostics?: {
|
|
540
|
+
operations: OperationRef[];
|
|
541
|
+
};
|
|
267
542
|
/**
|
|
268
543
|
* `true` when the scorer's `applies` predicate returned `false` for this item,
|
|
269
544
|
* so the scorer was deliberately skipped (NOT run). Distinct from a `null`
|
|
@@ -320,11 +595,15 @@ type TimingStats = {
|
|
|
320
595
|
/**
|
|
321
596
|
* Per-model latency stats across a run, on `EvalSummary.modelTiming`.
|
|
322
597
|
*
|
|
323
|
-
* Every field here is a distribution over **per-call** values, pooled across
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
598
|
+
* Every field here is a distribution over **per-call** values, pooled across
|
|
599
|
+
* every successful provider call the run made — the same population `calls`
|
|
600
|
+
* counts. Since 0.24 that includes the calls made by items that later FAILED or
|
|
601
|
+
* were stopped on budget: those calls really happened and really took that long,
|
|
602
|
+
* and dropping them would bias the latency of exactly the models whose slowness
|
|
603
|
+
* caused the timeouts. One provider call is one sample, so an item that makes
|
|
604
|
+
* ten calls weighs ten times an item that makes one. That is the right
|
|
605
|
+
* weighting for a model comparison: these numbers describe the model's latency,
|
|
606
|
+
* not the item's.
|
|
328
607
|
*
|
|
329
608
|
* This is deliberately a different weighting from the wall-clock
|
|
330
609
|
* `EvalSummary.timing`, which samples once per item because it describes the
|
|
@@ -347,6 +626,14 @@ type ModelTimingStats = {
|
|
|
347
626
|
* throttled hard on the day of the run shows it here, which is what keeps
|
|
348
627
|
* `wireMs` an honest comparison. */
|
|
349
628
|
retryMs: TimingStats;
|
|
629
|
+
/** Total `CallTiming.rateLimitRetries` over the same calls `calls` counts:
|
|
630
|
+
* how many rate-limit 429s this model's calls absorbed and retried. A plain
|
|
631
|
+
* sum, not a distribution; a call that did not report the field adds `0`.
|
|
632
|
+
* Nonzero means the provider throttled the run, so lower `concurrency`
|
|
633
|
+
* before calls start failing. `runEval` always sets it; optional because
|
|
634
|
+
* artifacts written before it existed do not carry it, so treat absence as
|
|
635
|
+
* "not recorded", not as `0`. */
|
|
636
|
+
rateLimitRetries?: number;
|
|
350
637
|
/** Per-call time to first content delta (ms), over the STREAMING calls that
|
|
351
638
|
* reported one — non-streaming calls are excluded from the sample rather
|
|
352
639
|
* than entered as `0`. Absent when no call reported one. This is the
|
|
@@ -360,20 +647,72 @@ type ModelTimingStats = {
|
|
|
360
647
|
* them. Present exactly when `firstTokenMs` is. */
|
|
361
648
|
firstTokenCalls?: number;
|
|
362
649
|
};
|
|
650
|
+
/**
|
|
651
|
+
* Why an item's workflow failed, captured from the thrown value before it is
|
|
652
|
+
* flattened to `EvalItem.error`. Populated on `failed` items only.
|
|
653
|
+
*
|
|
654
|
+
* When a `ProviderError` is found — the thrown value itself or the first one
|
|
655
|
+
* down its `cause` chain — every field comes from it. Otherwise only the thrown
|
|
656
|
+
* value's `name` is recorded. This record never copies `ProviderError.body` or
|
|
657
|
+
* the message: the body can echo prompt text and is redaction-eligible.
|
|
658
|
+
* `EvalItem.error` keeps the error message as before, which for some providers
|
|
659
|
+
* (a non-JSON error response) embeds the response text — that is outside this
|
|
660
|
+
* record's guarantee.
|
|
661
|
+
*/
|
|
662
|
+
type EvalItemFailure = {
|
|
663
|
+
/** `'ProviderError'` when one was found, else the thrown value's own `name`. */
|
|
664
|
+
name: string;
|
|
665
|
+
/** Adapter/profile name, e.g. `'openai'`. */
|
|
666
|
+
provider?: string;
|
|
667
|
+
/** HTTP status; `0` for a network-level failure. */
|
|
668
|
+
status?: number;
|
|
669
|
+
/** `ProviderError.retryable` — the semantic failover hint. */
|
|
670
|
+
retryable?: boolean;
|
|
671
|
+
/** Provider request id, when the response carried one. */
|
|
672
|
+
requestId?: string;
|
|
673
|
+
};
|
|
363
674
|
type EvalItem = {
|
|
364
675
|
input: unknown;
|
|
365
676
|
annotations?: unknown;
|
|
366
677
|
output: unknown;
|
|
367
678
|
error?: string;
|
|
679
|
+
/**
|
|
680
|
+
* Structured cause of a `failed` item — see {@link EvalItemFailure}. Absent
|
|
681
|
+
* on every other outcome, on pre-0.24 artifacts, and when the thrown value
|
|
682
|
+
* carried no `name` (a thrown string, say).
|
|
683
|
+
*/
|
|
684
|
+
failure?: EvalItemFailure;
|
|
368
685
|
scorerErrors?: string[];
|
|
369
686
|
scores: Record<string, number | null>;
|
|
370
687
|
duration?: number;
|
|
688
|
+
/** Compatibility view of `accounting.breakdown.generation` — this item's
|
|
689
|
+
* measured workflow spend. On a pre-0.24 artifact this is whatever the
|
|
690
|
+
* caller reported instead. */
|
|
371
691
|
cost?: number;
|
|
372
692
|
/** True when `cost` is a lower bound because this item used unpriced work. */
|
|
373
693
|
unpriced?: boolean;
|
|
694
|
+
/** Compatibility view of `accounting.breakdown.judging` — this item's judges. */
|
|
374
695
|
scorerCost?: number;
|
|
375
696
|
scoreDetails?: Record<string, ScorerDetail>;
|
|
376
|
-
/**
|
|
697
|
+
/** How this item ended. Absent only on pre-0.24 artifacts. */
|
|
698
|
+
outcome?: EvalItemOutcome;
|
|
699
|
+
/** Generation AND judging spend for this item; `breakdown` splits them.
|
|
700
|
+
* Absent on pre-0.24 artifacts. */
|
|
701
|
+
accounting?: Accounting;
|
|
702
|
+
/**
|
|
703
|
+
* What the `executeWorkflow` callback claimed, kept for inspection and never
|
|
704
|
+
* used as a measurement. `cost` is the callback's own `cost` return;
|
|
705
|
+
* `metadata` holds the reserved keys (`models`, `modelCallCounts`,
|
|
706
|
+
* `workflows`, `workflowCallCounts`, `tokens`, `agentCalls`) that used to
|
|
707
|
+
* overwrite the measured ones. Non-reserved caller keys still merge into
|
|
708
|
+
* `metadata` below.
|
|
709
|
+
*/
|
|
710
|
+
callerReport?: {
|
|
711
|
+
cost?: number;
|
|
712
|
+
metadata?: Record<string, unknown>;
|
|
713
|
+
};
|
|
714
|
+
/** Tracked metadata merged with the caller's NON-reserved keys (caller wins
|
|
715
|
+
* on those). Reserved keys stay measured — see {@link EvalItem.callerReport}. */
|
|
377
716
|
metadata?: Record<string, unknown>;
|
|
378
717
|
/** Per-model provider-call latency for this item, rolled up from
|
|
379
718
|
* `agent_call_end.timing`. Absent when the item made no timed provider call
|
|
@@ -389,6 +728,11 @@ type EvalItem = {
|
|
|
389
728
|
* sums and per-model totals from `item.timing`, but not the distribution.
|
|
390
729
|
* (`rescore` already reports no timing stats at all, so nothing regresses.) */
|
|
391
730
|
timing?: Record<string, ItemModelTiming>;
|
|
731
|
+
/** The captured operations this item's workflow performed. Present only under
|
|
732
|
+
* `captureRequests`; references only, resolved through the run's artifact. */
|
|
733
|
+
diagnostics?: {
|
|
734
|
+
operations: OperationRef[];
|
|
735
|
+
};
|
|
392
736
|
/** Trace events captured during this item's execution. Only populated when
|
|
393
737
|
* `runEval` was called with `{ captureTraces: true }`. Verbose-mode
|
|
394
738
|
* `agent_call_start.data.messages` snapshots are stripped to keep memory bounded;
|
|
@@ -397,7 +741,18 @@ type EvalItem = {
|
|
|
397
741
|
};
|
|
398
742
|
type EvalSummary = {
|
|
399
743
|
count: number;
|
|
744
|
+
/**
|
|
745
|
+
* Items carrying an `error` string. UNCHANGED legacy meaning — it therefore
|
|
746
|
+
* still counts budget-stopped and cancelled items, which also carry one. Read
|
|
747
|
+
* {@link EvalSummary.coverage} to tell a model failure from a budget stop.
|
|
748
|
+
*/
|
|
400
749
|
failures: number;
|
|
750
|
+
/**
|
|
751
|
+
* Per-outcome counts for items and for each scorer. Every key of the outcome
|
|
752
|
+
* unions is present, including zeros, so a consumer can render "0 skipped"
|
|
753
|
+
* without inferring it from an absent key. Absent on pre-0.24 artifacts.
|
|
754
|
+
*/
|
|
755
|
+
coverage?: EvalCoverage;
|
|
401
756
|
scorers: Record<string, {
|
|
402
757
|
mean: number;
|
|
403
758
|
min: number;
|
|
@@ -448,6 +803,15 @@ type EvalSummary = {
|
|
|
448
803
|
* tripped (either not configured or all scorers within tolerance).
|
|
449
804
|
*/
|
|
450
805
|
degraded?: DegradedScorer[];
|
|
806
|
+
/**
|
|
807
|
+
* The item error rate against the run's `failOnItemErrorRate` limit.
|
|
808
|
+
* Present exactly when at least one item `failed` (so a clean run's summary
|
|
809
|
+
* is unchanged); `exceeded` says whether the gate tripped. Like `degraded`,
|
|
810
|
+
* `runEval` never throws on it — the CLI and consumers decide the exit code.
|
|
811
|
+
* Absent on rescores (the source run owns item failures) and on pre-0.24
|
|
812
|
+
* artifacts.
|
|
813
|
+
*/
|
|
814
|
+
itemErrorRate?: ItemErrorRate;
|
|
451
815
|
};
|
|
452
816
|
/**
|
|
453
817
|
* Sample-size context for one side of a comparison.
|
|
@@ -548,7 +912,20 @@ type EvalComparison = {
|
|
|
548
912
|
baselineTotal: number;
|
|
549
913
|
candidateTotal: number;
|
|
550
914
|
delta: number;
|
|
551
|
-
|
|
915
|
+
/** `null` when the baseline total is 0 — a percentage change from zero is
|
|
916
|
+
* not a number, and reporting `0` or `Infinity` misleads. */
|
|
917
|
+
deltaPercent: number | null;
|
|
918
|
+
/**
|
|
919
|
+
* `true` only when the two sides are comparable as SPEND: both accountings
|
|
920
|
+
* `complete`, the same `scope`, and the same case/scorer coverage. A
|
|
921
|
+
* partial or budget-truncated run is cheaper because it did less work, so
|
|
922
|
+
* without this an incomplete candidate reads as a saving.
|
|
923
|
+
*
|
|
924
|
+
* Raw totals are reported either way, and quality comparison is unaffected.
|
|
925
|
+
*/
|
|
926
|
+
certified: boolean;
|
|
927
|
+
/** Why certification was refused. Present exactly when `certified` is false. */
|
|
928
|
+
reason?: string;
|
|
552
929
|
};
|
|
553
930
|
regressions: EvalRegression[];
|
|
554
931
|
improvements: EvalImprovement[];
|
|
@@ -607,6 +984,22 @@ type RunEvalOptions = {
|
|
|
607
984
|
* payload, subscribe to `runtime.on('trace', ...)` directly.
|
|
608
985
|
*/
|
|
609
986
|
captureTraces?: boolean;
|
|
987
|
+
/**
|
|
988
|
+
* Capture the provider-neutral REQUESTS this run submits into a diagnostic
|
|
989
|
+
* artifact, so a failed case can be inspected after the process exits.
|
|
990
|
+
*
|
|
991
|
+
* Off by default and deliberately explicit to turn on: it requires
|
|
992
|
+
* `config.diagnostics.artifacts` on the runtime, and without it the run fails
|
|
993
|
+
* fast with `AxlError('DIAGNOSTICS_UNAVAILABLE')` BEFORE the dataset is
|
|
994
|
+
* loaded — a capture misconfiguration must never be discovered after the run
|
|
995
|
+
* has already spent money.
|
|
996
|
+
*
|
|
997
|
+
* Capture is diagnostics, never measurement: `accounting` is byte-identical
|
|
998
|
+
* with capture on, off, truncated, or failing outright. Pass an object to
|
|
999
|
+
* override the byte bounds (defaults 256 KiB per record, 16 MiB per run,
|
|
1000
|
+
* 1 MiB pending queue).
|
|
1001
|
+
*/
|
|
1002
|
+
captureRequests?: CaptureRequestsOption;
|
|
610
1003
|
};
|
|
611
1004
|
|
|
612
1005
|
declare function defineEval(config: EvalConfig): EvalConfig;
|
|
@@ -638,6 +1031,17 @@ declare function evalCompare(baseline: EvalResult | EvalResult[], candidate: Eva
|
|
|
638
1031
|
* `evalCompare` used for the means/CI (`baseline/candidateScored/Failed`).
|
|
639
1032
|
*/
|
|
640
1033
|
declare function evaluateScorerErrorRateGate(comparison: EvalComparison, maxScorerErrorRate: number): string | null;
|
|
1034
|
+
/**
|
|
1035
|
+
* Decide whether a comparison must be REFUSED because a side lost too many
|
|
1036
|
+
* items (`axl-eval compare`, default-on at `0.05`, overridden by
|
|
1037
|
+
* `--max-item-error-rate`; `1` disables it). Pure and testable — returns a
|
|
1038
|
+
* human-readable refusal reason naming coverage and the side, or `null` to
|
|
1039
|
+
* allow. See {@link compareItemErrorRates} for the per-run rule.
|
|
1040
|
+
*
|
|
1041
|
+
* Throws on an invalid limit: the gate is default-on, so a bad value must not
|
|
1042
|
+
* quietly disable it.
|
|
1043
|
+
*/
|
|
1044
|
+
declare function evaluateItemErrorRateGate(baseline: EvalResult | EvalResult[], candidate: EvalResult | EvalResult[], maxItemErrorRate?: number): string | null;
|
|
641
1045
|
|
|
642
1046
|
/** A scorer's failure-rate verdict against a tolerance — see {@link evaluateScorerTolerance}. */
|
|
643
1047
|
type ScorerToleranceVerdict = {
|
|
@@ -696,10 +1100,50 @@ type RescoreOptions = {
|
|
|
696
1100
|
* be cancelled mid-flight. Also checked between items to short-circuit
|
|
697
1101
|
* remaining work. Mirrors `RunEvalOptions.signal`. */
|
|
698
1102
|
signal?: AbortSignal;
|
|
1103
|
+
/**
|
|
1104
|
+
* Known-spend threshold for the NEW judging work only, e.g. `'$0.10'`.
|
|
1105
|
+
*
|
|
1106
|
+
* The source run's spend is history: it is recorded under
|
|
1107
|
+
* `accounting.source.generation` and is NOT counted toward this limit, so a
|
|
1108
|
+
* rescore budget means "spend at most this much re-judging", which is the
|
|
1109
|
+
* only question a rescore can answer. Same validation and `>=` semantics as
|
|
1110
|
+
* `EvalConfig.budget`.
|
|
1111
|
+
*/
|
|
1112
|
+
budget?: string;
|
|
1113
|
+
/**
|
|
1114
|
+
* Carry the source run's captured requests forward into this rescore, and
|
|
1115
|
+
* capture the judge calls this rescore makes.
|
|
1116
|
+
*
|
|
1117
|
+
* The source artifact is COPIED rather than referenced. Reference counting
|
|
1118
|
+
* would make deleting the original run either impossible or silently
|
|
1119
|
+
* destructive to every rescore of it; copying costs bytes once and makes each
|
|
1120
|
+
* result independently deletable. The copy preserves the ORIGINAL operation
|
|
1121
|
+
* ids as provenance, so a reader can still line a record up against the run
|
|
1122
|
+
* that produced it — even after that run is gone.
|
|
1123
|
+
*
|
|
1124
|
+
* Both halves land in ONE artifact and share ONE `maxRunBytes` budget: the
|
|
1125
|
+
* copied bytes count against it, so the artifact never grows to twice the
|
|
1126
|
+
* bound the caller asked for. A copy that would exceed it is partial and the
|
|
1127
|
+
* manifest says `truncated`; a source artifact that no longer exists, or a
|
|
1128
|
+
* copy that fails, yields `unavailable` with `artifactId: ''` — never the
|
|
1129
|
+
* source's id, which this result does not own. None of it prevents the
|
|
1130
|
+
* numeric rescore results from being read.
|
|
1131
|
+
*/
|
|
1132
|
+
captureRequests?: CaptureRequestsOption;
|
|
699
1133
|
};
|
|
700
1134
|
/**
|
|
701
1135
|
* Re-run scorers on the saved outputs of an existing eval result.
|
|
702
1136
|
* Preserves original input/output/annotations. Only re-runs scoring.
|
|
1137
|
+
*
|
|
1138
|
+
* ## Accounting
|
|
1139
|
+
*
|
|
1140
|
+
* A rescore opens its OWN scope (`accounting.scope === 'rescore'`) covering
|
|
1141
|
+
* only the judging it performs. The original run's generation spend is never
|
|
1142
|
+
* added to the new total — a rescore that reported the sum would make a $0.20
|
|
1143
|
+
* re-judge of a $0.75 run look like $0.95 of new spend. Instead the source is
|
|
1144
|
+
* recorded verbatim under `accounting.source`, so a reader can add them up
|
|
1145
|
+
* deliberately, and `source.generation` is `null` when the source artifact
|
|
1146
|
+
* predates accounting (never synthesized from `totalCost`).
|
|
703
1147
|
*/
|
|
704
1148
|
declare function rescore(result: EvalResult, scorers: Scorer[], runtime: AxlRuntime, options?: RescoreOptions): Promise<EvalResult>;
|
|
705
1149
|
|
|
@@ -714,7 +1158,16 @@ type MultiRunSummary = {
|
|
|
714
1158
|
*/
|
|
715
1159
|
workflows: string[];
|
|
716
1160
|
dataset: string;
|
|
1161
|
+
/** Compatibility view: the sum of each run's `totalCost`. */
|
|
717
1162
|
totalCost: number;
|
|
1163
|
+
/**
|
|
1164
|
+
* The union of every run's accounting, folded conservatively: known spend and
|
|
1165
|
+
* operation counts sum, incompleteness reasons union, and the group takes the
|
|
1166
|
+
* WORST completeness in it — one legacy run makes the whole group
|
|
1167
|
+
* `'unverified'`, one incomplete run makes it `'incomplete'`. Inheriting
|
|
1168
|
+
* run[0]'s flags would report a contaminated group as clean.
|
|
1169
|
+
*/
|
|
1170
|
+
accounting: Accounting;
|
|
718
1171
|
totalDuration: number;
|
|
719
1172
|
scorers: Record<string, {
|
|
720
1173
|
mean: number;
|
|
@@ -739,4 +1192,87 @@ type MultiRunSummary = {
|
|
|
739
1192
|
*/
|
|
740
1193
|
declare function aggregateRuns(runs: EvalResult[]): MultiRunSummary;
|
|
741
1194
|
|
|
742
|
-
|
|
1195
|
+
/**
|
|
1196
|
+
* Eval-side accounting helpers.
|
|
1197
|
+
*
|
|
1198
|
+
* The core owns measurement (`@axlsdk/axl`'s `Accounting`, `trackOutcome`,
|
|
1199
|
+
* `AdmissionController`). This module owns the three things eval adds on top:
|
|
1200
|
+
*
|
|
1201
|
+
* - **Scope plumbing** — {@link trackScope} opens a core accounting scope when
|
|
1202
|
+
* the runtime has one, and produces an explicitly `uninstrumented` record
|
|
1203
|
+
* when it does not. An eval driven by a hand-rolled runtime therefore reports
|
|
1204
|
+
* "we measured nothing", never a confident `$0`.
|
|
1205
|
+
* - **Reading** — {@link readAccounting} turns any `EvalResult`, including a
|
|
1206
|
+
* pre-0.24 artifact with no `accounting` at all, into an `EvalAccounting`.
|
|
1207
|
+
* An absent record reads `'unverified'` and is NEVER upgraded to `'complete'`.
|
|
1208
|
+
* - **Union** — {@link aggregateAccounting} folds several records
|
|
1209
|
+
* conservatively: sums of known spend, unions of reasons, and the WORST
|
|
1210
|
+
* completeness of any input.
|
|
1211
|
+
*
|
|
1212
|
+
* Everything here is pure except `trackScope`, which only delegates.
|
|
1213
|
+
*/
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* Read the accounting of any `EvalResult`, including one written before this
|
|
1217
|
+
* field existed.
|
|
1218
|
+
*
|
|
1219
|
+
* A legacy artifact reads `'unverified'`: its `totalCost` is repeated as
|
|
1220
|
+
* `knownCost` for continuity, but no operation counts, no usage and no
|
|
1221
|
+
* generation/judging split are invented, and it is never reported as
|
|
1222
|
+
* `'complete'`. Turning an absent completeness into `'complete'` is exactly the
|
|
1223
|
+
* bug that would let a legacy run certify a cost comparison.
|
|
1224
|
+
*
|
|
1225
|
+
* **The structural identities do not hold on a synthesized record.** For a live
|
|
1226
|
+
* record (`'complete'` or `'incomplete'`) `provenance` sums to `knownCost` and
|
|
1227
|
+
* `breakdown` splits it. A legacy artifact reports only a single total, so the
|
|
1228
|
+
* synthesized record carries `knownCost` with an all-zero `breakdown` and
|
|
1229
|
+
* `provenance` — inventing a generation/judging split from a number of unknown
|
|
1230
|
+
* origin would be a fabrication. Consumers that render a breakdown must treat
|
|
1231
|
+
* `'unverified'` as "no split available", not as "the split is zero"; the
|
|
1232
|
+
* completeness field is what tells them which case they are in.
|
|
1233
|
+
*/
|
|
1234
|
+
declare function readAccounting(result: EvalResult): EvalAccounting;
|
|
1235
|
+
/**
|
|
1236
|
+
* How much work the budget actually refused: cases never started or stopped
|
|
1237
|
+
* mid-flight, plus judges that never ran for the same reason.
|
|
1238
|
+
*
|
|
1239
|
+
* This is the quantity that makes a run short. Spend alone does not: a run can
|
|
1240
|
+
* settle exactly on its limit having refused nothing. Missing coverage (a
|
|
1241
|
+
* pre-0.24 artifact) reads `0` — an artifact that never recorded outcomes
|
|
1242
|
+
* cannot be used to assert that work was refused — and so do missing or
|
|
1243
|
+
* negative counts inside a malformed block.
|
|
1244
|
+
*/
|
|
1245
|
+
declare function refusedWork(coverage: EvalCoverage | undefined): number;
|
|
1246
|
+
/**
|
|
1247
|
+
* `true` when a run's budget both closed AND refused work — the one reading
|
|
1248
|
+
* that entitles a consumer to call the run truncated.
|
|
1249
|
+
*
|
|
1250
|
+
* A closed controller alone is the NORMAL end state of a run whose final
|
|
1251
|
+
* settlement lands exactly on its limit, which is exactly how `--budget` is
|
|
1252
|
+
* used as a CI threshold. Reporting that as a stop fails a perfect run and
|
|
1253
|
+
* prints a self-contradicting message ("0 never started, 0 stopped mid-flight,
|
|
1254
|
+
* 2 completed … incomplete by design").
|
|
1255
|
+
*
|
|
1256
|
+
* The argument is structural rather than an `EvalResult` because the three
|
|
1257
|
+
* consumers hold different things: the CLI has the result, the Studio server
|
|
1258
|
+
* has a persisted blob it parses itself, and the Studio browser mirror has a
|
|
1259
|
+
* client-side type. All three can produce `{ budget, coverage }`.
|
|
1260
|
+
*/
|
|
1261
|
+
declare function isBudgetStopped(summary: {
|
|
1262
|
+
budget?: {
|
|
1263
|
+
status?: string;
|
|
1264
|
+
} | undefined;
|
|
1265
|
+
coverage?: EvalCoverage | undefined;
|
|
1266
|
+
}): boolean;
|
|
1267
|
+
/**
|
|
1268
|
+
* Fold several accounting records into one, conservatively.
|
|
1269
|
+
*
|
|
1270
|
+
* Numbers sum; reasons union with their counts; completeness takes the WORST
|
|
1271
|
+
* of the inputs (`unverified` > `incomplete` > `complete`), so a group
|
|
1272
|
+
* containing one legacy run is `unverified` as a whole and one incomplete run
|
|
1273
|
+
* makes the group incomplete. Inheriting the first input's flags — the bug this
|
|
1274
|
+
* exists to prevent — would report a group as complete because run 1 was.
|
|
1275
|
+
*/
|
|
1276
|
+
declare function aggregateAccounting(inputs: readonly Accounting[]): Accounting;
|
|
1277
|
+
|
|
1278
|
+
export { type BootstrapCIResult, type CaptureRequestsOption, DEFAULT_COPY_MAX_BYTES, DEFAULT_SIDECAR_MAX_BYTES, type Dataset, type DatasetConfig, type DatasetItem, type DegradedScorer, type DiagnosticManifest, type EvalAccounting, type EvalBudgetStatus, type EvalCompareOptions, type EvalComparison, type EvalComparisonPartial, type EvalConfig, type EvalCoverage, type EvalImprovement, type EvalItem, type EvalItemFailure, type EvalItemOutcome, type EvalProgressEvent, type EvalRegression, type EvalResult, type EvalSummary, type ExtraKeyPolicy, type ItemErrorRate, type ItemModelTiming, type LlmScorerConfig, type ModelTimingStats, type MultiRunSummary, type OperationRef, type RequestRecord, type RescoreOptions, type RunEvalOptions, type Scorer, type ScorerApplies, type ScorerConfig, type ScorerContext, type ScorerDetail, type ScorerFn, type ScorerOutcome, type ScorerResult, type ScorerToleranceVerdict, type SidecarValidation, aggregateAccounting, aggregateRuns, dataset, defineEval, evalCompare, evaluateItemErrorRateGate, evaluateScorerErrorRateGate, evaluateScorerTolerance, isBudgetStopped, llmScorer, normalizeScorerResult, pairedBootstrapCI, parseRequestRecords, readAccounting, refusedWork, rescore, resolveCaptureLimits, runEval, scorer, serializeRequestRecords, toDiagnosticManifest, unavailableManifest, validateRequestSidecar };
|