@rulvar/evals 1.16.1 → 1.17.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 +118 -6
- package/dist/index.js +152 -13
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { CompiledWorkflow, DeclaredLadder, Effort, Engine, EvidenceRef, Json, JsonSchema, KnowledgeSnapshot, ModelClaim, ModelKnowledgeStore, ModelRef, ModelSpec, RunOutcome, SchemaSpec, TaskClass, Usage, WireError, Workflow } from "@rulvar/core";
|
|
2
2
|
|
|
3
|
+
//#region src/envelope.d.ts
|
|
4
|
+
/** Thrown when authorizing a run's ceiling would exceed the envelope. */
|
|
5
|
+
declare class SweepBudgetError extends Error {
|
|
6
|
+
/** What was about to start, e.g. `eval target 'sweep-math'`. */
|
|
7
|
+
readonly runLabel: string;
|
|
8
|
+
/** The per-run ceiling that did not fit. */
|
|
9
|
+
readonly ceilingUsd: number;
|
|
10
|
+
/** Total already authorized before this refusal. */
|
|
11
|
+
readonly authorizedUsd: number;
|
|
12
|
+
readonly maxTotalUsd: number;
|
|
13
|
+
constructor(runLabel: string, ceilingUsd: number, authorizedUsd: number, maxTotalUsd: number);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One envelope bounds one whole sweep invocation: share the instance
|
|
17
|
+
* across the canary loop and runSweepMatrix so canary, target, and
|
|
18
|
+
* judge runs all draw from the same remainder.
|
|
19
|
+
*/
|
|
20
|
+
declare class SpendEnvelope {
|
|
21
|
+
readonly maxTotalUsd: number;
|
|
22
|
+
private readonly maxMicroUsd;
|
|
23
|
+
private authorizedMicroUsd;
|
|
24
|
+
constructor(maxTotalUsd: number);
|
|
25
|
+
/** Total authorized so far (debit-only; never decreases). */
|
|
26
|
+
get authorizedUsd(): number;
|
|
27
|
+
get remainingUsd(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Authorizes one run's immutable ceiling or throws SweepBudgetError.
|
|
30
|
+
* An unbounded run cannot be authorized: under an envelope every run
|
|
31
|
+
* MUST carry an explicit positive ceiling, otherwise the aggregate
|
|
32
|
+
* bound would be unaccountable.
|
|
33
|
+
*/
|
|
34
|
+
authorize(ceilingUsd: number | undefined, runLabel: string): void;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
3
37
|
//#region src/case.d.ts
|
|
4
38
|
/**
|
|
5
39
|
* One quality-measurement case. The shape is the
|
|
@@ -79,6 +113,13 @@ interface RunEvalCaseOptions {
|
|
|
79
113
|
budgetUsd?: number;
|
|
80
114
|
/** Run ceiling for each judge run. */
|
|
81
115
|
judgeBudgetUsd?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Aggregate debit-only envelope (v1.16.2 review P1-2): every target
|
|
118
|
+
* and judge run authorizes its ceiling here BEFORE starting, and an
|
|
119
|
+
* envelope requires the matching per-run ceiling to be set. A
|
|
120
|
+
* refusal throws SweepBudgetError before any provider work.
|
|
121
|
+
*/
|
|
122
|
+
envelope?: SpendEnvelope;
|
|
82
123
|
}
|
|
83
124
|
/** Thrown when a judge run does not settle ok. */
|
|
84
125
|
declare class EvalJudgeError extends Error {
|
|
@@ -105,6 +146,8 @@ interface EvalSuiteResult {
|
|
|
105
146
|
interface RunEvalSuiteOptions {
|
|
106
147
|
budgetUsd?: number;
|
|
107
148
|
judgeBudgetUsd?: number;
|
|
149
|
+
/** See RunEvalCaseOptions.envelope; shared across every case of the suite. */
|
|
150
|
+
envelope?: SpendEnvelope;
|
|
108
151
|
}
|
|
109
152
|
/**
|
|
110
153
|
* Runs cases sequentially (deterministic journal and cassette order) and
|
|
@@ -232,14 +275,52 @@ interface CanaryProbeSet {
|
|
|
232
275
|
/** The fixed prompts; order matters and enters the fingerprint. */
|
|
233
276
|
prompts: string[];
|
|
234
277
|
}
|
|
278
|
+
interface CanaryRunOptions {
|
|
279
|
+
/**
|
|
280
|
+
* Immutable ceiling per probe run (v1.16.2 review P1-2): every probe
|
|
281
|
+
* is an ordinary paid engine run and gets its own recorded
|
|
282
|
+
* RunMeta.budgetUsd.
|
|
283
|
+
*/
|
|
284
|
+
budgetUsd?: number;
|
|
285
|
+
/**
|
|
286
|
+
* Aggregate debit-only envelope shared with the surrounding sweep;
|
|
287
|
+
* each probe authorizes budgetUsd BEFORE running, and an envelope
|
|
288
|
+
* requires budgetUsd to be set.
|
|
289
|
+
*/
|
|
290
|
+
envelope?: SpendEnvelope;
|
|
291
|
+
}
|
|
292
|
+
interface CanaryReport {
|
|
293
|
+
fingerprint: string;
|
|
294
|
+
/**
|
|
295
|
+
* True only when every probe settled ok. A fingerprint containing a
|
|
296
|
+
* non-ok probe status is a measurement artifact (budget exhaustion,
|
|
297
|
+
* transient provider failure), NOT evidence of model drift: never
|
|
298
|
+
* feed it to flipStaleOnCanaryDrift.
|
|
299
|
+
*/
|
|
300
|
+
allOk: boolean;
|
|
301
|
+
probes: Array<{
|
|
302
|
+
prompt: string;
|
|
303
|
+
status: RunOutcome<unknown>["status"];
|
|
304
|
+
}>;
|
|
305
|
+
}
|
|
235
306
|
/** The committed v1 normalization (OQ-06): NFC, trim, collapse whitespace. */
|
|
236
307
|
declare function normalizeCanaryOutput(output: unknown): string;
|
|
237
308
|
/**
|
|
238
|
-
* Runs the fixed probe set through the ordinary engine
|
|
239
|
-
*
|
|
240
|
-
*
|
|
309
|
+
* Runs the fixed probe set through the ordinary engine. Probes run
|
|
310
|
+
* sequentially in declaration order, one run per probe, so recordings
|
|
311
|
+
* replay deterministically. Each probe run carries the optional
|
|
312
|
+
* immutable ceiling (options.budgetUsd) and authorizes it against the
|
|
313
|
+
* optional envelope before starting. A non-ok probe enters the
|
|
314
|
+
* fingerprint as `!status` and clears allOk: callers gate drift
|
|
315
|
+
* flipping on allOk, because a budget-starved or transiently failing
|
|
316
|
+
* probe fingerprints differently without the model having drifted.
|
|
317
|
+
*/
|
|
318
|
+
declare function runCanary(engine: Engine, probes: CanaryProbeSet, options?: CanaryRunOptions): Promise<CanaryReport>;
|
|
319
|
+
/**
|
|
320
|
+
* The fingerprint alone (the pre-v1.16.2-review surface, kept
|
|
321
|
+
* compatible). Prefer runCanary: its allOk is the drift-flip gate.
|
|
241
322
|
*/
|
|
242
|
-
declare function canaryFingerprint(engine: Engine, probes: CanaryProbeSet): Promise<string>;
|
|
323
|
+
declare function canaryFingerprint(engine: Engine, probes: CanaryProbeSet, options?: CanaryRunOptions): Promise<string>;
|
|
243
324
|
interface CanaryDriftReport {
|
|
244
325
|
model: ModelRef;
|
|
245
326
|
freshFingerprint: string;
|
|
@@ -253,7 +334,13 @@ interface CanaryDriftReport {
|
|
|
253
334
|
* recorded canary fingerprint differs from the fresh one. Claims
|
|
254
335
|
* without a recorded fingerprint have no baseline and
|
|
255
336
|
* stay untouched (the documented no-probe posture); a second run is
|
|
256
|
-
* an idempotent noop. CAS-rebased like every maintenance commit
|
|
337
|
+
* an idempotent noop. CAS-rebased like every maintenance commit; the
|
|
338
|
+
* retries run no engine work and pay nothing.
|
|
339
|
+
*
|
|
340
|
+
* Only pass fingerprints from an allOk probe set (runCanary): a
|
|
341
|
+
* fingerprint containing a `!status` probe differs from any healthy
|
|
342
|
+
* baseline by construction, and flipping on it would blame the model
|
|
343
|
+
* for a budget ceiling or a transient provider failure.
|
|
257
344
|
*/
|
|
258
345
|
declare function flipStaleOnCanaryDrift(store: ModelKnowledgeStore, model: ModelRef, freshFingerprint: string, options?: {
|
|
259
346
|
attempts?: number;
|
|
@@ -297,6 +384,17 @@ interface RunSweepOptions {
|
|
|
297
384
|
thresholds?: Partial<SweepThresholds>;
|
|
298
385
|
/** Passed through to every suite run (budget, VCR hooks ride the engine). */
|
|
299
386
|
suite?: RunEvalSuiteOptions;
|
|
387
|
+
/**
|
|
388
|
+
* Aggregate debit-only envelope over the WHOLE matrix (v1.16.2
|
|
389
|
+
* review P1-2): every target and judge run authorizes its immutable
|
|
390
|
+
* ceiling before starting, so the pool times cases times judge-call
|
|
391
|
+
* product cannot exceed it, falsification pool growth included. An
|
|
392
|
+
* envelope requires suite.budgetUsd (and suite.judgeBudgetUsd once a
|
|
393
|
+
* grader judges); a cell refused by the envelope lands in the report
|
|
394
|
+
* as envelopeExhausted with NO claim. Share the instance with the
|
|
395
|
+
* canary loop so probes draw from the same remainder.
|
|
396
|
+
*/
|
|
397
|
+
envelope?: SpendEnvelope;
|
|
300
398
|
/** When given, emitted claims commit through the committer identity. */
|
|
301
399
|
store?: ModelKnowledgeStore;
|
|
302
400
|
/**
|
|
@@ -313,6 +411,20 @@ interface SweepCellReport {
|
|
|
313
411
|
n: number;
|
|
314
412
|
totalCostUsd: number;
|
|
315
413
|
caseNames: string[];
|
|
414
|
+
/**
|
|
415
|
+
* Count of case results whose TARGET run settled 'exhausted' (its
|
|
416
|
+
* per-run ceiling, not the envelope). A budget-starved measurement
|
|
417
|
+
* must not become a model belief, so any exhausted target suppresses
|
|
418
|
+
* the cell's claim even when the degraded passRate crosses a
|
|
419
|
+
* threshold: the alternative is committing a false weakness that
|
|
420
|
+
* blames the model for the ceiling.
|
|
421
|
+
*/
|
|
422
|
+
exhaustedRuns?: number;
|
|
423
|
+
/**
|
|
424
|
+
* The aggregate envelope refused a run of this cell before it
|
|
425
|
+
* started; stats cover nothing reliable and the cell emits no claim.
|
|
426
|
+
*/
|
|
427
|
+
envelopeExhausted?: true;
|
|
316
428
|
}
|
|
317
429
|
interface SweepReport {
|
|
318
430
|
reportId: string;
|
|
@@ -417,4 +529,4 @@ declare function runValueCheckpoint(checkpointPool: CheckpointPool, options: Run
|
|
|
417
529
|
/** The deterministic render for the M12 gate docs amendment. */
|
|
418
530
|
declare function renderCheckpointReport(report: CheckpointReport): string;
|
|
419
531
|
//#endregion
|
|
420
|
-
export { type CanaryDriftReport, type CanaryProbeSet, 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, type SweepCase, type SweepCellReport, type SweepModel, type SweepPool, type SweepReport, type SweepThresholds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
|
532
|
+
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, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
|
|
1
|
+
import { ConfigError, KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
//#region src/case.ts
|
|
4
4
|
/**
|
|
@@ -32,6 +32,7 @@ var EvalJudgeError = class extends Error {
|
|
|
32
32
|
async function runEvalCase(engine, evalCase, options = {}) {
|
|
33
33
|
const name = options.name ?? evalCase.workflow.name;
|
|
34
34
|
const timing = {};
|
|
35
|
+
options.envelope?.authorize(options.budgetUsd, `eval target '${name}'`);
|
|
35
36
|
const handle = engine.run(evalCase.workflow, evalCase.args, {
|
|
36
37
|
name: `eval:${name}`,
|
|
37
38
|
...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
|
|
@@ -53,6 +54,7 @@ async function runEvalCase(engine, evalCase, options = {}) {
|
|
|
53
54
|
async judge(spec) {
|
|
54
55
|
const ordinal = judgeOrdinal;
|
|
55
56
|
judgeOrdinal += 1;
|
|
57
|
+
options.envelope?.authorize(options.judgeBudgetUsd, `eval judge '${name}:${String(ordinal)}'`);
|
|
56
58
|
const judged = await runJudge(engine, `${name}:${ordinal}`, spec, options.judgeBudgetUsd);
|
|
57
59
|
judgeCostUsd += judged.costUsd;
|
|
58
60
|
return judged.output;
|
|
@@ -109,7 +111,8 @@ async function runEvalSuite(engine, cases, options = {}) {
|
|
|
109
111
|
results.push(await runEvalCase(engine, evalCase, {
|
|
110
112
|
name,
|
|
111
113
|
...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd },
|
|
112
|
-
...options.judgeBudgetUsd === void 0 ? {} : { judgeBudgetUsd: options.judgeBudgetUsd }
|
|
114
|
+
...options.judgeBudgetUsd === void 0 ? {} : { judgeBudgetUsd: options.judgeBudgetUsd },
|
|
115
|
+
...options.envelope === void 0 ? {} : { envelope: options.envelope }
|
|
113
116
|
}));
|
|
114
117
|
}
|
|
115
118
|
return {
|
|
@@ -337,26 +340,54 @@ function normalizeCanaryOutput(output) {
|
|
|
337
340
|
return (typeof output === "string" ? output : JSON.stringify(output ?? null)).normalize("NFC").trim().replace(/\s+/gu, " ");
|
|
338
341
|
}
|
|
339
342
|
/**
|
|
340
|
-
* Runs the fixed probe set through the ordinary engine
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
+
* Runs the fixed probe set through the ordinary engine. Probes run
|
|
344
|
+
* sequentially in declaration order, one run per probe, so recordings
|
|
345
|
+
* replay deterministically. Each probe run carries the optional
|
|
346
|
+
* immutable ceiling (options.budgetUsd) and authorizes it against the
|
|
347
|
+
* optional envelope before starting. A non-ok probe enters the
|
|
348
|
+
* fingerprint as `!status` and clears allOk: callers gate drift
|
|
349
|
+
* flipping on allOk, because a budget-starved or transiently failing
|
|
350
|
+
* probe fingerprints differently without the model having drifted.
|
|
343
351
|
*/
|
|
344
|
-
async function
|
|
352
|
+
async function runCanary(engine, probes, options = {}) {
|
|
345
353
|
const outputs = [];
|
|
354
|
+
const probeReports = [];
|
|
346
355
|
for (const [index, prompt] of probes.prompts.entries()) {
|
|
356
|
+
options.envelope?.authorize(options.budgetUsd, `canary probe ${String(index)}`);
|
|
347
357
|
const workflow = defineWorkflow({ name: `kb-canary:${String(index)}` }, async (ctx) => await ctx.agent(prompt, { agentType: probes.agentType }));
|
|
348
|
-
const outcome = await engine.run(workflow, null).result;
|
|
358
|
+
const outcome = await engine.run(workflow, null, options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }).result;
|
|
359
|
+
probeReports.push({
|
|
360
|
+
prompt,
|
|
361
|
+
status: outcome.status
|
|
362
|
+
});
|
|
349
363
|
outputs.push(outcome.status === "ok" ? normalizeCanaryOutput(outcome.value) : `!${outcome.status}`);
|
|
350
364
|
}
|
|
351
365
|
const body = JSON.stringify([probes.prompts.length, outputs]);
|
|
352
|
-
return
|
|
366
|
+
return {
|
|
367
|
+
fingerprint: createHash("sha256").update(body, "utf8").digest("hex"),
|
|
368
|
+
allOk: probeReports.every((probe) => probe.status === "ok"),
|
|
369
|
+
probes: probeReports
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The fingerprint alone (the pre-v1.16.2-review surface, kept
|
|
374
|
+
* compatible). Prefer runCanary: its allOk is the drift-flip gate.
|
|
375
|
+
*/
|
|
376
|
+
async function canaryFingerprint(engine, probes, options = {}) {
|
|
377
|
+
return (await runCanary(engine, probes, options)).fingerprint;
|
|
353
378
|
}
|
|
354
379
|
/**
|
|
355
380
|
* Flips the model's ACTIVE eval-measured claims to stale when their
|
|
356
381
|
* recorded canary fingerprint differs from the fresh one. Claims
|
|
357
382
|
* without a recorded fingerprint have no baseline and
|
|
358
383
|
* stay untouched (the documented no-probe posture); a second run is
|
|
359
|
-
* an idempotent noop. CAS-rebased like every maintenance commit
|
|
384
|
+
* an idempotent noop. CAS-rebased like every maintenance commit; the
|
|
385
|
+
* retries run no engine work and pay nothing.
|
|
386
|
+
*
|
|
387
|
+
* Only pass fingerprints from an allOk probe set (runCanary): a
|
|
388
|
+
* fingerprint containing a `!status` probe differs from any healthy
|
|
389
|
+
* baseline by construction, and flipping on it would blame the model
|
|
390
|
+
* for a budget ceiling or a transient provider failure.
|
|
360
391
|
*/
|
|
361
392
|
async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
|
|
362
393
|
const attempts = options?.attempts ?? 3;
|
|
@@ -393,6 +424,82 @@ async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
|
|
|
393
424
|
throw lastCas ?? /* @__PURE__ */ new Error("flipStaleOnCanaryDrift: unreachable");
|
|
394
425
|
}
|
|
395
426
|
//#endregion
|
|
427
|
+
//#region src/envelope.ts
|
|
428
|
+
/**
|
|
429
|
+
* The debit-only aggregate spend envelope (v1.16.2 review P1-2). A
|
|
430
|
+
* sweep multiplies paid runs: pool members times cases for targets,
|
|
431
|
+
* one judge run per GraderContext.judge call, one canary run per
|
|
432
|
+
* probe per member, and the falsification union can grow the pool
|
|
433
|
+
* beyond the config. Per-run ceilings alone do not bound that
|
|
434
|
+
* product, so the envelope authorizes each run's IMMUTABLE ceiling
|
|
435
|
+
* BEFORE the run starts: a run whose ceiling does not fit the
|
|
436
|
+
* remainder is refused before any provider work.
|
|
437
|
+
*
|
|
438
|
+
* Authorizations are never returned: not when a run completes under
|
|
439
|
+
* its ceiling, not on CAS retries (they run no paid work), and not on
|
|
440
|
+
* replay. A replayed run authorizes exactly like a fresh one and then
|
|
441
|
+
* spends nothing; the envelope bounds the authorized worst case, not
|
|
442
|
+
* the observed spend, so replay never double-PAYS anything while the
|
|
443
|
+
* accounting stays one-directional. The envelope lives for one
|
|
444
|
+
* invocation and is never persisted.
|
|
445
|
+
*
|
|
446
|
+
* Accounting is integer micro-USD, so exact fits pass: 0.1 + 0.2
|
|
447
|
+
* against a 0.3 envelope is a fit, not a float rejection.
|
|
448
|
+
*/
|
|
449
|
+
const MICRO = 1e6;
|
|
450
|
+
/** Thrown when authorizing a run's ceiling would exceed the envelope. */
|
|
451
|
+
var SweepBudgetError = class extends Error {
|
|
452
|
+
/** What was about to start, e.g. `eval target 'sweep-math'`. */
|
|
453
|
+
runLabel;
|
|
454
|
+
/** The per-run ceiling that did not fit. */
|
|
455
|
+
ceilingUsd;
|
|
456
|
+
/** Total already authorized before this refusal. */
|
|
457
|
+
authorizedUsd;
|
|
458
|
+
maxTotalUsd;
|
|
459
|
+
constructor(runLabel, ceilingUsd, authorizedUsd, maxTotalUsd) {
|
|
460
|
+
super(`sweep envelope exhausted: authorizing $${String(ceilingUsd)} for ${runLabel} would exceed maxTotalUsd $${String(maxTotalUsd)} ($${String(authorizedUsd)} already authorized); the run was refused before any provider call`);
|
|
461
|
+
this.name = "SweepBudgetError";
|
|
462
|
+
this.runLabel = runLabel;
|
|
463
|
+
this.ceilingUsd = ceilingUsd;
|
|
464
|
+
this.authorizedUsd = authorizedUsd;
|
|
465
|
+
this.maxTotalUsd = maxTotalUsd;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
469
|
+
* One envelope bounds one whole sweep invocation: share the instance
|
|
470
|
+
* across the canary loop and runSweepMatrix so canary, target, and
|
|
471
|
+
* judge runs all draw from the same remainder.
|
|
472
|
+
*/
|
|
473
|
+
var SpendEnvelope = class {
|
|
474
|
+
maxTotalUsd;
|
|
475
|
+
maxMicroUsd;
|
|
476
|
+
authorizedMicroUsd = 0;
|
|
477
|
+
constructor(maxTotalUsd) {
|
|
478
|
+
if (!Number.isFinite(maxTotalUsd) || maxTotalUsd <= 0) throw new ConfigError(`SpendEnvelope maxTotalUsd must be a positive finite number, got ${String(maxTotalUsd)}`);
|
|
479
|
+
this.maxTotalUsd = maxTotalUsd;
|
|
480
|
+
this.maxMicroUsd = Math.round(maxTotalUsd * MICRO);
|
|
481
|
+
}
|
|
482
|
+
/** Total authorized so far (debit-only; never decreases). */
|
|
483
|
+
get authorizedUsd() {
|
|
484
|
+
return this.authorizedMicroUsd / MICRO;
|
|
485
|
+
}
|
|
486
|
+
get remainingUsd() {
|
|
487
|
+
return Math.max(0, this.maxMicroUsd - this.authorizedMicroUsd) / MICRO;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Authorizes one run's immutable ceiling or throws SweepBudgetError.
|
|
491
|
+
* An unbounded run cannot be authorized: under an envelope every run
|
|
492
|
+
* MUST carry an explicit positive ceiling, otherwise the aggregate
|
|
493
|
+
* bound would be unaccountable.
|
|
494
|
+
*/
|
|
495
|
+
authorize(ceilingUsd, runLabel) {
|
|
496
|
+
if (ceilingUsd === void 0 || !Number.isFinite(ceilingUsd) || ceilingUsd <= 0) throw new ConfigError(`the spend envelope requires an explicit positive per-run ceiling for ${runLabel}; got ${String(ceilingUsd)} (an unbounded run under an aggregate envelope would be unaccountable)`);
|
|
497
|
+
const micro = Math.round(ceilingUsd * MICRO);
|
|
498
|
+
if (this.authorizedMicroUsd + micro > this.maxMicroUsd) throw new SweepBudgetError(runLabel, ceilingUsd, this.authorizedUsd, this.maxTotalUsd);
|
|
499
|
+
this.authorizedMicroUsd += micro;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
//#endregion
|
|
396
503
|
//#region src/checkpoint.ts
|
|
397
504
|
/**
|
|
398
505
|
* The phases 1-2 measured-value checkpoint (M12-T01; the quantitative
|
|
@@ -549,6 +656,16 @@ function renderCheckpointReport(report) {
|
|
|
549
656
|
}
|
|
550
657
|
//#endregion
|
|
551
658
|
//#region src/sweeps.ts
|
|
659
|
+
/**
|
|
660
|
+
* Matrix sweeps (M11-T02). The deconfounder of the whole
|
|
661
|
+
* knowledge feature: a FIXED eval matrix (workflow x model x
|
|
662
|
+
* taskClass), independent of current routing, measured through the
|
|
663
|
+
* ordinary engine (journaled, budgeted, VCR-recordable), emitting
|
|
664
|
+
* eval-measured claims through the eval-committer identity.
|
|
665
|
+
*
|
|
666
|
+
* Sweep volume is never authorized by proposal volume: the model pool
|
|
667
|
+
* and the case list are EXPLICIT caller data (fixed pools only).
|
|
668
|
+
*/
|
|
552
669
|
const SWEEP_THRESHOLD_DEFAULTS = {
|
|
553
670
|
strength: .9,
|
|
554
671
|
weakness: .5
|
|
@@ -575,6 +692,7 @@ async function runSweepMatrix(pool, options) {
|
|
|
575
692
|
...SWEEP_THRESHOLD_DEFAULTS,
|
|
576
693
|
...options.thresholds
|
|
577
694
|
};
|
|
695
|
+
if (options.envelope !== void 0 && options.suite?.budgetUsd === void 0) throw new ConfigError("runSweepMatrix: an aggregate envelope requires suite.budgetUsd (the per-target ceiling); unbounded targets under an envelope would be unaccountable");
|
|
578
696
|
const byTaskClass = /* @__PURE__ */ new Map();
|
|
579
697
|
for (const entry of pool.cases) {
|
|
580
698
|
const bucket = byTaskClass.get(entry.taskClass) ?? [];
|
|
@@ -586,7 +704,27 @@ async function runSweepMatrix(pool, options) {
|
|
|
586
704
|
for (const member of pool.models) {
|
|
587
705
|
const engine = await options.engineFor(member);
|
|
588
706
|
for (const [taskClass, bucket] of byTaskClass) {
|
|
589
|
-
|
|
707
|
+
let suite;
|
|
708
|
+
try {
|
|
709
|
+
suite = await runEvalSuite(engine, bucket.map((entry) => entry.case), {
|
|
710
|
+
...options.suite ?? {},
|
|
711
|
+
...options.envelope === void 0 ? {} : { envelope: options.envelope }
|
|
712
|
+
});
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (!(error instanceof SweepBudgetError)) throw error;
|
|
715
|
+
cells.push({
|
|
716
|
+
model: member.model,
|
|
717
|
+
...member.effort === void 0 ? {} : { effort: member.effort },
|
|
718
|
+
taskClass,
|
|
719
|
+
passRate: 0,
|
|
720
|
+
n: 0,
|
|
721
|
+
totalCostUsd: 0,
|
|
722
|
+
caseNames: [],
|
|
723
|
+
envelopeExhausted: true
|
|
724
|
+
});
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const exhaustedRuns = suite.results.filter((result) => result.status === "exhausted").length;
|
|
590
728
|
const cell = {
|
|
591
729
|
model: member.model,
|
|
592
730
|
...member.effort === void 0 ? {} : { effort: member.effort },
|
|
@@ -594,11 +732,12 @@ async function runSweepMatrix(pool, options) {
|
|
|
594
732
|
passRate: suite.passRate,
|
|
595
733
|
n: suite.results.length,
|
|
596
734
|
totalCostUsd: suite.totalCostUsd,
|
|
597
|
-
caseNames: suite.results.map((result) => result.name)
|
|
735
|
+
caseNames: suite.results.map((result) => result.name),
|
|
736
|
+
...exhaustedRuns === 0 ? {} : { exhaustedRuns }
|
|
598
737
|
};
|
|
599
738
|
cells.push(cell);
|
|
600
739
|
const polarity = cell.passRate >= thresholds.strength ? "strength" : cell.passRate <= thresholds.weakness ? "weakness" : void 0;
|
|
601
|
-
if (polarity !== void 0 && cell.n > 0) {
|
|
740
|
+
if (polarity !== void 0 && cell.n > 0 && exhaustedRuns === 0) {
|
|
602
741
|
const epoch = options.modelEpochFor?.(member);
|
|
603
742
|
claims.push({
|
|
604
743
|
id: claimIdOf(options.reportId, member, taskClass),
|
|
@@ -639,4 +778,4 @@ async function runSweepMatrix(pool, options) {
|
|
|
639
778
|
return report;
|
|
640
779
|
}
|
|
641
780
|
//#endregion
|
|
642
|
-
export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|
|
781
|
+
export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, 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.17.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.17.0",
|
|
26
|
+
"@rulvar/testing": "1.17.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.20.0",
|