@leostera/evalkit 0.10.0 → 0.11.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 +67 -1
- package/dist/cli.mjs +182 -73
- package/dist/dashboard/assets/index-CFJzcw3o.css +2 -0
- package/dist/dashboard/assets/{index-DNR4shrM.js → index-DgoX2x_M.js} +34 -32
- package/dist/dashboard/index.html +2 -2
- package/dist/{index-db-B1FeXzmS.mjs → index-db-DI7xKMSX.mjs} +237 -47
- package/dist/index.d.mts +837 -220
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +241 -52
- package/dist/runner.d.mts +568 -227
- package/dist/runner.d.mts.map +1 -1
- package/dist/runner.mjs +2 -2
- package/dist/share-worker.d.mts +1 -1
- package/dist/share-worker.d.mts.map +1 -1
- package/dist/share-worker.mjs +1 -1
- package/dist/share.d.mts +9 -6
- package/dist/share.d.mts.map +1 -1
- package/dist/share.mjs +2 -2
- package/dist/skills/evalkit/SKILL.md +2 -2
- package/dist/skills/evalkit-agents/SKILL.md +3 -3
- package/dist/skills/evalkit-datasets/SKILL.md +1 -1
- package/dist/skills/evalkit-evals/SKILL.md +1 -1
- package/dist/skills/evalkit-judges/SKILL.md +1 -1
- package/dist/skills/evalkit-results/SKILL.md +1 -1
- package/dist/{src-Bccv9igQ.mjs → src-CXzbHAKN.mjs} +1 -1
- package/dist/{src-B14fXAGp.mjs → src-D6Ax2t9b.mjs} +379 -206
- package/dist/src-Dc3GXacB.mjs +3637 -0
- package/dist/upgrade-rMsGzUEJ.mjs +230 -0
- package/dist/upgrade-transcript-C8DUykLd.mjs +402 -0
- package/package.json +2 -1
- package/dist/dashboard/assets/index-TPYprg75.css +0 -2
- package/dist/src-BfSQKOJP.mjs +0 -1877
- package/dist/upgrade-G-uFPJKg.mjs +0 -111
package/dist/cli.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
':' //; exec "${EVALKIT_RUNTIME:-$(command -v bun || command -v node)}" "$0" "$@"
|
|
3
|
-
import {
|
|
4
|
-
import { a as readRunEvents, c as
|
|
5
|
-
import { f as encodeBundle, i as normalizeBasePath, l as dashboardIndex, s as repoKey } from "./index-db-
|
|
6
|
-
import { t as createDirectoryShareApp } from "./src-
|
|
3
|
+
import { Et as resolveLimits, Q as canonicalParameters, St as formatLimit, Tt as parseLimits, Z as defineEvalMatrix, _ as recordError, i as agentRuntimes, l as defineSuite, lt as passMark, o as authoringId, v as registerEvals, wt as parseLimitFlag } from "./src-D6Ax2t9b.mjs";
|
|
4
|
+
import { _ as removeRunContainer, a as readRunEvents, c as readRunSubagents, d as localReportStore, f as runWorkspacePath, g as fixtureSet, h as globMatcher, i as ownerIsGone, l as readRunSummary, m as staticDatasetFiles, n as upgradeReports, o as readRunManifest, p as staticDatasetFile, r as runMatrix, t as runEval, u as readRunTranscript } from "./src-Dc3GXacB.mjs";
|
|
5
|
+
import { f as encodeBundle, i as normalizeBasePath, l as dashboardIndex, s as repoKey } from "./index-db-DI7xKMSX.mjs";
|
|
6
|
+
import { t as createDirectoryShareApp } from "./src-CXzbHAKN.mjs";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
7
8
|
import { Effect, LogLevel, Logger } from "effect";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { cp, lstat, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
10
11
|
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
|
-
import {
|
|
12
|
+
import childProcess, { execFile, spawn } from "node:child_process";
|
|
12
13
|
import fs, { existsSync } from "node:fs";
|
|
13
14
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
15
|
import { Hono } from "hono";
|
|
15
|
-
import childProcess, { execFile, spawn } from "node:child_process";
|
|
16
16
|
import { promisify, stripVTControlCharacters } from "node:util";
|
|
17
17
|
import { createServer } from "node:http";
|
|
18
18
|
import { EventEmitter } from "node:events";
|
|
@@ -241,7 +241,7 @@ function createDashboardReportReader(root) {
|
|
|
241
241
|
} catch {
|
|
242
242
|
return false;
|
|
243
243
|
}
|
|
244
|
-
return typeof version === "number" && version <
|
|
244
|
+
return typeof version === "number" && version < 7;
|
|
245
245
|
}
|
|
246
246
|
async function readRun(id) {
|
|
247
247
|
if (await isLegacyRun(id)) {
|
|
@@ -271,6 +271,9 @@ function createDashboardReportReader(root) {
|
|
|
271
271
|
manifest.aut.version
|
|
272
272
|
].filter(Boolean).join(" / ") } : {},
|
|
273
273
|
status: summary ? summary.status : interrupted ? "cancelled" : "running",
|
|
274
|
+
...manifest.limits ? { limits: manifest.limits } : {},
|
|
275
|
+
...manifest.isolation ? { isolation: manifest.isolation } : {},
|
|
276
|
+
...summary?.limit ? { limit: summary.limit } : {},
|
|
274
277
|
...interrupted ? { interrupted: true } : {},
|
|
275
278
|
startedAt: manifest.startedAt,
|
|
276
279
|
...summary?.endedAt ? { completedAt: summary.endedAt } : {},
|
|
@@ -332,9 +335,19 @@ function createDashboardReportReader(root) {
|
|
|
332
335
|
}
|
|
333
336
|
const candidateFile = (runId, path) => artifactFile(runId, "candidate", path);
|
|
334
337
|
const attachmentFile = (runId, name) => artifactFile(runId, "attachments", name);
|
|
335
|
-
|
|
338
|
+
/** The agent's transcript and EvalKit's events, as far as a running run has got. */
|
|
339
|
+
async function transcript(runId) {
|
|
336
340
|
runPath(root, runId);
|
|
337
|
-
|
|
341
|
+
const [recorded, subagents, runEvents] = await Promise.all([
|
|
342
|
+
ifPresent(() => readRunTranscript(root, runId)),
|
|
343
|
+
readRunSubagents(root, runId),
|
|
344
|
+
ifPresent(() => readRunEvents(root, runId))
|
|
345
|
+
]);
|
|
346
|
+
return {
|
|
347
|
+
transcript: recorded ?? null,
|
|
348
|
+
subagents,
|
|
349
|
+
runEvents: runEvents ?? []
|
|
350
|
+
};
|
|
338
351
|
}
|
|
339
352
|
return {
|
|
340
353
|
listRuns,
|
|
@@ -345,7 +358,7 @@ function createDashboardReportReader(root) {
|
|
|
345
358
|
listCandidateWorkspace,
|
|
346
359
|
candidateFile,
|
|
347
360
|
attachmentFile,
|
|
348
|
-
|
|
361
|
+
transcript
|
|
349
362
|
};
|
|
350
363
|
}
|
|
351
364
|
/** Both artifact endpoints walk a specific root; never broaden the candidate-file read root. */
|
|
@@ -522,7 +535,7 @@ function directorySink(root) {
|
|
|
522
535
|
}
|
|
523
536
|
/**
|
|
524
537
|
* One run's snapshot, shared by `evalkit build` (files on disk) and
|
|
525
|
-
* `evalkit share` (an upload bundle): `detail.json`, `
|
|
538
|
+
* `evalkit share` (an upload bundle): `detail.json`, `transcript.json`,
|
|
526
539
|
* `artifacts.json` and `workspace.json`, the candidate files under
|
|
527
540
|
* `workspace/`, and attachments under `attachments/` (both left out with
|
|
528
541
|
* `workspaceFiles: false`; they stay listed). Evaluator workspaces are never
|
|
@@ -531,7 +544,7 @@ function directorySink(root) {
|
|
|
531
544
|
async function snapshotRun(reports, runId, sink, options = {}) {
|
|
532
545
|
const detail = await reports.runDetail(runId);
|
|
533
546
|
await sink.json("detail.json", detail);
|
|
534
|
-
await sink.json("
|
|
547
|
+
await sink.json("transcript.json", await reports.transcript(runId));
|
|
535
548
|
await sink.json("artifacts.json", { artifacts: await reports.listArtifacts(runId) });
|
|
536
549
|
const workspace = await reports.listCandidateWorkspace(runId);
|
|
537
550
|
await sink.json("workspace.json", workspace);
|
|
@@ -979,8 +992,8 @@ function bytes(size) {
|
|
|
979
992
|
if (size < 1024 ** 2) return `${(size / 1024).toFixed(1)} kB`;
|
|
980
993
|
return `${(size / 1024 ** 2).toFixed(1)} MB`;
|
|
981
994
|
}
|
|
982
|
-
function plural(count, noun) {
|
|
983
|
-
return `${count} ${
|
|
995
|
+
function plural(count, noun, nouns = `${noun}s`) {
|
|
996
|
+
return `${count} ${count === 1 ? noun : nouns}`;
|
|
984
997
|
}
|
|
985
998
|
/** Times one build step, showing a live line while it runs. */
|
|
986
999
|
async function step(label, work, describe) {
|
|
@@ -1078,6 +1091,7 @@ async function confirm(question) {
|
|
|
1078
1091
|
//#region ../cli/src/run-reporter.ts
|
|
1079
1092
|
function runOutcome(result) {
|
|
1080
1093
|
if (result.status === "cancelled") return "cancelled";
|
|
1094
|
+
if (result.status === "limited") return "limited";
|
|
1081
1095
|
return result.status === "passed" ? "passed" : "failed";
|
|
1082
1096
|
}
|
|
1083
1097
|
function value(score) {
|
|
@@ -1148,17 +1162,26 @@ function parameterLabel(parameters) {
|
|
|
1148
1162
|
}
|
|
1149
1163
|
/** One printed line for a finished run; failing reasons on the same line. */
|
|
1150
1164
|
function runLine(scenario) {
|
|
1151
|
-
const mark = scenario.outcome === "passed" ? green("✓") : scenario.outcome === "cancelled" ? yellow("◌") : red("✗");
|
|
1165
|
+
const mark = scenario.outcome === "passed" ? green("✓") : scenario.outcome === "cancelled" ? yellow("◌") : scenario.outcome === "limited" ? yellow("✗") : red("✗");
|
|
1152
1166
|
const parameters = parameterLabel(scenario.parameters);
|
|
1153
1167
|
const batch = scenario.result.batch;
|
|
1154
1168
|
const position = batch ? dim(` #${batch.index + 1}/${batch.size}`) : "";
|
|
1155
1169
|
const details = [
|
|
1156
|
-
...scenario.outcome === "
|
|
1170
|
+
...scenario.outcome === "limited" ? [limitReason(scenario.result)] : [],
|
|
1171
|
+
...scenario.outcome === "failed" || scenario.outcome === "limited" ? scenario.reasons : [],
|
|
1157
1172
|
...scenario.outcome === "cancelled" ? ["cancelled"] : [],
|
|
1158
1173
|
...scenario.skipped.map((name) => `${name} skipped`)
|
|
1159
1174
|
];
|
|
1160
1175
|
return ` ${mark} ${bold(scenario.label)}${parameters ? ` ${dim(parameters)}` : ""}${position}${details.length ? ` ${dim(details.join(" · "))}` : ""}`;
|
|
1161
1176
|
}
|
|
1177
|
+
/** `limited: 41 of 40 steps · score 0.40 (graded what it left)`. */
|
|
1178
|
+
function limitReason(result) {
|
|
1179
|
+
const limit = result.limit;
|
|
1180
|
+
if (!limit) return "limited";
|
|
1181
|
+
const used = limit.limit === "time" ? `${formatLimit("time", Math.round(limit.used / 1e3) * 1e3)} of ${formatLimit("time", limit.value)}` : `${limit.used.toLocaleString("en-US")} of ${formatLimit(limit.limit, limit.value)}`;
|
|
1182
|
+
const overall = result.scoring?.overall;
|
|
1183
|
+
return `limited: ${used}${overall === void 0 || !result.scoring?.results.length ? "" : ` · score ${overall.toFixed(2)} (graded what it left)`}`;
|
|
1184
|
+
}
|
|
1162
1185
|
/**
|
|
1163
1186
|
* Prints run progress. On a terminal, failures print as they happen and a live
|
|
1164
1187
|
* line shows counts and what is running. Elsewhere (CI), every finished
|
|
@@ -1203,8 +1226,15 @@ function createRunReporter(options) {
|
|
|
1203
1226
|
timer.unref?.();
|
|
1204
1227
|
}
|
|
1205
1228
|
},
|
|
1206
|
-
|
|
1207
|
-
if (
|
|
1229
|
+
onUpdate(run, update) {
|
|
1230
|
+
if (update.kind === "record") {
|
|
1231
|
+
const activity = running.get(run.runId);
|
|
1232
|
+
const call = update.record.type === "message" && update.record.role === "assistant" ? update.record.content.filter((block) => block.type === "tool_call").at(-1) : void 0;
|
|
1233
|
+
if (activity && call?.type === "tool_call") activity.doing = `tool ${call.name}`;
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
const event = update.event;
|
|
1237
|
+
if (event.kind === "run-started") {
|
|
1208
1238
|
running.set(run.runId, {
|
|
1209
1239
|
label: options.labelFor(run),
|
|
1210
1240
|
startedAt: now()
|
|
@@ -1213,10 +1243,10 @@ function createRunReporter(options) {
|
|
|
1213
1243
|
}
|
|
1214
1244
|
const activity = running.get(run.runId);
|
|
1215
1245
|
if (!activity) return;
|
|
1216
|
-
if (event.
|
|
1217
|
-
else if (event.
|
|
1218
|
-
else if (event.
|
|
1219
|
-
else if (event.
|
|
1246
|
+
if (event.kind === "run-completed" || event.kind === "error") running.delete(run.runId);
|
|
1247
|
+
else if (event.kind === "limit-reached") activity.doing = `stopping at its ${event.limit} limit`;
|
|
1248
|
+
else if (event.kind === "scorer-started") activity.doing = `scoring ${event.scorer}`;
|
|
1249
|
+
else if (event.kind === "checkpoint-started") activity.doing = `check ${event.name}`;
|
|
1220
1250
|
},
|
|
1221
1251
|
onRun(scenario) {
|
|
1222
1252
|
scenarios.push(scenario);
|
|
@@ -1233,6 +1263,7 @@ function createRunReporter(options) {
|
|
|
1233
1263
|
return {
|
|
1234
1264
|
passed: scenarios.filter((s) => s.outcome === "passed").length,
|
|
1235
1265
|
failed: scenarios.filter((s) => s.outcome === "failed").length,
|
|
1266
|
+
limited: scenarios.filter((s) => s.outcome === "limited").length,
|
|
1236
1267
|
cancelled: scenarios.filter((s) => s.outcome === "cancelled").length,
|
|
1237
1268
|
runs: scenarios.length,
|
|
1238
1269
|
scenarios: new Set(scenarios.map((s) => `${s.label} ${parameterLabel(s.parameters)}`)).size,
|
|
@@ -1246,6 +1277,7 @@ function totalsLine(totals) {
|
|
|
1246
1277
|
const parts = [
|
|
1247
1278
|
green(`${totals.passed} passed`),
|
|
1248
1279
|
totals.failed ? red(`${totals.failed} failed`) : "",
|
|
1280
|
+
totals.limited ? yellow(`${totals.limited} limited`) : "",
|
|
1249
1281
|
totals.cancelled ? yellow(`${totals.cancelled} cancelled`) : ""
|
|
1250
1282
|
].filter(Boolean);
|
|
1251
1283
|
const coverage = totals.runs === totals.scenarios ? plural(totals.runs, "run") : `${plural(totals.runs, "run")} of ${plural(totals.scenarios, "scenario")}`;
|
|
@@ -1269,13 +1301,17 @@ function junitReport(scenarios) {
|
|
|
1269
1301
|
bySuite.set(suite, [...bySuite.get(suite) ?? [], scenario]);
|
|
1270
1302
|
}
|
|
1271
1303
|
const suites = [...bySuite].map(([suite, cases]) => {
|
|
1272
|
-
const failures = cases.filter((c) => c.outcome === "failed").length;
|
|
1304
|
+
const failures = cases.filter((c) => c.outcome === "failed" || c.outcome === "limited").length;
|
|
1273
1305
|
const skipped = cases.filter((c) => c.outcome === "cancelled" || c.outcome === "passed" && c.skipped.length > 0 && allSkipped(c)).length;
|
|
1274
1306
|
const time = cases.reduce((sum, c) => sum + (c.durationMs ?? 0), 0);
|
|
1275
1307
|
const testcases = cases.map((scenario) => {
|
|
1276
1308
|
const parameters = parameterLabel(scenario.parameters);
|
|
1277
1309
|
const batch = scenario.result.batch;
|
|
1278
1310
|
const attributes = `name="${escape((parameters ? `${scenario.evalId} [${parameters}]` : scenario.evalId) + (batch ? ` #${batch.index + 1}` : ""))}" classname="${escape(suite)}" time="${seconds(scenario.durationMs)}"`;
|
|
1311
|
+
if (scenario.outcome === "limited") {
|
|
1312
|
+
const reasons = [limitReason(scenario.result), ...scenario.reasons];
|
|
1313
|
+
return ` <testcase ${attributes}>\n <failure message="${escape(reasons.join("; "))}" type="limited">${escape(reasons.join("\n"))}</failure>\n </testcase>`;
|
|
1314
|
+
}
|
|
1279
1315
|
if (scenario.outcome === "failed") return ` <testcase ${attributes}>\n <failure message="${escape(scenario.reasons.join("; ") || "failed")}">${escape(scenario.reasons.join("\n"))}</failure>\n </testcase>`;
|
|
1280
1316
|
if (scenario.outcome === "cancelled") return ` <testcase ${attributes}>\n <skipped message="cancelled"/>\n </testcase>`;
|
|
1281
1317
|
if (allSkipped(scenario)) return ` <testcase ${attributes}>\n <skipped message="${escape(`all scorers skipped: ${scenario.skipped.join(", ")}`)}"/>\n </testcase>`;
|
|
@@ -1283,7 +1319,7 @@ function junitReport(scenarios) {
|
|
|
1283
1319
|
});
|
|
1284
1320
|
return ` <testsuite name="${escape(suite)}" tests="${cases.length}" failures="${failures}" errors="0" skipped="${skipped}" time="${seconds(time)}">\n${testcases.join("\n")}\n </testsuite>`;
|
|
1285
1321
|
});
|
|
1286
|
-
return `<?xml version="1.0" encoding="UTF-8"?>\n<testsuites name="evalkit" tests="${scenarios.length}" failures="${scenarios.filter((c) => c.outcome === "failed").length}">\n${suites.join("\n")}\n</testsuites>\n`;
|
|
1322
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<testsuites name="evalkit" tests="${scenarios.length}" failures="${scenarios.filter((c) => c.outcome === "failed" || c.outcome === "limited").length}">\n${suites.join("\n")}\n</testsuites>\n`;
|
|
1287
1323
|
}
|
|
1288
1324
|
/** True when every score was skip()ped. */
|
|
1289
1325
|
function allSkipped(scenario) {
|
|
@@ -1291,9 +1327,71 @@ function allSkipped(scenario) {
|
|
|
1291
1327
|
return results.length > 0 && results.every((score) => score.skipped);
|
|
1292
1328
|
}
|
|
1293
1329
|
//#endregion
|
|
1330
|
+
//#region ../cli/src/run-options.ts
|
|
1331
|
+
/**
|
|
1332
|
+
* What every run of the project shares, from its config, wherever it is
|
|
1333
|
+
* launched: the CLI's `run` and the dashboard's buttons.
|
|
1334
|
+
*/
|
|
1335
|
+
function projectRunOptions(root, config) {
|
|
1336
|
+
return {
|
|
1337
|
+
root,
|
|
1338
|
+
...config?.isolation ? { isolation: config.isolation } : {},
|
|
1339
|
+
limits: { base: parseLimits(config?.limits, "The config") },
|
|
1340
|
+
...config?.keepSandboxes ? { keepWorkspaces: config.keepSandboxes } : {},
|
|
1341
|
+
...config?.artifacts?.exclude ? { artifactExclude: config.artifacts.exclude } : {},
|
|
1342
|
+
...config?.artifacts?.attachments ? { attachmentLimits: config.artifacts.attachments } : {},
|
|
1343
|
+
...config?.judge ? { judge: config.judge } : {}
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* The project's fixtures for one invocation, or undefined when it has
|
|
1348
|
+
* none. The caller stops them when its runs are done.
|
|
1349
|
+
*/
|
|
1350
|
+
function projectFixtures(root, config) {
|
|
1351
|
+
const fixtures = config?.fixtures;
|
|
1352
|
+
if (!fixtures || !Object.keys(fixtures).length) return void 0;
|
|
1353
|
+
return fixtureSet(fixtures, { root });
|
|
1354
|
+
}
|
|
1355
|
+
/** Runs `work` with the project's fixtures, stopping them afterwards. */
|
|
1356
|
+
async function withFixtures(root, config, work) {
|
|
1357
|
+
const fixtures = projectFixtures(root, config);
|
|
1358
|
+
try {
|
|
1359
|
+
return await work(fixtures);
|
|
1360
|
+
} finally {
|
|
1361
|
+
await fixtures?.stop();
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region ../cli/src/package-manager.ts
|
|
1366
|
+
const LOCKFILES = [
|
|
1367
|
+
["bun.lock", "bun"],
|
|
1368
|
+
["bun.lockb", "bun"],
|
|
1369
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
1370
|
+
["yarn.lock", "yarn"],
|
|
1371
|
+
["package-lock.json", "npm"]
|
|
1372
|
+
];
|
|
1373
|
+
/**
|
|
1374
|
+
* The package manager a project uses: the one that started this process
|
|
1375
|
+
* (`npm_config_user_agent`), else the nearest lockfile at or above `root`
|
|
1376
|
+
* (a workspace keeps it at its root), else bun. Suggesting another manager's
|
|
1377
|
+
* command can make someone run its install and rewrite `node_modules`.
|
|
1378
|
+
*/
|
|
1379
|
+
function detectPackageManager(root, userAgent = process.env.npm_config_user_agent) {
|
|
1380
|
+
const started = userAgent?.split("/")[0];
|
|
1381
|
+
if (started === "bun" || started === "pnpm" || started === "yarn" || started === "npm") return started;
|
|
1382
|
+
for (let dir = root;; dir = dirname(dir)) {
|
|
1383
|
+
for (const [file, manager] of LOCKFILES) if (existsSync(join(dir, file))) return manager;
|
|
1384
|
+
if (dirname(dir) === dir) return "bun";
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
/** `pnpm run evalkit dev` (or `npm run evalkit -- dev`: npm needs the `--` for flags). */
|
|
1388
|
+
function evalkitCommand(manager, args) {
|
|
1389
|
+
return manager === "npm" ? `npm run evalkit -- ${args}` : `${manager} run evalkit ${args}`;
|
|
1390
|
+
}
|
|
1391
|
+
//#endregion
|
|
1294
1392
|
//#region ../cli/src/version.ts
|
|
1295
1393
|
/** The CLI version; kept in sync with package.json when releasing. */
|
|
1296
|
-
const VERSION = "0.
|
|
1394
|
+
const VERSION = "0.11.0";
|
|
1297
1395
|
//#endregion
|
|
1298
1396
|
//#region ../cli/src/run-command.ts
|
|
1299
1397
|
/** Validates `run` flags into a selection, parameter overrides and limits. */
|
|
@@ -1306,14 +1404,7 @@ function normalizeRunOptions(values, positionals) {
|
|
|
1306
1404
|
if (!Number.isSafeInteger(number) || number < 1) throw new Error(`--${name} must be a positive integer`);
|
|
1307
1405
|
return number;
|
|
1308
1406
|
}
|
|
1309
|
-
|
|
1310
|
-
["max-tokens", "maxTokens"],
|
|
1311
|
-
["chat-timeout-ms", "chatTimeoutMs"],
|
|
1312
|
-
["turn-budget", "turnBudget"]
|
|
1313
|
-
]) {
|
|
1314
|
-
const value = integer(flag);
|
|
1315
|
-
if (value !== void 0) parameters[key] = value;
|
|
1316
|
-
}
|
|
1407
|
+
const limits = resolveLimits(...(values.limit ?? []).flatMap((value) => value.split(",")).map(parseLimitFlag));
|
|
1317
1408
|
const selection = {};
|
|
1318
1409
|
for (const key of ["model", "mode"]) if (values[key]?.length) selection[key] = values[key].flatMap((value) => value.split(","));
|
|
1319
1410
|
for (const value of values.select ?? []) {
|
|
@@ -1339,6 +1430,7 @@ function normalizeRunOptions(values, positionals) {
|
|
|
1339
1430
|
positionals,
|
|
1340
1431
|
parameters,
|
|
1341
1432
|
selection,
|
|
1433
|
+
limits,
|
|
1342
1434
|
concurrency: integer("concurrency"),
|
|
1343
1435
|
repeat: integer("repeat")
|
|
1344
1436
|
};
|
|
@@ -1411,6 +1503,7 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1411
1503
|
const cells = matrix.count(selection);
|
|
1412
1504
|
if (!cells) throw new Error("Selection contains no eligible cells; check cases and exclusions");
|
|
1413
1505
|
const repeat = parsed.repeat ?? project.config.execution?.repeat;
|
|
1506
|
+
const configLimits = parseLimits(project.config.limits, "The config");
|
|
1414
1507
|
let runs = 0;
|
|
1415
1508
|
for (const cell of matrix.cells(selection)) runs += repeat ?? cell.eval.repeat ?? 1;
|
|
1416
1509
|
const plan = {
|
|
@@ -1421,6 +1514,12 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1421
1514
|
parameters: selection.parameters,
|
|
1422
1515
|
overrides: selection.overrides,
|
|
1423
1516
|
repeat: repeat ?? "scenario",
|
|
1517
|
+
limits: {
|
|
1518
|
+
config: configLimits,
|
|
1519
|
+
cli: parsed.limits
|
|
1520
|
+
},
|
|
1521
|
+
isolation: project.config.isolation ?? { kind: "none" },
|
|
1522
|
+
fixtures: Object.keys(project.config.fixtures ?? {}),
|
|
1424
1523
|
scoring: Object.fromEntries(registry.catalog({ root: project.root }).filter((entry) => matrix.evals.some((definition) => definition.id === entry.id)).map((entry) => [entry.id, plannedChecks(entry)]))
|
|
1425
1524
|
};
|
|
1426
1525
|
if (values["dry-run"]) {
|
|
@@ -1462,7 +1561,9 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1462
1561
|
reporter.start();
|
|
1463
1562
|
let summary;
|
|
1464
1563
|
try {
|
|
1465
|
-
summary = await Effect.runPromise(runnerLogs(runMatrix(matrix, {
|
|
1564
|
+
summary = await withFixtures(project.root, project.config, (fixtures) => Effect.runPromise(runnerLogs(runMatrix(matrix, {
|
|
1565
|
+
...projectRunOptions(project.root, project.config),
|
|
1566
|
+
...fixtures ? { fixtures } : {},
|
|
1466
1567
|
selection,
|
|
1467
1568
|
concurrency: parsed.concurrency ?? project.config.execution?.concurrency ?? 4,
|
|
1468
1569
|
...repeat ? { repeat } : {},
|
|
@@ -1473,12 +1574,12 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1473
1574
|
catalog: runCatalog(registry, project.root),
|
|
1474
1575
|
report: localReportStore(reportDir),
|
|
1475
1576
|
workspaceRoot: sandboxDir,
|
|
1476
|
-
keepWorkspaces: project.config.keepSandboxes,
|
|
1477
|
-
artifactExclude: project.config.artifacts?.exclude,
|
|
1478
|
-
attachmentLimits: project.config.artifacts?.attachments,
|
|
1479
|
-
judge: project.config.judge,
|
|
1480
1577
|
signal: controller.signal,
|
|
1481
|
-
|
|
1578
|
+
limits: {
|
|
1579
|
+
base: configLimits,
|
|
1580
|
+
override: parsed.limits
|
|
1581
|
+
},
|
|
1582
|
+
onUpdate: (run, update) => reporter.onUpdate(run, update),
|
|
1482
1583
|
onResult: (cell, results) => {
|
|
1483
1584
|
for (const result of results) {
|
|
1484
1585
|
const report = runReport(cell, result, suiteFor(authoringId(cell.eval)));
|
|
@@ -1494,7 +1595,7 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1494
1595
|
reporter.onRun(report);
|
|
1495
1596
|
}
|
|
1496
1597
|
}
|
|
1497
|
-
})));
|
|
1598
|
+
}))));
|
|
1498
1599
|
} finally {
|
|
1499
1600
|
process.off("SIGINT", onInterrupt);
|
|
1500
1601
|
}
|
|
@@ -1508,12 +1609,14 @@ async function runProjectCommand(cwd, parsed) {
|
|
|
1508
1609
|
else {
|
|
1509
1610
|
console.log(`\n${totalsLine(totals)}`);
|
|
1510
1611
|
if (values.junit) console.log(` ${dim(`JUnit report: ${values.junit}`)}`);
|
|
1511
|
-
|
|
1512
|
-
|
|
1612
|
+
const manager = detectPackageManager(project.root);
|
|
1613
|
+
const hint = (args, why) => console.log(` ${cyan("➜")} ${evalkitCommand(manager, args).padEnd(24)} ${dim(why)}`);
|
|
1614
|
+
hint("dev", "to inspect");
|
|
1615
|
+
if (project.config.share) hint("share", "to publish");
|
|
1513
1616
|
console.log("");
|
|
1514
1617
|
}
|
|
1515
1618
|
if (controller.signal.aborted) process.exitCode = 130;
|
|
1516
|
-
else if (summary.failed || broken.length) process.exitCode = 1;
|
|
1619
|
+
else if (summary.failed || summary.limited || broken.length) process.exitCode = 1;
|
|
1517
1620
|
}
|
|
1518
1621
|
/** A scenario's checkpoints and final scorers, as `--dry-run` lists them. */
|
|
1519
1622
|
function plannedChecks(entry) {
|
|
@@ -1582,8 +1685,8 @@ async function tidyReports(options) {
|
|
|
1582
1685
|
return result;
|
|
1583
1686
|
}
|
|
1584
1687
|
/**
|
|
1585
|
-
* Remove the run workspaces `keep` would not have kept
|
|
1586
|
-
* by default. Unfinished runs are never touched, so a run in progress keeps
|
|
1688
|
+
* Remove the run workspaces `keep` would not have kept, and their kept
|
|
1689
|
+
* containers: those of passed runs by default. Unfinished runs are never touched, so a run in progress keeps
|
|
1587
1690
|
* its workspace; workspaces without a report are left alone.
|
|
1588
1691
|
*/
|
|
1589
1692
|
async function tidySandboxes(options) {
|
|
@@ -1600,6 +1703,7 @@ async function tidySandboxes(options) {
|
|
|
1600
1703
|
recursive: true,
|
|
1601
1704
|
force: true
|
|
1602
1705
|
});
|
|
1706
|
+
if (run.isolation?.kind === "container") await removeRunContainer(run.id, options.runtime);
|
|
1603
1707
|
removed += 1;
|
|
1604
1708
|
}
|
|
1605
1709
|
return { removed };
|
|
@@ -1678,7 +1782,7 @@ async function runBundle(options) {
|
|
|
1678
1782
|
files.push({
|
|
1679
1783
|
path: "run.json",
|
|
1680
1784
|
data: encode({
|
|
1681
|
-
snapshotVersion:
|
|
1785
|
+
snapshotVersion: 3,
|
|
1682
1786
|
...options.run
|
|
1683
1787
|
})
|
|
1684
1788
|
});
|
|
@@ -1873,7 +1977,7 @@ export const greetingAgent = defineAgent({
|
|
|
1873
1977
|
identity: { id: 'greeting-agent', kind: 'in-process' },
|
|
1874
1978
|
async start({ context }) {
|
|
1875
1979
|
return {
|
|
1876
|
-
// Return the reply; EvalKit records it.
|
|
1980
|
+
// Return the reply; EvalKit records it. Use the transcript writer in start() to record tool calls and usage.
|
|
1877
1981
|
async send(message: string) {
|
|
1878
1982
|
const file = join(context.workspace.root, 'greeting', 'greeting.txt');
|
|
1879
1983
|
const prefix = (await readFile(file, 'utf8')).trim();
|
|
@@ -1940,7 +2044,7 @@ scores, evidence, and candidate files (never evaluator workspaces): review it be
|
|
|
1940
2044
|
}
|
|
1941
2045
|
//#endregion
|
|
1942
2046
|
//#region ../cli/src/new-project.ts
|
|
1943
|
-
const npmDependency = "^0.
|
|
2047
|
+
const npmDependency = "^0.11.0";
|
|
1944
2048
|
const ignored = [
|
|
1945
2049
|
"node_modules/",
|
|
1946
2050
|
"_evalkit-results/",
|
|
@@ -2055,9 +2159,11 @@ async function writeStarterReadme(root, name, inPlace) {
|
|
|
2055
2159
|
}
|
|
2056
2160
|
//#endregion
|
|
2057
2161
|
//#region ../cli/src/compare.ts
|
|
2162
|
+
/** Runs that ended on their own; a limited run is a finished failure. */
|
|
2058
2163
|
const FINISHED = /* @__PURE__ */ new Set([
|
|
2059
2164
|
"passed",
|
|
2060
2165
|
"failed",
|
|
2166
|
+
"limited",
|
|
2061
2167
|
"errored"
|
|
2062
2168
|
]);
|
|
2063
2169
|
function unitKey(run) {
|
|
@@ -5364,12 +5470,13 @@ function createProgram(actions) {
|
|
|
5364
5470
|
showGlobalOptions: false
|
|
5365
5471
|
}).addHelpText("after", `\nRun from your eval project's directory. Docs: https://evalkit.leostera.dev/docs/\n`);
|
|
5366
5472
|
program.command("new").description("create an eval project (never overwrites files)").argument("<directory>", "where to create it; use . for the current directory").addHelpText("after", examples(["bunx @leostera/evalkit new ./agent-evals", "evalkit new ."])).action((directory) => actions.create(directory));
|
|
5367
|
-
program.command("run").description("run scenarios and save the results in _evalkit-results/").argument("[eval-ids...]", "scenarios to run (default: all)").addOption(configOption()).option("-e, --eval <id>", "run this scenario; repeatable, or comma-separated", collect).option("-s, --suite <id>", "run the scenarios in this suite; with -m, only those in both").option("-m, --matrix <id>", "run this matrix").option("--model <value>", "--select model=<value> for each comma-separated value", collect).option("--mode <value>", "--select mode=<value> for each comma-separated value", collect).option("-a, --select <axis=value>", "keep only these values of a matrix axis (a for axis); repeatable, comma-separated", collect).option("--param <name=json>", "set a parameter for every scenario; repeatable", collect).option("-n, --repeat <n>", "runs of each scenario, as one batch; overrides config and scenario").option("-j, --concurrency <n>", "matrix cells to run at once (default: 4)").option("--
|
|
5473
|
+
program.command("run").description("run scenarios and save the results in _evalkit-results/").argument("[eval-ids...]", "scenarios to run (default: all)").addOption(configOption()).option("-e, --eval <id>", "run this scenario; repeatable, or comma-separated", collect).option("-s, --suite <id>", "run the scenarios in this suite; with -m, only those in both").option("-m, --matrix <id>", "run this matrix").option("--model <value>", "--select model=<value> for each comma-separated value", collect).option("--mode <value>", "--select mode=<value> for each comma-separated value", collect).option("-a, --select <axis=value>", "keep only these values of a matrix axis (a for axis); repeatable, comma-separated; one value per axis runs one cell", collect).option("--param <name=json>", "set a parameter for every scenario; repeatable", collect).option("-n, --repeat <n>", "runs of each scenario, as one batch; overrides config and scenario").option("-j, --concurrency <n>", "matrix cells to run at once (default: 4)").option("--limit <kind=value>", "stop each run past this limit: steps=80, tokens=2000000, time=20m; repeatable; overrides config, scenario and matrix", collect).option("--local", "require the agent's local runtime").option("--all", "allow more matrix cells than execution.maxCells").option("--dry-run", "print the plan as JSON; run nothing").option("--json", "print one JSON result per matrix cell").option("--junit <file>", "write a JUnit XML report").option("--skip-broken", "run healthy scenarios when some files fail to load, then exit 1").option("-v, --verbose", "print every scenario, not only failures").addOption(logLevelOption()).addHelpText("after", examples([
|
|
5368
5474
|
"evalkit run",
|
|
5369
5475
|
"evalkit run greeting -n 5",
|
|
5370
5476
|
"evalkit run -s billing --junit _evalkit-results/junit.xml",
|
|
5371
5477
|
"evalkit run -m models --model model-a,model-b --dry-run",
|
|
5372
|
-
"evalkit run -m models -s testing -a model=luna -a toolchain=riot"
|
|
5478
|
+
"evalkit run -m models -s testing -a model=luna -a toolchain=riot",
|
|
5479
|
+
"evalkit run todo-app -a model=luna -a skills=none -a docs=search"
|
|
5373
5480
|
])).action((evalIds, options) => {
|
|
5374
5481
|
setRunnerLogLevel(options.logLevel);
|
|
5375
5482
|
return actions.run(runInput(evalIds, options));
|
|
@@ -5408,9 +5515,7 @@ function runInput(evalIds, flags) {
|
|
|
5408
5515
|
mode: flags.mode,
|
|
5409
5516
|
select: flags.select,
|
|
5410
5517
|
param: flags.param,
|
|
5411
|
-
|
|
5412
|
-
"chat-timeout-ms": flags.chatTimeoutMs,
|
|
5413
|
-
"turn-budget": flags.turnBudget,
|
|
5518
|
+
limit: flags.limit,
|
|
5414
5519
|
concurrency: flags.concurrency,
|
|
5415
5520
|
repeat: flags.repeat,
|
|
5416
5521
|
json: flags.json,
|
|
@@ -5444,22 +5549,20 @@ async function runEvals(options) {
|
|
|
5444
5549
|
const evaluations = options.evalIds.map((id) => registry.get(id));
|
|
5445
5550
|
if (evaluations.some((e) => !e)) throw new Error("Unknown eval");
|
|
5446
5551
|
const provenance = await runProvenance(registry);
|
|
5447
|
-
await Effect.runPromise(runnerLogs(Effect.all(evaluations.map((evaluation) => {
|
|
5552
|
+
await withFixtures(projectRoot, project?.config, (fixtures) => Effect.runPromise(runnerLogs(Effect.all(evaluations.map((evaluation) => {
|
|
5448
5553
|
const name = evaluation.name ?? authoringId(evaluation);
|
|
5449
5554
|
const started = performance.now();
|
|
5450
5555
|
return Effect.sync(() => runLog.started(name, {}, options.suiteId)).pipe(Effect.andThen(runEval(evaluation, {
|
|
5556
|
+
...projectRunOptions(projectRoot, project?.config),
|
|
5557
|
+
...fixtures ? { fixtures } : {},
|
|
5451
5558
|
report: localReportStore(reportRoot),
|
|
5452
5559
|
workspaceRoot: sandboxRoot,
|
|
5453
|
-
keepWorkspaces: project?.config?.keepSandboxes,
|
|
5454
|
-
artifactExclude: project?.config?.artifacts?.exclude,
|
|
5455
|
-
attachmentLimits: project?.config?.artifacts?.attachments,
|
|
5456
|
-
judge: project?.config?.judge,
|
|
5457
5560
|
suiteId: options.suiteId,
|
|
5458
5561
|
runtime: runtimeFor(evaluation),
|
|
5459
5562
|
concurrency: options.concurrency,
|
|
5460
5563
|
...provenance
|
|
5461
5564
|
})), Effect.tap((result) => runLog.finished(name, {}, result, performance.now() - started)), Effect.tapError((error) => Effect.sync(() => runLog.failed(name, error))));
|
|
5462
|
-
}), { concurrency: options.concurrency })));
|
|
5565
|
+
}), { concurrency: options.concurrency }))));
|
|
5463
5566
|
}
|
|
5464
5567
|
/** Detected per launch: `evalkit dev` keeps running while commits change. */
|
|
5465
5568
|
async function runProvenance(registry) {
|
|
@@ -5517,20 +5620,18 @@ function registerMatrixRoutes(app, registry, dashboardMatrix, repeat) {
|
|
|
5517
5620
|
const name = evaluation.name ?? authoringId(evaluation);
|
|
5518
5621
|
const started = performance.now();
|
|
5519
5622
|
runLog.started(name, body.parameters ?? {}, suiteId);
|
|
5520
|
-
runProvenance(registry).then((provenance) => Effect.runPromise(runnerLogs(runMatrix(dashboardMatrix, {
|
|
5623
|
+
runProvenance(registry).then((provenance) => withFixtures(projectRoot, project?.config, (fixtures) => Effect.runPromise(runnerLogs(runMatrix(dashboardMatrix, {
|
|
5624
|
+
...projectRunOptions(projectRoot, project?.config),
|
|
5625
|
+
...fixtures ? { fixtures } : {},
|
|
5521
5626
|
selection,
|
|
5522
5627
|
concurrency: 1,
|
|
5523
5628
|
...repeat ? { repeat } : {},
|
|
5524
5629
|
...suiteId ? { suiteId } : {},
|
|
5525
5630
|
report: localReportStore(reportRoot),
|
|
5526
5631
|
workspaceRoot: sandboxRoot,
|
|
5527
|
-
keepWorkspaces: project?.config?.keepSandboxes,
|
|
5528
|
-
artifactExclude: project?.config?.artifacts?.exclude,
|
|
5529
|
-
attachmentLimits: project?.config?.artifacts?.attachments,
|
|
5530
|
-
judge: project?.config?.judge,
|
|
5531
5632
|
...provenance,
|
|
5532
5633
|
onResult: (cell, result) => runLog.cell(cell, result, performance.now() - started)
|
|
5533
|
-
})))).catch((error) => runLog.failed(name, error)).finally(() => {
|
|
5634
|
+
}))))).catch((error) => runLog.failed(name, error)).finally(() => {
|
|
5534
5635
|
matrixRunning = false;
|
|
5535
5636
|
});
|
|
5536
5637
|
} else {
|
|
@@ -5625,9 +5726,9 @@ function registerReportRoutes(app, registry, reports) {
|
|
|
5625
5726
|
return context.text(error instanceof Error ? error.message : "Not found", 404);
|
|
5626
5727
|
}
|
|
5627
5728
|
});
|
|
5628
|
-
app.get("/v1/runs/:runId/
|
|
5729
|
+
app.get("/v1/runs/:runId/transcript", async (context) => {
|
|
5629
5730
|
try {
|
|
5630
|
-
return context.json(
|
|
5731
|
+
return context.json(await reports.transcript(context.req.param("runId")));
|
|
5631
5732
|
} catch (error) {
|
|
5632
5733
|
return context.text(error instanceof Error ? error.message : "Not found", 404);
|
|
5633
5734
|
}
|
|
@@ -5781,7 +5882,8 @@ async function tidyAction(options) {
|
|
|
5781
5882
|
registry: project.registry,
|
|
5782
5883
|
reportRoot,
|
|
5783
5884
|
sandboxRoot,
|
|
5784
|
-
keep: project.config.keepSandboxes
|
|
5885
|
+
keep: project.config.keepSandboxes,
|
|
5886
|
+
...project.config.isolation?.kind === "container" ? { runtime: project.config.isolation.runtime } : {}
|
|
5785
5887
|
}), (value) => {
|
|
5786
5888
|
const keep = project.config.keepSandboxes ?? "failed";
|
|
5787
5889
|
if (keep === "all") return "all kept (keepSandboxes: 'all')";
|
|
@@ -5800,14 +5902,21 @@ async function upgradeAction(options) {
|
|
|
5800
5902
|
banner("upgrade", VERSION);
|
|
5801
5903
|
const { root, config } = await loadConfig(projectRoot, options.config);
|
|
5802
5904
|
reportRoot = resolve(root, config.reportDir ?? "_evalkit-results");
|
|
5803
|
-
const report = await step(options.dryRun ? "Checking results" : "Upgrading results", () => upgradeReports(reportRoot, { dryRun: options.dryRun }), (value) => `${plural(value.
|
|
5905
|
+
const report = await step(options.dryRun ? "Checking results" : "Upgrading results", () => upgradeReports(reportRoot, { dryRun: options.dryRun }), (value) => `${plural(value.transcripts, "run")} ${options.dryRun ? "to upgrade" : "upgraded"}`);
|
|
5804
5906
|
console.log("");
|
|
5805
|
-
if (!report.upgraded) console.log(` ${cyan("✓")} Every run is already in the current format.`);
|
|
5806
|
-
|
|
5807
|
-
const into = `${plural(report.upgraded, "run")}
|
|
5808
|
-
console.log(options.dryRun ? ` ${cyan("◆")} Would upgrade ${into}
|
|
5907
|
+
if (!report.upgraded && !report.transcripts && !report.failed.length) console.log(` ${cyan("✓")} Every run is already in the current format.`);
|
|
5908
|
+
if (report.upgraded) {
|
|
5909
|
+
const into = `${plural(report.upgraded, "run")} with trials into ${plural(report.runs, "run")}`;
|
|
5910
|
+
console.log(options.dryRun ? ` ${cyan("◆")} Would upgrade ${into}.` : ` ${cyan("✓")} Upgraded ${into}.`);
|
|
5809
5911
|
if (report.batches) console.log(` ${dim(`${plural(report.batches, "run")} had more than one trial; each trial is now a run in a batch.`)}`);
|
|
5810
5912
|
}
|
|
5913
|
+
if (report.transcripts) console.log(options.dryRun ? ` ${cyan("◆")} Would convert ${plural(report.transcripts, "trajectory", "trajectories")} to ATXF transcripts.` : ` ${cyan("✓")} Converted ${plural(report.transcripts, "trajectory", "trajectories")} to ATXF transcripts.`);
|
|
5914
|
+
if (options.dryRun && (report.upgraded || report.transcripts)) console.log(` ${dim("Nothing was changed.")}`);
|
|
5915
|
+
if (report.failed.length) {
|
|
5916
|
+
console.log(` ${red("✗")} Left ${plural(report.failed.length, "run")} as ${report.failed.length === 1 ? "it was" : "they were"}; ${report.failed.length === 1 ? "it" : "they"} could not be converted:`);
|
|
5917
|
+
for (const { runId, reason } of report.failed) console.log(` ${runId} ${dim(reason)}`);
|
|
5918
|
+
process.exitCode = 1;
|
|
5919
|
+
}
|
|
5811
5920
|
if (report.running.length) console.log(` ${yellow("◆")} Left ${plural(report.running.length, "run")} still running alone; upgrade again when they finish.`);
|
|
5812
5921
|
console.log("");
|
|
5813
5922
|
}
|