@openpond/evals 0.3.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/CONTRACT.md +11 -3
- package/README.md +45 -9
- package/RELEASING.md +13 -46
- package/dist/benchmarks.js +218 -0
- package/dist/builtin-benchmarks/harness-refiner.js +1267 -0
- package/dist/compatibility.js +2 -2
- package/dist/conformance.js +1 -2
- package/dist/evidence/authoring.js +1 -1
- package/dist/evidence/conformance.js +1 -1
- package/dist/evidence/contracts.js +1 -2
- package/dist/evidence/eligibility.js +1 -1
- package/dist/graders.js +1 -1
- package/dist/harness.js +33 -101
- package/dist/index.js +3 -3
- package/dist/model-improvement-qualification.js +125 -0
- package/dist/review-conformance.js +209 -0
- package/dist/runs.js +1 -9
- package/dist/tasksets.js +2 -15
- 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/compatibility.d.ts +1 -1
- package/dist/types/compatibility.d.ts.map +1 -1
- package/dist/types/conformance.d.ts +2 -0
- package/dist/types/conformance.d.ts.map +1 -1
- package/dist/types/evidence/conformance.d.ts +24 -24
- package/dist/types/evidence/contracts.d.ts +26 -26
- package/dist/types/harness.d.ts +3 -409
- package/dist/types/harness.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/model-improvement-qualification.d.ts +222 -0
- package/dist/types/model-improvement-qualification.d.ts.map +1 -0
- package/dist/types/review-conformance.d.ts +607 -0
- package/dist/types/review-conformance.d.ts.map +1 -0
- package/dist/types/runs.d.ts +1 -9
- package/dist/types/runs.d.ts.map +1 -1
- package/dist/types/tasksets.d.ts +2 -25
- package/dist/types/tasksets.d.ts.map +1 -1
- package/package.json +18 -10
- package/dist/common.js +0 -67
- package/dist/harness-improvements.js +0 -329
- package/dist/harness-workspaces.js +0 -368
- package/dist/sha256.js +0 -91
- package/dist/types/common.d.ts +0 -51
- package/dist/types/common.d.ts.map +0 -1
- package/dist/types/harness-improvements.d.ts +0 -523
- package/dist/types/harness-improvements.d.ts.map +0 -1
- package/dist/types/harness-workspaces.d.ts +0 -802
- package/dist/types/harness-workspaces.d.ts.map +0 -1
- package/dist/types/sha256.d.ts +0 -2
- package/dist/types/sha256.d.ts.map +0 -1
package/CONTRACT.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Portable contract map
|
|
2
2
|
|
|
3
|
-
`@openpond/
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`@openpond/harness` owns immutable Harness identities, workspaces,
|
|
4
|
+
model-driven refinement/review policy, improvements, traces, tools, model
|
|
5
|
+
identities, and shared hashing.
|
|
6
|
+
`@openpond/evals` owns Tasksets, graders, evaluation runs and receipts,
|
|
7
|
+
execution orchestration, conformance fixtures, and Work-evidence eligibility.
|
|
8
|
+
Evals depends on Harness for exact release identities but does not re-export
|
|
9
|
+
Harness APIs; Harness never depends on Evals. Host applications own persistence, provider sessions, secret
|
|
10
|
+
leases, connected-app authorization, model streaming, artifact bytes, and
|
|
11
|
+
runtime processes.
|
|
6
12
|
|
|
7
13
|
| Existing object | Portable object | Migration rule |
|
|
8
14
|
| --- | --- | --- |
|
|
@@ -16,6 +22,8 @@ authorization, model streaming, artifact bytes, and runtime processes.
|
|
|
16
22
|
| completed Work or Development turn | `WorkEvidenceReceipt` | Project the authoritative terminal turn, immutable Agent snapshot when available, model/runtime identity, sanitized trace reference, exact output revisions, validation evidence, interventions, timing, usage, and explicit consent provenance. Keep the raw source and trace host-private. |
|
|
17
23
|
| Agent plus environment runtime events | `WorkProcessTrace` | Emit one ordered trace with `agent` and `environment` layers. Bind every environment step to its outer Agent tool call or stable Agent-turn receipt hash. Hash inputs/outputs and expose only enumerated, bounded attributes. |
|
|
18
24
|
| user feedback on Work output | `WorkFeedbackReceipt` | Append a new receipt bound to the evidence receipt and, when selected, the exact content-addressed output-revision descriptor. Corrections are separate artifacts and never mutate prior receipts. |
|
|
25
|
+
| bounded cross-Work review | `HarnessEvaluationReviewReceipt` | Select only currently authorized immutable evidence, advance one watermark, group one stable claim, route to the smallest correct layer, and name the next authority without performing downstream effects. |
|
|
26
|
+
| model-improvement qualification | `ModelImprovementQualificationReceipt` | Bind the originating review, exact Harness, Taskset, real baseline Evaluation, Model, Environment/tool/permission/policy hashes, Verifier, source policies, privacy, budget, and non-frozen signal. Weak or confounded evidence emits `no_training`; training and activation remain host effects. |
|
|
19
27
|
|
|
20
28
|
## Compatibility policy
|
|
21
29
|
|
package/README.md
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
# `@openpond/evals`
|
|
2
2
|
|
|
3
|
-
Portable
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
[`@openpond/harness`](../harness/README.md) for exact Harness identities but
|
|
8
|
+
does not re-export Harness APIs. Applications import the two packages directly,
|
|
9
|
+
which keeps refinement and evaluation authority visibly separate.
|
|
6
10
|
|
|
7
11
|
```ts
|
|
8
12
|
import {
|
|
9
13
|
AttemptReceiptSchema,
|
|
10
|
-
HarnessReleaseSchema,
|
|
11
14
|
TasksetReleaseSchema,
|
|
15
|
+
ModelImprovementQualificationReceiptSchema,
|
|
16
|
+
BenchmarkDefinitionSchema,
|
|
17
|
+
compareBenchmarkRuns,
|
|
12
18
|
validateTasksetRelease,
|
|
13
19
|
verifyAttemptReceipt,
|
|
14
20
|
} from "@openpond/evals";
|
|
15
21
|
|
|
22
|
+
import { HarnessReleaseSchema } from "@openpond/harness";
|
|
23
|
+
|
|
16
24
|
import {
|
|
17
25
|
WorkEvidenceReceiptSchema,
|
|
18
26
|
classifyWorkEvidence,
|
|
@@ -20,10 +28,38 @@ import {
|
|
|
20
28
|
} from "@openpond/evals/evidence";
|
|
21
29
|
```
|
|
22
30
|
|
|
23
|
-
Subpath exports are available at `/harness`, `/tasksets`, `/graders`, `/runs`,
|
|
24
|
-
`/conformance`,
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
Subpath exports are available at `/harness`, `/tasksets`, `/benchmarks`, `/graders`, `/runs`,
|
|
32
|
+
`/conformance`, `/evidence`, `/review`, and
|
|
33
|
+
`/model-improvement-qualification`. The package is an evaluation protocol library,
|
|
34
|
+
not a hosted client. It does not execute OpenPond Desktop or Sandbox sessions,
|
|
35
|
+
resolve credentials, or persist artifacts.
|
|
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
|
+
|
|
52
|
+
## Harness Evaluation review
|
|
53
|
+
|
|
54
|
+
`@openpond/harness` owns the public model-driven Refiner and continuous-review
|
|
55
|
+
policy plus immutable bounded decisions and receipts. Evals does not own
|
|
56
|
+
learning prompts, evidence selection, scheduling, or Harness mutation. It
|
|
57
|
+
binds an accepted review to the exact Harness, Taskset, scored
|
|
58
|
+
baseline Evaluation, base Model, Environment/tool/permission/policy hashes,
|
|
59
|
+
Verifier, source-policy checks, privacy approval, budget approval, and eligible
|
|
60
|
+
non-frozen learning signal. Frozen Evaluation evidence is rejected as training
|
|
61
|
+
evidence. Weak, constant, confounded, uncalibrated, revoked, or incompletely
|
|
62
|
+
authorized signal produces `no_training`; it cannot qualify a managed run.
|
|
27
63
|
|
|
28
64
|
## Work evidence
|
|
29
65
|
|
|
@@ -66,7 +102,7 @@ overrides that state.
|
|
|
66
102
|
Implement `HarnessRuntime` for environment state and `HarnessExecutor` around
|
|
67
103
|
the host's existing model/Agent loop. Do not reimplement prompting, tool dispatch,
|
|
68
104
|
session persistence, cost accounting, cancellation, or cleanup in this package.
|
|
69
|
-
A
|
|
105
|
+
A step-based environment maps reset/step/observation semantics to the same
|
|
70
106
|
interfaces and keeps provider allocation, authentication, and cleanup in its host.
|
|
71
107
|
|
|
72
108
|
## Conformance
|
package/RELEASING.md
CHANGED
|
@@ -1,55 +1,22 @@
|
|
|
1
1
|
# Releasing `@openpond/evals`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `openpond.agentSnapshot.v2`
|
|
8
|
-
- `openpond.harnessRelease.v2`
|
|
9
|
-
- `openpond.tasksetRelease.v2`
|
|
10
|
-
- `openpond.runManifest.v1`
|
|
11
|
-
- `openpond.attemptReceipt.v1`
|
|
12
|
-
- `openpond.harnessTrace.v1`
|
|
13
|
-
- `openpond.harnessCompatibility.v1`
|
|
14
|
-
- `openpond.graderEvidence.v1`
|
|
15
|
-
- `openpond.evaluationResult.v1`
|
|
16
|
-
|
|
17
|
-
The package also carries the Work evidence schemas introduced in `0.2.0`:
|
|
18
|
-
|
|
19
|
-
- `openpond.workEvidenceReceipt.v1`
|
|
20
|
-
- `openpond.workProcessTrace.v1`
|
|
21
|
-
- `openpond.workFeedbackReceipt.v1`
|
|
22
|
-
- `openpond.workEvidenceEligibility.v1`
|
|
23
|
-
|
|
24
|
-
## Trusted publishing
|
|
25
|
-
|
|
26
|
-
The package already exists on npm and the `npm-production` environment is
|
|
27
|
-
configured for trusted publishing through `release-evals.yml`. Do not run a
|
|
28
|
-
manual `npm publish` or repeat the original bootstrap. The workflow uses its own
|
|
29
|
-
pinned npm version for trusted publication and provenance.
|
|
30
|
-
|
|
31
|
-
To inspect an already-published version:
|
|
3
|
+
`@openpond/evals` is the public evaluation package for OpenPond Harnesses. It
|
|
4
|
+
depends on `@openpond/harness` for exact Harness identities but exposes only
|
|
5
|
+
evaluation APIs. Publish the matching Harness peer before Evals so npm can
|
|
6
|
+
satisfy the declared range.
|
|
32
7
|
|
|
33
8
|
```bash
|
|
34
9
|
pnpm evals:check
|
|
35
|
-
npm trust list @openpond/evals
|
|
36
|
-
npm view @openpond/evals version dist.integrity dist.attestations
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Later releases
|
|
40
|
-
|
|
41
|
-
For releases that do not already carry an intentional version bump, merge
|
|
42
|
-
feature work without changing the package version. When the intended package
|
|
43
|
-
changes are on `master`, prepare a separate release from a clean current
|
|
44
|
-
`master` checkout:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
10
|
pnpm release:evals:patch
|
|
48
11
|
# or release:evals:minor / release:evals:major
|
|
49
12
|
```
|
|
50
13
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
14
|
+
Merging a release PR triggers `release-evals.yml`, trusted npm publishing,
|
|
15
|
+
registry verification, provenance verification, and the package-specific tag.
|
|
16
|
+
Release notes must call out removal or movement of a public subpath and use the
|
|
17
|
+
appropriate semver increment.
|
|
18
|
+
|
|
19
|
+
Version `0.4.0` removes the retired Harness root barrel and
|
|
20
|
+
`./harness-improvements` / `./harness-workspaces` compatibility subpaths.
|
|
21
|
+
Consumers must import those public contracts from `@openpond/harness`; this is
|
|
22
|
+
an intentional breaking package-boundary correction.
|
|
@@ -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
|
+
}
|