@openpond/evals 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CONTRACT.md +27 -1
  2. package/README.md +31 -2
  3. package/conformance/telemetry/v1/invalid-batch.json +17 -0
  4. package/conformance/telemetry/v1/valid-batch.json +58 -0
  5. package/dist/artifact-verification.js +172 -0
  6. package/dist/builtin-benchmarks/harness-refiner.js +469 -24
  7. package/dist/execution-contracts.js +153 -0
  8. package/dist/execution-receipts.js +185 -0
  9. package/dist/graders.js +8 -2
  10. package/dist/index.js +10 -0
  11. package/dist/learned-preference.js +334 -0
  12. package/dist/preferences.js +803 -0
  13. package/dist/rollouts.js +241 -0
  14. package/dist/tasksets.js +24 -0
  15. package/dist/telemetry/index.js +4 -0
  16. package/dist/telemetry-analysis.js +183 -0
  17. package/dist/telemetry-bundle.js +60 -0
  18. package/dist/telemetry-catalog.js +50 -0
  19. package/dist/telemetry.js +112 -0
  20. package/dist/types/artifact-verification.d.ts +31 -0
  21. package/dist/types/artifact-verification.d.ts.map +1 -0
  22. package/dist/types/builtin-benchmarks/harness-refiner.d.ts +22 -0
  23. package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
  24. package/dist/types/conformance.d.ts +44 -0
  25. package/dist/types/conformance.d.ts.map +1 -1
  26. package/dist/types/evidence/conformance.d.ts +24 -24
  27. package/dist/types/evidence/contracts.d.ts +42 -42
  28. package/dist/types/execution-contracts.d.ts +806 -0
  29. package/dist/types/execution-contracts.d.ts.map +1 -0
  30. package/dist/types/execution-receipts.d.ts +55 -0
  31. package/dist/types/execution-receipts.d.ts.map +1 -0
  32. package/dist/types/graders.d.ts.map +1 -1
  33. package/dist/types/index.d.ts +10 -0
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/learned-preference.d.ts +282 -0
  36. package/dist/types/learned-preference.d.ts.map +1 -0
  37. package/dist/types/preferences.d.ts +583 -0
  38. package/dist/types/preferences.d.ts.map +1 -0
  39. package/dist/types/review-conformance.d.ts +20 -15
  40. package/dist/types/review-conformance.d.ts.map +1 -1
  41. package/dist/types/rollouts.d.ts +221 -0
  42. package/dist/types/rollouts.d.ts.map +1 -0
  43. package/dist/types/tasksets.d.ts +89 -0
  44. package/dist/types/tasksets.d.ts.map +1 -1
  45. package/dist/types/telemetry/index.d.ts +5 -0
  46. package/dist/types/telemetry/index.d.ts.map +1 -0
  47. package/dist/types/telemetry-analysis.d.ts +116 -0
  48. package/dist/types/telemetry-analysis.d.ts.map +1 -0
  49. package/dist/types/telemetry-bundle.d.ts +309 -0
  50. package/dist/types/telemetry-bundle.d.ts.map +1 -0
  51. package/dist/types/telemetry-catalog.d.ts +269 -0
  52. package/dist/types/telemetry-catalog.d.ts.map +1 -0
  53. package/dist/types/telemetry.d.ts +266 -0
  54. package/dist/types/telemetry.d.ts.map +1 -0
  55. package/package.json +20 -2
  56. package/schemas/telemetry/v1/evidence-completeness.schema.json +82 -0
  57. package/schemas/telemetry/v1/evidence-reference.schema.json +41 -0
  58. package/schemas/telemetry/v1/metric-definition.schema.json +96 -0
  59. package/schemas/telemetry/v1/metric-observation.schema.json +182 -0
  60. package/schemas/telemetry/v1/run-metric-summary.schema.json +98 -0
  61. package/schemas/telemetry/v1/run-telemetry-batch.schema.json +405 -0
  62. package/schemas/telemetry/v1/run-telemetry-event.schema.json +201 -0
  63. package/schemas/telemetry/v1/telemetry-cohort.schema.json +100 -0
  64. package/schemas/telemetry/v1/telemetry-export-bundle.schema.json +655 -0
@@ -0,0 +1,334 @@
1
+ import { z } from "zod";
2
+ import { ImmutableReleaseRefSchema, MetadataSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, contentHash, } from "@openpond/harness";
3
+ import { verifyComparisonAssignment, verifyPreferenceReceipt, } from "./preferences.js";
4
+ const PreferenceDatasetPartitionSchema = z.enum([
5
+ "reward_train",
6
+ "reward_validation",
7
+ "reward_qualification",
8
+ ]);
9
+ export const PreferenceEvidenceAuthoritySchema = z.enum([
10
+ "human",
11
+ "synthetic_fixture",
12
+ ]);
13
+ export const RewardModelQualificationKindSchema = z.enum([
14
+ "synthetic_smoke",
15
+ "human_heldout",
16
+ ]);
17
+ const PreferenceDatasetGroupSchema = z.object({
18
+ id: ReleaseIdSchema,
19
+ assignmentRef: ImmutableReleaseRefSchema,
20
+ scenarioRef: ImmutableReleaseRefSchema,
21
+ scenarioSplit: z.enum(["train", "validation"]),
22
+ preferenceResultRef: ImmutableReleaseRefSchema,
23
+ receiptRefs: z.array(ImmutableReleaseRefSchema).min(1).max(100),
24
+ attemptRefs: z.array(ImmutableReleaseRefSchema).min(2).max(4),
25
+ artifactManifestRefs: z.array(ImmutableReleaseRefSchema).min(2).max(4),
26
+ orderedBuckets: z.array(z.array(ReleaseIdSchema).min(1).max(4)).max(4),
27
+ rejectAll: z.boolean(),
28
+ partition: PreferenceDatasetPartitionSchema,
29
+ metadata: MetadataSchema,
30
+ }).strict().superRefine((group, context) => {
31
+ const attemptIds = group.attemptRefs.map((attempt) => attempt.id);
32
+ if (new Set(attemptIds).size !== attemptIds.length) {
33
+ context.addIssue({
34
+ code: "custom",
35
+ path: ["attemptRefs"],
36
+ message: "Preference dataset group Attempt refs must be unique.",
37
+ });
38
+ }
39
+ if (group.artifactManifestRefs.length !== group.attemptRefs.length) {
40
+ context.addIssue({
41
+ code: "custom",
42
+ path: ["artifactManifestRefs"],
43
+ message: "Every preference dataset Attempt requires one Artifact Manifest ref.",
44
+ });
45
+ }
46
+ const bucketIds = group.orderedBuckets.flat();
47
+ if (group.rejectAll && bucketIds.length > 0) {
48
+ context.addIssue({
49
+ code: "custom",
50
+ path: ["orderedBuckets"],
51
+ message: "A reject-all preference dataset group cannot contain ordered buckets.",
52
+ });
53
+ }
54
+ if (!group.rejectAll
55
+ && (bucketIds.length !== attemptIds.length
56
+ || new Set(bucketIds).size !== bucketIds.length
57
+ || bucketIds.some((attemptId) => !attemptIds.includes(attemptId)))) {
58
+ context.addIssue({
59
+ code: "custom",
60
+ path: ["orderedBuckets"],
61
+ message: "Ordered buckets must contain every group Attempt exactly once.",
62
+ });
63
+ }
64
+ });
65
+ const PreferenceDatasetPairSchema = z.object({
66
+ groupId: ReleaseIdSchema,
67
+ preferredAttemptRef: ImmutableReleaseRefSchema,
68
+ dispreferredAttemptRef: ImmutableReleaseRefSchema,
69
+ relation: z.enum(["preferred", "tie"]),
70
+ }).strict().superRefine((pair, context) => {
71
+ if (pair.preferredAttemptRef.id === pair.dispreferredAttemptRef.id
72
+ && pair.preferredAttemptRef.contentHash === pair.dispreferredAttemptRef.contentHash) {
73
+ context.addIssue({
74
+ code: "custom",
75
+ message: "A derived preference pair must reference two different Attempts.",
76
+ });
77
+ }
78
+ });
79
+ export const PreferenceDatasetReleaseContentSchema = z.object({
80
+ schemaVersion: z.literal("openpond.preferenceDatasetRelease.v1"),
81
+ id: ReleaseIdSchema,
82
+ revision: z.number().int().positive(),
83
+ tasksetRelease: ImmutableReleaseRefSchema,
84
+ comparisonRelease: ImmutableReleaseRefSchema,
85
+ authority: PreferenceEvidenceAuthoritySchema,
86
+ qualificationEligibility: z.enum(["smoke_only", "human_heldout"]),
87
+ fixtureRelease: ImmutableReleaseRefSchema.nullable(),
88
+ groups: z.array(PreferenceDatasetGroupSchema).min(1).max(100_000),
89
+ derivedPairs: z.array(PreferenceDatasetPairSchema).max(1_000_000),
90
+ createdAt: ReleaseTimestampSchema,
91
+ metadata: MetadataSchema,
92
+ }).strict().superRefine((release, context) => {
93
+ if (release.authority === "synthetic_fixture"
94
+ && (release.qualificationEligibility !== "smoke_only"
95
+ || release.fixtureRelease === null)) {
96
+ context.addIssue({
97
+ code: "custom",
98
+ path: ["qualificationEligibility"],
99
+ message: "Synthetic preference datasets require a fixture release and are smoke-only.",
100
+ });
101
+ }
102
+ if (release.authority === "human"
103
+ && (release.qualificationEligibility !== "human_heldout"
104
+ || release.fixtureRelease !== null)) {
105
+ context.addIssue({
106
+ code: "custom",
107
+ path: ["authority"],
108
+ message: "Human preference datasets must be human-heldout eligible and cannot bind a fixture release.",
109
+ });
110
+ }
111
+ const groupIds = release.groups.map((group) => group.id);
112
+ if (new Set(groupIds).size !== groupIds.length) {
113
+ context.addIssue({
114
+ code: "custom",
115
+ path: ["groups"],
116
+ message: "Preference dataset group IDs must be unique.",
117
+ });
118
+ }
119
+ const groups = new Map(release.groups.map((group) => [group.id, group]));
120
+ for (const [index, pair] of release.derivedPairs.entries()) {
121
+ const group = groups.get(pair.groupId);
122
+ if (!group) {
123
+ context.addIssue({
124
+ code: "custom",
125
+ path: ["derivedPairs", index, "groupId"],
126
+ message: "Derived preference pairs must reference a group in the same release.",
127
+ });
128
+ continue;
129
+ }
130
+ const attemptIds = new Set(group.attemptRefs.map((attempt) => attempt.id));
131
+ if (!attemptIds.has(pair.preferredAttemptRef.id)
132
+ || !attemptIds.has(pair.dispreferredAttemptRef.id)) {
133
+ context.addIssue({
134
+ code: "custom",
135
+ path: ["derivedPairs", index],
136
+ message: "Derived preference pairs must reference Attempts in their source group.",
137
+ });
138
+ }
139
+ }
140
+ });
141
+ export const PreferenceDatasetReleaseSchema = PreferenceDatasetReleaseContentSchema
142
+ .extend({ contentHash: ReleaseHashSchema })
143
+ .strict();
144
+ const RewardModelQualificationMetricsSchema = z.object({
145
+ sampleCount: z.number().int().positive(),
146
+ finiteScoreRate: z.number().min(0).max(1),
147
+ scoreVariance: z.number().nonnegative(),
148
+ checkpointReloadPassed: z.boolean(),
149
+ processorCompatibilityPassed: z.boolean(),
150
+ invalidAttemptExclusionPassed: z.boolean(),
151
+ orderedPairAccuracy: z.number().min(0).max(1).nullable(),
152
+ bucketAccuracy: z.number().min(0).max(1).nullable(),
153
+ tieAgreement: z.number().min(0).max(1).nullable(),
154
+ }).strict();
155
+ export const RewardModelQualificationReportContentSchema = z.object({
156
+ schemaVersion: z.literal("openpond.rewardModelQualificationReport.v1"),
157
+ id: ReleaseIdSchema,
158
+ kind: RewardModelQualificationKindSchema,
159
+ rewardModelVersion: ImmutableReleaseRefSchema,
160
+ preferenceDatasetRelease: ImmutableReleaseRefSchema,
161
+ tasksetRelease: ImmutableReleaseRefSchema,
162
+ processorRelease: ImmutableReleaseRefSchema,
163
+ metrics: RewardModelQualificationMetricsSchema,
164
+ passed: z.boolean(),
165
+ productionRewardEligible: z.boolean(),
166
+ createdAt: ReleaseTimestampSchema,
167
+ metadata: MetadataSchema,
168
+ }).strict().superRefine((report, context) => {
169
+ if (report.kind === "synthetic_smoke" && report.productionRewardEligible) {
170
+ context.addIssue({
171
+ code: "custom",
172
+ path: ["productionRewardEligible"],
173
+ message: "Synthetic-smoke Reward Model qualification can never grant production reward eligibility.",
174
+ });
175
+ }
176
+ if (report.kind === "human_heldout"
177
+ && report.productionRewardEligible !== report.passed) {
178
+ context.addIssue({
179
+ code: "custom",
180
+ path: ["productionRewardEligible"],
181
+ message: "Human-heldout reward eligibility must match the frozen qualification outcome.",
182
+ });
183
+ }
184
+ });
185
+ export const RewardModelQualificationReportSchema = RewardModelQualificationReportContentSchema
186
+ .extend({ contentHash: ReleaseHashSchema })
187
+ .strict();
188
+ export function createPreferenceDatasetRelease(input) {
189
+ const content = PreferenceDatasetReleaseContentSchema.parse(input);
190
+ return PreferenceDatasetReleaseSchema.parse({
191
+ ...content,
192
+ contentHash: contentHash(content),
193
+ });
194
+ }
195
+ export function materializePreferenceDatasetRelease(input) {
196
+ if (input.groups.length === 0) {
197
+ throw new Error("Preference dataset materialization requires at least one reviewed group.");
198
+ }
199
+ const tasksetRef = releaseRef(input.tasksetRelease);
200
+ const comparisonRef = releaseRef(input.comparisonRelease);
201
+ const fixtureRefs = new Map();
202
+ const groups = input.groups.map(({ assignment, receipt, partition }) => {
203
+ if (!verifyComparisonAssignment(assignment)) {
204
+ throw new Error("Preference dataset assignment failed immutable verification.");
205
+ }
206
+ if (!verifyPreferenceReceipt(receipt)) {
207
+ throw new Error("Preference dataset receipt failed immutable verification.");
208
+ }
209
+ if (!sameReleaseRef(assignment.lineage.tasksetRelease, tasksetRef)) {
210
+ throw new Error("Preference dataset assignment belongs to a different Taskset release.");
211
+ }
212
+ if (!sameReleaseRef(assignment.comparisonRelease, comparisonRef)) {
213
+ throw new Error("Preference dataset assignment belongs to a different comparison release.");
214
+ }
215
+ if (!sameReleaseRef(receipt.assignmentRef, releaseRef(assignment))) {
216
+ throw new Error("Preference dataset receipt belongs to a different assignment.");
217
+ }
218
+ if (input.authority === "synthetic_fixture") {
219
+ if (receipt.reviewer.kind !== "fixture") {
220
+ throw new Error("Synthetic preference datasets accept only fixture receipts.");
221
+ }
222
+ fixtureRefs.set(receipt.reviewer.fixtureRelease.contentHash, receipt.reviewer.fixtureRelease);
223
+ }
224
+ else if (receipt.reviewer.kind !== "human") {
225
+ throw new Error("Human preference datasets accept only human receipts.");
226
+ }
227
+ const scenario = input.tasksetRelease.tasks.find((task) => task.id === assignment.taskRef.id);
228
+ if (!scenario || (scenario.split !== "train" && scenario.split !== "validation")) {
229
+ throw new Error("Preference model datasets require train or validation scenarios.");
230
+ }
231
+ if (partition === "reward_train" && scenario.split !== "train") {
232
+ throw new Error("Reward training groups must use train scenarios.");
233
+ }
234
+ if (partition !== "reward_train" && scenario.split !== "validation") {
235
+ throw new Error("Reward validation and qualification groups must use validation scenarios.");
236
+ }
237
+ return {
238
+ id: `preference-group:${assignment.id}`,
239
+ assignmentRef: releaseRef(assignment),
240
+ scenarioRef: assignment.taskRef,
241
+ scenarioSplit: scenario.split,
242
+ preferenceResultRef: releaseRef(receipt),
243
+ receiptRefs: [releaseRef(receipt)],
244
+ attemptRefs: assignment.candidates.map((candidate) => candidate.attemptRef),
245
+ artifactManifestRefs: assignment.candidates.map((candidate) => candidate.artifactManifestRef),
246
+ orderedBuckets: receipt.order,
247
+ rejectAll: receipt.rejectAll,
248
+ partition,
249
+ metadata: { purpose: assignment.purpose, ...assignment.metadata },
250
+ };
251
+ });
252
+ if (input.authority === "synthetic_fixture" && fixtureRefs.size !== 1) {
253
+ throw new Error("Synthetic preference dataset groups must share one immutable fixture release.");
254
+ }
255
+ const fixtureRelease = input.authority === "synthetic_fixture"
256
+ ? [...fixtureRefs.values()][0]
257
+ : null;
258
+ return createPreferenceDatasetRelease({
259
+ schemaVersion: "openpond.preferenceDatasetRelease.v1",
260
+ id: input.id,
261
+ revision: input.revision,
262
+ tasksetRelease: tasksetRef,
263
+ comparisonRelease: comparisonRef,
264
+ authority: input.authority,
265
+ qualificationEligibility: input.authority === "synthetic_fixture" ? "smoke_only" : "human_heldout",
266
+ fixtureRelease,
267
+ groups,
268
+ derivedPairs: groups.flatMap(derivePairs),
269
+ createdAt: input.createdAt,
270
+ metadata: input.metadata ?? {},
271
+ });
272
+ }
273
+ export function verifyPreferenceDatasetRelease(value) {
274
+ return verifyHashed(value, PreferenceDatasetReleaseContentSchema, PreferenceDatasetReleaseSchema);
275
+ }
276
+ export function createRewardModelQualificationReport(input) {
277
+ const content = RewardModelQualificationReportContentSchema.parse(input);
278
+ return RewardModelQualificationReportSchema.parse({
279
+ ...content,
280
+ contentHash: contentHash(content),
281
+ });
282
+ }
283
+ export function verifyRewardModelQualificationReport(value) {
284
+ return verifyHashed(value, RewardModelQualificationReportContentSchema, RewardModelQualificationReportSchema);
285
+ }
286
+ function verifyHashed(value, contentSchema, fullSchema) {
287
+ const parsed = fullSchema.safeParse(value);
288
+ if (!parsed.success || !parsed.data)
289
+ return false;
290
+ const { contentHash: actual, ...content } = parsed.data;
291
+ try {
292
+ return contentHash(contentSchema.parse(content)) === actual;
293
+ }
294
+ catch {
295
+ return false;
296
+ }
297
+ }
298
+ function derivePairs(group) {
299
+ if (group.rejectAll)
300
+ return [];
301
+ const attemptById = new Map(group.attemptRefs.map((attempt) => [attempt.id, attempt]));
302
+ const pairs = [];
303
+ for (const [bucketIndex, bucket] of group.orderedBuckets.entries()) {
304
+ for (let left = 0; left < bucket.length; left += 1) {
305
+ for (let right = left + 1; right < bucket.length; right += 1) {
306
+ pairs.push({
307
+ groupId: group.id,
308
+ preferredAttemptRef: attemptById.get(bucket[left]),
309
+ dispreferredAttemptRef: attemptById.get(bucket[right]),
310
+ relation: "tie",
311
+ });
312
+ }
313
+ }
314
+ for (const lowerBucket of group.orderedBuckets.slice(bucketIndex + 1)) {
315
+ for (const preferredId of bucket) {
316
+ for (const dispreferredId of lowerBucket) {
317
+ pairs.push({
318
+ groupId: group.id,
319
+ preferredAttemptRef: attemptById.get(preferredId),
320
+ dispreferredAttemptRef: attemptById.get(dispreferredId),
321
+ relation: "preferred",
322
+ });
323
+ }
324
+ }
325
+ }
326
+ }
327
+ return pairs;
328
+ }
329
+ function releaseRef(value) {
330
+ return { id: value.id, contentHash: value.contentHash };
331
+ }
332
+ function sameReleaseRef(left, right) {
333
+ return left.id === right.id && left.contentHash === right.contentHash;
334
+ }