@leostera/evalkit 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -1
- package/README.md +10 -8
- package/dist/cli.mjs +1227 -258
- package/dist/dashboard/assets/index-BK9x-zoi.css +2 -0
- package/dist/dashboard/assets/index-DzsDLGHx.js +32 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/files-C-U0-PWW.mjs +0 -2
- package/dist/index-db-vbqEGFaU.mjs +0 -2
- package/dist/index.d.mts +1344 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -4
- package/dist/runner.d.mts +807 -11
- package/dist/runner.d.mts.map +1 -1
- package/dist/runner.mjs +2 -2
- package/dist/share-worker.d.mts +76 -1
- package/dist/share-worker.d.mts.map +1 -1
- package/dist/share-worker.mjs +0 -2
- package/dist/share.d.mts +149 -6
- package/dist/share.d.mts.map +1 -1
- package/dist/share.mjs +1 -1
- package/dist/skills/evalkit/SKILL.md +32 -0
- package/dist/skills/evalkit-agents/SKILL.md +16 -0
- package/dist/skills/evalkit-datasets/SKILL.md +17 -0
- package/dist/skills/evalkit-evals/SKILL.md +19 -0
- package/dist/skills/evalkit-judges/SKILL.md +17 -0
- package/dist/skills/evalkit-matrices/SKILL.md +14 -0
- package/dist/skills/evalkit-results/SKILL.md +14 -0
- package/dist/{src-DoPs9Yug.mjs → src-CVSCn3bZ.mjs} +445 -115
- package/dist/{src-DzZueFn3.mjs → src-CZnDXP1K.mjs} +46 -10
- package/dist/{src-ButXPY8P.mjs → src-D4tCwxNb.mjs} +418 -94
- package/package.json +3 -2
- package/dist/cli.mjs.map +0 -1
- package/dist/dashboard/assets/index-D9NtA46z.css +0 -2
- package/dist/dashboard/assets/index-DrBwLKJD.js +0 -30
- package/dist/files-C-U0-PWW.mjs.map +0 -1
- package/dist/index-db-HdZXelOS.d.mts +0 -142
- package/dist/index-db-HdZXelOS.d.mts.map +0 -1
- package/dist/index-db-vbqEGFaU.mjs.map +0 -1
- package/dist/index-lOw0fmak.d.mts +0 -1028
- package/dist/index-lOw0fmak.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/share-worker.mjs.map +0 -1
- package/dist/src-ButXPY8P.mjs.map +0 -1
- package/dist/src-DoPs9Yug.mjs.map +0 -1
- package/dist/src-DzZueFn3.mjs.map +0 -1
|
@@ -1,17 +1,36 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as TrialManifestSchema, C as RunManifestSchema, D as RunSummarySchema, F as normalizeAutEvent, G as relativeDatasetPath, H as toJsonObject, I as turnResultEvents, M as TrialScoringSchema, N as TrialSummarySchema, O as ScoreResultSchema, Q as resourceUri, R as canonicalParameters, V as toJson, b as JsonValueSchema, d as recordError, h as parseTrajectoryJsonl, i as authoringId, j as TrialMetadataSchema, k as TrajectoryEventSchema, r as agentRuntimes, w as RunMetadataSchema, y as CheckpointResultSchema } from "./src-D4tCwxNb.mjs";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import { Data, Effect, Either, Exit, Scope } from "effect";
|
|
4
|
-
import { appendFile, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
-
import path, { join } from "node:path";
|
|
6
4
|
import os from "node:os";
|
|
7
|
-
import {
|
|
5
|
+
import { appendFile, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import path, { join } from "node:path";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
//#region ../runner/src/errors.ts
|
|
9
9
|
var AutExecutionError = class extends Data.TaggedError("AutExecutionError") {};
|
|
10
|
-
var
|
|
10
|
+
var DatasetError = class extends Data.TaggedError("DatasetError") {};
|
|
11
11
|
var ReportError = class extends Data.TaggedError("ReportError") {};
|
|
12
12
|
var ScoringError = class extends Data.TaggedError("ScoringError") {};
|
|
13
13
|
var RuleExecutionError = class extends Data.TaggedError("RuleExecutionError") {};
|
|
14
14
|
var CheckpointExecutionError = class extends Data.TaggedError("CheckpointExecutionError") {};
|
|
15
|
+
/** The run was cancelled (Ctrl+C); the trial stops and is finalized as cancelled. */
|
|
16
|
+
var CancelledError = class extends Data.TaggedError("CancelledError") {};
|
|
17
|
+
function cancelled() {
|
|
18
|
+
return new CancelledError({ message: "Cancelled" });
|
|
19
|
+
}
|
|
20
|
+
/** Settles like `work`, or rejects with `CancelledError` as soon as `signal` aborts. */
|
|
21
|
+
async function unlessAborted(work, signal) {
|
|
22
|
+
if (signal.aborted) throw cancelled();
|
|
23
|
+
let onAbort;
|
|
24
|
+
const aborted = new Promise((_, reject) => {
|
|
25
|
+
onAbort = () => reject(cancelled());
|
|
26
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([work(), aborted]);
|
|
30
|
+
} finally {
|
|
31
|
+
signal.removeEventListener("abort", onAbort);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
15
34
|
//#endregion
|
|
16
35
|
//#region ../runner/src/snapshot.ts
|
|
17
36
|
/**
|
|
@@ -51,46 +70,193 @@ async function snapshotCandidateWorkspace(candidateRoot, writer) {
|
|
|
51
70
|
return artifacts;
|
|
52
71
|
}
|
|
53
72
|
//#endregion
|
|
54
|
-
//#region ../runner/src/
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
73
|
+
//#region ../runner/src/datasets.ts
|
|
74
|
+
const loads = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
/**
|
|
76
|
+
* Loads a dataset at most once per process and project root. A failed load is
|
|
77
|
+
* forgotten, so the next trial that needs the dataset tries again.
|
|
78
|
+
*/
|
|
79
|
+
function loadDataset(dataset, root, signal) {
|
|
80
|
+
let byRoot = loads.get(dataset);
|
|
81
|
+
if (!byRoot) loads.set(dataset, byRoot = /* @__PURE__ */ new Map());
|
|
82
|
+
const cached = byRoot.get(root);
|
|
83
|
+
if (cached) return cached;
|
|
84
|
+
const loading = loadUncached(dataset, root, signal);
|
|
85
|
+
byRoot.set(root, loading);
|
|
86
|
+
loading.catch(() => {
|
|
87
|
+
if (byRoot.get(root) === loading) byRoot.delete(root);
|
|
88
|
+
});
|
|
89
|
+
return loading;
|
|
64
90
|
}
|
|
65
|
-
async function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
async function loadUncached(dataset, root, signal) {
|
|
92
|
+
const { source } = dataset;
|
|
93
|
+
if (source.kind === "computed") {
|
|
94
|
+
const loaded = await source.onLoad({
|
|
95
|
+
root,
|
|
96
|
+
signal
|
|
97
|
+
});
|
|
98
|
+
if (typeof loaded !== "object" || loaded === null) throw new Error(`Dataset ${dataset.id} onLoad must return { files?, value? }`);
|
|
99
|
+
return loaded;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...source.files !== void 0 ? { files: source.files } : {},
|
|
103
|
+
...source.valueFile !== void 0 ? { value: await readValueFile(dataset.id, path.resolve(root, source.valueFile)) } : source.value !== void 0 ? { value: source.value } : {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** `.json` is one value; `.jsonl` is an array of one JSON object per non-blank line. */
|
|
107
|
+
async function readValueFile(id, file) {
|
|
108
|
+
const text = await readFile(file, "utf8").catch((cause) => {
|
|
109
|
+
throw new Error(`Dataset ${id} cannot read ${file}: ${String(cause)}`);
|
|
110
|
+
});
|
|
111
|
+
if (file.endsWith(".jsonl")) return text.split("\n").flatMap((line, index) => {
|
|
112
|
+
if (!line.trim()) return [];
|
|
113
|
+
try {
|
|
114
|
+
const row = JSON.parse(line);
|
|
115
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) throw new Error("expected a JSON object");
|
|
116
|
+
return [row];
|
|
117
|
+
} catch (cause) {
|
|
118
|
+
throw new Error(`Dataset ${id} ${file}:${index + 1}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
72
119
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
120
|
+
});
|
|
121
|
+
if (file.endsWith(".json")) try {
|
|
122
|
+
return JSON.parse(text);
|
|
123
|
+
} catch (cause) {
|
|
124
|
+
throw new Error(`Dataset ${id} ${file}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
125
|
+
}
|
|
126
|
+
throw new Error(`Dataset ${id} valueFile must be a .json or .jsonl file: ${file}`);
|
|
127
|
+
}
|
|
128
|
+
/** Loads (cached), derives this trial's content with `onRun`, and resolves it. */
|
|
129
|
+
async function prepareDatasets(datasets, root, trial) {
|
|
130
|
+
const prepared = [];
|
|
131
|
+
for (const dataset of datasets) {
|
|
132
|
+
const loaded = await loadDataset(dataset, root, trial.signal);
|
|
133
|
+
const content = dataset.source.kind === "computed" && dataset.source.onRun ? await dataset.source.onRun(loaded, trial) : loaded;
|
|
134
|
+
if (typeof content !== "object" || content === null) throw new Error(`Dataset ${dataset.id} onRun must return { files?, value? }`);
|
|
135
|
+
prepared.push(await resolveContent(dataset, content, root));
|
|
136
|
+
}
|
|
137
|
+
return prepared;
|
|
138
|
+
}
|
|
139
|
+
async function resolveContent(dataset, content, root) {
|
|
140
|
+
const label = `Dataset ${dataset.id}`;
|
|
141
|
+
const value = content.value === void 0 ? void 0 : toJson(content.value, `datasets.${dataset.id}.value`);
|
|
142
|
+
let files = { kind: "none" };
|
|
143
|
+
if (typeof content.files === "string") {
|
|
144
|
+
const source = path.resolve(root, content.files);
|
|
145
|
+
if (!(await stat(source).catch(() => void 0))?.isDirectory()) throw new Error(`${label} files directory does not exist: ${content.files}`);
|
|
146
|
+
files = {
|
|
147
|
+
kind: "directory",
|
|
148
|
+
source
|
|
149
|
+
};
|
|
150
|
+
} else if (content.files !== void 0) files = {
|
|
151
|
+
kind: "entries",
|
|
152
|
+
entries: Object.entries(content.files).map(([file, source]) => preparedFile(relativeDatasetPath(file, `${label} file`), source, root, label))
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
dataset,
|
|
156
|
+
files,
|
|
157
|
+
...value === void 0 ? {} : { value },
|
|
158
|
+
digest: await digest(files, value)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function preparedFile(file, source, root, label) {
|
|
162
|
+
if (typeof source === "string") return {
|
|
163
|
+
path: file,
|
|
164
|
+
kind: "bytes",
|
|
165
|
+
data: new TextEncoder().encode(source)
|
|
166
|
+
};
|
|
167
|
+
if (source instanceof Uint8Array) return {
|
|
168
|
+
path: file,
|
|
169
|
+
kind: "bytes",
|
|
170
|
+
data: source
|
|
171
|
+
};
|
|
172
|
+
if (typeof source === "object" && source !== null && typeof source.file === "string") return {
|
|
173
|
+
path: file,
|
|
174
|
+
kind: "file",
|
|
175
|
+
source: path.resolve(root, source.file)
|
|
176
|
+
};
|
|
177
|
+
throw new Error(`${label} file ${file} must be a string, bytes, or { file: 'path' }`);
|
|
178
|
+
}
|
|
179
|
+
/** Lists a directory's files as sorted relative POSIX paths. */
|
|
180
|
+
async function listFiles(directory, prefix = "") {
|
|
181
|
+
const entries = await readdir(path.join(directory, prefix), { withFileTypes: true });
|
|
182
|
+
const files = [];
|
|
183
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
184
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
185
|
+
if (entry.isDirectory()) files.push(...await listFiles(directory, relative));
|
|
186
|
+
else if (entry.isFile()) files.push(relative);
|
|
76
187
|
}
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
188
|
+
return files;
|
|
189
|
+
}
|
|
190
|
+
async function digest(files, value) {
|
|
191
|
+
const hash = createHash("sha256");
|
|
192
|
+
const addFile = (file, data) => {
|
|
193
|
+
hash.update(`file ${file} ${data.byteLength}\n`);
|
|
194
|
+
hash.update(data);
|
|
195
|
+
};
|
|
196
|
+
if (files.kind === "directory") for (const file of await listFiles(files.source)) addFile(file, await readFile(path.join(files.source, file)));
|
|
197
|
+
else if (files.kind === "entries") for (const entry of [...files.entries].sort((a, b) => a.path.localeCompare(b.path))) addFile(entry.path, entry.kind === "bytes" ? entry.data : await readFile(entry.source));
|
|
198
|
+
if (value !== void 0) hash.update(`value ${canonicalParameters(value)}\n`);
|
|
199
|
+
return `sha256:${hash.digest("hex")}`;
|
|
200
|
+
}
|
|
201
|
+
function trialDatasets(prepared) {
|
|
202
|
+
return prepared.map(({ dataset, digest }) => ({
|
|
203
|
+
id: dataset.id,
|
|
204
|
+
visibility: dataset.visibility,
|
|
205
|
+
source: dataset.source.kind,
|
|
206
|
+
digest
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The files a static dataset places in every workspace, relative to its
|
|
211
|
+
* mount. Computed datasets return `undefined`: their files only exist once
|
|
212
|
+
* `onLoad` runs, and browsing must never run project code.
|
|
213
|
+
*/
|
|
214
|
+
async function staticDatasetFiles(dataset, root) {
|
|
215
|
+
const { source } = dataset;
|
|
216
|
+
if (source.kind === "computed") return void 0;
|
|
217
|
+
if (source.files === void 0) return [];
|
|
218
|
+
if (typeof source.files === "string") {
|
|
219
|
+
const directory = path.resolve(root, source.files);
|
|
220
|
+
return Promise.all((await listFiles(directory)).map(async (file) => ({
|
|
221
|
+
path: file,
|
|
222
|
+
size: (await stat(path.join(directory, file))).size
|
|
223
|
+
})));
|
|
86
224
|
}
|
|
225
|
+
return Promise.all(Object.entries(source.files).sort(([a], [b]) => a.localeCompare(b)).map(async ([file, content]) => ({
|
|
226
|
+
path: file,
|
|
227
|
+
size: typeof content === "string" ? new TextEncoder().encode(content).byteLength : content instanceof Uint8Array ? content.byteLength : (await stat(path.resolve(root, content.file))).size
|
|
228
|
+
})));
|
|
87
229
|
}
|
|
88
|
-
/**
|
|
230
|
+
/** One file of a static dataset, by its path below the mount. */
|
|
231
|
+
async function staticDatasetFile(dataset, root, file) {
|
|
232
|
+
const { source } = dataset;
|
|
233
|
+
const missing = () => /* @__PURE__ */ new Error(`Dataset ${dataset.id} has no file ${JSON.stringify(file)}`);
|
|
234
|
+
if (source.kind === "computed" || source.files === void 0) throw missing();
|
|
235
|
+
if (typeof source.files === "string") {
|
|
236
|
+
const directory = path.resolve(root, source.files);
|
|
237
|
+
const resolved = path.resolve(directory, file);
|
|
238
|
+
if (!resolved.startsWith(`${directory}${path.sep}`)) throw missing();
|
|
239
|
+
if (!(await stat(resolved).catch(() => void 0))?.isFile()) throw missing();
|
|
240
|
+
return readFile(resolved);
|
|
241
|
+
}
|
|
242
|
+
if (!Object.hasOwn(source.files, file)) throw missing();
|
|
243
|
+
const content = source.files[file];
|
|
244
|
+
if (typeof content === "string") return new TextEncoder().encode(content);
|
|
245
|
+
if (content instanceof Uint8Array) return new Uint8Array(content);
|
|
246
|
+
return readFile(path.resolve(root, content.file));
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region ../runner/src/workspace.ts
|
|
89
250
|
function trialDirectoryName(trialId) {
|
|
90
251
|
return trialId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
91
252
|
}
|
|
92
|
-
|
|
93
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Creates isolated candidate and evaluator directories and writes each
|
|
255
|
+
* dataset's files below its mount, on the side its visibility allows.
|
|
256
|
+
* Two datasets writing the same file is an error, never an overwrite.
|
|
257
|
+
*/
|
|
258
|
+
async function createTrialWorkspace(trialId, datasets = [], persistentRoot) {
|
|
259
|
+
const root = persistentRoot ? path.join(path.resolve(persistentRoot), trialDirectoryName(trialId)) : await mkdtemp(path.join(os.tmpdir(), "evalkit-trial-"));
|
|
94
260
|
const persistent = persistentRoot !== void 0;
|
|
95
261
|
const artifacts = {
|
|
96
262
|
candidate: { root: path.join(root, "candidate") },
|
|
@@ -98,18 +264,9 @@ async function createTrialWorkspace(context, fixtures = [], persistentRoot) {
|
|
|
98
264
|
};
|
|
99
265
|
try {
|
|
100
266
|
await Promise.all([mkdir(artifacts.candidate.root, { recursive: true }), mkdir(artifacts.evaluator.root, { recursive: true })]);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const target = destinationPath(workspaceRoot(fixture.visibility, artifacts), fixture.kind === "inline" ? fixture.file : fixture.dst);
|
|
105
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
106
|
-
if (fixture.kind === "directory") await cp(fixture.src, target, {
|
|
107
|
-
recursive: true,
|
|
108
|
-
force: false,
|
|
109
|
-
errorOnExist: true
|
|
110
|
-
});
|
|
111
|
-
else if (fixture.kind === "file") await copyFile(fixture.src, target);
|
|
112
|
-
else await writeFile(target, fixture.data);
|
|
267
|
+
for (const prepared of datasets) {
|
|
268
|
+
const side = prepared.dataset.visibility === "candidate" ? artifacts.candidate.root : artifacts.evaluator.root;
|
|
269
|
+
await writeDatasetFiles(prepared, path.join(side, prepared.dataset.mount));
|
|
113
270
|
}
|
|
114
271
|
return {
|
|
115
272
|
artifacts,
|
|
@@ -126,21 +283,50 @@ async function createTrialWorkspace(context, fixtures = [], persistentRoot) {
|
|
|
126
283
|
throw error;
|
|
127
284
|
}
|
|
128
285
|
}
|
|
286
|
+
async function writeDatasetFiles(prepared, target) {
|
|
287
|
+
const { files, dataset } = prepared;
|
|
288
|
+
if (files.kind === "none") return;
|
|
289
|
+
await mkdir(target, { recursive: true });
|
|
290
|
+
const entries = files.kind === "directory" ? (await listFiles(files.source)).map((file) => ({
|
|
291
|
+
path: file,
|
|
292
|
+
kind: "file",
|
|
293
|
+
source: path.join(files.source, file)
|
|
294
|
+
})) : files.entries;
|
|
295
|
+
for (const entry of entries) await writeEntry(dataset.id, target, entry);
|
|
296
|
+
}
|
|
297
|
+
async function writeEntry(datasetId, target, entry) {
|
|
298
|
+
const destination = path.join(target, ...entry.path.split("/"));
|
|
299
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
300
|
+
await (entry.kind === "bytes" ? writeFile(destination, entry.data, { flag: "wx" }) : copyFile(entry.source, destination, 1)).catch((cause) => {
|
|
301
|
+
throw collision(datasetId, entry.path, cause);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function collision(datasetId, file, cause) {
|
|
305
|
+
return cause.code === "EEXIST" ? /* @__PURE__ */ new Error(`Dataset ${datasetId} would overwrite ${file}, which another dataset already wrote`) : cause instanceof Error ? cause : new Error(String(cause));
|
|
306
|
+
}
|
|
129
307
|
//#endregion
|
|
130
308
|
//#region ../runner/src/score.ts
|
|
131
|
-
/**
|
|
132
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Shared normalization for inline and final predicate and judge rules.
|
|
311
|
+
* `passAt` turns a numeric value into a verdict; an explicit `passed` wins.
|
|
312
|
+
*/
|
|
313
|
+
function normalizeScore(value, passAt) {
|
|
314
|
+
if (typeof value === "object" && value !== null && "skipped" in value && value.skipped === true) return {
|
|
315
|
+
skipped: true,
|
|
316
|
+
explanation: value.reason
|
|
317
|
+
};
|
|
133
318
|
const normalized = typeof value === "boolean" ? { value: Number(value) } : typeof value === "number" ? { value } : value;
|
|
134
319
|
if (!normalized || !Number.isFinite(normalized.value) || normalized.value < 0 || normalized.value > 1) throw new Error(`Score value must be a finite number from 0 to 1; received ${normalized?.value}`);
|
|
135
320
|
if ("judge" in normalized && normalized.judge) {
|
|
136
321
|
const { usage } = normalized.judge;
|
|
137
322
|
if (usage && Object.values(usage).some((tokens) => !Number.isSafeInteger(tokens) || tokens < 0)) throw new Error("Judge token usage must contain nonnegative integers");
|
|
138
323
|
}
|
|
324
|
+
const passed = "passed" in normalized && normalized.passed !== void 0 ? normalized.passed : normalized.value >= (passAt ?? 1);
|
|
139
325
|
return {
|
|
140
326
|
value: normalized.value,
|
|
141
|
-
passed
|
|
142
|
-
...normalized.explanation ? { explanation: normalized.explanation } : {},
|
|
143
|
-
...normalized.evidence
|
|
327
|
+
passed,
|
|
328
|
+
..."explanation" in normalized && normalized.explanation ? { explanation: normalized.explanation } : {},
|
|
329
|
+
..."evidence" in normalized && normalized.evidence !== void 0 ? { evidence: toJson(normalized.evidence, "evidence") } : {},
|
|
144
330
|
..."judge" in normalized && normalized.judge !== void 0 ? { judge: normalized.judge } : {}
|
|
145
331
|
};
|
|
146
332
|
}
|
|
@@ -156,6 +342,7 @@ function runJudgeAgent(rule, agent, input, placement) {
|
|
|
156
342
|
const events = [];
|
|
157
343
|
const context = {
|
|
158
344
|
...input.context,
|
|
345
|
+
data: input.data,
|
|
159
346
|
workspace: input.artifacts.evaluator,
|
|
160
347
|
evaluatorWorkspace: input.artifacts.evaluator
|
|
161
348
|
};
|
|
@@ -173,7 +360,7 @@ function runJudgeAgent(rule, agent, input, placement) {
|
|
|
173
360
|
context,
|
|
174
361
|
onEvent: (event) => {
|
|
175
362
|
const observed = Schema.decodeUnknownSync(TrajectoryEventSchema)({
|
|
176
|
-
...event,
|
|
363
|
+
...normalizeAutEvent(event, () => /* @__PURE__ */ new Date()),
|
|
177
364
|
source: "aut"
|
|
178
365
|
});
|
|
179
366
|
if (observed.source !== "aut") throw new Error("Invalid judge event source");
|
|
@@ -183,10 +370,13 @@ function runJudgeAgent(rule, agent, input, placement) {
|
|
|
183
370
|
}),
|
|
184
371
|
catch: (cause) => failure$2(rule.name, cause)
|
|
185
372
|
}), (session) => Effect.tryPromise({
|
|
186
|
-
try: () =>
|
|
373
|
+
try: async () => {
|
|
374
|
+
const result = await session.send(prompt);
|
|
375
|
+
if (result !== void 0) events.push(...turnResultEvents(result, 1, () => /* @__PURE__ */ new Date()));
|
|
376
|
+
},
|
|
187
377
|
catch: (cause) => failure$2(rule.name, cause)
|
|
188
378
|
}), (session) => Effect.tryPromise({
|
|
189
|
-
try: () => session.close(),
|
|
379
|
+
try: async () => session.close?.(),
|
|
190
380
|
catch: (cause) => failure$2(rule.name, cause)
|
|
191
381
|
}).pipe(Effect.catchAll((error) => Effect.sync(() => {
|
|
192
382
|
closeError = error;
|
|
@@ -229,7 +419,7 @@ function evaluateRule(rule, input, judgeAgent, placement) {
|
|
|
229
419
|
catch: (cause) => failure$1(rule.name, cause)
|
|
230
420
|
}) } : yield* runJudgeAgent(rule, judgeAgent, input, placement);
|
|
231
421
|
const score = yield* Effect.try({
|
|
232
|
-
try: () => normalizeScore(evaluated.value),
|
|
422
|
+
try: () => normalizeScore(evaluated.value, rule.kind === "predicate" ? rule.passAt : void 0),
|
|
233
423
|
catch: (cause) => failure$1(rule.name, cause)
|
|
234
424
|
});
|
|
235
425
|
const withJudge = rule.kind === "judge" && "judge" in evaluated ? {
|
|
@@ -242,6 +432,10 @@ function evaluateRule(rule, input, judgeAgent, placement) {
|
|
|
242
432
|
durationMs: 0,
|
|
243
433
|
...withJudge
|
|
244
434
|
}).pipe(Effect.mapError((cause) => failure$1(rule.name, cause)));
|
|
435
|
+
if (validated.skipped) return {
|
|
436
|
+
skipped: true,
|
|
437
|
+
...validated.explanation === void 0 ? {} : { explanation: validated.explanation }
|
|
438
|
+
};
|
|
245
439
|
return {
|
|
246
440
|
value: validated.value,
|
|
247
441
|
passed: validated.passed,
|
|
@@ -315,24 +509,26 @@ function evaluateCheckpoint(step, index, input, now, judgeAgent) {
|
|
|
315
509
|
try: () => normalizeScore(Boolean(match)),
|
|
316
510
|
catch: (cause) => failure(index, step.name, cause)
|
|
317
511
|
}) : yield* evaluateRule(step, input, judgeAgent, "transcript").pipe(Effect.mapError((cause) => failure(index, step.name, cause)));
|
|
512
|
+
const status = score.skipped ? "skipped" : score.passed ? "passed" : "failed";
|
|
513
|
+
const { skipped: _, ...recorded } = score;
|
|
318
514
|
const result = yield* Schema.decodeUnknown(CheckpointResultSchema)({
|
|
319
515
|
step: index,
|
|
320
516
|
kind: step.kind,
|
|
321
517
|
name: step.name,
|
|
322
|
-
status
|
|
518
|
+
status,
|
|
323
519
|
durationMs: now().getTime() - started.getTime(),
|
|
324
|
-
...
|
|
520
|
+
...recorded,
|
|
325
521
|
...match ? { matchedToolCall: {
|
|
326
522
|
eventIndex: match.eventIndex,
|
|
327
523
|
id: match.id
|
|
328
524
|
} } : {}
|
|
329
525
|
}).pipe(Effect.mapError((cause) => failure(index, step.name, cause)));
|
|
330
|
-
yield* Effect.
|
|
526
|
+
yield* Effect.logDebug("checkpoint completed").pipe(Effect.annotateLogs({ status: result.status }));
|
|
331
527
|
return {
|
|
332
528
|
...result,
|
|
333
|
-
status
|
|
529
|
+
status
|
|
334
530
|
};
|
|
335
|
-
}).pipe(Effect.tapError((error) => Effect.
|
|
531
|
+
}).pipe(Effect.tapError((error) => Effect.logDebug("checkpoint execution error").pipe(Effect.annotateLogs({ errorName: error._tag }))), Effect.annotateLogs(annotations));
|
|
336
532
|
}
|
|
337
533
|
//#endregion
|
|
338
534
|
//#region ../runner/src/effect-boundary.ts
|
|
@@ -346,7 +542,9 @@ async function runBoundary(effect) {
|
|
|
346
542
|
//#region ../runner/src/transcript.ts
|
|
347
543
|
async function executeTranscript(definition, session, input) {
|
|
348
544
|
const { now, emitRunner, progress } = input;
|
|
545
|
+
let turn = 0;
|
|
349
546
|
for (const [index, step] of definition.transcript.entries()) {
|
|
547
|
+
if (input.signal.aborted) throw cancelled();
|
|
350
548
|
await emitRunner({
|
|
351
549
|
kind: "transcript-step-started",
|
|
352
550
|
step: index,
|
|
@@ -354,7 +552,9 @@ async function executeTranscript(definition, session, input) {
|
|
|
354
552
|
});
|
|
355
553
|
if (step.kind === "user") {
|
|
356
554
|
const cursor = input.events.length;
|
|
357
|
-
|
|
555
|
+
turn++;
|
|
556
|
+
const result = await sendUserTurn(session, step.message.replaceAll("{{randomSeed}}", String(input.randomSeed)), input.signal);
|
|
557
|
+
if (result !== void 0) for (const event of turnResultEvents(result, turn, now)) await input.emitAut(event);
|
|
358
558
|
progress.lastTurn = turnView(input.events, cursor, input.events.length, index);
|
|
359
559
|
} else if (step.kind === "predicate" || step.kind === "judge" || step.kind === "expect-tool-call") {
|
|
360
560
|
const result = await runCheckpoint(definition, step, index, input);
|
|
@@ -370,7 +570,7 @@ async function executeTranscript(definition, session, input) {
|
|
|
370
570
|
step: index,
|
|
371
571
|
timestamp: now().toISOString()
|
|
372
572
|
});
|
|
373
|
-
if (
|
|
573
|
+
if (result.status === "failed" && definition.policy?.failfast) {
|
|
374
574
|
progress.stoppedEarly = true;
|
|
375
575
|
await skipRemainingSteps(definition, index, input);
|
|
376
576
|
break;
|
|
@@ -384,10 +584,10 @@ async function executeTranscript(definition, session, input) {
|
|
|
384
584
|
});
|
|
385
585
|
}
|
|
386
586
|
}
|
|
387
|
-
async function sendUserTurn(session, message) {
|
|
388
|
-
|
|
389
|
-
try: () => session.send(message),
|
|
390
|
-
catch: (cause) => cause instanceof ReportError ? cause : new AutExecutionError({
|
|
587
|
+
async function sendUserTurn(session, message, signal) {
|
|
588
|
+
return runBoundary(Effect.tryPromise({
|
|
589
|
+
try: () => unlessAborted(() => session.send(message), signal),
|
|
590
|
+
catch: (cause) => cause instanceof ReportError || cause instanceof CancelledError ? cause : new AutExecutionError({
|
|
391
591
|
cause,
|
|
392
592
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
393
593
|
})
|
|
@@ -407,7 +607,9 @@ async function runCheckpoint(definition, step, index, input) {
|
|
|
407
607
|
context: input.context,
|
|
408
608
|
artifacts: input.artifacts,
|
|
409
609
|
trajectory: { events: input.events },
|
|
410
|
-
turn: progress.lastTurn
|
|
610
|
+
turn: progress.lastTurn,
|
|
611
|
+
...turnOutput(progress.lastTurn),
|
|
612
|
+
data: input.data
|
|
411
613
|
}, now, definition.judge)).catch(async (error) => {
|
|
412
614
|
const recorded = recordError(error);
|
|
413
615
|
progress.checkpoints.push({
|
|
@@ -445,24 +647,29 @@ async function skipRemainingSteps(definition, after, { now, emitRunner, progress
|
|
|
445
647
|
});
|
|
446
648
|
}
|
|
447
649
|
}
|
|
650
|
+
/** The scorer-facing `output`: the content of the turn's last assistant message. */
|
|
651
|
+
function turnOutput(turn) {
|
|
652
|
+
const last = turn?.assistantMessages.at(-1);
|
|
653
|
+
return last === void 0 ? {} : { output: last.content };
|
|
654
|
+
}
|
|
448
655
|
//#endregion
|
|
449
656
|
//#region ../runner/src/final-scoring.ts
|
|
450
657
|
function scoreSummary(results, checkpoints, skippedScorers) {
|
|
451
|
-
const valid = results.filter((result) => result.value !== void 0);
|
|
658
|
+
const valid = results.filter((result) => result.value !== void 0 && !result.skipped);
|
|
452
659
|
const hasError = results.some((result) => result.error !== void 0);
|
|
453
660
|
return {
|
|
454
661
|
results,
|
|
455
662
|
...checkpoints.length ? { checkpoints } : {},
|
|
456
663
|
...skippedScorers.length ? { skippedScorers } : {},
|
|
457
664
|
...valid.length === 0 ? {} : { overall: valid.reduce((sum, result) => sum + (result.value ?? 0), 0) / valid.length },
|
|
458
|
-
passed: !hasError && results.every((result) => result.passed === true) && checkpoints.every((result) => result.status === "passed") && !skippedScorers.length
|
|
665
|
+
passed: !hasError && results.every((result) => result.skipped || result.passed === true) && checkpoints.every((result) => result.status === "passed" || result.status === "skipped") && !skippedScorers.length
|
|
459
666
|
};
|
|
460
667
|
}
|
|
461
668
|
/** Append completed scores to the supplied arrays, even when a later scorer or report write fails. */
|
|
462
669
|
async function evaluateFinalScorers(definition, input) {
|
|
463
670
|
const { context, workspace, events, lastTurn, results, skippedScorers, now, emitRunner } = input;
|
|
464
671
|
for (const scorer of definition.scoring) {
|
|
465
|
-
if (input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
672
|
+
if (input.cancelled || input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
466
673
|
skippedScorers.push(scorer.name);
|
|
467
674
|
continue;
|
|
468
675
|
}
|
|
@@ -478,7 +685,9 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
478
685
|
context,
|
|
479
686
|
artifacts: workspace.artifacts,
|
|
480
687
|
trajectory: { events },
|
|
481
|
-
...lastTurn ? { turn: lastTurn } : {}
|
|
688
|
+
...lastTurn ? { turn: lastTurn } : {},
|
|
689
|
+
...turnOutput(lastTurn),
|
|
690
|
+
data: input.data
|
|
482
691
|
}, definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
483
692
|
cause,
|
|
484
693
|
message: cause.message
|
|
@@ -510,7 +719,7 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
510
719
|
await emitRunner({
|
|
511
720
|
kind: "scorer-completed",
|
|
512
721
|
scorer: scorer.name,
|
|
513
|
-
value: result.value ?? 0,
|
|
722
|
+
...result.skipped ? { skipped: true } : { value: result.value ?? 0 },
|
|
514
723
|
timestamp: now().toISOString()
|
|
515
724
|
});
|
|
516
725
|
}
|
|
@@ -618,6 +827,20 @@ const readTrialScoring = (root, runId, trialId) => decode(join(root, runId, "tri
|
|
|
618
827
|
async function readTrialEvents(root, runId, trialId) {
|
|
619
828
|
return parseTrajectoryJsonl(await readFile(join(root, runId, "trials", trialId, "trajectory.jsonl"), "utf8"));
|
|
620
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* True when a run's owning process is known to be gone: it ran on this host
|
|
832
|
+
* and its PID no longer exists. Runs from other hosts are never judged.
|
|
833
|
+
*/
|
|
834
|
+
function ownerIsGone(owner) {
|
|
835
|
+
if (!owner || owner.hostname !== os.hostname()) return false;
|
|
836
|
+
if (owner.pid === process.pid) return false;
|
|
837
|
+
try {
|
|
838
|
+
process.kill(owner.pid, 0);
|
|
839
|
+
return false;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
return error.code === "ESRCH";
|
|
842
|
+
}
|
|
843
|
+
}
|
|
621
844
|
//#endregion
|
|
622
845
|
//#region ../runner/src/matrix.ts
|
|
623
846
|
/** Bounded workers pull cells from the iterator; no Cartesian job array is allocated. */
|
|
@@ -630,10 +853,11 @@ function runMatrix(matrix, options) {
|
|
|
630
853
|
const summary = {
|
|
631
854
|
cells: 0,
|
|
632
855
|
passed: 0,
|
|
633
|
-
failed: 0
|
|
856
|
+
failed: 0,
|
|
857
|
+
cancelled: 0
|
|
634
858
|
};
|
|
635
859
|
yield* Effect.all(Array.from({ length: Math.min(count, concurrency) }, () => Effect.gen(function* () {
|
|
636
|
-
while (
|
|
860
|
+
while (!options.signal?.aborted) {
|
|
637
861
|
const next = iterator.next();
|
|
638
862
|
if (next.done) return;
|
|
639
863
|
const cell = next.value;
|
|
@@ -641,9 +865,11 @@ function runMatrix(matrix, options) {
|
|
|
641
865
|
"local",
|
|
642
866
|
"remote",
|
|
643
867
|
"sandbox"
|
|
644
|
-
].find((name) => cell.eval.agent
|
|
868
|
+
].find((name) => agentRuntimes(cell.eval.agent)[name]);
|
|
869
|
+
const suiteId = options.suiteId ?? options.suiteFor?.(cell);
|
|
645
870
|
const result = yield* runEval(cell.eval, {
|
|
646
871
|
...options,
|
|
872
|
+
...suiteId ? { suiteId } : {},
|
|
647
873
|
concurrency: 1,
|
|
648
874
|
runtime,
|
|
649
875
|
parameters: cell.parameters,
|
|
@@ -653,7 +879,8 @@ function runMatrix(matrix, options) {
|
|
|
653
879
|
}
|
|
654
880
|
});
|
|
655
881
|
summary.cells++;
|
|
656
|
-
if (result.status === "
|
|
882
|
+
if (result.status === "cancelled") summary.cancelled++;
|
|
883
|
+
else if (result.status === "completed" && (result.aggregateScoring?.passRate === 1 || result.scoring?.passed)) summary.passed++;
|
|
657
884
|
else summary.failed++;
|
|
658
885
|
if (options.onResult) yield* Effect.tryPromise(async () => options.onResult(cell, result));
|
|
659
886
|
}
|
|
@@ -694,12 +921,16 @@ function assertUuid(value, label) {
|
|
|
694
921
|
}
|
|
695
922
|
function runMetadata(definition, options, runId, startedAt) {
|
|
696
923
|
return {
|
|
697
|
-
schemaVersion:
|
|
924
|
+
schemaVersion: 5,
|
|
698
925
|
runId,
|
|
699
926
|
runUri: canonicalId("run", runId),
|
|
700
927
|
evalId: definition.id,
|
|
701
928
|
...options.suiteId ? { suiteId: options.suiteId } : {},
|
|
702
929
|
...options.source ? { source: options.source } : {},
|
|
930
|
+
process: {
|
|
931
|
+
pid: process.pid,
|
|
932
|
+
hostname: os.hostname()
|
|
933
|
+
},
|
|
703
934
|
...definition.agent.identity ? { aut: definition.agent.identity } : {},
|
|
704
935
|
parameters: options.parameters,
|
|
705
936
|
matrix: options.matrix,
|
|
@@ -731,9 +962,10 @@ async function executeRun(definition, options) {
|
|
|
731
962
|
const aggregateStartedAt = now();
|
|
732
963
|
const runId = options.runId ?? createId("run");
|
|
733
964
|
const runWriter = await options.report.startRun(runMetadata(definition, options, runId, aggregateStartedAt.toISOString()), { catalog: options.catalog });
|
|
965
|
+
const signal = options.signal;
|
|
734
966
|
const trialEffects = Array.from({ length: requestedTrials }, (_, trialIndex) => {
|
|
735
967
|
const trial = Effect.tryPromise({
|
|
736
|
-
try: () => runTrial(definition, {
|
|
968
|
+
try: async () => signal?.aborted ? void 0 : runTrial(definition, {
|
|
737
969
|
...options,
|
|
738
970
|
runId,
|
|
739
971
|
trialId: createId("trial"),
|
|
@@ -745,18 +977,38 @@ async function executeRun(definition, options) {
|
|
|
745
977
|
});
|
|
746
978
|
return options.semaphore ? options.semaphore.withPermits(1)(trial) : trial;
|
|
747
979
|
});
|
|
748
|
-
const trials = await Effect.runPromise(Effect.all(trialEffects, { concurrency: options.concurrency ?? 32 }));
|
|
749
|
-
const
|
|
750
|
-
const status = trials.some((trial) => trial.status === "failed") ? "failed" : "completed";
|
|
980
|
+
const trials = (await Effect.runPromise(Effect.all(trialEffects, { concurrency: options.concurrency ?? 32 }))).filter((trial) => trial !== void 0);
|
|
981
|
+
const counts = trialCounts(trials);
|
|
982
|
+
const status = counts.cancelled || signal?.aborted ? "cancelled" : trials.some((trial) => trial.status === "failed") ? "failed" : "completed";
|
|
751
983
|
const endedAt = now();
|
|
752
984
|
await runWriter.finalize({
|
|
753
985
|
status,
|
|
754
986
|
endedAt: endedAt.toISOString(),
|
|
755
987
|
durationMs: endedAt.getTime() - aggregateStartedAt.getTime(),
|
|
756
988
|
trialCount: trials.length,
|
|
757
|
-
passed:
|
|
758
|
-
failed
|
|
989
|
+
passed: counts.passed,
|
|
990
|
+
failed: counts.failed,
|
|
991
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {}
|
|
759
992
|
});
|
|
993
|
+
if (!trials.length) return {
|
|
994
|
+
runId,
|
|
995
|
+
runUri: canonicalId("run", runId),
|
|
996
|
+
trialId: "",
|
|
997
|
+
trialUri: canonicalId("trial", runId),
|
|
998
|
+
trialIndex: 0,
|
|
999
|
+
evalId: authoringId(definition),
|
|
1000
|
+
status,
|
|
1001
|
+
reportLocation: runWriter.location,
|
|
1002
|
+
trialCount: 0,
|
|
1003
|
+
passed: 0,
|
|
1004
|
+
failed: 0,
|
|
1005
|
+
aggregateScoring: {
|
|
1006
|
+
passed: 0,
|
|
1007
|
+
failed: 0,
|
|
1008
|
+
passRate: 0
|
|
1009
|
+
},
|
|
1010
|
+
trials: []
|
|
1011
|
+
};
|
|
760
1012
|
const overallScores = trials.map((trial) => trial.scoring?.overall).filter((score) => score !== void 0);
|
|
761
1013
|
return {
|
|
762
1014
|
...trials[0],
|
|
@@ -764,12 +1016,13 @@ async function executeRun(definition, options) {
|
|
|
764
1016
|
evalId: authoringId(definition),
|
|
765
1017
|
status,
|
|
766
1018
|
trialCount: trials.length,
|
|
767
|
-
passed:
|
|
768
|
-
failed,
|
|
1019
|
+
passed: counts.passed,
|
|
1020
|
+
failed: counts.failed,
|
|
769
1021
|
aggregateScoring: {
|
|
770
|
-
passed:
|
|
771
|
-
failed,
|
|
772
|
-
|
|
1022
|
+
passed: counts.passed,
|
|
1023
|
+
failed: counts.failed,
|
|
1024
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {},
|
|
1025
|
+
passRate: counts.passed / trials.length,
|
|
773
1026
|
...overallScores.length ? { overall: overallScores.reduce((sum, score) => sum + score, 0) / overallScores.length } : {}
|
|
774
1027
|
},
|
|
775
1028
|
trials
|
|
@@ -781,25 +1034,45 @@ async function runTrial(definition, options) {
|
|
|
781
1034
|
const trialId = options.trialId ?? createId("trial");
|
|
782
1035
|
const startedAt = now().toISOString();
|
|
783
1036
|
const selectedRuntime = options.runtime;
|
|
784
|
-
const runtime = selectedRuntime ? definition.agent
|
|
1037
|
+
const runtime = selectedRuntime ? agentRuntimes(definition.agent)[selectedRuntime] : void 0;
|
|
785
1038
|
if (selectedRuntime && !runtime) throw new Error(`AUT does not declare the requested ${selectedRuntime} runtime`);
|
|
786
1039
|
const randomSeed = crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(1))[0];
|
|
787
|
-
const
|
|
1040
|
+
const trialIndex = options.trialIndex ?? 0;
|
|
1041
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
1042
|
+
const root = options.root ?? process.cwd();
|
|
1043
|
+
const baseContext = {
|
|
788
1044
|
runId,
|
|
789
1045
|
evalId: authoringId(definition),
|
|
790
1046
|
trialId,
|
|
791
|
-
trialIndex
|
|
1047
|
+
trialIndex,
|
|
792
1048
|
metadata: {
|
|
793
|
-
...definition.metadata,
|
|
1049
|
+
...toJsonObject(definition.metadata ?? {}, `${definition.id}.metadata`),
|
|
794
1050
|
randomSeed
|
|
795
1051
|
},
|
|
796
1052
|
...options.parameters ? { parameters: options.parameters } : {},
|
|
797
1053
|
...selectedRuntime ? { runtime: selectedRuntime } : {}
|
|
798
1054
|
};
|
|
1055
|
+
let datasetsError;
|
|
1056
|
+
let prepared = [];
|
|
1057
|
+
try {
|
|
1058
|
+
prepared = await prepareDatasets(definition.datasets ?? [], root, {
|
|
1059
|
+
evalId: baseContext.evalId,
|
|
1060
|
+
runId,
|
|
1061
|
+
trialId,
|
|
1062
|
+
trialIndex,
|
|
1063
|
+
seed: randomSeed,
|
|
1064
|
+
parameters: options.parameters ?? {},
|
|
1065
|
+
signal
|
|
1066
|
+
});
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
datasetsError = error;
|
|
1069
|
+
}
|
|
1070
|
+
const candidateData = datasetValues(prepared, "candidate");
|
|
1071
|
+
const scoringData = datasetValues(prepared, "all");
|
|
799
1072
|
const events = [];
|
|
800
1073
|
const runWriter = options.runWriter ?? await options.report.startRun(runMetadata(definition, options, runId, startedAt), { catalog: options.catalog });
|
|
801
1074
|
const trialWriter = await runWriter.startTrial({
|
|
802
|
-
schemaVersion:
|
|
1075
|
+
schemaVersion: 5,
|
|
803
1076
|
runId,
|
|
804
1077
|
runUri: canonicalId("run", runId),
|
|
805
1078
|
trialId,
|
|
@@ -809,15 +1082,30 @@ async function runTrial(definition, options) {
|
|
|
809
1082
|
...definition.agent.identity ? { aut: definition.agent.identity } : {},
|
|
810
1083
|
parameters: options.parameters,
|
|
811
1084
|
matrix: options.matrix,
|
|
812
|
-
startedAt
|
|
1085
|
+
startedAt,
|
|
1086
|
+
...prepared.length ? { datasets: trialDatasets(prepared) } : {}
|
|
813
1087
|
});
|
|
814
1088
|
let writeChain = Promise.resolve();
|
|
815
1089
|
let reportingFailed = false;
|
|
1090
|
+
const trialRef = {
|
|
1091
|
+
runId,
|
|
1092
|
+
trialId,
|
|
1093
|
+
trialIndex,
|
|
1094
|
+
evalId: definition.id,
|
|
1095
|
+
...options.suiteId ? { suiteId: options.suiteId } : {},
|
|
1096
|
+
...options.parameters ? { parameters: options.parameters } : {}
|
|
1097
|
+
};
|
|
1098
|
+
const observe = (event) => {
|
|
1099
|
+
try {
|
|
1100
|
+
options.onEvent?.(trialRef, event);
|
|
1101
|
+
} catch {}
|
|
1102
|
+
};
|
|
816
1103
|
const append = async (event) => {
|
|
817
1104
|
events.push(event);
|
|
818
1105
|
writeChain = writeChain.then(() => trialWriter.appendEvent(event));
|
|
819
1106
|
try {
|
|
820
1107
|
await writeChain;
|
|
1108
|
+
observe(event);
|
|
821
1109
|
} catch (cause) {
|
|
822
1110
|
reportingFailed = true;
|
|
823
1111
|
throw new ReportError({
|
|
@@ -830,10 +1118,11 @@ async function runTrial(definition, options) {
|
|
|
830
1118
|
...event,
|
|
831
1119
|
source: "runner"
|
|
832
1120
|
});
|
|
833
|
-
const
|
|
1121
|
+
const emitAut = (event) => append({
|
|
834
1122
|
...event,
|
|
835
1123
|
source: "aut"
|
|
836
1124
|
});
|
|
1125
|
+
const onAutEvent = (event) => emitAut(normalizeAutEvent(event, now));
|
|
837
1126
|
let workspace;
|
|
838
1127
|
let context;
|
|
839
1128
|
let primaryError;
|
|
@@ -852,15 +1141,20 @@ async function runTrial(definition, options) {
|
|
|
852
1141
|
kind: "trial-started",
|
|
853
1142
|
timestamp: now().toISOString()
|
|
854
1143
|
});
|
|
1144
|
+
if (signal.aborted) throw cancelled();
|
|
1145
|
+
if (datasetsError) throw new DatasetError({
|
|
1146
|
+
cause: datasetsError,
|
|
1147
|
+
message: datasetsError instanceof Error ? datasetsError.message : String(datasetsError)
|
|
1148
|
+
});
|
|
855
1149
|
workspace = await runBoundary(Effect.acquireRelease(Effect.tryPromise({
|
|
856
|
-
try: () => createTrialWorkspace(
|
|
857
|
-
catch: (cause) => new
|
|
1150
|
+
try: () => createTrialWorkspace(trialId, prepared, options.workspaceRoot),
|
|
1151
|
+
catch: (cause) => new DatasetError({
|
|
858
1152
|
cause,
|
|
859
1153
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
860
1154
|
})
|
|
861
1155
|
}), (created) => Effect.tryPromise({
|
|
862
1156
|
try: () => created.cleanup(),
|
|
863
|
-
catch: (cause) => new
|
|
1157
|
+
catch: (cause) => new DatasetError({
|
|
864
1158
|
cause,
|
|
865
1159
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
866
1160
|
})
|
|
@@ -869,7 +1163,8 @@ async function runTrial(definition, options) {
|
|
|
869
1163
|
}).pipe(Effect.tap(() => Effect.logError("trial workspace cleanup failed")))))).pipe(Effect.provideService(Scope.Scope, workspaceScope)));
|
|
870
1164
|
const trialWorkspace = workspace;
|
|
871
1165
|
const trialContext = {
|
|
872
|
-
...
|
|
1166
|
+
...baseContext,
|
|
1167
|
+
data: candidateData,
|
|
873
1168
|
workspace: trialWorkspace.artifacts.candidate,
|
|
874
1169
|
evaluatorWorkspace: trialWorkspace.artifacts.evaluator
|
|
875
1170
|
};
|
|
@@ -892,11 +1187,14 @@ async function runTrial(definition, options) {
|
|
|
892
1187
|
now,
|
|
893
1188
|
randomSeed,
|
|
894
1189
|
emitRunner,
|
|
1190
|
+
emitAut,
|
|
1191
|
+
data: scoringData,
|
|
1192
|
+
signal,
|
|
895
1193
|
progress
|
|
896
1194
|
}),
|
|
897
1195
|
catch: (cause) => cause
|
|
898
1196
|
}), (opened) => Effect.tryPromise({
|
|
899
|
-
try: () => opened.close(),
|
|
1197
|
+
try: async () => opened.close?.(),
|
|
900
1198
|
catch: (cause) => new AutExecutionError({
|
|
901
1199
|
cause,
|
|
902
1200
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -916,6 +1214,7 @@ async function runTrial(definition, options) {
|
|
|
916
1214
|
if (!primaryError) primaryError = closeError;
|
|
917
1215
|
await tryEmitError(closeError, emitRunner, now);
|
|
918
1216
|
}
|
|
1217
|
+
const wasCancelled = primaryError instanceof CancelledError;
|
|
919
1218
|
const { checkpoints, stoppedEarly, lastTurn } = progress;
|
|
920
1219
|
const scoreResults = [];
|
|
921
1220
|
const skippedScorers = [];
|
|
@@ -927,9 +1226,11 @@ async function runTrial(definition, options) {
|
|
|
927
1226
|
lastTurn,
|
|
928
1227
|
primaryError,
|
|
929
1228
|
stoppedEarly,
|
|
1229
|
+
cancelled: wasCancelled,
|
|
930
1230
|
reportingFailed,
|
|
931
1231
|
results: scoreResults,
|
|
932
1232
|
skippedScorers,
|
|
1233
|
+
data: scoringData,
|
|
933
1234
|
now,
|
|
934
1235
|
emitRunner
|
|
935
1236
|
});
|
|
@@ -960,7 +1261,7 @@ async function runTrial(definition, options) {
|
|
|
960
1261
|
} catch (error) {
|
|
961
1262
|
primaryError = error;
|
|
962
1263
|
}
|
|
963
|
-
status = primaryError ? "failed" : "completed";
|
|
1264
|
+
status = wasCancelled ? "cancelled" : primaryError ? "failed" : "completed";
|
|
964
1265
|
const error = primaryError ? recordError(primaryError) : void 0;
|
|
965
1266
|
const endedAt = now().toISOString();
|
|
966
1267
|
await trialWriter.finalize({
|
|
@@ -971,14 +1272,22 @@ async function runTrial(definition, options) {
|
|
|
971
1272
|
...artifacts ? { artifacts } : {},
|
|
972
1273
|
...error ? { error } : {}
|
|
973
1274
|
});
|
|
974
|
-
if (!options.runWriter)
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1275
|
+
if (!options.runWriter) {
|
|
1276
|
+
const counts = trialCounts([{
|
|
1277
|
+
status,
|
|
1278
|
+
scoring
|
|
1279
|
+
}]);
|
|
1280
|
+
await runWriter.finalize({
|
|
1281
|
+
status,
|
|
1282
|
+
endedAt,
|
|
1283
|
+
durationMs: new Date(endedAt).getTime() - new Date(startedAt).getTime(),
|
|
1284
|
+
trialCount: 1,
|
|
1285
|
+
passed: counts.passed,
|
|
1286
|
+
failed: counts.failed,
|
|
1287
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {},
|
|
1288
|
+
...error ? { error } : {}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
982
1291
|
return {
|
|
983
1292
|
runId,
|
|
984
1293
|
runUri: canonicalId("run", runId),
|
|
@@ -993,7 +1302,28 @@ async function runTrial(definition, options) {
|
|
|
993
1302
|
...error ? { error } : {}
|
|
994
1303
|
};
|
|
995
1304
|
}
|
|
1305
|
+
function datasetValues(prepared, audience) {
|
|
1306
|
+
const values = {};
|
|
1307
|
+
for (const { dataset, value } of prepared) {
|
|
1308
|
+
if (value === void 0) continue;
|
|
1309
|
+
if (audience === "candidate" && dataset.visibility !== "candidate") continue;
|
|
1310
|
+
values[dataset.id] = value;
|
|
1311
|
+
}
|
|
1312
|
+
return values;
|
|
1313
|
+
}
|
|
1314
|
+
/** A cancelled trial is neither passed nor failed. */
|
|
1315
|
+
function trialCounts(trials) {
|
|
1316
|
+
let passed = 0;
|
|
1317
|
+
let failed = 0;
|
|
1318
|
+
let cancelledTrials = 0;
|
|
1319
|
+
for (const trial of trials) if (trial.status === "cancelled") cancelledTrials++;
|
|
1320
|
+
else if (trial.status === "completed" && trial.scoring?.passed) passed++;
|
|
1321
|
+
else failed++;
|
|
1322
|
+
return {
|
|
1323
|
+
passed,
|
|
1324
|
+
failed,
|
|
1325
|
+
cancelled: cancelledTrials
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
996
1328
|
//#endregion
|
|
997
|
-
export {
|
|
998
|
-
|
|
999
|
-
//# sourceMappingURL=src-DoPs9Yug.mjs.map
|
|
1329
|
+
export { ReportError as _, readRunSummary as a, readTrialScoring as c, staticDatasetFile as d, staticDatasetFiles as f, DatasetError as g, CheckpointExecutionError as h, readRunManifest as i, readTrialSummary as l, CancelledError as m, runMatrix as n, readTrialEvents as o, AutExecutionError as p, ownerIsGone as r, readTrialManifest as s, runEval as t, localReportStore as u, ScoringError as v };
|