@leostera/evalkit 0.6.0 → 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 +53 -7
- package/dist/cli.mjs +5186 -10301
- package/dist/dashboard/assets/index-BEBo_81Y.js +34 -0
- package/dist/dashboard/assets/index-C24IquYZ.css +2 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.d.mts +113 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -8
- package/dist/runner.d.mts +87 -3
- package/dist/runner.d.mts.map +1 -1
- package/dist/runner.mjs +2 -2
- package/dist/skills/evalkit-agents/SKILL.md +1 -1
- 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-CVSCn3bZ.mjs → src-DhromKNu.mjs} +178 -30
- package/dist/{src-D4tCwxNb.mjs → src-b2Wnmzbb.mjs} +54 -3
- package/package.json +1 -1
- package/dist/dashboard/assets/index-BK9x-zoi.css +0 -2
- package/dist/dashboard/assets/index-DzsDLGHx.js +0 -32
|
@@ -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,15 +591,17 @@ 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();
|
|
594
|
+
const message = step.kind === "user" ? userMessage(step, index, input) : void 0;
|
|
548
595
|
await emitRunner({
|
|
549
596
|
kind: "transcript-step-started",
|
|
550
597
|
step: index,
|
|
598
|
+
...message === void 0 ? {} : { message },
|
|
551
599
|
timestamp: now().toISOString()
|
|
552
600
|
});
|
|
553
|
-
if (
|
|
601
|
+
if (message !== void 0) {
|
|
554
602
|
const cursor = input.events.length;
|
|
555
603
|
turn++;
|
|
556
|
-
const result = await sendUserTurn(session,
|
|
604
|
+
const result = await sendUserTurn(session, message, input.signal);
|
|
557
605
|
if (result !== void 0) for (const event of turnResultEvents(result, turn, now)) await input.emitAut(event);
|
|
558
606
|
progress.lastTurn = turnView(input.events, cursor, input.events.length, index);
|
|
559
607
|
} else if (step.kind === "predicate" || step.kind === "judge" || step.kind === "expect-tool-call") {
|
|
@@ -584,6 +632,18 @@ async function executeTranscript(definition, session, input) {
|
|
|
584
632
|
});
|
|
585
633
|
}
|
|
586
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
|
+
}
|
|
587
647
|
async function sendUserTurn(session, message, signal) {
|
|
588
648
|
return runBoundary(Effect.tryPromise({
|
|
589
649
|
try: () => unlessAborted(() => session.send(message), signal),
|
|
@@ -609,7 +669,8 @@ async function runCheckpoint(definition, step, index, input) {
|
|
|
609
669
|
trajectory: { events: input.events },
|
|
610
670
|
turn: progress.lastTurn,
|
|
611
671
|
...turnOutput(progress.lastTurn),
|
|
612
|
-
data: input.data
|
|
672
|
+
data: input.data,
|
|
673
|
+
checkpoints: [...progress.checkpoints]
|
|
613
674
|
}, now, definition.judge)).catch(async (error) => {
|
|
614
675
|
const recorded = recordError(error);
|
|
615
676
|
progress.checkpoints.push({
|
|
@@ -654,20 +715,33 @@ function turnOutput(turn) {
|
|
|
654
715
|
}
|
|
655
716
|
//#endregion
|
|
656
717
|
//#region ../runner/src/final-scoring.ts
|
|
657
|
-
function scoreSummary(results, checkpoints, skippedScorers) {
|
|
718
|
+
function scoreSummary(results, checkpoints, skippedScorers, metrics = []) {
|
|
658
719
|
const valid = results.filter((result) => result.value !== void 0 && !result.skipped);
|
|
659
720
|
const hasError = results.some((result) => result.error !== void 0);
|
|
660
721
|
return {
|
|
661
722
|
results,
|
|
723
|
+
...metrics.length ? { metrics } : {},
|
|
662
724
|
...checkpoints.length ? { checkpoints } : {},
|
|
663
725
|
...skippedScorers.length ? { skippedScorers } : {},
|
|
664
726
|
...valid.length === 0 ? {} : { overall: valid.reduce((sum, result) => sum + (result.value ?? 0), 0) / valid.length },
|
|
665
727
|
passed: !hasError && results.every((result) => result.skipped || result.passed === true) && checkpoints.every((result) => result.status === "passed" || result.status === "skipped") && !skippedScorers.length
|
|
666
728
|
};
|
|
667
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
|
+
}
|
|
668
742
|
/** Append completed scores to the supplied arrays, even when a later scorer or report write fails. */
|
|
669
743
|
async function evaluateFinalScorers(definition, input) {
|
|
670
|
-
const { context, workspace,
|
|
744
|
+
const { context, workspace, results, skippedScorers, now, emitRunner } = input;
|
|
671
745
|
for (const scorer of definition.scoring) {
|
|
672
746
|
if (input.cancelled || input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
|
|
673
747
|
skippedScorers.push(scorer.name);
|
|
@@ -681,14 +755,11 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
681
755
|
});
|
|
682
756
|
let result;
|
|
683
757
|
try {
|
|
684
|
-
const score = await runBoundary(evaluateRule(scorer, {
|
|
758
|
+
const score = await runBoundary(evaluateRule(scorer, scoringContext({
|
|
759
|
+
...input,
|
|
685
760
|
context,
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
...lastTurn ? { turn: lastTurn } : {},
|
|
689
|
-
...turnOutput(lastTurn),
|
|
690
|
-
data: input.data
|
|
691
|
-
}, definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
761
|
+
workspace
|
|
762
|
+
}), definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
|
|
692
763
|
cause,
|
|
693
764
|
message: cause.message
|
|
694
765
|
}))));
|
|
@@ -725,6 +796,68 @@ async function evaluateFinalScorers(definition, input) {
|
|
|
725
796
|
}
|
|
726
797
|
}
|
|
727
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
|
|
728
861
|
//#region ../runner/src/local-report-store.ts
|
|
729
862
|
const MANIFEST_FILE = "manifest.json";
|
|
730
863
|
/** The project's evals and suites when the run started (schema version 4+). */
|
|
@@ -1153,7 +1286,7 @@ async function runTrial(definition, options) {
|
|
|
1153
1286
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
1154
1287
|
})
|
|
1155
1288
|
}), (created) => Effect.tryPromise({
|
|
1156
|
-
try: () => created.cleanup(),
|
|
1289
|
+
try: () => created.cleanup(keepWorkspace(options.keepWorkspaces, scoring?.passed === true)),
|
|
1157
1290
|
catch: (cause) => new DatasetError({
|
|
1158
1291
|
cause,
|
|
1159
1292
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -1218,6 +1351,7 @@ async function runTrial(definition, options) {
|
|
|
1218
1351
|
const { checkpoints, stoppedEarly, lastTurn } = progress;
|
|
1219
1352
|
const scoreResults = [];
|
|
1220
1353
|
const skippedScorers = [];
|
|
1354
|
+
const metrics = [];
|
|
1221
1355
|
try {
|
|
1222
1356
|
await evaluateFinalScorers(definition, {
|
|
1223
1357
|
context,
|
|
@@ -1231,13 +1365,23 @@ async function runTrial(definition, options) {
|
|
|
1231
1365
|
results: scoreResults,
|
|
1232
1366
|
skippedScorers,
|
|
1233
1367
|
data: scoringData,
|
|
1368
|
+
checkpoints,
|
|
1234
1369
|
now,
|
|
1235
1370
|
emitRunner
|
|
1236
1371
|
});
|
|
1237
|
-
|
|
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);
|
|
1238
1382
|
await trialWriter.writeScores(scoring);
|
|
1239
1383
|
if (workspace) try {
|
|
1240
|
-
artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter);
|
|
1384
|
+
artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter, options.artifactExclude);
|
|
1241
1385
|
} catch (captureError) {
|
|
1242
1386
|
if (!primaryError) primaryError = captureError;
|
|
1243
1387
|
await tryEmitError(captureError, emitRunner, now);
|
|
@@ -1252,7 +1396,7 @@ async function runTrial(definition, options) {
|
|
|
1252
1396
|
await tryEmitError(cleanupError, emitRunner, now);
|
|
1253
1397
|
}
|
|
1254
1398
|
}
|
|
1255
|
-
scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers);
|
|
1399
|
+
scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers, metrics);
|
|
1256
1400
|
if (!primaryError) try {
|
|
1257
1401
|
await emitRunner({
|
|
1258
1402
|
kind: "trial-completed",
|
|
@@ -1325,5 +1469,9 @@ function trialCounts(trials) {
|
|
|
1325
1469
|
cancelled: cancelledTrials
|
|
1326
1470
|
};
|
|
1327
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
|
+
}
|
|
1328
1476
|
//#endregion
|
|
1329
|
-
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),
|
|
@@ -759,6 +784,7 @@ const runnerEvent = {
|
|
|
759
784
|
source: Schema.Literal("runner"),
|
|
760
785
|
kind: Schema.Literal("transcript-step-started"),
|
|
761
786
|
step: Schema.Number,
|
|
787
|
+
message: Schema.optional(Schema.String),
|
|
762
788
|
timestamp: Schema.String
|
|
763
789
|
}),
|
|
764
790
|
stepCompleted: Schema.Struct({
|
|
@@ -873,6 +899,10 @@ function expectToolCall(expected) {
|
|
|
873
899
|
expected
|
|
874
900
|
};
|
|
875
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
|
+
*/
|
|
876
906
|
function user(message) {
|
|
877
907
|
return {
|
|
878
908
|
kind: "user",
|
|
@@ -906,6 +936,15 @@ function predicate(name, run, options = {}) {
|
|
|
906
936
|
...options
|
|
907
937
|
});
|
|
908
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
|
+
}
|
|
909
948
|
function judge(name, options) {
|
|
910
949
|
if (!name.trim() || !options.rubric.trim()) throw new Error("Judge name and rubric must not be empty");
|
|
911
950
|
return markDefinitionSite({
|
|
@@ -924,6 +963,12 @@ function defineEval(definition) {
|
|
|
924
963
|
if (datasetIds.has(dataset.id)) throw new Error(`Eval ${definition.id} uses dataset ${dataset.id} more than once`);
|
|
925
964
|
datasetIds.add(dataset.id);
|
|
926
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
|
+
}
|
|
927
972
|
let hasUser = false;
|
|
928
973
|
for (const step of definition.transcript) if (step.kind === "user") hasUser = true;
|
|
929
974
|
else if (step.kind !== "agent" && !hasUser) throw new Error("Checkpoint requires a preceding user step");
|
|
@@ -1000,6 +1045,12 @@ function registerEvals(registrations) {
|
|
|
1000
1045
|
catalog: (options) => evals.map((evaluation) => catalogEntry(evaluation, suiteIdByEvalId.get(authoringId(evaluation)), options))
|
|
1001
1046
|
};
|
|
1002
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
|
+
];
|
|
1003
1054
|
function recordError(error) {
|
|
1004
1055
|
if (error instanceof Error) return {
|
|
1005
1056
|
name: error.name,
|
|
@@ -1012,4 +1063,4 @@ function recordError(error) {
|
|
|
1012
1063
|
};
|
|
1013
1064
|
}
|
|
1014
1065
|
//#endregion
|
|
1015
|
-
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 };
|