@tangle-network/agent-eval 0.120.2 → 0.120.3
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/CHANGELOG.md +6 -0
- package/dist/analyst/index.d.ts +3111 -0
- package/dist/analyst/index.js +403 -0
- package/dist/analyst/index.js.map +1 -0
- package/dist/authenticity/index.d.ts +161 -0
- package/dist/authenticity/index.js +215 -0
- package/dist/authenticity/index.js.map +1 -0
- package/dist/belief-state/index.d.ts +1301 -0
- package/dist/belief-state/index.js +2152 -0
- package/dist/belief-state/index.js.map +1 -0
- package/dist/benchmarks/index.d.ts +974 -0
- package/dist/benchmarks/index.js +60 -0
- package/dist/benchmarks/index.js.map +1 -0
- package/dist/builder-eval/index.d.ts +695 -0
- package/dist/builder-eval/index.js +366 -0
- package/dist/builder-eval/index.js.map +1 -0
- package/dist/campaign/index.d.ts +7454 -0
- package/dist/campaign/index.js +272 -0
- package/dist/campaign/index.js.map +1 -0
- package/dist/chunk-3CDFMEMO.js +3878 -0
- package/dist/chunk-3CDFMEMO.js.map +1 -0
- package/dist/chunk-3RF76KTD.js +84 -0
- package/dist/chunk-3RF76KTD.js.map +1 -0
- package/dist/chunk-3XH4Y2SS.js +750 -0
- package/dist/chunk-3XH4Y2SS.js.map +1 -0
- package/dist/chunk-3YYRZDON.js +45 -0
- package/dist/chunk-3YYRZDON.js.map +1 -0
- package/dist/chunk-5BYTIDZ7.js +550 -0
- package/dist/chunk-5BYTIDZ7.js.map +1 -0
- package/dist/chunk-5CVUPHJ4.js +2668 -0
- package/dist/chunk-5CVUPHJ4.js.map +1 -0
- package/dist/chunk-ARU2PZFM.js +312 -0
- package/dist/chunk-ARU2PZFM.js.map +1 -0
- package/dist/chunk-BOD4O7OF.js +40 -0
- package/dist/chunk-BOD4O7OF.js.map +1 -0
- package/dist/chunk-CVJP5TMD.js +766 -0
- package/dist/chunk-CVJP5TMD.js.map +1 -0
- package/dist/chunk-DPZAEKA6.js +880 -0
- package/dist/chunk-DPZAEKA6.js.map +1 -0
- package/dist/chunk-DTJ6QUQB.js +131 -0
- package/dist/chunk-DTJ6QUQB.js.map +1 -0
- package/dist/chunk-GGE4NNQT.js +65 -0
- package/dist/chunk-GGE4NNQT.js.map +1 -0
- package/dist/chunk-H5UD2323.js +286 -0
- package/dist/chunk-H5UD2323.js.map +1 -0
- package/dist/chunk-HHWE3POT.js +94 -0
- package/dist/chunk-HHWE3POT.js.map +1 -0
- package/dist/chunk-HKUCJ437.js +787 -0
- package/dist/chunk-HKUCJ437.js.map +1 -0
- package/dist/chunk-JHCHEVET.js +274 -0
- package/dist/chunk-JHCHEVET.js.map +1 -0
- package/dist/chunk-K4DBDHLK.js +158 -0
- package/dist/chunk-K4DBDHLK.js.map +1 -0
- package/dist/chunk-K6N6XJJX.js +306 -0
- package/dist/chunk-K6N6XJJX.js.map +1 -0
- package/dist/chunk-MA6HLL3S.js +65 -0
- package/dist/chunk-MA6HLL3S.js.map +1 -0
- package/dist/chunk-MAZ26DC7.js +99 -0
- package/dist/chunk-MAZ26DC7.js.map +1 -0
- package/dist/chunk-MOXWMGPC.js +577 -0
- package/dist/chunk-MOXWMGPC.js.map +1 -0
- package/dist/chunk-NJC7U437.js +626 -0
- package/dist/chunk-NJC7U437.js.map +1 -0
- package/dist/chunk-NMN4WGSJ.js +1030 -0
- package/dist/chunk-NMN4WGSJ.js.map +1 -0
- package/dist/chunk-NPCTHQIO.js +91 -0
- package/dist/chunk-NPCTHQIO.js.map +1 -0
- package/dist/chunk-ONWEPEDO.js +57 -0
- package/dist/chunk-ONWEPEDO.js.map +1 -0
- package/dist/chunk-OYZAPX5G.js +1526 -0
- package/dist/chunk-OYZAPX5G.js.map +1 -0
- package/dist/chunk-P5MGQ2FY.js +7958 -0
- package/dist/chunk-P5MGQ2FY.js.map +1 -0
- package/dist/chunk-PC4UYEBM.js +166 -0
- package/dist/chunk-PC4UYEBM.js.map +1 -0
- package/dist/chunk-PJQFMIOX.js +1182 -0
- package/dist/chunk-PJQFMIOX.js.map +1 -0
- package/dist/chunk-PXD6ZFNY.js +1107 -0
- package/dist/chunk-PXD6ZFNY.js.map +1 -0
- package/dist/chunk-PXE2VKMX.js +140 -0
- package/dist/chunk-PXE2VKMX.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-QBRSJK47.js +622 -0
- package/dist/chunk-QBRSJK47.js.map +1 -0
- package/dist/chunk-S3UZOQ5Y.js +328 -0
- package/dist/chunk-S3UZOQ5Y.js.map +1 -0
- package/dist/chunk-SQQED7ZH.js +998 -0
- package/dist/chunk-SQQED7ZH.js.map +1 -0
- package/dist/chunk-SYV364BL.js +1266 -0
- package/dist/chunk-SYV364BL.js.map +1 -0
- package/dist/chunk-T4SQEITX.js +95 -0
- package/dist/chunk-T4SQEITX.js.map +1 -0
- package/dist/chunk-TT4KNT67.js +124 -0
- package/dist/chunk-TT4KNT67.js.map +1 -0
- package/dist/chunk-U5CHZ5M3.js +357 -0
- package/dist/chunk-U5CHZ5M3.js.map +1 -0
- package/dist/chunk-ULOKLHIQ.js +1937 -0
- package/dist/chunk-ULOKLHIQ.js.map +1 -0
- package/dist/chunk-VI2UW6B6.js +162 -0
- package/dist/chunk-VI2UW6B6.js.map +1 -0
- package/dist/chunk-VQMK5FMP.js +247 -0
- package/dist/chunk-VQMK5FMP.js.map +1 -0
- package/dist/chunk-VSMTAMNK.js +53 -0
- package/dist/chunk-VSMTAMNK.js.map +1 -0
- package/dist/chunk-VZSRQ272.js +149 -0
- package/dist/chunk-VZSRQ272.js.map +1 -0
- package/dist/chunk-WW2A73HW.js +159 -0
- package/dist/chunk-WW2A73HW.js.map +1 -0
- package/dist/chunk-X4UCIOTZ.js +136 -0
- package/dist/chunk-X4UCIOTZ.js.map +1 -0
- package/dist/chunk-XJYR7XFV.js +317 -0
- package/dist/chunk-XJYR7XFV.js.map +1 -0
- package/dist/chunk-ZET2UAYW.js +89 -0
- package/dist/chunk-ZET2UAYW.js.map +1 -0
- package/dist/chunk-ZMXDQ4K7.js +870 -0
- package/dist/chunk-ZMXDQ4K7.js.map +1 -0
- package/dist/chunk-ZZUXHH3R.js +99 -0
- package/dist/chunk-ZZUXHH3R.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract/index.d.ts +4972 -0
- package/dist/contract/index.js +1654 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/control.d.ts +1013 -0
- package/dist/control.js +34 -0
- package/dist/control.js.map +1 -0
- package/dist/fuzz.d.ts +759 -0
- package/dist/fuzz.js +714 -0
- package/dist/fuzz.js.map +1 -0
- package/dist/hosted/index.d.ts +730 -0
- package/dist/hosted/index.js +14 -0
- package/dist/hosted/index.js.map +1 -0
- package/dist/index.d.ts +16780 -0
- package/dist/index.js +12168 -0
- package/dist/index.js.map +1 -0
- package/dist/matrix/index.d.ts +155 -0
- package/dist/matrix/index.js +8 -0
- package/dist/matrix/index.js.map +1 -0
- package/dist/meta-eval/index.d.ts +1030 -0
- package/dist/meta-eval/index.js +417 -0
- package/dist/meta-eval/index.js.map +1 -0
- package/dist/multishot/index.d.ts +579 -0
- package/dist/multishot/index.js +589 -0
- package/dist/multishot/index.js.map +1 -0
- package/dist/openapi.json +992 -0
- package/dist/pipelines/index.d.ts +567 -0
- package/dist/pipelines/index.js +515 -0
- package/dist/pipelines/index.js.map +1 -0
- package/dist/reporting.d.ts +1277 -0
- package/dist/reporting.js +48 -0
- package/dist/reporting.js.map +1 -0
- package/dist/rl.d.ts +4092 -0
- package/dist/rl.js +1724 -0
- package/dist/rl.js.map +1 -0
- package/dist/run-campaign-75RTPVV5.js +14 -0
- package/dist/run-campaign-75RTPVV5.js.map +1 -0
- package/dist/storyboard/index.d.ts +279 -0
- package/dist/storyboard/index.js +767 -0
- package/dist/storyboard/index.js.map +1 -0
- package/dist/trace-attributes.d.ts +52 -0
- package/dist/trace-attributes.js +62 -0
- package/dist/trace-attributes.js.map +1 -0
- package/dist/traces.d.ts +2343 -0
- package/dist/traces.js +249 -0
- package/dist/traces.js.map +1 -0
- package/dist/wire/index.d.ts +1252 -0
- package/dist/wire/index.js +81 -0
- package/dist/wire/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1654 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromClaudeCodeSession,
|
|
3
|
+
fromCodexSession,
|
|
4
|
+
fromKimiCodeSession,
|
|
5
|
+
fromOpenCodeSession,
|
|
6
|
+
fromPiSession,
|
|
7
|
+
fromPigraphSession,
|
|
8
|
+
parseCodeAgentJsonl
|
|
9
|
+
} from "../chunk-HKUCJ437.js";
|
|
10
|
+
import {
|
|
11
|
+
createHostedClient
|
|
12
|
+
} from "../chunk-ZZUXHH3R.js";
|
|
13
|
+
import {
|
|
14
|
+
analyzeRuns,
|
|
15
|
+
summarizeExecution
|
|
16
|
+
} from "../chunk-SQQED7ZH.js";
|
|
17
|
+
import {
|
|
18
|
+
REFERENCE_EQUIVALENCE_INPUT_LIMITS,
|
|
19
|
+
REFERENCE_EQUIVALENCE_JUDGE_VERSION,
|
|
20
|
+
buildEvidenceVector,
|
|
21
|
+
buildLoopProvenanceRecord,
|
|
22
|
+
campaignBreakdown,
|
|
23
|
+
campaignMeanComposite,
|
|
24
|
+
canonicalDigest,
|
|
25
|
+
composeGate,
|
|
26
|
+
createReferenceEquivalenceJudge,
|
|
27
|
+
defaultProductionGate,
|
|
28
|
+
emitLoopProvenance,
|
|
29
|
+
evolutionaryProposer,
|
|
30
|
+
gepaProposer,
|
|
31
|
+
heldOutGate,
|
|
32
|
+
loopProvenanceArgsFromResult,
|
|
33
|
+
pairArms,
|
|
34
|
+
paretoPolicy,
|
|
35
|
+
paretoSignificanceGate,
|
|
36
|
+
powerPreflight,
|
|
37
|
+
renderSurfaceDiff,
|
|
38
|
+
runEval,
|
|
39
|
+
runImprovementLoop,
|
|
40
|
+
runReferenceEquivalenceJudge,
|
|
41
|
+
surfaceContentHash,
|
|
42
|
+
surfaceHash,
|
|
43
|
+
verifyLoopProvenanceRecord
|
|
44
|
+
} from "../chunk-3CDFMEMO.js";
|
|
45
|
+
import {
|
|
46
|
+
assertCampaignSplitIdentity,
|
|
47
|
+
assertRealAgentReceipts,
|
|
48
|
+
campaignCoverage,
|
|
49
|
+
campaignSplitDigest,
|
|
50
|
+
createRunCostLedger,
|
|
51
|
+
fsCampaignStorage,
|
|
52
|
+
inMemoryCampaignStorage,
|
|
53
|
+
resolveRunDir,
|
|
54
|
+
runCampaign
|
|
55
|
+
} from "../chunk-SYV364BL.js";
|
|
56
|
+
import {
|
|
57
|
+
buildDefaultAnalystRegistry,
|
|
58
|
+
createChatClient
|
|
59
|
+
} from "../chunk-5BYTIDZ7.js";
|
|
60
|
+
import "../chunk-HHWE3POT.js";
|
|
61
|
+
import "../chunk-5CVUPHJ4.js";
|
|
62
|
+
import {
|
|
63
|
+
FileSystemOutcomeStore,
|
|
64
|
+
InMemoryOutcomeStore
|
|
65
|
+
} from "../chunk-3RF76KTD.js";
|
|
66
|
+
import "../chunk-ARU2PZFM.js";
|
|
67
|
+
import "../chunk-NJC7U437.js";
|
|
68
|
+
import "../chunk-DPZAEKA6.js";
|
|
69
|
+
import {
|
|
70
|
+
pairedBootstrap
|
|
71
|
+
} from "../chunk-PJQFMIOX.js";
|
|
72
|
+
import {
|
|
73
|
+
CostLedger
|
|
74
|
+
} from "../chunk-ZMXDQ4K7.js";
|
|
75
|
+
import "../chunk-VI2UW6B6.js";
|
|
76
|
+
import {
|
|
77
|
+
recordAggregateMeasurements,
|
|
78
|
+
summarizeExecutionMeasurements
|
|
79
|
+
} from "../chunk-H5UD2323.js";
|
|
80
|
+
import "../chunk-PXE2VKMX.js";
|
|
81
|
+
import "../chunk-ZET2UAYW.js";
|
|
82
|
+
import "../chunk-GGE4NNQT.js";
|
|
83
|
+
import "../chunk-PXD6ZFNY.js";
|
|
84
|
+
import "../chunk-PC4UYEBM.js";
|
|
85
|
+
import {
|
|
86
|
+
parseRunRecordSafe
|
|
87
|
+
} from "../chunk-S3UZOQ5Y.js";
|
|
88
|
+
import "../chunk-MA6HLL3S.js";
|
|
89
|
+
import "../chunk-XJYR7XFV.js";
|
|
90
|
+
import "../chunk-VSMTAMNK.js";
|
|
91
|
+
import "../chunk-ONWEPEDO.js";
|
|
92
|
+
import {
|
|
93
|
+
LLM_MODEL_ATTR_KEYS
|
|
94
|
+
} from "../chunk-K4DBDHLK.js";
|
|
95
|
+
import "../chunk-PZ5AY32C.js";
|
|
96
|
+
|
|
97
|
+
// src/contract/self-improve.ts
|
|
98
|
+
import { createHash } from "crypto";
|
|
99
|
+
var SelfImproveRunError = class extends Error {
|
|
100
|
+
cost;
|
|
101
|
+
receipts;
|
|
102
|
+
constructor(cause, ledger) {
|
|
103
|
+
const original = cause instanceof Error ? cause : new Error(String(cause));
|
|
104
|
+
super(original.message, { cause: original });
|
|
105
|
+
this.name = "SelfImproveRunError";
|
|
106
|
+
this.cost = ledger.summary();
|
|
107
|
+
this.receipts = ledger.list();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
function splitTrainHoldout(scenarios, fraction) {
|
|
111
|
+
function hash(s) {
|
|
112
|
+
let h = 2166136261 >>> 0;
|
|
113
|
+
for (let i = 0; i < s.length; i++) {
|
|
114
|
+
h ^= s.charCodeAt(i);
|
|
115
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
116
|
+
}
|
|
117
|
+
return h;
|
|
118
|
+
}
|
|
119
|
+
const sorted = [...scenarios].sort((a, b) => hash(a.id) - hash(b.id));
|
|
120
|
+
const nHoldout = Math.max(1, Math.min(sorted.length - 1, Math.round(sorted.length * fraction)));
|
|
121
|
+
return {
|
|
122
|
+
holdout: sorted.slice(0, nHoldout),
|
|
123
|
+
train: sorted.slice(nHoldout)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function meanComposite(byScenario) {
|
|
127
|
+
const perScenario = {};
|
|
128
|
+
const values = [];
|
|
129
|
+
for (const [id, agg] of Object.entries(byScenario)) {
|
|
130
|
+
perScenario[id] = agg.meanComposite;
|
|
131
|
+
values.push(agg.meanComposite);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
compositeMean: values.length === 0 ? 0 : values.reduce((s, v) => s + v, 0) / values.length,
|
|
135
|
+
perScenario
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function selfImprove(opts) {
|
|
139
|
+
const startedAt = Date.now();
|
|
140
|
+
const requestedRunDir = opts.runDir ?? `mem://selfImprove-${startedAt}`;
|
|
141
|
+
const runDir = resolveRunDir(requestedRunDir);
|
|
142
|
+
const storage = opts.storage ?? (runDir.startsWith("mem://") ? inMemoryCampaignStorage() : fsCampaignStorage());
|
|
143
|
+
const costLedger = createRunCostLedger({
|
|
144
|
+
storage,
|
|
145
|
+
runDir,
|
|
146
|
+
costCeilingUsd: opts.budget?.dollars
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
return await runSelfImprove(opts, costLedger, startedAt, runDir, storage);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new SelfImproveRunError(error, costLedger);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
|
|
155
|
+
const budget = opts.budget ?? {};
|
|
156
|
+
const generations = budget.generations ?? 3;
|
|
157
|
+
const populationSize = budget.populationSize ?? 2;
|
|
158
|
+
const maxConcurrency = budget.maxConcurrency ?? 2;
|
|
159
|
+
const holdoutFraction = budget.holdoutFraction ?? 0.25;
|
|
160
|
+
const expectUsage = opts.expectUsage ?? "assert";
|
|
161
|
+
const explicitHoldout = budget.holdoutScenarios;
|
|
162
|
+
const { train, holdout } = explicitHoldout ? {
|
|
163
|
+
train: opts.scenarios.filter((s) => !explicitHoldout.some((h) => h.id === s.id)),
|
|
164
|
+
holdout: explicitHoldout
|
|
165
|
+
} : splitTrainHoldout(opts.scenarios, holdoutFraction);
|
|
166
|
+
if (train.length === 0) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
"selfImprove: train split is empty. Reduce holdoutFraction or pass more scenarios."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (holdout.length === 0) {
|
|
172
|
+
throw new Error("selfImprove: holdout split is empty. Pass more scenarios.");
|
|
173
|
+
}
|
|
174
|
+
const proposer = opts.proposer ?? gepaProposer({
|
|
175
|
+
llm: {
|
|
176
|
+
baseUrl: opts.llm?.baseUrl ?? "https://router.tangle.tools/v1",
|
|
177
|
+
apiKey: opts.llm?.apiKey ?? process.env.OPENAI_API_KEY ?? ""
|
|
178
|
+
},
|
|
179
|
+
model: opts.llm?.model ?? "anthropic/claude-sonnet-4.6",
|
|
180
|
+
target: opts.proposerTarget ?? "agent surface (system prompt or config) being optimized by selfImprove",
|
|
181
|
+
// Pass-through: when unset, gepaProposer falls back to its own
|
|
182
|
+
// domain-neutral engineering primitives.
|
|
183
|
+
mutationPrimitives: opts.mutationPrimitives
|
|
184
|
+
});
|
|
185
|
+
const gate = opts.gate ?? defaultProductionGate({
|
|
186
|
+
holdoutScenarios: holdout,
|
|
187
|
+
deltaThreshold: 0.05
|
|
188
|
+
});
|
|
189
|
+
if (opts.onProgress) {
|
|
190
|
+
opts.onProgress({ kind: "baseline.started", scenarios: opts.scenarios.length });
|
|
191
|
+
}
|
|
192
|
+
const result = await runImprovementLoop({
|
|
193
|
+
scenarios: train,
|
|
194
|
+
baselineSurface: opts.baselineSurface,
|
|
195
|
+
dispatchWithSurface: opts.agent,
|
|
196
|
+
proposer,
|
|
197
|
+
judges: [opts.judge],
|
|
198
|
+
populationSize,
|
|
199
|
+
maxGenerations: generations,
|
|
200
|
+
promoteTopK: budget.promoteTopK,
|
|
201
|
+
reps: budget.reps,
|
|
202
|
+
holdoutScenarios: holdout,
|
|
203
|
+
gate,
|
|
204
|
+
neutralize: opts.neutralize,
|
|
205
|
+
autoOnPromote: opts.autoOnPromote ?? "none",
|
|
206
|
+
ghOwner: opts.ghOwner,
|
|
207
|
+
ghRepo: opts.ghRepo,
|
|
208
|
+
storage,
|
|
209
|
+
runDir,
|
|
210
|
+
maxConcurrency,
|
|
211
|
+
cellPlacement: opts.cellPlacement,
|
|
212
|
+
dispatchTimeoutMs: opts.dispatchTimeoutMs,
|
|
213
|
+
costLedger,
|
|
214
|
+
expectUsage,
|
|
215
|
+
labeledStore: opts.labeledStore,
|
|
216
|
+
captureSource: opts.captureSource,
|
|
217
|
+
analyzeGeneration: opts.analyzeGeneration,
|
|
218
|
+
findings: opts.findings
|
|
219
|
+
});
|
|
220
|
+
const baseline = meanComposite(result.baselineOnHoldout.aggregates.byScenario);
|
|
221
|
+
const winnerStats = meanComposite(result.winnerOnHoldout.aggregates.byScenario);
|
|
222
|
+
let power;
|
|
223
|
+
const baselineHoldoutComposites = result.baselineOnHoldout.cells.filter((cell) => !cell.error).map((cell) => {
|
|
224
|
+
const scores = Object.values(cell.judgeScores);
|
|
225
|
+
return scores.length === 0 ? Number.NaN : scores.reduce((sum, s) => sum + s.composite, 0) / scores.length;
|
|
226
|
+
}).filter((v) => Number.isFinite(v));
|
|
227
|
+
if (baselineHoldoutComposites.length >= 3) {
|
|
228
|
+
power = powerPreflight({
|
|
229
|
+
baselineComposites: baselineHoldoutComposites,
|
|
230
|
+
sharedScorerChannel: true
|
|
231
|
+
});
|
|
232
|
+
if (opts.onProgress) {
|
|
233
|
+
opts.onProgress({
|
|
234
|
+
kind: "power.estimated",
|
|
235
|
+
n: power.n,
|
|
236
|
+
sd: power.sd,
|
|
237
|
+
mde: power.mde,
|
|
238
|
+
underpowered: power.underpowered
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (power.underpowered && generations > 0) {
|
|
242
|
+
console.warn(`[selfImprove] ${power.recommendation}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (opts.onProgress) {
|
|
246
|
+
opts.onProgress({
|
|
247
|
+
kind: "baseline.completed",
|
|
248
|
+
compositeMean: baseline.compositeMean,
|
|
249
|
+
durationMs: Date.now() - startedAt
|
|
250
|
+
});
|
|
251
|
+
opts.onProgress({
|
|
252
|
+
kind: "gate.decided",
|
|
253
|
+
decision: result.gateResult.decision,
|
|
254
|
+
lift: winnerStats.compositeMean - baseline.compositeMean
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const cost = result.cost;
|
|
258
|
+
const totalCost = cost.totalCostUsd;
|
|
259
|
+
const insight = await analyzeRuns({
|
|
260
|
+
runs: [
|
|
261
|
+
...cellsToRunRecords(result.baselineCampaign.cells, "baseline", runDir, opts.baselineSurface),
|
|
262
|
+
...cellsToRunRecords(result.winnerOnHoldout.cells, "winner", runDir, result.winnerSurface)
|
|
263
|
+
],
|
|
264
|
+
baselineCandidateId: "baseline",
|
|
265
|
+
candidateCandidateId: "winner"
|
|
266
|
+
});
|
|
267
|
+
const durationMs = Date.now() - startedAt;
|
|
268
|
+
const { record: provenance } = await emitLoopProvenance({
|
|
269
|
+
...loopProvenanceArgsFromResult({
|
|
270
|
+
runId: `${runDir}#${startedAt}`,
|
|
271
|
+
runDir,
|
|
272
|
+
timestamp: new Date(startedAt).toISOString(),
|
|
273
|
+
baselineSurface: opts.baselineSurface,
|
|
274
|
+
result,
|
|
275
|
+
costReceipts: costLedger.list(),
|
|
276
|
+
totalCostUsd: totalCost,
|
|
277
|
+
totalDurationMs: durationMs
|
|
278
|
+
}),
|
|
279
|
+
storage,
|
|
280
|
+
hostedClient: opts.hostedTenant ? createHostedClient(opts.hostedTenant) : void 0
|
|
281
|
+
});
|
|
282
|
+
if (opts.onProvenance) opts.onProvenance(provenance);
|
|
283
|
+
const summary = {
|
|
284
|
+
baseline,
|
|
285
|
+
winner: {
|
|
286
|
+
...winnerStats,
|
|
287
|
+
surface: result.winnerSurface,
|
|
288
|
+
...result.winnerLabel ? { label: result.winnerLabel } : {},
|
|
289
|
+
...result.winnerRationale ? { rationale: result.winnerRationale } : {}
|
|
290
|
+
},
|
|
291
|
+
lift: winnerStats.compositeMean - baseline.compositeMean,
|
|
292
|
+
diff: result.promotedDiff,
|
|
293
|
+
provenance,
|
|
294
|
+
gateDecision: result.gateResult.decision,
|
|
295
|
+
generationsExplored: result.generations.length,
|
|
296
|
+
durationMs,
|
|
297
|
+
totalCostUsd: totalCost,
|
|
298
|
+
cost,
|
|
299
|
+
receipts: costLedger.list(),
|
|
300
|
+
insight,
|
|
301
|
+
...power ? { power } : {},
|
|
302
|
+
raw: result
|
|
303
|
+
};
|
|
304
|
+
if (opts.hostedTenant) {
|
|
305
|
+
try {
|
|
306
|
+
await shipEvalRunToHosted(opts.hostedTenant, opts, summary, result, runDir);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
309
|
+
console.warn(`[agent-eval] hosted ingest failed (continuing): ${msg}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return summary;
|
|
313
|
+
}
|
|
314
|
+
async function shipEvalRunToHosted(tenant, opts, summary, raw, runDir) {
|
|
315
|
+
const client = createHostedClient(tenant);
|
|
316
|
+
function snapshotFromCampaign(index, surface, campaign, durationMs) {
|
|
317
|
+
const cells = campaign.cells.map((cell) => {
|
|
318
|
+
const judgeScores = Object.values(cell.judgeScores);
|
|
319
|
+
const composite = judgeScores.length === 0 ? 0 : judgeScores.reduce((s, j) => s + j.composite, 0) / judgeScores.length;
|
|
320
|
+
return {
|
|
321
|
+
scenarioId: cell.scenarioId,
|
|
322
|
+
rep: cell.rep,
|
|
323
|
+
compositeMean: composite,
|
|
324
|
+
dimensions: Object.fromEntries(
|
|
325
|
+
Object.entries(cell.judgeScores).map(([name, score]) => [name, score.dimensions])
|
|
326
|
+
),
|
|
327
|
+
errorMessage: cell.error ?? void 0
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
const compositeMean = cells.length === 0 ? 0 : cells.reduce((s, c) => s + c.compositeMean, 0) / cells.length;
|
|
331
|
+
return {
|
|
332
|
+
index,
|
|
333
|
+
surfaceHash: surfaceHash(surface),
|
|
334
|
+
surface,
|
|
335
|
+
cells,
|
|
336
|
+
compositeMean,
|
|
337
|
+
costUsd: campaign.aggregates.totalCostUsd,
|
|
338
|
+
durationMs
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const generations = [];
|
|
342
|
+
generations.push(snapshotFromCampaign(0, opts.baselineSurface, raw.baselineCampaign, 0));
|
|
343
|
+
for (const gen of raw.generations) {
|
|
344
|
+
const winner = gen.surfaces.reduce(
|
|
345
|
+
(best, s) => s.campaign.aggregates.cellsExecuted > 0 && (best === void 0 || averageComposite(s.campaign) > averageComposite(best.campaign)) ? s : best,
|
|
346
|
+
gen.surfaces[0]
|
|
347
|
+
);
|
|
348
|
+
if (!winner) continue;
|
|
349
|
+
generations.push(
|
|
350
|
+
snapshotFromCampaign(gen.record.generationIndex + 1, winner.surface, winner.campaign, 0)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const event = {
|
|
354
|
+
runId: `${runDir}#${Date.now()}`,
|
|
355
|
+
runDir,
|
|
356
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
|
+
status: "finished",
|
|
358
|
+
labels: opts.hostedLabels ?? {},
|
|
359
|
+
baseline: generations[0],
|
|
360
|
+
generations,
|
|
361
|
+
gateDecision: summary.gateDecision,
|
|
362
|
+
holdoutLift: summary.lift,
|
|
363
|
+
totalCostUsd: summary.totalCostUsd,
|
|
364
|
+
totalDurationMs: summary.durationMs,
|
|
365
|
+
insightReport: summary.insight
|
|
366
|
+
};
|
|
367
|
+
await client.ingestEvalRun(event);
|
|
368
|
+
}
|
|
369
|
+
function averageComposite(campaign) {
|
|
370
|
+
const aggs = Object.values(campaign.aggregates.byScenario);
|
|
371
|
+
return aggs.length === 0 ? 0 : aggs.reduce((s, a) => s + a.meanComposite, 0) / aggs.length;
|
|
372
|
+
}
|
|
373
|
+
function hashString(s) {
|
|
374
|
+
let h = 2166136261 >>> 0;
|
|
375
|
+
for (let i = 0; i < s.length; i++) {
|
|
376
|
+
h ^= s.charCodeAt(i);
|
|
377
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
378
|
+
}
|
|
379
|
+
return h.toString(16).padStart(8, "0");
|
|
380
|
+
}
|
|
381
|
+
function cellsToRunRecords(cells, candidateId, runId, surface) {
|
|
382
|
+
const promptHash = surfaceContentHash(surface);
|
|
383
|
+
const configHash = `sha256:${createHash("sha256").update(candidateId).digest("hex")}`;
|
|
384
|
+
return cells.map((cell) => {
|
|
385
|
+
const perJudge = {};
|
|
386
|
+
const perDimMeanAccum = {};
|
|
387
|
+
let compositeSum = 0;
|
|
388
|
+
let compositeCount = 0;
|
|
389
|
+
for (const [judgeId, score] of Object.entries(cell.judgeScores)) {
|
|
390
|
+
perJudge[judgeId] = { ...score.dimensions };
|
|
391
|
+
for (const [dim, value] of Object.entries(score.dimensions)) {
|
|
392
|
+
if (!Number.isFinite(value)) continue;
|
|
393
|
+
const accum = perDimMeanAccum[dim] ?? { sum: 0, n: 0 };
|
|
394
|
+
accum.sum += value;
|
|
395
|
+
accum.n += 1;
|
|
396
|
+
perDimMeanAccum[dim] = accum;
|
|
397
|
+
}
|
|
398
|
+
if (Number.isFinite(score.composite)) {
|
|
399
|
+
compositeSum += score.composite;
|
|
400
|
+
compositeCount += 1;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const perDimMean = {};
|
|
404
|
+
for (const [dim, { sum, n }] of Object.entries(perDimMeanAccum)) {
|
|
405
|
+
perDimMean[dim] = n === 0 ? 0 : sum / n;
|
|
406
|
+
}
|
|
407
|
+
const composite = compositeCount === 0 ? 0 : compositeSum / compositeCount;
|
|
408
|
+
const judgeScores = {
|
|
409
|
+
perJudge,
|
|
410
|
+
perDimMean,
|
|
411
|
+
composite
|
|
412
|
+
};
|
|
413
|
+
return {
|
|
414
|
+
runId: `${runId}::${candidateId}::${cell.cellId}`,
|
|
415
|
+
experimentId: runId,
|
|
416
|
+
candidateId,
|
|
417
|
+
// Pair on (scenarioId, rep) — analyzeRuns pairs on (experimentId, seed).
|
|
418
|
+
// Synthesize a stable seed for that pairing.
|
|
419
|
+
seed: cell.rep * 1e6 + hashString(cell.scenarioId).slice(0, 6).split("").reduce((a, c) => a * 31 + c.charCodeAt(0) >>> 0, 0),
|
|
420
|
+
model: "campaign-cell",
|
|
421
|
+
promptHash,
|
|
422
|
+
configHash,
|
|
423
|
+
commitSha: "cell",
|
|
424
|
+
wallMs: cell.durationMs,
|
|
425
|
+
costUsd: cell.costUsd,
|
|
426
|
+
tokenUsage: {
|
|
427
|
+
input: cell.tokenUsage.input,
|
|
428
|
+
output: cell.tokenUsage.output,
|
|
429
|
+
...cell.tokenUsage.reasoning === void 0 ? {} : { reasoning: cell.tokenUsage.reasoning },
|
|
430
|
+
...cell.tokenUsage.cached === void 0 ? {} : { cached: cell.tokenUsage.cached },
|
|
431
|
+
...cell.tokenUsage.cacheWrite === void 0 ? {} : { cacheWrite: cell.tokenUsage.cacheWrite }
|
|
432
|
+
},
|
|
433
|
+
outcome: {
|
|
434
|
+
holdoutScore: composite,
|
|
435
|
+
raw: {},
|
|
436
|
+
judgeScores
|
|
437
|
+
},
|
|
438
|
+
splitTag: "holdout",
|
|
439
|
+
...cell.error ? { failureMode: cell.error } : {}
|
|
440
|
+
};
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/contract/define-agent-eval.ts
|
|
445
|
+
function defineAgentEval(defaults) {
|
|
446
|
+
const defaultEvaluateOptions = evaluateDefaults(defaults);
|
|
447
|
+
return {
|
|
448
|
+
scenarios: defaults.scenarios,
|
|
449
|
+
baselineSurface: defaults.baselineSurface,
|
|
450
|
+
async evaluate(opts = {}) {
|
|
451
|
+
const { agent, judge, judges, runDir, scenarios, surface, ...campaignOpts } = opts;
|
|
452
|
+
const selectedAgent = agent ?? defaults.agent;
|
|
453
|
+
const selectedSurface = surface ?? defaults.baselineSurface;
|
|
454
|
+
const selectedRunDir = runDir ?? defaults.runDir ?? `mem://defineAgentEval-${Date.now()}`;
|
|
455
|
+
const selectedStorage = campaignOpts.storage ?? defaultEvaluateOptions.storage ?? (selectedRunDir.startsWith("mem://") ? inMemoryCampaignStorage() : void 0);
|
|
456
|
+
const evalOptions = {
|
|
457
|
+
...defaultEvaluateOptions,
|
|
458
|
+
...campaignOpts,
|
|
459
|
+
...selectedStorage ? { storage: selectedStorage } : {},
|
|
460
|
+
runDir: selectedRunDir,
|
|
461
|
+
scenarios: scenarios ?? defaults.scenarios,
|
|
462
|
+
dispatch: (scenario, ctx) => selectedAgent(selectedSurface, scenario, ctx),
|
|
463
|
+
judges: evaluateJudges(judges, judge ?? defaults.judge)
|
|
464
|
+
};
|
|
465
|
+
if (evalOptions.reps !== void 0)
|
|
466
|
+
evalOptions.reps = requirePositiveInteger(evalOptions.reps, "reps");
|
|
467
|
+
return runEval(evalOptions);
|
|
468
|
+
},
|
|
469
|
+
async improve(opts = {}) {
|
|
470
|
+
const {
|
|
471
|
+
budget: budgetOverride,
|
|
472
|
+
hostedTenant: hostedTenantOverride,
|
|
473
|
+
llm: llmOverride,
|
|
474
|
+
...topLevelOverrides
|
|
475
|
+
} = opts;
|
|
476
|
+
const merged = mergeDefined(defaults, topLevelOverrides);
|
|
477
|
+
const budget = mergeBudget(defaults.budget, budgetOverride);
|
|
478
|
+
const llm = mergeLlm(defaults.llm, llmOverride);
|
|
479
|
+
const hostedTenant = mergeHostedTenant(defaults.hostedTenant, hostedTenantOverride);
|
|
480
|
+
return selfImprove({
|
|
481
|
+
...merged,
|
|
482
|
+
...budget ? { budget } : {},
|
|
483
|
+
...llm ? { llm } : {},
|
|
484
|
+
...hostedTenant ? { hostedTenant } : {}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function evaluateDefaults(defaults) {
|
|
490
|
+
const out = {};
|
|
491
|
+
if (defaults.storage) out.storage = defaults.storage;
|
|
492
|
+
if (defaults.labeledStore) out.labeledStore = defaults.labeledStore;
|
|
493
|
+
if (defaults.captureSource) out.captureSource = defaults.captureSource;
|
|
494
|
+
if (defaults.cellPlacement) out.cellPlacement = defaults.cellPlacement;
|
|
495
|
+
if (defaults.expectUsage) out.expectUsage = defaults.expectUsage;
|
|
496
|
+
if (defaults.budget?.dollars !== void 0) out.costCeiling = defaults.budget.dollars;
|
|
497
|
+
if (defaults.budget?.maxConcurrency !== void 0)
|
|
498
|
+
out.maxConcurrency = defaults.budget.maxConcurrency;
|
|
499
|
+
if (defaults.budget?.reps !== void 0)
|
|
500
|
+
out.reps = requirePositiveInteger(defaults.budget.reps, "budget.reps");
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function mergeBudget(defaults, overrides) {
|
|
504
|
+
const merged = mergeOptionalObject(defaults, overrides);
|
|
505
|
+
if (merged?.reps !== void 0) merged.reps = requirePositiveInteger(merged.reps, "budget.reps");
|
|
506
|
+
return merged;
|
|
507
|
+
}
|
|
508
|
+
function mergeLlm(defaults, overrides) {
|
|
509
|
+
return mergeOptionalObject(defaults, overrides);
|
|
510
|
+
}
|
|
511
|
+
function mergeHostedTenant(defaults, overrides) {
|
|
512
|
+
const merged = mergeOptionalObject(defaults, overrides);
|
|
513
|
+
if (!merged) return void 0;
|
|
514
|
+
if (!merged.endpoint?.trim() || !merged.apiKey?.trim() || !merged.tenantId?.trim()) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
"defineAgentEval.improve: hostedTenant requires endpoint, apiKey, and tenantId after merging defaults and overrides"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return merged;
|
|
520
|
+
}
|
|
521
|
+
function mergeDefined(defaults, overrides) {
|
|
522
|
+
if (!overrides) return defaults;
|
|
523
|
+
const merged = { ...defaults };
|
|
524
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
525
|
+
if (value !== void 0) merged[key] = value;
|
|
526
|
+
}
|
|
527
|
+
return merged;
|
|
528
|
+
}
|
|
529
|
+
function mergeOptionalObject(defaults, overrides) {
|
|
530
|
+
if (!defaults && !overrides) return void 0;
|
|
531
|
+
return mergeDefined(defaults ?? {}, overrides);
|
|
532
|
+
}
|
|
533
|
+
function evaluateJudges(judges, defaultJudge) {
|
|
534
|
+
if (judges !== void 0) {
|
|
535
|
+
if (judges.length === 0) {
|
|
536
|
+
throw new Error("defineAgentEval.evaluate: judges must not be empty");
|
|
537
|
+
}
|
|
538
|
+
return judges;
|
|
539
|
+
}
|
|
540
|
+
return [defaultJudge];
|
|
541
|
+
}
|
|
542
|
+
function requirePositiveInteger(value, field) {
|
|
543
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
544
|
+
throw new Error(`defineAgentEval: ${field} must be a positive integer`);
|
|
545
|
+
}
|
|
546
|
+
return value;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/contract/measured-comparison.ts
|
|
550
|
+
import {
|
|
551
|
+
agentImprovementMeasuredComparisonSchema
|
|
552
|
+
} from "@tangle-network/agent-interface";
|
|
553
|
+
function measuredComparisonFromSelfImproveResult(options) {
|
|
554
|
+
const { result } = options;
|
|
555
|
+
verifyLoopProvenanceRecord(result.provenance);
|
|
556
|
+
const power = result.power;
|
|
557
|
+
if (!power) throw new Error("agent improvement comparison requires heldout power analysis");
|
|
558
|
+
if (result.provenance.gate.reasons.length === 0) {
|
|
559
|
+
throw new Error("agent improvement comparison requires measured decision reasons");
|
|
560
|
+
}
|
|
561
|
+
const receiptLedger = new CostLedger({ receipts: result.receipts });
|
|
562
|
+
const receiptCost = receiptLedger.summary();
|
|
563
|
+
const receipts = receiptLedger.list();
|
|
564
|
+
assertRealAgentReceipts(receipts, { allowMixed: false });
|
|
565
|
+
const receiptsById = new Map(receipts.map((receipt) => [receipt.callId, receipt]));
|
|
566
|
+
const receiptIdsByCell = indexReceiptIdsByCell(receipts);
|
|
567
|
+
assertCompleteMeasuredCampaign(
|
|
568
|
+
result.raw.baselineOnHoldout,
|
|
569
|
+
"heldout baseline",
|
|
570
|
+
receiptsById,
|
|
571
|
+
receiptIdsByCell
|
|
572
|
+
);
|
|
573
|
+
assertCompleteMeasuredCampaign(
|
|
574
|
+
result.raw.winnerOnHoldout,
|
|
575
|
+
"heldout candidate",
|
|
576
|
+
receiptsById,
|
|
577
|
+
receiptIdsByCell
|
|
578
|
+
);
|
|
579
|
+
const pairs = pairMeasuredCells(
|
|
580
|
+
result.raw.baselineOnHoldout.cells,
|
|
581
|
+
result.raw.winnerOnHoldout.cells
|
|
582
|
+
);
|
|
583
|
+
const composite = measuredObjective(
|
|
584
|
+
{
|
|
585
|
+
kind: "objective",
|
|
586
|
+
name: "composite",
|
|
587
|
+
direction: "higher-is-better",
|
|
588
|
+
unit: "score"
|
|
589
|
+
},
|
|
590
|
+
pairs,
|
|
591
|
+
measuredComposite
|
|
592
|
+
);
|
|
593
|
+
assertMeasuredNumber(result.lift, composite.delta, "heldout lift");
|
|
594
|
+
assertMeasuredNumber(result.baseline.compositeMean, composite.baseline, "heldout baseline");
|
|
595
|
+
assertMeasuredNumber(result.winner.compositeMean, composite.candidate, "heldout candidate");
|
|
596
|
+
const baselineContentHash = surfaceContentHash(options.baselineSurface);
|
|
597
|
+
const candidateContentHash = surfaceContentHash(result.winner.surface);
|
|
598
|
+
const canonicalDiff = baselineContentHash === candidateContentHash ? "" : renderSurfaceDiff(result.raw.winnerSurface, options.baselineSurface);
|
|
599
|
+
assertMeasuredIdentity(result.gateDecision, result.raw.gateResult.decision, "raw decision");
|
|
600
|
+
assertMeasuredIdentity(
|
|
601
|
+
candidateContentHash,
|
|
602
|
+
surfaceContentHash(result.raw.winnerSurface),
|
|
603
|
+
"raw winner surface"
|
|
604
|
+
);
|
|
605
|
+
assertMeasuredIdentity(result.diff, canonicalDiff, "surface diff");
|
|
606
|
+
assertMeasuredIdentity(result.raw.promotedDiff, canonicalDiff, "raw surface diff");
|
|
607
|
+
if (result.gateDecision === "ship" && (baselineContentHash === candidateContentHash || result.diff.trim().length === 0)) {
|
|
608
|
+
throw new Error("a shipped improvement requires a changed surface and non-empty diff");
|
|
609
|
+
}
|
|
610
|
+
assertMeasuredIdentity(
|
|
611
|
+
canonicalDigest(power),
|
|
612
|
+
canonicalDigest(
|
|
613
|
+
powerPreflight({
|
|
614
|
+
baselineComposites: pairs.map(([cell]) => measuredComposite(cell)),
|
|
615
|
+
sharedScorerChannel: true
|
|
616
|
+
})
|
|
617
|
+
),
|
|
618
|
+
"power analysis"
|
|
619
|
+
);
|
|
620
|
+
assertMeasuredNumber(power.n, composite.n, "power sample size");
|
|
621
|
+
assertMeasuredNumber(power.confidence, 0.95, "power confidence");
|
|
622
|
+
assertCompleteMeasuredCampaign(
|
|
623
|
+
result.raw.baselineCampaign,
|
|
624
|
+
"search baseline",
|
|
625
|
+
receiptsById,
|
|
626
|
+
receiptIdsByCell
|
|
627
|
+
);
|
|
628
|
+
assertGenerationMeasurements(result.raw.generations, receiptsById, receiptIdsByCell);
|
|
629
|
+
if (result.raw.neutralizedOnHoldout === void 0 !== (result.raw.neutralizedSurface === void 0)) {
|
|
630
|
+
throw new Error("neutralized surface and campaign must be supplied together");
|
|
631
|
+
}
|
|
632
|
+
if (result.raw.neutralizedOnHoldout) {
|
|
633
|
+
assertCompleteMeasuredCampaign(
|
|
634
|
+
result.raw.neutralizedOnHoldout,
|
|
635
|
+
"heldout neutralized",
|
|
636
|
+
receiptsById,
|
|
637
|
+
receiptIdsByCell
|
|
638
|
+
);
|
|
639
|
+
pairMeasuredCells(result.raw.baselineOnHoldout.cells, result.raw.neutralizedOnHoldout.cells);
|
|
640
|
+
}
|
|
641
|
+
const rebuiltProvenance = buildLoopProvenanceRecord(
|
|
642
|
+
loopProvenanceArgsFromResult({
|
|
643
|
+
runId: result.provenance.runId,
|
|
644
|
+
runDir: result.provenance.runDir,
|
|
645
|
+
timestamp: result.provenance.timestamp,
|
|
646
|
+
baselineSurface: options.baselineSurface,
|
|
647
|
+
result: result.raw,
|
|
648
|
+
costReceipts: receipts,
|
|
649
|
+
totalCostUsd: result.totalCostUsd,
|
|
650
|
+
totalDurationMs: result.durationMs
|
|
651
|
+
})
|
|
652
|
+
);
|
|
653
|
+
assertMeasuredIdentity(
|
|
654
|
+
result.provenance.recordDigest,
|
|
655
|
+
rebuiltProvenance.recordDigest,
|
|
656
|
+
"provenance record"
|
|
657
|
+
);
|
|
658
|
+
assertMeasuredIdentity(
|
|
659
|
+
options.benchmark.splitDigest,
|
|
660
|
+
rebuiltProvenance.evidence.holdout.splitDigest,
|
|
661
|
+
"benchmark heldout split"
|
|
662
|
+
);
|
|
663
|
+
const generationsExplored = result.raw.generations.length;
|
|
664
|
+
assertMeasuredNumber(result.generationsExplored, generationsExplored, "generation count");
|
|
665
|
+
assertMeasuredNumber(result.totalCostUsd, result.cost.totalCostUsd, "cost summary");
|
|
666
|
+
assertMeasuredIdentity(
|
|
667
|
+
canonicalDigest(result.cost),
|
|
668
|
+
canonicalDigest(result.raw.cost),
|
|
669
|
+
"raw cost summary"
|
|
670
|
+
);
|
|
671
|
+
if (!result.cost.accountingComplete) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`cost accounting is incomplete: ${result.cost.incompleteReasons.join("; ") || "unknown reason"}`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
if (!receiptCost.accountingComplete) {
|
|
677
|
+
throw new Error(
|
|
678
|
+
`cost accounting is incomplete: ${receiptCost.incompleteReasons.join("; ") || "unknown reason"}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
assertMeasuredIdentity(
|
|
682
|
+
canonicalDigest(result.cost),
|
|
683
|
+
canonicalDigest(receiptCost),
|
|
684
|
+
"cost receipt summary"
|
|
685
|
+
);
|
|
686
|
+
assertMeasuredNumber(result.totalCostUsd, receiptCost.totalCostUsd, "cost receipts");
|
|
687
|
+
assertMeasuredOptional(result.winner.label, result.raw.winnerLabel, "raw winner label");
|
|
688
|
+
assertMeasuredOptional(
|
|
689
|
+
result.winner.rationale,
|
|
690
|
+
result.raw.winnerRationale,
|
|
691
|
+
"raw winner rationale"
|
|
692
|
+
);
|
|
693
|
+
return agentImprovementMeasuredComparisonSchema.parse({
|
|
694
|
+
schemaVersion: 1,
|
|
695
|
+
kind: "agent-improvement-measured-comparison",
|
|
696
|
+
benchmark: options.benchmark,
|
|
697
|
+
baselineProfileDigest: options.baselineProfileDigest,
|
|
698
|
+
candidateBundleDigest: options.candidateBundleDigest,
|
|
699
|
+
overall: {
|
|
700
|
+
name: "composite",
|
|
701
|
+
baseline: composite.baseline,
|
|
702
|
+
candidate: composite.candidate,
|
|
703
|
+
delta: composite.delta,
|
|
704
|
+
confidenceInterval: composite.confidenceInterval,
|
|
705
|
+
n: composite.n,
|
|
706
|
+
direction: "higher-is-better",
|
|
707
|
+
unit: "score"
|
|
708
|
+
},
|
|
709
|
+
objectives: measuredObjectives(pairs),
|
|
710
|
+
...result.winner.label || result.winner.rationale ? {
|
|
711
|
+
candidate: {
|
|
712
|
+
...result.winner.label ? { label: result.winner.label } : {},
|
|
713
|
+
...result.winner.rationale ? { rationale: result.winner.rationale } : {}
|
|
714
|
+
}
|
|
715
|
+
} : {},
|
|
716
|
+
decision: {
|
|
717
|
+
outcome: result.gateDecision,
|
|
718
|
+
reasons: rebuiltProvenance.gate.reasons,
|
|
719
|
+
contributingChecks: rebuiltProvenance.gate.contributingGates.map((check) => ({
|
|
720
|
+
name: check.name,
|
|
721
|
+
passed: check.passed
|
|
722
|
+
}))
|
|
723
|
+
},
|
|
724
|
+
power: {
|
|
725
|
+
sufficient: power.scaleAssumed && !power.underpowered,
|
|
726
|
+
n: power.n,
|
|
727
|
+
minimumDetectableDelta: power.mde,
|
|
728
|
+
confidenceLevel: power.confidence,
|
|
729
|
+
scaleAssumed: power.scaleAssumed,
|
|
730
|
+
sharedScorerChannel: power.sharedChannelCaveat !== void 0,
|
|
731
|
+
reason: power.recommendation
|
|
732
|
+
},
|
|
733
|
+
provenance: {
|
|
734
|
+
kind: "agent-eval-loop",
|
|
735
|
+
schema: rebuiltProvenance.schema,
|
|
736
|
+
runId: rebuiltProvenance.runId,
|
|
737
|
+
recordDigest: rebuiltProvenance.recordDigest,
|
|
738
|
+
baselineContentHash,
|
|
739
|
+
candidateContentHash
|
|
740
|
+
},
|
|
741
|
+
diff: canonicalDiff,
|
|
742
|
+
evaluation: {
|
|
743
|
+
generationsExplored,
|
|
744
|
+
durationMs: result.durationMs,
|
|
745
|
+
totalCostUsd: result.totalCostUsd
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
function assertGenerationMeasurements(generations, receiptsById, receiptIdsByCell) {
|
|
750
|
+
for (const generation of generations) {
|
|
751
|
+
for (const measured of generation.surfaces) {
|
|
752
|
+
const candidate = generation.record.candidates.find(
|
|
753
|
+
(entry) => entry.surfaceHash === measured.surfaceHash
|
|
754
|
+
);
|
|
755
|
+
if (!candidate) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`generation ${generation.record.generationIndex} is missing candidate ${measured.surfaceHash}`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
const composite = campaignMeanComposite(measured.campaign);
|
|
761
|
+
const breakdown = campaignBreakdown(measured.campaign);
|
|
762
|
+
const coverage = campaignCoverage(
|
|
763
|
+
measured.campaign.cells,
|
|
764
|
+
measured.campaign.scenarios,
|
|
765
|
+
measured.campaign.reps,
|
|
766
|
+
true
|
|
767
|
+
);
|
|
768
|
+
assertScorableMeasuredCells(
|
|
769
|
+
measured.campaign,
|
|
770
|
+
coverage.scorableCellIds,
|
|
771
|
+
receiptsById,
|
|
772
|
+
receiptIdsByCell
|
|
773
|
+
);
|
|
774
|
+
assertMeasuredNumber(
|
|
775
|
+
candidate.composite,
|
|
776
|
+
composite,
|
|
777
|
+
`candidate ${measured.surfaceHash} composite`
|
|
778
|
+
);
|
|
779
|
+
if (!candidate.coverage) {
|
|
780
|
+
throw new Error(`candidate ${measured.surfaceHash} does not report campaign coverage`);
|
|
781
|
+
}
|
|
782
|
+
assertMeasuredIdentity(
|
|
783
|
+
canonicalDigest(candidate.coverage),
|
|
784
|
+
canonicalDigest({
|
|
785
|
+
expectedCells: coverage.expectedCellIds.length,
|
|
786
|
+
scorableCells: coverage.scorableCellIds.length,
|
|
787
|
+
unscorableCells: coverage.unscorableCells
|
|
788
|
+
}),
|
|
789
|
+
`candidate ${measured.surfaceHash} coverage`
|
|
790
|
+
);
|
|
791
|
+
assertMeasuredIdentity(
|
|
792
|
+
String(candidate.eligibleForPromotion),
|
|
793
|
+
String(coverage.complete),
|
|
794
|
+
`candidate ${measured.surfaceHash} eligibility`
|
|
795
|
+
);
|
|
796
|
+
assertMeasuredIdentity(
|
|
797
|
+
canonicalDigest(candidate.dimensions),
|
|
798
|
+
canonicalDigest(breakdown.dimensions),
|
|
799
|
+
`candidate ${measured.surfaceHash} dimensions`
|
|
800
|
+
);
|
|
801
|
+
assertMeasuredIdentity(
|
|
802
|
+
canonicalDigest(candidate.scenarios),
|
|
803
|
+
canonicalDigest(breakdown.scenarios),
|
|
804
|
+
`candidate ${measured.surfaceHash} scenarios`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function assertCompleteMeasuredCampaign(campaign, name, receiptsById, receiptIdsByCell) {
|
|
810
|
+
assertCampaignSplitIdentity(campaign.scenarios, campaign.reps, campaign.splitDigest);
|
|
811
|
+
const coverage = campaignCoverage(campaign.cells, campaign.scenarios, campaign.reps, true);
|
|
812
|
+
if (!coverage.complete) {
|
|
813
|
+
throw new Error(
|
|
814
|
+
`${name} is incomplete (${coverage.scorableCellIds.length}/${coverage.expectedCellIds.length} designed cells scorable)`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
assertScorableMeasuredCells(campaign, coverage.scorableCellIds, receiptsById, receiptIdsByCell);
|
|
818
|
+
}
|
|
819
|
+
function assertScorableMeasuredCells(campaign, scorableCellIds, receiptsById, receiptIdsByCell) {
|
|
820
|
+
const cellsById = new Map(campaign.cells.map((cell) => [cell.cellId, cell]));
|
|
821
|
+
for (const cellId of scorableCellIds) {
|
|
822
|
+
const cell = cellsById.get(cellId);
|
|
823
|
+
if (!cell) throw new Error(`measured campaign is missing scorable cell ${cellId}`);
|
|
824
|
+
assertMeasuredCell(cell, receiptsById, receiptIdsByCell, campaign.runDir);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function measuredObjectives(pairs) {
|
|
828
|
+
const qualityColumns = /* @__PURE__ */ new Map();
|
|
829
|
+
for (const [baseline, candidate] of pairs) {
|
|
830
|
+
for (const cell of [baseline, candidate]) {
|
|
831
|
+
for (const [objective, score] of Object.entries(cell.judgeScores)) {
|
|
832
|
+
if (score.failed) continue;
|
|
833
|
+
qualityColumns.set(`objective:${objective}`, {
|
|
834
|
+
kind: "objective",
|
|
835
|
+
name: objective,
|
|
836
|
+
direction: "higher-is-better",
|
|
837
|
+
unit: "score"
|
|
838
|
+
});
|
|
839
|
+
for (const name of Object.keys(score.dimensions)) {
|
|
840
|
+
qualityColumns.set(`dimension:${objective}:${name}`, {
|
|
841
|
+
kind: "dimension",
|
|
842
|
+
objective,
|
|
843
|
+
name,
|
|
844
|
+
direction: "higher-is-better",
|
|
845
|
+
unit: "score"
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return [
|
|
852
|
+
...[...qualityColumns.entries()].sort(([left], [right]) => left.localeCompare(right)).map(
|
|
853
|
+
([, column]) => measuredObjective(column, pairs, (cell) => measuredQuality(cell, column))
|
|
854
|
+
),
|
|
855
|
+
measuredCostObjective(pairs),
|
|
856
|
+
measuredObjective(
|
|
857
|
+
{
|
|
858
|
+
kind: "latency",
|
|
859
|
+
name: "latency",
|
|
860
|
+
direction: "lower-is-better",
|
|
861
|
+
unit: "milliseconds"
|
|
862
|
+
},
|
|
863
|
+
pairs,
|
|
864
|
+
(cell) => cell.durationMs
|
|
865
|
+
)
|
|
866
|
+
];
|
|
867
|
+
}
|
|
868
|
+
function measuredCostObjective(pairs) {
|
|
869
|
+
const cells = pairs.flat();
|
|
870
|
+
for (const cell of cells) finiteMeasuredValue(cell.costUsd, "cost:cost");
|
|
871
|
+
return measuredObjective(
|
|
872
|
+
{
|
|
873
|
+
kind: "cost",
|
|
874
|
+
name: "cost",
|
|
875
|
+
direction: "lower-is-better",
|
|
876
|
+
unit: "usd"
|
|
877
|
+
},
|
|
878
|
+
pairs,
|
|
879
|
+
(cell) => cell.costUsd
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
function measuredComposite(cell) {
|
|
883
|
+
const values = Object.values(cell.judgeScores).filter((score) => !score.failed).map((score) => score.composite).filter(Number.isFinite);
|
|
884
|
+
if (values.length === 0) {
|
|
885
|
+
throw new Error(`heldout cell ${measuredCellKey(cell)} has no successful composite score`);
|
|
886
|
+
}
|
|
887
|
+
return measuredMean(values);
|
|
888
|
+
}
|
|
889
|
+
function pairMeasuredCells(baselineCells, candidateCells) {
|
|
890
|
+
const cells = [...baselineCells, ...candidateCells];
|
|
891
|
+
const errors = cells.filter((cell) => cell.error);
|
|
892
|
+
if (errors.length > 0) {
|
|
893
|
+
throw new Error(
|
|
894
|
+
`measured objectives cannot publish ${errors.length} errored heldout cells: ${errors.map((cell) => measuredCellKey(cell)).join(", ")}`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
const rows = [
|
|
898
|
+
...baselineCells.map((cell) => ({
|
|
899
|
+
pairKey: cell.scenarioId,
|
|
900
|
+
repKey: String(cell.rep),
|
|
901
|
+
arm: "baseline",
|
|
902
|
+
cell
|
|
903
|
+
})),
|
|
904
|
+
...candidateCells.map((cell) => ({
|
|
905
|
+
pairKey: cell.scenarioId,
|
|
906
|
+
repKey: String(cell.rep),
|
|
907
|
+
arm: "candidate",
|
|
908
|
+
cell
|
|
909
|
+
}))
|
|
910
|
+
];
|
|
911
|
+
const paired = pairArms(rows, { baselineArm: "baseline", treatmentArm: "candidate" });
|
|
912
|
+
if (paired.pairs.length === 0 || paired.unpairedBaseline.length > 0 || paired.unpairedTreatment.length > 0) {
|
|
913
|
+
throw new Error("measured objectives require the same non-empty paired heldout cells");
|
|
914
|
+
}
|
|
915
|
+
return paired.pairs.map((pair) => {
|
|
916
|
+
const baseline = pair.baseline.cell;
|
|
917
|
+
const candidate = pair.treatment.cell;
|
|
918
|
+
if (baseline.seed !== candidate.seed) {
|
|
919
|
+
throw new Error(`heldout cell ${baseline.cellId} does not share one paired seed`);
|
|
920
|
+
}
|
|
921
|
+
return [baseline, candidate];
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
function assertMeasuredCell(cell, receiptsById, receiptIdsByCell, runDir) {
|
|
925
|
+
if (!cell.scenarioId.trim() || !Number.isSafeInteger(cell.rep) || cell.rep < 0 || !Number.isSafeInteger(cell.seed) || cell.cellId !== measuredCellKey(cell)) {
|
|
926
|
+
throw new Error(`heldout cell '${cell.cellId}' has an invalid scenario/rep identity`);
|
|
927
|
+
}
|
|
928
|
+
nonnegativeMeasuredValue(cell.costUsd, `heldout cell ${cell.cellId} cost`);
|
|
929
|
+
nonnegativeMeasuredValue(cell.durationMs, `heldout cell ${cell.cellId} latency`);
|
|
930
|
+
if (!cell.tokenUsage) {
|
|
931
|
+
throw new Error(`heldout cell ${cell.cellId} does not report token usage`);
|
|
932
|
+
}
|
|
933
|
+
nonnegativeMeasuredInteger(cell.tokenUsage.input, `heldout cell ${cell.cellId} input tokens`);
|
|
934
|
+
nonnegativeMeasuredInteger(cell.tokenUsage.output, `heldout cell ${cell.cellId} output tokens`);
|
|
935
|
+
if (cell.tokenUsage.cached !== void 0) {
|
|
936
|
+
nonnegativeMeasuredInteger(cell.tokenUsage.cached, `heldout cell ${cell.cellId} cached tokens`);
|
|
937
|
+
}
|
|
938
|
+
if (!Array.isArray(cell.costCallIds)) {
|
|
939
|
+
throw new Error(`heldout cell ${cell.cellId} does not identify its cost receipts`);
|
|
940
|
+
}
|
|
941
|
+
if (new Set(cell.costCallIds).size !== cell.costCallIds.length) {
|
|
942
|
+
throw new Error(`heldout cell ${cell.cellId} repeats a cost receipt`);
|
|
943
|
+
}
|
|
944
|
+
const linkedReceipts = cell.costCallIds.map((callId) => {
|
|
945
|
+
const receipt = receiptsById.get(callId);
|
|
946
|
+
if (!receipt)
|
|
947
|
+
throw new Error(`heldout cell ${cell.cellId} references missing cost receipt ${callId}`);
|
|
948
|
+
if (receipt.tags?.runDir !== runDir || receipt.tags.cellId !== cell.cellId || receipt.tags.scenarioId !== cell.scenarioId || receipt.tags.rep !== String(cell.rep)) {
|
|
949
|
+
throw new Error(`heldout cell ${cell.cellId} references a cost receipt from another cell`);
|
|
950
|
+
}
|
|
951
|
+
return receipt;
|
|
952
|
+
});
|
|
953
|
+
assertMeasuredIdentity(
|
|
954
|
+
canonicalDigest([...cell.costCallIds].sort()),
|
|
955
|
+
canonicalDigest(
|
|
956
|
+
[
|
|
957
|
+
...receiptIdsByCell.get(receiptCellKey(runDir, cell.cellId, cell.scenarioId, cell.rep)) ?? []
|
|
958
|
+
].sort()
|
|
959
|
+
),
|
|
960
|
+
`heldout cell ${cell.cellId} cost receipt IDs`
|
|
961
|
+
);
|
|
962
|
+
const agentReceipts = linkedReceipts.filter((receipt) => receipt.channel === "agent");
|
|
963
|
+
if (agentReceipts.some((receipt) => receipt.error)) {
|
|
964
|
+
throw new Error(`measured cell ${cell.cellId} links a failed agent receipt`);
|
|
965
|
+
}
|
|
966
|
+
assertRealAgentReceipts(agentReceipts, { allowMixed: false });
|
|
967
|
+
assertMeasuredNumber(
|
|
968
|
+
cell.costUsd,
|
|
969
|
+
agentReceipts.reduce((total, receipt) => total + receipt.costUsd, 0),
|
|
970
|
+
`heldout cell ${cell.cellId} cost receipts`
|
|
971
|
+
);
|
|
972
|
+
assertMeasuredNumber(
|
|
973
|
+
cell.tokenUsage.input,
|
|
974
|
+
agentReceipts.reduce((total, receipt) => total + receipt.inputTokens, 0),
|
|
975
|
+
`heldout cell ${cell.cellId} input receipts`
|
|
976
|
+
);
|
|
977
|
+
assertMeasuredNumber(
|
|
978
|
+
cell.tokenUsage.output,
|
|
979
|
+
agentReceipts.reduce((total, receipt) => total + receipt.outputTokens, 0),
|
|
980
|
+
`heldout cell ${cell.cellId} output receipts`
|
|
981
|
+
);
|
|
982
|
+
assertMeasuredNumber(
|
|
983
|
+
cell.tokenUsage.cached ?? 0,
|
|
984
|
+
agentReceipts.reduce((total, receipt) => total + (receipt.cachedTokens ?? 0), 0),
|
|
985
|
+
`heldout cell ${cell.cellId} cached receipts`
|
|
986
|
+
);
|
|
987
|
+
for (const [judge, score] of Object.entries(cell.judgeScores)) {
|
|
988
|
+
if (score.failed) {
|
|
989
|
+
throw new Error(`heldout cell ${cell.cellId} contains failed judge '${judge}'`);
|
|
990
|
+
}
|
|
991
|
+
finiteMeasuredValue(score.composite, `objective:${judge}`);
|
|
992
|
+
for (const [dimension, value] of Object.entries(score.dimensions)) {
|
|
993
|
+
finiteMeasuredValue(value, `dimension:${judge}:${dimension}`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
measuredComposite(cell);
|
|
997
|
+
}
|
|
998
|
+
function measuredObjective(identity, pairs, value) {
|
|
999
|
+
const key = measuredObjectiveKey(identity);
|
|
1000
|
+
const baseline = pairs.map(([cell]) => finiteMeasuredValue(value(cell), key));
|
|
1001
|
+
const candidate = pairs.map(([, cell]) => finiteMeasuredValue(value(cell), key));
|
|
1002
|
+
const interval = pairedBootstrap(baseline, candidate, {
|
|
1003
|
+
confidence: 0.95,
|
|
1004
|
+
resamples: 2e3,
|
|
1005
|
+
statistic: "mean",
|
|
1006
|
+
seed: measuredSeed(key)
|
|
1007
|
+
});
|
|
1008
|
+
const baselineMean = measuredMean(baseline);
|
|
1009
|
+
const candidateMean = measuredMean(candidate);
|
|
1010
|
+
return {
|
|
1011
|
+
...identity,
|
|
1012
|
+
availability: "measured",
|
|
1013
|
+
baseline: baselineMean,
|
|
1014
|
+
candidate: candidateMean,
|
|
1015
|
+
delta: candidateMean - baselineMean,
|
|
1016
|
+
confidenceInterval: {
|
|
1017
|
+
level: interval.confidence,
|
|
1018
|
+
lower: interval.low,
|
|
1019
|
+
upper: interval.high,
|
|
1020
|
+
method: "paired-bootstrap",
|
|
1021
|
+
statistic: "mean",
|
|
1022
|
+
resamples: interval.resamples
|
|
1023
|
+
},
|
|
1024
|
+
n: interval.n
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
function measuredQuality(cell, column) {
|
|
1028
|
+
const objective = column.kind === "objective" ? column.name : column.objective;
|
|
1029
|
+
const score = cell.judgeScores[objective];
|
|
1030
|
+
if (!score || score.failed) {
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
`heldout cell ${measuredCellKey(cell)} is missing measured objective '${objective}'`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
const value = column.kind === "objective" ? score.composite : score.dimensions[column.name];
|
|
1036
|
+
if (value === void 0) {
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
`heldout cell ${measuredCellKey(cell)} is missing '${objective}' dimension '${column.name}'`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
return finiteMeasuredValue(value, measuredObjectiveKey(column));
|
|
1042
|
+
}
|
|
1043
|
+
function measuredObjectiveKey(identity) {
|
|
1044
|
+
return identity.kind === "dimension" ? `${identity.kind}:${identity.objective}:${identity.name}` : `${identity.kind}:${identity.name}`;
|
|
1045
|
+
}
|
|
1046
|
+
function measuredCellKey(cell) {
|
|
1047
|
+
return `${cell.scenarioId}:${cell.rep}`;
|
|
1048
|
+
}
|
|
1049
|
+
function finiteMeasuredValue(value, name) {
|
|
1050
|
+
if (!Number.isFinite(value)) throw new Error(`measured objective '${name}' is not finite`);
|
|
1051
|
+
return value;
|
|
1052
|
+
}
|
|
1053
|
+
function nonnegativeMeasuredValue(value, name) {
|
|
1054
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be non-negative`);
|
|
1055
|
+
return value;
|
|
1056
|
+
}
|
|
1057
|
+
function nonnegativeMeasuredInteger(value, name) {
|
|
1058
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1059
|
+
throw new Error(`${name} must be a non-negative safe integer`);
|
|
1060
|
+
}
|
|
1061
|
+
return value;
|
|
1062
|
+
}
|
|
1063
|
+
function measuredMean(values) {
|
|
1064
|
+
if (values.length === 0) throw new Error("measured objective has no paired values");
|
|
1065
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1066
|
+
}
|
|
1067
|
+
function measuredSeed(value) {
|
|
1068
|
+
let seed = 2166136261;
|
|
1069
|
+
for (const byte of Buffer.from(value, "utf8")) {
|
|
1070
|
+
seed = Math.imul(seed ^ byte, 16777619) >>> 0;
|
|
1071
|
+
}
|
|
1072
|
+
return seed;
|
|
1073
|
+
}
|
|
1074
|
+
function assertMeasuredNumber(actual, expected, name) {
|
|
1075
|
+
const tolerance = Number.EPSILON * Math.max(1, Math.abs(actual), Math.abs(expected)) * 8;
|
|
1076
|
+
if (!Number.isFinite(actual) || !Number.isFinite(expected) || Math.abs(actual - expected) > tolerance) {
|
|
1077
|
+
throw new Error(`${name} does not agree across the measured comparison`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
function assertMeasuredIdentity(actual, expected, name) {
|
|
1081
|
+
if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
|
|
1082
|
+
}
|
|
1083
|
+
function assertMeasuredOptional(actual, expected, name) {
|
|
1084
|
+
if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
|
|
1085
|
+
}
|
|
1086
|
+
function indexReceiptIdsByCell(receipts) {
|
|
1087
|
+
const idsByCell = /* @__PURE__ */ new Map();
|
|
1088
|
+
for (const receipt of receipts) {
|
|
1089
|
+
const tags = receipt.tags;
|
|
1090
|
+
if (!tags?.runDir || !tags.cellId || !tags.scenarioId || tags.rep === void 0) continue;
|
|
1091
|
+
const key = receiptCellKey(tags.runDir, tags.cellId, tags.scenarioId, tags.rep);
|
|
1092
|
+
const ids = idsByCell.get(key) ?? [];
|
|
1093
|
+
ids.push(receipt.callId);
|
|
1094
|
+
idsByCell.set(key, ids);
|
|
1095
|
+
}
|
|
1096
|
+
return idsByCell;
|
|
1097
|
+
}
|
|
1098
|
+
function receiptCellKey(runDir, cellId, scenarioId, rep) {
|
|
1099
|
+
return JSON.stringify([runDir, cellId, scenarioId, String(rep)]);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/contract/eval-reporting-suite.ts
|
|
1103
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
1104
|
+
import { dirname, join as join2 } from "path";
|
|
1105
|
+
|
|
1106
|
+
// src/contract/intake/run-record-dir.ts
|
|
1107
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
1108
|
+
import { join } from "path";
|
|
1109
|
+
var ANALYSIS_ARTIFACT = "analysis.json";
|
|
1110
|
+
function defaultInclude(fileName) {
|
|
1111
|
+
if (fileName === ANALYSIS_ARTIFACT) return false;
|
|
1112
|
+
return fileName.endsWith(".json") || fileName.endsWith(".jsonl");
|
|
1113
|
+
}
|
|
1114
|
+
async function fromRunRecordDir(path, options = {}) {
|
|
1115
|
+
const onInvalid = options.onInvalid ?? "throw";
|
|
1116
|
+
const include = options.include ?? defaultInclude;
|
|
1117
|
+
const stats = await stat(path);
|
|
1118
|
+
const filePaths = stats.isDirectory() ? await collectFiles(path, include, options.recursive ?? false) : [path];
|
|
1119
|
+
const runs = [];
|
|
1120
|
+
const rejected = [];
|
|
1121
|
+
for (const file of filePaths) {
|
|
1122
|
+
const raw = await parseRecordFile(file);
|
|
1123
|
+
for (const { index, value } of raw) {
|
|
1124
|
+
const parsed = parseRunRecordSafe(value);
|
|
1125
|
+
if (parsed.ok) {
|
|
1126
|
+
runs.push(parsed.value);
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
const rejection = { file, index, reason: parsed.error.message };
|
|
1130
|
+
if (onInvalid === "throw") {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`fromRunRecordDir: invalid RunRecord in '${file}' at index ${index}: ${parsed.error.message}`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
rejected.push(rejection);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return { runs, rejected, files: filePaths };
|
|
1139
|
+
}
|
|
1140
|
+
async function parseRecordFile(file) {
|
|
1141
|
+
const text = await readFile(file, "utf8");
|
|
1142
|
+
const trimmed = text.trim();
|
|
1143
|
+
if (trimmed.length === 0) return [];
|
|
1144
|
+
if (trimmed.startsWith("[")) {
|
|
1145
|
+
const parsed = JSON.parse(trimmed);
|
|
1146
|
+
if (!Array.isArray(parsed)) {
|
|
1147
|
+
throw new Error(`fromRunRecordDir: file '${file}' did not parse to an array`);
|
|
1148
|
+
}
|
|
1149
|
+
return parsed.map((value, index) => ({ index, value }));
|
|
1150
|
+
}
|
|
1151
|
+
const out = [];
|
|
1152
|
+
const lines = trimmed.split("\n");
|
|
1153
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1154
|
+
const line = lines[i].trim();
|
|
1155
|
+
if (line.length === 0) continue;
|
|
1156
|
+
try {
|
|
1157
|
+
out.push({ index: i, value: JSON.parse(line) });
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`fromRunRecordDir: file '${file}' line ${i + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return out;
|
|
1165
|
+
}
|
|
1166
|
+
async function collectFiles(dir, include, recursive) {
|
|
1167
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1168
|
+
const files = [];
|
|
1169
|
+
const subdirs = [];
|
|
1170
|
+
for (const entry of entries) {
|
|
1171
|
+
if (entry.isDirectory()) {
|
|
1172
|
+
if (recursive) subdirs.push(join(dir, entry.name));
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (include(entry.name)) files.push(join(dir, entry.name));
|
|
1176
|
+
}
|
|
1177
|
+
files.sort();
|
|
1178
|
+
subdirs.sort();
|
|
1179
|
+
for (const sub of subdirs) {
|
|
1180
|
+
files.push(...await collectFiles(sub, include, recursive));
|
|
1181
|
+
}
|
|
1182
|
+
return files;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/contract/eval-reporting-suite.ts
|
|
1186
|
+
var ANALYSIS_ARTIFACT2 = "analysis.json";
|
|
1187
|
+
async function evalReportingSuite(input, options = {}) {
|
|
1188
|
+
const fromPath = typeof input === "string";
|
|
1189
|
+
let runs;
|
|
1190
|
+
let files = [];
|
|
1191
|
+
let rejected = [];
|
|
1192
|
+
if (fromPath) {
|
|
1193
|
+
const loaded = await fromRunRecordDir(input, options.load);
|
|
1194
|
+
runs = loaded.runs;
|
|
1195
|
+
files = loaded.files;
|
|
1196
|
+
rejected = loaded.rejected;
|
|
1197
|
+
} else {
|
|
1198
|
+
runs = input;
|
|
1199
|
+
}
|
|
1200
|
+
if (runs.length === 0) {
|
|
1201
|
+
throw new Error(
|
|
1202
|
+
fromPath ? `evalReportingSuite: no RunRecords found at '${input}'` : "evalReportingSuite: no RunRecords to analyze"
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
const report = await analyzeRuns({ ...options.analyze, runs });
|
|
1206
|
+
const result = {
|
|
1207
|
+
report,
|
|
1208
|
+
provenance: {
|
|
1209
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1210
|
+
runCount: runs.length,
|
|
1211
|
+
sourcePath: fromPath ? input : null,
|
|
1212
|
+
files,
|
|
1213
|
+
rejected
|
|
1214
|
+
},
|
|
1215
|
+
writtenTo: null
|
|
1216
|
+
};
|
|
1217
|
+
const target = resolveWriteTarget(options.write, fromPath ? input : null);
|
|
1218
|
+
if (target) {
|
|
1219
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1220
|
+
await writeFile(target, `${JSON.stringify(result, null, 2)}
|
|
1221
|
+
`, "utf8");
|
|
1222
|
+
result.writtenTo = target;
|
|
1223
|
+
}
|
|
1224
|
+
return result;
|
|
1225
|
+
}
|
|
1226
|
+
function resolveWriteTarget(write, sourcePath) {
|
|
1227
|
+
if (!write) return null;
|
|
1228
|
+
if (typeof write === "string") {
|
|
1229
|
+
const looksLikeDir = write.endsWith("/") || !write.endsWith(".json") && !write.endsWith(".jsonl");
|
|
1230
|
+
return looksLikeDir ? join2(write, ANALYSIS_ARTIFACT2) : write;
|
|
1231
|
+
}
|
|
1232
|
+
if (sourcePath === null) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
"evalReportingSuite: write:true needs a source path to anchor analysis.json \u2014 pass an explicit output path when analyzing in-memory records"
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const isFile = sourcePath.endsWith(".json") || sourcePath.endsWith(".jsonl");
|
|
1238
|
+
return isFile ? join2(dirname(sourcePath), ANALYSIS_ARTIFACT2) : join2(sourcePath, ANALYSIS_ARTIFACT2);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/contract/diff.ts
|
|
1242
|
+
function keyForCell(cell) {
|
|
1243
|
+
return JSON.stringify([cell.scenarioId, cell.rep]);
|
|
1244
|
+
}
|
|
1245
|
+
function diffDimensions(before, after) {
|
|
1246
|
+
const out = {};
|
|
1247
|
+
const judges = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
1248
|
+
for (const judge of judges) {
|
|
1249
|
+
const beforeDims = before[judge] ?? {};
|
|
1250
|
+
const afterDims = after[judge] ?? {};
|
|
1251
|
+
const dims = /* @__PURE__ */ new Set([...Object.keys(beforeDims), ...Object.keys(afterDims)]);
|
|
1252
|
+
const judgeOut = {};
|
|
1253
|
+
for (const dim of dims) {
|
|
1254
|
+
const rawBefore = beforeDims[dim];
|
|
1255
|
+
const rawAfter = afterDims[dim];
|
|
1256
|
+
const b = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? rawBefore : null;
|
|
1257
|
+
const a = typeof rawAfter === "number" && Number.isFinite(rawAfter) ? rawAfter : null;
|
|
1258
|
+
judgeOut[dim] = {
|
|
1259
|
+
before: b,
|
|
1260
|
+
after: a,
|
|
1261
|
+
delta: b !== null && a !== null ? a - b : null
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
out[judge] = judgeOut;
|
|
1265
|
+
}
|
|
1266
|
+
return out;
|
|
1267
|
+
}
|
|
1268
|
+
function diffGenerations(before, after) {
|
|
1269
|
+
const beforeMap = new Map(before.cells.map((c) => [keyForCell(c), c]));
|
|
1270
|
+
const afterMap = new Map(after.cells.map((c) => [keyForCell(c), c]));
|
|
1271
|
+
const matched = [];
|
|
1272
|
+
const removed = [];
|
|
1273
|
+
const added = [];
|
|
1274
|
+
for (const [key, beforeCell] of beforeMap) {
|
|
1275
|
+
const afterCell = afterMap.get(key);
|
|
1276
|
+
if (!afterCell) {
|
|
1277
|
+
removed.push(beforeCell);
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
matched.push({
|
|
1281
|
+
scenarioId: beforeCell.scenarioId,
|
|
1282
|
+
rep: beforeCell.rep,
|
|
1283
|
+
compositeBefore: beforeCell.compositeMean,
|
|
1284
|
+
compositeAfter: afterCell.compositeMean,
|
|
1285
|
+
compositeDelta: afterCell.compositeMean - beforeCell.compositeMean,
|
|
1286
|
+
dimensions: diffDimensions(beforeCell.dimensions, afterCell.dimensions)
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
for (const [key, afterCell] of afterMap) {
|
|
1290
|
+
if (!beforeMap.has(key)) added.push(afterCell);
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
beforeIndex: before.index,
|
|
1294
|
+
afterIndex: after.index,
|
|
1295
|
+
beforeSurfaceHash: before.surfaceHash,
|
|
1296
|
+
afterSurfaceHash: after.surfaceHash,
|
|
1297
|
+
surfaceChanged: before.surfaceHash !== after.surfaceHash,
|
|
1298
|
+
matched,
|
|
1299
|
+
removed,
|
|
1300
|
+
added,
|
|
1301
|
+
compositeBefore: before.compositeMean,
|
|
1302
|
+
compositeAfter: after.compositeMean,
|
|
1303
|
+
compositeDelta: after.compositeMean - before.compositeMean,
|
|
1304
|
+
costUsdBefore: before.costUsd,
|
|
1305
|
+
costUsdAfter: after.costUsd,
|
|
1306
|
+
costUsdDelta: after.costUsd - before.costUsd,
|
|
1307
|
+
durationMsBefore: before.durationMs,
|
|
1308
|
+
durationMsAfter: after.durationMs,
|
|
1309
|
+
durationMsDelta: after.durationMs - before.durationMs
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
function winnerOf(run) {
|
|
1313
|
+
if (run.generations.length === 0) return null;
|
|
1314
|
+
let winner = run.generations[0];
|
|
1315
|
+
for (const gen of run.generations) {
|
|
1316
|
+
if (gen.index > winner.index) winner = gen;
|
|
1317
|
+
}
|
|
1318
|
+
return winner;
|
|
1319
|
+
}
|
|
1320
|
+
function diffRuns(before, after) {
|
|
1321
|
+
const beforeWinner = winnerOf(before);
|
|
1322
|
+
const afterWinner = winnerOf(after);
|
|
1323
|
+
const baselineDiff = before.baseline && after.baseline ? diffGenerations(before.baseline, after.baseline) : null;
|
|
1324
|
+
const winnersDiff = beforeWinner && afterWinner ? diffGenerations(beforeWinner, afterWinner) : null;
|
|
1325
|
+
const beforeLift = before.holdoutLift ?? null;
|
|
1326
|
+
const afterLift = after.holdoutLift ?? null;
|
|
1327
|
+
return {
|
|
1328
|
+
beforeRunId: before.runId,
|
|
1329
|
+
afterRunId: after.runId,
|
|
1330
|
+
beforeTimestamp: before.timestamp,
|
|
1331
|
+
afterTimestamp: after.timestamp,
|
|
1332
|
+
beforeGateDecision: before.gateDecision ?? null,
|
|
1333
|
+
afterGateDecision: after.gateDecision ?? null,
|
|
1334
|
+
beforeHoldoutLift: beforeLift,
|
|
1335
|
+
afterHoldoutLift: afterLift,
|
|
1336
|
+
holdoutLiftDelta: beforeLift !== null && afterLift !== null ? afterLift - beforeLift : null,
|
|
1337
|
+
beforeTotalCostUsd: before.totalCostUsd,
|
|
1338
|
+
afterTotalCostUsd: after.totalCostUsd,
|
|
1339
|
+
totalCostUsdDelta: after.totalCostUsd - before.totalCostUsd,
|
|
1340
|
+
beforeTotalDurationMs: before.totalDurationMs,
|
|
1341
|
+
afterTotalDurationMs: after.totalDurationMs,
|
|
1342
|
+
totalDurationMsDelta: after.totalDurationMs - before.totalDurationMs,
|
|
1343
|
+
baselineDiff,
|
|
1344
|
+
winnersDiff
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function diffRunBaselineToWinner(run) {
|
|
1348
|
+
if (!run.baseline) return null;
|
|
1349
|
+
const winner = winnerOf(run);
|
|
1350
|
+
if (!winner || winner.index === run.baseline.index) return null;
|
|
1351
|
+
return diffGenerations(run.baseline, winner);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/contract/intake/agent-trace.ts
|
|
1355
|
+
function rangeLines(r) {
|
|
1356
|
+
return Math.max(0, r.end_line - r.start_line + 1);
|
|
1357
|
+
}
|
|
1358
|
+
function parseAgentTrace(records) {
|
|
1359
|
+
const acc = /* @__PURE__ */ new Map();
|
|
1360
|
+
for (const record of records) {
|
|
1361
|
+
const sha = record.vcs?.revision;
|
|
1362
|
+
if (!sha) continue;
|
|
1363
|
+
let a = acc.get(sha);
|
|
1364
|
+
if (!a) {
|
|
1365
|
+
a = {
|
|
1366
|
+
models: /* @__PURE__ */ new Set(),
|
|
1367
|
+
tools: /* @__PURE__ */ new Set(),
|
|
1368
|
+
files: /* @__PURE__ */ new Set(),
|
|
1369
|
+
conversationCount: 0,
|
|
1370
|
+
lineCount: 0,
|
|
1371
|
+
humanInvolved: false
|
|
1372
|
+
};
|
|
1373
|
+
acc.set(sha, a);
|
|
1374
|
+
}
|
|
1375
|
+
if (record.tool?.name) a.tools.add(record.tool.name);
|
|
1376
|
+
for (const file of record.files ?? []) {
|
|
1377
|
+
a.files.add(file.path);
|
|
1378
|
+
for (const conv of file.conversations ?? []) {
|
|
1379
|
+
a.conversationCount += 1;
|
|
1380
|
+
for (const range of conv.ranges ?? []) {
|
|
1381
|
+
const contributor = range.contributor ?? conv.contributor;
|
|
1382
|
+
a.lineCount += rangeLines(range);
|
|
1383
|
+
if (!contributor) continue;
|
|
1384
|
+
if (contributor.type === "human" || contributor.type === "mixed") {
|
|
1385
|
+
a.humanInvolved = true;
|
|
1386
|
+
}
|
|
1387
|
+
if ((contributor.type === "ai" || contributor.type === "mixed") && contributor.model_id) {
|
|
1388
|
+
a.models.add(contributor.model_id);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const index = /* @__PURE__ */ new Map();
|
|
1395
|
+
for (const [sha, a] of acc) {
|
|
1396
|
+
index.set(sha, {
|
|
1397
|
+
commitSha: sha,
|
|
1398
|
+
aiModels: [...a.models].sort(),
|
|
1399
|
+
tools: [...a.tools].sort(),
|
|
1400
|
+
conversationCount: a.conversationCount,
|
|
1401
|
+
fileCount: a.files.size,
|
|
1402
|
+
lineCount: a.lineCount,
|
|
1403
|
+
humanInvolved: a.humanInvolved
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
return index;
|
|
1407
|
+
}
|
|
1408
|
+
function partitionRunsByAuthoringModel(runs, index) {
|
|
1409
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
1410
|
+
const unattributed = [];
|
|
1411
|
+
for (const run of runs) {
|
|
1412
|
+
const provenance = index.get(run.commitSha);
|
|
1413
|
+
if (!provenance || provenance.aiModels.length === 0) {
|
|
1414
|
+
unattributed.push(run);
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
for (const model of provenance.aiModels) {
|
|
1418
|
+
const cohort = byModel.get(model) ?? [];
|
|
1419
|
+
cohort.push(run);
|
|
1420
|
+
byModel.set(model, cohort);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return { byModel, unattributed };
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/contract/intake/feedback-table.ts
|
|
1427
|
+
function fromFeedbackTable(opts) {
|
|
1428
|
+
const { ratings, meta = [], scale, emitRaterScores = true } = opts;
|
|
1429
|
+
const metaByRun = new Map(meta.map((m) => [m.runId, m]));
|
|
1430
|
+
const normalise = (rating) => {
|
|
1431
|
+
if (typeof rating === "boolean") return rating ? 1 : 0;
|
|
1432
|
+
if (!Number.isFinite(rating)) return Number.NaN;
|
|
1433
|
+
if (!scale) return rating;
|
|
1434
|
+
const { min, max } = scale;
|
|
1435
|
+
if (max === min) return rating;
|
|
1436
|
+
return (rating - min) / (max - min);
|
|
1437
|
+
};
|
|
1438
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
1439
|
+
for (const row of ratings) {
|
|
1440
|
+
const list = byRun.get(row.runId) ?? [];
|
|
1441
|
+
list.push(row);
|
|
1442
|
+
byRun.set(row.runId, list);
|
|
1443
|
+
}
|
|
1444
|
+
const runs = [];
|
|
1445
|
+
const raterScores = [];
|
|
1446
|
+
for (const [runId, rowsForRun] of byRun) {
|
|
1447
|
+
const normalised = rowsForRun.map((r) => ({ rater: r.rater, score: normalise(r.rating) })).filter((r) => Number.isFinite(r.score));
|
|
1448
|
+
if (normalised.length === 0) continue;
|
|
1449
|
+
const meanScore = normalised.reduce((s, r) => s + r.score, 0) / normalised.length;
|
|
1450
|
+
const runMeta = metaByRun.get(runId) ?? { runId };
|
|
1451
|
+
const judgeScores = {
|
|
1452
|
+
perJudge: Object.fromEntries(normalised.map((r) => [r.rater, { rating: r.score }])),
|
|
1453
|
+
perDimMean: { rating: meanScore },
|
|
1454
|
+
composite: meanScore
|
|
1455
|
+
};
|
|
1456
|
+
const outcome = {
|
|
1457
|
+
// Feedback corpora ARE the holdout signal — score lands on
|
|
1458
|
+
// `holdoutScore` so downstream substrate primitives (`paretoChart`,
|
|
1459
|
+
// promotion gates) read it correctly by default.
|
|
1460
|
+
holdoutScore: meanScore,
|
|
1461
|
+
raw: Object.fromEntries(normalised.map((r) => [`rater:${r.rater}`, r.score])),
|
|
1462
|
+
judgeScores
|
|
1463
|
+
};
|
|
1464
|
+
runs.push({
|
|
1465
|
+
runId,
|
|
1466
|
+
experimentId: runMeta.experimentId ?? "feedback-corpus",
|
|
1467
|
+
candidateId: runMeta.candidateId ?? runId,
|
|
1468
|
+
seed: 0,
|
|
1469
|
+
model: runMeta.model ?? "unknown@unknown",
|
|
1470
|
+
promptHash: runMeta.promptHash ?? "sha256:unknown",
|
|
1471
|
+
configHash: runMeta.configHash ?? "sha256:unknown",
|
|
1472
|
+
commitSha: runMeta.commitSha ?? "unknown",
|
|
1473
|
+
wallMs: runMeta.wallMs ?? 0,
|
|
1474
|
+
costUsd: runMeta.costUsd ?? 0,
|
|
1475
|
+
tokenUsage: { input: 0, output: 0 },
|
|
1476
|
+
outcome,
|
|
1477
|
+
splitTag: runMeta.splitTag ?? "holdout"
|
|
1478
|
+
});
|
|
1479
|
+
if (emitRaterScores) {
|
|
1480
|
+
for (const r of normalised) raterScores.push({ runId, rater: r.rater, score: r.score });
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return { runs, raterScores };
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/contract/intake/otel-spans.ts
|
|
1487
|
+
var SCORE_KEYS = ["tangle.score", "eval.score", "score"];
|
|
1488
|
+
var MODEL_KEYS = ["tangle.model", ...LLM_MODEL_ATTR_KEYS, "model"];
|
|
1489
|
+
var PROMPT_HASH_KEYS = ["tangle.prompt_hash", "prompt.hash"];
|
|
1490
|
+
var CONFIG_HASH_KEYS = ["tangle.config_hash", "config.hash"];
|
|
1491
|
+
function fromOtelSpans(opts) {
|
|
1492
|
+
const { spans, defaultSplit = "holdout", experimentId = "otel-corpus" } = opts;
|
|
1493
|
+
const grouped = groupSpans(spans);
|
|
1494
|
+
const runs = [];
|
|
1495
|
+
for (const [groupKey, groupSpans2] of grouped) {
|
|
1496
|
+
const root = findRoot(groupSpans2);
|
|
1497
|
+
if (!root) continue;
|
|
1498
|
+
const measurements = summarizeExecutionMeasurements(
|
|
1499
|
+
groupSpans2.map((span) => ({
|
|
1500
|
+
id: span.spanId,
|
|
1501
|
+
...span.parentSpanId ? { parentId: span.parentSpanId } : {},
|
|
1502
|
+
attributes: span.attributes,
|
|
1503
|
+
modelCall: isExplicitModelCall(span),
|
|
1504
|
+
aggregate: isExplicitAggregate(span)
|
|
1505
|
+
}))
|
|
1506
|
+
);
|
|
1507
|
+
const callSpanIds = new Set(measurements.callSpanIds);
|
|
1508
|
+
const callSpans = groupSpans2.filter((span) => callSpanIds.has(span.spanId));
|
|
1509
|
+
const wallMs = Math.max(0, (root.endTimeUnixNano - root.startTimeUnixNano) / 1e6);
|
|
1510
|
+
const model = readAttrString(callSpans, MODEL_KEYS) ?? readAttrString(groupSpans2, MODEL_KEYS) ?? "unknown@unknown";
|
|
1511
|
+
const capturedCost = (measurements.cost.complete ? measurements.cost.value : void 0) ?? measurements.aggregate?.costUsd;
|
|
1512
|
+
const costUsd = capturedCost ?? 0;
|
|
1513
|
+
const promptHash = readAttrString(groupSpans2, PROMPT_HASH_KEYS) ?? "sha256:unknown";
|
|
1514
|
+
const configHash = readAttrString(groupSpans2, CONFIG_HASH_KEYS) ?? "sha256:unknown";
|
|
1515
|
+
const score = readAttrNumber(groupSpans2, SCORE_KEYS);
|
|
1516
|
+
const rawNumeric = collectNumericAttrs(groupSpans2);
|
|
1517
|
+
rawNumeric.error_span_count = groupSpans2.filter((span) => span.status?.code === "ERROR").length;
|
|
1518
|
+
rawNumeric.llm_span_count = measurements.modelCallCount;
|
|
1519
|
+
if (measurements.cost.value !== void 0 && !measurements.cost.complete) {
|
|
1520
|
+
rawNumeric.partial_observed_cost_usd = measurements.cost.value;
|
|
1521
|
+
}
|
|
1522
|
+
recordAggregateMeasurements(rawNumeric, measurements.aggregate);
|
|
1523
|
+
const judgeScores = score !== void 0 ? {
|
|
1524
|
+
perJudge: { "otel-derived": { score } },
|
|
1525
|
+
perDimMean: { score },
|
|
1526
|
+
composite: score
|
|
1527
|
+
} : void 0;
|
|
1528
|
+
const errorSpan = groupSpans2.find((s) => s.status?.code === "ERROR");
|
|
1529
|
+
const outcome = {
|
|
1530
|
+
...opts.defaultSplit === "search" ? { searchScore: score } : { holdoutScore: score },
|
|
1531
|
+
raw: rawNumeric,
|
|
1532
|
+
...judgeScores ? { judgeScores } : {}
|
|
1533
|
+
};
|
|
1534
|
+
runs.push({
|
|
1535
|
+
runId: groupKey,
|
|
1536
|
+
experimentId,
|
|
1537
|
+
candidateId: root.attributes["tangle.candidateId"] ?? "otel-default",
|
|
1538
|
+
seed: 0,
|
|
1539
|
+
model,
|
|
1540
|
+
promptHash,
|
|
1541
|
+
configHash,
|
|
1542
|
+
commitSha: root.attributes["tangle.commit_sha"] ?? "unknown",
|
|
1543
|
+
wallMs,
|
|
1544
|
+
costUsd,
|
|
1545
|
+
costProvenance: capturedCost === void 0 ? { kind: "uncaptured", usd: null } : { kind: "observed", usd: capturedCost },
|
|
1546
|
+
tokenUsage: measurements.tokenUsage,
|
|
1547
|
+
outcome,
|
|
1548
|
+
splitTag: defaultSplit,
|
|
1549
|
+
...errorSpan ? { failureMode: errorSpan.name } : {}
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
return runs;
|
|
1553
|
+
}
|
|
1554
|
+
function isExplicitModelCall(span) {
|
|
1555
|
+
const kind = span.attributes["openinference.span.kind"];
|
|
1556
|
+
if (typeof kind === "string") return kind.toUpperCase() === "LLM";
|
|
1557
|
+
const spanType = span.attributes["span.type"];
|
|
1558
|
+
return typeof spanType === "string" && spanType.toLowerCase() === "llm_request" || span.name.toLowerCase().includes("llm") || readAttrString([span], MODEL_KEYS) !== void 0 || typeof span.attributes["gen_ai.operation.name"] === "string";
|
|
1559
|
+
}
|
|
1560
|
+
function isExplicitAggregate(span) {
|
|
1561
|
+
const kind = span.attributes["openinference.span.kind"];
|
|
1562
|
+
return typeof kind === "string" && kind.toUpperCase() !== "LLM";
|
|
1563
|
+
}
|
|
1564
|
+
function groupSpans(spans) {
|
|
1565
|
+
const m = /* @__PURE__ */ new Map();
|
|
1566
|
+
for (const span of spans) {
|
|
1567
|
+
const key = span["tangle.runId"] ?? span.traceId;
|
|
1568
|
+
const list = m.get(key) ?? [];
|
|
1569
|
+
list.push(span);
|
|
1570
|
+
m.set(key, list);
|
|
1571
|
+
}
|
|
1572
|
+
return m;
|
|
1573
|
+
}
|
|
1574
|
+
function findRoot(group) {
|
|
1575
|
+
return group.find((s) => !s.parentSpanId) ?? group[0];
|
|
1576
|
+
}
|
|
1577
|
+
function readAttrString(spans, keys) {
|
|
1578
|
+
for (const span of spans) {
|
|
1579
|
+
for (const key of keys) {
|
|
1580
|
+
const v = span.attributes[key];
|
|
1581
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return void 0;
|
|
1585
|
+
}
|
|
1586
|
+
function readAttrNumber(spans, keys) {
|
|
1587
|
+
for (const span of spans) {
|
|
1588
|
+
for (const key of keys) {
|
|
1589
|
+
const v = span.attributes[key];
|
|
1590
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1591
|
+
if (typeof v === "string") {
|
|
1592
|
+
const parsed = Number(v);
|
|
1593
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return void 0;
|
|
1598
|
+
}
|
|
1599
|
+
function collectNumericAttrs(spans) {
|
|
1600
|
+
const raw = {};
|
|
1601
|
+
for (const span of spans) {
|
|
1602
|
+
for (const [k, v] of Object.entries(span.attributes)) {
|
|
1603
|
+
if (typeof v === "number" && Number.isFinite(v)) raw[k] = v;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return raw;
|
|
1607
|
+
}
|
|
1608
|
+
export {
|
|
1609
|
+
FileSystemOutcomeStore,
|
|
1610
|
+
InMemoryOutcomeStore,
|
|
1611
|
+
REFERENCE_EQUIVALENCE_INPUT_LIMITS,
|
|
1612
|
+
REFERENCE_EQUIVALENCE_JUDGE_VERSION,
|
|
1613
|
+
SelfImproveRunError,
|
|
1614
|
+
analyzeRuns,
|
|
1615
|
+
buildDefaultAnalystRegistry,
|
|
1616
|
+
buildEvidenceVector,
|
|
1617
|
+
campaignSplitDigest,
|
|
1618
|
+
composeGate,
|
|
1619
|
+
createChatClient,
|
|
1620
|
+
createReferenceEquivalenceJudge,
|
|
1621
|
+
defaultProductionGate,
|
|
1622
|
+
defineAgentEval,
|
|
1623
|
+
diffGenerations,
|
|
1624
|
+
diffRunBaselineToWinner,
|
|
1625
|
+
diffRuns,
|
|
1626
|
+
evalReportingSuite,
|
|
1627
|
+
evolutionaryProposer,
|
|
1628
|
+
fromClaudeCodeSession,
|
|
1629
|
+
fromCodexSession,
|
|
1630
|
+
fromFeedbackTable,
|
|
1631
|
+
fromKimiCodeSession,
|
|
1632
|
+
fromOpenCodeSession,
|
|
1633
|
+
fromOtelSpans,
|
|
1634
|
+
fromPiSession,
|
|
1635
|
+
fromPigraphSession,
|
|
1636
|
+
fromRunRecordDir,
|
|
1637
|
+
fsCampaignStorage,
|
|
1638
|
+
gepaProposer,
|
|
1639
|
+
heldOutGate,
|
|
1640
|
+
inMemoryCampaignStorage,
|
|
1641
|
+
measuredComparisonFromSelfImproveResult,
|
|
1642
|
+
paretoPolicy,
|
|
1643
|
+
paretoSignificanceGate,
|
|
1644
|
+
parseAgentTrace,
|
|
1645
|
+
parseCodeAgentJsonl,
|
|
1646
|
+
partitionRunsByAuthoringModel,
|
|
1647
|
+
runCampaign,
|
|
1648
|
+
runEval,
|
|
1649
|
+
runImprovementLoop,
|
|
1650
|
+
runReferenceEquivalenceJudge,
|
|
1651
|
+
selfImprove,
|
|
1652
|
+
summarizeExecution
|
|
1653
|
+
};
|
|
1654
|
+
//# sourceMappingURL=index.js.map
|