modelrig 0.5.0 → 0.7.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/CHANGELOG.md +125 -0
- package/README.md +54 -4
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +36 -7
- package/dist/adapters/attempt-meta-map.d.ts +63 -0
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
- package/dist/adapters/attempt-meta-map.js +154 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +25 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +29 -2
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +25 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +25 -2
- package/dist/adapters/openai-stream.d.ts +2 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +23 -3
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +36 -6
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +24 -5
- package/dist/artifacts/classification.d.ts +27 -0
- package/dist/artifacts/classification.d.ts.map +1 -0
- package/dist/artifacts/classification.js +38 -0
- package/dist/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +10 -1
- package/dist/artifacts/run-context.d.ts +25 -3
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +61 -0
- package/dist/artifacts/scrub.d.ts +26 -0
- package/dist/artifacts/scrub.d.ts.map +1 -1
- package/dist/artifacts/scrub.js +53 -0
- package/dist/artifacts/step.d.ts +9 -0
- package/dist/artifacts/step.d.ts.map +1 -1
- package/dist/artifacts/step.js +9 -0
- package/dist/bakeoff/engine.d.ts +46 -4
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +269 -19
- package/dist/bakeoff/eval-cases.d.ts +5 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +3 -0
- package/dist/bakeoff/judge.d.ts +39 -0
- package/dist/bakeoff/judge.d.ts.map +1 -0
- package/dist/bakeoff/judge.js +102 -0
- package/dist/bakeoff/quality.d.ts +44 -0
- package/dist/bakeoff/quality.d.ts.map +1 -0
- package/dist/bakeoff/quality.js +126 -0
- package/dist/bakeoff/reference.d.ts +34 -0
- package/dist/bakeoff/reference.d.ts.map +1 -0
- package/dist/bakeoff/reference.js +68 -0
- package/dist/bakeoff/replay.d.ts +25 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +48 -1
- package/dist/bakeoff/report.d.ts +8 -1
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +83 -12
- package/dist/bakeoff/samples.d.ts +35 -2
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +67 -6
- package/dist/bakeoff/stats.d.ts +17 -1
- package/dist/bakeoff/stats.d.ts.map +1 -1
- package/dist/bakeoff/stats.js +67 -0
- package/dist/bakeoff/types.d.ts +76 -5
- package/dist/bakeoff/types.d.ts.map +1 -1
- package/dist/bakeoff/types.js +4 -3
- package/dist/cli-query.d.ts +14 -0
- package/dist/cli-query.d.ts.map +1 -0
- package/dist/cli-query.js +59 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -23
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/detect/detector.d.ts +82 -0
- package/dist/detect/detector.d.ts.map +1 -0
- package/dist/detect/detector.js +23 -0
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -11
- package/dist/query/analytics.d.ts +84 -0
- package/dist/query/analytics.d.ts.map +1 -0
- package/dist/query/analytics.js +280 -0
- package/dist/query/columns.d.ts +59 -0
- package/dist/query/columns.d.ts.map +1 -0
- package/dist/query/columns.js +89 -0
- package/dist/query/grammar.d.ts +74 -7
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +179 -71
- package/dist/query/saved.d.ts +26 -0
- package/dist/query/saved.d.ts.map +1 -0
- package/dist/query/saved.js +51 -0
- package/dist/routes/loader.js +15 -10
- package/dist/routes/objective-loader.d.ts +35 -0
- package/dist/routes/objective-loader.d.ts.map +1 -0
- package/dist/routes/objective-loader.js +286 -0
- package/dist/run/accept.d.ts +43 -0
- package/dist/run/accept.d.ts.map +1 -0
- package/dist/run/accept.js +65 -0
- package/dist/run/cost.d.ts +13 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +27 -0
- package/dist/run/dispatch.d.ts +24 -1
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +19 -0
- package/dist/run/ladder-key.d.ts +4 -0
- package/dist/run/ladder-key.d.ts.map +1 -1
- package/dist/run/ladder-key.js +3 -0
- package/dist/run/quality-gate.d.ts +39 -0
- package/dist/run/quality-gate.d.ts.map +1 -0
- package/dist/run/quality-gate.js +70 -0
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +22 -3
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +22 -1
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +11 -4
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +46 -39
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +10 -0
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +17 -0
- package/dist/telemetry/attempt-meta.d.ts +178 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -0
- package/dist/telemetry/attempt-meta.js +319 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +6 -0
- package/dist/telemetry/ingest-protocol.d.ts +11 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +3 -0
- package/dist/telemetry/otel-map.d.ts +10 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -1
- package/dist/telemetry/otel-map.js +64 -0
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +15 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +25 -7
- package/dist/types/failures-teaching.d.ts.map +1 -1
- package/dist/types/failures-teaching.js +20 -0
- package/dist/types/failures.d.ts +1 -1
- package/dist/types/failures.d.ts.map +1 -1
- package/dist/types/failures.js +1 -0
- package/dist/types/provider.d.ts +9 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/quality.d.ts +41 -0
- package/dist/types/quality.d.ts.map +1 -0
- package/dist/types/quality.js +12 -0
- package/dist/types/route.d.ts +89 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +13 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-sample value-accuracy resolution for a bake-off (RB-1, near-optimal spec
|
|
4
|
+
* §4.3). DETERMINISTIC tiers only — the judge tier lives in judge.ts because it
|
|
5
|
+
* dispatches a metered inference. Precedence:
|
|
6
|
+
*
|
|
7
|
+
* 1. eval-case expected value (a corrected/golden artifact) ⇒ exact/normalized
|
|
8
|
+
* match for a primitive, or schema-aware field accuracy in [0,1] for an
|
|
9
|
+
* object/array.
|
|
10
|
+
* 2. declared `value_checks` (JSON-pointer assertions) ⇒ fraction passing.
|
|
11
|
+
* 3. null (the caller falls back to the judge, then to conformance).
|
|
12
|
+
*
|
|
13
|
+
* Pure; unit-tested directly. F5 (deterministic before judge) is exactly this
|
|
14
|
+
* ordering.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.fieldAccuracy = fieldAccuracy;
|
|
18
|
+
exports.resolveJsonPointer = resolveJsonPointer;
|
|
19
|
+
exports.checkPasses = checkPasses;
|
|
20
|
+
exports.resolveDeterministicQuality = resolveDeterministicQuality;
|
|
21
|
+
/** Normalize a scalar for comparison: trim strings; leave numbers/booleans as-is.
|
|
22
|
+
* "normalized match" (spec §4.3) means insensitivity to surrounding whitespace,
|
|
23
|
+
* never a fuzzy match — a wrong value is still wrong. */
|
|
24
|
+
function normalizeScalar(v) {
|
|
25
|
+
return typeof v === "string" ? v.trim() : v;
|
|
26
|
+
}
|
|
27
|
+
function scalarEqual(a, b) {
|
|
28
|
+
return normalizeScalar(a) === normalizeScalar(b);
|
|
29
|
+
}
|
|
30
|
+
function isObject(v) {
|
|
31
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Schema-aware field accuracy in [0,1]: the fraction of the EXPECTED value's leaf
|
|
35
|
+
* fields that the actual output matches. Recurses into objects/arrays keyed by
|
|
36
|
+
* the expected shape (extra keys in `actual` are ignored — the golden defines
|
|
37
|
+
* what "correct" covers). A scalar expected ⇒ 1 or 0. An empty object/array
|
|
38
|
+
* expected ⇒ 1 (nothing to be wrong about).
|
|
39
|
+
*/
|
|
40
|
+
function fieldAccuracy(expected, actual) {
|
|
41
|
+
if (isObject(expected)) {
|
|
42
|
+
const keys = Object.keys(expected);
|
|
43
|
+
if (keys.length === 0)
|
|
44
|
+
return 1;
|
|
45
|
+
let sum = 0;
|
|
46
|
+
for (const k of keys)
|
|
47
|
+
sum += fieldAccuracy(expected[k], isObject(actual) ? actual[k] : undefined);
|
|
48
|
+
return sum / keys.length;
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(expected)) {
|
|
51
|
+
if (expected.length === 0)
|
|
52
|
+
return 1;
|
|
53
|
+
const act = Array.isArray(actual) ? actual : [];
|
|
54
|
+
let sum = 0;
|
|
55
|
+
for (let i = 0; i < expected.length; i += 1)
|
|
56
|
+
sum += fieldAccuracy(expected[i], act[i]);
|
|
57
|
+
return sum / expected.length;
|
|
58
|
+
}
|
|
59
|
+
return scalarEqual(expected, actual) ? 1 : 0;
|
|
60
|
+
}
|
|
61
|
+
/** Resolve an RFC 6901 JSON pointer against a value. Returns `undefined` when any
|
|
62
|
+
* segment is missing (a missing field fails its value check). */
|
|
63
|
+
function resolveJsonPointer(value, pointer) {
|
|
64
|
+
if (pointer === "")
|
|
65
|
+
return value;
|
|
66
|
+
const segments = pointer
|
|
67
|
+
.split("/")
|
|
68
|
+
.slice(1)
|
|
69
|
+
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
70
|
+
let cur = value;
|
|
71
|
+
for (const seg of segments) {
|
|
72
|
+
if (isObject(cur)) {
|
|
73
|
+
cur = cur[seg];
|
|
74
|
+
}
|
|
75
|
+
else if (Array.isArray(cur)) {
|
|
76
|
+
const idx = Number(seg);
|
|
77
|
+
cur = Number.isInteger(idx) && idx >= 0 ? cur[idx] : undefined;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (cur === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return cur;
|
|
86
|
+
}
|
|
87
|
+
/** True when `output` satisfies one value check (RFC 6901 pointer + literal|regex). */
|
|
88
|
+
function checkPasses(output, check) {
|
|
89
|
+
const actual = resolveJsonPointer(output, check.path);
|
|
90
|
+
if (actual === undefined)
|
|
91
|
+
return false;
|
|
92
|
+
if ("regex" in check.expect) {
|
|
93
|
+
return new RegExp(check.expect.regex).test(typeof actual === "string" ? actual : JSON.stringify(actual));
|
|
94
|
+
}
|
|
95
|
+
const lit = check.expect.literal;
|
|
96
|
+
if (isObject(lit) || Array.isArray(lit)) {
|
|
97
|
+
return JSON.stringify(normalizeDeep(lit)) === JSON.stringify(normalizeDeep(actual));
|
|
98
|
+
}
|
|
99
|
+
return scalarEqual(lit, actual);
|
|
100
|
+
}
|
|
101
|
+
function normalizeDeep(v) {
|
|
102
|
+
if (Array.isArray(v))
|
|
103
|
+
return v.map(normalizeDeep);
|
|
104
|
+
if (isObject(v)) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const k of Object.keys(v).sort())
|
|
107
|
+
out[k] = normalizeDeep(v[k]);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
return normalizeScalar(v);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Deterministic value accuracy for one sample, or null when neither an eval-case
|
|
114
|
+
* expected value nor a value check applies (the caller then tries the judge).
|
|
115
|
+
*/
|
|
116
|
+
function resolveDeterministicQuality(input) {
|
|
117
|
+
if (input.hasExpected === true) {
|
|
118
|
+
return fieldAccuracy(input.expected, input.output);
|
|
119
|
+
}
|
|
120
|
+
const checks = input.valueChecks;
|
|
121
|
+
if (checks !== undefined && checks.length > 0) {
|
|
122
|
+
const passing = checks.filter((c) => checkPasses(input.output, c)).length;
|
|
123
|
+
return passing / checks.length;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reference: auto` resolution (RB-1, near-optimal spec §2/§3). The reference arm
|
|
3
|
+
* defines "optimal" for a bake-off: the strongest DECLARED candidate for the
|
|
4
|
+
* route's task.
|
|
5
|
+
*
|
|
6
|
+
* WHAT SIGNAL WE USE — and why it is what it is. The RigIndex Bradley-Terry layer
|
|
7
|
+
* (the truest per-task strength) is NOT shipped in the SDK package, and nothing
|
|
8
|
+
* at SDK runtime is keyed by a route's schema fingerprint cell. The strongest
|
|
9
|
+
* MEASURED quality signal that ships offline is each model's probed schema
|
|
10
|
+
* conformance in `registry.json` (surfaced on ModelSelectRow.conformRate). So the
|
|
11
|
+
* deterministic rule is:
|
|
12
|
+
*
|
|
13
|
+
* among the route's declared candidates with a probed conformance:
|
|
14
|
+
* rank by conformRate DESC, then effectiveUsdPer1k ASC, then provider/model ASC
|
|
15
|
+
* → the top row is the reference; null when no declared candidate is probed.
|
|
16
|
+
*
|
|
17
|
+
* This is pure, offline, deterministic, and reuses the exact registry number the
|
|
18
|
+
* capability gates already trust (select.ts CONFORMANCE_BAR). The report NAMES
|
|
19
|
+
* this rule and its source ("registry.json probed conformance") so the choice is
|
|
20
|
+
* never mistaken for a frontier oracle. An explicit `reference: provider/model`
|
|
21
|
+
* bypasses this entirely (validated in the loader).
|
|
22
|
+
*/
|
|
23
|
+
import type { CandidateSpec, RouteBundle } from "../types/route";
|
|
24
|
+
import type { ModelSelectRow } from "../registry/select";
|
|
25
|
+
/** The rule name + source, surfaced on the report so `auto` is legible. */
|
|
26
|
+
export declare const REFERENCE_AUTO_RULE: string;
|
|
27
|
+
export declare function pickReferenceCandidate(candidates: readonly CandidateSpec[], rows: readonly ModelSelectRow[]): CandidateSpec | null;
|
|
28
|
+
/**
|
|
29
|
+
* A `reference: auto` resolver bound to the packaged registry (createRig wires
|
|
30
|
+
* this). Registry rows load once, lazily, and cache; a missing/unreadable
|
|
31
|
+
* registry ⇒ [] ⇒ null (no reference arm, graceful).
|
|
32
|
+
*/
|
|
33
|
+
export declare function makeReferenceAutoResolver(registryPath?: string): (route: RouteBundle) => CandidateSpec | null;
|
|
34
|
+
//# sourceMappingURL=reference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/bakeoff/reference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,QAEiC,CAAC;AAElE,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,IAAI,EAAE,SAAS,cAAc,EAAE,GAC9B,aAAa,GAAG,IAAI,CAmBtB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,CAAC,EAAE,MAAM,GACpB,CAAC,KAAK,EAAE,WAAW,KAAK,aAAa,GAAG,IAAI,CAY9C"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `reference: auto` resolution (RB-1, near-optimal spec §2/§3). The reference arm
|
|
4
|
+
* defines "optimal" for a bake-off: the strongest DECLARED candidate for the
|
|
5
|
+
* route's task.
|
|
6
|
+
*
|
|
7
|
+
* WHAT SIGNAL WE USE — and why it is what it is. The RigIndex Bradley-Terry layer
|
|
8
|
+
* (the truest per-task strength) is NOT shipped in the SDK package, and nothing
|
|
9
|
+
* at SDK runtime is keyed by a route's schema fingerprint cell. The strongest
|
|
10
|
+
* MEASURED quality signal that ships offline is each model's probed schema
|
|
11
|
+
* conformance in `registry.json` (surfaced on ModelSelectRow.conformRate). So the
|
|
12
|
+
* deterministic rule is:
|
|
13
|
+
*
|
|
14
|
+
* among the route's declared candidates with a probed conformance:
|
|
15
|
+
* rank by conformRate DESC, then effectiveUsdPer1k ASC, then provider/model ASC
|
|
16
|
+
* → the top row is the reference; null when no declared candidate is probed.
|
|
17
|
+
*
|
|
18
|
+
* This is pure, offline, deterministic, and reuses the exact registry number the
|
|
19
|
+
* capability gates already trust (select.ts CONFORMANCE_BAR). The report NAMES
|
|
20
|
+
* this rule and its source ("registry.json probed conformance") so the choice is
|
|
21
|
+
* never mistaken for a frontier oracle. An explicit `reference: provider/model`
|
|
22
|
+
* bypasses this entirely (validated in the loader).
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.REFERENCE_AUTO_RULE = void 0;
|
|
26
|
+
exports.pickReferenceCandidate = pickReferenceCandidate;
|
|
27
|
+
exports.makeReferenceAutoResolver = makeReferenceAutoResolver;
|
|
28
|
+
const rows_1 = require("../registry/rows");
|
|
29
|
+
/** The rule name + source, surfaced on the report so `auto` is legible. */
|
|
30
|
+
exports.REFERENCE_AUTO_RULE = "strongest declared candidate by probed schema conformance " +
|
|
31
|
+
"(registry.json), tie-break effective $/1k then provider/model";
|
|
32
|
+
function pickReferenceCandidate(candidates, rows) {
|
|
33
|
+
const byKey = new Map(rows.map((r) => [r.key, r]));
|
|
34
|
+
const ranked = candidates
|
|
35
|
+
.map((c) => ({ candidate: c, row: byKey.get(`${c.provider}/${c.model}`) }))
|
|
36
|
+
.filter((x) => x.row !== undefined && x.row.conformRate !== null)
|
|
37
|
+
.sort((a, b) => {
|
|
38
|
+
const conf = (b.row.conformRate ?? 0) - (a.row.conformRate ?? 0);
|
|
39
|
+
if (conf !== 0)
|
|
40
|
+
return conf;
|
|
41
|
+
const ea = a.row.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY;
|
|
42
|
+
const eb = b.row.effectiveUsdPer1k ?? Number.POSITIVE_INFINITY;
|
|
43
|
+
if (ea !== eb)
|
|
44
|
+
return ea - eb;
|
|
45
|
+
return `${a.candidate.provider}/${a.candidate.model}`.localeCompare(`${b.candidate.provider}/${b.candidate.model}`);
|
|
46
|
+
});
|
|
47
|
+
const top = ranked[0];
|
|
48
|
+
return top === undefined ? null : { provider: top.candidate.provider, model: top.candidate.model };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A `reference: auto` resolver bound to the packaged registry (createRig wires
|
|
52
|
+
* this). Registry rows load once, lazily, and cache; a missing/unreadable
|
|
53
|
+
* registry ⇒ [] ⇒ null (no reference arm, graceful).
|
|
54
|
+
*/
|
|
55
|
+
function makeReferenceAutoResolver(registryPath) {
|
|
56
|
+
let rows = null;
|
|
57
|
+
return (route) => {
|
|
58
|
+
if (rows === null) {
|
|
59
|
+
try {
|
|
60
|
+
rows = (0, rows_1.loadRegistryRows)(registryPath);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
rows = [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pickReferenceCandidate(route.candidates, rows);
|
|
67
|
+
};
|
|
68
|
+
}
|
package/dist/bakeoff/replay.d.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { CaptureStore } from "../telemetry/capture";
|
|
21
21
|
import type { RunDeps } from "../run/run";
|
|
22
|
+
import type { JudgeScorer } from "./judge";
|
|
23
|
+
import type { QualityGate } from "../types/quality";
|
|
22
24
|
export interface ReplayRequest {
|
|
23
25
|
readonly route: string;
|
|
24
26
|
readonly variant: string;
|
|
@@ -28,12 +30,35 @@ export interface ReplayRequest {
|
|
|
28
30
|
* sample for the human value review. Off by default (bounds memory + keeps
|
|
29
31
|
* the pure-metrics path clean). */
|
|
30
32
|
readonly keepOutputs?: boolean;
|
|
33
|
+
/** RB-1: corrected/golden expected outputs keyed by captureId (eval-case path).
|
|
34
|
+
* Presence in the map ⇒ this sample has an expected value; `value` may be any
|
|
35
|
+
* JSON (including null). Quality tier 1 (eval case) when present. */
|
|
36
|
+
readonly expected?: ReadonlyMap<string, {
|
|
37
|
+
readonly value: unknown;
|
|
38
|
+
}>;
|
|
39
|
+
/** RB-1: an opt-in judge scorer (tier 3), used only when neither an eval-case
|
|
40
|
+
* expected value nor a declared value check yields a score. Capped internally. */
|
|
41
|
+
readonly judge?: JudgeScorer;
|
|
42
|
+
/** Q1 quality gate (spec §4.2): scored on each replayed sample's output to
|
|
43
|
+
* populate `valueAccuracy` + `qualityPassed`. NOT passed into the inner
|
|
44
|
+
* `run()` — a bake-off MEASURES quality, it does not gate serving; injecting
|
|
45
|
+
* the gate into the run would reject samples and corrupt the conformance
|
|
46
|
+
* numbers it is trying to compare. Runs in the SAME value tier as the RB-1
|
|
47
|
+
* judge — whichever is supplied wins after the deterministic checks (F5). */
|
|
48
|
+
readonly qualityGate?: QualityGate;
|
|
31
49
|
}
|
|
32
50
|
export interface ReplaySampleResult {
|
|
33
51
|
readonly captureId: string;
|
|
34
52
|
readonly conform: boolean;
|
|
35
53
|
readonly repaired: boolean;
|
|
54
|
+
/** Q1: the gate's score (or 1/0 from `ok` when it returned no score); null
|
|
55
|
+
* when no gate ran or the sample produced no output (a failure). */
|
|
36
56
|
readonly valueAccuracy: number | null;
|
|
57
|
+
/** Q1: whether the gate PASSED this sample (`verdict.ok`); null when no gate
|
|
58
|
+
* ran or the sample failed. Drives the bake-off's quality pass-rate CI and
|
|
59
|
+
* `effectiveUsdPer1kPassing` (stats.ts) — distinct from `valueAccuracy`, which
|
|
60
|
+
* may be a continuous score. */
|
|
61
|
+
readonly qualityPassed: boolean | null;
|
|
37
62
|
readonly costUsd: number;
|
|
38
63
|
/** Portion of costUsd spent on repair calls (repaired_by rows). */
|
|
39
64
|
readonly repairCostUsd: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/bakeoff/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;uCAEmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/bakeoff/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;uCAEmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;yEAEqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrE;sFACkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B;;;;;iFAK6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;wEACoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;;oCAGgC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;yDACqD;IACrD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;sDACkD;IAClD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,EACtB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAoIxC"}
|
package/dist/bakeoff/replay.js
CHANGED
|
@@ -24,6 +24,8 @@ const node_crypto_1 = require("node:crypto");
|
|
|
24
24
|
const failures_1 = require("../types/failures");
|
|
25
25
|
const run_1 = require("../run/run");
|
|
26
26
|
const variant_1 = require("../run/variant");
|
|
27
|
+
const quality_1 = require("./quality");
|
|
28
|
+
const quality_gate_1 = require("../run/quality-gate");
|
|
27
29
|
async function replayVariant(deps, captures, req) {
|
|
28
30
|
const rows = captures.readLast(req.route, req.lastN);
|
|
29
31
|
const replayId = (0, node_crypto_1.randomUUID)();
|
|
@@ -55,6 +57,12 @@ async function replayVariant(deps, captures, req) {
|
|
|
55
57
|
let repaired = false;
|
|
56
58
|
let failureClass = null;
|
|
57
59
|
let outputText = null;
|
|
60
|
+
let outputValue = undefined;
|
|
61
|
+
let produced = false;
|
|
62
|
+
// Q1 (spec §4.2): the gate scores the arm's output OUTSIDE the run loop —
|
|
63
|
+
// captured here so it can be evaluated after the try without leaking the
|
|
64
|
+
// (envelope-typed) failure path into the gate.
|
|
65
|
+
let gated = null;
|
|
58
66
|
try {
|
|
59
67
|
// Through the REAL run loop: the variant's template re-renders against
|
|
60
68
|
// the replay candidate's capability flags (never the captured prompt
|
|
@@ -71,8 +79,13 @@ async function replayVariant(deps, captures, req) {
|
|
|
71
79
|
});
|
|
72
80
|
conform = result.meta.validated;
|
|
73
81
|
repaired = result.meta.repairedBy !== null;
|
|
82
|
+
outputValue = result.output;
|
|
83
|
+
produced = true;
|
|
74
84
|
if (req.keepOutputs)
|
|
75
85
|
outputText = JSON.stringify(result.output, null, 2);
|
|
86
|
+
if (req.qualityGate !== undefined) {
|
|
87
|
+
gated = { output: result.output, provider: result.meta.provider, model: result.meta.model, servedVariant: result.meta.servedVariant };
|
|
88
|
+
}
|
|
76
89
|
}
|
|
77
90
|
catch (err) {
|
|
78
91
|
if (!(err instanceof failures_1.RigFailureError))
|
|
@@ -80,6 +93,39 @@ async function replayVariant(deps, captures, req) {
|
|
|
80
93
|
failureClass = err.failure.class;
|
|
81
94
|
}
|
|
82
95
|
const latencyMs = Date.now() - started;
|
|
96
|
+
// Unified value tier (RB-1 §4.3 + Q1 §4.2, F5 deterministic-first). Only on a
|
|
97
|
+
// produced output; a failed sample stays unscored (its per-sample quality is
|
|
98
|
+
// then 1/0 conformance). Precedence for `valueAccuracy` (the regret quality):
|
|
99
|
+
// eval-case expected → declared value_checks → Q1 SDK gate score → RB-1
|
|
100
|
+
// declared judge → null. The Q1 gate, when supplied, ALWAYS runs to set
|
|
101
|
+
// `qualityPassed` (the boolean verdict that drives the cost-first pass-rate
|
|
102
|
+
// gate), independent of which tier won `valueAccuracy`.
|
|
103
|
+
let valueAccuracy = null;
|
|
104
|
+
let qualityPassed = null;
|
|
105
|
+
if (produced) {
|
|
106
|
+
const expected = req.expected?.get(capture.inferenceId);
|
|
107
|
+
const det = (0, quality_1.resolveDeterministicQuality)({
|
|
108
|
+
output: outputValue,
|
|
109
|
+
...(route.valueChecks !== undefined ? { valueChecks: route.valueChecks } : {}),
|
|
110
|
+
...(expected !== undefined ? { hasExpected: true, expected: expected.value } : {}),
|
|
111
|
+
});
|
|
112
|
+
let gateScore = null;
|
|
113
|
+
if (req.qualityGate !== undefined && gated !== null) {
|
|
114
|
+
const verdict = await (0, quality_gate_1.evaluateQualityGate)(req.qualityGate, gated.output, {
|
|
115
|
+
route: req.route, provider: gated.provider, model: gated.model,
|
|
116
|
+
candidateIndex: 0, attemptSeq: 1, servedVariant: gated.servedVariant,
|
|
117
|
+
});
|
|
118
|
+
qualityPassed = verdict.pass;
|
|
119
|
+
gateScore = verdict.score !== null ? verdict.score : verdict.pass ? 1 : 0;
|
|
120
|
+
}
|
|
121
|
+
if (det !== null)
|
|
122
|
+
valueAccuracy = det;
|
|
123
|
+
else if (gateScore !== null)
|
|
124
|
+
valueAccuracy = gateScore;
|
|
125
|
+
else if (req.judge !== undefined) {
|
|
126
|
+
valueAccuracy = await req.judge.score(outputValue, JSON.parse(capture.variablesJson));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
83
129
|
// Per-sample cost from the tagged attempt rows — base + retries + repair
|
|
84
130
|
// + search, exactly what telemetry billed (F1-corrected).
|
|
85
131
|
const attemptRows = deps.telemetry.queryByTag("replay_capture", sampleTag);
|
|
@@ -92,7 +138,8 @@ async function replayVariant(deps, captures, req) {
|
|
|
92
138
|
captureId: capture.inferenceId,
|
|
93
139
|
conform,
|
|
94
140
|
repaired,
|
|
95
|
-
valueAccuracy
|
|
141
|
+
valueAccuracy, // eval-case / value-check / Q1 gate / RB-1 judge, else null (→ conformance)
|
|
142
|
+
qualityPassed, // Q1: boolean gate verdict; null when no gate ran or the sample failed
|
|
96
143
|
costUsd,
|
|
97
144
|
repairCostUsd,
|
|
98
145
|
pricingMissing,
|
package/dist/bakeoff/report.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ import type { RigConfig } from "../config";
|
|
|
8
8
|
import type { CacheSimAssumptions } from "./cache-sim";
|
|
9
9
|
import type { KeepOutputsMode } from "./samples";
|
|
10
10
|
import type { BakeoffArtifact } from "./types";
|
|
11
|
+
import type { ObjectiveMode } from "../types/route";
|
|
12
|
+
/**
|
|
13
|
+
* RB-1 headline (spec §4.5): one line stating the proposed arm's regret with CI
|
|
14
|
+
* vs the reference, its cost, and the ceiling. Returns null when there is no
|
|
15
|
+
* proposal or no reference (the plain matrix stands). Exported for the PR body.
|
|
16
|
+
*/
|
|
17
|
+
export declare function bakeoffHeadline(artifact: BakeoffArtifact): string | null;
|
|
11
18
|
/**
|
|
12
19
|
* One variant's measured-cold vs cache-adjusted input cost (cache-aware-routing
|
|
13
20
|
* spec §5.4). Built by the caller from `simulateCache` + the candidate's rates;
|
|
@@ -37,5 +44,5 @@ export declare function printMatrix(artifact: BakeoffArtifact, cacheAdjusted?: r
|
|
|
37
44
|
* Returns the process exit code: 0 on a run (or an empty suite), 1 when cases
|
|
38
45
|
* existed but NONE could be rehydrated (an actionable non-zero, not a crash).
|
|
39
46
|
*/
|
|
40
|
-
export declare function runEvalCaseBakeoffCli(config: RigConfig, task: string, variants: readonly string[], envelope: number | undefined, keep: KeepOutputsMode | undefined, asJson: boolean): Promise<number>;
|
|
47
|
+
export declare function runEvalCaseBakeoffCli(config: RigConfig, task: string, variants: readonly string[], envelope: number | undefined, keep: KeepOutputsMode | undefined, asJson: boolean, objectiveMode?: ObjectiveMode): Promise<number>;
|
|
41
48
|
//# sourceMappingURL=report.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bakeoff/report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bakeoff/report.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAkCpD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAexE;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAgCtF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,GAAE,SAAS,gBAAgB,EAAO,GAAG,IAAI,CAkD5G;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,MAAM,EAAE,OAAO,EACf,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
|
package/dist/bakeoff/report.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* with a reason before any bake-off runs — never a silent subset (spec §3.3).
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.bakeoffHeadline = bakeoffHeadline;
|
|
9
10
|
exports.formatCacheAdjustedSection = formatCacheAdjustedSection;
|
|
10
11
|
exports.printMatrix = printMatrix;
|
|
11
12
|
exports.runEvalCaseBakeoffCli = runEvalCaseBakeoffCli;
|
|
@@ -17,6 +18,55 @@ const engine_1 = require("./engine");
|
|
|
17
18
|
function fmtEff(usd) {
|
|
18
19
|
return Number.isFinite(usd) ? `$${usd.toFixed(4)}` : "∞ (0 conformant)";
|
|
19
20
|
}
|
|
21
|
+
/** RB-1: quality points (a 0..1 metric shown as pts, ×100). */
|
|
22
|
+
function fmtPts(v) {
|
|
23
|
+
return (v * 100).toFixed(1);
|
|
24
|
+
}
|
|
25
|
+
/** RB-1: an arm's regret cell — "0.8 [−0.4…2.1] n=212", "ref", or "—". */
|
|
26
|
+
function fmtRegret(m) {
|
|
27
|
+
if (m.isReference === true)
|
|
28
|
+
return "ref";
|
|
29
|
+
const r = m.regret;
|
|
30
|
+
if (r == null)
|
|
31
|
+
return "—";
|
|
32
|
+
return `${fmtPts(r.regretMean)} [${fmtPts(r.regretCi95[0])}…${fmtPts(r.regretCi95[1])}] n=${r.nPaired}`;
|
|
33
|
+
}
|
|
34
|
+
/** RB-1: an arm's quality cell — "94% value_accuracy" or "—". */
|
|
35
|
+
function fmtQuality(m) {
|
|
36
|
+
return m.qualityMean === null ? "—" : `${(m.qualityMean * 100).toFixed(0)}% ${m.qualityMetric}`;
|
|
37
|
+
}
|
|
38
|
+
/** The reference $/1k or per-run ceiling from the objective, formatted, or null
|
|
39
|
+
* when no ceiling is declared. */
|
|
40
|
+
function fmtCeiling(artifact) {
|
|
41
|
+
const c = artifact.objective?.priceCeiling;
|
|
42
|
+
if (c == null)
|
|
43
|
+
return null;
|
|
44
|
+
if (c.usdPer1k !== undefined)
|
|
45
|
+
return `$${c.usdPer1k.toFixed(2)}/1k`;
|
|
46
|
+
if (c.usdPerRun !== undefined)
|
|
47
|
+
return `$${c.usdPerRun.toFixed(4)}/run`;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* RB-1 headline (spec §4.5): one line stating the proposed arm's regret with CI
|
|
52
|
+
* vs the reference, its cost, and the ceiling. Returns null when there is no
|
|
53
|
+
* proposal or no reference (the plain matrix stands). Exported for the PR body.
|
|
54
|
+
*/
|
|
55
|
+
function bakeoffHeadline(artifact) {
|
|
56
|
+
const p = artifact.proposal;
|
|
57
|
+
const ref = artifact.reference;
|
|
58
|
+
if (p == null || ref == null || p.regret == null)
|
|
59
|
+
return null;
|
|
60
|
+
const arm = artifact.variants.find((v) => v.variant === p.variant);
|
|
61
|
+
const cost = arm !== undefined && Number.isFinite(arm.effectiveUsdPer1kConformant)
|
|
62
|
+
? `$${arm.effectiveUsdPer1kConformant.toFixed(2)}/1k`
|
|
63
|
+
: "cost n/a";
|
|
64
|
+
const ceiling = fmtCeiling(artifact);
|
|
65
|
+
const r = p.regret;
|
|
66
|
+
return (`${p.variant} — regret ${fmtPts(r.regretMean)} pts ` +
|
|
67
|
+
`(CI ${fmtPts(r.regretCi95[0])}…${fmtPts(r.regretCi95[1])}, n=${r.nPaired}) vs reference ${ref.model}, ` +
|
|
68
|
+
`${cost}${ceiling !== null ? ` vs ceiling ${ceiling}` : ""} — proposed`);
|
|
69
|
+
}
|
|
20
70
|
/**
|
|
21
71
|
* Render the cache-adjusted section (spec §5.4) as lines — measured-cold AND
|
|
22
72
|
* cache-adjusted input cost side by side, with the simulator's assumptions
|
|
@@ -59,27 +109,47 @@ function formatCacheAdjustedSection(rows) {
|
|
|
59
109
|
}
|
|
60
110
|
function printMatrix(artifact, cacheAdjusted = []) {
|
|
61
111
|
console.log(`bakeoff ${artifact.id}\nroute ${artifact.route}@${artifact.routeVersion} · replayed ${artifact.replayN} captured inputs · ${artifact.ranAt}`);
|
|
112
|
+
// RB-1: a reference arm is present ⇒ show quality + regret columns and badge
|
|
113
|
+
// the reference row; otherwise the pre-RB-1 matrix stands byte-for-byte.
|
|
114
|
+
const hasReference = artifact.variants.some((m) => m.isReference === true);
|
|
62
115
|
const header = ["variant", "n", "conform", "ci95", "repair", "repair$", "lat(ms)", "eff-$/1K-conf"];
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
116
|
+
if (hasReference)
|
|
117
|
+
header.push("quality", "regret(pts)");
|
|
118
|
+
const rows = artifact.variants.map((m) => {
|
|
119
|
+
const base = [
|
|
120
|
+
m.isReference === true ? `${m.variant} [ref]` : m.variant,
|
|
121
|
+
String(m.samples),
|
|
122
|
+
`${(m.conformRate * 100).toFixed(0)}%`,
|
|
123
|
+
`[${(m.conformCi95[0] * 100).toFixed(0)},${(m.conformCi95[1] * 100).toFixed(0)}]`,
|
|
124
|
+
`${(m.repairRate * 100).toFixed(0)}%`,
|
|
125
|
+
`$${m.meanRepairCostUsd.toFixed(4)}`,
|
|
126
|
+
m.meanLatencyMs.toFixed(0),
|
|
127
|
+
m.pricingMissing ? "UNPRICED (no ranking)" : fmtEff(m.effectiveUsdPer1kConformant),
|
|
128
|
+
];
|
|
129
|
+
if (hasReference)
|
|
130
|
+
base.push(fmtQuality(m), fmtRegret(m));
|
|
131
|
+
return base;
|
|
132
|
+
});
|
|
73
133
|
const widths = header.map((h, c) => Math.max(h.length, ...rows.map((r) => r[c].length)));
|
|
74
134
|
const line = (cells) => cells.map((cell, c) => cell.padEnd(widths[c])).join(" ");
|
|
75
135
|
console.log(line(header));
|
|
76
136
|
for (const row of rows)
|
|
77
137
|
console.log(line(row));
|
|
78
|
-
|
|
138
|
+
const headline = bakeoffHeadline(artifact);
|
|
139
|
+
if (headline !== null) {
|
|
140
|
+
console.log(`PROPOSAL: ${headline} — human decision required; nothing is executed.`);
|
|
141
|
+
}
|
|
142
|
+
else if (artifact.proposal !== null) {
|
|
79
143
|
console.log(`PROPOSAL: variant "${artifact.proposal.variant}" beats the incumbent by ` +
|
|
80
144
|
`${artifact.proposal.vsIncumbentSavingsPct.toFixed(1)}% effective cost — human decision required; ` +
|
|
81
145
|
"nothing is executed.");
|
|
82
146
|
}
|
|
147
|
+
else if (hasReference && artifact.objective !== undefined) {
|
|
148
|
+
const bound = artifact.objective.maxRegret === null && artifact.objective.priceCeiling === null;
|
|
149
|
+
console.log(bound
|
|
150
|
+
? "proposal: none — regret reported; declare objective.max_regret or price_ceiling to propose."
|
|
151
|
+
: "proposal: none (no arm is within the objective's bounds and better than the incumbent)");
|
|
152
|
+
}
|
|
83
153
|
else {
|
|
84
154
|
console.log("proposal: none (no variant beats the incumbent's gates)");
|
|
85
155
|
}
|
|
@@ -95,7 +165,7 @@ function printMatrix(artifact, cacheAdjusted = []) {
|
|
|
95
165
|
* Returns the process exit code: 0 on a run (or an empty suite), 1 when cases
|
|
96
166
|
* existed but NONE could be rehydrated (an actionable non-zero, not a crash).
|
|
97
167
|
*/
|
|
98
|
-
async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJson) {
|
|
168
|
+
async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJson, objectiveMode) {
|
|
99
169
|
const mirrorTarget = config.export?.mode === "direct" ? config.export : undefined;
|
|
100
170
|
const rig = (0, index_1.createRig)(config);
|
|
101
171
|
try {
|
|
@@ -103,6 +173,7 @@ async function runEvalCaseBakeoffCli(config, task, variants, envelope, keep, asJ
|
|
|
103
173
|
variants,
|
|
104
174
|
...(envelope !== undefined ? { envelopeUsdPerVariant: envelope } : {}),
|
|
105
175
|
...(keep !== undefined ? { keepOutputs: keep } : {}),
|
|
176
|
+
...(objectiveMode !== undefined ? { objectiveMode } : {}),
|
|
106
177
|
});
|
|
107
178
|
console.log(`eval cases for "${task}": ${selection.total} total · ` +
|
|
108
179
|
`${selection.rehydratable.length} rehydratable · ${selection.refusals.length} refused`);
|
|
@@ -8,8 +8,19 @@
|
|
|
8
8
|
* key; read server-side only; deny-anon RLS. NEVER the anon staging posture.
|
|
9
9
|
*
|
|
10
10
|
* Transport is raw PostgREST over fetch (same pattern as the exporter).
|
|
11
|
+
*
|
|
12
|
+
* ── SCRUB BEFORE PERSIST (S7, screening B0-a) ───────────────────────────────
|
|
13
|
+
* `bakeoff_samples` is the largest server-side customer-content path, so the
|
|
14
|
+
* task's scrub setting — the SAME `ScrubMode` the content-custody + capture-write
|
|
15
|
+
* paths honor — runs on `inputText` AND `outputText` BEFORE any row is built or
|
|
16
|
+
* POSTed (mirrors src/run/capture-write.ts). off/detect ⇒ verbatim (detect still
|
|
17
|
+
* COUNTS findings onto the row's `arm_meta.scrub`, never values); redact ⇒ masked
|
|
18
|
+
* copy; block ⇒ a sample with any finding becomes metadata-only (input/output
|
|
19
|
+
* dropped) and counted. Findings are `{type,count}` only — never a matched value.
|
|
11
20
|
*/
|
|
12
21
|
import type { BakeoffSamplesSink } from "./engine";
|
|
22
|
+
import { type ScrubMode } from "../artifacts/scrub";
|
|
23
|
+
import type { TaskSettingsResolver } from "../settings/task-settings";
|
|
13
24
|
/** 256KB per output, per the architect's caps; TOAST compresses at rest. */
|
|
14
25
|
export declare const OUTPUT_MAX_BYTES: number;
|
|
15
26
|
export type KeepOutputsMode = "winner" | "all";
|
|
@@ -19,7 +30,9 @@ export interface BakeoffSample {
|
|
|
19
30
|
readonly arm: string;
|
|
20
31
|
readonly armMeta: Record<string, unknown> | null;
|
|
21
32
|
readonly inputRef: string;
|
|
22
|
-
|
|
33
|
+
/** Null when `block`-mode scrub dropped it (metadata-only sample). At persist
|
|
34
|
+
* the NOT-NULL `input_text` column coalesces null → "" (see the insert body). */
|
|
35
|
+
readonly inputText: string | null;
|
|
23
36
|
readonly outputText: string | null;
|
|
24
37
|
readonly conform: boolean;
|
|
25
38
|
readonly costUsd: number;
|
|
@@ -45,7 +58,13 @@ export declare function truncateOutput(text: string | null): string | null;
|
|
|
45
58
|
/** Default retention: 90 days from now (or pass a sooner stamp). Pure over
|
|
46
59
|
* an injected `now` so it stays testable and workflow-safe. */
|
|
47
60
|
export declare function retentionExpiry(nowMs: number, days?: number): string;
|
|
48
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Flatten arms×samples into rows, scrubbing input + output with the task's
|
|
63
|
+
* `mode` (S7, default `off` ⇒ verbatim, byte-identical). Scrub findings ride the
|
|
64
|
+
* row's `arm_meta.scrub` as counts-by-type (never values); a `block`-mode sample
|
|
65
|
+
* with any finding is stored metadata-only (input/output dropped).
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildBakeoffSamples(bakeoffId: string, route: string, arms: readonly ArmSamples[], expiresAt: string, mode?: ScrubMode): BakeoffSample[];
|
|
49
68
|
export interface BakeoffSamplesClientConfig {
|
|
50
69
|
readonly supabaseUrl: string;
|
|
51
70
|
readonly serviceRoleKey: string;
|
|
@@ -73,5 +92,19 @@ export declare function createBakeoffSamplesClient(config: BakeoffSamplesClientC
|
|
|
73
92
|
export declare function buildDirectSamplesSink(cfg: {
|
|
74
93
|
readonly supabaseUrl: string;
|
|
75
94
|
readonly serviceRoleKey: string;
|
|
95
|
+
/** Control-plane task-settings resolver (settings-plane §1.5). Null local-only
|
|
96
|
+
* / direct-mode rigs have none — then `defaultScrub` governs. */
|
|
97
|
+
readonly resolver?: TaskSettingsResolver | null;
|
|
98
|
+
/** The rig-level scrub default (config content-custody scrub) — the fallback
|
|
99
|
+
* when no resolver answers for the route. Defaults `off` ⇒ verbatim. */
|
|
100
|
+
readonly defaultScrub?: ScrubMode;
|
|
76
101
|
}): BakeoffSamplesSink;
|
|
102
|
+
/**
|
|
103
|
+
* The effective scrub mode for a bake-off sample write — resolved EXACTLY as
|
|
104
|
+
* capture-write.ts does: a configured control plane WINS (`resolver.get(route)`),
|
|
105
|
+
* else the rig's config default. A local-only / direct-mode rig has no resolver,
|
|
106
|
+
* so it falls to `configDefault`. Pure and synchronous (the resolver read is
|
|
107
|
+
* cache-only, never a network hop).
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveSampleScrubMode(resolver: TaskSettingsResolver | null, route: string, configDefault: ScrubMode): ScrubMode;
|
|
77
110
|
//# sourceMappingURL=samples.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/bakeoff/samples.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/bakeoff/samples.ts"],"names":[],"mappings":"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"}
|