@leostera/evalkit 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -6
- package/README.md +10 -8
- package/dist/cli.mjs +1227 -258
- package/dist/dashboard/assets/index-CTJpd8q8.js +34 -0
- package/dist/dashboard/assets/index-YAabTN3X.css +2 -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 +1346 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -4
- package/dist/runner.d.mts +809 -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-CKqOzeBi.mjs} +448 -116
- package/dist/{src-ButXPY8P.mjs → src-CZPufWN_.mjs} +419 -94
- package/dist/{src-DzZueFn3.mjs → src-CZnDXP1K.mjs} +46 -10
- 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-CZPufWN_.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,15 +542,21 @@ 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();
|
|
548
|
+
const message = step.kind === "user" ? step.message.replaceAll("{{randomSeed}}", String(input.randomSeed)) : void 0;
|
|
350
549
|
await emitRunner({
|
|
351
550
|
kind: "transcript-step-started",
|
|
352
551
|
step: index,
|
|
552
|
+
...message === void 0 ? {} : { message },
|
|
353
553
|
timestamp: now().toISOString()
|
|
354
554
|
});
|
|
355
|
-
if (
|
|
555
|
+
if (message !== void 0) {
|
|
356
556
|
const cursor = input.events.length;
|
|
357
|
-
|
|
557
|
+
turn++;
|
|
558
|
+
const result = await sendUserTurn(session, message, input.signal);
|
|
559
|
+
if (result !== void 0) for (const event of turnResultEvents(result, turn, now)) await input.emitAut(event);
|
|
358
560
|
progress.lastTurn = turnView(input.events, cursor, input.events.length, index);
|
|
359
561
|
} else if (step.kind === "predicate" || step.kind === "judge" || step.kind === "expect-tool-call") {
|
|
360
562
|
const result = await runCheckpoint(definition, step, index, input);
|
|
@@ -370,7 +572,7 @@ async function executeTranscript(definition, session, input) {
|
|
|
370
572
|
step: index,
|
|
371
573
|
timestamp: now().toISOString()
|
|
372
574
|
});
|
|
373
|
-
if (
|
|
575
|
+
if (result.status === "failed" && definition.policy?.failfast) {
|
|
374
576
|
progress.stoppedEarly = true;
|
|
375
577
|
await skipRemainingSteps(definition, index, input);
|
|
376
578
|
break;
|
|
@@ -384,10 +586,10 @@ async function executeTranscript(definition, session, input) {
|
|
|
384
586
|
});
|
|
385
587
|
}
|
|
386
588
|
}
|
|
387
|
-
async function sendUserTurn(session, message) {
|
|
388
|
-
|
|
389
|
-
try: () => session.send(message),
|
|
390
|
-
catch: (cause) => cause instanceof ReportError ? cause : new AutExecutionError({
|
|
589
|
+
async function sendUserTurn(session, message, signal) {
|
|
590
|
+
return runBoundary(Effect.tryPromise({
|
|
591
|
+
try: () => unlessAborted(() => session.send(message), signal),
|
|
592
|
+
catch: (cause) => cause instanceof ReportError || cause instanceof CancelledError ? cause : new AutExecutionError({
|
|
391
593
|
cause,
|
|
392
594
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
393
595
|
})
|
|
@@ -407,7 +609,9 @@ async function runCheckpoint(definition, step, index, input) {
|
|
|
407
609
|
context: input.context,
|
|
408
610
|
artifacts: input.artifacts,
|
|
409
611
|
trajectory: { events: input.events },
|
|
410
|
-
turn: progress.lastTurn
|
|
612
|
+
turn: progress.lastTurn,
|
|
613
|
+
...turnOutput(progress.lastTurn),
|
|
614
|
+
data: input.data
|
|
411
615
|
}, now, definition.judge)).catch(async (error) => {
|
|
412
616
|
const recorded = recordError(error);
|
|
413
617
|
progress.checkpoints.push({
|
|
@@ -445,24 +649,29 @@ async function skipRemainingSteps(definition, after, { now, emitRunner, progress
|
|
|
445
649
|
});
|
|
446
650
|
}
|
|
447
651
|
}
|
|
652
|
+
/** The scorer-facing `output`: the content of the turn's last assistant message. */
|
|
653
|
+
function turnOutput(turn) {
|
|
654
|
+
const last = turn?.assistantMessages.at(-1);
|
|
655
|
+
return last === void 0 ? {} : { output: last.content };
|
|
656
|
+
}
|
|
448
657
|
//#endregion
|
|
449
658
|
//#region ../runner/src/final-scoring.ts
|
|
450
659
|
function scoreSummary(results, checkpoints, skippedScorers) {
|
|
451
|
-
const valid = results.filter((result) => result.value !== void 0);
|
|
660
|
+
const valid = results.filter((result) => result.value !== void 0 && !result.skipped);
|
|
452
661
|
const hasError = results.some((result) => result.error !== void 0);
|
|
453
662
|
return {
|
|
454
663
|
results,
|
|
455
664
|
...checkpoints.length ? { checkpoints } : {},
|
|
456
665
|
...skippedScorers.length ? { skippedScorers } : {},
|
|
457
666
|
...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
|
|
667
|
+
passed: !hasError && results.every((result) => result.skipped || result.passed === true) && checkpoints.every((result) => result.status === "passed" || result.status === "skipped") && !skippedScorers.length
|
|
459
668
|
};
|
|
460
669
|
}
|
|
461
670
|
/** Append completed scores to the supplied arrays, even when a later scorer or report write fails. */
|
|
462
671
|
async function evaluateFinalScorers(definition, input) {
|
|
463
672
|
const { context, workspace, events, lastTurn, results, skippedScorers, now, emitRunner } = input;
|
|
464
673
|
for (const scorer of definition.scoring) {
|
|
465
|
-
if (input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
674
|
+
if (input.cancelled || input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
466
675
|
skippedScorers.push(scorer.name);
|
|
467
676
|
continue;
|
|
468
677
|
}
|
|
@@ -478,7 +687,9 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
478
687
|
context,
|
|
479
688
|
artifacts: workspace.artifacts,
|
|
480
689
|
trajectory: { events },
|
|
481
|
-
...lastTurn ? { turn: lastTurn } : {}
|
|
690
|
+
...lastTurn ? { turn: lastTurn } : {},
|
|
691
|
+
...turnOutput(lastTurn),
|
|
692
|
+
data: input.data
|
|
482
693
|
}, definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
483
694
|
cause,
|
|
484
695
|
message: cause.message
|
|
@@ -510,7 +721,7 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
510
721
|
await emitRunner({
|
|
511
722
|
kind: "scorer-completed",
|
|
512
723
|
scorer: scorer.name,
|
|
513
|
-
value: result.value ?? 0,
|
|
724
|
+
...result.skipped ? { skipped: true } : { value: result.value ?? 0 },
|
|
514
725
|
timestamp: now().toISOString()
|
|
515
726
|
});
|
|
516
727
|
}
|
|
@@ -618,6 +829,20 @@ const readTrialScoring = (root, runId, trialId) => decode(join(root, runId, "tri
|
|
|
618
829
|
async function readTrialEvents(root, runId, trialId) {
|
|
619
830
|
return parseTrajectoryJsonl(await readFile(join(root, runId, "trials", trialId, "trajectory.jsonl"), "utf8"));
|
|
620
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* True when a run's owning process is known to be gone: it ran on this host
|
|
834
|
+
* and its PID no longer exists. Runs from other hosts are never judged.
|
|
835
|
+
*/
|
|
836
|
+
function ownerIsGone(owner) {
|
|
837
|
+
if (!owner || owner.hostname !== os.hostname()) return false;
|
|
838
|
+
if (owner.pid === process.pid) return false;
|
|
839
|
+
try {
|
|
840
|
+
process.kill(owner.pid, 0);
|
|
841
|
+
return false;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
return error.code === "ESRCH";
|
|
844
|
+
}
|
|
845
|
+
}
|
|
621
846
|
//#endregion
|
|
622
847
|
//#region ../runner/src/matrix.ts
|
|
623
848
|
/** Bounded workers pull cells from the iterator; no Cartesian job array is allocated. */
|
|
@@ -630,10 +855,11 @@ function runMatrix(matrix, options) {
|
|
|
630
855
|
const summary = {
|
|
631
856
|
cells: 0,
|
|
632
857
|
passed: 0,
|
|
633
|
-
failed: 0
|
|
858
|
+
failed: 0,
|
|
859
|
+
cancelled: 0
|
|
634
860
|
};
|
|
635
861
|
yield* Effect.all(Array.from({ length: Math.min(count, concurrency) }, () => Effect.gen(function* () {
|
|
636
|
-
while (
|
|
862
|
+
while (!options.signal?.aborted) {
|
|
637
863
|
const next = iterator.next();
|
|
638
864
|
if (next.done) return;
|
|
639
865
|
const cell = next.value;
|
|
@@ -641,9 +867,11 @@ function runMatrix(matrix, options) {
|
|
|
641
867
|
"local",
|
|
642
868
|
"remote",
|
|
643
869
|
"sandbox"
|
|
644
|
-
].find((name) => cell.eval.agent
|
|
870
|
+
].find((name) => agentRuntimes(cell.eval.agent)[name]);
|
|
871
|
+
const suiteId = options.suiteId ?? options.suiteFor?.(cell);
|
|
645
872
|
const result = yield* runEval(cell.eval, {
|
|
646
873
|
...options,
|
|
874
|
+
...suiteId ? { suiteId } : {},
|
|
647
875
|
concurrency: 1,
|
|
648
876
|
runtime,
|
|
649
877
|
parameters: cell.parameters,
|
|
@@ -653,7 +881,8 @@ function runMatrix(matrix, options) {
|
|
|
653
881
|
}
|
|
654
882
|
});
|
|
655
883
|
summary.cells++;
|
|
656
|
-
if (result.status === "
|
|
884
|
+
if (result.status === "cancelled") summary.cancelled++;
|
|
885
|
+
else if (result.status === "completed" && (result.aggregateScoring?.passRate === 1 || result.scoring?.passed)) summary.passed++;
|
|
657
886
|
else summary.failed++;
|
|
658
887
|
if (options.onResult) yield* Effect.tryPromise(async () => options.onResult(cell, result));
|
|
659
888
|
}
|
|
@@ -694,12 +923,16 @@ function assertUuid(value, label) {
|
|
|
694
923
|
}
|
|
695
924
|
function runMetadata(definition, options, runId, startedAt) {
|
|
696
925
|
return {
|
|
697
|
-
schemaVersion:
|
|
926
|
+
schemaVersion: 5,
|
|
698
927
|
runId,
|
|
699
928
|
runUri: canonicalId("run", runId),
|
|
700
929
|
evalId: definition.id,
|
|
701
930
|
...options.suiteId ? { suiteId: options.suiteId } : {},
|
|
702
931
|
...options.source ? { source: options.source } : {},
|
|
932
|
+
process: {
|
|
933
|
+
pid: process.pid,
|
|
934
|
+
hostname: os.hostname()
|
|
935
|
+
},
|
|
703
936
|
...definition.agent.identity ? { aut: definition.agent.identity } : {},
|
|
704
937
|
parameters: options.parameters,
|
|
705
938
|
matrix: options.matrix,
|
|
@@ -731,9 +964,10 @@ async function executeRun(definition, options) {
|
|
|
731
964
|
const aggregateStartedAt = now();
|
|
732
965
|
const runId = options.runId ?? createId("run");
|
|
733
966
|
const runWriter = await options.report.startRun(runMetadata(definition, options, runId, aggregateStartedAt.toISOString()), { catalog: options.catalog });
|
|
967
|
+
const signal = options.signal;
|
|
734
968
|
const trialEffects = Array.from({ length: requestedTrials }, (_, trialIndex) => {
|
|
735
969
|
const trial = Effect.tryPromise({
|
|
736
|
-
try: () => runTrial(definition, {
|
|
970
|
+
try: async () => signal?.aborted ? void 0 : runTrial(definition, {
|
|
737
971
|
...options,
|
|
738
972
|
runId,
|
|
739
973
|
trialId: createId("trial"),
|
|
@@ -745,18 +979,38 @@ async function executeRun(definition, options) {
|
|
|
745
979
|
});
|
|
746
980
|
return options.semaphore ? options.semaphore.withPermits(1)(trial) : trial;
|
|
747
981
|
});
|
|
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";
|
|
982
|
+
const trials = (await Effect.runPromise(Effect.all(trialEffects, { concurrency: options.concurrency ?? 32 }))).filter((trial) => trial !== void 0);
|
|
983
|
+
const counts = trialCounts(trials);
|
|
984
|
+
const status = counts.cancelled || signal?.aborted ? "cancelled" : trials.some((trial) => trial.status === "failed") ? "failed" : "completed";
|
|
751
985
|
const endedAt = now();
|
|
752
986
|
await runWriter.finalize({
|
|
753
987
|
status,
|
|
754
988
|
endedAt: endedAt.toISOString(),
|
|
755
989
|
durationMs: endedAt.getTime() - aggregateStartedAt.getTime(),
|
|
756
990
|
trialCount: trials.length,
|
|
757
|
-
passed:
|
|
758
|
-
failed
|
|
991
|
+
passed: counts.passed,
|
|
992
|
+
failed: counts.failed,
|
|
993
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {}
|
|
759
994
|
});
|
|
995
|
+
if (!trials.length) return {
|
|
996
|
+
runId,
|
|
997
|
+
runUri: canonicalId("run", runId),
|
|
998
|
+
trialId: "",
|
|
999
|
+
trialUri: canonicalId("trial", runId),
|
|
1000
|
+
trialIndex: 0,
|
|
1001
|
+
evalId: authoringId(definition),
|
|
1002
|
+
status,
|
|
1003
|
+
reportLocation: runWriter.location,
|
|
1004
|
+
trialCount: 0,
|
|
1005
|
+
passed: 0,
|
|
1006
|
+
failed: 0,
|
|
1007
|
+
aggregateScoring: {
|
|
1008
|
+
passed: 0,
|
|
1009
|
+
failed: 0,
|
|
1010
|
+
passRate: 0
|
|
1011
|
+
},
|
|
1012
|
+
trials: []
|
|
1013
|
+
};
|
|
760
1014
|
const overallScores = trials.map((trial) => trial.scoring?.overall).filter((score) => score !== void 0);
|
|
761
1015
|
return {
|
|
762
1016
|
...trials[0],
|
|
@@ -764,12 +1018,13 @@ async function executeRun(definition, options) {
|
|
|
764
1018
|
evalId: authoringId(definition),
|
|
765
1019
|
status,
|
|
766
1020
|
trialCount: trials.length,
|
|
767
|
-
passed:
|
|
768
|
-
failed,
|
|
1021
|
+
passed: counts.passed,
|
|
1022
|
+
failed: counts.failed,
|
|
769
1023
|
aggregateScoring: {
|
|
770
|
-
passed:
|
|
771
|
-
failed,
|
|
772
|
-
|
|
1024
|
+
passed: counts.passed,
|
|
1025
|
+
failed: counts.failed,
|
|
1026
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {},
|
|
1027
|
+
passRate: counts.passed / trials.length,
|
|
773
1028
|
...overallScores.length ? { overall: overallScores.reduce((sum, score) => sum + score, 0) / overallScores.length } : {}
|
|
774
1029
|
},
|
|
775
1030
|
trials
|
|
@@ -781,25 +1036,45 @@ async function runTrial(definition, options) {
|
|
|
781
1036
|
const trialId = options.trialId ?? createId("trial");
|
|
782
1037
|
const startedAt = now().toISOString();
|
|
783
1038
|
const selectedRuntime = options.runtime;
|
|
784
|
-
const runtime = selectedRuntime ? definition.agent
|
|
1039
|
+
const runtime = selectedRuntime ? agentRuntimes(definition.agent)[selectedRuntime] : void 0;
|
|
785
1040
|
if (selectedRuntime && !runtime) throw new Error(`AUT does not declare the requested ${selectedRuntime} runtime`);
|
|
786
1041
|
const randomSeed = crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(1))[0];
|
|
787
|
-
const
|
|
1042
|
+
const trialIndex = options.trialIndex ?? 0;
|
|
1043
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
1044
|
+
const root = options.root ?? process.cwd();
|
|
1045
|
+
const baseContext = {
|
|
788
1046
|
runId,
|
|
789
1047
|
evalId: authoringId(definition),
|
|
790
1048
|
trialId,
|
|
791
|
-
trialIndex
|
|
1049
|
+
trialIndex,
|
|
792
1050
|
metadata: {
|
|
793
|
-
...definition.metadata,
|
|
1051
|
+
...toJsonObject(definition.metadata ?? {}, `${definition.id}.metadata`),
|
|
794
1052
|
randomSeed
|
|
795
1053
|
},
|
|
796
1054
|
...options.parameters ? { parameters: options.parameters } : {},
|
|
797
1055
|
...selectedRuntime ? { runtime: selectedRuntime } : {}
|
|
798
1056
|
};
|
|
1057
|
+
let datasetsError;
|
|
1058
|
+
let prepared = [];
|
|
1059
|
+
try {
|
|
1060
|
+
prepared = await prepareDatasets(definition.datasets ?? [], root, {
|
|
1061
|
+
evalId: baseContext.evalId,
|
|
1062
|
+
runId,
|
|
1063
|
+
trialId,
|
|
1064
|
+
trialIndex,
|
|
1065
|
+
seed: randomSeed,
|
|
1066
|
+
parameters: options.parameters ?? {},
|
|
1067
|
+
signal
|
|
1068
|
+
});
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
datasetsError = error;
|
|
1071
|
+
}
|
|
1072
|
+
const candidateData = datasetValues(prepared, "candidate");
|
|
1073
|
+
const scoringData = datasetValues(prepared, "all");
|
|
799
1074
|
const events = [];
|
|
800
1075
|
const runWriter = options.runWriter ?? await options.report.startRun(runMetadata(definition, options, runId, startedAt), { catalog: options.catalog });
|
|
801
1076
|
const trialWriter = await runWriter.startTrial({
|
|
802
|
-
schemaVersion:
|
|
1077
|
+
schemaVersion: 5,
|
|
803
1078
|
runId,
|
|
804
1079
|
runUri: canonicalId("run", runId),
|
|
805
1080
|
trialId,
|
|
@@ -809,15 +1084,30 @@ async function runTrial(definition, options) {
|
|
|
809
1084
|
...definition.agent.identity ? { aut: definition.agent.identity } : {},
|
|
810
1085
|
parameters: options.parameters,
|
|
811
1086
|
matrix: options.matrix,
|
|
812
|
-
startedAt
|
|
1087
|
+
startedAt,
|
|
1088
|
+
...prepared.length ? { datasets: trialDatasets(prepared) } : {}
|
|
813
1089
|
});
|
|
814
1090
|
let writeChain = Promise.resolve();
|
|
815
1091
|
let reportingFailed = false;
|
|
1092
|
+
const trialRef = {
|
|
1093
|
+
runId,
|
|
1094
|
+
trialId,
|
|
1095
|
+
trialIndex,
|
|
1096
|
+
evalId: definition.id,
|
|
1097
|
+
...options.suiteId ? { suiteId: options.suiteId } : {},
|
|
1098
|
+
...options.parameters ? { parameters: options.parameters } : {}
|
|
1099
|
+
};
|
|
1100
|
+
const observe = (event) => {
|
|
1101
|
+
try {
|
|
1102
|
+
options.onEvent?.(trialRef, event);
|
|
1103
|
+
} catch {}
|
|
1104
|
+
};
|
|
816
1105
|
const append = async (event) => {
|
|
817
1106
|
events.push(event);
|
|
818
1107
|
writeChain = writeChain.then(() => trialWriter.appendEvent(event));
|
|
819
1108
|
try {
|
|
820
1109
|
await writeChain;
|
|
1110
|
+
observe(event);
|
|
821
1111
|
} catch (cause) {
|
|
822
1112
|
reportingFailed = true;
|
|
823
1113
|
throw new ReportError({
|
|
@@ -830,10 +1120,11 @@ async function runTrial(definition, options) {
|
|
|
830
1120
|
...event,
|
|
831
1121
|
source: "runner"
|
|
832
1122
|
});
|
|
833
|
-
const
|
|
1123
|
+
const emitAut = (event) => append({
|
|
834
1124
|
...event,
|
|
835
1125
|
source: "aut"
|
|
836
1126
|
});
|
|
1127
|
+
const onAutEvent = (event) => emitAut(normalizeAutEvent(event, now));
|
|
837
1128
|
let workspace;
|
|
838
1129
|
let context;
|
|
839
1130
|
let primaryError;
|
|
@@ -852,15 +1143,20 @@ async function runTrial(definition, options) {
|
|
|
852
1143
|
kind: "trial-started",
|
|
853
1144
|
timestamp: now().toISOString()
|
|
854
1145
|
});
|
|
1146
|
+
if (signal.aborted) throw cancelled();
|
|
1147
|
+
if (datasetsError) throw new DatasetError({
|
|
1148
|
+
cause: datasetsError,
|
|
1149
|
+
message: datasetsError instanceof Error ? datasetsError.message : String(datasetsError)
|
|
1150
|
+
});
|
|
855
1151
|
workspace = await runBoundary(Effect.acquireRelease(Effect.tryPromise({
|
|
856
|
-
try: () => createTrialWorkspace(
|
|
857
|
-
catch: (cause) => new
|
|
1152
|
+
try: () => createTrialWorkspace(trialId, prepared, options.workspaceRoot),
|
|
1153
|
+
catch: (cause) => new DatasetError({
|
|
858
1154
|
cause,
|
|
859
1155
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
860
1156
|
})
|
|
861
1157
|
}), (created) => Effect.tryPromise({
|
|
862
1158
|
try: () => created.cleanup(),
|
|
863
|
-
catch: (cause) => new
|
|
1159
|
+
catch: (cause) => new DatasetError({
|
|
864
1160
|
cause,
|
|
865
1161
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
866
1162
|
})
|
|
@@ -869,7 +1165,8 @@ async function runTrial(definition, options) {
|
|
|
869
1165
|
}).pipe(Effect.tap(() => Effect.logError("trial workspace cleanup failed")))))).pipe(Effect.provideService(Scope.Scope, workspaceScope)));
|
|
870
1166
|
const trialWorkspace = workspace;
|
|
871
1167
|
const trialContext = {
|
|
872
|
-
...
|
|
1168
|
+
...baseContext,
|
|
1169
|
+
data: candidateData,
|
|
873
1170
|
workspace: trialWorkspace.artifacts.candidate,
|
|
874
1171
|
evaluatorWorkspace: trialWorkspace.artifacts.evaluator
|
|
875
1172
|
};
|
|
@@ -892,11 +1189,14 @@ async function runTrial(definition, options) {
|
|
|
892
1189
|
now,
|
|
893
1190
|
randomSeed,
|
|
894
1191
|
emitRunner,
|
|
1192
|
+
emitAut,
|
|
1193
|
+
data: scoringData,
|
|
1194
|
+
signal,
|
|
895
1195
|
progress
|
|
896
1196
|
}),
|
|
897
1197
|
catch: (cause) => cause
|
|
898
1198
|
}), (opened) => Effect.tryPromise({
|
|
899
|
-
try: () => opened.close(),
|
|
1199
|
+
try: async () => opened.close?.(),
|
|
900
1200
|
catch: (cause) => new AutExecutionError({
|
|
901
1201
|
cause,
|
|
902
1202
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -916,6 +1216,7 @@ async function runTrial(definition, options) {
|
|
|
916
1216
|
if (!primaryError) primaryError = closeError;
|
|
917
1217
|
await tryEmitError(closeError, emitRunner, now);
|
|
918
1218
|
}
|
|
1219
|
+
const wasCancelled = primaryError instanceof CancelledError;
|
|
919
1220
|
const { checkpoints, stoppedEarly, lastTurn } = progress;
|
|
920
1221
|
const scoreResults = [];
|
|
921
1222
|
const skippedScorers = [];
|
|
@@ -927,9 +1228,11 @@ async function runTrial(definition, options) {
|
|
|
927
1228
|
lastTurn,
|
|
928
1229
|
primaryError,
|
|
929
1230
|
stoppedEarly,
|
|
1231
|
+
cancelled: wasCancelled,
|
|
930
1232
|
reportingFailed,
|
|
931
1233
|
results: scoreResults,
|
|
932
1234
|
skippedScorers,
|
|
1235
|
+
data: scoringData,
|
|
933
1236
|
now,
|
|
934
1237
|
emitRunner
|
|
935
1238
|
});
|
|
@@ -960,7 +1263,7 @@ async function runTrial(definition, options) {
|
|
|
960
1263
|
} catch (error) {
|
|
961
1264
|
primaryError = error;
|
|
962
1265
|
}
|
|
963
|
-
status = primaryError ? "failed" : "completed";
|
|
1266
|
+
status = wasCancelled ? "cancelled" : primaryError ? "failed" : "completed";
|
|
964
1267
|
const error = primaryError ? recordError(primaryError) : void 0;
|
|
965
1268
|
const endedAt = now().toISOString();
|
|
966
1269
|
await trialWriter.finalize({
|
|
@@ -971,14 +1274,22 @@ async function runTrial(definition, options) {
|
|
|
971
1274
|
...artifacts ? { artifacts } : {},
|
|
972
1275
|
...error ? { error } : {}
|
|
973
1276
|
});
|
|
974
|
-
if (!options.runWriter)
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1277
|
+
if (!options.runWriter) {
|
|
1278
|
+
const counts = trialCounts([{
|
|
1279
|
+
status,
|
|
1280
|
+
scoring
|
|
1281
|
+
}]);
|
|
1282
|
+
await runWriter.finalize({
|
|
1283
|
+
status,
|
|
1284
|
+
endedAt,
|
|
1285
|
+
durationMs: new Date(endedAt).getTime() - new Date(startedAt).getTime(),
|
|
1286
|
+
trialCount: 1,
|
|
1287
|
+
passed: counts.passed,
|
|
1288
|
+
failed: counts.failed,
|
|
1289
|
+
...counts.cancelled ? { cancelled: counts.cancelled } : {},
|
|
1290
|
+
...error ? { error } : {}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
982
1293
|
return {
|
|
983
1294
|
runId,
|
|
984
1295
|
runUri: canonicalId("run", runId),
|
|
@@ -993,7 +1304,28 @@ async function runTrial(definition, options) {
|
|
|
993
1304
|
...error ? { error } : {}
|
|
994
1305
|
};
|
|
995
1306
|
}
|
|
1307
|
+
function datasetValues(prepared, audience) {
|
|
1308
|
+
const values = {};
|
|
1309
|
+
for (const { dataset, value } of prepared) {
|
|
1310
|
+
if (value === void 0) continue;
|
|
1311
|
+
if (audience === "candidate" && dataset.visibility !== "candidate") continue;
|
|
1312
|
+
values[dataset.id] = value;
|
|
1313
|
+
}
|
|
1314
|
+
return values;
|
|
1315
|
+
}
|
|
1316
|
+
/** A cancelled trial is neither passed nor failed. */
|
|
1317
|
+
function trialCounts(trials) {
|
|
1318
|
+
let passed = 0;
|
|
1319
|
+
let failed = 0;
|
|
1320
|
+
let cancelledTrials = 0;
|
|
1321
|
+
for (const trial of trials) if (trial.status === "cancelled") cancelledTrials++;
|
|
1322
|
+
else if (trial.status === "completed" && trial.scoring?.passed) passed++;
|
|
1323
|
+
else failed++;
|
|
1324
|
+
return {
|
|
1325
|
+
passed,
|
|
1326
|
+
failed,
|
|
1327
|
+
cancelled: cancelledTrials
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
996
1330
|
//#endregion
|
|
997
|
-
export {
|
|
998
|
-
|
|
999
|
-
//# sourceMappingURL=src-DoPs9Yug.mjs.map
|
|
1331
|
+
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 };
|