@leostera/evalkit 0.9.0 → 0.10.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 +48 -1
- package/README.md +3 -2
- package/dist/cli.mjs +139 -29
- package/dist/dashboard/assets/{index-By1SJ1on.js → index-DNR4shrM.js} +32 -31
- package/dist/dashboard/assets/index-TPYprg75.css +2 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/{index-db-BbVblaHz.mjs → index-db-B1FeXzmS.mjs} +1 -1
- package/dist/index.d.mts +313 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/runner.d.mts +222 -6
- package/dist/runner.d.mts.map +1 -1
- package/dist/runner.mjs +1 -1
- package/dist/share-worker.mjs +1 -1
- package/dist/share.mjs +2 -2
- package/dist/skills/evalkit/SKILL.md +2 -2
- package/dist/skills/evalkit-evals/SKILL.md +1 -1
- package/dist/skills/evalkit-judges/SKILL.md +3 -3
- package/dist/skills/evalkit-results/SKILL.md +1 -1
- package/dist/{src-BrfIXfO2.mjs → src-B14fXAGp.mjs} +264 -21
- package/dist/{src-Bn6fnZnF.mjs → src-Bccv9igQ.mjs} +1 -1
- package/dist/{src-CcdLZqFk.mjs → src-BfSQKOJP.mjs} +260 -97
- package/dist/{upgrade-60SIRius.mjs → upgrade-G-uFPJKg.mjs} +1 -0
- package/package.json +1 -1
- package/dist/dashboard/assets/index-geXMXCx9.css +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as assertNoRemovedOptions } from "./upgrade-
|
|
1
|
+
import { n as assertNoRemovedOptions } from "./upgrade-G-uFPJKg.mjs";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
//#region ../core/src/identity.ts
|
|
4
4
|
const ResourceKindSchema = Schema.Literal("run", "artifact");
|
|
@@ -206,11 +206,194 @@ function toJsonObject(value, path) {
|
|
|
206
206
|
return converted;
|
|
207
207
|
}
|
|
208
208
|
//#endregion
|
|
209
|
+
//#region ../core/src/judge.ts
|
|
210
|
+
const KEY = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
|
|
211
|
+
function unitInterval(value) {
|
|
212
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A judge that answers with one of these choices, in this order:
|
|
216
|
+
* `choices({ A: ['Accurate and helpful', 1], B: ['Partly right', 0.5], C: ['Wrong', 0] })`.
|
|
217
|
+
*/
|
|
218
|
+
function choices(options) {
|
|
219
|
+
const entries = Object.entries(options);
|
|
220
|
+
if (entries.length < 2) throw new Error("choices(...) needs at least two choices");
|
|
221
|
+
const seen = /* @__PURE__ */ new Set();
|
|
222
|
+
return {
|
|
223
|
+
kind: "choices",
|
|
224
|
+
choices: entries.map(([key, [label, value]]) => {
|
|
225
|
+
if (!KEY.test(key)) throw new Error(`Choice ${JSON.stringify(key)} must be a short word of letters, digits, "-" or "_"`);
|
|
226
|
+
if (seen.has(key.toUpperCase())) throw new Error(`Choice ${key} is listed twice`);
|
|
227
|
+
seen.add(key.toUpperCase());
|
|
228
|
+
if (!label.trim()) throw new Error(`Choice ${key} needs a label`);
|
|
229
|
+
if (!unitInterval(value)) throw new Error(`Choice ${key} must be worth a number from 0 to 1; received ${String(value)}`);
|
|
230
|
+
return {
|
|
231
|
+
key,
|
|
232
|
+
label,
|
|
233
|
+
value
|
|
234
|
+
};
|
|
235
|
+
})
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* A judge that scores each criterion from 0 to 1; the score is the weighted
|
|
240
|
+
* mean. A number is the weight; `{ weight, description }` also says what the
|
|
241
|
+
* criterion means. Weights default to 1.
|
|
242
|
+
*/
|
|
243
|
+
function criteria(options) {
|
|
244
|
+
return {
|
|
245
|
+
kind: "criteria",
|
|
246
|
+
criteria: declaredCriteria(options)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** Checked criteria declarations, for `criteria()` and `predicate(..., { criteria })`. */
|
|
250
|
+
function declaredCriteria(options) {
|
|
251
|
+
const entries = Object.entries(options);
|
|
252
|
+
if (!entries.length) throw new Error("criteria(...) needs a criterion");
|
|
253
|
+
return entries.map(([name, option]) => {
|
|
254
|
+
if (!name.trim()) throw new Error("A criterion needs a name");
|
|
255
|
+
const weight = typeof option === "number" ? option : option.weight ?? 1;
|
|
256
|
+
if (!Number.isFinite(weight) || weight <= 0) throw new Error(`Criterion ${name} must have a positive weight; received ${String(weight)}`);
|
|
257
|
+
const description = typeof option === "number" ? void 0 : option.description;
|
|
258
|
+
return {
|
|
259
|
+
name,
|
|
260
|
+
weight,
|
|
261
|
+
...description ? { description } : {}
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/** What a criterion counts for in the weighted mean. */
|
|
266
|
+
const criterionValue = (result) => "value" in result ? result.value : 0;
|
|
267
|
+
/** The weighted mean of criteria; weights default to 1, a criterion not reached counts 0. */
|
|
268
|
+
function weightedMean(results) {
|
|
269
|
+
let total = 0;
|
|
270
|
+
let weights = 0;
|
|
271
|
+
for (const result of results) {
|
|
272
|
+
const weight = result.weight ?? 1;
|
|
273
|
+
total += weight * criterionValue(result);
|
|
274
|
+
weights += weight;
|
|
275
|
+
}
|
|
276
|
+
if (!weights) throw new Error("Criteria need a positive total weight");
|
|
277
|
+
return total / weights;
|
|
278
|
+
}
|
|
279
|
+
/** The inputs a judge sees when the rule names none. */
|
|
280
|
+
const DEFAULT_JUDGE_INPUTS = ["conversation", "reply"];
|
|
281
|
+
/** The names of the inputs a judge sees, in prompt order. */
|
|
282
|
+
function judgeInputNames(rule) {
|
|
283
|
+
return rule.inputs ? Object.keys(rule.inputs) : [...DEFAULT_JUDGE_INPUTS];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The prompt a judge agent receives. `values` holds each input's text;
|
|
287
|
+
* an input without one shows as a `{placeholder}`, which is how the
|
|
288
|
+
* dashboard shows the prompt before any run.
|
|
289
|
+
*/
|
|
290
|
+
function judgePrompt(options) {
|
|
291
|
+
const inputs = options.inputs.map((name) => `${name}:\n${options.values?.[name] ?? `{${name}}`}`).join("\n\n");
|
|
292
|
+
return [
|
|
293
|
+
"You are judging one run of an AI agent. Judge only by the rubric, using only the inputs below.",
|
|
294
|
+
`Rubric:\n${options.rubric.trim()}`,
|
|
295
|
+
inputs,
|
|
296
|
+
answerInstruction(options.answer)
|
|
297
|
+
].join("\n\n");
|
|
298
|
+
}
|
|
299
|
+
function answerInstruction(answer) {
|
|
300
|
+
switch (answer.kind) {
|
|
301
|
+
case "verdict": return "Answer with only a JSON object: {\"value\": <a number from 0 to 1>, \"explanation\": \"<one or two sentences why>\"}";
|
|
302
|
+
case "choices": return [`Answer with one of ${answer.choices.map((choice) => choice.key).join(", ")} on the first line, then one sentence why on the next.`, ...answer.choices.map((choice) => `${choice.key}) ${choice.label}`)].join("\n");
|
|
303
|
+
case "criteria": return [
|
|
304
|
+
"Score each criterion from 0 to 1:",
|
|
305
|
+
...answer.criteria.map((criterion) => `- ${criterion.name}${criterion.description ? `: ${criterion.description}` : ""}`),
|
|
306
|
+
`Answer with only a JSON object: {"criteria": {${answer.criteria.map((criterion) => `${JSON.stringify(criterion.name)}: {"value": <0 to 1>, "explanation": "<why>"}`).join(", ")}}, "explanation": "<one sentence overall>"}`
|
|
307
|
+
].join("\n");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** Thrown when a judge's reply is not in the format asked for; the message says why. */
|
|
311
|
+
var UnreadableAnswer = class extends Error {};
|
|
312
|
+
/**
|
|
313
|
+
* Reads a judge's reply in the format its rule asked for. Throws
|
|
314
|
+
* `UnreadableAnswer` with a reason the judge can be told, so it can answer again.
|
|
315
|
+
*/
|
|
316
|
+
function readJudgeAnswer(answer, reply) {
|
|
317
|
+
if (answer.kind === "choices") return readChoice(answer.choices, reply);
|
|
318
|
+
const object = jsonObject(reply);
|
|
319
|
+
if (answer.kind === "verdict") {
|
|
320
|
+
if (!unitInterval(object.value)) throw new UnreadableAnswer("\"value\" must be a number from 0 to 1");
|
|
321
|
+
return {
|
|
322
|
+
value: object.value,
|
|
323
|
+
...typeof object.explanation === "string" && object.explanation.trim() ? { explanation: object.explanation.trim() } : {},
|
|
324
|
+
...object.evidence !== void 0 ? { evidence: object.evidence } : {}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const scored = object.criteria;
|
|
328
|
+
if (typeof scored !== "object" || scored === null || Array.isArray(scored)) throw new UnreadableAnswer("\"criteria\" must be an object with one entry per criterion");
|
|
329
|
+
const results = answer.criteria.map((criterion) => {
|
|
330
|
+
const entry = scored[criterion.name];
|
|
331
|
+
const value = typeof entry === "number" ? entry : typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry.value : void 0;
|
|
332
|
+
if (!unitInterval(value)) throw new UnreadableAnswer(`criterion ${JSON.stringify(criterion.name)} needs a "value" from 0 to 1`);
|
|
333
|
+
const explanation = typeof entry === "object" && entry !== null && !Array.isArray(entry) && typeof entry.explanation === "string" && entry.explanation.trim() ? entry.explanation.trim() : void 0;
|
|
334
|
+
return {
|
|
335
|
+
name: criterion.name,
|
|
336
|
+
value,
|
|
337
|
+
weight: criterion.weight,
|
|
338
|
+
...explanation ? { explanation } : {}
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
return {
|
|
342
|
+
value: weightedMean(results),
|
|
343
|
+
criteria: results,
|
|
344
|
+
...typeof object.explanation === "string" && object.explanation.trim() ? { explanation: object.explanation.trim() } : {}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function readChoice(options, reply) {
|
|
348
|
+
if (typeof reply !== "string") throw new UnreadableAnswer("answer with plain text, not JSON");
|
|
349
|
+
const lines = reply.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
350
|
+
const first = lines[0] ?? "";
|
|
351
|
+
const match = /^(?:answer\s*[:-]?\s*)?[*_("'[\s]*([A-Za-z0-9][A-Za-z0-9_-]*)/i.exec(first);
|
|
352
|
+
const said = match?.[1]?.toUpperCase();
|
|
353
|
+
const choice = options.find((option) => option.key.toUpperCase() === said);
|
|
354
|
+
if (!choice) throw new UnreadableAnswer(`the first line must be one of ${options.map((option) => option.key).join(", ")}`);
|
|
355
|
+
const explanation = [first.slice(match[0].length).replace(/^[*_)"'\].:\s-]+/, "").trim(), ...lines.slice(1)].filter(Boolean).join(" ");
|
|
356
|
+
return {
|
|
357
|
+
value: choice.value,
|
|
358
|
+
choice: choice.key,
|
|
359
|
+
...explanation ? { explanation } : {}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/** The JSON object in a reply: the reply itself, or the first `{…}` in its text (code fences allowed). */
|
|
363
|
+
function jsonObject(reply) {
|
|
364
|
+
if (typeof reply === "object" && reply !== null && !Array.isArray(reply)) return reply;
|
|
365
|
+
if (typeof reply !== "string") throw new UnreadableAnswer("answer with a JSON object");
|
|
366
|
+
const start = reply.indexOf("{");
|
|
367
|
+
const end = reply.lastIndexOf("}");
|
|
368
|
+
if (start === -1 || end < start) throw new UnreadableAnswer("answer with only a JSON object");
|
|
369
|
+
let parsed;
|
|
370
|
+
try {
|
|
371
|
+
parsed = JSON.parse(reply.slice(start, end + 1));
|
|
372
|
+
} catch {
|
|
373
|
+
throw new UnreadableAnswer("the JSON object could not be parsed");
|
|
374
|
+
}
|
|
375
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new UnreadableAnswer("answer with a JSON object");
|
|
376
|
+
return parsed;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* The pass mark in words: `only 1`, `≥ 0.5`, or for choices the ones that
|
|
380
|
+
* pass, such as `A or B`.
|
|
381
|
+
*/
|
|
382
|
+
function passMark(passAt, answer) {
|
|
383
|
+
const at = passAt ?? 1;
|
|
384
|
+
if (answer?.kind === "choices") {
|
|
385
|
+
const passing = answer.choices.filter((choice) => choice.value >= at).map((choice) => choice.key);
|
|
386
|
+
if (!passing.length) return "no choice passes";
|
|
387
|
+
if (passing.length === answer.choices.length) return "any choice";
|
|
388
|
+
return passing.length === 1 ? `only ${passing[0]}` : `${passing.slice(0, -1).join(", ")} or ${passing.at(-1)}`;
|
|
389
|
+
}
|
|
390
|
+
return at === 1 ? "only 1" : `≥ ${at}`;
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
209
393
|
//#region ../core/src/catalog.ts
|
|
210
394
|
function catalogEntry(evaluation, suite, options = {}) {
|
|
211
395
|
const at = (value) => relativeSite(definitionSite(value), options.root);
|
|
212
396
|
const definedAt = at(evaluation);
|
|
213
|
-
const judgeAgent = evaluation.judge ? catalogAgent(evaluation.judge, at) : void 0;
|
|
214
397
|
const policy = evaluation.policy ?? {};
|
|
215
398
|
return {
|
|
216
399
|
id: evaluation.id,
|
|
@@ -218,15 +401,14 @@ function catalogEntry(evaluation, suite, options = {}) {
|
|
|
218
401
|
...evaluation.name ? { name: evaluation.name } : {},
|
|
219
402
|
...suite ? { suiteId: authoringId(suite) } : {},
|
|
220
403
|
agent: catalogAgent(evaluation.agent, at),
|
|
221
|
-
...judgeAgent ? { judgeAgent } : {},
|
|
222
404
|
datasets: (evaluation.datasets ?? []).map((dataset) => catalogDataset(dataset, at)),
|
|
223
405
|
repeat: evaluation.repeat ?? 1,
|
|
224
406
|
policy: {
|
|
225
407
|
...policy.timeoutMs !== void 0 ? { timeoutMs: policy.timeoutMs } : {},
|
|
226
408
|
...policy.failfast !== void 0 ? { failfast: policy.failfast } : {}
|
|
227
409
|
},
|
|
228
|
-
transcript: evaluation.transcript.map((step) => catalogStep(step, at)),
|
|
229
|
-
scorers: evaluation.scoring.map((rule) => catalogScorer(rule, at)),
|
|
410
|
+
transcript: evaluation.transcript.map((step) => catalogStep(step, at, options.judge)),
|
|
411
|
+
scorers: evaluation.scoring.map((rule) => catalogScorer(rule, at, options.judge)),
|
|
230
412
|
...evaluation.metadata !== void 0 ? { metadata: toJson(evaluation.metadata, `${evaluation.id}.metadata`) } : {},
|
|
231
413
|
...definedAt ? { definedAt } : {}
|
|
232
414
|
};
|
|
@@ -270,18 +452,37 @@ function catalogDataset(dataset, at) {
|
|
|
270
452
|
...source.valueFile !== void 0 ? { value: { file: source.valueFile } } : source.value !== void 0 ? { value: { shape: shapeOf(source.value) } } : {}
|
|
271
453
|
};
|
|
272
454
|
}
|
|
273
|
-
function catalogScorer(rule, at) {
|
|
455
|
+
function catalogScorer(rule, at, defaultJudge) {
|
|
274
456
|
const definedAt = at(rule);
|
|
275
457
|
return {
|
|
276
458
|
name: rule.name,
|
|
277
459
|
kind: rule.kind,
|
|
278
|
-
...rule.
|
|
460
|
+
...rule.description ? { description: rule.description } : {},
|
|
461
|
+
...rule.passAt !== void 0 ? { passAt: rule.passAt } : {},
|
|
279
462
|
...rule.supportsPartial !== void 0 ? { supportsPartial: rule.supportsPartial } : {},
|
|
280
|
-
...rule.kind === "judge" ?
|
|
463
|
+
...rule.kind === "judge" ? catalogJudge(rule, at, defaultJudge) : rule.criteria ? { answer: {
|
|
464
|
+
kind: "criteria",
|
|
465
|
+
criteria: rule.criteria
|
|
466
|
+
} } : {},
|
|
281
467
|
...definedAt ? { definedAt } : {}
|
|
282
468
|
};
|
|
283
469
|
}
|
|
284
|
-
function
|
|
470
|
+
function catalogJudge(rule, at, defaultJudge) {
|
|
471
|
+
const agent = rule.agent ?? defaultJudge;
|
|
472
|
+
const inputs = judgeInputNames(rule);
|
|
473
|
+
return {
|
|
474
|
+
rubric: rule.rubric,
|
|
475
|
+
...agent ? { agent: catalogAgent(agent, at) } : {},
|
|
476
|
+
inputs,
|
|
477
|
+
answer: rule.answer,
|
|
478
|
+
prompt: judgePrompt({
|
|
479
|
+
rubric: rule.rubric,
|
|
480
|
+
answer: rule.answer,
|
|
481
|
+
inputs
|
|
482
|
+
})
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function catalogStep(step, at, defaultJudge) {
|
|
285
486
|
switch (step.kind) {
|
|
286
487
|
case "user": return typeof step.message === "string" ? {
|
|
287
488
|
kind: "user",
|
|
@@ -298,7 +499,7 @@ function catalogStep(step, at) {
|
|
|
298
499
|
};
|
|
299
500
|
default: return {
|
|
300
501
|
kind: "checkpoint",
|
|
301
|
-
scorer: catalogScorer(step, at)
|
|
502
|
+
scorer: catalogScorer(step, at, defaultJudge)
|
|
302
503
|
};
|
|
303
504
|
}
|
|
304
505
|
}
|
|
@@ -562,10 +763,11 @@ function turnResultEvents(result, turn, now) {
|
|
|
562
763
|
role: "assistant",
|
|
563
764
|
content: result.reply
|
|
564
765
|
}, now));
|
|
565
|
-
if (result.usage) events.push(normalizeAutEvent({
|
|
766
|
+
if (result.usage || result.model) events.push(normalizeAutEvent({
|
|
566
767
|
kind: "turn-completed",
|
|
567
768
|
turn,
|
|
568
|
-
usage: result.usage
|
|
769
|
+
...result.usage ? { usage: result.usage } : {},
|
|
770
|
+
...result.model ? { model: result.model } : {}
|
|
569
771
|
}, now));
|
|
570
772
|
return events;
|
|
571
773
|
}
|
|
@@ -660,9 +862,32 @@ const JudgeRunInfoSchema = Schema.Struct({
|
|
|
660
862
|
name: Schema.optional(Schema.String),
|
|
661
863
|
version: Schema.optional(Schema.String)
|
|
662
864
|
})),
|
|
865
|
+
model: Schema.optional(Schema.String),
|
|
663
866
|
usage: Schema.optional(UsageSchema),
|
|
867
|
+
prompt: Schema.optional(Schema.String),
|
|
868
|
+
reply: Schema.optional(JsonValueSchema),
|
|
869
|
+
attempts: Schema.optional(Schema.Number),
|
|
664
870
|
events: Schema.optional(Schema.mutable(Schema.Array(JsonValueSchema)))
|
|
665
871
|
});
|
|
872
|
+
const CriterionResultSchema = Schema.Union(Schema.Struct({
|
|
873
|
+
name: Schema.String,
|
|
874
|
+
value: Schema.Number,
|
|
875
|
+
weight: Schema.optional(Schema.Number),
|
|
876
|
+
explanation: Schema.optional(Schema.String),
|
|
877
|
+
evidence: Schema.optional(JsonValueSchema)
|
|
878
|
+
}), Schema.Struct({
|
|
879
|
+
name: Schema.String,
|
|
880
|
+
reached: Schema.Literal(false),
|
|
881
|
+
weight: Schema.optional(Schema.Number),
|
|
882
|
+
explanation: Schema.optional(Schema.String)
|
|
883
|
+
}));
|
|
884
|
+
/** What every score and checkpoint result can carry about its verdict. */
|
|
885
|
+
const verdictFields = {
|
|
886
|
+
passAt: Schema.optional(Schema.Number),
|
|
887
|
+
choice: Schema.optional(Schema.String),
|
|
888
|
+
criteria: Schema.optional(Schema.mutable(Schema.Array(CriterionResultSchema))),
|
|
889
|
+
attachments: Schema.optional(Schema.mutable(Schema.Array(Schema.String)))
|
|
890
|
+
};
|
|
666
891
|
const ScoreResultSchema = Schema.Struct({
|
|
667
892
|
name: Schema.String,
|
|
668
893
|
kind: Schema.Literal("predicate", "judge"),
|
|
@@ -670,6 +895,7 @@ const ScoreResultSchema = Schema.Struct({
|
|
|
670
895
|
passed: Schema.optional(Schema.Boolean),
|
|
671
896
|
explanation: Schema.optional(Schema.String),
|
|
672
897
|
evidence: Schema.optional(JsonValueSchema),
|
|
898
|
+
...verdictFields,
|
|
673
899
|
judge: Schema.optional(JudgeRunInfoSchema),
|
|
674
900
|
durationMs: Schema.Number,
|
|
675
901
|
error: Schema.optional(RecordedErrorSchema),
|
|
@@ -684,6 +910,7 @@ const CheckpointResultSchema = Schema.Struct({
|
|
|
684
910
|
passed: Schema.optional(Schema.Boolean),
|
|
685
911
|
explanation: Schema.optional(Schema.String),
|
|
686
912
|
evidence: Schema.optional(JsonValueSchema),
|
|
913
|
+
...verdictFields,
|
|
687
914
|
judge: Schema.optional(JudgeRunInfoSchema),
|
|
688
915
|
durationMs: Schema.optional(Schema.Number),
|
|
689
916
|
error: Schema.optional(RecordedErrorSchema),
|
|
@@ -768,7 +995,8 @@ const autEvent = {
|
|
|
768
995
|
turn: Schema.Number,
|
|
769
996
|
timestamp: Schema.String,
|
|
770
997
|
latencyMs: Schema.optional(Schema.Number),
|
|
771
|
-
usage: Schema.optional(UsageSchema)
|
|
998
|
+
usage: Schema.optional(UsageSchema),
|
|
999
|
+
model: Schema.optional(Schema.String)
|
|
772
1000
|
}),
|
|
773
1001
|
completed: Schema.Struct({
|
|
774
1002
|
source: Schema.Literal("aut"),
|
|
@@ -938,12 +1166,14 @@ function skip(reason) {
|
|
|
938
1166
|
/** A scorer in code. Its `evidence` type carries through to `checkpoint(rule)`. */
|
|
939
1167
|
function predicate(name, run, options = {}) {
|
|
940
1168
|
if (!name.trim()) throw new Error("Predicate name must not be empty");
|
|
941
|
-
|
|
1169
|
+
assertPassAt(`Predicate ${name}`, options.passAt);
|
|
1170
|
+
const { criteria: declared, ...rest } = options;
|
|
942
1171
|
return markDefinitionSite({
|
|
943
1172
|
kind: "predicate",
|
|
944
1173
|
name,
|
|
945
1174
|
run,
|
|
946
|
-
...
|
|
1175
|
+
...rest,
|
|
1176
|
+
...declared ? { criteria: declaredCriteria(Object.fromEntries(Object.entries(declared).map(([key, option]) => [key, typeof option === "string" ? { description: option } : option]))) } : {}
|
|
947
1177
|
});
|
|
948
1178
|
}
|
|
949
1179
|
/** A built-in metric's name is a type error here, as well as at definition time. */
|
|
@@ -956,14 +1186,23 @@ function metric(name, run, options = {}) {
|
|
|
956
1186
|
...options
|
|
957
1187
|
});
|
|
958
1188
|
}
|
|
1189
|
+
const INPUT_NAME = /^[a-z][a-zA-Z0-9_-]{0,31}$/;
|
|
959
1190
|
function judge(name, options) {
|
|
960
|
-
if (!name.trim() || !options.rubric
|
|
1191
|
+
if (!name.trim() || !options.rubric?.trim()) throw new Error("Judge name and rubric must not be empty");
|
|
1192
|
+
assertPassAt(`Judge ${name}`, options.passAt);
|
|
1193
|
+
for (const input of Object.keys(options.inputs ?? {})) if (!INPUT_NAME.test(input)) throw new Error(`Judge ${name} input ${JSON.stringify(input)} must start with a lowercase letter and use letters, digits, "-" or "_"`);
|
|
1194
|
+
if (options.inputs && !Object.keys(options.inputs).length) throw new Error(`Judge ${name} inputs must name at least one input`);
|
|
1195
|
+
if (options.agent && typeof options.agent.start !== "function") throw new Error(`Judge ${name} agent must be made with defineAgent()`);
|
|
961
1196
|
return markDefinitionSite({
|
|
962
1197
|
kind: "judge",
|
|
963
1198
|
name,
|
|
964
|
-
...options
|
|
1199
|
+
...options,
|
|
1200
|
+
answer: options.answer ?? { kind: "verdict" }
|
|
965
1201
|
});
|
|
966
1202
|
}
|
|
1203
|
+
function assertPassAt(owner, passAt) {
|
|
1204
|
+
if (passAt !== void 0 && !(Number.isFinite(passAt) && passAt >= 0 && passAt <= 1)) throw new Error(`${owner} passAt must be a number from 0 to 1; received ${passAt}`);
|
|
1205
|
+
}
|
|
967
1206
|
function defineEval(definition) {
|
|
968
1207
|
authoringId(definition);
|
|
969
1208
|
assertNoRemovedOptions(definition);
|
|
@@ -984,10 +1223,13 @@ function defineEval(definition) {
|
|
|
984
1223
|
let hasUser = false;
|
|
985
1224
|
for (const step of definition.transcript) if (step.kind === "user") hasUser = true;
|
|
986
1225
|
else if (step.kind !== "agent" && !hasUser) throw new Error("Checkpoint requires a preceding user step");
|
|
987
|
-
|
|
988
|
-
if (definition.judge && definition.judge === definition.agent) throw new Error("Judge agent must be distinct from the agent under test");
|
|
1226
|
+
assertJudgesIndependent(definition);
|
|
989
1227
|
return markDefinitionSite(definition);
|
|
990
1228
|
}
|
|
1229
|
+
/** A judge never grades with the agent it is judging. */
|
|
1230
|
+
function assertJudgesIndependent(definition, defaultJudge) {
|
|
1231
|
+
for (const rule of [...definition.transcript, ...definition.scoring]) if (rule.kind === "judge" && (rule.agent ?? defaultJudge) === definition.agent) throw new Error(`Judge ${rule.name} in ${definition.id} uses the agent under test; a judge needs its own agent`);
|
|
1232
|
+
}
|
|
991
1233
|
function defineSuite(suite) {
|
|
992
1234
|
authoringId(suite);
|
|
993
1235
|
return suite;
|
|
@@ -1070,7 +1312,8 @@ function registerEvals(registrations) {
|
|
|
1070
1312
|
const BUILT_IN_METRICS = [
|
|
1071
1313
|
"agent time",
|
|
1072
1314
|
"tokens",
|
|
1073
|
-
"tool calls"
|
|
1315
|
+
"tool calls",
|
|
1316
|
+
"judge tokens"
|
|
1074
1317
|
];
|
|
1075
1318
|
function recordError(error) {
|
|
1076
1319
|
if (error instanceof Error) return {
|
|
@@ -1084,4 +1327,4 @@ function recordError(error) {
|
|
|
1084
1327
|
};
|
|
1085
1328
|
}
|
|
1086
1329
|
//#endregion
|
|
1087
|
-
export {
|
|
1330
|
+
export { judgeInputNames as $, RunDatasetSchema as A, normalizeAutEvent as B, BatchSchema as C, MetricResultSchema as D, JudgeRunInfoSchema as E, RunStatusSchema as F, DEFAULT_ARTIFACT_EXCLUDE as G, defineEvalMatrix as H, RunSummarySchema as I, catalogEntry as J, DEFAULT_ATTACHMENT_LIMITS as K, ScoreResultSchema as L, RunMetadataSchema as M, RunScoringSchema as N, REPORT_SCHEMA_VERSION as O, RunSourceSchema as P, criteria as Q, TrajectoryEventSchema as R, AutIdentitySchema as S, JsonValueSchema as T, canonicalParameters as U, turnResultEvents as V, turnTokens as W, answerInstruction as X, UnreadableAnswer as Y, choices as Z, user as _, resourceUriSchema as _t, assertJudgesIndependent as a, toJson as at, ArtifactEntrySchema as b, defineEval as c, isDataset as ct, judge as d, ResourceKindSchema as dt, judgePrompt as et, metric as f, ResourceUriSchema as ft, skip as g, resourceUri as gt, registerEvals as h, parseResourceUri as ht, agentRuntimes as i, NotJsonError as it, RunManifestSchema as j, RecordedErrorSchema as k, defineSuite as l, relativeDatasetPath as lt, recordError as m, authorId as mt, DEFAULT_AGENT_RUNTIMES as n, readJudgeAnswer as nt, authoringId as o, toJsonObject as ot, predicate as p, UuidSchema as pt, defineConfig as q, agent as r, weightedMean as rt, defineAgent as s, defineDataset as st, BUILT_IN_METRICS as t, passMark as tt, expectToolCall as u, definitionSite as ut, parseTrajectoryJsonl as v, resourceUuid as vt, CheckpointResultSchema as w, AttachmentEntrySchema as x, ApiErrorSchema as y, UsageSchema as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as asyncIndex, r as createShareApp, t as ShareIndexDb } from "./index-db-
|
|
1
|
+
import { n as asyncIndex, r as createShareApp, t as ShareIndexDb } from "./index-db-B1FeXzmS.mjs";
|
|
2
2
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
//#region ../share/src/fs-files.ts
|