@prismatic-io/lux 0.0.2-preview.32 → 0.0.2-preview.33
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/lib/cli/output-schemas.d.ts +6 -2
- package/lib/cli/output-schemas.d.ts.map +1 -1
- package/lib/cli/output-schemas.js +5 -2
- package/lib/cli/output-schemas.js.map +1 -1
- package/lib/cli/render/format.d.ts.map +1 -1
- package/lib/cli/render/format.js +1 -1
- package/lib/cli/render/format.js.map +1 -1
- package/lib/cli/render/reporter.js +2 -2
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/core/experiment.d.ts +1 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +5 -1
- package/lib/core/experiment.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +4 -3
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +13 -6
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +1 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-effects.d.ts.map +1 -1
- package/lib/orchestrator/experiment-effects.js +4 -2
- package/lib/orchestrator/experiment-effects.js.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +15 -2
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +1 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +2 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.js +99 -43
- package/lib/orchestrator/experiment-selection.js.map +1 -1
- package/lib/orchestrator/grading.d.ts +1 -0
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +7 -0
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +3 -5
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/paired-effects.d.ts.map +1 -1
- package/lib/orchestrator/paired-effects.js +7 -1
- package/lib/orchestrator/paired-effects.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.js +18 -6
- package/lib/orchestrator/suite-compare.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +2 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +6 -2
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/server.d.ts.map +1 -1
- package/lib/previewer/server.js +2 -1
- package/lib/previewer/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/cli/output-schemas.ts +5 -2
- package/src/cli/render/format.ts +1 -1
- package/src/cli/render/reporter.ts +2 -2
- package/src/core/experiment.ts +7 -1
- package/src/orchestrator/compare.ts +23 -9
- package/src/orchestrator/experiment-effects.ts +4 -2
- package/src/orchestrator/experiment-evaluation.ts +18 -2
- package/src/orchestrator/experiment-selection.ts +101 -18
- package/src/orchestrator/grading.ts +11 -0
- package/src/orchestrator/list-runs.ts +5 -6
- package/src/orchestrator/paired-effects.ts +6 -1
- package/src/orchestrator/suite-compare.ts +19 -5
- package/src/orchestrator/suite-summary.ts +7 -2
- package/src/previewer/server.ts +2 -0
- package/viewer/app.css +32 -0
- package/viewer/charts.js +16 -4
- package/viewer/detail.js +26 -8
- package/viewer/format.js +2 -2
|
@@ -74,10 +74,19 @@ const pairedMetricDeltas = (
|
|
|
74
74
|
const a = before[index];
|
|
75
75
|
const b = after[index];
|
|
76
76
|
if (!a || !b) continue;
|
|
77
|
-
const
|
|
77
|
+
const am: Record<string, number> = {
|
|
78
|
+
...a.metrics,
|
|
79
|
+
...(a.score !== undefined ? { score: a.score } : {}),
|
|
80
|
+
};
|
|
81
|
+
const bm: Record<string, number> = {
|
|
82
|
+
...b.metrics,
|
|
83
|
+
...(b.score !== undefined ? { score: b.score } : {}),
|
|
84
|
+
};
|
|
85
|
+
const names = new Set([...Object.keys(am), ...Object.keys(bm)]);
|
|
78
86
|
for (const name of names) {
|
|
79
|
-
|
|
80
|
-
const
|
|
87
|
+
if (name === "score" && (!a.scoreBasis || a.scoreBasis !== b.scoreBasis)) continue;
|
|
88
|
+
const av = am[name];
|
|
89
|
+
const bv = bm[name];
|
|
81
90
|
if (av === undefined || bv === undefined) continue;
|
|
82
91
|
const values = deltas.get(name) ?? [];
|
|
83
92
|
values.push(bv - av);
|
|
@@ -196,9 +205,14 @@ export const formatSuiteComparisonText = (comparison: SuiteComparison): string =
|
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
for (const entry of comparison.cases) {
|
|
199
|
-
if (
|
|
208
|
+
if (entry.regressed) {
|
|
209
|
+
lines.push(
|
|
210
|
+
` ↓ ${suiteGroupKey(entry)}: ${entry.before ? percent(entry.before.passRate) : "—"} → ${entry.after ? percent(entry.after.passRate) : "missing"}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const score = entry.pairedMetricDeltas.score;
|
|
200
214
|
lines.push(
|
|
201
|
-
`
|
|
215
|
+
` ${suiteGroupKey(entry)} score change: ${score ? `${score.mean >= 0 ? "+" : ""}${score.mean} (${score.n} paired)` : "unavailable"}`,
|
|
202
216
|
);
|
|
203
217
|
}
|
|
204
218
|
return lines.join("\n");
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { hasAgentUsage } from "../core/usage.js";
|
|
12
12
|
import { runComparisonIdentity } from "./comparison-identity.js";
|
|
13
13
|
import { safeReadJson } from "./fs-read.js";
|
|
14
|
+
import { gradingScoreBasis } from "./grading.js";
|
|
14
15
|
import type { SuiteCaseResult } from "./orchestrator.js";
|
|
15
16
|
import type { SuiteRun } from "./suite-runs.js";
|
|
16
17
|
|
|
@@ -65,6 +66,7 @@ export const SuiteSampleSchema = z.object({
|
|
|
65
66
|
passed: z.boolean().nullable(),
|
|
66
67
|
identityHash: z.string().nullable(),
|
|
67
68
|
score: z.exactOptional(z.number()),
|
|
69
|
+
scoreBasis: z.exactOptional(z.string().nullable()),
|
|
68
70
|
metrics: z.record(z.string(), z.number()),
|
|
69
71
|
});
|
|
70
72
|
export type SuiteSample = z.infer<typeof SuiteSampleSchema>;
|
|
@@ -354,7 +356,10 @@ export const buildSuiteSummary = (
|
|
|
354
356
|
samples: entries.map((entry, index) => {
|
|
355
357
|
const samples: MetricSamples = new Map();
|
|
356
358
|
const result = entry.result?.result;
|
|
357
|
-
if (result)
|
|
359
|
+
if (result) {
|
|
360
|
+
add(samples, "score", result.grading.caseScore ?? undefined);
|
|
361
|
+
collectMetadata(samples, result.metadata);
|
|
362
|
+
}
|
|
358
363
|
return {
|
|
359
364
|
index,
|
|
360
365
|
caseId: entry.item.caseId,
|
|
@@ -364,7 +369,7 @@ export const buildSuiteSummary = (
|
|
|
364
369
|
passed: entry.result?.casePassed ?? null,
|
|
365
370
|
identityHash: result ? runComparisonIdentity(result.metadata) : null,
|
|
366
371
|
...(result?.grading.caseScore !== null && result?.grading.caseScore !== undefined
|
|
367
|
-
? { score: result.grading.caseScore }
|
|
372
|
+
? { score: result.grading.caseScore, scoreBasis: gradingScoreBasis(result.grading) }
|
|
368
373
|
: {}),
|
|
369
374
|
metrics: Object.fromEntries(
|
|
370
375
|
[...samples.entries()].map(([name, sample]) => [name, sample.values[0] ?? 0]),
|
package/src/previewer/server.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type DriverMetricDescriptor, type DriverMetrics, exec } from "../core/i
|
|
|
7
7
|
import {
|
|
8
8
|
compareRuns,
|
|
9
9
|
compareSuiteRuns,
|
|
10
|
+
hasRegression,
|
|
10
11
|
type ListedRun,
|
|
11
12
|
listExperimentRuns,
|
|
12
13
|
listRuns,
|
|
@@ -424,6 +425,7 @@ const handleRunComparison = async ({
|
|
|
424
425
|
]);
|
|
425
426
|
sendJson(request, response, 200, {
|
|
426
427
|
kind: "run",
|
|
428
|
+
regressed: hasRegression(comparison),
|
|
427
429
|
comparison: {
|
|
428
430
|
...comparison,
|
|
429
431
|
pathA: displayPath(runsRoot, before.runDir),
|
package/viewer/app.css
CHANGED
|
@@ -1256,6 +1256,37 @@ h1 {
|
|
|
1256
1256
|
color: var(--muted);
|
|
1257
1257
|
}
|
|
1258
1258
|
|
|
1259
|
+
.score-bar {
|
|
1260
|
+
position: relative;
|
|
1261
|
+
display: block;
|
|
1262
|
+
width: 100%;
|
|
1263
|
+
min-width: 92px;
|
|
1264
|
+
height: 9px;
|
|
1265
|
+
background: #282d30;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.score-fill {
|
|
1269
|
+
display: block;
|
|
1270
|
+
height: 100%;
|
|
1271
|
+
background: var(--muted);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.score-fill.tone-positive,
|
|
1275
|
+
.score-fill.tone-after {
|
|
1276
|
+
background: var(--lime);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.score-fill.tone-negative {
|
|
1280
|
+
background: var(--coral);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.score-bar > .has-threshold {
|
|
1284
|
+
position: absolute;
|
|
1285
|
+
top: -2px;
|
|
1286
|
+
height: 13px;
|
|
1287
|
+
border-left: 1px solid currentColor;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1259
1290
|
.quantized-bar {
|
|
1260
1291
|
display: grid;
|
|
1261
1292
|
grid-template-columns: repeat(20, minmax(2px, 1fr));
|
|
@@ -1405,6 +1436,7 @@ h1 {
|
|
|
1405
1436
|
text-transform: uppercase;
|
|
1406
1437
|
}
|
|
1407
1438
|
|
|
1439
|
+
.assertion-content > .score-bar,
|
|
1408
1440
|
.assertion-content > .quantized-bar {
|
|
1409
1441
|
max-width: 220px;
|
|
1410
1442
|
margin-top: 8px;
|
package/viewer/charts.js
CHANGED
|
@@ -51,28 +51,40 @@ const distributionBar = (segments, label) => {
|
|
|
51
51
|
</div>`;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
export const scoreBar = (value, tone, label, threshold = null) => {
|
|
55
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
56
|
+
return `<span class="score-bar unavailable" role="img" aria-label="${escapeHtml(label)}"></span>`;
|
|
57
|
+
}
|
|
58
|
+
return `<span class="score-bar" role="img" aria-label="${escapeHtml(label)}">
|
|
59
|
+
<i class="score-fill ${escapeHtml(tone)}" style="width: ${value * 100}%"></i>
|
|
60
|
+
${typeof threshold === "number" ? `<i class="has-threshold" style="left: ${threshold * 100}%"></i>` : ""}
|
|
61
|
+
</span>`;
|
|
62
|
+
};
|
|
63
|
+
|
|
54
64
|
export const comparisonBars = ({
|
|
55
65
|
title,
|
|
56
66
|
before,
|
|
57
67
|
after,
|
|
58
68
|
maximum,
|
|
69
|
+
continuous = false,
|
|
59
70
|
beforeText,
|
|
60
71
|
afterText,
|
|
61
72
|
deltaText,
|
|
62
73
|
deltaTone,
|
|
63
74
|
}) => {
|
|
75
|
+
const bar = continuous ? scoreBar : quantizedBar;
|
|
64
76
|
const scale = Math.max(maximum ?? 0, before ?? 0, after ?? 0, 1);
|
|
65
77
|
return `<figure class="comparison-bars">
|
|
66
78
|
<figcaption><span>${escapeHtml(title)}</span><b class="${escapeHtml(
|
|
67
79
|
deltaTone,
|
|
68
80
|
)}">${escapeHtml(deltaText)}</b></figcaption>
|
|
69
|
-
<div class="bar-lane"><strong>A</strong>${
|
|
70
|
-
|
|
81
|
+
<div class="bar-lane"><strong>A</strong>${bar(
|
|
82
|
+
before === null || before === undefined ? null : before / scale,
|
|
71
83
|
"tone-before",
|
|
72
84
|
`${title} baseline ${beforeText}`,
|
|
73
85
|
)}<span>${escapeHtml(beforeText)}</span></div>
|
|
74
|
-
<div class="bar-lane"><strong>B</strong>${
|
|
75
|
-
|
|
86
|
+
<div class="bar-lane"><strong>B</strong>${bar(
|
|
87
|
+
after === null || after === undefined ? null : after / scale,
|
|
76
88
|
"tone-after",
|
|
77
89
|
`${title} candidate ${afterText}`,
|
|
78
90
|
)}<span>${escapeHtml(afterText)}</span></div>
|
package/viewer/detail.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
const assertionScore = (assertion) =>
|
|
2
|
+
assertion.errorMessage !== undefined || assertion.abstention !== undefined
|
|
3
|
+
? null
|
|
4
|
+
: assertion.score;
|
|
5
|
+
|
|
1
6
|
const assertionPresentation = (assertion) => {
|
|
7
|
+
if (assertionScore(assertion) === null) return { className: " unavailable", mark: "?", tone: "" };
|
|
2
8
|
if (assertion.passed === null) return { className: " informational", mark: "ℹ", tone: "" };
|
|
3
9
|
if (assertion.passed) return { className: " passed", mark: "✓", tone: "tone-positive" };
|
|
4
10
|
return { className: "", mark: "×", tone: "tone-negative" };
|
|
5
11
|
};
|
|
6
12
|
|
|
7
|
-
import { comparisonBars, compositionFigure, quantizedBar } from "./charts.js";
|
|
13
|
+
import { comparisonBars, compositionFigure, quantizedBar, scoreBar } from "./charts.js";
|
|
8
14
|
import {
|
|
9
15
|
badge,
|
|
10
16
|
compactNumber,
|
|
@@ -104,6 +110,7 @@ const assertionMeta = (assertion) =>
|
|
|
104
110
|
const assertionDiagnostics = (assertion) => {
|
|
105
111
|
const rows = [
|
|
106
112
|
assertion.comment ? `<p class="assertion-comment">${escapeHtml(assertion.comment)}</p>` : "",
|
|
113
|
+
assertion.abstention ? `<p class="assertion-comment">Insufficient evidence</p>` : "",
|
|
107
114
|
assertion.errorMessage
|
|
108
115
|
? `<p class="diagnostic-error">${escapeHtml(assertion.errorMessage)}</p>`
|
|
109
116
|
: "",
|
|
@@ -118,6 +125,7 @@ const assertionDiagnostics = (assertion) => {
|
|
|
118
125
|
|
|
119
126
|
const inferredMetricUnit = (name, unit) => {
|
|
120
127
|
if (unit) return unit;
|
|
128
|
+
if (name === "score") return "score";
|
|
121
129
|
if (name.endsWith("Ms")) return "milliseconds";
|
|
122
130
|
if (name.endsWith(".bytes")) return "bytes";
|
|
123
131
|
if (name.endsWith(".usd")) return "usd";
|
|
@@ -323,18 +331,18 @@ export const renderRunDetail = (item, detail) => {
|
|
|
323
331
|
<span class="assertion-content">
|
|
324
332
|
<strong>${escapeHtml(assertion.name)}</strong>
|
|
325
333
|
<small>${escapeHtml(assertionMeta(assertion))}</small>
|
|
326
|
-
${
|
|
327
|
-
assertion
|
|
334
|
+
${scoreBar(
|
|
335
|
+
assertionScore(assertion),
|
|
328
336
|
assertionPresentation(assertion).tone,
|
|
329
337
|
`${assertion.name} scored ${percentage(
|
|
330
|
-
assertion
|
|
338
|
+
assertionScore(assertion),
|
|
331
339
|
)}${assertion.passed === null ? " (informational)" : `; threshold ${percentage(assertion.threshold)}`}`,
|
|
332
340
|
assertion.passed === null ? null : assertion.threshold,
|
|
333
341
|
)}
|
|
334
342
|
${assertionDiagnostics(assertion)}
|
|
335
343
|
</span>
|
|
336
344
|
<span class="assertion-score">${escapeHtml(
|
|
337
|
-
percentage(assertion
|
|
345
|
+
percentage(assertionScore(assertion)),
|
|
338
346
|
)}</span>
|
|
339
347
|
</article>`,
|
|
340
348
|
)
|
|
@@ -899,18 +907,20 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
899
907
|
runDelta?.agentUsageAvailable?.b === false ? null : totalTokens(runDelta?.usage?.b?.agent);
|
|
900
908
|
const tokenDelta = tokenA === null || tokenB === null ? null : tokenB - tokenA;
|
|
901
909
|
const scoreDelta =
|
|
910
|
+
comparison.grading.caseScoreComparable !== false &&
|
|
902
911
|
typeof comparison.grading.caseScoreA === "number" &&
|
|
903
912
|
typeof comparison.grading.caseScoreB === "number"
|
|
904
913
|
? comparison.grading.caseScoreB - comparison.grading.caseScoreA
|
|
905
914
|
: null;
|
|
915
|
+
let comparisonStatus = "ungraded";
|
|
916
|
+
if (payload.regressed === true) comparisonStatus = "failed";
|
|
917
|
+
if (payload.regressed === false) comparisonStatus = "passed";
|
|
906
918
|
const driverMetrics = driverMetricComparisons(runDelta);
|
|
907
919
|
const subagentsA = subagentTotals(runDelta?.agentActivity?.a);
|
|
908
920
|
const subagentsB = subagentTotals(runDelta?.agentActivity?.b);
|
|
909
921
|
return `
|
|
910
922
|
<header class="detail-header comparison-header">
|
|
911
|
-
<div class="detail-kicker">${badge(
|
|
912
|
-
comparison.grading.regressions ? "failed" : "passed",
|
|
913
|
-
)}<span>run comparison</span></div>
|
|
923
|
+
<div class="detail-kicker">${badge(comparisonStatus)}<span>run comparison</span></div>
|
|
914
924
|
<div class="detail-title-row">
|
|
915
925
|
<div><h2>${escapeHtml(labels.before)} → ${escapeHtml(
|
|
916
926
|
labels.after,
|
|
@@ -926,6 +936,7 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
926
936
|
)}. Treat deltas as potentially incomparable.</div>`
|
|
927
937
|
: '<div class="integrity-banner">✓ Case, evaluator, runtime, and driver identities are comparable</div>'
|
|
928
938
|
}
|
|
939
|
+
${comparison.grading.caseScoreComparable === false ? '<div class="attention-banner">Score change unavailable: scored assertions or weights differ, or evidence is missing.</div>' : ""}
|
|
929
940
|
<dl class="comparison-stats">
|
|
930
941
|
<div><dt>Case score</dt><dd>${escapeHtml(
|
|
931
942
|
`${percentage(comparison.grading.caseScoreA)} → ${percentage(
|
|
@@ -956,6 +967,7 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
956
967
|
<div class="comparison-plot-grid">
|
|
957
968
|
${comparisonBars({
|
|
958
969
|
title: "Case score",
|
|
970
|
+
continuous: true,
|
|
959
971
|
before: comparison.grading.caseScoreA,
|
|
960
972
|
after: comparison.grading.caseScoreB,
|
|
961
973
|
maximum: 1,
|
|
@@ -1051,6 +1063,12 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
1051
1063
|
value: comparison.grading.deltas.filter((delta) => delta.status === "removed")
|
|
1052
1064
|
.length,
|
|
1053
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
key: "unavailable",
|
|
1068
|
+
label: "Unavailable",
|
|
1069
|
+
value: comparison.grading.deltas.filter((delta) => delta.status === "unavailable")
|
|
1070
|
+
.length,
|
|
1071
|
+
},
|
|
1054
1072
|
{
|
|
1055
1073
|
key: "unchanged",
|
|
1056
1074
|
label: "Unchanged",
|
package/viewer/format.js
CHANGED
|
@@ -6,11 +6,11 @@ export const escapeHtml = (value) =>
|
|
|
6
6
|
);
|
|
7
7
|
|
|
8
8
|
export const percentage = (value) =>
|
|
9
|
-
typeof value === "number" ? `${
|
|
9
|
+
typeof value === "number" ? `${Number((value * 100).toPrecision(15))}%` : "—";
|
|
10
10
|
|
|
11
11
|
export const signedPercentage = (value) => {
|
|
12
12
|
if (typeof value !== "number") return "—";
|
|
13
|
-
const rounded =
|
|
13
|
+
const rounded = Number((value * 100).toPrecision(15));
|
|
14
14
|
return `${rounded > 0 ? "+" : ""}${rounded}%`;
|
|
15
15
|
};
|
|
16
16
|
|