@rulvar/evals 1.50.0 → 1.52.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/dist/index.d.ts +141 -2
- package/dist/index.js +272 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompiledWorkflow, DeclaredLadder, Effort, Engine, EvidenceRef, Json, JsonSchema, KnowledgeSnapshot, ModelClaim, ModelKnowledgeStore, ModelRef, ModelSpec, RunOutcome, SchemaSpec, TaskClass, Usage, WireError, Workflow } from "@rulvar/core";
|
|
1
|
+
import { CompiledWorkflow, DeclaredLadder, Effort, Engine, EvidenceRef, Json, JsonSchema, KnowledgeSnapshot, ModelClaim, ModelKnowledgeStore, ModelRef, ModelSpec, RunOutcome, SchemaSpec, TaskClass, Usage, WireError, Workflow, WorkflowEvent } from "@rulvar/core";
|
|
2
2
|
|
|
3
3
|
//#region src/envelope.d.ts
|
|
4
4
|
/** Thrown when authorizing a run's ceiling would exceed the envelope. */
|
|
@@ -391,6 +391,145 @@ declare function flipStaleOnCanaryDrift(store: ModelKnowledgeStore, model: Model
|
|
|
391
391
|
attempts?: number;
|
|
392
392
|
}): Promise<CanaryDriftReport>;
|
|
393
393
|
//#endregion
|
|
394
|
+
//#region src/benchmark.d.ts
|
|
395
|
+
/** One benchmark: a workflow measured over a series of repeats. */
|
|
396
|
+
interface BenchmarkSpec {
|
|
397
|
+
name: string;
|
|
398
|
+
workflow: Workflow | CompiledWorkflow;
|
|
399
|
+
args: Json;
|
|
400
|
+
/**
|
|
401
|
+
* Scored repeats to attempt; a positive integer. The regression
|
|
402
|
+
* protocol this kit serves calls for at least 5 before a series is
|
|
403
|
+
* citable; the kit does not enforce that floor, it reports what ran.
|
|
404
|
+
*/
|
|
405
|
+
repeats: number;
|
|
406
|
+
/**
|
|
407
|
+
* Per-run graders over the settled outcome, the same contract the
|
|
408
|
+
* eval runners use (golden, rubric, and LLM-judge graders compose
|
|
409
|
+
* unchanged). A failing grader rejects the run from scoring; a
|
|
410
|
+
* throwing grader is a defect of the spec and propagates.
|
|
411
|
+
*/
|
|
412
|
+
graders?: Grader[];
|
|
413
|
+
}
|
|
414
|
+
/** A per-run metric extractor over the run's full event stream. */
|
|
415
|
+
type BenchmarkMetricExtractor = (events: readonly WorkflowEvent[], outcome: RunOutcome<Json>) => number;
|
|
416
|
+
interface RunBenchmarkOptions {
|
|
417
|
+
/** Run ceiling for each target run. */
|
|
418
|
+
budgetUsd?: number;
|
|
419
|
+
/** Run ceiling for each judge run a grader performs. */
|
|
420
|
+
judgeBudgetUsd?: number;
|
|
421
|
+
/**
|
|
422
|
+
* Aggregate debit-only envelope: every target and judge run
|
|
423
|
+
* authorizes its ceiling here BEFORE starting, exactly like the eval
|
|
424
|
+
* runners. A target-run refusal throws SweepBudgetError; a judge-run
|
|
425
|
+
* refusal rejects that run from scoring as 'judge:refused'.
|
|
426
|
+
*/
|
|
427
|
+
envelope?: SpendEnvelope;
|
|
428
|
+
/**
|
|
429
|
+
* Host-supplied fingerprint labels: the commit, the pricing snapshot
|
|
430
|
+
* id, the corpus hash, the series name (cold/warm). The kit never
|
|
431
|
+
* shells out or guesses; identity the host does not supply is not
|
|
432
|
+
* recorded.
|
|
433
|
+
*/
|
|
434
|
+
labels?: Record<string, string>;
|
|
435
|
+
/** Named per-run metric extractors; each scored series gets percentiles. */
|
|
436
|
+
metrics?: Record<string, BenchmarkMetricExtractor>;
|
|
437
|
+
}
|
|
438
|
+
/** Nearest-rank percentile summary of one scored series. */
|
|
439
|
+
interface BenchmarkPercentiles {
|
|
440
|
+
min: number;
|
|
441
|
+
p50: number;
|
|
442
|
+
p90: number;
|
|
443
|
+
max: number;
|
|
444
|
+
mean: number;
|
|
445
|
+
}
|
|
446
|
+
/** The replay-strict verification verdict of one run. */
|
|
447
|
+
interface BenchmarkVerification {
|
|
448
|
+
/** Every clause below held. */
|
|
449
|
+
verified: boolean;
|
|
450
|
+
/** The dry-run resume had zero misses and zero reruns. */
|
|
451
|
+
pureReplay: boolean;
|
|
452
|
+
/** The replayed settle status equals the journaled one. */
|
|
453
|
+
statusReproduced: boolean;
|
|
454
|
+
/** The journaled output digest, when the settle recorded one. */
|
|
455
|
+
outputHash?: string;
|
|
456
|
+
/** The digest of the replayed result, when hashable. */
|
|
457
|
+
replayedOutputHash?: string;
|
|
458
|
+
/**
|
|
459
|
+
* Digest equality where comparable. A run that settled ok with a
|
|
460
|
+
* value but no journaled digest (a non-JCS-serializable result)
|
|
461
|
+
* fails this clause explicitly: a benchmark demands comparable
|
|
462
|
+
* outputs. A run with no output value passes it vacuously.
|
|
463
|
+
*/
|
|
464
|
+
outputReproduced: boolean;
|
|
465
|
+
/** Workflow-provenance determinism warnings across live and replay. */
|
|
466
|
+
determinismWarnings: number;
|
|
467
|
+
/** Machine-readable failure reasons; empty when verified. */
|
|
468
|
+
reasons: string[];
|
|
469
|
+
}
|
|
470
|
+
/** The full record of one benchmark run, scored or not. */
|
|
471
|
+
interface BenchmarkRunRecord {
|
|
472
|
+
/** 1-based ordinal in execution order. */
|
|
473
|
+
ordinal: number;
|
|
474
|
+
runId: string;
|
|
475
|
+
status: RunOutcome<Json>["status"];
|
|
476
|
+
/** Counted into the percentile series. */
|
|
477
|
+
scored: boolean;
|
|
478
|
+
/** Why the run was excluded; empty when scored. */
|
|
479
|
+
rejectedReasons: string[];
|
|
480
|
+
/** run:start to run:end, from event timestamps. */
|
|
481
|
+
wallMs: number;
|
|
482
|
+
/** The target run's cost (judge runs are separate). */
|
|
483
|
+
costUsd: number;
|
|
484
|
+
/** The judge-run share this run's grading spent. */
|
|
485
|
+
judgeCostUsd: number;
|
|
486
|
+
usage: Usage;
|
|
487
|
+
/** agent:end events on the live stream (logical dispatches). */
|
|
488
|
+
agentDispatches: number;
|
|
489
|
+
/** agent:phase:end events on the live stream (model activations). */
|
|
490
|
+
invocations: number;
|
|
491
|
+
verdicts: GraderVerdict[];
|
|
492
|
+
verification: BenchmarkVerification;
|
|
493
|
+
/** Extractor values for this run. */
|
|
494
|
+
metrics: Record<string, number>;
|
|
495
|
+
error?: WireError;
|
|
496
|
+
}
|
|
497
|
+
/** Where the numbers came from; percentiles without this are hearsay. */
|
|
498
|
+
interface BenchmarkFingerprint {
|
|
499
|
+
node: string;
|
|
500
|
+
platform: string;
|
|
501
|
+
arch: string;
|
|
502
|
+
/** Resolved versions of the rulvar packages doing the measuring. */
|
|
503
|
+
packages: Record<string, string>;
|
|
504
|
+
/** The first run's run:start timestamp (event time, no clock read). */
|
|
505
|
+
startedAt?: string;
|
|
506
|
+
labels?: Record<string, string>;
|
|
507
|
+
}
|
|
508
|
+
interface BenchmarkReport {
|
|
509
|
+
name: string;
|
|
510
|
+
/** Repeats attempted (equals runs.length). */
|
|
511
|
+
repeats: number;
|
|
512
|
+
/** Runs that entered the percentile series. */
|
|
513
|
+
scored: number;
|
|
514
|
+
runs: BenchmarkRunRecord[];
|
|
515
|
+
/** Absent when no run scored: the kit never fabricates a series. */
|
|
516
|
+
wallMs?: BenchmarkPercentiles;
|
|
517
|
+
costUsd?: BenchmarkPercentiles;
|
|
518
|
+
/** Percentiles per named extractor, over scored runs. */
|
|
519
|
+
metrics: Record<string, BenchmarkPercentiles>;
|
|
520
|
+
/** Every target and judge run, scored or rejected (honest spend). */
|
|
521
|
+
totalCostUsd: number;
|
|
522
|
+
judgeCostUsd: number;
|
|
523
|
+
fingerprint: BenchmarkFingerprint;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Runs the spec's repeats sequentially and reports the verified series.
|
|
527
|
+
* Throws only for spec defects (invalid repeats, a throwing grader or
|
|
528
|
+
* extractor) and for a target-run envelope refusal; everything a run
|
|
529
|
+
* does wrong lands in its record instead.
|
|
530
|
+
*/
|
|
531
|
+
declare function runBenchmark(engine: Engine, spec: BenchmarkSpec, options?: RunBenchmarkOptions): Promise<BenchmarkReport>;
|
|
532
|
+
//#endregion
|
|
394
533
|
//#region src/sweeps.d.ts
|
|
395
534
|
/** One fixed pool member; effort is part of the claim subject identity. */
|
|
396
535
|
interface SweepModel {
|
|
@@ -610,4 +749,4 @@ declare function runValueCheckpoint(checkpointPool: CheckpointPool, options: Run
|
|
|
610
749
|
/** The deterministic render for the M12 gate docs amendment. */
|
|
611
750
|
declare function renderCheckpointReport(report: CheckpointReport): string;
|
|
612
751
|
//#endregion
|
|
613
|
-
export { type CanaryDriftReport, type CanaryProbeSet, type CanaryReport, type CanaryRunOptions, type CheckpointArm, type CheckpointCell, type CheckpointLadder, type CheckpointPool, type CheckpointReport, type CriterionOneReport, type CriterionTwoReport, type EvalCase, type EvalCaseResult, type EvalCommitterOptions, EvalJudgeError, type EvalMatrixReport, type EvalSuiteResult, type GoldenGraderOptions, type Grader, type GraderContext, type GraderVerdict, JUDGE_VERDICT_SCHEMA, type JudgeGraderOptions, type JudgeSpec, type MatrixCell, type MatrixCellReport, type MeasuredClaimInput, type OrchestratedCase, type RubricCriterion, type RubricGraderOptions, type RunCheckpointOptions, type RunEvalCaseOptions, type RunEvalSuiteOptions, type RunSweepOptions, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, type SweepCase, type SweepCellReport, type SweepModel, type SweepPool, type SweepReport, type SweepThresholds, agentTypeRuleHolds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
|
752
|
+
export { type BenchmarkFingerprint, type BenchmarkMetricExtractor, type BenchmarkPercentiles, type BenchmarkReport, type BenchmarkRunRecord, type BenchmarkSpec, type BenchmarkVerification, type CanaryDriftReport, type CanaryProbeSet, type CanaryReport, type CanaryRunOptions, type CheckpointArm, type CheckpointCell, type CheckpointLadder, type CheckpointPool, type CheckpointReport, type CriterionOneReport, type CriterionTwoReport, type EvalCase, type EvalCaseResult, type EvalCommitterOptions, EvalJudgeError, type EvalMatrixReport, type EvalSuiteResult, type GoldenGraderOptions, type Grader, type GraderContext, type GraderVerdict, JUDGE_VERDICT_SCHEMA, type JudgeGraderOptions, type JudgeSpec, type MatrixCell, type MatrixCellReport, type MeasuredClaimInput, type OrchestratedCase, type RubricCriterion, type RubricGraderOptions, type RunBenchmarkOptions, type RunCheckpointOptions, type RunEvalCaseOptions, type RunEvalSuiteOptions, type RunSweepOptions, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, type SweepCase, type SweepCellReport, type SweepModel, type SweepPool, type SweepReport, type SweepThresholds, agentTypeRuleHolds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runBenchmark, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { ConfigError, KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow, hashRunOutput, lastRunSettle } from "@rulvar/core";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
//#region src/envelope.ts
|
|
4
5
|
/**
|
|
@@ -234,6 +235,12 @@ async function runEvalCase(engine, evalCase, options = {}) {
|
|
|
234
235
|
...incomplete === void 0 ? {} : { incomplete }
|
|
235
236
|
};
|
|
236
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Runs one judge invocation through the engine. Package-internal: the
|
|
240
|
+
* benchmark kit reuses it so benchmark judge runs are journaled,
|
|
241
|
+
* budgeted, and VCR-recordable exactly like eval judge runs; not part
|
|
242
|
+
* of the public index.
|
|
243
|
+
*/
|
|
237
244
|
async function runJudge(engine, judgeName, spec, budgetUsd) {
|
|
238
245
|
const workflowName = `eval-judge:${judgeName}`;
|
|
239
246
|
const judgeWorkflow = defineWorkflow({ name: workflowName }, async (ctx) => {
|
|
@@ -627,6 +634,269 @@ async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
|
|
|
627
634
|
throw lastCas ?? /* @__PURE__ */ new Error("flipStaleOnCanaryDrift: unreachable");
|
|
628
635
|
}
|
|
629
636
|
//#endregion
|
|
637
|
+
//#region src/benchmark.ts
|
|
638
|
+
/**
|
|
639
|
+
* The reproducible benchmark kit (RV-213): repeated, verified, and
|
|
640
|
+
* fingerprinted measurement of one workflow on one engine, built
|
|
641
|
+
* strictly on the public core APIs.
|
|
642
|
+
*
|
|
643
|
+
* What makes a run SCORED rather than merely finished: it settles 'ok',
|
|
644
|
+
* every grader passes, and the replay-strict verification holds: a
|
|
645
|
+
* dry-run resume replays it with zero would-be-live calls, reproduces
|
|
646
|
+
* the recorded settle status and the journaled output digest, and the
|
|
647
|
+
* re-executed body raises zero workflow-provenance determinism
|
|
648
|
+
* warnings. A hand-rolled benchmark loop reports clean numbers for a
|
|
649
|
+
* workflow whose result replay cannot reproduce; this kit rejects that
|
|
650
|
+
* run from the series and says why, so the percentiles only ever
|
|
651
|
+
* summarize runs that are evidence.
|
|
652
|
+
*
|
|
653
|
+
* Percentiles use the nearest-rank method (1-based, ascending): the
|
|
654
|
+
* p-th percentile of n values is the element at index ceil(p/100 * n)
|
|
655
|
+
* in the sorted series. No interpolation, so a reported p50/p90 is
|
|
656
|
+
* always a value that actually occurred.
|
|
657
|
+
*
|
|
658
|
+
* Judging stays blind by construction: graders (including LLM judges
|
|
659
|
+
* via the shared judge channel) see the run's output and their own
|
|
660
|
+
* rubric, never a system label, a run ordinal, or a candidate
|
|
661
|
+
* identity, so comparing two systems is running the same spec twice
|
|
662
|
+
* and comparing reports. Repeats run SEQUENTIALLY in ordinal order;
|
|
663
|
+
* cold-versus-warm cache series are a host concern (run the benchmark
|
|
664
|
+
* once per series). Wall time is measured from the run's own
|
|
665
|
+
* run:start/run:end event timestamps; the kit reads no clock of its
|
|
666
|
+
* own.
|
|
667
|
+
*/
|
|
668
|
+
const require = createRequire(import.meta.url);
|
|
669
|
+
function packageVersion(name) {
|
|
670
|
+
try {
|
|
671
|
+
return require(`${name}/package.json`).version;
|
|
672
|
+
} catch {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function percentilesOf(values) {
|
|
677
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
678
|
+
const at = (index) => sorted[Math.min(Math.max(index, 0), sorted.length - 1)] ?? 0;
|
|
679
|
+
const rank = (p) => at(Math.ceil(p / 100 * sorted.length) - 1);
|
|
680
|
+
return {
|
|
681
|
+
min: at(0),
|
|
682
|
+
p50: rank(50),
|
|
683
|
+
p90: rank(90),
|
|
684
|
+
max: at(sorted.length - 1),
|
|
685
|
+
mean: sorted.reduce((sum, value) => sum + value, 0) / sorted.length
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* The documented event catalog, enumerated: capture subscribes through
|
|
690
|
+
* the typed `handle.on` surface instead of consuming the handle's
|
|
691
|
+
* single `events` iterable, so the kit never competes with a host (or
|
|
692
|
+
* a test harness) that already consumes the stream. An event type the
|
|
693
|
+
* catalog gains later is invisible to extractors until this list
|
|
694
|
+
* learns it; lifecycle, cost, and determinism events are all here.
|
|
695
|
+
*/
|
|
696
|
+
const EVENT_VOCABULARY = [
|
|
697
|
+
"run:start",
|
|
698
|
+
"run:end",
|
|
699
|
+
"phase:start",
|
|
700
|
+
"log",
|
|
701
|
+
"budget:update",
|
|
702
|
+
"external:waiting",
|
|
703
|
+
"approval:pending",
|
|
704
|
+
"child:start",
|
|
705
|
+
"child:end",
|
|
706
|
+
"agent:queued",
|
|
707
|
+
"agent:start",
|
|
708
|
+
"agent:phase:start",
|
|
709
|
+
"agent:phase:end",
|
|
710
|
+
"agent:end",
|
|
711
|
+
"agent:error",
|
|
712
|
+
"agent:schema-retry",
|
|
713
|
+
"agent:stream",
|
|
714
|
+
"tool:start",
|
|
715
|
+
"tool:end",
|
|
716
|
+
"determinism:warning",
|
|
717
|
+
"plan:revised",
|
|
718
|
+
"node:parked",
|
|
719
|
+
"node:cancelled",
|
|
720
|
+
"node:linked",
|
|
721
|
+
"orchestrator:woke",
|
|
722
|
+
"orchestrator:budget",
|
|
723
|
+
"escalation:raised",
|
|
724
|
+
"escalation:decided",
|
|
725
|
+
"spawn:admitted",
|
|
726
|
+
"spawn:rejected",
|
|
727
|
+
"verify:failed",
|
|
728
|
+
"ledger:op",
|
|
729
|
+
"stall:detected",
|
|
730
|
+
"guard:oscillation",
|
|
731
|
+
"resolution:applied",
|
|
732
|
+
"resolution:superseded",
|
|
733
|
+
"termination:debit",
|
|
734
|
+
"termination:denied",
|
|
735
|
+
"termination:config-drift",
|
|
736
|
+
"journal:compat"
|
|
737
|
+
];
|
|
738
|
+
async function collectRun(handle) {
|
|
739
|
+
const events = [];
|
|
740
|
+
const record = (event) => {
|
|
741
|
+
events.push(event);
|
|
742
|
+
};
|
|
743
|
+
const detachers = EVENT_VOCABULARY.map((type) => handle.on(type, record));
|
|
744
|
+
const outcome = await handle.result;
|
|
745
|
+
for (const detach of detachers) detach();
|
|
746
|
+
return {
|
|
747
|
+
events,
|
|
748
|
+
outcome,
|
|
749
|
+
runId: handle.runId
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function wallMsOf(events) {
|
|
753
|
+
const start = events.find((event) => event.type === "run:start")?.ts;
|
|
754
|
+
const end = events.find((event) => event.type === "run:end")?.ts;
|
|
755
|
+
return start !== void 0 && end !== void 0 ? Math.max(0, Date.parse(end) - Date.parse(start)) : 0;
|
|
756
|
+
}
|
|
757
|
+
function workflowWarningsOf(events) {
|
|
758
|
+
return events.filter((event) => event.type === "determinism:warning" && event.provenance === "workflow").length;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Runs the spec's repeats sequentially and reports the verified series.
|
|
762
|
+
* Throws only for spec defects (invalid repeats, a throwing grader or
|
|
763
|
+
* extractor) and for a target-run envelope refusal; everything a run
|
|
764
|
+
* does wrong lands in its record instead.
|
|
765
|
+
*/
|
|
766
|
+
async function runBenchmark(engine, spec, options = {}) {
|
|
767
|
+
if (!Number.isInteger(spec.repeats) || spec.repeats < 1) throw new TypeError(`BenchmarkSpec.repeats must be a positive integer, got ${String(spec.repeats)}`);
|
|
768
|
+
const runs = [];
|
|
769
|
+
let startedAt;
|
|
770
|
+
let totalCostUsd = 0;
|
|
771
|
+
let totalJudgeCostUsd = 0;
|
|
772
|
+
for (let ordinal = 1; ordinal <= spec.repeats; ordinal += 1) {
|
|
773
|
+
options.envelope?.authorize(options.budgetUsd, `benchmark '${spec.name}' run ${String(ordinal)}`);
|
|
774
|
+
const live = await collectRun(engine.run(spec.workflow, spec.args, {
|
|
775
|
+
name: `benchmark:${spec.name}:${String(ordinal)}`,
|
|
776
|
+
...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
|
|
777
|
+
}));
|
|
778
|
+
startedAt ??= live.events.find((event) => event.type === "run:start")?.ts;
|
|
779
|
+
totalCostUsd += live.outcome.cost.totalUsd;
|
|
780
|
+
const rejectedReasons = [];
|
|
781
|
+
if (live.outcome.status !== "ok") rejectedReasons.push(`status:${live.outcome.status}`);
|
|
782
|
+
const replayHandle = engine.resume(live.runId, spec.workflow, {
|
|
783
|
+
args: spec.args,
|
|
784
|
+
dryRun: true
|
|
785
|
+
});
|
|
786
|
+
const replay = await collectRun(replayHandle);
|
|
787
|
+
const preview = await replayHandle.preview;
|
|
788
|
+
const recorded = lastRunSettle(await engine.stores.journal.load(live.runId));
|
|
789
|
+
const outputHash = recorded?.outputHash;
|
|
790
|
+
const replayedOutputHash = hashRunOutput(replay.outcome.value);
|
|
791
|
+
const pureReplay = preview.misses === 0 && preview.reruns === 0;
|
|
792
|
+
const statusReproduced = recorded !== void 0 && replay.outcome.status === recorded.runStatus;
|
|
793
|
+
const outputReproduced = live.outcome.value === void 0 ? true : outputHash !== void 0 && replayedOutputHash === outputHash;
|
|
794
|
+
const determinismWarnings = workflowWarningsOf(live.events) + workflowWarningsOf(replay.events);
|
|
795
|
+
const reasons = [];
|
|
796
|
+
if (!pureReplay) reasons.push("verification:not-pure-replay");
|
|
797
|
+
if (recorded === void 0) reasons.push("verification:no-recorded-settle");
|
|
798
|
+
else if (!statusReproduced) reasons.push("verification:status-diverged");
|
|
799
|
+
if (!outputReproduced) reasons.push(outputHash === void 0 ? "verification:output-not-hashable" : "verification:output-diverged");
|
|
800
|
+
if (determinismWarnings > 0) reasons.push("verification:determinism-warning");
|
|
801
|
+
const verification = {
|
|
802
|
+
verified: reasons.length === 0,
|
|
803
|
+
pureReplay,
|
|
804
|
+
statusReproduced,
|
|
805
|
+
...outputHash === void 0 ? {} : { outputHash },
|
|
806
|
+
...replayedOutputHash === void 0 ? {} : { replayedOutputHash },
|
|
807
|
+
outputReproduced,
|
|
808
|
+
determinismWarnings,
|
|
809
|
+
reasons
|
|
810
|
+
};
|
|
811
|
+
rejectedReasons.push(...reasons);
|
|
812
|
+
let judgeCostUsd = 0;
|
|
813
|
+
let judgeOrdinal = 0;
|
|
814
|
+
const context = {
|
|
815
|
+
value: live.outcome.value,
|
|
816
|
+
outcome: live.outcome,
|
|
817
|
+
async judge(judgeSpec) {
|
|
818
|
+
const which = judgeOrdinal;
|
|
819
|
+
judgeOrdinal += 1;
|
|
820
|
+
options.envelope?.authorize(options.judgeBudgetUsd, `benchmark judge '${spec.name}:${String(ordinal)}:${String(which)}'`);
|
|
821
|
+
const judged = await runJudge(engine, `${spec.name}:${String(ordinal)}:${String(which)}`, judgeSpec, options.judgeBudgetUsd);
|
|
822
|
+
judgeCostUsd += judged.costUsd;
|
|
823
|
+
return judged.output;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
const verdicts = [];
|
|
827
|
+
for (const grader of spec.graders ?? []) try {
|
|
828
|
+
const verdict = await grader.grade(context);
|
|
829
|
+
verdicts.push(verdict);
|
|
830
|
+
if (!verdict.passed) rejectedReasons.push(`grader:${verdict.grader}`);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
if (error instanceof SweepBudgetError) {
|
|
833
|
+
rejectedReasons.push("judge:refused");
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
if (error instanceof EvalJudgeError && error.status === "exhausted") {
|
|
837
|
+
judgeCostUsd += error.costUsd;
|
|
838
|
+
rejectedReasons.push("judge:exhausted");
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
totalJudgeCostUsd += judgeCostUsd;
|
|
844
|
+
totalCostUsd += judgeCostUsd;
|
|
845
|
+
const metricValues = {};
|
|
846
|
+
for (const [metricName, extract] of Object.entries(options.metrics ?? {})) metricValues[metricName] = extract(live.events, live.outcome);
|
|
847
|
+
runs.push({
|
|
848
|
+
ordinal,
|
|
849
|
+
runId: live.runId,
|
|
850
|
+
status: live.outcome.status,
|
|
851
|
+
scored: rejectedReasons.length === 0,
|
|
852
|
+
rejectedReasons,
|
|
853
|
+
wallMs: wallMsOf(live.events),
|
|
854
|
+
costUsd: live.outcome.cost.totalUsd,
|
|
855
|
+
judgeCostUsd,
|
|
856
|
+
usage: live.outcome.usage,
|
|
857
|
+
agentDispatches: live.events.filter((event) => event.type === "agent:end").length,
|
|
858
|
+
invocations: live.events.filter((event) => event.type === "agent:phase:end").length,
|
|
859
|
+
verdicts,
|
|
860
|
+
verification,
|
|
861
|
+
metrics: metricValues,
|
|
862
|
+
...live.outcome.error === void 0 ? {} : { error: live.outcome.error }
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
const scoredRuns = runs.filter((run) => run.scored);
|
|
866
|
+
const seriesOf = (pick) => scoredRuns.length === 0 ? void 0 : percentilesOf(scoredRuns.map(pick));
|
|
867
|
+
const metricSeries = {};
|
|
868
|
+
for (const metricName of Object.keys(options.metrics ?? {})) {
|
|
869
|
+
const series = seriesOf((run) => run.metrics[metricName] ?? 0);
|
|
870
|
+
if (series !== void 0) metricSeries[metricName] = series;
|
|
871
|
+
}
|
|
872
|
+
const wallMs = seriesOf((run) => run.wallMs);
|
|
873
|
+
const costUsd = seriesOf((run) => run.costUsd);
|
|
874
|
+
const packages = {};
|
|
875
|
+
for (const name of ["@rulvar/core", "@rulvar/evals"]) {
|
|
876
|
+
const version = packageVersion(name);
|
|
877
|
+
if (version !== void 0) packages[name] = version;
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
name: spec.name,
|
|
881
|
+
repeats: runs.length,
|
|
882
|
+
scored: scoredRuns.length,
|
|
883
|
+
runs,
|
|
884
|
+
...wallMs === void 0 ? {} : { wallMs },
|
|
885
|
+
...costUsd === void 0 ? {} : { costUsd },
|
|
886
|
+
metrics: metricSeries,
|
|
887
|
+
totalCostUsd,
|
|
888
|
+
judgeCostUsd: totalJudgeCostUsd,
|
|
889
|
+
fingerprint: {
|
|
890
|
+
node: process.version,
|
|
891
|
+
platform: process.platform,
|
|
892
|
+
arch: process.arch,
|
|
893
|
+
packages,
|
|
894
|
+
...startedAt === void 0 ? {} : { startedAt },
|
|
895
|
+
...options.labels === void 0 ? {} : { labels: options.labels }
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
//#endregion
|
|
630
900
|
//#region src/checkpoint.ts
|
|
631
901
|
/**
|
|
632
902
|
* The phases 1-2 measured-value checkpoint (M12-T01; the quantitative
|
|
@@ -901,4 +1171,4 @@ async function runSweepMatrix(pool, options) {
|
|
|
901
1171
|
return report;
|
|
902
1172
|
}
|
|
903
1173
|
//#endregion
|
|
904
|
-
export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, agentTypeRuleHolds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
|
1174
|
+
export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, agentTypeRuleHolds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runBenchmark, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/evals",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.52.0",
|
|
4
4
|
"description": "Rulvar evals: eval cases, golden outputs, rubric and judge graders, matrix sweeps, canary fingerprint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
26
|
-
"@rulvar/testing": "1.
|
|
25
|
+
"@rulvar/core": "1.52.0",
|
|
26
|
+
"@rulvar/testing": "1.52.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.20.0",
|