@valerypopoff/rivet2-evaluations 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/bundle.cjs +684 -312
- package/dist/cjs/bundle.cjs.map +4 -4
- package/dist/esm/canonical.js +37 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/library.js +84 -0
- package/dist/esm/librarySync.js +118 -0
- package/dist/esm/normalization.js +101 -1
- package/dist/esm/runner.js +201 -129
- package/dist/esm/serialization.js +3 -0
- package/dist/esm/store.js +25 -5
- package/dist/types/canonical.d.ts +3 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/library.d.ts +8 -0
- package/dist/types/librarySync.d.ts +17 -0
- package/dist/types/normalization.d.ts +15 -0
- package/dist/types/runner.d.ts +58 -2
- package/dist/types/store.d.ts +10 -3
- package/dist/types/types.d.ts +188 -2
- package/package.json +2 -2
package/dist/cjs/bundle.cjs
CHANGED
|
@@ -28,30 +28,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
|
|
30
30
|
// src/index.ts
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
33
|
EvaluationGraphExecutionError: () => EvaluationGraphExecutionError,
|
|
34
34
|
InMemoryEvaluationRunStore: () => InMemoryEvaluationRunStore,
|
|
35
35
|
LEGACY_EVALUATOR_INPUT_IDS: () => LEGACY_EVALUATOR_INPUT_IDS,
|
|
36
|
+
applyEvaluationLibraryMutation: () => applyEvaluationLibraryMutation,
|
|
36
37
|
areEvaluationDataTypesCompatible: () => areEvaluationDataTypesCompatible,
|
|
37
38
|
assertEvaluationDatasetSnapshot: () => assertEvaluationDatasetSnapshot,
|
|
38
39
|
assertEvaluationDatasetValuesMatchDeclaredTypes: () => assertEvaluationDatasetValuesMatchDeclaredTypes,
|
|
40
|
+
assertEvaluationRecordingArtifact: () => assertEvaluationRecordingArtifact,
|
|
39
41
|
assertPortableJson: () => assertPortableJson,
|
|
40
42
|
canonicalStringify: () => canonicalStringify,
|
|
41
43
|
canonicalizePortableJson: () => canonicalizePortableJson,
|
|
42
44
|
clonePortableJson: () => clonePortableJson,
|
|
45
|
+
createEmptyEvaluationLibrary: () => createEmptyEvaluationLibrary,
|
|
43
46
|
createEmptyEvaluationProjectData: () => createEmptyEvaluationProjectData,
|
|
44
47
|
createEmptyMetrics: () => createEmptyMetrics,
|
|
45
48
|
createEvaluationBaselineSnapshot: () => createEvaluationBaselineSnapshot,
|
|
49
|
+
createEvaluationRunPlan: () => createEvaluationRunPlan,
|
|
50
|
+
createEvaluationRunShell: () => createEvaluationRunShell,
|
|
46
51
|
deserializeEvaluationDatasetJson: () => deserializeEvaluationDatasetJson,
|
|
47
52
|
deserializeEvaluationProjectData: () => deserializeEvaluationProjectData,
|
|
48
53
|
deserializeEvaluationProjectDataFromString: () => deserializeEvaluationProjectDataFromString,
|
|
49
54
|
deserializeEvaluationSuiteBundleJson: () => deserializeEvaluationSuiteBundleJson,
|
|
55
|
+
diffEvaluationLibraryMutation: () => diffEvaluationLibraryMutation,
|
|
50
56
|
evaluateAssertion: () => evaluateAssertion,
|
|
51
57
|
evaluateThresholdResults: () => evaluateThresholdResults,
|
|
58
|
+
evaluationLibraryValueEquals: () => evaluationLibraryValueEquals,
|
|
52
59
|
finalizeEvaluationRecordingRetention: () => finalizeEvaluationRecordingRetention,
|
|
60
|
+
finalizeEvaluationRun: () => finalizeEvaluationRun,
|
|
53
61
|
fingerprint: () => fingerprint,
|
|
54
62
|
fingerprintEvaluationDataset: () => fingerprintEvaluationDataset,
|
|
63
|
+
getEvaluationLibraryResource: () => getEvaluationLibraryResource,
|
|
64
|
+
getEvaluationSuiteBundle: () => getEvaluationSuiteBundle,
|
|
55
65
|
getEvaluationSuiteMode: () => getEvaluationSuiteMode,
|
|
56
66
|
getEvaluationTopLevelOutputId: () => getEvaluationTopLevelOutputId,
|
|
57
67
|
hasAuthoritativeEvaluationCriteria: () => hasAuthoritativeEvaluationCriteria,
|
|
@@ -61,11 +71,15 @@ __export(index_exports, {
|
|
|
61
71
|
localizeEvaluationDataset: () => localizeEvaluationDataset,
|
|
62
72
|
normalizeEvaluationAggregate: () => normalizeEvaluationAggregate,
|
|
63
73
|
normalizeEvaluationBaselineSnapshot: () => normalizeEvaluationBaselineSnapshot,
|
|
74
|
+
normalizeEvaluationLibrary: () => normalizeEvaluationLibrary,
|
|
64
75
|
normalizeEvaluationRun: () => normalizeEvaluationRun,
|
|
65
76
|
normalizeEvaluationTrial: () => normalizeEvaluationTrial,
|
|
66
77
|
portableEvaluationDataTypes: () => portableEvaluationDataTypes,
|
|
78
|
+
preserveEvaluationRunName: () => preserveEvaluationRunName,
|
|
79
|
+
reconcileEvaluationRunSnapshots: () => reconcileEvaluationRunSnapshots,
|
|
67
80
|
runEvaluationCases: () => runEvaluationCases,
|
|
68
81
|
runEvaluationSuite: () => runEvaluationSuite,
|
|
82
|
+
runEvaluationTrial: () => runEvaluationTrial,
|
|
69
83
|
runEvaluationWorkPool: () => runEvaluationWorkPool,
|
|
70
84
|
serializeEvaluationDatasetJson: () => serializeEvaluationDatasetJson,
|
|
71
85
|
serializeEvaluationProjectData: () => serializeEvaluationProjectData,
|
|
@@ -79,7 +93,7 @@ __export(index_exports, {
|
|
|
79
93
|
validateEvaluationDataset: () => validateEvaluationDataset,
|
|
80
94
|
validateEvaluationDatasetForTransfer: () => validateEvaluationDatasetForTransfer
|
|
81
95
|
});
|
|
82
|
-
module.exports = __toCommonJS(
|
|
96
|
+
module.exports = __toCommonJS(src_exports);
|
|
83
97
|
|
|
84
98
|
// src/types.ts
|
|
85
99
|
var EvaluationGraphExecutionError = class extends Error {
|
|
@@ -178,13 +192,29 @@ function fingerprintEvaluationDataset(value) {
|
|
|
178
192
|
return fingerprint({ datasetId: value.id, fields: value.fields, cases: value.cases });
|
|
179
193
|
}
|
|
180
194
|
function assertEvaluationDatasetSnapshot(snapshot) {
|
|
181
|
-
if (snapshot.
|
|
195
|
+
if (typeof snapshot.projectId !== "string" || snapshot.projectId.length === 0 || typeof snapshot.fingerprint !== "string" || snapshot.fingerprint.length === 0 || typeof snapshot.createdAt !== "string" || !Number.isFinite(Date.parse(snapshot.createdAt))) {
|
|
196
|
+
throw new Error("An evaluation dataset snapshot has invalid identity or creation time.");
|
|
197
|
+
}
|
|
198
|
+
if (snapshot.dataset.projectId !== snapshot.projectId) {
|
|
182
199
|
throw new Error("An evaluation dataset snapshot must belong to its project.");
|
|
183
200
|
}
|
|
184
201
|
if (fingerprintEvaluationDataset(snapshot.dataset) !== snapshot.fingerprint) {
|
|
185
202
|
throw new Error("An evaluation dataset snapshot fingerprint must match its dataset ID, fields, and cases.");
|
|
186
203
|
}
|
|
187
204
|
}
|
|
205
|
+
function assertEvaluationRecordingArtifact(value) {
|
|
206
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
207
|
+
throw new Error("An evaluation recording artifact must be an object.");
|
|
208
|
+
}
|
|
209
|
+
const artifact = value;
|
|
210
|
+
if (typeof artifact.projectId !== "string" || artifact.projectId.length === 0 || typeof artifact.runId !== "string" || artifact.runId.length === 0 || typeof artifact.trialId !== "string" || artifact.trialId.length === 0 || typeof artifact.serialized !== "string" || typeof artifact.createdAt !== "string" || !Number.isFinite(Date.parse(artifact.createdAt))) {
|
|
211
|
+
throw new Error("An evaluation recording artifact has invalid identity, payload, or creation time.");
|
|
212
|
+
}
|
|
213
|
+
const reference = artifact.reference;
|
|
214
|
+
if (typeof reference !== "object" || reference === null || typeof reference.id !== "string" || reference.id.length === 0 || !["temporary", "failure", "baseline", "retained"].includes(reference.retention ?? "") || reference.expiresAt !== void 0 && (typeof reference.expiresAt !== "string" || !Number.isFinite(Date.parse(reference.expiresAt)))) {
|
|
215
|
+
throw new Error("An evaluation recording artifact has an invalid retention reference.");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
188
218
|
|
|
189
219
|
// src/assertions.ts
|
|
190
220
|
function readQuotedPathKey(source, start) {
|
|
@@ -680,6 +710,12 @@ function isRunExecutionStatus(value) {
|
|
|
680
710
|
function isTerminalRunExecutionStatus(status) {
|
|
681
711
|
return status === "completed" || status === "canceled" || status === "error";
|
|
682
712
|
}
|
|
713
|
+
function reconcileRecordingReference(primary, secondary) {
|
|
714
|
+
if (!primary) return secondary;
|
|
715
|
+
if (!secondary) return primary;
|
|
716
|
+
const rank = (retention) => retention === "temporary" ? 0 : retention === "failure" ? 1 : 2;
|
|
717
|
+
return rank(secondary.retention) > rank(primary.retention) ? secondary : primary;
|
|
718
|
+
}
|
|
683
719
|
function shouldReplaceEvaluationRun(existing, incoming) {
|
|
684
720
|
if (!existing) return true;
|
|
685
721
|
const existingRevision = existing.revision ?? 0;
|
|
@@ -687,6 +723,64 @@ function shouldReplaceEvaluationRun(existing, incoming) {
|
|
|
687
723
|
if (incomingRevision !== existingRevision) return incomingRevision > existingRevision;
|
|
688
724
|
return !isTerminalRunExecutionStatus(existing.executionStatus) || isTerminalRunExecutionStatus(incoming.executionStatus);
|
|
689
725
|
}
|
|
726
|
+
function preserveEvaluationRunName(existing, incoming) {
|
|
727
|
+
return (existing == null ? void 0 : existing.name) !== void 0 && incoming.name === void 0 ? { ...incoming, name: existing.name } : incoming;
|
|
728
|
+
}
|
|
729
|
+
function reconcileEvaluationRunSnapshots(existing, incoming) {
|
|
730
|
+
if (!existing) return incoming;
|
|
731
|
+
const existingRevision = existing.revision ?? 0;
|
|
732
|
+
const incomingRevision = incoming.revision ?? 0;
|
|
733
|
+
if (incomingRevision < existingRevision) return existing;
|
|
734
|
+
if (incomingRevision > existingRevision) return preserveEvaluationRunName(existing, incoming);
|
|
735
|
+
if (!shouldReplaceEvaluationRun(existing, incoming)) return existing;
|
|
736
|
+
const existingTerminal = isTerminalRunExecutionStatus(existing.executionStatus);
|
|
737
|
+
const incomingTerminal = isTerminalRunExecutionStatus(incoming.executionStatus);
|
|
738
|
+
const existingTrials = existing.trials ?? [];
|
|
739
|
+
const incomingTrials = incoming.trials ?? [];
|
|
740
|
+
const incomingIsPrimary = incomingTerminal !== existingTerminal ? incomingTerminal : incomingTrials.length >= existingTrials.length;
|
|
741
|
+
const primary = preserveEvaluationRunName(existing, incomingIsPrimary ? incoming : existing);
|
|
742
|
+
const secondary = incomingIsPrimary ? existing : incoming;
|
|
743
|
+
const primaryTrials = primary.trials ?? [];
|
|
744
|
+
const secondaryTrialList = secondary.trials ?? [];
|
|
745
|
+
const name = existing.name ?? incoming.name;
|
|
746
|
+
const primaryTrialIds = new Set(primaryTrials.map((trial) => trial.id));
|
|
747
|
+
const secondaryTrials = new Map(secondaryTrialList.map((trial) => [trial.id, trial]));
|
|
748
|
+
return {
|
|
749
|
+
...primary,
|
|
750
|
+
trials: [
|
|
751
|
+
...primaryTrials.map((trial) => {
|
|
752
|
+
const other = secondaryTrials.get(trial.id);
|
|
753
|
+
if (!other) return trial;
|
|
754
|
+
const secondaryObservations = new Map(
|
|
755
|
+
(other.observations ?? []).map((observation) => [observation.id, observation])
|
|
756
|
+
);
|
|
757
|
+
const primaryObservationIds = new Set((trial.observations ?? []).map((observation) => observation.id));
|
|
758
|
+
const recording = reconcileRecordingReference(trial.recording, other.recording);
|
|
759
|
+
return {
|
|
760
|
+
...trial,
|
|
761
|
+
...recording === void 0 ? {} : { recording },
|
|
762
|
+
observations: [
|
|
763
|
+
...(trial.observations ?? []).map((observation) => {
|
|
764
|
+
const otherObservation = secondaryObservations.get(observation.id);
|
|
765
|
+
const observationRecording = reconcileRecordingReference(
|
|
766
|
+
observation.recording,
|
|
767
|
+
otherObservation == null ? void 0 : otherObservation.recording
|
|
768
|
+
);
|
|
769
|
+
return observationRecording === observation.recording ? observation : {
|
|
770
|
+
...observation,
|
|
771
|
+
...observationRecording === void 0 ? {} : { recording: observationRecording }
|
|
772
|
+
};
|
|
773
|
+
}),
|
|
774
|
+
...(other.observations ?? []).filter((observation) => !primaryObservationIds.has(observation.id))
|
|
775
|
+
]
|
|
776
|
+
};
|
|
777
|
+
}),
|
|
778
|
+
...secondaryTrialList.filter((trial) => !primaryTrialIds.has(trial.id))
|
|
779
|
+
],
|
|
780
|
+
...name === void 0 ? {} : { name },
|
|
781
|
+
warnings: [.../* @__PURE__ */ new Set([...primary.warnings ?? [], ...secondary.warnings ?? []])]
|
|
782
|
+
};
|
|
783
|
+
}
|
|
690
784
|
function isPurpose(value) {
|
|
691
785
|
return value === "evaluation" || value === "execution-benchmark";
|
|
692
786
|
}
|
|
@@ -797,6 +891,11 @@ function normalizeEvaluationAggregate(value, trials) {
|
|
|
797
891
|
if (!isRecord(value)) throw new Error("Evaluation aggregate must be an object.");
|
|
798
892
|
const normalized = structuredClone(value);
|
|
799
893
|
if (!isNormalizedScore(normalized.meanScore)) delete normalized.meanScore;
|
|
894
|
+
if (!isNormalizedScore(normalized.medianScore)) delete normalized.medianScore;
|
|
895
|
+
if (!isNormalizedScore(normalized.p95Score)) delete normalized.p95Score;
|
|
896
|
+
if (typeof normalized.medianLatencyMs !== "number" || !Number.isFinite(normalized.medianLatencyMs) || normalized.medianLatencyMs < 0) {
|
|
897
|
+
delete normalized.medianLatencyMs;
|
|
898
|
+
}
|
|
800
899
|
if (trials) {
|
|
801
900
|
const completed = trials.filter((trial) => trial.executionStatus === "completed");
|
|
802
901
|
const evaluated2 = completed.filter((trial) => trial.qualityStatus === "passed" || trial.qualityStatus === "failed");
|
|
@@ -899,6 +998,10 @@ function normalizeEvaluationRun(value) {
|
|
|
899
998
|
} : value.provenance;
|
|
900
999
|
const normalized = structuredClone(value);
|
|
901
1000
|
delete normalized.verdict;
|
|
1001
|
+
const name = typeof value.name === "string" && value.name.trim().length > 0 ? value.name.trim() : void 0;
|
|
1002
|
+
delete normalized.name;
|
|
1003
|
+
const requestedTrialCount = typeof value.requestedTrialCount === "number" && Number.isSafeInteger(value.requestedTrialCount) && value.requestedTrialCount >= 0 ? value.requestedTrialCount : void 0;
|
|
1004
|
+
delete normalized.requestedTrialCount;
|
|
902
1005
|
const aggregate2 = value.aggregate === void 0 ? void 0 : normalizeEvaluationAggregate(value.aggregate, trials);
|
|
903
1006
|
return {
|
|
904
1007
|
...normalized,
|
|
@@ -918,6 +1021,8 @@ function normalizeEvaluationRun(value) {
|
|
|
918
1021
|
provenance,
|
|
919
1022
|
trials,
|
|
920
1023
|
thresholdResults: Array.isArray(value.thresholdResults) ? structuredClone(value.thresholdResults) : [],
|
|
1024
|
+
...name === void 0 ? {} : { name },
|
|
1025
|
+
...requestedTrialCount === void 0 ? {} : { requestedTrialCount },
|
|
921
1026
|
...aggregate2 === void 0 ? {} : { aggregate: accountingStatus === "partial" ? withoutAuthoritativeCost(aggregate2) : aggregate2 }
|
|
922
1027
|
};
|
|
923
1028
|
}
|
|
@@ -1036,7 +1141,7 @@ async function runEvaluationWorkPool(options) {
|
|
|
1036
1141
|
const work = options.work[index];
|
|
1037
1142
|
const result = await options.execute(work, index);
|
|
1038
1143
|
results[index] = result;
|
|
1039
|
-
(_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results);
|
|
1144
|
+
await ((_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results));
|
|
1040
1145
|
}
|
|
1041
1146
|
};
|
|
1042
1147
|
await Promise.all(Array.from({ length: Math.min(options.concurrency, options.work.length) }, worker));
|
|
@@ -1316,9 +1421,7 @@ function validateSuite(project, suite, dataset, purpose) {
|
|
|
1316
1421
|
for (const assertion of suite.assertions) {
|
|
1317
1422
|
const outputId = getEvaluationTopLevelOutputId(assertion.outputPath);
|
|
1318
1423
|
if (assertion.outputPath.trim() !== "$" && outputId === void 0) {
|
|
1319
|
-
throw new Error(
|
|
1320
|
-
`Assertion "${assertion.name}" output path must start with a named target Graph Output.`
|
|
1321
|
-
);
|
|
1424
|
+
throw new Error(`Assertion "${assertion.name}" output path must start with a named target Graph Output.`);
|
|
1322
1425
|
}
|
|
1323
1426
|
if (outputId !== void 0 && !graphOutputIds.includes(outputId)) {
|
|
1324
1427
|
throw new Error(`Assertion "${assertion.name}" references missing target output "${outputId}".`);
|
|
@@ -1697,9 +1800,9 @@ async function runWithTimeout(run, timeoutMs, outer) {
|
|
|
1697
1800
|
outer == null ? void 0 : outer.removeEventListener("abort", abort);
|
|
1698
1801
|
}
|
|
1699
1802
|
}
|
|
1700
|
-
async function
|
|
1803
|
+
async function runEvaluationTrial(input) {
|
|
1701
1804
|
var _a, _b, _c, _d;
|
|
1702
|
-
const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal } = input;
|
|
1805
|
+
const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal, trialId } = input;
|
|
1703
1806
|
const evaluationMode = getEvaluationSuiteMode(suite);
|
|
1704
1807
|
const { inputs, expected } = resolveCaseInputs(suite, dataset, testCase, purpose);
|
|
1705
1808
|
const seed = deriveSeed((_a = suite.configuration) == null ? void 0 : _a.seed, testCase.id, trialIndex);
|
|
@@ -1745,7 +1848,7 @@ async function runTrial(input) {
|
|
|
1745
1848
|
const executionStatus2 = "canceled";
|
|
1746
1849
|
const qualityStatus2 = "not-evaluated";
|
|
1747
1850
|
return {
|
|
1748
|
-
id: makeId("trial"),
|
|
1851
|
+
id: trialId ?? makeId("trial"),
|
|
1749
1852
|
caseId: testCase.id,
|
|
1750
1853
|
caseName: testCase.name,
|
|
1751
1854
|
caseIndex,
|
|
@@ -1873,7 +1976,7 @@ async function runTrial(input) {
|
|
|
1873
1976
|
const executionStatus2 = "canceled";
|
|
1874
1977
|
const qualityStatus2 = "not-evaluated";
|
|
1875
1978
|
return {
|
|
1876
|
-
id: makeId("trial"),
|
|
1979
|
+
id: trialId ?? makeId("trial"),
|
|
1877
1980
|
caseId: testCase.id,
|
|
1878
1981
|
caseName: testCase.name,
|
|
1879
1982
|
caseIndex,
|
|
@@ -1958,7 +2061,7 @@ async function runTrial(input) {
|
|
|
1958
2061
|
}
|
|
1959
2062
|
const totalMetrics = mergeMetrics(targetMetrics, evaluatorMetrics);
|
|
1960
2063
|
return {
|
|
1961
|
-
id: makeId("trial"),
|
|
2064
|
+
id: trialId ?? makeId("trial"),
|
|
1962
2065
|
caseId: testCase.id,
|
|
1963
2066
|
caseName: testCase.name,
|
|
1964
2067
|
caseIndex,
|
|
@@ -1984,6 +2087,13 @@ function percentile(values, percentage) {
|
|
|
1984
2087
|
const ordered = values.slice().sort((left, right) => left - right);
|
|
1985
2088
|
return ordered[Math.min(ordered.length - 1, Math.ceil(percentage / 100 * ordered.length) - 1)];
|
|
1986
2089
|
}
|
|
2090
|
+
function median(values) {
|
|
2091
|
+
if (values.length === 0) return 0;
|
|
2092
|
+
const ordered = values.slice().sort((left, right) => left - right);
|
|
2093
|
+
const upperIndex = Math.floor(ordered.length / 2);
|
|
2094
|
+
if (ordered.length % 2 !== 0) return ordered[upperIndex];
|
|
2095
|
+
return (ordered[upperIndex - 1] + ordered[upperIndex]) / 2;
|
|
2096
|
+
}
|
|
1987
2097
|
function meanObservationScore(observations) {
|
|
1988
2098
|
let weightedScore = 0;
|
|
1989
2099
|
let totalWeight = 0;
|
|
@@ -2058,6 +2168,7 @@ function aggregate(trials, evaluationMode = "pass-fail") {
|
|
|
2058
2168
|
const scoredTrialCount = cases.reduce((sum, testCase) => sum + (testCase.scoredTrialCount ?? 0), 0);
|
|
2059
2169
|
const scoringCaseMeans = cases.map((testCase) => testCase.meanScore).filter((score) => score !== void 0);
|
|
2060
2170
|
const scoringMeanScore = scoringCaseMeans.length === 0 ? void 0 : scoringCaseMeans.reduce((sum, score) => sum + score, 0) / scoringCaseMeans.length;
|
|
2171
|
+
const incurredLatencies = incurred.map((trial) => trial.totalMetrics.durationMs);
|
|
2061
2172
|
const aggregateValue = {
|
|
2062
2173
|
trialCount: trials.length,
|
|
2063
2174
|
evaluatedTrialCount: evaluated.length,
|
|
@@ -2071,13 +2182,15 @@ function aggregate(trials, evaluationMode = "pass-fail") {
|
|
|
2071
2182
|
...evaluationMode === "scoring" ? {
|
|
2072
2183
|
scoredTrialCount,
|
|
2073
2184
|
missingScoreTrialCount: trials.length - scoredTrialCount,
|
|
2074
|
-
...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore }
|
|
2185
|
+
...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore },
|
|
2186
|
+
...scoringCaseMeans.length === 0 ? {} : {
|
|
2187
|
+
medianScore: median(scoringCaseMeans),
|
|
2188
|
+
p95Score: percentile(scoringCaseMeans, 95)
|
|
2189
|
+
}
|
|
2075
2190
|
} : meanScore === void 0 ? {} : { meanScore },
|
|
2076
|
-
averageLatencyMs: incurred.length === 0 ? 0 :
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
95
|
|
2080
|
-
),
|
|
2191
|
+
averageLatencyMs: incurred.length === 0 ? 0 : incurredLatencies.reduce((sum, durationMs) => sum + durationMs, 0) / incurred.length,
|
|
2192
|
+
medianLatencyMs: median(incurredLatencies),
|
|
2193
|
+
p95LatencyMs: percentile(incurredLatencies, 95),
|
|
2081
2194
|
...incurred.length === 0 || totalCostUsd === void 0 ? {} : { totalCostUsd, averageCostUsd: totalCostUsd / incurred.length },
|
|
2082
2195
|
targetErrorRate: settled.length === 0 ? 0 : errors.length / settled.length,
|
|
2083
2196
|
evaluatorErrorRate: settled.length === 0 ? 0 : settled.filter(
|
|
@@ -2405,8 +2518,8 @@ function createProvenance(project, suite, dataset, options) {
|
|
|
2405
2518
|
accountingComplete: true
|
|
2406
2519
|
};
|
|
2407
2520
|
}
|
|
2408
|
-
|
|
2409
|
-
var _a
|
|
2521
|
+
function createEvaluationRunPlan(options) {
|
|
2522
|
+
var _a;
|
|
2410
2523
|
const suite = resolveSuite(options.evaluationData, options.suiteId);
|
|
2411
2524
|
const purpose = options.purpose ?? "evaluation";
|
|
2412
2525
|
const evaluationMode = getEvaluationSuiteMode(suite);
|
|
@@ -2416,6 +2529,110 @@ async function runEvaluationSuite(options) {
|
|
|
2416
2529
|
);
|
|
2417
2530
|
}
|
|
2418
2531
|
validateSuite(options.project, suite, options.dataset, purpose);
|
|
2532
|
+
const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
|
|
2533
|
+
const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
|
|
2534
|
+
(testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
|
|
2535
|
+
);
|
|
2536
|
+
return { suite, purpose, evaluationMode, work };
|
|
2537
|
+
}
|
|
2538
|
+
function finalizeEvaluationRun(options) {
|
|
2539
|
+
var _a, _b;
|
|
2540
|
+
const run = options.run;
|
|
2541
|
+
const evaluationMode = getEvaluationSuiteMode(options.suite);
|
|
2542
|
+
run.trials = options.trials.slice();
|
|
2543
|
+
run.completedAt = options.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2544
|
+
const outcome = aggregate(run.trials, evaluationMode);
|
|
2545
|
+
run.aggregate = outcome.aggregate;
|
|
2546
|
+
run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
|
|
2547
|
+
run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
|
|
2548
|
+
if (run.accountingStatus === "partial" && !run.warnings.includes("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.")) {
|
|
2549
|
+
run.warnings.push("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.");
|
|
2550
|
+
}
|
|
2551
|
+
if (options.canceled) {
|
|
2552
|
+
run.executionStatus = "canceled";
|
|
2553
|
+
run.qualityStatus = "not-evaluated";
|
|
2554
|
+
run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
|
|
2555
|
+
return run;
|
|
2556
|
+
}
|
|
2557
|
+
if (run.purpose === "execution-benchmark") {
|
|
2558
|
+
run.executionStatus = "completed";
|
|
2559
|
+
run.qualityStatus = "not-evaluated";
|
|
2560
|
+
run.qualityReason = { code: "benchmark", message: "This run measured execution without evaluating output quality." };
|
|
2561
|
+
return run;
|
|
2562
|
+
}
|
|
2563
|
+
if (evaluationMode === "scoring") {
|
|
2564
|
+
run.executionStatus = "completed";
|
|
2565
|
+
if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
|
|
2566
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2567
|
+
run.qualityReason = { code: "scores-incomplete", message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage." };
|
|
2568
|
+
run.warnings.push(`Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`);
|
|
2569
|
+
} else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
|
|
2570
|
+
run.qualityStatus = "scored";
|
|
2571
|
+
run.qualityReason = { code: "scores-complete", message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages." };
|
|
2572
|
+
} else {
|
|
2573
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2574
|
+
run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
|
|
2575
|
+
}
|
|
2576
|
+
return run;
|
|
2577
|
+
}
|
|
2578
|
+
const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === options.suite.id);
|
|
2579
|
+
const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
|
|
2580
|
+
if (baseline && !usableBaseline && !run.warnings.includes("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.")) {
|
|
2581
|
+
run.warnings.push("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.");
|
|
2582
|
+
}
|
|
2583
|
+
run.thresholdResults = evaluateThresholdResults(outcome.aggregate, options.suite.thresholds, usableBaseline);
|
|
2584
|
+
const hasPassRateThreshold = ((_a = options.suite.thresholds) == null ? void 0 : _a.some((threshold) => threshold.metric === "pass-rate")) ?? false;
|
|
2585
|
+
const hasTargetErrorRateThreshold = ((_b = options.suite.thresholds) == null ? void 0 : _b.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
|
|
2586
|
+
const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
|
|
2587
|
+
const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
|
|
2588
|
+
const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
|
|
2589
|
+
const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
|
|
2590
|
+
const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
|
|
2591
|
+
run.executionStatus = "completed";
|
|
2592
|
+
if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
|
|
2593
|
+
run.qualityStatus = "failed";
|
|
2594
|
+
run.qualityReason = { code: hasThresholdFailure ? "thresholds-failed" : "checks-failed", message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed." };
|
|
2595
|
+
} else if (hasUnavailableEvidence) {
|
|
2596
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2597
|
+
run.qualityReason = { code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error", message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated." };
|
|
2598
|
+
} else if (hasAuthoritativePass) {
|
|
2599
|
+
run.qualityStatus = "passed";
|
|
2600
|
+
run.qualityReason = { code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed", message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed." };
|
|
2601
|
+
} else {
|
|
2602
|
+
run.qualityStatus = "not-evaluated";
|
|
2603
|
+
run.qualityReason = { code: "no-completed-trials", message: "No completed trial produced an authoritative quality result." };
|
|
2604
|
+
}
|
|
2605
|
+
return run;
|
|
2606
|
+
}
|
|
2607
|
+
function createEvaluationRunShell(options) {
|
|
2608
|
+
const plan = createEvaluationRunPlan(options);
|
|
2609
|
+
const runId = options.runId ?? makeId("evaluation");
|
|
2610
|
+
const run = {
|
|
2611
|
+
version: 2,
|
|
2612
|
+
id: runId,
|
|
2613
|
+
projectId: options.project.metadata.id,
|
|
2614
|
+
suiteId: plan.suite.id,
|
|
2615
|
+
suiteName: plan.suite.name,
|
|
2616
|
+
revision: 0,
|
|
2617
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2618
|
+
purpose: plan.purpose,
|
|
2619
|
+
evaluationMode: plan.evaluationMode,
|
|
2620
|
+
executionStatus: "running",
|
|
2621
|
+
qualityStatus: "not-evaluated",
|
|
2622
|
+
qualityReason: { code: "in-progress", message: "The run is still in progress." },
|
|
2623
|
+
accountingStatus: "complete",
|
|
2624
|
+
provenance: createProvenance(options.project, plan.suite, options.dataset, options),
|
|
2625
|
+
trials: [],
|
|
2626
|
+
thresholdResults: [],
|
|
2627
|
+
warnings: [],
|
|
2628
|
+
requestedTrialCount: plan.work.length
|
|
2629
|
+
};
|
|
2630
|
+
return { run, plan };
|
|
2631
|
+
}
|
|
2632
|
+
async function runEvaluationSuite(options) {
|
|
2633
|
+
var _a, _b;
|
|
2634
|
+
const plan = createEvaluationRunPlan(options);
|
|
2635
|
+
const { suite, purpose, evaluationMode } = plan;
|
|
2419
2636
|
const runId = options.runId ?? makeId("evaluation");
|
|
2420
2637
|
const run = {
|
|
2421
2638
|
version: 2,
|
|
@@ -2436,27 +2653,61 @@ async function runEvaluationSuite(options) {
|
|
|
2436
2653
|
thresholdResults: [],
|
|
2437
2654
|
warnings: []
|
|
2438
2655
|
};
|
|
2439
|
-
const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
|
|
2440
2656
|
const concurrency = Math.min(
|
|
2441
2657
|
MAX_CONCURRENCY,
|
|
2442
|
-
Math.max(1, Math.floor(((
|
|
2658
|
+
Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.concurrency) ?? DEFAULT_CONCURRENCY))
|
|
2443
2659
|
);
|
|
2444
|
-
const work =
|
|
2445
|
-
|
|
2446
|
-
);
|
|
2447
|
-
|
|
2448
|
-
|
|
2660
|
+
const work = plan.work;
|
|
2661
|
+
run.requestedTrialCount = work.length;
|
|
2662
|
+
const settledTrials = Array.from({ length: work.length });
|
|
2663
|
+
let settledTrialCount = 0;
|
|
2664
|
+
const nextRevision = () => {
|
|
2449
2665
|
run.revision = (run.revision ?? 0) + 1;
|
|
2450
|
-
|
|
2666
|
+
return run.revision;
|
|
2667
|
+
};
|
|
2668
|
+
const publishLegacySnapshot = () => {
|
|
2669
|
+
if (!options.onUpdate) return;
|
|
2670
|
+
run.trials = settledTrials.filter((trial) => trial !== void 0);
|
|
2671
|
+
options.onUpdate(structuredClone(run));
|
|
2672
|
+
};
|
|
2673
|
+
const publishStarted = async () => {
|
|
2674
|
+
const revision = nextRevision();
|
|
2675
|
+
if (options.onEvent) {
|
|
2676
|
+
const shell = structuredClone(run);
|
|
2677
|
+
shell.trials = [];
|
|
2678
|
+
await options.onEvent({ type: "run-started", revision, run: shell });
|
|
2679
|
+
}
|
|
2680
|
+
publishLegacySnapshot();
|
|
2681
|
+
};
|
|
2682
|
+
const publishSettledTrial = async (trial) => {
|
|
2683
|
+
var _a2;
|
|
2684
|
+
const revision = nextRevision();
|
|
2685
|
+
await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, {
|
|
2686
|
+
type: "trial-settled",
|
|
2687
|
+
revision,
|
|
2688
|
+
runId: run.id,
|
|
2689
|
+
projectId: run.projectId,
|
|
2690
|
+
suiteId: run.suiteId,
|
|
2691
|
+
requestedTrialCount: run.requestedTrialCount ?? work.length,
|
|
2692
|
+
settledTrialCount,
|
|
2693
|
+
trial: structuredClone(trial)
|
|
2694
|
+
}));
|
|
2695
|
+
publishLegacySnapshot();
|
|
2451
2696
|
};
|
|
2452
|
-
|
|
2697
|
+
const publishFinalized = async () => {
|
|
2698
|
+
var _a2;
|
|
2699
|
+
const revision = nextRevision();
|
|
2700
|
+
await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type: "run-finalized", revision, run: structuredClone(run) }));
|
|
2701
|
+
publishLegacySnapshot();
|
|
2702
|
+
};
|
|
2703
|
+
await publishStarted();
|
|
2453
2704
|
const workPool = runEvaluationWorkPool({
|
|
2454
2705
|
work,
|
|
2455
2706
|
concurrency,
|
|
2456
2707
|
signal: options.signal,
|
|
2457
2708
|
execute: async (task) => {
|
|
2458
2709
|
try {
|
|
2459
|
-
return await
|
|
2710
|
+
return await runEvaluationTrial({
|
|
2460
2711
|
...task,
|
|
2461
2712
|
project: options.project,
|
|
2462
2713
|
suite,
|
|
@@ -2489,98 +2740,22 @@ async function runEvaluationSuite(options) {
|
|
|
2489
2740
|
};
|
|
2490
2741
|
}
|
|
2491
2742
|
},
|
|
2492
|
-
onSettled: (
|
|
2493
|
-
|
|
2494
|
-
|
|
2743
|
+
onSettled: async (result, index) => {
|
|
2744
|
+
settledTrials[index] = result;
|
|
2745
|
+
settledTrialCount += 1;
|
|
2746
|
+
await publishSettledTrial(result);
|
|
2495
2747
|
}
|
|
2496
2748
|
});
|
|
2497
2749
|
const results = await workPool;
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
);
|
|
2508
|
-
}
|
|
2509
|
-
if ((_c = options.signal) == null ? void 0 : _c.aborted) {
|
|
2510
|
-
run.executionStatus = "canceled";
|
|
2511
|
-
run.qualityStatus = "not-evaluated";
|
|
2512
|
-
run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
|
|
2513
|
-
} else if (purpose === "execution-benchmark") {
|
|
2514
|
-
run.executionStatus = "completed";
|
|
2515
|
-
run.qualityStatus = "not-evaluated";
|
|
2516
|
-
run.qualityReason = {
|
|
2517
|
-
code: "benchmark",
|
|
2518
|
-
message: "This run measured execution without evaluating output quality."
|
|
2519
|
-
};
|
|
2520
|
-
} else if (evaluationMode === "scoring") {
|
|
2521
|
-
run.executionStatus = "completed";
|
|
2522
|
-
if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
|
|
2523
|
-
run.qualityStatus = "unable-to-evaluate";
|
|
2524
|
-
run.qualityReason = {
|
|
2525
|
-
code: "scores-incomplete",
|
|
2526
|
-
message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage."
|
|
2527
|
-
};
|
|
2528
|
-
run.warnings.push(
|
|
2529
|
-
`Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`
|
|
2530
|
-
);
|
|
2531
|
-
} else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
|
|
2532
|
-
run.qualityStatus = "scored";
|
|
2533
|
-
run.qualityReason = {
|
|
2534
|
-
code: "scores-complete",
|
|
2535
|
-
message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages."
|
|
2536
|
-
};
|
|
2537
|
-
} else {
|
|
2538
|
-
run.qualityStatus = "unable-to-evaluate";
|
|
2539
|
-
run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
|
|
2540
|
-
}
|
|
2541
|
-
} else {
|
|
2542
|
-
const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === suite.id);
|
|
2543
|
-
const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
|
|
2544
|
-
if (baseline && !usableBaseline)
|
|
2545
|
-
run.warnings.push(
|
|
2546
|
-
"The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed."
|
|
2547
|
-
);
|
|
2548
|
-
run.thresholdResults = evaluateThresholdResults(outcome.aggregate, suite.thresholds, usableBaseline);
|
|
2549
|
-
const hasPassRateThreshold = ((_d = suite.thresholds) == null ? void 0 : _d.some((threshold) => threshold.metric === "pass-rate")) ?? false;
|
|
2550
|
-
const hasTargetErrorRateThreshold = ((_e = suite.thresholds) == null ? void 0 : _e.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
|
|
2551
|
-
const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
|
|
2552
|
-
const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
|
|
2553
|
-
const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
|
|
2554
|
-
const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
|
|
2555
|
-
const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
|
|
2556
|
-
if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
|
|
2557
|
-
run.qualityStatus = "failed";
|
|
2558
|
-
run.qualityReason = {
|
|
2559
|
-
code: hasThresholdFailure ? "thresholds-failed" : "checks-failed",
|
|
2560
|
-
message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed."
|
|
2561
|
-
};
|
|
2562
|
-
} else if (hasUnavailableEvidence) {
|
|
2563
|
-
run.qualityStatus = "unable-to-evaluate";
|
|
2564
|
-
run.qualityReason = {
|
|
2565
|
-
code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error",
|
|
2566
|
-
message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated."
|
|
2567
|
-
};
|
|
2568
|
-
} else if (hasAuthoritativePass) {
|
|
2569
|
-
run.qualityStatus = "passed";
|
|
2570
|
-
run.qualityReason = {
|
|
2571
|
-
code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed",
|
|
2572
|
-
message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed."
|
|
2573
|
-
};
|
|
2574
|
-
} else {
|
|
2575
|
-
run.qualityStatus = "not-evaluated";
|
|
2576
|
-
run.qualityReason = {
|
|
2577
|
-
code: "no-completed-trials",
|
|
2578
|
-
message: "No completed trial produced an authoritative quality result."
|
|
2579
|
-
};
|
|
2580
|
-
}
|
|
2581
|
-
run.executionStatus = "completed";
|
|
2582
|
-
}
|
|
2583
|
-
publish();
|
|
2750
|
+
finalizeEvaluationRun({
|
|
2751
|
+
run,
|
|
2752
|
+
trials: results.filter((result) => result !== void 0),
|
|
2753
|
+
suite,
|
|
2754
|
+
evaluationData: options.evaluationData,
|
|
2755
|
+
baseline: options.baseline,
|
|
2756
|
+
canceled: ((_b = options.signal) == null ? void 0 : _b.aborted) === true
|
|
2757
|
+
});
|
|
2758
|
+
await publishFinalized();
|
|
2584
2759
|
return run;
|
|
2585
2760
|
}
|
|
2586
2761
|
async function runEvaluationCases(options) {
|
|
@@ -2619,93 +2794,131 @@ function createEvaluationBaselineSnapshot(run) {
|
|
|
2619
2794
|
};
|
|
2620
2795
|
}
|
|
2621
2796
|
|
|
2622
|
-
// src/
|
|
2623
|
-
var
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
|
|
2797
|
+
// src/datasetTransfer.ts
|
|
2798
|
+
var DATASET_EXPORT_VERSION = 1;
|
|
2799
|
+
function isRecord2(value) {
|
|
2800
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2801
|
+
}
|
|
2802
|
+
function requireString(value, path) {
|
|
2803
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
|
|
2804
|
+
return value;
|
|
2805
|
+
}
|
|
2806
|
+
function requireText(value, path) {
|
|
2807
|
+
if (typeof value !== "string") throw new Error(`${path} must be a string.`);
|
|
2808
|
+
return value;
|
|
2809
|
+
}
|
|
2810
|
+
function parseFields(value) {
|
|
2811
|
+
if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
|
|
2812
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2813
|
+
return value.map((field, index) => {
|
|
2814
|
+
if (!isRecord2(field)) throw new Error(`fields[${index}] must be an object.`);
|
|
2815
|
+
const id = requireString(field.id, `fields[${index}].id`);
|
|
2816
|
+
if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
|
|
2817
|
+
ids.add(id);
|
|
2818
|
+
const role = field.role;
|
|
2819
|
+
if (role !== "input" && role !== "expected" && role !== "metadata") {
|
|
2820
|
+
throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
|
|
2647
2821
|
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
assertEvaluationDatasetSnapshot(snapshot);
|
|
2651
|
-
const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
|
|
2652
|
-
if (this.#datasetSnapshots.has(key)) return;
|
|
2653
|
-
this.#datasetSnapshots.set(key, structuredClone(snapshot));
|
|
2654
|
-
}
|
|
2655
|
-
async getDatasetSnapshot(input) {
|
|
2656
|
-
const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
|
|
2657
|
-
return snapshot ? structuredClone(snapshot) : void 0;
|
|
2658
|
-
}
|
|
2659
|
-
async putRecording(artifact) {
|
|
2660
|
-
const key = `${artifact.projectId}/${artifact.reference.id}`;
|
|
2661
|
-
const existing = this.#recordings.get(key);
|
|
2662
|
-
if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
|
|
2663
|
-
throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
|
|
2822
|
+
if (field.description !== void 0 && typeof field.description !== "string") {
|
|
2823
|
+
throw new Error(`fields[${index}].description must be a string.`);
|
|
2664
2824
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
this.#recordings.set(key, next);
|
|
2668
|
-
}
|
|
2669
|
-
async getRecording(input) {
|
|
2670
|
-
const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
|
|
2671
|
-
if (!artifact) return void 0;
|
|
2672
|
-
if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
|
|
2673
|
-
this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
|
|
2674
|
-
return void 0;
|
|
2825
|
+
if (field.required !== void 0 && typeof field.required !== "boolean") {
|
|
2826
|
+
throw new Error(`fields[${index}].required must be a boolean.`);
|
|
2675
2827
|
}
|
|
2676
|
-
return
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
id: artifact.reference.id,
|
|
2684
|
-
retention: input.retention,
|
|
2685
|
-
...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
|
|
2828
|
+
return {
|
|
2829
|
+
id,
|
|
2830
|
+
name: requireText(field.name, `fields[${index}].name`),
|
|
2831
|
+
dataType: requireString(field.dataType, `fields[${index}].dataType`),
|
|
2832
|
+
role,
|
|
2833
|
+
...typeof field.description === "string" ? { description: field.description } : {},
|
|
2834
|
+
...typeof field.required === "boolean" ? { required: field.required } : {}
|
|
2686
2835
|
};
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
function parseCases(value, fieldIds) {
|
|
2839
|
+
if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
|
|
2840
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2841
|
+
return value.map((testCase, index) => {
|
|
2842
|
+
if (!isRecord2(testCase)) throw new Error(`cases[${index}] must be an object.`);
|
|
2843
|
+
const id = requireString(testCase.id, `cases[${index}].id`);
|
|
2844
|
+
if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
|
|
2845
|
+
ids.add(id);
|
|
2846
|
+
if (!isRecord2(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
|
|
2847
|
+
for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
|
|
2848
|
+
if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
|
|
2849
|
+
assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
|
|
2850
|
+
}
|
|
2851
|
+
if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
|
|
2852
|
+
throw new Error(`cases[${index}].tags must be an array of strings.`);
|
|
2692
2853
|
}
|
|
2854
|
+
if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
|
|
2855
|
+
if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
|
|
2856
|
+
return {
|
|
2857
|
+
id,
|
|
2858
|
+
name: requireText(testCase.name, `cases[${index}].name`),
|
|
2859
|
+
values: testCase.values,
|
|
2860
|
+
...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
|
|
2861
|
+
...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
|
|
2862
|
+
...typeof testCase.note === "string" ? { note: testCase.note } : {}
|
|
2863
|
+
};
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
function validateEvaluationDataset(value, scope) {
|
|
2867
|
+
if (!isRecord2(value)) throw new Error("Evaluation dataset must be an object.");
|
|
2868
|
+
const fields = parseFields(value.fields);
|
|
2869
|
+
const importedProjectId = value.projectId === void 0 ? void 0 : requireString(value.projectId, "dataset.projectId");
|
|
2870
|
+
const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
|
|
2871
|
+
const dataset = {
|
|
2872
|
+
id: (scope == null ? void 0 : scope.id) ?? requireString(value.id, "dataset.id"),
|
|
2873
|
+
name: requireText(value.name, "dataset.name"),
|
|
2874
|
+
...projectId === void 0 ? {} : { projectId },
|
|
2875
|
+
fields,
|
|
2876
|
+
cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
|
|
2877
|
+
};
|
|
2878
|
+
return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
|
|
2879
|
+
}
|
|
2880
|
+
function localizeEvaluationDataset(value, id) {
|
|
2881
|
+
const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
|
|
2882
|
+
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
2883
|
+
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
2884
|
+
}
|
|
2885
|
+
function validateEvaluationDatasetForTransfer(value, scope) {
|
|
2886
|
+
const dataset = validateEvaluationDataset(value, scope);
|
|
2887
|
+
assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
|
|
2888
|
+
return dataset;
|
|
2889
|
+
}
|
|
2890
|
+
function serializeEvaluationDatasetJson(dataset) {
|
|
2891
|
+
const validated = validateEvaluationDatasetForTransfer(dataset);
|
|
2892
|
+
return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
|
|
2893
|
+
}
|
|
2894
|
+
function deserializeEvaluationDatasetJson(source, scope) {
|
|
2895
|
+
let parsed;
|
|
2896
|
+
try {
|
|
2897
|
+
parsed = JSON.parse(source);
|
|
2898
|
+
} catch {
|
|
2899
|
+
throw new Error("Evaluation dataset JSON is not valid JSON.");
|
|
2693
2900
|
}
|
|
2694
|
-
|
|
2901
|
+
if (!isRecord2(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord2(parsed.dataset)) {
|
|
2902
|
+
throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
|
|
2903
|
+
}
|
|
2904
|
+
const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
|
|
2905
|
+
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
2906
|
+
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
2907
|
+
}
|
|
2695
2908
|
|
|
2696
2909
|
// src/serialization.ts
|
|
2697
2910
|
var yaml = __toESM(require("yaml"), 1);
|
|
2698
|
-
function
|
|
2911
|
+
function isRecord3(value) {
|
|
2699
2912
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2700
2913
|
}
|
|
2701
2914
|
function requireRecord(value, path) {
|
|
2702
|
-
if (!
|
|
2915
|
+
if (!isRecord3(value)) throw new Error(`${path} must be an object.`);
|
|
2703
2916
|
return value;
|
|
2704
2917
|
}
|
|
2705
|
-
function
|
|
2918
|
+
function requireString2(value, path) {
|
|
2706
2919
|
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path} must be a non-empty string.`);
|
|
2707
2920
|
}
|
|
2708
|
-
function
|
|
2921
|
+
function requireText2(value, path) {
|
|
2709
2922
|
if (typeof value !== "string") throw new Error(`${path} must be a string.`);
|
|
2710
2923
|
}
|
|
2711
2924
|
function requireBoolean(value, path) {
|
|
@@ -2747,21 +2960,21 @@ var EVALUATOR_CONTEXT_INPUTS = /* @__PURE__ */ new Set(["case", "inputs", "expec
|
|
|
2747
2960
|
function validateSuiteShape(value, index) {
|
|
2748
2961
|
const path = `evaluations.suites[${index}]`;
|
|
2749
2962
|
const suite = requireRecord(value, path);
|
|
2750
|
-
for (const key of ["id", "targetGraphId", "datasetId"])
|
|
2751
|
-
|
|
2963
|
+
for (const key of ["id", "targetGraphId", "datasetId"]) requireString2(suite[key], `${path}.${key}`);
|
|
2964
|
+
requireText2(suite.name, `${path}.name`);
|
|
2752
2965
|
if (suite.evaluationMode !== void 0 && suite.evaluationMode !== "pass-fail" && suite.evaluationMode !== "scoring") {
|
|
2753
2966
|
throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
|
|
2754
2967
|
}
|
|
2755
2968
|
for (const [bindingIndex, bindingValue] of requireArray(suite.inputBindings, `${path}.inputBindings`).entries()) {
|
|
2756
2969
|
const binding = requireRecord(bindingValue, `${path}.inputBindings[${bindingIndex}]`);
|
|
2757
|
-
|
|
2758
|
-
|
|
2970
|
+
requireString2(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
|
|
2971
|
+
requireString2(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
|
|
2759
2972
|
}
|
|
2760
2973
|
for (const [assertionIndex, assertionValue] of requireArray(suite.assertions, `${path}.assertions`).entries()) {
|
|
2761
2974
|
const assertionPath = `${path}.assertions[${assertionIndex}]`;
|
|
2762
2975
|
const assertion = requireRecord(assertionValue, assertionPath);
|
|
2763
|
-
for (const key of ["id", "outputPath"])
|
|
2764
|
-
|
|
2976
|
+
for (const key of ["id", "outputPath"]) requireString2(assertion[key], `${assertionPath}.${key}`);
|
|
2977
|
+
requireText2(assertion.name, `${assertionPath}.name`);
|
|
2765
2978
|
if (typeof assertion.operator !== "string" || !ASSERTION_OPERATORS.has(assertion.operator)) {
|
|
2766
2979
|
throw new Error(`${assertionPath}.operator is not supported.`);
|
|
2767
2980
|
}
|
|
@@ -2770,7 +2983,7 @@ function validateSuiteShape(value, index) {
|
|
|
2770
2983
|
if (expected.kind === "literal") {
|
|
2771
2984
|
if (!Object.hasOwn(expected, "value")) throw new Error(`${assertionPath}.expected.value is required.`);
|
|
2772
2985
|
} else if (expected.kind === "dataset-field") {
|
|
2773
|
-
|
|
2986
|
+
requireString2(expected.fieldId, `${assertionPath}.expected.fieldId`);
|
|
2774
2987
|
} else {
|
|
2775
2988
|
throw new Error(`${assertionPath}.expected.kind must be "literal" or "dataset-field".`);
|
|
2776
2989
|
}
|
|
@@ -2778,8 +2991,8 @@ function validateSuiteShape(value, index) {
|
|
|
2778
2991
|
for (const [evaluatorIndex, evaluatorValue] of requireArray(suite.evaluators, `${path}.evaluators`).entries()) {
|
|
2779
2992
|
const evaluatorPath = `${path}.evaluators[${evaluatorIndex}]`;
|
|
2780
2993
|
const evaluator = requireRecord(evaluatorValue, evaluatorPath);
|
|
2781
|
-
for (const key of ["id", "graphId"])
|
|
2782
|
-
|
|
2994
|
+
for (const key of ["id", "graphId"]) requireString2(evaluator[key], `${evaluatorPath}.${key}`);
|
|
2995
|
+
requireText2(evaluator.name, `${evaluatorPath}.name`);
|
|
2783
2996
|
validateOptionalPrimitive(evaluator, "required", "boolean", evaluatorPath);
|
|
2784
2997
|
validateOptionalPrimitive(evaluator, "runOnTargetError", "boolean", evaluatorPath);
|
|
2785
2998
|
validateOptionalPrimitive(evaluator, "scoreWeight", "number", evaluatorPath);
|
|
@@ -2790,10 +3003,10 @@ function validateSuiteShape(value, index) {
|
|
|
2790
3003
|
).entries()) {
|
|
2791
3004
|
const bindingPath = `${evaluatorPath}.inputBindings[${bindingIndex}]`;
|
|
2792
3005
|
const binding = requireRecord(bindingValue, bindingPath);
|
|
2793
|
-
|
|
3006
|
+
requireString2(binding.graphInputId, `${bindingPath}.graphInputId`);
|
|
2794
3007
|
const source = requireRecord(binding.source, `${bindingPath}.source`);
|
|
2795
|
-
if (source.kind === "dataset-field")
|
|
2796
|
-
else if (source.kind === "target-output")
|
|
3008
|
+
if (source.kind === "dataset-field") requireString2(source.fieldId, `${bindingPath}.source.fieldId`);
|
|
3009
|
+
else if (source.kind === "target-output") requireString2(source.outputId, `${bindingPath}.source.outputId`);
|
|
2797
3010
|
else if (source.kind === "context") {
|
|
2798
3011
|
if (typeof source.context !== "string" || !EVALUATOR_CONTEXT_INPUTS.has(source.context)) {
|
|
2799
3012
|
throw new Error(`${bindingPath}.source.context is not supported.`);
|
|
@@ -2808,7 +3021,7 @@ function validateSuiteShape(value, index) {
|
|
|
2808
3021
|
for (const [thresholdIndex, thresholdValue2] of requireArray(suite.thresholds, `${path}.thresholds`).entries()) {
|
|
2809
3022
|
const thresholdPath = `${path}.thresholds[${thresholdIndex}]`;
|
|
2810
3023
|
const threshold = requireRecord(thresholdValue2, thresholdPath);
|
|
2811
|
-
for (const key of ["id", "metric", "operator"])
|
|
3024
|
+
for (const key of ["id", "metric", "operator"]) requireString2(threshold[key], `${thresholdPath}.${key}`);
|
|
2812
3025
|
requireFiniteNumber(threshold.value, `${thresholdPath}.value`);
|
|
2813
3026
|
}
|
|
2814
3027
|
}
|
|
@@ -2823,7 +3036,7 @@ function validateSuiteShape(value, index) {
|
|
|
2823
3036
|
validateOptionalPrimitive(suite, "description", "string", path);
|
|
2824
3037
|
if (suite.tags !== void 0) {
|
|
2825
3038
|
for (const [tagIndex, tag] of requireArray(suite.tags, `${path}.tags`).entries()) {
|
|
2826
|
-
|
|
3039
|
+
requireString2(tag, `${path}.tags[${tagIndex}]`);
|
|
2827
3040
|
}
|
|
2828
3041
|
}
|
|
2829
3042
|
}
|
|
@@ -2834,7 +3047,7 @@ function validateNumericRecord(value, path) {
|
|
|
2834
3047
|
function validateBaselineShape(value, index) {
|
|
2835
3048
|
const path = `evaluations.baselines[${index}]`;
|
|
2836
3049
|
const baseline = requireRecord(value, path);
|
|
2837
|
-
for (const key of ["id", "suiteId", "createdAt"])
|
|
3050
|
+
for (const key of ["id", "suiteId", "createdAt"]) requireString2(baseline[key], `${path}.${key}`);
|
|
2838
3051
|
validateOptionalPrimitive(baseline, "sourceRunId", "string", path);
|
|
2839
3052
|
if (baseline.evaluationMode !== void 0 && baseline.evaluationMode !== "pass-fail" && baseline.evaluationMode !== "scoring") {
|
|
2840
3053
|
throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
|
|
@@ -2847,14 +3060,14 @@ function validateBaselineShape(value, index) {
|
|
|
2847
3060
|
"targetFingerprint",
|
|
2848
3061
|
"executionMode"
|
|
2849
3062
|
]) {
|
|
2850
|
-
|
|
3063
|
+
requireString2(provenance[key], `${path}.provenance.${key}`);
|
|
2851
3064
|
}
|
|
2852
3065
|
const evaluatorFingerprints = requireRecord(
|
|
2853
3066
|
provenance.evaluatorFingerprints,
|
|
2854
3067
|
`${path}.provenance.evaluatorFingerprints`
|
|
2855
3068
|
);
|
|
2856
3069
|
for (const [key, fingerprint2] of Object.entries(evaluatorFingerprints)) {
|
|
2857
|
-
|
|
3070
|
+
requireString2(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
|
|
2858
3071
|
}
|
|
2859
3072
|
requireBoolean(provenance.accountingComplete, `${path}.provenance.accountingComplete`);
|
|
2860
3073
|
const aggregate2 = requireRecord(baseline.aggregate, `${path}.aggregate`);
|
|
@@ -2878,6 +3091,9 @@ function validateBaselineShape(value, index) {
|
|
|
2878
3091
|
"notEvaluatedTrialCount",
|
|
2879
3092
|
"unableToEvaluateTrialCount",
|
|
2880
3093
|
"meanScore",
|
|
3094
|
+
"medianScore",
|
|
3095
|
+
"p95Score",
|
|
3096
|
+
"medianLatencyMs",
|
|
2881
3097
|
"totalCostUsd",
|
|
2882
3098
|
"averageCostUsd",
|
|
2883
3099
|
"scoredTrialCount",
|
|
@@ -2889,8 +3105,8 @@ function validateBaselineShape(value, index) {
|
|
|
2889
3105
|
for (const [caseIndex, caseValue] of requireArray(baseline.cases, `${path}.cases`).entries()) {
|
|
2890
3106
|
const casePath = `${path}.cases[${caseIndex}]`;
|
|
2891
3107
|
const caseAggregate = requireRecord(caseValue, casePath);
|
|
2892
|
-
|
|
2893
|
-
|
|
3108
|
+
requireString2(caseAggregate.caseId, `${casePath}.caseId`);
|
|
3109
|
+
requireString2(caseAggregate.caseName, `${casePath}.caseName`);
|
|
2894
3110
|
validateNumericRecord(caseAggregate.metrics, `${casePath}.metrics`);
|
|
2895
3111
|
for (const key of [
|
|
2896
3112
|
"passRate",
|
|
@@ -2944,117 +3160,160 @@ function deserializeEvaluationProjectDataFromString(data) {
|
|
|
2944
3160
|
return deserializeEvaluationProjectData(yaml.parse(data));
|
|
2945
3161
|
}
|
|
2946
3162
|
|
|
2947
|
-
// src/
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
}
|
|
2956
|
-
function requireText2(value, path) {
|
|
2957
|
-
if (typeof value !== "string") throw new Error(`${path} must be a string.`);
|
|
2958
|
-
return value;
|
|
3163
|
+
// src/library.ts
|
|
3164
|
+
function createEmptyEvaluationLibrary() {
|
|
3165
|
+
return {
|
|
3166
|
+
version: 1,
|
|
3167
|
+
data: createEmptyEvaluationProjectData(),
|
|
3168
|
+
datasets: [],
|
|
3169
|
+
migratedLegacyProjectIds: []
|
|
3170
|
+
};
|
|
2959
3171
|
}
|
|
2960
|
-
function
|
|
2961
|
-
if (
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
|
|
3172
|
+
function normalizeEvaluationLibrary(value) {
|
|
3173
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return createEmptyEvaluationLibrary();
|
|
3174
|
+
const candidate = value;
|
|
3175
|
+
let data = createEmptyEvaluationProjectData();
|
|
3176
|
+
try {
|
|
3177
|
+
data = deserializeEvaluationProjectData(candidate.data);
|
|
3178
|
+
} catch {
|
|
3179
|
+
const dataCandidate = typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data) ? candidate.data : void 0;
|
|
3180
|
+
const suites2 = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.suites) ? dataCandidate.suites.flatMap((suite) => {
|
|
3181
|
+
try {
|
|
3182
|
+
return [deserializeEvaluationProjectData({ version: 1, suites: [suite], baselines: [] }).suites[0]];
|
|
3183
|
+
} catch {
|
|
3184
|
+
return [];
|
|
3185
|
+
}
|
|
3186
|
+
}) : [];
|
|
3187
|
+
const baselines = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.baselines) ? dataCandidate.baselines.flatMap((baseline) => {
|
|
3188
|
+
try {
|
|
3189
|
+
return [deserializeEvaluationProjectData({ version: 1, suites: [], baselines: [baseline] }).baselines[0]];
|
|
3190
|
+
} catch {
|
|
3191
|
+
return [];
|
|
3192
|
+
}
|
|
3193
|
+
}) : [];
|
|
3194
|
+
const suiteIds2 = new Set(suites2.map((suite) => suite.id));
|
|
3195
|
+
data = { version: 1, suites: suites2, baselines: baselines.filter((baseline) => suiteIds2.has(baseline.suiteId)) };
|
|
3196
|
+
}
|
|
3197
|
+
const datasets = Array.isArray(candidate.datasets) ? candidate.datasets.flatMap((dataset) => {
|
|
3198
|
+
try {
|
|
3199
|
+
return [localizeEvaluationDataset(dataset)];
|
|
3200
|
+
} catch {
|
|
3201
|
+
return [];
|
|
2977
3202
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
...
|
|
2985
|
-
|
|
3203
|
+
}) : [];
|
|
3204
|
+
const suites = uniqueById(data.suites);
|
|
3205
|
+
const suiteIds = new Set(suites.map((suite) => suite.id));
|
|
3206
|
+
return {
|
|
3207
|
+
version: 1,
|
|
3208
|
+
data: {
|
|
3209
|
+
...data,
|
|
3210
|
+
suites,
|
|
3211
|
+
baselines: uniqueById(data.baselines).filter((baseline) => suiteIds.has(baseline.suiteId))
|
|
3212
|
+
},
|
|
3213
|
+
datasets: uniqueById(datasets),
|
|
3214
|
+
migratedLegacyProjectIds: Array.isArray(candidate.migratedLegacyProjectIds) ? Array.from(new Set(candidate.migratedLegacyProjectIds.filter((id) => typeof id === "string"))) : []
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
function uniqueById(values) {
|
|
3218
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3219
|
+
return values.filter((value) => {
|
|
3220
|
+
if (seen.has(value.id)) return false;
|
|
3221
|
+
seen.add(value.id);
|
|
3222
|
+
return true;
|
|
2986
3223
|
});
|
|
2987
3224
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3225
|
+
|
|
3226
|
+
// src/store.ts
|
|
3227
|
+
var InMemoryEvaluationRunStore = class {
|
|
3228
|
+
#runs = /* @__PURE__ */ new Map();
|
|
3229
|
+
#datasetSnapshots = /* @__PURE__ */ new Map();
|
|
3230
|
+
#recordings = /* @__PURE__ */ new Map();
|
|
3231
|
+
#library = createEmptyEvaluationLibrary();
|
|
3232
|
+
async getLibrary() {
|
|
3233
|
+
return structuredClone(this.#library);
|
|
3234
|
+
}
|
|
3235
|
+
async putLibrary(library) {
|
|
3236
|
+
this.#library = structuredClone(normalizeEvaluationLibrary(library));
|
|
3237
|
+
}
|
|
3238
|
+
async put(run) {
|
|
3239
|
+
const normalized = normalizeEvaluationRun(run);
|
|
3240
|
+
const key = `${normalized.projectId}/${normalized.id}`;
|
|
3241
|
+
const existing = this.#runs.get(key);
|
|
3242
|
+
const next = reconcileEvaluationRunSnapshots(existing, normalized);
|
|
3243
|
+
if (next === existing) return;
|
|
3244
|
+
this.#runs.set(key, structuredClone(next));
|
|
3245
|
+
}
|
|
3246
|
+
async updateRunName(input) {
|
|
3247
|
+
const key = `${input.projectId}/${input.runId}`;
|
|
3248
|
+
const existing = this.#runs.get(key);
|
|
3249
|
+
if (!existing) return void 0;
|
|
3250
|
+
const renamed = normalizeEvaluationRun({ ...existing, name: input.name });
|
|
3251
|
+
this.#runs.set(key, structuredClone(renamed));
|
|
3252
|
+
return structuredClone(renamed);
|
|
3253
|
+
}
|
|
3254
|
+
async get(input) {
|
|
3255
|
+
const run = this.#runs.get(`${input.projectId}/${input.runId}`);
|
|
3256
|
+
return run ? normalizeEvaluationRun(run) : void 0;
|
|
3257
|
+
}
|
|
3258
|
+
async list(input) {
|
|
3259
|
+
return Array.from(this.#runs.values()).filter(
|
|
3260
|
+
(run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
|
|
3261
|
+
).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
|
|
3262
|
+
}
|
|
3263
|
+
async delete(input) {
|
|
3264
|
+
this.#runs.delete(`${input.projectId}/${input.runId}`);
|
|
3265
|
+
for (const [key, artifact] of this.#recordings) {
|
|
3266
|
+
if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
|
|
3000
3267
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3268
|
+
}
|
|
3269
|
+
async putDatasetSnapshot(snapshot) {
|
|
3270
|
+
assertEvaluationDatasetSnapshot(snapshot);
|
|
3271
|
+
const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
|
|
3272
|
+
if (this.#datasetSnapshots.has(key)) return;
|
|
3273
|
+
this.#datasetSnapshots.set(key, structuredClone(snapshot));
|
|
3274
|
+
}
|
|
3275
|
+
async getDatasetSnapshot(input) {
|
|
3276
|
+
const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
|
|
3277
|
+
return snapshot ? structuredClone(snapshot) : void 0;
|
|
3278
|
+
}
|
|
3279
|
+
async putRecording(artifact) {
|
|
3280
|
+
assertEvaluationRecordingArtifact(artifact);
|
|
3281
|
+
const key = `${artifact.projectId}/${artifact.reference.id}`;
|
|
3282
|
+
const existing = this.#recordings.get(key);
|
|
3283
|
+
if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
|
|
3284
|
+
throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
|
|
3003
3285
|
}
|
|
3004
|
-
|
|
3005
|
-
if (
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3286
|
+
const next = structuredClone(artifact);
|
|
3287
|
+
if (existing) next.reference = structuredClone(existing.reference);
|
|
3288
|
+
this.#recordings.set(key, next);
|
|
3289
|
+
}
|
|
3290
|
+
async getRecording(input) {
|
|
3291
|
+
const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
|
|
3292
|
+
if (!artifact) return void 0;
|
|
3293
|
+
if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
|
|
3294
|
+
this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
|
|
3295
|
+
return void 0;
|
|
3296
|
+
}
|
|
3297
|
+
return structuredClone(artifact);
|
|
3298
|
+
}
|
|
3299
|
+
async updateRecordingRetention(input) {
|
|
3300
|
+
const key = `${input.projectId}/${input.recordingId}`;
|
|
3301
|
+
const artifact = this.#recordings.get(key);
|
|
3302
|
+
if (!artifact) return false;
|
|
3303
|
+
artifact.reference = {
|
|
3304
|
+
id: artifact.reference.id,
|
|
3305
|
+
retention: input.retention,
|
|
3306
|
+
...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
|
|
3013
3307
|
};
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
function validateEvaluationDataset(value, scope) {
|
|
3017
|
-
if (!isRecord3(value)) throw new Error("Evaluation dataset must be an object.");
|
|
3018
|
-
const fields = parseFields(value.fields);
|
|
3019
|
-
const importedProjectId = value.projectId === void 0 ? void 0 : requireString2(value.projectId, "dataset.projectId");
|
|
3020
|
-
const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
|
|
3021
|
-
const dataset = {
|
|
3022
|
-
id: (scope == null ? void 0 : scope.id) ?? requireString2(value.id, "dataset.id"),
|
|
3023
|
-
name: requireText2(value.name, "dataset.name"),
|
|
3024
|
-
...projectId === void 0 ? {} : { projectId },
|
|
3025
|
-
fields,
|
|
3026
|
-
cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
|
|
3027
|
-
};
|
|
3028
|
-
return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
|
|
3029
|
-
}
|
|
3030
|
-
function localizeEvaluationDataset(value, id) {
|
|
3031
|
-
const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
|
|
3032
|
-
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
3033
|
-
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
3034
|
-
}
|
|
3035
|
-
function validateEvaluationDatasetForTransfer(value, scope) {
|
|
3036
|
-
const dataset = validateEvaluationDataset(value, scope);
|
|
3037
|
-
assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
|
|
3038
|
-
return dataset;
|
|
3039
|
-
}
|
|
3040
|
-
function serializeEvaluationDatasetJson(dataset) {
|
|
3041
|
-
const validated = validateEvaluationDatasetForTransfer(dataset);
|
|
3042
|
-
return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
|
|
3043
|
-
}
|
|
3044
|
-
function deserializeEvaluationDatasetJson(source, scope) {
|
|
3045
|
-
let parsed;
|
|
3046
|
-
try {
|
|
3047
|
-
parsed = JSON.parse(source);
|
|
3048
|
-
} catch {
|
|
3049
|
-
throw new Error("Evaluation dataset JSON is not valid JSON.");
|
|
3308
|
+
return true;
|
|
3050
3309
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
3310
|
+
async promoteBaseline(input) {
|
|
3311
|
+
for (const artifact of this.#recordings.values()) {
|
|
3312
|
+
if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
|
|
3313
|
+
artifact.reference = { id: artifact.reference.id, retention: "baseline" };
|
|
3314
|
+
}
|
|
3053
3315
|
}
|
|
3054
|
-
|
|
3055
|
-
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
3056
|
-
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
3057
|
-
}
|
|
3316
|
+
};
|
|
3058
3317
|
|
|
3059
3318
|
// src/suiteTransfer.ts
|
|
3060
3319
|
var SUITE_BUNDLE_EXPORT_VERSION = 1;
|
|
@@ -3106,30 +3365,139 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
|
|
|
3106
3365
|
}
|
|
3107
3366
|
};
|
|
3108
3367
|
}
|
|
3368
|
+
|
|
3369
|
+
// src/librarySync.ts
|
|
3370
|
+
function evaluationLibraryValueEquals(left, right) {
|
|
3371
|
+
if (left === void 0 || right === void 0) return left === right;
|
|
3372
|
+
return canonicalStringify(left) === canonicalStringify(right);
|
|
3373
|
+
}
|
|
3374
|
+
function getEvaluationSuiteBundle(library, id) {
|
|
3375
|
+
const suite = library.data.suites.find((candidate) => candidate.id === id);
|
|
3376
|
+
if (!suite) return void 0;
|
|
3377
|
+
return {
|
|
3378
|
+
suite,
|
|
3379
|
+
baselines: library.data.baselines.filter((baseline) => baseline.suiteId === id)
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
function getEvaluationLibraryResource(library, kind, id) {
|
|
3383
|
+
if (kind === "suite") return { kind, id, value: getEvaluationSuiteBundle(library, id) };
|
|
3384
|
+
return { kind, id, value: library.datasets.find((dataset) => dataset.id === id) };
|
|
3385
|
+
}
|
|
3386
|
+
function resourceIds(library, kind) {
|
|
3387
|
+
return new Set(kind === "suite" ? library.data.suites.map((suite) => suite.id) : library.datasets.map((dataset) => dataset.id));
|
|
3388
|
+
}
|
|
3389
|
+
function diffEvaluationLibraryMutation(previous, next, versions) {
|
|
3390
|
+
const changes = [];
|
|
3391
|
+
for (const kind of ["dataset", "suite"]) {
|
|
3392
|
+
const ids = /* @__PURE__ */ new Set([...resourceIds(previous, kind), ...resourceIds(next, kind)]);
|
|
3393
|
+
for (const id of [...ids].sort()) {
|
|
3394
|
+
if (kind === "suite") {
|
|
3395
|
+
const before = getEvaluationSuiteBundle(previous, id);
|
|
3396
|
+
const after = getEvaluationSuiteBundle(next, id);
|
|
3397
|
+
if (evaluationLibraryValueEquals(before, after)) continue;
|
|
3398
|
+
const currentVersion = versions.suites[id];
|
|
3399
|
+
if (before !== void 0 && currentVersion === void 0) {
|
|
3400
|
+
throw new Error(`The suite "${id}" has no synchronization version.`);
|
|
3401
|
+
}
|
|
3402
|
+
const expectedVersion = before === void 0 ? null : currentVersion;
|
|
3403
|
+
if (after === void 0) {
|
|
3404
|
+
changes.push({ kind: "delete-suite", id, expectedVersion });
|
|
3405
|
+
} else {
|
|
3406
|
+
changes.push({
|
|
3407
|
+
kind: "put-suite",
|
|
3408
|
+
id,
|
|
3409
|
+
expectedVersion,
|
|
3410
|
+
suite: after.suite,
|
|
3411
|
+
baselines: after.baselines
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
} else {
|
|
3415
|
+
const before = previous.datasets.find((dataset) => dataset.id === id);
|
|
3416
|
+
const after = next.datasets.find((dataset) => dataset.id === id);
|
|
3417
|
+
if (evaluationLibraryValueEquals(before, after)) continue;
|
|
3418
|
+
const currentVersion = versions.datasets[id];
|
|
3419
|
+
if (before !== void 0 && currentVersion === void 0) {
|
|
3420
|
+
throw new Error(`The dataset "${id}" has no synchronization version.`);
|
|
3421
|
+
}
|
|
3422
|
+
const expectedVersion = before === void 0 ? null : currentVersion;
|
|
3423
|
+
if (after === void 0) {
|
|
3424
|
+
changes.push({ kind: "delete-dataset", id, expectedVersion });
|
|
3425
|
+
} else {
|
|
3426
|
+
changes.push({ kind: "put-dataset", id, expectedVersion, dataset: after });
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
return changes.length === 0 ? void 0 : { changes };
|
|
3432
|
+
}
|
|
3433
|
+
function replaceById(items, value) {
|
|
3434
|
+
const index = items.findIndex((item) => item.id === value.id);
|
|
3435
|
+
if (index === -1) return [...items, value];
|
|
3436
|
+
return items.map((item) => item.id === value.id ? value : item);
|
|
3437
|
+
}
|
|
3438
|
+
function applyEvaluationLibraryMutation(library, mutation) {
|
|
3439
|
+
let suites = library.data.suites;
|
|
3440
|
+
let baselines = library.data.baselines;
|
|
3441
|
+
let datasets = library.datasets;
|
|
3442
|
+
for (const change of mutation.changes) {
|
|
3443
|
+
switch (change.kind) {
|
|
3444
|
+
case "put-suite":
|
|
3445
|
+
suites = replaceById(suites, change.suite);
|
|
3446
|
+
baselines = [...baselines.filter((baseline) => baseline.suiteId !== change.id), ...change.baselines];
|
|
3447
|
+
break;
|
|
3448
|
+
case "delete-suite":
|
|
3449
|
+
suites = suites.filter((suite) => suite.id !== change.id);
|
|
3450
|
+
baselines = baselines.filter((baseline) => baseline.suiteId !== change.id);
|
|
3451
|
+
break;
|
|
3452
|
+
case "put-dataset":
|
|
3453
|
+
datasets = replaceById(datasets, change.dataset);
|
|
3454
|
+
break;
|
|
3455
|
+
case "delete-dataset":
|
|
3456
|
+
datasets = datasets.filter((dataset) => dataset.id !== change.id);
|
|
3457
|
+
break;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
return normalizeEvaluationLibrary({
|
|
3461
|
+
version: 1,
|
|
3462
|
+
data: { version: 1, suites, baselines },
|
|
3463
|
+
datasets,
|
|
3464
|
+
migratedLegacyProjectIds: library.migratedLegacyProjectIds
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3109
3467
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3110
3468
|
0 && (module.exports = {
|
|
3111
3469
|
EvaluationGraphExecutionError,
|
|
3112
3470
|
InMemoryEvaluationRunStore,
|
|
3113
3471
|
LEGACY_EVALUATOR_INPUT_IDS,
|
|
3472
|
+
applyEvaluationLibraryMutation,
|
|
3114
3473
|
areEvaluationDataTypesCompatible,
|
|
3115
3474
|
assertEvaluationDatasetSnapshot,
|
|
3116
3475
|
assertEvaluationDatasetValuesMatchDeclaredTypes,
|
|
3476
|
+
assertEvaluationRecordingArtifact,
|
|
3117
3477
|
assertPortableJson,
|
|
3118
3478
|
canonicalStringify,
|
|
3119
3479
|
canonicalizePortableJson,
|
|
3120
3480
|
clonePortableJson,
|
|
3481
|
+
createEmptyEvaluationLibrary,
|
|
3121
3482
|
createEmptyEvaluationProjectData,
|
|
3122
3483
|
createEmptyMetrics,
|
|
3123
3484
|
createEvaluationBaselineSnapshot,
|
|
3485
|
+
createEvaluationRunPlan,
|
|
3486
|
+
createEvaluationRunShell,
|
|
3124
3487
|
deserializeEvaluationDatasetJson,
|
|
3125
3488
|
deserializeEvaluationProjectData,
|
|
3126
3489
|
deserializeEvaluationProjectDataFromString,
|
|
3127
3490
|
deserializeEvaluationSuiteBundleJson,
|
|
3491
|
+
diffEvaluationLibraryMutation,
|
|
3128
3492
|
evaluateAssertion,
|
|
3129
3493
|
evaluateThresholdResults,
|
|
3494
|
+
evaluationLibraryValueEquals,
|
|
3130
3495
|
finalizeEvaluationRecordingRetention,
|
|
3496
|
+
finalizeEvaluationRun,
|
|
3131
3497
|
fingerprint,
|
|
3132
3498
|
fingerprintEvaluationDataset,
|
|
3499
|
+
getEvaluationLibraryResource,
|
|
3500
|
+
getEvaluationSuiteBundle,
|
|
3133
3501
|
getEvaluationSuiteMode,
|
|
3134
3502
|
getEvaluationTopLevelOutputId,
|
|
3135
3503
|
hasAuthoritativeEvaluationCriteria,
|
|
@@ -3139,11 +3507,15 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
|
|
|
3139
3507
|
localizeEvaluationDataset,
|
|
3140
3508
|
normalizeEvaluationAggregate,
|
|
3141
3509
|
normalizeEvaluationBaselineSnapshot,
|
|
3510
|
+
normalizeEvaluationLibrary,
|
|
3142
3511
|
normalizeEvaluationRun,
|
|
3143
3512
|
normalizeEvaluationTrial,
|
|
3144
3513
|
portableEvaluationDataTypes,
|
|
3514
|
+
preserveEvaluationRunName,
|
|
3515
|
+
reconcileEvaluationRunSnapshots,
|
|
3145
3516
|
runEvaluationCases,
|
|
3146
3517
|
runEvaluationSuite,
|
|
3518
|
+
runEvaluationTrial,
|
|
3147
3519
|
runEvaluationWorkPool,
|
|
3148
3520
|
serializeEvaluationDatasetJson,
|
|
3149
3521
|
serializeEvaluationProjectData,
|