@valerypopoff/rivet2-evaluations 2.3.0 → 2.4.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 +572 -309
- package/dist/cjs/bundle.cjs.map +4 -4
- package/dist/esm/canonical.js +37 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/library.js +84 -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 +1 -0
- package/dist/types/library.d.ts +8 -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 +74 -2
- package/package.json +2 -2
package/dist/cjs/bundle.cjs
CHANGED
|
@@ -36,13 +36,17 @@ __export(index_exports, {
|
|
|
36
36
|
areEvaluationDataTypesCompatible: () => areEvaluationDataTypesCompatible,
|
|
37
37
|
assertEvaluationDatasetSnapshot: () => assertEvaluationDatasetSnapshot,
|
|
38
38
|
assertEvaluationDatasetValuesMatchDeclaredTypes: () => assertEvaluationDatasetValuesMatchDeclaredTypes,
|
|
39
|
+
assertEvaluationRecordingArtifact: () => assertEvaluationRecordingArtifact,
|
|
39
40
|
assertPortableJson: () => assertPortableJson,
|
|
40
41
|
canonicalStringify: () => canonicalStringify,
|
|
41
42
|
canonicalizePortableJson: () => canonicalizePortableJson,
|
|
42
43
|
clonePortableJson: () => clonePortableJson,
|
|
44
|
+
createEmptyEvaluationLibrary: () => createEmptyEvaluationLibrary,
|
|
43
45
|
createEmptyEvaluationProjectData: () => createEmptyEvaluationProjectData,
|
|
44
46
|
createEmptyMetrics: () => createEmptyMetrics,
|
|
45
47
|
createEvaluationBaselineSnapshot: () => createEvaluationBaselineSnapshot,
|
|
48
|
+
createEvaluationRunPlan: () => createEvaluationRunPlan,
|
|
49
|
+
createEvaluationRunShell: () => createEvaluationRunShell,
|
|
46
50
|
deserializeEvaluationDatasetJson: () => deserializeEvaluationDatasetJson,
|
|
47
51
|
deserializeEvaluationProjectData: () => deserializeEvaluationProjectData,
|
|
48
52
|
deserializeEvaluationProjectDataFromString: () => deserializeEvaluationProjectDataFromString,
|
|
@@ -50,6 +54,7 @@ __export(index_exports, {
|
|
|
50
54
|
evaluateAssertion: () => evaluateAssertion,
|
|
51
55
|
evaluateThresholdResults: () => evaluateThresholdResults,
|
|
52
56
|
finalizeEvaluationRecordingRetention: () => finalizeEvaluationRecordingRetention,
|
|
57
|
+
finalizeEvaluationRun: () => finalizeEvaluationRun,
|
|
53
58
|
fingerprint: () => fingerprint,
|
|
54
59
|
fingerprintEvaluationDataset: () => fingerprintEvaluationDataset,
|
|
55
60
|
getEvaluationSuiteMode: () => getEvaluationSuiteMode,
|
|
@@ -61,11 +66,15 @@ __export(index_exports, {
|
|
|
61
66
|
localizeEvaluationDataset: () => localizeEvaluationDataset,
|
|
62
67
|
normalizeEvaluationAggregate: () => normalizeEvaluationAggregate,
|
|
63
68
|
normalizeEvaluationBaselineSnapshot: () => normalizeEvaluationBaselineSnapshot,
|
|
69
|
+
normalizeEvaluationLibrary: () => normalizeEvaluationLibrary,
|
|
64
70
|
normalizeEvaluationRun: () => normalizeEvaluationRun,
|
|
65
71
|
normalizeEvaluationTrial: () => normalizeEvaluationTrial,
|
|
66
72
|
portableEvaluationDataTypes: () => portableEvaluationDataTypes,
|
|
73
|
+
preserveEvaluationRunName: () => preserveEvaluationRunName,
|
|
74
|
+
reconcileEvaluationRunSnapshots: () => reconcileEvaluationRunSnapshots,
|
|
67
75
|
runEvaluationCases: () => runEvaluationCases,
|
|
68
76
|
runEvaluationSuite: () => runEvaluationSuite,
|
|
77
|
+
runEvaluationTrial: () => runEvaluationTrial,
|
|
69
78
|
runEvaluationWorkPool: () => runEvaluationWorkPool,
|
|
70
79
|
serializeEvaluationDatasetJson: () => serializeEvaluationDatasetJson,
|
|
71
80
|
serializeEvaluationProjectData: () => serializeEvaluationProjectData,
|
|
@@ -178,13 +187,29 @@ function fingerprintEvaluationDataset(value) {
|
|
|
178
187
|
return fingerprint({ datasetId: value.id, fields: value.fields, cases: value.cases });
|
|
179
188
|
}
|
|
180
189
|
function assertEvaluationDatasetSnapshot(snapshot) {
|
|
181
|
-
if (snapshot.
|
|
190
|
+
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))) {
|
|
191
|
+
throw new Error("An evaluation dataset snapshot has invalid identity or creation time.");
|
|
192
|
+
}
|
|
193
|
+
if (snapshot.dataset.projectId !== snapshot.projectId) {
|
|
182
194
|
throw new Error("An evaluation dataset snapshot must belong to its project.");
|
|
183
195
|
}
|
|
184
196
|
if (fingerprintEvaluationDataset(snapshot.dataset) !== snapshot.fingerprint) {
|
|
185
197
|
throw new Error("An evaluation dataset snapshot fingerprint must match its dataset ID, fields, and cases.");
|
|
186
198
|
}
|
|
187
199
|
}
|
|
200
|
+
function assertEvaluationRecordingArtifact(value) {
|
|
201
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
202
|
+
throw new Error("An evaluation recording artifact must be an object.");
|
|
203
|
+
}
|
|
204
|
+
const artifact = value;
|
|
205
|
+
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))) {
|
|
206
|
+
throw new Error("An evaluation recording artifact has invalid identity, payload, or creation time.");
|
|
207
|
+
}
|
|
208
|
+
const reference = artifact.reference;
|
|
209
|
+
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)))) {
|
|
210
|
+
throw new Error("An evaluation recording artifact has an invalid retention reference.");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
188
213
|
|
|
189
214
|
// src/assertions.ts
|
|
190
215
|
function readQuotedPathKey(source, start) {
|
|
@@ -680,6 +705,12 @@ function isRunExecutionStatus(value) {
|
|
|
680
705
|
function isTerminalRunExecutionStatus(status) {
|
|
681
706
|
return status === "completed" || status === "canceled" || status === "error";
|
|
682
707
|
}
|
|
708
|
+
function reconcileRecordingReference(primary, secondary) {
|
|
709
|
+
if (!primary) return secondary;
|
|
710
|
+
if (!secondary) return primary;
|
|
711
|
+
const rank = (retention) => retention === "temporary" ? 0 : retention === "failure" ? 1 : 2;
|
|
712
|
+
return rank(secondary.retention) > rank(primary.retention) ? secondary : primary;
|
|
713
|
+
}
|
|
683
714
|
function shouldReplaceEvaluationRun(existing, incoming) {
|
|
684
715
|
if (!existing) return true;
|
|
685
716
|
const existingRevision = existing.revision ?? 0;
|
|
@@ -687,6 +718,64 @@ function shouldReplaceEvaluationRun(existing, incoming) {
|
|
|
687
718
|
if (incomingRevision !== existingRevision) return incomingRevision > existingRevision;
|
|
688
719
|
return !isTerminalRunExecutionStatus(existing.executionStatus) || isTerminalRunExecutionStatus(incoming.executionStatus);
|
|
689
720
|
}
|
|
721
|
+
function preserveEvaluationRunName(existing, incoming) {
|
|
722
|
+
return (existing == null ? void 0 : existing.name) !== void 0 && incoming.name === void 0 ? { ...incoming, name: existing.name } : incoming;
|
|
723
|
+
}
|
|
724
|
+
function reconcileEvaluationRunSnapshots(existing, incoming) {
|
|
725
|
+
if (!existing) return incoming;
|
|
726
|
+
const existingRevision = existing.revision ?? 0;
|
|
727
|
+
const incomingRevision = incoming.revision ?? 0;
|
|
728
|
+
if (incomingRevision < existingRevision) return existing;
|
|
729
|
+
if (incomingRevision > existingRevision) return preserveEvaluationRunName(existing, incoming);
|
|
730
|
+
if (!shouldReplaceEvaluationRun(existing, incoming)) return existing;
|
|
731
|
+
const existingTerminal = isTerminalRunExecutionStatus(existing.executionStatus);
|
|
732
|
+
const incomingTerminal = isTerminalRunExecutionStatus(incoming.executionStatus);
|
|
733
|
+
const existingTrials = existing.trials ?? [];
|
|
734
|
+
const incomingTrials = incoming.trials ?? [];
|
|
735
|
+
const incomingIsPrimary = incomingTerminal !== existingTerminal ? incomingTerminal : incomingTrials.length >= existingTrials.length;
|
|
736
|
+
const primary = preserveEvaluationRunName(existing, incomingIsPrimary ? incoming : existing);
|
|
737
|
+
const secondary = incomingIsPrimary ? existing : incoming;
|
|
738
|
+
const primaryTrials = primary.trials ?? [];
|
|
739
|
+
const secondaryTrialList = secondary.trials ?? [];
|
|
740
|
+
const name = existing.name ?? incoming.name;
|
|
741
|
+
const primaryTrialIds = new Set(primaryTrials.map((trial) => trial.id));
|
|
742
|
+
const secondaryTrials = new Map(secondaryTrialList.map((trial) => [trial.id, trial]));
|
|
743
|
+
return {
|
|
744
|
+
...primary,
|
|
745
|
+
trials: [
|
|
746
|
+
...primaryTrials.map((trial) => {
|
|
747
|
+
const other = secondaryTrials.get(trial.id);
|
|
748
|
+
if (!other) return trial;
|
|
749
|
+
const secondaryObservations = new Map(
|
|
750
|
+
(other.observations ?? []).map((observation) => [observation.id, observation])
|
|
751
|
+
);
|
|
752
|
+
const primaryObservationIds = new Set((trial.observations ?? []).map((observation) => observation.id));
|
|
753
|
+
const recording = reconcileRecordingReference(trial.recording, other.recording);
|
|
754
|
+
return {
|
|
755
|
+
...trial,
|
|
756
|
+
...recording === void 0 ? {} : { recording },
|
|
757
|
+
observations: [
|
|
758
|
+
...(trial.observations ?? []).map((observation) => {
|
|
759
|
+
const otherObservation = secondaryObservations.get(observation.id);
|
|
760
|
+
const observationRecording = reconcileRecordingReference(
|
|
761
|
+
observation.recording,
|
|
762
|
+
otherObservation == null ? void 0 : otherObservation.recording
|
|
763
|
+
);
|
|
764
|
+
return observationRecording === observation.recording ? observation : {
|
|
765
|
+
...observation,
|
|
766
|
+
...observationRecording === void 0 ? {} : { recording: observationRecording }
|
|
767
|
+
};
|
|
768
|
+
}),
|
|
769
|
+
...(other.observations ?? []).filter((observation) => !primaryObservationIds.has(observation.id))
|
|
770
|
+
]
|
|
771
|
+
};
|
|
772
|
+
}),
|
|
773
|
+
...secondaryTrialList.filter((trial) => !primaryTrialIds.has(trial.id))
|
|
774
|
+
],
|
|
775
|
+
...name === void 0 ? {} : { name },
|
|
776
|
+
warnings: [.../* @__PURE__ */ new Set([...primary.warnings ?? [], ...secondary.warnings ?? []])]
|
|
777
|
+
};
|
|
778
|
+
}
|
|
690
779
|
function isPurpose(value) {
|
|
691
780
|
return value === "evaluation" || value === "execution-benchmark";
|
|
692
781
|
}
|
|
@@ -797,6 +886,11 @@ function normalizeEvaluationAggregate(value, trials) {
|
|
|
797
886
|
if (!isRecord(value)) throw new Error("Evaluation aggregate must be an object.");
|
|
798
887
|
const normalized = structuredClone(value);
|
|
799
888
|
if (!isNormalizedScore(normalized.meanScore)) delete normalized.meanScore;
|
|
889
|
+
if (!isNormalizedScore(normalized.medianScore)) delete normalized.medianScore;
|
|
890
|
+
if (!isNormalizedScore(normalized.p95Score)) delete normalized.p95Score;
|
|
891
|
+
if (typeof normalized.medianLatencyMs !== "number" || !Number.isFinite(normalized.medianLatencyMs) || normalized.medianLatencyMs < 0) {
|
|
892
|
+
delete normalized.medianLatencyMs;
|
|
893
|
+
}
|
|
800
894
|
if (trials) {
|
|
801
895
|
const completed = trials.filter((trial) => trial.executionStatus === "completed");
|
|
802
896
|
const evaluated2 = completed.filter((trial) => trial.qualityStatus === "passed" || trial.qualityStatus === "failed");
|
|
@@ -899,6 +993,10 @@ function normalizeEvaluationRun(value) {
|
|
|
899
993
|
} : value.provenance;
|
|
900
994
|
const normalized = structuredClone(value);
|
|
901
995
|
delete normalized.verdict;
|
|
996
|
+
const name = typeof value.name === "string" && value.name.trim().length > 0 ? value.name.trim() : void 0;
|
|
997
|
+
delete normalized.name;
|
|
998
|
+
const requestedTrialCount = typeof value.requestedTrialCount === "number" && Number.isSafeInteger(value.requestedTrialCount) && value.requestedTrialCount >= 0 ? value.requestedTrialCount : void 0;
|
|
999
|
+
delete normalized.requestedTrialCount;
|
|
902
1000
|
const aggregate2 = value.aggregate === void 0 ? void 0 : normalizeEvaluationAggregate(value.aggregate, trials);
|
|
903
1001
|
return {
|
|
904
1002
|
...normalized,
|
|
@@ -918,6 +1016,8 @@ function normalizeEvaluationRun(value) {
|
|
|
918
1016
|
provenance,
|
|
919
1017
|
trials,
|
|
920
1018
|
thresholdResults: Array.isArray(value.thresholdResults) ? structuredClone(value.thresholdResults) : [],
|
|
1019
|
+
...name === void 0 ? {} : { name },
|
|
1020
|
+
...requestedTrialCount === void 0 ? {} : { requestedTrialCount },
|
|
921
1021
|
...aggregate2 === void 0 ? {} : { aggregate: accountingStatus === "partial" ? withoutAuthoritativeCost(aggregate2) : aggregate2 }
|
|
922
1022
|
};
|
|
923
1023
|
}
|
|
@@ -1036,7 +1136,7 @@ async function runEvaluationWorkPool(options) {
|
|
|
1036
1136
|
const work = options.work[index];
|
|
1037
1137
|
const result = await options.execute(work, index);
|
|
1038
1138
|
results[index] = result;
|
|
1039
|
-
(_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results);
|
|
1139
|
+
await ((_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results));
|
|
1040
1140
|
}
|
|
1041
1141
|
};
|
|
1042
1142
|
await Promise.all(Array.from({ length: Math.min(options.concurrency, options.work.length) }, worker));
|
|
@@ -1316,9 +1416,7 @@ function validateSuite(project, suite, dataset, purpose) {
|
|
|
1316
1416
|
for (const assertion of suite.assertions) {
|
|
1317
1417
|
const outputId = getEvaluationTopLevelOutputId(assertion.outputPath);
|
|
1318
1418
|
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
|
-
);
|
|
1419
|
+
throw new Error(`Assertion "${assertion.name}" output path must start with a named target Graph Output.`);
|
|
1322
1420
|
}
|
|
1323
1421
|
if (outputId !== void 0 && !graphOutputIds.includes(outputId)) {
|
|
1324
1422
|
throw new Error(`Assertion "${assertion.name}" references missing target output "${outputId}".`);
|
|
@@ -1697,9 +1795,9 @@ async function runWithTimeout(run, timeoutMs, outer) {
|
|
|
1697
1795
|
outer == null ? void 0 : outer.removeEventListener("abort", abort);
|
|
1698
1796
|
}
|
|
1699
1797
|
}
|
|
1700
|
-
async function
|
|
1798
|
+
async function runEvaluationTrial(input) {
|
|
1701
1799
|
var _a, _b, _c, _d;
|
|
1702
|
-
const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal } = input;
|
|
1800
|
+
const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal, trialId } = input;
|
|
1703
1801
|
const evaluationMode = getEvaluationSuiteMode(suite);
|
|
1704
1802
|
const { inputs, expected } = resolveCaseInputs(suite, dataset, testCase, purpose);
|
|
1705
1803
|
const seed = deriveSeed((_a = suite.configuration) == null ? void 0 : _a.seed, testCase.id, trialIndex);
|
|
@@ -1745,7 +1843,7 @@ async function runTrial(input) {
|
|
|
1745
1843
|
const executionStatus2 = "canceled";
|
|
1746
1844
|
const qualityStatus2 = "not-evaluated";
|
|
1747
1845
|
return {
|
|
1748
|
-
id: makeId("trial"),
|
|
1846
|
+
id: trialId ?? makeId("trial"),
|
|
1749
1847
|
caseId: testCase.id,
|
|
1750
1848
|
caseName: testCase.name,
|
|
1751
1849
|
caseIndex,
|
|
@@ -1873,7 +1971,7 @@ async function runTrial(input) {
|
|
|
1873
1971
|
const executionStatus2 = "canceled";
|
|
1874
1972
|
const qualityStatus2 = "not-evaluated";
|
|
1875
1973
|
return {
|
|
1876
|
-
id: makeId("trial"),
|
|
1974
|
+
id: trialId ?? makeId("trial"),
|
|
1877
1975
|
caseId: testCase.id,
|
|
1878
1976
|
caseName: testCase.name,
|
|
1879
1977
|
caseIndex,
|
|
@@ -1958,7 +2056,7 @@ async function runTrial(input) {
|
|
|
1958
2056
|
}
|
|
1959
2057
|
const totalMetrics = mergeMetrics(targetMetrics, evaluatorMetrics);
|
|
1960
2058
|
return {
|
|
1961
|
-
id: makeId("trial"),
|
|
2059
|
+
id: trialId ?? makeId("trial"),
|
|
1962
2060
|
caseId: testCase.id,
|
|
1963
2061
|
caseName: testCase.name,
|
|
1964
2062
|
caseIndex,
|
|
@@ -1984,6 +2082,13 @@ function percentile(values, percentage) {
|
|
|
1984
2082
|
const ordered = values.slice().sort((left, right) => left - right);
|
|
1985
2083
|
return ordered[Math.min(ordered.length - 1, Math.ceil(percentage / 100 * ordered.length) - 1)];
|
|
1986
2084
|
}
|
|
2085
|
+
function median(values) {
|
|
2086
|
+
if (values.length === 0) return 0;
|
|
2087
|
+
const ordered = values.slice().sort((left, right) => left - right);
|
|
2088
|
+
const upperIndex = Math.floor(ordered.length / 2);
|
|
2089
|
+
if (ordered.length % 2 !== 0) return ordered[upperIndex];
|
|
2090
|
+
return (ordered[upperIndex - 1] + ordered[upperIndex]) / 2;
|
|
2091
|
+
}
|
|
1987
2092
|
function meanObservationScore(observations) {
|
|
1988
2093
|
let weightedScore = 0;
|
|
1989
2094
|
let totalWeight = 0;
|
|
@@ -2058,6 +2163,7 @@ function aggregate(trials, evaluationMode = "pass-fail") {
|
|
|
2058
2163
|
const scoredTrialCount = cases.reduce((sum, testCase) => sum + (testCase.scoredTrialCount ?? 0), 0);
|
|
2059
2164
|
const scoringCaseMeans = cases.map((testCase) => testCase.meanScore).filter((score) => score !== void 0);
|
|
2060
2165
|
const scoringMeanScore = scoringCaseMeans.length === 0 ? void 0 : scoringCaseMeans.reduce((sum, score) => sum + score, 0) / scoringCaseMeans.length;
|
|
2166
|
+
const incurredLatencies = incurred.map((trial) => trial.totalMetrics.durationMs);
|
|
2061
2167
|
const aggregateValue = {
|
|
2062
2168
|
trialCount: trials.length,
|
|
2063
2169
|
evaluatedTrialCount: evaluated.length,
|
|
@@ -2071,13 +2177,15 @@ function aggregate(trials, evaluationMode = "pass-fail") {
|
|
|
2071
2177
|
...evaluationMode === "scoring" ? {
|
|
2072
2178
|
scoredTrialCount,
|
|
2073
2179
|
missingScoreTrialCount: trials.length - scoredTrialCount,
|
|
2074
|
-
...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore }
|
|
2180
|
+
...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore },
|
|
2181
|
+
...scoringCaseMeans.length === 0 ? {} : {
|
|
2182
|
+
medianScore: median(scoringCaseMeans),
|
|
2183
|
+
p95Score: percentile(scoringCaseMeans, 95)
|
|
2184
|
+
}
|
|
2075
2185
|
} : meanScore === void 0 ? {} : { meanScore },
|
|
2076
|
-
averageLatencyMs: incurred.length === 0 ? 0 :
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
95
|
|
2080
|
-
),
|
|
2186
|
+
averageLatencyMs: incurred.length === 0 ? 0 : incurredLatencies.reduce((sum, durationMs) => sum + durationMs, 0) / incurred.length,
|
|
2187
|
+
medianLatencyMs: median(incurredLatencies),
|
|
2188
|
+
p95LatencyMs: percentile(incurredLatencies, 95),
|
|
2081
2189
|
...incurred.length === 0 || totalCostUsd === void 0 ? {} : { totalCostUsd, averageCostUsd: totalCostUsd / incurred.length },
|
|
2082
2190
|
targetErrorRate: settled.length === 0 ? 0 : errors.length / settled.length,
|
|
2083
2191
|
evaluatorErrorRate: settled.length === 0 ? 0 : settled.filter(
|
|
@@ -2405,8 +2513,8 @@ function createProvenance(project, suite, dataset, options) {
|
|
|
2405
2513
|
accountingComplete: true
|
|
2406
2514
|
};
|
|
2407
2515
|
}
|
|
2408
|
-
|
|
2409
|
-
var _a
|
|
2516
|
+
function createEvaluationRunPlan(options) {
|
|
2517
|
+
var _a;
|
|
2410
2518
|
const suite = resolveSuite(options.evaluationData, options.suiteId);
|
|
2411
2519
|
const purpose = options.purpose ?? "evaluation";
|
|
2412
2520
|
const evaluationMode = getEvaluationSuiteMode(suite);
|
|
@@ -2416,6 +2524,110 @@ async function runEvaluationSuite(options) {
|
|
|
2416
2524
|
);
|
|
2417
2525
|
}
|
|
2418
2526
|
validateSuite(options.project, suite, options.dataset, purpose);
|
|
2527
|
+
const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
|
|
2528
|
+
const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
|
|
2529
|
+
(testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
|
|
2530
|
+
);
|
|
2531
|
+
return { suite, purpose, evaluationMode, work };
|
|
2532
|
+
}
|
|
2533
|
+
function finalizeEvaluationRun(options) {
|
|
2534
|
+
var _a, _b;
|
|
2535
|
+
const run = options.run;
|
|
2536
|
+
const evaluationMode = getEvaluationSuiteMode(options.suite);
|
|
2537
|
+
run.trials = options.trials.slice();
|
|
2538
|
+
run.completedAt = options.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2539
|
+
const outcome = aggregate(run.trials, evaluationMode);
|
|
2540
|
+
run.aggregate = outcome.aggregate;
|
|
2541
|
+
run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
|
|
2542
|
+
run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
|
|
2543
|
+
if (run.accountingStatus === "partial" && !run.warnings.includes("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.")) {
|
|
2544
|
+
run.warnings.push("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.");
|
|
2545
|
+
}
|
|
2546
|
+
if (options.canceled) {
|
|
2547
|
+
run.executionStatus = "canceled";
|
|
2548
|
+
run.qualityStatus = "not-evaluated";
|
|
2549
|
+
run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
|
|
2550
|
+
return run;
|
|
2551
|
+
}
|
|
2552
|
+
if (run.purpose === "execution-benchmark") {
|
|
2553
|
+
run.executionStatus = "completed";
|
|
2554
|
+
run.qualityStatus = "not-evaluated";
|
|
2555
|
+
run.qualityReason = { code: "benchmark", message: "This run measured execution without evaluating output quality." };
|
|
2556
|
+
return run;
|
|
2557
|
+
}
|
|
2558
|
+
if (evaluationMode === "scoring") {
|
|
2559
|
+
run.executionStatus = "completed";
|
|
2560
|
+
if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
|
|
2561
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2562
|
+
run.qualityReason = { code: "scores-incomplete", message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage." };
|
|
2563
|
+
run.warnings.push(`Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`);
|
|
2564
|
+
} else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
|
|
2565
|
+
run.qualityStatus = "scored";
|
|
2566
|
+
run.qualityReason = { code: "scores-complete", message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages." };
|
|
2567
|
+
} else {
|
|
2568
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2569
|
+
run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
|
|
2570
|
+
}
|
|
2571
|
+
return run;
|
|
2572
|
+
}
|
|
2573
|
+
const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === options.suite.id);
|
|
2574
|
+
const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
|
|
2575
|
+
if (baseline && !usableBaseline && !run.warnings.includes("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.")) {
|
|
2576
|
+
run.warnings.push("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.");
|
|
2577
|
+
}
|
|
2578
|
+
run.thresholdResults = evaluateThresholdResults(outcome.aggregate, options.suite.thresholds, usableBaseline);
|
|
2579
|
+
const hasPassRateThreshold = ((_a = options.suite.thresholds) == null ? void 0 : _a.some((threshold) => threshold.metric === "pass-rate")) ?? false;
|
|
2580
|
+
const hasTargetErrorRateThreshold = ((_b = options.suite.thresholds) == null ? void 0 : _b.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
|
|
2581
|
+
const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
|
|
2582
|
+
const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
|
|
2583
|
+
const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
|
|
2584
|
+
const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
|
|
2585
|
+
const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
|
|
2586
|
+
run.executionStatus = "completed";
|
|
2587
|
+
if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
|
|
2588
|
+
run.qualityStatus = "failed";
|
|
2589
|
+
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." };
|
|
2590
|
+
} else if (hasUnavailableEvidence) {
|
|
2591
|
+
run.qualityStatus = "unable-to-evaluate";
|
|
2592
|
+
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." };
|
|
2593
|
+
} else if (hasAuthoritativePass) {
|
|
2594
|
+
run.qualityStatus = "passed";
|
|
2595
|
+
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." };
|
|
2596
|
+
} else {
|
|
2597
|
+
run.qualityStatus = "not-evaluated";
|
|
2598
|
+
run.qualityReason = { code: "no-completed-trials", message: "No completed trial produced an authoritative quality result." };
|
|
2599
|
+
}
|
|
2600
|
+
return run;
|
|
2601
|
+
}
|
|
2602
|
+
function createEvaluationRunShell(options) {
|
|
2603
|
+
const plan = createEvaluationRunPlan(options);
|
|
2604
|
+
const runId = options.runId ?? makeId("evaluation");
|
|
2605
|
+
const run = {
|
|
2606
|
+
version: 2,
|
|
2607
|
+
id: runId,
|
|
2608
|
+
projectId: options.project.metadata.id,
|
|
2609
|
+
suiteId: plan.suite.id,
|
|
2610
|
+
suiteName: plan.suite.name,
|
|
2611
|
+
revision: 0,
|
|
2612
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2613
|
+
purpose: plan.purpose,
|
|
2614
|
+
evaluationMode: plan.evaluationMode,
|
|
2615
|
+
executionStatus: "running",
|
|
2616
|
+
qualityStatus: "not-evaluated",
|
|
2617
|
+
qualityReason: { code: "in-progress", message: "The run is still in progress." },
|
|
2618
|
+
accountingStatus: "complete",
|
|
2619
|
+
provenance: createProvenance(options.project, plan.suite, options.dataset, options),
|
|
2620
|
+
trials: [],
|
|
2621
|
+
thresholdResults: [],
|
|
2622
|
+
warnings: [],
|
|
2623
|
+
requestedTrialCount: plan.work.length
|
|
2624
|
+
};
|
|
2625
|
+
return { run, plan };
|
|
2626
|
+
}
|
|
2627
|
+
async function runEvaluationSuite(options) {
|
|
2628
|
+
var _a, _b;
|
|
2629
|
+
const plan = createEvaluationRunPlan(options);
|
|
2630
|
+
const { suite, purpose, evaluationMode } = plan;
|
|
2419
2631
|
const runId = options.runId ?? makeId("evaluation");
|
|
2420
2632
|
const run = {
|
|
2421
2633
|
version: 2,
|
|
@@ -2436,27 +2648,61 @@ async function runEvaluationSuite(options) {
|
|
|
2436
2648
|
thresholdResults: [],
|
|
2437
2649
|
warnings: []
|
|
2438
2650
|
};
|
|
2439
|
-
const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
|
|
2440
2651
|
const concurrency = Math.min(
|
|
2441
2652
|
MAX_CONCURRENCY,
|
|
2442
|
-
Math.max(1, Math.floor(((
|
|
2653
|
+
Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.concurrency) ?? DEFAULT_CONCURRENCY))
|
|
2443
2654
|
);
|
|
2444
|
-
const work =
|
|
2445
|
-
|
|
2446
|
-
);
|
|
2447
|
-
|
|
2448
|
-
|
|
2655
|
+
const work = plan.work;
|
|
2656
|
+
run.requestedTrialCount = work.length;
|
|
2657
|
+
const settledTrials = Array.from({ length: work.length });
|
|
2658
|
+
let settledTrialCount = 0;
|
|
2659
|
+
const nextRevision = () => {
|
|
2449
2660
|
run.revision = (run.revision ?? 0) + 1;
|
|
2450
|
-
|
|
2661
|
+
return run.revision;
|
|
2662
|
+
};
|
|
2663
|
+
const publishLegacySnapshot = () => {
|
|
2664
|
+
if (!options.onUpdate) return;
|
|
2665
|
+
run.trials = settledTrials.filter((trial) => trial !== void 0);
|
|
2666
|
+
options.onUpdate(structuredClone(run));
|
|
2451
2667
|
};
|
|
2452
|
-
|
|
2668
|
+
const publishStarted = async () => {
|
|
2669
|
+
const revision = nextRevision();
|
|
2670
|
+
if (options.onEvent) {
|
|
2671
|
+
const shell = structuredClone(run);
|
|
2672
|
+
shell.trials = [];
|
|
2673
|
+
await options.onEvent({ type: "run-started", revision, run: shell });
|
|
2674
|
+
}
|
|
2675
|
+
publishLegacySnapshot();
|
|
2676
|
+
};
|
|
2677
|
+
const publishSettledTrial = async (trial) => {
|
|
2678
|
+
var _a2;
|
|
2679
|
+
const revision = nextRevision();
|
|
2680
|
+
await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, {
|
|
2681
|
+
type: "trial-settled",
|
|
2682
|
+
revision,
|
|
2683
|
+
runId: run.id,
|
|
2684
|
+
projectId: run.projectId,
|
|
2685
|
+
suiteId: run.suiteId,
|
|
2686
|
+
requestedTrialCount: run.requestedTrialCount ?? work.length,
|
|
2687
|
+
settledTrialCount,
|
|
2688
|
+
trial: structuredClone(trial)
|
|
2689
|
+
}));
|
|
2690
|
+
publishLegacySnapshot();
|
|
2691
|
+
};
|
|
2692
|
+
const publishFinalized = async () => {
|
|
2693
|
+
var _a2;
|
|
2694
|
+
const revision = nextRevision();
|
|
2695
|
+
await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type: "run-finalized", revision, run: structuredClone(run) }));
|
|
2696
|
+
publishLegacySnapshot();
|
|
2697
|
+
};
|
|
2698
|
+
await publishStarted();
|
|
2453
2699
|
const workPool = runEvaluationWorkPool({
|
|
2454
2700
|
work,
|
|
2455
2701
|
concurrency,
|
|
2456
2702
|
signal: options.signal,
|
|
2457
2703
|
execute: async (task) => {
|
|
2458
2704
|
try {
|
|
2459
|
-
return await
|
|
2705
|
+
return await runEvaluationTrial({
|
|
2460
2706
|
...task,
|
|
2461
2707
|
project: options.project,
|
|
2462
2708
|
suite,
|
|
@@ -2489,98 +2735,22 @@ async function runEvaluationSuite(options) {
|
|
|
2489
2735
|
};
|
|
2490
2736
|
}
|
|
2491
2737
|
},
|
|
2492
|
-
onSettled: (
|
|
2493
|
-
|
|
2494
|
-
|
|
2738
|
+
onSettled: async (result, index) => {
|
|
2739
|
+
settledTrials[index] = result;
|
|
2740
|
+
settledTrialCount += 1;
|
|
2741
|
+
await publishSettledTrial(result);
|
|
2495
2742
|
}
|
|
2496
2743
|
});
|
|
2497
2744
|
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();
|
|
2745
|
+
finalizeEvaluationRun({
|
|
2746
|
+
run,
|
|
2747
|
+
trials: results.filter((result) => result !== void 0),
|
|
2748
|
+
suite,
|
|
2749
|
+
evaluationData: options.evaluationData,
|
|
2750
|
+
baseline: options.baseline,
|
|
2751
|
+
canceled: ((_b = options.signal) == null ? void 0 : _b.aborted) === true
|
|
2752
|
+
});
|
|
2753
|
+
await publishFinalized();
|
|
2584
2754
|
return run;
|
|
2585
2755
|
}
|
|
2586
2756
|
async function runEvaluationCases(options) {
|
|
@@ -2619,93 +2789,131 @@ function createEvaluationBaselineSnapshot(run) {
|
|
|
2619
2789
|
};
|
|
2620
2790
|
}
|
|
2621
2791
|
|
|
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);
|
|
2792
|
+
// src/datasetTransfer.ts
|
|
2793
|
+
var DATASET_EXPORT_VERSION = 1;
|
|
2794
|
+
function isRecord2(value) {
|
|
2795
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2796
|
+
}
|
|
2797
|
+
function requireString(value, path) {
|
|
2798
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
|
|
2799
|
+
return value;
|
|
2800
|
+
}
|
|
2801
|
+
function requireText(value, path) {
|
|
2802
|
+
if (typeof value !== "string") throw new Error(`${path} must be a string.`);
|
|
2803
|
+
return value;
|
|
2804
|
+
}
|
|
2805
|
+
function parseFields(value) {
|
|
2806
|
+
if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
|
|
2807
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2808
|
+
return value.map((field, index) => {
|
|
2809
|
+
if (!isRecord2(field)) throw new Error(`fields[${index}] must be an object.`);
|
|
2810
|
+
const id = requireString(field.id, `fields[${index}].id`);
|
|
2811
|
+
if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
|
|
2812
|
+
ids.add(id);
|
|
2813
|
+
const role = field.role;
|
|
2814
|
+
if (role !== "input" && role !== "expected" && role !== "metadata") {
|
|
2815
|
+
throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
|
|
2647
2816
|
}
|
|
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.");
|
|
2817
|
+
if (field.description !== void 0 && typeof field.description !== "string") {
|
|
2818
|
+
throw new Error(`fields[${index}].description must be a string.`);
|
|
2664
2819
|
}
|
|
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;
|
|
2820
|
+
if (field.required !== void 0 && typeof field.required !== "boolean") {
|
|
2821
|
+
throw new Error(`fields[${index}].required must be a boolean.`);
|
|
2675
2822
|
}
|
|
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 }
|
|
2823
|
+
return {
|
|
2824
|
+
id,
|
|
2825
|
+
name: requireText(field.name, `fields[${index}].name`),
|
|
2826
|
+
dataType: requireString(field.dataType, `fields[${index}].dataType`),
|
|
2827
|
+
role,
|
|
2828
|
+
...typeof field.description === "string" ? { description: field.description } : {},
|
|
2829
|
+
...typeof field.required === "boolean" ? { required: field.required } : {}
|
|
2686
2830
|
};
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
function parseCases(value, fieldIds) {
|
|
2834
|
+
if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
|
|
2835
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2836
|
+
return value.map((testCase, index) => {
|
|
2837
|
+
if (!isRecord2(testCase)) throw new Error(`cases[${index}] must be an object.`);
|
|
2838
|
+
const id = requireString(testCase.id, `cases[${index}].id`);
|
|
2839
|
+
if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
|
|
2840
|
+
ids.add(id);
|
|
2841
|
+
if (!isRecord2(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
|
|
2842
|
+
for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
|
|
2843
|
+
if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
|
|
2844
|
+
assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
|
|
2845
|
+
}
|
|
2846
|
+
if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
|
|
2847
|
+
throw new Error(`cases[${index}].tags must be an array of strings.`);
|
|
2692
2848
|
}
|
|
2849
|
+
if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
|
|
2850
|
+
if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
|
|
2851
|
+
return {
|
|
2852
|
+
id,
|
|
2853
|
+
name: requireText(testCase.name, `cases[${index}].name`),
|
|
2854
|
+
values: testCase.values,
|
|
2855
|
+
...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
|
|
2856
|
+
...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
|
|
2857
|
+
...typeof testCase.note === "string" ? { note: testCase.note } : {}
|
|
2858
|
+
};
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
function validateEvaluationDataset(value, scope) {
|
|
2862
|
+
if (!isRecord2(value)) throw new Error("Evaluation dataset must be an object.");
|
|
2863
|
+
const fields = parseFields(value.fields);
|
|
2864
|
+
const importedProjectId = value.projectId === void 0 ? void 0 : requireString(value.projectId, "dataset.projectId");
|
|
2865
|
+
const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
|
|
2866
|
+
const dataset = {
|
|
2867
|
+
id: (scope == null ? void 0 : scope.id) ?? requireString(value.id, "dataset.id"),
|
|
2868
|
+
name: requireText(value.name, "dataset.name"),
|
|
2869
|
+
...projectId === void 0 ? {} : { projectId },
|
|
2870
|
+
fields,
|
|
2871
|
+
cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
|
|
2872
|
+
};
|
|
2873
|
+
return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
|
|
2874
|
+
}
|
|
2875
|
+
function localizeEvaluationDataset(value, id) {
|
|
2876
|
+
const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
|
|
2877
|
+
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
2878
|
+
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
2879
|
+
}
|
|
2880
|
+
function validateEvaluationDatasetForTransfer(value, scope) {
|
|
2881
|
+
const dataset = validateEvaluationDataset(value, scope);
|
|
2882
|
+
assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
|
|
2883
|
+
return dataset;
|
|
2884
|
+
}
|
|
2885
|
+
function serializeEvaluationDatasetJson(dataset) {
|
|
2886
|
+
const validated = validateEvaluationDatasetForTransfer(dataset);
|
|
2887
|
+
return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
|
|
2888
|
+
}
|
|
2889
|
+
function deserializeEvaluationDatasetJson(source, scope) {
|
|
2890
|
+
let parsed;
|
|
2891
|
+
try {
|
|
2892
|
+
parsed = JSON.parse(source);
|
|
2893
|
+
} catch {
|
|
2894
|
+
throw new Error("Evaluation dataset JSON is not valid JSON.");
|
|
2693
2895
|
}
|
|
2694
|
-
|
|
2896
|
+
if (!isRecord2(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord2(parsed.dataset)) {
|
|
2897
|
+
throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
|
|
2898
|
+
}
|
|
2899
|
+
const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
|
|
2900
|
+
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
2901
|
+
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
2902
|
+
}
|
|
2695
2903
|
|
|
2696
2904
|
// src/serialization.ts
|
|
2697
2905
|
var yaml = __toESM(require("yaml"), 1);
|
|
2698
|
-
function
|
|
2906
|
+
function isRecord3(value) {
|
|
2699
2907
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2700
2908
|
}
|
|
2701
2909
|
function requireRecord(value, path) {
|
|
2702
|
-
if (!
|
|
2910
|
+
if (!isRecord3(value)) throw new Error(`${path} must be an object.`);
|
|
2703
2911
|
return value;
|
|
2704
2912
|
}
|
|
2705
|
-
function
|
|
2913
|
+
function requireString2(value, path) {
|
|
2706
2914
|
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path} must be a non-empty string.`);
|
|
2707
2915
|
}
|
|
2708
|
-
function
|
|
2916
|
+
function requireText2(value, path) {
|
|
2709
2917
|
if (typeof value !== "string") throw new Error(`${path} must be a string.`);
|
|
2710
2918
|
}
|
|
2711
2919
|
function requireBoolean(value, path) {
|
|
@@ -2747,21 +2955,21 @@ var EVALUATOR_CONTEXT_INPUTS = /* @__PURE__ */ new Set(["case", "inputs", "expec
|
|
|
2747
2955
|
function validateSuiteShape(value, index) {
|
|
2748
2956
|
const path = `evaluations.suites[${index}]`;
|
|
2749
2957
|
const suite = requireRecord(value, path);
|
|
2750
|
-
for (const key of ["id", "targetGraphId", "datasetId"])
|
|
2751
|
-
|
|
2958
|
+
for (const key of ["id", "targetGraphId", "datasetId"]) requireString2(suite[key], `${path}.${key}`);
|
|
2959
|
+
requireText2(suite.name, `${path}.name`);
|
|
2752
2960
|
if (suite.evaluationMode !== void 0 && suite.evaluationMode !== "pass-fail" && suite.evaluationMode !== "scoring") {
|
|
2753
2961
|
throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
|
|
2754
2962
|
}
|
|
2755
2963
|
for (const [bindingIndex, bindingValue] of requireArray(suite.inputBindings, `${path}.inputBindings`).entries()) {
|
|
2756
2964
|
const binding = requireRecord(bindingValue, `${path}.inputBindings[${bindingIndex}]`);
|
|
2757
|
-
|
|
2758
|
-
|
|
2965
|
+
requireString2(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
|
|
2966
|
+
requireString2(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
|
|
2759
2967
|
}
|
|
2760
2968
|
for (const [assertionIndex, assertionValue] of requireArray(suite.assertions, `${path}.assertions`).entries()) {
|
|
2761
2969
|
const assertionPath = `${path}.assertions[${assertionIndex}]`;
|
|
2762
2970
|
const assertion = requireRecord(assertionValue, assertionPath);
|
|
2763
|
-
for (const key of ["id", "outputPath"])
|
|
2764
|
-
|
|
2971
|
+
for (const key of ["id", "outputPath"]) requireString2(assertion[key], `${assertionPath}.${key}`);
|
|
2972
|
+
requireText2(assertion.name, `${assertionPath}.name`);
|
|
2765
2973
|
if (typeof assertion.operator !== "string" || !ASSERTION_OPERATORS.has(assertion.operator)) {
|
|
2766
2974
|
throw new Error(`${assertionPath}.operator is not supported.`);
|
|
2767
2975
|
}
|
|
@@ -2770,7 +2978,7 @@ function validateSuiteShape(value, index) {
|
|
|
2770
2978
|
if (expected.kind === "literal") {
|
|
2771
2979
|
if (!Object.hasOwn(expected, "value")) throw new Error(`${assertionPath}.expected.value is required.`);
|
|
2772
2980
|
} else if (expected.kind === "dataset-field") {
|
|
2773
|
-
|
|
2981
|
+
requireString2(expected.fieldId, `${assertionPath}.expected.fieldId`);
|
|
2774
2982
|
} else {
|
|
2775
2983
|
throw new Error(`${assertionPath}.expected.kind must be "literal" or "dataset-field".`);
|
|
2776
2984
|
}
|
|
@@ -2778,8 +2986,8 @@ function validateSuiteShape(value, index) {
|
|
|
2778
2986
|
for (const [evaluatorIndex, evaluatorValue] of requireArray(suite.evaluators, `${path}.evaluators`).entries()) {
|
|
2779
2987
|
const evaluatorPath = `${path}.evaluators[${evaluatorIndex}]`;
|
|
2780
2988
|
const evaluator = requireRecord(evaluatorValue, evaluatorPath);
|
|
2781
|
-
for (const key of ["id", "graphId"])
|
|
2782
|
-
|
|
2989
|
+
for (const key of ["id", "graphId"]) requireString2(evaluator[key], `${evaluatorPath}.${key}`);
|
|
2990
|
+
requireText2(evaluator.name, `${evaluatorPath}.name`);
|
|
2783
2991
|
validateOptionalPrimitive(evaluator, "required", "boolean", evaluatorPath);
|
|
2784
2992
|
validateOptionalPrimitive(evaluator, "runOnTargetError", "boolean", evaluatorPath);
|
|
2785
2993
|
validateOptionalPrimitive(evaluator, "scoreWeight", "number", evaluatorPath);
|
|
@@ -2790,10 +2998,10 @@ function validateSuiteShape(value, index) {
|
|
|
2790
2998
|
).entries()) {
|
|
2791
2999
|
const bindingPath = `${evaluatorPath}.inputBindings[${bindingIndex}]`;
|
|
2792
3000
|
const binding = requireRecord(bindingValue, bindingPath);
|
|
2793
|
-
|
|
3001
|
+
requireString2(binding.graphInputId, `${bindingPath}.graphInputId`);
|
|
2794
3002
|
const source = requireRecord(binding.source, `${bindingPath}.source`);
|
|
2795
|
-
if (source.kind === "dataset-field")
|
|
2796
|
-
else if (source.kind === "target-output")
|
|
3003
|
+
if (source.kind === "dataset-field") requireString2(source.fieldId, `${bindingPath}.source.fieldId`);
|
|
3004
|
+
else if (source.kind === "target-output") requireString2(source.outputId, `${bindingPath}.source.outputId`);
|
|
2797
3005
|
else if (source.kind === "context") {
|
|
2798
3006
|
if (typeof source.context !== "string" || !EVALUATOR_CONTEXT_INPUTS.has(source.context)) {
|
|
2799
3007
|
throw new Error(`${bindingPath}.source.context is not supported.`);
|
|
@@ -2808,7 +3016,7 @@ function validateSuiteShape(value, index) {
|
|
|
2808
3016
|
for (const [thresholdIndex, thresholdValue2] of requireArray(suite.thresholds, `${path}.thresholds`).entries()) {
|
|
2809
3017
|
const thresholdPath = `${path}.thresholds[${thresholdIndex}]`;
|
|
2810
3018
|
const threshold = requireRecord(thresholdValue2, thresholdPath);
|
|
2811
|
-
for (const key of ["id", "metric", "operator"])
|
|
3019
|
+
for (const key of ["id", "metric", "operator"]) requireString2(threshold[key], `${thresholdPath}.${key}`);
|
|
2812
3020
|
requireFiniteNumber(threshold.value, `${thresholdPath}.value`);
|
|
2813
3021
|
}
|
|
2814
3022
|
}
|
|
@@ -2823,7 +3031,7 @@ function validateSuiteShape(value, index) {
|
|
|
2823
3031
|
validateOptionalPrimitive(suite, "description", "string", path);
|
|
2824
3032
|
if (suite.tags !== void 0) {
|
|
2825
3033
|
for (const [tagIndex, tag] of requireArray(suite.tags, `${path}.tags`).entries()) {
|
|
2826
|
-
|
|
3034
|
+
requireString2(tag, `${path}.tags[${tagIndex}]`);
|
|
2827
3035
|
}
|
|
2828
3036
|
}
|
|
2829
3037
|
}
|
|
@@ -2834,7 +3042,7 @@ function validateNumericRecord(value, path) {
|
|
|
2834
3042
|
function validateBaselineShape(value, index) {
|
|
2835
3043
|
const path = `evaluations.baselines[${index}]`;
|
|
2836
3044
|
const baseline = requireRecord(value, path);
|
|
2837
|
-
for (const key of ["id", "suiteId", "createdAt"])
|
|
3045
|
+
for (const key of ["id", "suiteId", "createdAt"]) requireString2(baseline[key], `${path}.${key}`);
|
|
2838
3046
|
validateOptionalPrimitive(baseline, "sourceRunId", "string", path);
|
|
2839
3047
|
if (baseline.evaluationMode !== void 0 && baseline.evaluationMode !== "pass-fail" && baseline.evaluationMode !== "scoring") {
|
|
2840
3048
|
throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
|
|
@@ -2847,14 +3055,14 @@ function validateBaselineShape(value, index) {
|
|
|
2847
3055
|
"targetFingerprint",
|
|
2848
3056
|
"executionMode"
|
|
2849
3057
|
]) {
|
|
2850
|
-
|
|
3058
|
+
requireString2(provenance[key], `${path}.provenance.${key}`);
|
|
2851
3059
|
}
|
|
2852
3060
|
const evaluatorFingerprints = requireRecord(
|
|
2853
3061
|
provenance.evaluatorFingerprints,
|
|
2854
3062
|
`${path}.provenance.evaluatorFingerprints`
|
|
2855
3063
|
);
|
|
2856
3064
|
for (const [key, fingerprint2] of Object.entries(evaluatorFingerprints)) {
|
|
2857
|
-
|
|
3065
|
+
requireString2(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
|
|
2858
3066
|
}
|
|
2859
3067
|
requireBoolean(provenance.accountingComplete, `${path}.provenance.accountingComplete`);
|
|
2860
3068
|
const aggregate2 = requireRecord(baseline.aggregate, `${path}.aggregate`);
|
|
@@ -2878,6 +3086,9 @@ function validateBaselineShape(value, index) {
|
|
|
2878
3086
|
"notEvaluatedTrialCount",
|
|
2879
3087
|
"unableToEvaluateTrialCount",
|
|
2880
3088
|
"meanScore",
|
|
3089
|
+
"medianScore",
|
|
3090
|
+
"p95Score",
|
|
3091
|
+
"medianLatencyMs",
|
|
2881
3092
|
"totalCostUsd",
|
|
2882
3093
|
"averageCostUsd",
|
|
2883
3094
|
"scoredTrialCount",
|
|
@@ -2889,8 +3100,8 @@ function validateBaselineShape(value, index) {
|
|
|
2889
3100
|
for (const [caseIndex, caseValue] of requireArray(baseline.cases, `${path}.cases`).entries()) {
|
|
2890
3101
|
const casePath = `${path}.cases[${caseIndex}]`;
|
|
2891
3102
|
const caseAggregate = requireRecord(caseValue, casePath);
|
|
2892
|
-
|
|
2893
|
-
|
|
3103
|
+
requireString2(caseAggregate.caseId, `${casePath}.caseId`);
|
|
3104
|
+
requireString2(caseAggregate.caseName, `${casePath}.caseName`);
|
|
2894
3105
|
validateNumericRecord(caseAggregate.metrics, `${casePath}.metrics`);
|
|
2895
3106
|
for (const key of [
|
|
2896
3107
|
"passRate",
|
|
@@ -2944,117 +3155,160 @@ function deserializeEvaluationProjectDataFromString(data) {
|
|
|
2944
3155
|
return deserializeEvaluationProjectData(yaml.parse(data));
|
|
2945
3156
|
}
|
|
2946
3157
|
|
|
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;
|
|
3158
|
+
// src/library.ts
|
|
3159
|
+
function createEmptyEvaluationLibrary() {
|
|
3160
|
+
return {
|
|
3161
|
+
version: 1,
|
|
3162
|
+
data: createEmptyEvaluationProjectData(),
|
|
3163
|
+
datasets: [],
|
|
3164
|
+
migratedLegacyProjectIds: []
|
|
3165
|
+
};
|
|
2959
3166
|
}
|
|
2960
|
-
function
|
|
2961
|
-
if (
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
|
|
3167
|
+
function normalizeEvaluationLibrary(value) {
|
|
3168
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return createEmptyEvaluationLibrary();
|
|
3169
|
+
const candidate = value;
|
|
3170
|
+
let data = createEmptyEvaluationProjectData();
|
|
3171
|
+
try {
|
|
3172
|
+
data = deserializeEvaluationProjectData(candidate.data);
|
|
3173
|
+
} catch {
|
|
3174
|
+
const dataCandidate = typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data) ? candidate.data : void 0;
|
|
3175
|
+
const suites2 = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.suites) ? dataCandidate.suites.flatMap((suite) => {
|
|
3176
|
+
try {
|
|
3177
|
+
return [deserializeEvaluationProjectData({ version: 1, suites: [suite], baselines: [] }).suites[0]];
|
|
3178
|
+
} catch {
|
|
3179
|
+
return [];
|
|
3180
|
+
}
|
|
3181
|
+
}) : [];
|
|
3182
|
+
const baselines = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.baselines) ? dataCandidate.baselines.flatMap((baseline) => {
|
|
3183
|
+
try {
|
|
3184
|
+
return [deserializeEvaluationProjectData({ version: 1, suites: [], baselines: [baseline] }).baselines[0]];
|
|
3185
|
+
} catch {
|
|
3186
|
+
return [];
|
|
3187
|
+
}
|
|
3188
|
+
}) : [];
|
|
3189
|
+
const suiteIds2 = new Set(suites2.map((suite) => suite.id));
|
|
3190
|
+
data = { version: 1, suites: suites2, baselines: baselines.filter((baseline) => suiteIds2.has(baseline.suiteId)) };
|
|
3191
|
+
}
|
|
3192
|
+
const datasets = Array.isArray(candidate.datasets) ? candidate.datasets.flatMap((dataset) => {
|
|
3193
|
+
try {
|
|
3194
|
+
return [localizeEvaluationDataset(dataset)];
|
|
3195
|
+
} catch {
|
|
3196
|
+
return [];
|
|
2977
3197
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
...
|
|
2985
|
-
|
|
3198
|
+
}) : [];
|
|
3199
|
+
const suites = uniqueById(data.suites);
|
|
3200
|
+
const suiteIds = new Set(suites.map((suite) => suite.id));
|
|
3201
|
+
return {
|
|
3202
|
+
version: 1,
|
|
3203
|
+
data: {
|
|
3204
|
+
...data,
|
|
3205
|
+
suites,
|
|
3206
|
+
baselines: uniqueById(data.baselines).filter((baseline) => suiteIds.has(baseline.suiteId))
|
|
3207
|
+
},
|
|
3208
|
+
datasets: uniqueById(datasets),
|
|
3209
|
+
migratedLegacyProjectIds: Array.isArray(candidate.migratedLegacyProjectIds) ? Array.from(new Set(candidate.migratedLegacyProjectIds.filter((id) => typeof id === "string"))) : []
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
function uniqueById(values) {
|
|
3213
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3214
|
+
return values.filter((value) => {
|
|
3215
|
+
if (seen.has(value.id)) return false;
|
|
3216
|
+
seen.add(value.id);
|
|
3217
|
+
return true;
|
|
2986
3218
|
});
|
|
2987
3219
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3220
|
+
|
|
3221
|
+
// src/store.ts
|
|
3222
|
+
var InMemoryEvaluationRunStore = class {
|
|
3223
|
+
#runs = /* @__PURE__ */ new Map();
|
|
3224
|
+
#datasetSnapshots = /* @__PURE__ */ new Map();
|
|
3225
|
+
#recordings = /* @__PURE__ */ new Map();
|
|
3226
|
+
#library = createEmptyEvaluationLibrary();
|
|
3227
|
+
async getLibrary() {
|
|
3228
|
+
return structuredClone(this.#library);
|
|
3229
|
+
}
|
|
3230
|
+
async putLibrary(library) {
|
|
3231
|
+
this.#library = structuredClone(normalizeEvaluationLibrary(library));
|
|
3232
|
+
}
|
|
3233
|
+
async put(run) {
|
|
3234
|
+
const normalized = normalizeEvaluationRun(run);
|
|
3235
|
+
const key = `${normalized.projectId}/${normalized.id}`;
|
|
3236
|
+
const existing = this.#runs.get(key);
|
|
3237
|
+
const next = reconcileEvaluationRunSnapshots(existing, normalized);
|
|
3238
|
+
if (next === existing) return;
|
|
3239
|
+
this.#runs.set(key, structuredClone(next));
|
|
3240
|
+
}
|
|
3241
|
+
async updateRunName(input) {
|
|
3242
|
+
const key = `${input.projectId}/${input.runId}`;
|
|
3243
|
+
const existing = this.#runs.get(key);
|
|
3244
|
+
if (!existing) return void 0;
|
|
3245
|
+
const renamed = normalizeEvaluationRun({ ...existing, name: input.name });
|
|
3246
|
+
this.#runs.set(key, structuredClone(renamed));
|
|
3247
|
+
return structuredClone(renamed);
|
|
3248
|
+
}
|
|
3249
|
+
async get(input) {
|
|
3250
|
+
const run = this.#runs.get(`${input.projectId}/${input.runId}`);
|
|
3251
|
+
return run ? normalizeEvaluationRun(run) : void 0;
|
|
3252
|
+
}
|
|
3253
|
+
async list(input) {
|
|
3254
|
+
return Array.from(this.#runs.values()).filter(
|
|
3255
|
+
(run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
|
|
3256
|
+
).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
|
|
3257
|
+
}
|
|
3258
|
+
async delete(input) {
|
|
3259
|
+
this.#runs.delete(`${input.projectId}/${input.runId}`);
|
|
3260
|
+
for (const [key, artifact] of this.#recordings) {
|
|
3261
|
+
if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
|
|
3000
3262
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3263
|
+
}
|
|
3264
|
+
async putDatasetSnapshot(snapshot) {
|
|
3265
|
+
assertEvaluationDatasetSnapshot(snapshot);
|
|
3266
|
+
const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
|
|
3267
|
+
if (this.#datasetSnapshots.has(key)) return;
|
|
3268
|
+
this.#datasetSnapshots.set(key, structuredClone(snapshot));
|
|
3269
|
+
}
|
|
3270
|
+
async getDatasetSnapshot(input) {
|
|
3271
|
+
const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
|
|
3272
|
+
return snapshot ? structuredClone(snapshot) : void 0;
|
|
3273
|
+
}
|
|
3274
|
+
async putRecording(artifact) {
|
|
3275
|
+
assertEvaluationRecordingArtifact(artifact);
|
|
3276
|
+
const key = `${artifact.projectId}/${artifact.reference.id}`;
|
|
3277
|
+
const existing = this.#recordings.get(key);
|
|
3278
|
+
if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
|
|
3279
|
+
throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
|
|
3003
3280
|
}
|
|
3004
|
-
|
|
3005
|
-
if (
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3281
|
+
const next = structuredClone(artifact);
|
|
3282
|
+
if (existing) next.reference = structuredClone(existing.reference);
|
|
3283
|
+
this.#recordings.set(key, next);
|
|
3284
|
+
}
|
|
3285
|
+
async getRecording(input) {
|
|
3286
|
+
const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
|
|
3287
|
+
if (!artifact) return void 0;
|
|
3288
|
+
if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
|
|
3289
|
+
this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
|
|
3290
|
+
return void 0;
|
|
3291
|
+
}
|
|
3292
|
+
return structuredClone(artifact);
|
|
3293
|
+
}
|
|
3294
|
+
async updateRecordingRetention(input) {
|
|
3295
|
+
const key = `${input.projectId}/${input.recordingId}`;
|
|
3296
|
+
const artifact = this.#recordings.get(key);
|
|
3297
|
+
if (!artifact) return false;
|
|
3298
|
+
artifact.reference = {
|
|
3299
|
+
id: artifact.reference.id,
|
|
3300
|
+
retention: input.retention,
|
|
3301
|
+
...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
|
|
3013
3302
|
};
|
|
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.");
|
|
3303
|
+
return true;
|
|
3050
3304
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
3305
|
+
async promoteBaseline(input) {
|
|
3306
|
+
for (const artifact of this.#recordings.values()) {
|
|
3307
|
+
if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
|
|
3308
|
+
artifact.reference = { id: artifact.reference.id, retention: "baseline" };
|
|
3309
|
+
}
|
|
3053
3310
|
}
|
|
3054
|
-
|
|
3055
|
-
const { projectId: _legacyProjectId, ...localDataset } = dataset;
|
|
3056
|
-
return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
|
|
3057
|
-
}
|
|
3311
|
+
};
|
|
3058
3312
|
|
|
3059
3313
|
// src/suiteTransfer.ts
|
|
3060
3314
|
var SUITE_BUNDLE_EXPORT_VERSION = 1;
|
|
@@ -3114,13 +3368,17 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
|
|
|
3114
3368
|
areEvaluationDataTypesCompatible,
|
|
3115
3369
|
assertEvaluationDatasetSnapshot,
|
|
3116
3370
|
assertEvaluationDatasetValuesMatchDeclaredTypes,
|
|
3371
|
+
assertEvaluationRecordingArtifact,
|
|
3117
3372
|
assertPortableJson,
|
|
3118
3373
|
canonicalStringify,
|
|
3119
3374
|
canonicalizePortableJson,
|
|
3120
3375
|
clonePortableJson,
|
|
3376
|
+
createEmptyEvaluationLibrary,
|
|
3121
3377
|
createEmptyEvaluationProjectData,
|
|
3122
3378
|
createEmptyMetrics,
|
|
3123
3379
|
createEvaluationBaselineSnapshot,
|
|
3380
|
+
createEvaluationRunPlan,
|
|
3381
|
+
createEvaluationRunShell,
|
|
3124
3382
|
deserializeEvaluationDatasetJson,
|
|
3125
3383
|
deserializeEvaluationProjectData,
|
|
3126
3384
|
deserializeEvaluationProjectDataFromString,
|
|
@@ -3128,6 +3386,7 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
|
|
|
3128
3386
|
evaluateAssertion,
|
|
3129
3387
|
evaluateThresholdResults,
|
|
3130
3388
|
finalizeEvaluationRecordingRetention,
|
|
3389
|
+
finalizeEvaluationRun,
|
|
3131
3390
|
fingerprint,
|
|
3132
3391
|
fingerprintEvaluationDataset,
|
|
3133
3392
|
getEvaluationSuiteMode,
|
|
@@ -3139,11 +3398,15 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
|
|
|
3139
3398
|
localizeEvaluationDataset,
|
|
3140
3399
|
normalizeEvaluationAggregate,
|
|
3141
3400
|
normalizeEvaluationBaselineSnapshot,
|
|
3401
|
+
normalizeEvaluationLibrary,
|
|
3142
3402
|
normalizeEvaluationRun,
|
|
3143
3403
|
normalizeEvaluationTrial,
|
|
3144
3404
|
portableEvaluationDataTypes,
|
|
3405
|
+
preserveEvaluationRunName,
|
|
3406
|
+
reconcileEvaluationRunSnapshots,
|
|
3145
3407
|
runEvaluationCases,
|
|
3146
3408
|
runEvaluationSuite,
|
|
3409
|
+
runEvaluationTrial,
|
|
3147
3410
|
runEvaluationWorkPool,
|
|
3148
3411
|
serializeEvaluationDatasetJson,
|
|
3149
3412
|
serializeEvaluationProjectData,
|