modelrig 0.4.0 → 0.6.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 +238 -0
- package/README.md +65 -3
- 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 +39 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +82 -4
- 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 +3 -2
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +35 -9
- 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 +33 -2
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +95 -1
- 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 +65 -2
- 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 +47 -24
- 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 +34 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -12
- 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/registry/select.d.ts +59 -0
- package/dist/registry/select.d.ts.map +1 -1
- package/dist/registry/select.js +66 -0
- package/dist/routes/loader-warnings.d.ts +28 -0
- package/dist/routes/loader-warnings.d.ts.map +1 -0
- package/dist/routes/loader-warnings.js +56 -0
- package/dist/routes/loader.d.ts +1 -11
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +54 -31
- 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-defaults.d.ts +52 -0
- package/dist/run/raw-defaults.d.ts.map +1 -0
- package/dist/run/raw-defaults.js +123 -0
- package/dist/run/raw.d.ts +12 -0
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +107 -8
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +22 -1
- package/dist/run/resolve.d.ts +17 -1
- package/dist/run/resolve.d.ts.map +1 -1
- package/dist/run/resolve.js +20 -4
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +12 -5
- package/dist/run/run.d.ts +7 -1
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +54 -69
- package/dist/run/variant.d.ts +43 -0
- package/dist/run/variant.d.ts.map +1 -0
- package/dist/run/variant.js +86 -0
- package/dist/savings/emit.d.ts +33 -0
- package/dist/savings/emit.d.ts.map +1 -1
- package/dist/savings/emit.js +75 -3
- 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 +120 -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/dist/watch/evaluate.d.ts +27 -1
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +13 -0
- package/dist/watch/proposals.d.ts +28 -0
- package/dist/watch/proposals.d.ts.map +1 -1
- package/dist/watch/proposals.js +51 -0
- package/package.json +1 -1
package/dist/run/run.js
CHANGED
|
@@ -17,6 +17,7 @@ const pricing_1 = require("../registry/pricing");
|
|
|
17
17
|
const failures_1 = require("../types/failures");
|
|
18
18
|
const manifest_1 = require("../routes/manifest");
|
|
19
19
|
const resolve_1 = require("./resolve");
|
|
20
|
+
const variant_1 = require("./variant");
|
|
20
21
|
const envelope_1 = require("./envelope");
|
|
21
22
|
const retry_1 = require("./retry");
|
|
22
23
|
const repair_1 = require("./repair");
|
|
@@ -25,37 +26,10 @@ const validate_1 = require("./validate");
|
|
|
25
26
|
const dispatch_1 = require("./dispatch");
|
|
26
27
|
const record_attempt_1 = require("./record-attempt");
|
|
27
28
|
const ladder_key_1 = require("./ladder-key");
|
|
29
|
+
const quality_gate_1 = require("./quality-gate");
|
|
30
|
+
const accept_1 = require("./accept");
|
|
28
31
|
const run_context_1 = require("../artifacts/run-context");
|
|
29
32
|
const capture_write_1 = require("./capture-write");
|
|
30
|
-
/** Look up a named variant; null = default (base config). Unknown names are
|
|
31
|
-
* config errors — the caller asked for something the bundle doesn't define. */
|
|
32
|
-
function resolveVariant(route, name) {
|
|
33
|
-
if (name === undefined || name === "default")
|
|
34
|
-
return null;
|
|
35
|
-
const variant = route.variants.find((v) => v.name === name);
|
|
36
|
-
if (variant === undefined) {
|
|
37
|
-
throw new loader_1.RouteConfigError(`route "${route.route}" has no variant "${name}" — defined: [${route.variants
|
|
38
|
-
.map((v) => v.name)
|
|
39
|
-
.join(", ")}]`);
|
|
40
|
-
}
|
|
41
|
-
return variant;
|
|
42
|
-
}
|
|
43
|
-
/** The variant-effective view of a route: narrowed candidates, overridden
|
|
44
|
-
* json rung, overridden template. Loader validation guarantees variant
|
|
45
|
-
* candidates ⊆ declared candidates, so the candidate-set invariant holds. */
|
|
46
|
-
function effectiveRoute(route, variant) {
|
|
47
|
-
if (variant === null)
|
|
48
|
-
return route;
|
|
49
|
-
return Object.freeze({
|
|
50
|
-
...route,
|
|
51
|
-
candidates: variant.candidates ?? route.candidates,
|
|
52
|
-
systemTemplate: variant.promptOverride ?? route.systemTemplate,
|
|
53
|
-
policy: Object.freeze({
|
|
54
|
-
...route.policy,
|
|
55
|
-
json: variant.json ?? route.policy.json,
|
|
56
|
-
}),
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
33
|
/** Phase 1 prompt convention: the bundle's system template carries the whole
|
|
60
34
|
* task (constitution, data, step instructions); the user turn is a terse
|
|
61
35
|
* trigger. Providers treat long system prompts + short user turns well, and
|
|
@@ -73,10 +47,11 @@ function costPerMTok(provider, model) {
|
|
|
73
47
|
}
|
|
74
48
|
const DEFAULT_SLEEP = (ms) => ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
49
|
async function run(deps, routeName, opts) {
|
|
50
|
+
const mode = deps.mode ?? "live";
|
|
76
51
|
const baseRoute = deps.routes.get(routeName);
|
|
77
|
-
const variant = resolveVariant(baseRoute, opts.variant);
|
|
52
|
+
const variant = (0, variant_1.resolveVariant)(baseRoute, opts.variant, mode);
|
|
78
53
|
const servedVariant = variant?.name ?? null;
|
|
79
|
-
const route = (0, resolve_1.applyRunRequirements)(effectiveRoute(baseRoute, variant), opts.zeroRetention === true);
|
|
54
|
+
const route = (0, resolve_1.applyRunRequirements)((0, variant_1.effectiveRoute)(baseRoute, variant), opts.zeroRetention === true);
|
|
80
55
|
const effectiveTags = route.require.includes("zero_retention") ? { ...opts.tags, zdr_enforced: "true" } : opts.tags;
|
|
81
56
|
// Data-hygiene warning (spec WS5): once per process per dimension.
|
|
82
57
|
(0, manifest_1.warnMissingRequiredDimensions)(deps.manifest ?? null, opts.tags);
|
|
@@ -96,7 +71,7 @@ async function run(deps, routeName, opts) {
|
|
|
96
71
|
// exit that records an inference also records a ground-truth step referencing
|
|
97
72
|
// it (attempt-per-row, ruling c). Inert without a run.start on this path.
|
|
98
73
|
const runCtx = (0, run_context_1.getActiveRunContext)();
|
|
99
|
-
const autoStepKey = runCtx !== null ? runCtx.nextStepKey(route.route) : null;
|
|
74
|
+
const autoStepKey = runCtx !== null ? (0, run_context_1.resolveStepKey)(opts.stepKey, opts.tags.step, () => runCtx.nextStepKey(route.route)) : null;
|
|
100
75
|
// Per-call ladder key (spec §2.1 R0): one call_id, a monotonic attempt_seq per
|
|
101
76
|
// row, and the current candidate_index (advanced by nextCandidate below).
|
|
102
77
|
const ladder = (0, ladder_key_1.createLadderKeyMinter)();
|
|
@@ -146,6 +121,14 @@ async function run(deps, routeName, opts) {
|
|
|
146
121
|
systemPrompt = originalSystemPrompt + (0, repair_1.repairFeedbackBlock)(errors);
|
|
147
122
|
}
|
|
148
123
|
else {
|
|
124
|
+
// Declare-first backstop (spec §4.4): the loader rejects a `trial` candidate
|
|
125
|
+
// as `repair_model`, but a programmatically-built bundle (runBundle) could
|
|
126
|
+
// bypass the loader — so refuse to mint a trial candidate for LIVE repair
|
|
127
|
+
// here too (the primary ladder's trial-drop has no repair-path equivalent).
|
|
128
|
+
if (mode === "live" &&
|
|
129
|
+
route.candidates.some((c) => c.provider === step.model.provider && c.model === step.model.model && c.trial === true)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
149
132
|
// Brand stays in resolve.ts; loader validated repairModel ⊆ declared.
|
|
150
133
|
const minted = (0, resolve_1.resolveDeclaredCandidate)(route, step.model);
|
|
151
134
|
if (minted === null)
|
|
@@ -190,6 +173,7 @@ async function run(deps, routeName, opts) {
|
|
|
190
173
|
failure: result.failure,
|
|
191
174
|
repairedBy,
|
|
192
175
|
pricingMissing: repairFailurePricingMissing,
|
|
176
|
+
metaInputs: (0, dispatch_1.attemptMetaInputs)({ systemPrompt, userDirective: REPAIR_DIRECTIVE, outputSchema: route.outputSchema, result }),
|
|
193
177
|
});
|
|
194
178
|
if (failures_1.TERMINAL_CLASSES.has(result.failure.class)) {
|
|
195
179
|
throw new failures_1.RigFailureError(result.failure);
|
|
@@ -221,6 +205,7 @@ async function run(deps, routeName, opts) {
|
|
|
221
205
|
failure,
|
|
222
206
|
repairedBy,
|
|
223
207
|
pricingMissing: repairPricingMissing,
|
|
208
|
+
metaInputs: (0, dispatch_1.attemptMetaInputs)({ systemPrompt, userDirective: REPAIR_DIRECTIVE, outputSchema: route.outputSchema, result }),
|
|
224
209
|
});
|
|
225
210
|
captureAttempt(inferenceId, result.text);
|
|
226
211
|
if (!outcome.valid) {
|
|
@@ -255,7 +240,7 @@ async function run(deps, routeName, opts) {
|
|
|
255
240
|
};
|
|
256
241
|
}
|
|
257
242
|
const searchAvailable = deps.search !== undefined && deps.search !== null;
|
|
258
|
-
const iter = (0, resolve_1.resolveCandidates)(route, deps.capabilities, costPerMTok, searchAvailable, (0, resolve_1.candidateFilterFromOptions)(opts));
|
|
243
|
+
const iter = (0, resolve_1.resolveCandidates)(route, deps.capabilities, costPerMTok, searchAvailable, (0, resolve_1.candidateFilterFromOptions)(opts), undefined, mode);
|
|
259
244
|
// Advance the iterator AND publish the served candidate's index to the ladder,
|
|
260
245
|
// so every row of this candidate's turn (incl. repair rows) carries it (§2.1).
|
|
261
246
|
const nextCandidate = () => {
|
|
@@ -405,47 +390,45 @@ async function run(deps, routeName, opts) {
|
|
|
405
390
|
servedVariant,
|
|
406
391
|
pricingMissing,
|
|
407
392
|
callId: ladder.callId,
|
|
393
|
+
metaInputs: (0, dispatch_1.attemptMetaInputs)({ systemPrompt, userDirective: USER_DIRECTIVE, outputSchema: route.outputSchema, result, tools: opts.tools }),
|
|
408
394
|
});
|
|
409
395
|
}
|
|
410
396
|
const outcome = (0, validate_1.validateOutput)(route, result.text);
|
|
397
|
+
const metaInputs = (0, dispatch_1.attemptMetaInputs)({ systemPrompt, userDirective: USER_DIRECTIVE, outputSchema: route.outputSchema, result, tools: opts.tools });
|
|
411
398
|
if (outcome.valid) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
399
|
+
// Q1 quality gate (spec §4.1): a schema-valid output must still clear the
|
|
400
|
+
// builder's bar before it is accepted. Tool-call turns never reach here;
|
|
401
|
+
// a `schema: null` route gates on the parsed JSON just the same. Gate
|
|
402
|
+
// logic (async await, fail-closed throw, tag/message shaping) lives in
|
|
403
|
+
// ./quality-gate.ts so this stays ONE seam.
|
|
404
|
+
const gateMeta = { route: route.route, provider: candidate.provider, model: candidate.model,
|
|
405
|
+
candidateIndex: iter.index, attemptSeq: ladder.peekNextSeq(), servedVariant };
|
|
406
|
+
const gate = opts.qualityGate === undefined
|
|
407
|
+
? null
|
|
408
|
+
: await (0, quality_gate_1.evaluateQualityGate)(opts.qualityGate, outcome.parsed, gateMeta);
|
|
409
|
+
if (gate !== null && !gate.pass) {
|
|
410
|
+
// Rejected on quality: its OWN class + budget; the repair rung is NEVER
|
|
411
|
+
// entered from here (spec §4.1 step 3). The rejected text is captured
|
|
412
|
+
// so it stays replayable.
|
|
413
|
+
const failure = { class: "quality_rejected",
|
|
414
|
+
message: (0, quality_gate_1.qualityRejectedMessage)(gate), provider: candidate.provider, model: candidate.model };
|
|
415
|
+
const rejectedId = recordAttempt((0, accept_1.successAttemptRow)(candidate, result.usage, costUsd, latencyMs, pricingMissing, failure, metaInputs, (0, quality_gate_1.qualityTags)(gate)));
|
|
416
|
+
captureAttempt(rejectedId, result.text);
|
|
417
|
+
lastFailure = failure;
|
|
418
|
+
if (ledger.consume("quality_rejected"))
|
|
419
|
+
continue; // re-sample the SAME candidate
|
|
420
|
+
candidate = nextCandidate();
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const acceptTags = gate !== null ? (0, quality_gate_1.qualityTags)(gate) : undefined;
|
|
424
|
+
const inferenceId = recordAttempt((0, accept_1.successAttemptRow)(candidate, result.usage, costUsd, latencyMs, pricingMissing, null, metaInputs, acceptTags));
|
|
426
425
|
captureAttempt(inferenceId, result.text);
|
|
427
|
-
const meta = {
|
|
428
|
-
inferenceId,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
requestedTier: route.policy.tier,
|
|
434
|
-
servedTier: result.usage.servedTier,
|
|
435
|
-
tokensIn: result.usage.tokensIn,
|
|
436
|
-
tokensOut: result.usage.tokensOut,
|
|
437
|
-
tokensCached: result.usage.tokensCached,
|
|
438
|
-
costEstimateUsd: costUsd,
|
|
439
|
-
latencyMs,
|
|
440
|
-
validated: true,
|
|
441
|
-
attemptsByClass: ledger.snapshot(),
|
|
442
|
-
servedVariant,
|
|
443
|
-
repairedBy: null,
|
|
444
|
-
callId: ladder.callId,
|
|
445
|
-
...(injectedSources !== null
|
|
446
|
-
? { citations: (0, grounding_1.extractCitations)(result.text, injectedSources) }
|
|
447
|
-
: {}),
|
|
448
|
-
};
|
|
426
|
+
const meta = (0, accept_1.buildSuccessMeta)({
|
|
427
|
+
inferenceId, route, candidate, usage: result.usage, costUsd, latencyMs,
|
|
428
|
+
attemptsByClass: ledger.snapshot(), servedVariant, callId: ladder.callId,
|
|
429
|
+
...(injectedSources !== null ? { citations: (0, grounding_1.extractCitations)(result.text, injectedSources) } : {}),
|
|
430
|
+
...(gate !== null ? { quality: { score: gate.score, verdict: "pass", judgedBy: null } } : {}),
|
|
431
|
+
});
|
|
449
432
|
return { output: outcome.parsed, meta };
|
|
450
433
|
}
|
|
451
434
|
// Schema-invalid output — a real (billed) attempt that failed.
|
|
@@ -468,6 +451,7 @@ async function run(deps, routeName, opts) {
|
|
|
468
451
|
...(result.usage.ttfbMs !== undefined ? { ttfbMs: result.usage.ttfbMs } : {}),
|
|
469
452
|
failure,
|
|
470
453
|
pricingMissing,
|
|
454
|
+
metaInputs, // computed once above (same `result`) — reused across valid/invalid
|
|
471
455
|
});
|
|
472
456
|
captureAttempt(invalidId, result.text);
|
|
473
457
|
lastFailure = failure;
|
|
@@ -520,6 +504,7 @@ async function run(deps, routeName, opts) {
|
|
|
520
504
|
...(result.usage?.ttfbMs !== undefined ? { ttfbMs: result.usage.ttfbMs } : {}),
|
|
521
505
|
failure,
|
|
522
506
|
pricingMissing: failurePricingMissing,
|
|
507
|
+
metaInputs: (0, dispatch_1.attemptMetaInputs)({ systemPrompt, userDirective: USER_DIRECTIVE, outputSchema: route.outputSchema, result, tools: opts.tools }),
|
|
523
508
|
});
|
|
524
509
|
if (failures_1.TERMINAL_CLASSES.has(failure.class)) {
|
|
525
510
|
throw new failures_1.RigFailureError(failure);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant resolution — extracted from run.ts (spec §4.4, R3) to host the
|
|
3
|
+
* declare-first "a trial variant selected live is a config error" rule and to
|
|
4
|
+
* keep run.ts under the LOC cap. Pure: bundle + requested name (+ run mode) →
|
|
5
|
+
* the resolved variant and the variant-effective route view.
|
|
6
|
+
*/
|
|
7
|
+
import type { RouteBundle, RouteVariant } from "../types/route";
|
|
8
|
+
import type { RunMode } from "./resolve";
|
|
9
|
+
/**
|
|
10
|
+
* A variant is trial-only when it has candidates and EVERY one maps to a
|
|
11
|
+
* declared `trial` candidate. Computed dynamically from `route.candidates`
|
|
12
|
+
* rather than trusted from the stored `variant.trialOnly` stamp, so a bundle
|
|
13
|
+
* built in code — a programmatic `runBundle` whose variants never passed
|
|
14
|
+
* through the loader that sets the flag — is judged exactly like a loaded one
|
|
15
|
+
* (and still earns the teaching error on a live trial selection). The loader's
|
|
16
|
+
* stamp, when present, is honored as a fast path.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isTrialOnlyVariant(route: RouteBundle, variant: RouteVariant): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Look up a named variant; null = default (base config). Unknown names are
|
|
21
|
+
* config errors — the caller asked for something the bundle doesn't define.
|
|
22
|
+
*
|
|
23
|
+
* Declare-first (spec §4.4): a `trialOnly` variant — one whose candidates are
|
|
24
|
+
* ALL declared `trial` candidates — is valid only in bake-off replay
|
|
25
|
+
* (`mode: "trial"`). Selecting it on a LIVE path is a `RouteConfigError` with a
|
|
26
|
+
* teaching message that names the bake-off, never a silent fallback (residual
|
|
27
|
+
* flag F2′): the run loop must not dispatch an undeclared OR trial model live.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveVariant(route: RouteBundle, name: string | undefined, mode?: RunMode): RouteVariant | null;
|
|
30
|
+
/**
|
|
31
|
+
* The variant-effective view of a route: narrowed candidates, overridden json
|
|
32
|
+
* rung, overridden template. Loader validation guarantees variant candidates ⊆
|
|
33
|
+
* declared candidates, so the candidate-set invariant holds.
|
|
34
|
+
*
|
|
35
|
+
* Declare-first (spec §4.4): a variant's `candidates` entries are authored as
|
|
36
|
+
* bare `{provider, model}` and do NOT carry the route-level `trial` flag. We
|
|
37
|
+
* MUST re-attach it from the matching declared candidate — otherwise a variant
|
|
38
|
+
* that narrows to a trial candidate (e.g. a mixed trial+live variant, which is
|
|
39
|
+
* not `trialOnly` and IS selectable live) would strip the flag and
|
|
40
|
+
* `resolveCandidates`' live trial-drop would let the trial model serve live.
|
|
41
|
+
*/
|
|
42
|
+
export declare function effectiveRoute(route: RouteBundle, variant: RouteVariant | null): RouteBundle;
|
|
43
|
+
//# sourceMappingURL=variant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant.d.ts","sourceRoot":"","sources":["../../src/run/variant.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAYrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,GAAE,OAAgB,GACrB,YAAY,GAAG,IAAI,CAkBrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,WAAW,CAoB5F"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Variant resolution — extracted from run.ts (spec §4.4, R3) to host the
|
|
4
|
+
* declare-first "a trial variant selected live is a config error" rule and to
|
|
5
|
+
* keep run.ts under the LOC cap. Pure: bundle + requested name (+ run mode) →
|
|
6
|
+
* the resolved variant and the variant-effective route view.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.isTrialOnlyVariant = isTrialOnlyVariant;
|
|
10
|
+
exports.resolveVariant = resolveVariant;
|
|
11
|
+
exports.effectiveRoute = effectiveRoute;
|
|
12
|
+
const loader_1 = require("../routes/loader");
|
|
13
|
+
/**
|
|
14
|
+
* A variant is trial-only when it has candidates and EVERY one maps to a
|
|
15
|
+
* declared `trial` candidate. Computed dynamically from `route.candidates`
|
|
16
|
+
* rather than trusted from the stored `variant.trialOnly` stamp, so a bundle
|
|
17
|
+
* built in code — a programmatic `runBundle` whose variants never passed
|
|
18
|
+
* through the loader that sets the flag — is judged exactly like a loaded one
|
|
19
|
+
* (and still earns the teaching error on a live trial selection). The loader's
|
|
20
|
+
* stamp, when present, is honored as a fast path.
|
|
21
|
+
*/
|
|
22
|
+
function isTrialOnlyVariant(route, variant) {
|
|
23
|
+
if (variant.trialOnly === true)
|
|
24
|
+
return true;
|
|
25
|
+
const cands = variant.candidates;
|
|
26
|
+
return (cands !== undefined &&
|
|
27
|
+
cands.length > 0 &&
|
|
28
|
+
cands.every((vc) => route.candidates.some((rc) => rc.provider === vc.provider && rc.model === vc.model && rc.trial === true)));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Look up a named variant; null = default (base config). Unknown names are
|
|
32
|
+
* config errors — the caller asked for something the bundle doesn't define.
|
|
33
|
+
*
|
|
34
|
+
* Declare-first (spec §4.4): a `trialOnly` variant — one whose candidates are
|
|
35
|
+
* ALL declared `trial` candidates — is valid only in bake-off replay
|
|
36
|
+
* (`mode: "trial"`). Selecting it on a LIVE path is a `RouteConfigError` with a
|
|
37
|
+
* teaching message that names the bake-off, never a silent fallback (residual
|
|
38
|
+
* flag F2′): the run loop must not dispatch an undeclared OR trial model live.
|
|
39
|
+
*/
|
|
40
|
+
function resolveVariant(route, name, mode = "live") {
|
|
41
|
+
if (name === undefined || name === "default")
|
|
42
|
+
return null;
|
|
43
|
+
const variant = route.variants.find((v) => v.name === name);
|
|
44
|
+
if (variant === undefined) {
|
|
45
|
+
throw new loader_1.RouteConfigError(`route "${route.route}" has no variant "${name}" — defined: [${route.variants
|
|
46
|
+
.map((v) => v.name)
|
|
47
|
+
.join(", ")}]`);
|
|
48
|
+
}
|
|
49
|
+
if (mode === "live" && isTrialOnlyVariant(route, variant)) {
|
|
50
|
+
throw new loader_1.RouteConfigError(`route "${route.route}" variant "${name}" is a trial variant (all its candidates are declared ` +
|
|
51
|
+
`trial: true) — trial variants are declared but never served live. Measure it with a bake-off ` +
|
|
52
|
+
`(rig.bakeoff / "Run bake-off" in the console), then promote the winner to a live candidate.`);
|
|
53
|
+
}
|
|
54
|
+
return variant;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The variant-effective view of a route: narrowed candidates, overridden json
|
|
58
|
+
* rung, overridden template. Loader validation guarantees variant candidates ⊆
|
|
59
|
+
* declared candidates, so the candidate-set invariant holds.
|
|
60
|
+
*
|
|
61
|
+
* Declare-first (spec §4.4): a variant's `candidates` entries are authored as
|
|
62
|
+
* bare `{provider, model}` and do NOT carry the route-level `trial` flag. We
|
|
63
|
+
* MUST re-attach it from the matching declared candidate — otherwise a variant
|
|
64
|
+
* that narrows to a trial candidate (e.g. a mixed trial+live variant, which is
|
|
65
|
+
* not `trialOnly` and IS selectable live) would strip the flag and
|
|
66
|
+
* `resolveCandidates`' live trial-drop would let the trial model serve live.
|
|
67
|
+
*/
|
|
68
|
+
function effectiveRoute(route, variant) {
|
|
69
|
+
if (variant === null)
|
|
70
|
+
return route;
|
|
71
|
+
const candidates = variant.candidates === undefined
|
|
72
|
+
? route.candidates
|
|
73
|
+
: variant.candidates.map((vc) => {
|
|
74
|
+
const declared = route.candidates.find((c) => c.provider === vc.provider && c.model === vc.model);
|
|
75
|
+
return declared?.trial === true ? { ...vc, trial: true } : vc;
|
|
76
|
+
});
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
...route,
|
|
79
|
+
candidates,
|
|
80
|
+
systemTemplate: variant.promptOverride ?? route.systemTemplate,
|
|
81
|
+
policy: Object.freeze({
|
|
82
|
+
...route.policy,
|
|
83
|
+
json: variant.json ?? route.policy.json,
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
}
|
package/dist/savings/emit.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* greps for it.
|
|
7
7
|
*/
|
|
8
8
|
import type { CandidateSpec, RouteBundle } from "../types/route";
|
|
9
|
+
/** A challenger to declare/promote — just the identifying pair. Loose on purpose
|
|
10
|
+
* (`provider: string`): a challenger comes from a stored proposal or the picker,
|
|
11
|
+
* where the provider is already validated, and the emitters only render YAML. */
|
|
12
|
+
export interface ChallengerRef {
|
|
13
|
+
readonly provider: string;
|
|
14
|
+
readonly model: string;
|
|
15
|
+
}
|
|
9
16
|
export interface SwapChange {
|
|
10
17
|
readonly route: string;
|
|
11
18
|
readonly file: string;
|
|
@@ -34,5 +41,31 @@ export declare function buildRestoreYaml(route: string, incumbent: readonly {
|
|
|
34
41
|
readonly provider: string;
|
|
35
42
|
readonly model: string;
|
|
36
43
|
}[], currentVersion: number): string;
|
|
44
|
+
/** The paste-ready change that DECLARES a `trial` challenger (routing-console
|
|
45
|
+
* spec §4.4, declare step). Additive-only: nothing existing is removed. */
|
|
46
|
+
export interface DeclareTrialChange {
|
|
47
|
+
readonly route: string;
|
|
48
|
+
readonly file: string;
|
|
49
|
+
readonly challenger: ChallengerRef;
|
|
50
|
+
/** The auto-named trial variant a bake-off replays (`trial-<provider>-<model>`). */
|
|
51
|
+
readonly trialVariant: string;
|
|
52
|
+
readonly newVersion: number;
|
|
53
|
+
/** Paste-ready YAML: version bump + full candidates block (challenger appended
|
|
54
|
+
* with `trial: true`) + the trial variant to add. Complete, no elisions (A4). */
|
|
55
|
+
readonly yaml: string;
|
|
56
|
+
}
|
|
57
|
+
/** A stable, YAML-safe trial variant name for a challenger. */
|
|
58
|
+
export declare function trialVariantName(challenger: ChallengerRef): string;
|
|
59
|
+
/**
|
|
60
|
+
* Emit the declare-first YAML that adds a `trial` challenger to a route (spec
|
|
61
|
+
* §4.4 step 1): the challenger appended to `candidates` with `trial: true`, a
|
|
62
|
+
* `trial-<provider>-<model>` variant that narrows to it, and `version: n+1`. The
|
|
63
|
+
* challenger is DECLARED but never served live (the loader/resolver enforce
|
|
64
|
+
* that) — so a bake-off can measure it on real traffic before promotion. Pure,
|
|
65
|
+
* no filesystem (emit.test.ts greps for that). Throws when the challenger is
|
|
66
|
+
* already a declared candidate — you don't declare what's already declared (the
|
|
67
|
+
* server maps this to 422).
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildDeclareTrialChange(bundle: RouteBundle, challenger: ChallengerRef): DeclareTrialChange;
|
|
37
70
|
export declare function buildSwapChange(bundle: RouteBundle, winningVariant: string): SwapChange;
|
|
38
71
|
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/savings/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAgB,MAAM,gBAAgB,CAAC;AAE/E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;yCACqC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;+DAC2D;IAC3D,QAAQ,CAAC,mBAAmB,EAAE,SAAS,aAAa,EAAE,CAAC;CACxD;
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/savings/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAgB,MAAM,gBAAgB,CAAC;AAE/E;;iFAEiF;AACjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;yCACqC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;+DAC2D;IAC3D,QAAQ,CAAC,mBAAmB,EAAE,SAAS,aAAa,EAAE,CAAC;CACxD;AA2CD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAC3E,cAAc,EAAE,MAAM,GACrB,MAAM,CAgBR;AAED;2EAC2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,oFAAoF;IACpF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;qFACiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,GAAG,kBAAkB,CAiC1G;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAgEvF"}
|
package/dist/savings/emit.js
CHANGED
|
@@ -8,9 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.buildRestoreYaml = buildRestoreYaml;
|
|
11
|
+
exports.trialVariantName = trialVariantName;
|
|
12
|
+
exports.buildDeclareTrialChange = buildDeclareTrialChange;
|
|
11
13
|
exports.buildSwapChange = buildSwapChange;
|
|
12
14
|
function candidateLines(candidates) {
|
|
13
|
-
|
|
15
|
+
// Preserve a candidate's `trial: true` (declare-first, spec §4.4) so an
|
|
16
|
+
// emitted candidates block never silently promotes a DECLARED-trial candidate
|
|
17
|
+
// to live. The promote path drops the flag from the WINNER explicitly (it
|
|
18
|
+
// passes a winner entry without `trial`), never here.
|
|
19
|
+
return candidates.flatMap((c) => [
|
|
20
|
+
` - provider: ${c.provider}`,
|
|
21
|
+
` model: ${c.model}`,
|
|
22
|
+
...(c.trial === true ? [" trial: true"] : []),
|
|
23
|
+
]);
|
|
14
24
|
}
|
|
15
25
|
function overrideLines(variant) {
|
|
16
26
|
const lines = [];
|
|
@@ -57,26 +67,88 @@ function buildRestoreYaml(route, incumbent, currentVersion) {
|
|
|
57
67
|
"",
|
|
58
68
|
].join("\n");
|
|
59
69
|
}
|
|
70
|
+
/** A stable, YAML-safe trial variant name for a challenger. */
|
|
71
|
+
function trialVariantName(challenger) {
|
|
72
|
+
return `trial-${challenger.provider}-${challenger.model}`.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Emit the declare-first YAML that adds a `trial` challenger to a route (spec
|
|
76
|
+
* §4.4 step 1): the challenger appended to `candidates` with `trial: true`, a
|
|
77
|
+
* `trial-<provider>-<model>` variant that narrows to it, and `version: n+1`. The
|
|
78
|
+
* challenger is DECLARED but never served live (the loader/resolver enforce
|
|
79
|
+
* that) — so a bake-off can measure it on real traffic before promotion. Pure,
|
|
80
|
+
* no filesystem (emit.test.ts greps for that). Throws when the challenger is
|
|
81
|
+
* already a declared candidate — you don't declare what's already declared (the
|
|
82
|
+
* server maps this to 422).
|
|
83
|
+
*/
|
|
84
|
+
function buildDeclareTrialChange(bundle, challenger) {
|
|
85
|
+
if (bundle.candidates.some((c) => c.provider === challenger.provider && c.model === challenger.model)) {
|
|
86
|
+
throw new Error(`${challenger.provider}/${challenger.model} is already a declared candidate on "${bundle.route}" — ` +
|
|
87
|
+
"nothing to declare (pick a model that is not yet in the route to trial it)");
|
|
88
|
+
}
|
|
89
|
+
const file = `modelrig/routes/${bundle.route}.yaml`;
|
|
90
|
+
const newVersion = bundle.version + 1;
|
|
91
|
+
const variant = trialVariantName(challenger);
|
|
92
|
+
const newCandidates = [...bundle.candidates, { ...challenger, trial: true }];
|
|
93
|
+
const yaml = [
|
|
94
|
+
`# declare a trial of ${challenger.provider}/${challenger.model} on ${bundle.route}`,
|
|
95
|
+
`# a trial candidate is DECLARED but NEVER served live — a bake-off measures it, then Promote makes it live.`,
|
|
96
|
+
`# apply to ${file}:`,
|
|
97
|
+
`# 1) bump the version`,
|
|
98
|
+
`version: ${newVersion}`,
|
|
99
|
+
`# 2) replace your candidates: block with this (the challenger is appended and marked for trial)`,
|
|
100
|
+
"candidates:",
|
|
101
|
+
...candidateLines(newCandidates),
|
|
102
|
+
`# 3) add this entry to your variants: block (create the block if the route has none)`,
|
|
103
|
+
"variants:",
|
|
104
|
+
` - name: ${variant}`,
|
|
105
|
+
" candidates:",
|
|
106
|
+
` - provider: ${challenger.provider}`,
|
|
107
|
+
` model: ${challenger.model}`,
|
|
108
|
+
`# Requires the modelrig SDK release that understands \`trial:\` — an older SDK ignores the flag and`,
|
|
109
|
+
`# would serve the model live. Bake it off with: modelrig bakeoff --from-proposal <id>`,
|
|
110
|
+
"",
|
|
111
|
+
].join("\n");
|
|
112
|
+
return { route: bundle.route, file, challenger, trialVariant: variant, newVersion, yaml };
|
|
113
|
+
}
|
|
60
114
|
function buildSwapChange(bundle, winningVariant) {
|
|
61
115
|
const variant = bundle.variants.find((v) => v.name === winningVariant);
|
|
62
116
|
if (variant === undefined) {
|
|
63
117
|
throw new Error(`route "${bundle.route}" has no variant "${winningVariant}" — cannot emit a swap for it`);
|
|
64
118
|
}
|
|
65
|
-
|
|
119
|
+
// Promote drops `trial: true` from the WINNER and moves it to position 0. The
|
|
120
|
+
// flag is stripped EXPLICITLY here (not merely relied upon being absent from a
|
|
121
|
+
// bare variant candidate) so a hand-authored `trial: true` inside the variant's
|
|
122
|
+
// candidate block can never leave the promoted winner as a trial candidate — a
|
|
123
|
+
// silent no-op promotion where the "promoted" model is then dropped live. Other
|
|
124
|
+
// declared candidates keep their own trial flags (candidateLines preserves them).
|
|
125
|
+
const winners = (variant.candidates ?? bundle.candidates).map((c) => ({
|
|
126
|
+
provider: c.provider,
|
|
127
|
+
model: c.model,
|
|
128
|
+
}));
|
|
66
129
|
if (winners.length === 0) {
|
|
67
130
|
throw new Error(`variant "${winningVariant}" has no candidates to promote`);
|
|
68
131
|
}
|
|
69
132
|
const incumbent = bundle.candidates[0];
|
|
133
|
+
// Trial-winner promotes also remove the now-redundant trial variant (spec §4.4).
|
|
134
|
+
const isTrialPromotion = variant.trialOnly === true;
|
|
70
135
|
const promoted = promotedOrder(bundle.candidates, winners);
|
|
71
136
|
const file = `modelrig/routes/${bundle.route}.yaml`;
|
|
72
137
|
const newVersion = bundle.version + 1;
|
|
138
|
+
const applyComment = isTrialPromotion
|
|
139
|
+
? `# apply to ${file} — replace version + candidates, and REMOVE the trial variant "${winningVariant}" from variants:`
|
|
140
|
+
: `# apply to ${file} — replace the version and candidates fields; nothing else changes`;
|
|
141
|
+
const trialRemovalLine = isTrialPromotion
|
|
142
|
+
? [`# remove this now-promoted trial variant from variants: (its candidate is live at position 0 above):`, `# - name: ${winningVariant}`]
|
|
143
|
+
: [];
|
|
73
144
|
const yaml = [
|
|
74
145
|
`# swap: serve ${bundle.route} on ${winners[0].provider}/${winners[0].model}`,
|
|
75
|
-
|
|
146
|
+
applyComment,
|
|
76
147
|
`version: ${newVersion}`,
|
|
77
148
|
"candidates:",
|
|
78
149
|
...candidateLines(promoted),
|
|
79
150
|
...overrideLines(variant),
|
|
151
|
+
...trialRemovalLine,
|
|
80
152
|
"",
|
|
81
153
|
].join("\n");
|
|
82
154
|
const revertYaml = [
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Re-exported from the SDK entrypoint so `index.ts` stays under the LOC cap and
|
|
4
4
|
* the settings surface has one home.
|
|
5
5
|
*/
|
|
6
|
-
export { createTaskSettingsResolver, effectiveCapture, DEFAULT_TASK_SETTINGS, } from "./task-settings";
|
|
6
|
+
export { createTaskSettingsResolver, effectiveCapture, effectiveScrubMode, DEFAULT_TASK_SETTINGS, } from "./task-settings";
|
|
7
7
|
export type { TaskSettings, TaskSettingsResolver, TaskSettingsFetcher } from "./task-settings";
|
|
8
8
|
export { createApiTaskSettingsFetcher, startApiSettings } from "./api-fetcher";
|
|
9
9
|
export type { ApiTaskSettingsFetcherConfig } from "./api-fetcher";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/settings/index.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* the settings surface has one home.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.startApiSettings = exports.createApiTaskSettingsFetcher = exports.DEFAULT_TASK_SETTINGS = exports.effectiveCapture = exports.createTaskSettingsResolver = void 0;
|
|
8
|
+
exports.startApiSettings = exports.createApiTaskSettingsFetcher = exports.DEFAULT_TASK_SETTINGS = exports.effectiveScrubMode = exports.effectiveCapture = exports.createTaskSettingsResolver = void 0;
|
|
9
9
|
var task_settings_1 = require("./task-settings");
|
|
10
10
|
Object.defineProperty(exports, "createTaskSettingsResolver", { enumerable: true, get: function () { return task_settings_1.createTaskSettingsResolver; } });
|
|
11
11
|
Object.defineProperty(exports, "effectiveCapture", { enumerable: true, get: function () { return task_settings_1.effectiveCapture; } });
|
|
12
|
+
Object.defineProperty(exports, "effectiveScrubMode", { enumerable: true, get: function () { return task_settings_1.effectiveScrubMode; } });
|
|
12
13
|
Object.defineProperty(exports, "DEFAULT_TASK_SETTINGS", { enumerable: true, get: function () { return task_settings_1.DEFAULT_TASK_SETTINGS; } });
|
|
13
14
|
var api_fetcher_1 = require("./api-fetcher");
|
|
14
15
|
Object.defineProperty(exports, "createApiTaskSettingsFetcher", { enumerable: true, get: function () { return api_fetcher_1.createApiTaskSettingsFetcher; } });
|
|
@@ -88,4 +88,14 @@ export declare function createTaskSettingsResolver(config: TaskSettingsResolverC
|
|
|
88
88
|
* because there is no control-plane row to resolve.
|
|
89
89
|
*/
|
|
90
90
|
export declare function effectiveCapture(resolver: TaskSettingsResolver | null, task: string | undefined, yamlCapture: boolean): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* The EFFECTIVE scrub mode for an artifact, given the task's configured mode and
|
|
93
|
+
* the artifact's `data_classification` (screening SC-0 S8-c). A `pii`/`phi`
|
|
94
|
+
* artifact resolves a FLOOR of `redact` — content carrying PII/PHI is never
|
|
95
|
+
* uploaded verbatim just because the task's mode was `off`/`detect`. A stronger
|
|
96
|
+
* configured mode wins: `block` stays `block` (block ≥ redact — it refuses the
|
|
97
|
+
* upload entirely). A non-sensitive (or absent) classification leaves the
|
|
98
|
+
* configured mode untouched. Pure; the single place the floor is spelled.
|
|
99
|
+
*/
|
|
100
|
+
export declare function effectiveScrubMode(configured: ScrubMode, classification: string | null | undefined): ScrubMode;
|
|
91
101
|
//# sourceMappingURL=task-settings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-settings.d.ts","sourceRoot":"","sources":["../../src/settings/task-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"task-settings.d.ts","sourceRoot":"","sources":["../../src/settings/task-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C;0EAC0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED;;;uCAGuC;AACvC,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;2EAG2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC;sFACkF;IAClF,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC;IAChC;oEACgE;IAChE,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9C;;gEAE4D;IAC5D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;2EACuE;IACvE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,4CAA4C;IAC5C,KAAK,IAAI,IAAI,CAAC;IACd,2CAA2C;IAC3C,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC;gFAC4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,KAAK,IAAI,CAAC;CAC3E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,0BAA0B,GACjC,oBAAoB,CA4DtB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EACrC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,OAAO,GACnB,OAAO,CAGT;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,SAAS,EACrB,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACxC,SAAS,CAIX"}
|
|
@@ -27,6 +27,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
27
27
|
exports.DEFAULT_TASK_SETTINGS = void 0;
|
|
28
28
|
exports.createTaskSettingsResolver = createTaskSettingsResolver;
|
|
29
29
|
exports.effectiveCapture = effectiveCapture;
|
|
30
|
+
exports.effectiveScrubMode = effectiveScrubMode;
|
|
31
|
+
const classification_1 = require("../artifacts/classification");
|
|
30
32
|
/** The safe floor for a task the control plane has not answered for yet: capture
|
|
31
33
|
* OFF, artifacts inherit the default, content OFF, scrub the schema default. A
|
|
32
34
|
* run that reaches the cache before the first fetch lands sees this — never a
|
|
@@ -114,3 +116,18 @@ function effectiveCapture(resolver, task, yamlCapture) {
|
|
|
114
116
|
return yamlCapture;
|
|
115
117
|
return resolver.get(task).capture;
|
|
116
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* The EFFECTIVE scrub mode for an artifact, given the task's configured mode and
|
|
121
|
+
* the artifact's `data_classification` (screening SC-0 S8-c). A `pii`/`phi`
|
|
122
|
+
* artifact resolves a FLOOR of `redact` — content carrying PII/PHI is never
|
|
123
|
+
* uploaded verbatim just because the task's mode was `off`/`detect`. A stronger
|
|
124
|
+
* configured mode wins: `block` stays `block` (block ≥ redact — it refuses the
|
|
125
|
+
* upload entirely). A non-sensitive (or absent) classification leaves the
|
|
126
|
+
* configured mode untouched. Pure; the single place the floor is spelled.
|
|
127
|
+
*/
|
|
128
|
+
function effectiveScrubMode(configured, classification) {
|
|
129
|
+
if (classification == null || !classification_1.SENSITIVE_CLASSIFICATIONS.has(classification))
|
|
130
|
+
return configured;
|
|
131
|
+
// Sensitive tier: never below `redact`. `block` is stronger and stays.
|
|
132
|
+
return configured === "block" ? "block" : "redact";
|
|
133
|
+
}
|