@openpond/evals 0.4.0 → 0.4.1
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/README.md +22 -5
- package/dist/benchmarks.js +218 -0
- package/dist/builtin-benchmarks/harness-refiner.js +1267 -0
- package/dist/index.js +1 -0
- package/dist/types/benchmarks.d.ts +293 -0
- package/dist/types/benchmarks.d.ts.map +1 -0
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts +119 -0
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -0
- package/dist/types/evidence/conformance.d.ts +8 -8
- package/dist/types/evidence/contracts.d.ts +8 -8
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/review-conformance.d.ts +10 -10
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# `@openpond/evals`
|
|
2
2
|
|
|
3
|
-
Portable evaluation contracts
|
|
4
|
-
manifests, attempt and evaluation receipts,
|
|
5
|
-
|
|
6
|
-
qualification receipts. The package depends on
|
|
3
|
+
Portable evaluation and benchmark contracts plus pure helpers for Tasksets,
|
|
4
|
+
graders, run manifests, attempt and evaluation receipts, paired benchmark
|
|
5
|
+
comparisons, execution adapters, conformance fixtures, Work-evidence
|
|
6
|
+
eligibility, and no-training/SFT/preference/RL qualification receipts. The package depends on
|
|
7
7
|
[`@openpond/harness`](../harness/README.md) for exact Harness identities but
|
|
8
8
|
does not re-export Harness APIs. Applications import the two packages directly,
|
|
9
9
|
which keeps refinement and evaluation authority visibly separate.
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
AttemptReceiptSchema,
|
|
14
14
|
TasksetReleaseSchema,
|
|
15
15
|
ModelImprovementQualificationReceiptSchema,
|
|
16
|
+
BenchmarkDefinitionSchema,
|
|
17
|
+
compareBenchmarkRuns,
|
|
16
18
|
validateTasksetRelease,
|
|
17
19
|
verifyAttemptReceipt,
|
|
18
20
|
} from "@openpond/evals";
|
|
@@ -26,12 +28,27 @@ import {
|
|
|
26
28
|
} from "@openpond/evals/evidence";
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
Subpath exports are available at `/harness`, `/tasksets`, `/graders`, `/runs`,
|
|
31
|
+
Subpath exports are available at `/harness`, `/tasksets`, `/benchmarks`, `/graders`, `/runs`,
|
|
30
32
|
`/conformance`, `/evidence`, `/review`, and
|
|
31
33
|
`/model-improvement-qualification`. The package is an evaluation protocol library,
|
|
32
34
|
not a hosted client. It does not execute OpenPond Desktop or Sandbox sessions,
|
|
33
35
|
resolve credentials, or persist artifacts.
|
|
34
36
|
|
|
37
|
+
## Benchmarks
|
|
38
|
+
|
|
39
|
+
`BenchmarkDefinition` binds a named benchmark to an immutable Taskset Release,
|
|
40
|
+
its adaptation and held-out splits, primary metric, and quality gate.
|
|
41
|
+
`BenchmarkRunSummary` records the pinned model and reasoning effort together
|
|
42
|
+
with pass counts, foreground provider usage, cost, and latency. Compare a
|
|
43
|
+
baseline and candidate with `compareBenchmarkRuns`; it rejects mismatched
|
|
44
|
+
Taskset releases, models, reasoning effort, cases, seeds, repetitions, runtime,
|
|
45
|
+
environment, tools, or limits and never reports an efficiency win when the
|
|
46
|
+
configured quality gate fails.
|
|
47
|
+
|
|
48
|
+
The package defines portable schemas and comparison math. Hosts remain
|
|
49
|
+
responsible for scheduling cases, pinning runtime and tools, persisting
|
|
50
|
+
receipts, and keeping held-out evidence out of adaptation.
|
|
51
|
+
|
|
35
52
|
## Harness Evaluation review
|
|
36
53
|
|
|
37
54
|
`@openpond/harness` owns the public model-driven Refiner and continuous-review
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ImmutableReleaseRefSchema, MetadataSchema, ModelRefSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, contentHash, } from "@openpond/harness";
|
|
3
|
+
import { TaskSplitSchema } from "./tasksets.js";
|
|
4
|
+
export { harnessRefinerBenchmarkAssets, harnessRefinerBenchmarkRelease, } from "./builtin-benchmarks/harness-refiner.js";
|
|
5
|
+
export const BenchmarkMetricSchema = z.enum([
|
|
6
|
+
"foreground_tokens",
|
|
7
|
+
"success_rate",
|
|
8
|
+
"latency_ms",
|
|
9
|
+
"cost_usd",
|
|
10
|
+
]);
|
|
11
|
+
export const BenchmarkRunPhaseSchema = z.enum(["baseline", "candidate"]);
|
|
12
|
+
export const BenchmarkProtocolSchema = z.object({
|
|
13
|
+
split: TaskSplitSchema,
|
|
14
|
+
taskIds: z.array(ReleaseIdSchema).min(1).max(100_000),
|
|
15
|
+
seeds: z.array(z.string().trim().min(1).max(500)).min(1).max(100),
|
|
16
|
+
repetitions: z.number().int().positive().max(20),
|
|
17
|
+
runtimeTargetHash: ReleaseHashSchema,
|
|
18
|
+
environmentHash: ReleaseHashSchema,
|
|
19
|
+
toolContractHash: ReleaseHashSchema,
|
|
20
|
+
limitsHash: ReleaseHashSchema,
|
|
21
|
+
}).strict();
|
|
22
|
+
export const BenchmarkDefinitionSchema = z.object({
|
|
23
|
+
schemaVersion: z.literal("openpond.benchmarkDefinition.v1"),
|
|
24
|
+
id: ReleaseIdSchema,
|
|
25
|
+
title: z.string().trim().min(1).max(500),
|
|
26
|
+
description: z.string().trim().min(1).max(5_000),
|
|
27
|
+
tasksetRelease: ImmutableReleaseRefSchema,
|
|
28
|
+
adaptationSplit: TaskSplitSchema,
|
|
29
|
+
evaluationSplit: TaskSplitSchema,
|
|
30
|
+
primaryMetric: BenchmarkMetricSchema,
|
|
31
|
+
qualityGate: z.enum(["none", "non_regression", "all_pass"]),
|
|
32
|
+
caseCounts: z.object({
|
|
33
|
+
adaptation: z.number().int().nonnegative(),
|
|
34
|
+
evaluation: z.number().int().positive(),
|
|
35
|
+
}).strict(),
|
|
36
|
+
metadata: MetadataSchema,
|
|
37
|
+
}).strict();
|
|
38
|
+
export const BenchmarkRunRequestSchema = z.object({
|
|
39
|
+
schemaVersion: z.literal("openpond.benchmarkRunRequest.v1"),
|
|
40
|
+
phase: BenchmarkRunPhaseSchema,
|
|
41
|
+
model: ModelRefSchema,
|
|
42
|
+
reasoningEffort: z.string().trim().min(1).max(100).nullable(),
|
|
43
|
+
split: TaskSplitSchema,
|
|
44
|
+
seeds: z.array(z.string().trim().min(1).max(500)).min(1).max(100),
|
|
45
|
+
repetitions: z.number().int().positive().max(20),
|
|
46
|
+
metadata: MetadataSchema,
|
|
47
|
+
}).strict();
|
|
48
|
+
const BenchmarkUsageSchema = z.object({
|
|
49
|
+
inputTokens: z.number().int().nonnegative(),
|
|
50
|
+
outputTokens: z.number().int().nonnegative(),
|
|
51
|
+
totalTokens: z.number().int().nonnegative(),
|
|
52
|
+
}).strict();
|
|
53
|
+
export const BenchmarkRunSummaryContentSchema = z.object({
|
|
54
|
+
schemaVersion: z.literal("openpond.benchmarkRunSummary.v1"),
|
|
55
|
+
id: ReleaseIdSchema,
|
|
56
|
+
phase: BenchmarkRunPhaseSchema,
|
|
57
|
+
tasksetRelease: ImmutableReleaseRefSchema,
|
|
58
|
+
harnessRelease: ImmutableReleaseRefSchema,
|
|
59
|
+
evaluationResult: ImmutableReleaseRefSchema,
|
|
60
|
+
model: ModelRefSchema,
|
|
61
|
+
reasoningEffort: z.string().trim().min(1).max(100).nullable(),
|
|
62
|
+
protocol: BenchmarkProtocolSchema,
|
|
63
|
+
attemptCount: z.number().int().positive(),
|
|
64
|
+
passedCount: z.number().int().nonnegative(),
|
|
65
|
+
terminalCount: z.number().int().nonnegative(),
|
|
66
|
+
usage: BenchmarkUsageSchema,
|
|
67
|
+
costUsd: z.number().nonnegative().nullable(),
|
|
68
|
+
latencyMs: z.number().int().nonnegative(),
|
|
69
|
+
createdAt: ReleaseTimestampSchema,
|
|
70
|
+
metadata: MetadataSchema,
|
|
71
|
+
}).strict();
|
|
72
|
+
export const BenchmarkRunSummarySchema = BenchmarkRunSummaryContentSchema
|
|
73
|
+
.extend({ contentHash: ReleaseHashSchema })
|
|
74
|
+
.strict();
|
|
75
|
+
export const BenchmarkComparisonContentSchema = z.object({
|
|
76
|
+
schemaVersion: z.literal("openpond.benchmarkComparison.v1"),
|
|
77
|
+
id: ReleaseIdSchema,
|
|
78
|
+
baseline: ImmutableReleaseRefSchema,
|
|
79
|
+
candidate: ImmutableReleaseRefSchema,
|
|
80
|
+
tasksetRelease: ImmutableReleaseRefSchema,
|
|
81
|
+
primaryMetric: BenchmarkMetricSchema,
|
|
82
|
+
qualityPassed: z.boolean(),
|
|
83
|
+
baselinePassRate: z.number().min(0).max(1),
|
|
84
|
+
candidatePassRate: z.number().min(0).max(1),
|
|
85
|
+
foregroundTokenDelta: z.number().int(),
|
|
86
|
+
foregroundTokenDeltaPercent: z.number().finite().nullable(),
|
|
87
|
+
improved: z.boolean(),
|
|
88
|
+
createdAt: ReleaseTimestampSchema,
|
|
89
|
+
metadata: MetadataSchema,
|
|
90
|
+
}).strict();
|
|
91
|
+
export const BenchmarkComparisonSchema = BenchmarkComparisonContentSchema
|
|
92
|
+
.extend({ contentHash: ReleaseHashSchema })
|
|
93
|
+
.strict();
|
|
94
|
+
export function createBenchmarkDefinition(input) {
|
|
95
|
+
return BenchmarkDefinitionSchema.parse(input);
|
|
96
|
+
}
|
|
97
|
+
export function createBenchmarkRunSummary(input) {
|
|
98
|
+
if (input.receipts.length !== input.evaluation.attemptCount) {
|
|
99
|
+
throw new Error("Benchmark receipt count does not match its Evaluation result.");
|
|
100
|
+
}
|
|
101
|
+
const usage = input.receipts.reduce((total, receipt) => addUsage(total, providerUsage(receipt.metadata.usage)), emptyUsage());
|
|
102
|
+
const costs = input.receipts.flatMap((receipt) => typeof receipt.costUsd === "number" ? [receipt.costUsd] : []);
|
|
103
|
+
const content = BenchmarkRunSummaryContentSchema.parse({
|
|
104
|
+
schemaVersion: "openpond.benchmarkRunSummary.v1",
|
|
105
|
+
id: input.id,
|
|
106
|
+
phase: input.phase,
|
|
107
|
+
tasksetRelease: input.evaluation.tasksetRelease,
|
|
108
|
+
harnessRelease: input.evaluation.harnessRelease,
|
|
109
|
+
evaluationResult: {
|
|
110
|
+
id: input.evaluation.id,
|
|
111
|
+
contentHash: input.evaluation.contentHash,
|
|
112
|
+
},
|
|
113
|
+
model: input.evaluation.model,
|
|
114
|
+
reasoningEffort: input.reasoningEffort,
|
|
115
|
+
protocol: input.protocol,
|
|
116
|
+
attemptCount: input.evaluation.attemptCount,
|
|
117
|
+
passedCount: input.receipts.filter((receipt) => receipt.metadata.passed === true).length,
|
|
118
|
+
terminalCount: input.evaluation.terminalCount,
|
|
119
|
+
usage,
|
|
120
|
+
costUsd: costs.length ? costs.reduce((sum, value) => sum + value, 0) : null,
|
|
121
|
+
latencyMs: input.receipts.reduce((sum, receipt) => sum + receipt.latencyMs, 0),
|
|
122
|
+
createdAt: input.createdAt,
|
|
123
|
+
metadata: input.metadata ?? {},
|
|
124
|
+
});
|
|
125
|
+
return BenchmarkRunSummarySchema.parse({
|
|
126
|
+
...content,
|
|
127
|
+
contentHash: contentHash(content),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function compareBenchmarkRuns(input) {
|
|
131
|
+
const { baseline, candidate } = input;
|
|
132
|
+
if (baseline.tasksetRelease.contentHash !== candidate.tasksetRelease.contentHash
|
|
133
|
+
|| baseline.model.provider !== candidate.model.provider
|
|
134
|
+
|| baseline.model.model !== candidate.model.model
|
|
135
|
+
|| baseline.reasoningEffort !== candidate.reasoningEffort
|
|
136
|
+
|| contentHash(baseline.protocol) !== contentHash(candidate.protocol)) {
|
|
137
|
+
throw new Error("Benchmark runs are not comparable under the pinned protocol.");
|
|
138
|
+
}
|
|
139
|
+
const baselinePassRate = baseline.passedCount / baseline.attemptCount;
|
|
140
|
+
const candidatePassRate = candidate.passedCount / candidate.attemptCount;
|
|
141
|
+
const complete = baseline.terminalCount === baseline.attemptCount
|
|
142
|
+
&& candidate.terminalCount === candidate.attemptCount;
|
|
143
|
+
const qualityPassed = complete && (input.qualityGate === "none"
|
|
144
|
+
|| (input.qualityGate === "all_pass"
|
|
145
|
+
? candidatePassRate === 1
|
|
146
|
+
: (baseline.passedCount > 0 || candidate.passedCount > 0)
|
|
147
|
+
&& candidatePassRate >= baselinePassRate));
|
|
148
|
+
const foregroundTokenDelta = candidate.usage.totalTokens - baseline.usage.totalTokens;
|
|
149
|
+
const foregroundTokenDeltaPercent = baseline.usage.totalTokens > 0
|
|
150
|
+
? (foregroundTokenDelta / baseline.usage.totalTokens) * 100
|
|
151
|
+
: null;
|
|
152
|
+
const metricImproved = input.primaryMetric === "foreground_tokens"
|
|
153
|
+
? foregroundTokenDelta < 0
|
|
154
|
+
: input.primaryMetric === "success_rate"
|
|
155
|
+
? candidatePassRate > baselinePassRate
|
|
156
|
+
: input.primaryMetric === "latency_ms"
|
|
157
|
+
? candidate.latencyMs < baseline.latencyMs
|
|
158
|
+
: candidate.costUsd !== null
|
|
159
|
+
&& baseline.costUsd !== null
|
|
160
|
+
&& candidate.costUsd < baseline.costUsd;
|
|
161
|
+
const content = BenchmarkComparisonContentSchema.parse({
|
|
162
|
+
schemaVersion: "openpond.benchmarkComparison.v1",
|
|
163
|
+
id: input.id,
|
|
164
|
+
baseline: { id: baseline.id, contentHash: baseline.contentHash },
|
|
165
|
+
candidate: { id: candidate.id, contentHash: candidate.contentHash },
|
|
166
|
+
tasksetRelease: baseline.tasksetRelease,
|
|
167
|
+
primaryMetric: input.primaryMetric,
|
|
168
|
+
qualityPassed,
|
|
169
|
+
baselinePassRate,
|
|
170
|
+
candidatePassRate,
|
|
171
|
+
foregroundTokenDelta,
|
|
172
|
+
foregroundTokenDeltaPercent,
|
|
173
|
+
improved: qualityPassed && metricImproved,
|
|
174
|
+
createdAt: input.createdAt,
|
|
175
|
+
metadata: input.metadata ?? {},
|
|
176
|
+
});
|
|
177
|
+
return BenchmarkComparisonSchema.parse({
|
|
178
|
+
...content,
|
|
179
|
+
contentHash: contentHash(content),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function providerUsage(input) {
|
|
183
|
+
const records = Array.isArray(input) ? input : input ? [input] : [];
|
|
184
|
+
return records.reduce((total, value) => addUsage(total, usageRecord(value)), emptyUsage());
|
|
185
|
+
}
|
|
186
|
+
function usageRecord(value) {
|
|
187
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
188
|
+
return emptyUsage();
|
|
189
|
+
}
|
|
190
|
+
const record = value;
|
|
191
|
+
const inputTokens = token(record, ["inputTokens", "input_tokens", "promptTokens", "prompt_tokens"]);
|
|
192
|
+
const outputTokens = token(record, ["outputTokens", "output_tokens", "completionTokens", "completion_tokens"]);
|
|
193
|
+
const reportedTotal = token(record, ["totalTokens", "total_tokens"]);
|
|
194
|
+
return {
|
|
195
|
+
inputTokens,
|
|
196
|
+
outputTokens,
|
|
197
|
+
totalTokens: reportedTotal || inputTokens + outputTokens,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function token(record, keys) {
|
|
201
|
+
for (const key of keys) {
|
|
202
|
+
const value = record[key];
|
|
203
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
204
|
+
return Math.trunc(value);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
function emptyUsage() {
|
|
210
|
+
return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
211
|
+
}
|
|
212
|
+
function addUsage(left, right) {
|
|
213
|
+
return {
|
|
214
|
+
inputTokens: left.inputTokens + right.inputTokens,
|
|
215
|
+
outputTokens: left.outputTokens + right.outputTokens,
|
|
216
|
+
totalTokens: left.totalTokens + right.totalTokens,
|
|
217
|
+
};
|
|
218
|
+
}
|