@leostera/evalkit 0.6.1 → 0.7.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 +37 -1
- package/dist/cli.mjs +5186 -10301
- package/dist/dashboard/assets/{index-CTJpd8q8.js → index-BEBo_81Y.js} +4 -4
- package/dist/dashboard/assets/index-C24IquYZ.css +2 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.d.mts +111 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -8
- package/dist/runner.d.mts +85 -3
- package/dist/runner.d.mts.map +1 -1
- package/dist/runner.mjs +2 -2
- package/dist/skills/evalkit-evals/SKILL.md +1 -1
- package/dist/skills/evalkit-judges/SKILL.md +3 -2
- package/dist/skills/evalkit-results/SKILL.md +1 -1
- package/dist/{src-CKqOzeBi.mjs → src-DhromKNu.mjs} +175 -29
- package/dist/{src-CZPufWN_.mjs → src-b2Wnmzbb.mjs} +53 -3
- package/package.json +1 -1
- package/dist/dashboard/assets/index-YAabTN3X.css +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as RunSummarySchema, D as RunMetadataSchema, E as RunManifestSchema, F as TrialScoringSchema, H as DEFAULT_ARTIFACT_EXCLUDE, I as TrialSummarySchema, K as toJson, M as TrajectoryEventSchema, N as TrialManifestSchema, P as TrialMetadataSchema, R as normalizeAutEvent, S as JsonValueSchema, V as canonicalParameters, X as relativeDatasetPath, _ as parseTrajectoryJsonl, a as authoringId, i as agentRuntimes, j as ScoreResultSchema, p as recordError, q as toJsonObject, rt as resourceUri, x as CheckpointResultSchema, z as turnResultEvents } from "./src-b2Wnmzbb.mjs";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import { Data, Effect, Either, Exit, Scope } from "effect";
|
|
4
4
|
import os from "node:os";
|
|
@@ -32,21 +32,63 @@ async function unlessAborted(work, signal) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
//#endregion
|
|
35
|
+
//#region ../core/src/glob.ts
|
|
36
|
+
/**
|
|
37
|
+
* A glob matcher for discovery: `*` and `?` stay within a path segment, `**`
|
|
38
|
+
* spans segments, and `{a,b}` is an alternation.
|
|
39
|
+
*/
|
|
40
|
+
function globMatcher(pattern) {
|
|
41
|
+
let source = "";
|
|
42
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
43
|
+
const char = pattern[index];
|
|
44
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
45
|
+
index++;
|
|
46
|
+
if (pattern[index + 1] === "/") {
|
|
47
|
+
index++;
|
|
48
|
+
source += "(?:.*/)?";
|
|
49
|
+
} else source += ".*";
|
|
50
|
+
} else if (char === "*") source += "[^/]*";
|
|
51
|
+
else if (char === "?") source += "[^/]";
|
|
52
|
+
else if (char === "{") {
|
|
53
|
+
const end = pattern.indexOf("}", index);
|
|
54
|
+
if (end === -1) source += "\\{";
|
|
55
|
+
else {
|
|
56
|
+
source += `(?:${pattern.slice(index + 1, end).split(",").map((part) => part.replace(/[.+^$()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join("|")})`;
|
|
57
|
+
index = end;
|
|
58
|
+
}
|
|
59
|
+
} else source += char.replace(/[.+^$()|[\]\\]/g, "\\$&");
|
|
60
|
+
}
|
|
61
|
+
const expression = new RegExp(`^${source}$`);
|
|
62
|
+
return (path) => expression.test(path);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
35
65
|
//#region ../runner/src/snapshot.ts
|
|
66
|
+
/** A pattern without `/` matches a name at any depth, like `.gitignore`. */
|
|
67
|
+
function excludeMatcher(patterns) {
|
|
68
|
+
const matchers = patterns.map((pattern) => {
|
|
69
|
+
const trimmed = pattern.replace(/^\.\//, "").replace(/\/$/, "");
|
|
70
|
+
const match = globMatcher(trimmed);
|
|
71
|
+
return trimmed.includes("/") ? match : (relativePath) => match(path.posix.basename(relativePath));
|
|
72
|
+
});
|
|
73
|
+
return (relativePath) => matchers.some((match) => match(relativePath));
|
|
74
|
+
}
|
|
36
75
|
/**
|
|
37
|
-
* Copies
|
|
38
|
-
*
|
|
76
|
+
* Copies the candidate workspace's regular files into the trial report,
|
|
77
|
+
* leaving out excluded paths. Symbolic links and special files are skipped,
|
|
78
|
+
* never followed, so a snapshot cannot read outside the trial.
|
|
39
79
|
*/
|
|
40
|
-
async function snapshotCandidateWorkspace(candidateRoot, writer) {
|
|
80
|
+
async function snapshotCandidateWorkspace(candidateRoot, writer, exclude = DEFAULT_ARTIFACT_EXCLUDE) {
|
|
41
81
|
const artifacts = [];
|
|
82
|
+
const excluded = excludeMatcher(exclude);
|
|
42
83
|
async function visit(directory, relativeDirectory) {
|
|
43
84
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
44
85
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
45
86
|
for (const entry of entries) {
|
|
46
87
|
const absolutePath = path.join(directory, entry.name);
|
|
47
88
|
const relativePath = path.join(relativeDirectory, entry.name);
|
|
89
|
+
if (excluded(relativePath.split(path.sep).join("/"))) continue;
|
|
48
90
|
const stat = await lstat(absolutePath);
|
|
49
|
-
if (stat.isSymbolicLink())
|
|
91
|
+
if (stat.isSymbolicLink()) continue;
|
|
50
92
|
if (stat.isDirectory()) {
|
|
51
93
|
artifacts.push({
|
|
52
94
|
path: path.join("candidate", relativePath),
|
|
@@ -55,7 +97,7 @@ async function snapshotCandidateWorkspace(candidateRoot, writer) {
|
|
|
55
97
|
await visit(absolutePath, relativePath);
|
|
56
98
|
continue;
|
|
57
99
|
}
|
|
58
|
-
if (!stat.isFile())
|
|
100
|
+
if (!stat.isFile()) continue;
|
|
59
101
|
const data = await readFile(absolutePath);
|
|
60
102
|
const artifactPath = path.join("candidate", relativePath);
|
|
61
103
|
await writer.writeArtifact(artifactPath, data);
|
|
@@ -247,8 +289,9 @@ async function staticDatasetFile(dataset, root, file) {
|
|
|
247
289
|
}
|
|
248
290
|
//#endregion
|
|
249
291
|
//#region ../runner/src/workspace.ts
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
/** Where a trial's persistent workspace lives below `root`. */
|
|
293
|
+
function trialWorkspacePath(root, trialId) {
|
|
294
|
+
return path.join(path.resolve(root), trialId.replace(/[^a-zA-Z0-9._-]/g, "_"));
|
|
252
295
|
}
|
|
253
296
|
/**
|
|
254
297
|
* Creates isolated candidate and evaluator directories and writes each
|
|
@@ -256,7 +299,7 @@ function trialDirectoryName(trialId) {
|
|
|
256
299
|
* Two datasets writing the same file is an error, never an overwrite.
|
|
257
300
|
*/
|
|
258
301
|
async function createTrialWorkspace(trialId, datasets = [], persistentRoot) {
|
|
259
|
-
const root = persistentRoot ?
|
|
302
|
+
const root = persistentRoot ? trialWorkspacePath(persistentRoot, trialId) : await mkdtemp(path.join(os.tmpdir(), "evalkit-trial-"));
|
|
260
303
|
const persistent = persistentRoot !== void 0;
|
|
261
304
|
const artifacts = {
|
|
262
305
|
candidate: { root: path.join(root, "candidate") },
|
|
@@ -270,10 +313,13 @@ async function createTrialWorkspace(trialId, datasets = [], persistentRoot) {
|
|
|
270
313
|
}
|
|
271
314
|
return {
|
|
272
315
|
artifacts,
|
|
273
|
-
cleanup:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
316
|
+
cleanup: async (keep = false) => {
|
|
317
|
+
if (persistent && keep) return;
|
|
318
|
+
await rm(root, {
|
|
319
|
+
recursive: true,
|
|
320
|
+
force: true
|
|
321
|
+
});
|
|
322
|
+
}
|
|
277
323
|
};
|
|
278
324
|
} catch (error) {
|
|
279
325
|
await rm(root, {
|
|
@@ -545,7 +591,7 @@ async function executeTranscript(definition, session, input) {
|
|
|
545
591
|
let turn = 0;
|
|
546
592
|
for (const [index, step] of definition.transcript.entries()) {
|
|
547
593
|
if (input.signal.aborted) throw cancelled();
|
|
548
|
-
const message = step.kind === "user" ? step
|
|
594
|
+
const message = step.kind === "user" ? userMessage(step, index, input) : void 0;
|
|
549
595
|
await emitRunner({
|
|
550
596
|
kind: "transcript-step-started",
|
|
551
597
|
step: index,
|
|
@@ -586,6 +632,18 @@ async function executeTranscript(definition, session, input) {
|
|
|
586
632
|
});
|
|
587
633
|
}
|
|
588
634
|
}
|
|
635
|
+
/** A computed message sees the parameters and candidate data the agent also sees. */
|
|
636
|
+
function userMessage(step, index, { context, randomSeed }) {
|
|
637
|
+
if (typeof step.message === "string") return step.message;
|
|
638
|
+
const message = step.message({
|
|
639
|
+
parameters: context.parameters ?? {},
|
|
640
|
+
data: context.data,
|
|
641
|
+
seed: randomSeed,
|
|
642
|
+
trialIndex: context.trialIndex
|
|
643
|
+
});
|
|
644
|
+
if (typeof message !== "string" || !message.trim()) throw new Error(`The user message at step ${index + 1} must compute a non-empty string`);
|
|
645
|
+
return message;
|
|
646
|
+
}
|
|
589
647
|
async function sendUserTurn(session, message, signal) {
|
|
590
648
|
return runBoundary(Effect.tryPromise({
|
|
591
649
|
try: () => unlessAborted(() => session.send(message), signal),
|
|
@@ -611,7 +669,8 @@ async function runCheckpoint(definition, step, index, input) {
|
|
|
611
669
|
trajectory: { events: input.events },
|
|
612
670
|
turn: progress.lastTurn,
|
|
613
671
|
...turnOutput(progress.lastTurn),
|
|
614
|
-
data: input.data
|
|
672
|
+
data: input.data,
|
|
673
|
+
checkpoints: [...progress.checkpoints]
|
|
615
674
|
}, now, definition.judge)).catch(async (error) => {
|
|
616
675
|
const recorded = recordError(error);
|
|
617
676
|
progress.checkpoints.push({
|
|
@@ -656,20 +715,33 @@ function turnOutput(turn) {
|
|
|
656
715
|
}
|
|
657
716
|
//#endregion
|
|
658
717
|
//#region ../runner/src/final-scoring.ts
|
|
659
|
-
function scoreSummary(results, checkpoints, skippedScorers) {
|
|
718
|
+
function scoreSummary(results, checkpoints, skippedScorers, metrics = []) {
|
|
660
719
|
const valid = results.filter((result) => result.value !== void 0 && !result.skipped);
|
|
661
720
|
const hasError = results.some((result) => result.error !== void 0);
|
|
662
721
|
return {
|
|
663
722
|
results,
|
|
723
|
+
...metrics.length ? { metrics } : {},
|
|
664
724
|
...checkpoints.length ? { checkpoints } : {},
|
|
665
725
|
...skippedScorers.length ? { skippedScorers } : {},
|
|
666
726
|
...valid.length === 0 ? {} : { overall: valid.reduce((sum, result) => sum + (result.value ?? 0), 0) / valid.length },
|
|
667
727
|
passed: !hasError && results.every((result) => result.skipped || result.passed === true) && checkpoints.every((result) => result.status === "passed" || result.status === "skipped") && !skippedScorers.length
|
|
668
728
|
};
|
|
669
729
|
}
|
|
730
|
+
/** What final scorers and metrics receive. */
|
|
731
|
+
function scoringContext(input) {
|
|
732
|
+
return {
|
|
733
|
+
context: input.context,
|
|
734
|
+
artifacts: input.workspace.artifacts,
|
|
735
|
+
trajectory: { events: input.events },
|
|
736
|
+
...input.lastTurn ? { turn: input.lastTurn } : {},
|
|
737
|
+
...turnOutput(input.lastTurn),
|
|
738
|
+
data: input.data,
|
|
739
|
+
checkpoints: input.checkpoints
|
|
740
|
+
};
|
|
741
|
+
}
|
|
670
742
|
/** Append completed scores to the supplied arrays, even when a later scorer or report write fails. */
|
|
671
743
|
async function evaluateFinalScorers(definition, input) {
|
|
672
|
-
const { context, workspace,
|
|
744
|
+
const { context, workspace, results, skippedScorers, now, emitRunner } = input;
|
|
673
745
|
for (const scorer of definition.scoring) {
|
|
674
746
|
if (input.cancelled || input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
675
747
|
skippedScorers.push(scorer.name);
|
|
@@ -683,14 +755,11 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
683
755
|
});
|
|
684
756
|
let result;
|
|
685
757
|
try {
|
|
686
|
-
const score = await runBoundary(evaluateRule(scorer, {
|
|
758
|
+
const score = await runBoundary(evaluateRule(scorer, scoringContext({
|
|
759
|
+
...input,
|
|
687
760
|
context,
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
...lastTurn ? { turn: lastTurn } : {},
|
|
691
|
-
...turnOutput(lastTurn),
|
|
692
|
-
data: input.data
|
|
693
|
-
}, definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
761
|
+
workspace
|
|
762
|
+
}), definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
694
763
|
cause,
|
|
695
764
|
message: cause.message
|
|
696
765
|
}))));
|
|
@@ -727,6 +796,68 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
727
796
|
}
|
|
728
797
|
}
|
|
729
798
|
//#endregion
|
|
799
|
+
//#region ../runner/src/metrics.ts
|
|
800
|
+
/**
|
|
801
|
+
* What EvalKit measures for every trial from its trajectory: the agent's time
|
|
802
|
+
* (first event to the last one before final scoring), tokens when the agent
|
|
803
|
+
* reported usage, and tool calls.
|
|
804
|
+
*/
|
|
805
|
+
function builtInMetrics(events) {
|
|
806
|
+
const times = events.filter((event) => !event.kind.startsWith("scorer-")).map((event) => Date.parse(event.timestamp)).filter((time) => !Number.isNaN(time));
|
|
807
|
+
const metrics = [];
|
|
808
|
+
if (times.length) metrics.push({
|
|
809
|
+
name: "agent time",
|
|
810
|
+
value: Math.max(...times) - Math.min(...times),
|
|
811
|
+
unit: "ms",
|
|
812
|
+
better: "lower",
|
|
813
|
+
builtIn: true
|
|
814
|
+
});
|
|
815
|
+
let tokens;
|
|
816
|
+
for (const event of events) if (event.source === "aut" && event.kind === "turn-completed" && event.usage) {
|
|
817
|
+
const { totalTokens, inputTokens, outputTokens } = event.usage;
|
|
818
|
+
const turn = totalTokens ?? (inputTokens === void 0 && outputTokens === void 0 ? void 0 : (inputTokens ?? 0) + (outputTokens ?? 0));
|
|
819
|
+
if (turn !== void 0) tokens = (tokens ?? 0) + turn;
|
|
820
|
+
}
|
|
821
|
+
if (tokens !== void 0) metrics.push({
|
|
822
|
+
name: "tokens",
|
|
823
|
+
value: tokens,
|
|
824
|
+
unit: "tokens",
|
|
825
|
+
better: "lower",
|
|
826
|
+
builtIn: true
|
|
827
|
+
});
|
|
828
|
+
metrics.push({
|
|
829
|
+
name: "tool calls",
|
|
830
|
+
value: events.filter((event) => event.source === "aut" && event.kind === "tool-call").length,
|
|
831
|
+
builtIn: true
|
|
832
|
+
});
|
|
833
|
+
return metrics;
|
|
834
|
+
}
|
|
835
|
+
/** Runs the scenario's metrics; a failing metric records its error and the rest still run. */
|
|
836
|
+
async function evaluateMetrics(definition, context) {
|
|
837
|
+
const results = [];
|
|
838
|
+
for (const entry of definition.metrics ?? []) {
|
|
839
|
+
const shape = {
|
|
840
|
+
name: entry.name,
|
|
841
|
+
...entry.unit ? { unit: entry.unit } : {},
|
|
842
|
+
...entry.better ? { better: entry.better } : {}
|
|
843
|
+
};
|
|
844
|
+
try {
|
|
845
|
+
const value = await entry.run(context);
|
|
846
|
+
if (value !== void 0 && !Number.isFinite(value)) throw new Error(`Metric ${entry.name} must return a finite number or undefined; got ${String(value)}`);
|
|
847
|
+
results.push({
|
|
848
|
+
...shape,
|
|
849
|
+
...value === void 0 ? {} : { value }
|
|
850
|
+
});
|
|
851
|
+
} catch (error) {
|
|
852
|
+
results.push({
|
|
853
|
+
...shape,
|
|
854
|
+
error: recordError(error)
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return results;
|
|
859
|
+
}
|
|
860
|
+
//#endregion
|
|
730
861
|
//#region ../runner/src/local-report-store.ts
|
|
731
862
|
const MANIFEST_FILE = "manifest.json";
|
|
732
863
|
/** The project's evals and suites when the run started (schema version 4+). */
|
|
@@ -1155,7 +1286,7 @@ async function runTrial(definition, options) {
|
|
|
1155
1286
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
1156
1287
|
})
|
|
1157
1288
|
}), (created) => Effect.tryPromise({
|
|
1158
|
-
try: () => created.cleanup(),
|
|
1289
|
+
try: () => created.cleanup(keepWorkspace(options.keepWorkspaces, scoring?.passed === true)),
|
|
1159
1290
|
catch: (cause) => new DatasetError({
|
|
1160
1291
|
cause,
|
|
1161
1292
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -1220,6 +1351,7 @@ async function runTrial(definition, options) {
|
|
|
1220
1351
|
const { checkpoints, stoppedEarly, lastTurn } = progress;
|
|
1221
1352
|
const scoreResults = [];
|
|
1222
1353
|
const skippedScorers = [];
|
|
1354
|
+
const metrics = [];
|
|
1223
1355
|
try {
|
|
1224
1356
|
await evaluateFinalScorers(definition, {
|
|
1225
1357
|
context,
|
|
@@ -1233,13 +1365,23 @@ async function runTrial(definition, options) {
|
|
|
1233
1365
|
results: scoreResults,
|
|
1234
1366
|
skippedScorers,
|
|
1235
1367
|
data: scoringData,
|
|
1368
|
+
checkpoints,
|
|
1236
1369
|
now,
|
|
1237
1370
|
emitRunner
|
|
1238
1371
|
});
|
|
1239
|
-
|
|
1372
|
+
metrics.push(...builtInMetrics(events));
|
|
1373
|
+
if (context && workspace && !wasCancelled) metrics.push(...await evaluateMetrics(definition, scoringContext({
|
|
1374
|
+
context,
|
|
1375
|
+
workspace,
|
|
1376
|
+
events,
|
|
1377
|
+
lastTurn,
|
|
1378
|
+
data: scoringData,
|
|
1379
|
+
checkpoints
|
|
1380
|
+
})));
|
|
1381
|
+
scoring = scoreSummary(scoreResults, checkpoints, skippedScorers, metrics);
|
|
1240
1382
|
await trialWriter.writeScores(scoring);
|
|
1241
1383
|
if (workspace) try {
|
|
1242
|
-
artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter);
|
|
1384
|
+
artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter, options.artifactExclude);
|
|
1243
1385
|
} catch (captureError) {
|
|
1244
1386
|
if (!primaryError) primaryError = captureError;
|
|
1245
1387
|
await tryEmitError(captureError, emitRunner, now);
|
|
@@ -1254,7 +1396,7 @@ async function runTrial(definition, options) {
|
|
|
1254
1396
|
await tryEmitError(cleanupError, emitRunner, now);
|
|
1255
1397
|
}
|
|
1256
1398
|
}
|
|
1257
|
-
scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers);
|
|
1399
|
+
scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers, metrics);
|
|
1258
1400
|
if (!primaryError) try {
|
|
1259
1401
|
await emitRunner({
|
|
1260
1402
|
kind: "trial-completed",
|
|
@@ -1327,5 +1469,9 @@ function trialCounts(trials) {
|
|
|
1327
1469
|
cancelled: cancelledTrials
|
|
1328
1470
|
};
|
|
1329
1471
|
}
|
|
1472
|
+
/** Persistent workspaces of trials that did not pass are kept for debugging by default. */
|
|
1473
|
+
function keepWorkspace(policy = "failed", passed) {
|
|
1474
|
+
return policy === "all" || policy === "failed" && !passed;
|
|
1475
|
+
}
|
|
1330
1476
|
//#endregion
|
|
1331
|
-
export {
|
|
1477
|
+
export { CheckpointExecutionError as _, readRunSummary as a, ScoringError as b, readTrialScoring as c, trialWorkspacePath as d, staticDatasetFile as f, CancelledError as g, AutExecutionError as h, readRunManifest as i, readTrialSummary as l, globMatcher as m, runMatrix as n, readTrialEvents as o, staticDatasetFiles as p, ownerIsGone as r, readTrialManifest as s, runEval as t, localReportStore as u, DatasetError as v, ReportError as y };
|
|
@@ -283,10 +283,10 @@ function catalogScorer(rule, at) {
|
|
|
283
283
|
}
|
|
284
284
|
function catalogStep(step, at) {
|
|
285
285
|
switch (step.kind) {
|
|
286
|
-
case "user": return {
|
|
286
|
+
case "user": return typeof step.message === "string" ? {
|
|
287
287
|
kind: "user",
|
|
288
288
|
message: step.message
|
|
289
|
-
};
|
|
289
|
+
} : { kind: "user" };
|
|
290
290
|
case "agent": return {
|
|
291
291
|
kind: "agent",
|
|
292
292
|
expectation: step.expectation
|
|
@@ -316,6 +316,22 @@ function shapeOf(value) {
|
|
|
316
316
|
return { type: typeof json };
|
|
317
317
|
}
|
|
318
318
|
//#endregion
|
|
319
|
+
//#region ../core/src/config.ts
|
|
320
|
+
/** Left out of candidate snapshots unless `artifacts.exclude` says otherwise. */
|
|
321
|
+
const DEFAULT_ARTIFACT_EXCLUDE = [
|
|
322
|
+
".git",
|
|
323
|
+
"node_modules",
|
|
324
|
+
".venv",
|
|
325
|
+
"__pycache__",
|
|
326
|
+
"_build",
|
|
327
|
+
"_opam",
|
|
328
|
+
"target"
|
|
329
|
+
];
|
|
330
|
+
/** Pure, typed project configuration. Loading/discovery belongs to the CLI. */
|
|
331
|
+
function defineConfig(configuration) {
|
|
332
|
+
return configuration;
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
319
335
|
//#region ../core/src/canonical-json.ts
|
|
320
336
|
/** Canonical JSON: keys sorted recursively, array order preserved. Rejects non-JSON inputs. */
|
|
321
337
|
function canonicalParameters(value) {
|
|
@@ -663,8 +679,17 @@ const CheckpointResultSchema = Schema.Struct({
|
|
|
663
679
|
id: Schema.String
|
|
664
680
|
}))
|
|
665
681
|
});
|
|
682
|
+
const MetricResultSchema = Schema.Struct({
|
|
683
|
+
name: Schema.String,
|
|
684
|
+
value: Schema.optional(Schema.Number),
|
|
685
|
+
unit: Schema.optional(Schema.String),
|
|
686
|
+
better: Schema.optional(Schema.Literal("lower", "higher")),
|
|
687
|
+
builtIn: Schema.optional(Schema.Literal(true)),
|
|
688
|
+
error: Schema.optional(RecordedErrorSchema)
|
|
689
|
+
});
|
|
666
690
|
const TrialScoringSchema = Schema.Struct({
|
|
667
691
|
results: Schema.mutable(Schema.Array(ScoreResultSchema)),
|
|
692
|
+
metrics: Schema.optional(Schema.mutable(Schema.Array(MetricResultSchema))),
|
|
668
693
|
checkpoints: Schema.optional(Schema.mutable(Schema.Array(CheckpointResultSchema))),
|
|
669
694
|
skippedScorers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
|
|
670
695
|
overall: Schema.optional(Schema.Number),
|
|
@@ -874,6 +899,10 @@ function expectToolCall(expected) {
|
|
|
874
899
|
expected
|
|
875
900
|
};
|
|
876
901
|
}
|
|
902
|
+
/**
|
|
903
|
+
* A user message. Pass a function to compute it for each trial, e.g. from a
|
|
904
|
+
* matrix axis: `user(({ parameters }) => `Set up OCaml with ${parameters.toolchain}.`)`.
|
|
905
|
+
*/
|
|
877
906
|
function user(message) {
|
|
878
907
|
return {
|
|
879
908
|
kind: "user",
|
|
@@ -907,6 +936,15 @@ function predicate(name, run, options = {}) {
|
|
|
907
936
|
...options
|
|
908
937
|
});
|
|
909
938
|
}
|
|
939
|
+
function metric(name, run, options = {}) {
|
|
940
|
+
if (!name.trim()) throw new Error("Metric name must not be empty");
|
|
941
|
+
return markDefinitionSite({
|
|
942
|
+
kind: "metric",
|
|
943
|
+
name,
|
|
944
|
+
run,
|
|
945
|
+
...options
|
|
946
|
+
});
|
|
947
|
+
}
|
|
910
948
|
function judge(name, options) {
|
|
911
949
|
if (!name.trim() || !options.rubric.trim()) throw new Error("Judge name and rubric must not be empty");
|
|
912
950
|
return markDefinitionSite({
|
|
@@ -925,6 +963,12 @@ function defineEval(definition) {
|
|
|
925
963
|
if (datasetIds.has(dataset.id)) throw new Error(`Eval ${definition.id} uses dataset ${dataset.id} more than once`);
|
|
926
964
|
datasetIds.add(dataset.id);
|
|
927
965
|
}
|
|
966
|
+
const metricNames = new Set(BUILT_IN_METRICS);
|
|
967
|
+
for (const [index, entry] of (definition.metrics ?? []).entries()) {
|
|
968
|
+
if (entry?.kind !== "metric") throw new Error(`Eval ${definition.id} metrics[${index}] is not a metric; create it with metric(...)`);
|
|
969
|
+
if (metricNames.has(entry.name)) throw new Error(`Eval ${definition.id} has more than one metric named ${entry.name} (built-in: ${BUILT_IN_METRICS.join(", ")})`);
|
|
970
|
+
metricNames.add(entry.name);
|
|
971
|
+
}
|
|
928
972
|
let hasUser = false;
|
|
929
973
|
for (const step of definition.transcript) if (step.kind === "user") hasUser = true;
|
|
930
974
|
else if (step.kind !== "agent" && !hasUser) throw new Error("Checkpoint requires a preceding user step");
|
|
@@ -1001,6 +1045,12 @@ function registerEvals(registrations) {
|
|
|
1001
1045
|
catalog: (options) => evals.map((evaluation) => catalogEntry(evaluation, suiteIdByEvalId.get(authoringId(evaluation)), options))
|
|
1002
1046
|
};
|
|
1003
1047
|
}
|
|
1048
|
+
/** Metrics EvalKit records for every trial; scenario metrics may not reuse these names. */
|
|
1049
|
+
const BUILT_IN_METRICS = [
|
|
1050
|
+
"agent time",
|
|
1051
|
+
"tokens",
|
|
1052
|
+
"tool calls"
|
|
1053
|
+
];
|
|
1004
1054
|
function recordError(error) {
|
|
1005
1055
|
if (error instanceof Error) return {
|
|
1006
1056
|
name: error.name,
|
|
@@ -1013,4 +1063,4 @@ function recordError(error) {
|
|
|
1013
1063
|
};
|
|
1014
1064
|
}
|
|
1015
1065
|
//#endregion
|
|
1016
|
-
export {
|
|
1066
|
+
export { ResourceUriSchema as $, RunSummarySchema as A, defineEvalMatrix as B, JudgeRunInfoSchema as C, RunMetadataSchema as D, RunManifestSchema as E, TrialScoringSchema as F, NotJsonError as G, DEFAULT_ARTIFACT_EXCLUDE as H, TrialSummarySchema as I, defineDataset as J, toJson as K, UsageSchema as L, TrajectoryEventSchema as M, TrialManifestSchema as N, RunSourceSchema as O, TrialMetadataSchema as P, ResourceKindSchema as Q, normalizeAutEvent as R, JsonValueSchema as S, RecordedErrorSchema as T, defineConfig as U, canonicalParameters as V, catalogEntry as W, relativeDatasetPath as X, isDataset as Y, definitionSite as Z, parseTrajectoryJsonl as _, authoringId as a, resourceUuid as at, AutIdentitySchema as b, defineSuite as c, metric as d, UuidSchema as et, predicate as f, user as g, skip as h, agentRuntimes as i, resourceUriSchema as it, ScoreResultSchema as j, RunStatusSchema as k, expectToolCall as l, registerEvals as m, DEFAULT_AGENT_RUNTIMES as n, parseResourceUri as nt, defineAgent as o, recordError as p, toJsonObject as q, agent as r, resourceUri as rt, defineEval as s, BUILT_IN_METRICS as t, authorId as tt, judge as u, ApiErrorSchema as v, MetricResultSchema as w, CheckpointResultSchema as x, ArtifactEntrySchema as y, turnResultEvents as z };
|