@anvia/core 0.23.0 → 0.25.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.
@@ -1,16 +1,22 @@
1
1
  import {
2
2
  ExtractorBuilder
3
- } from "../chunk-YLMDBQO4.js";
4
- import "../chunk-JI5YZWNT.js";
5
- import "../chunk-DALYP4PX.js";
6
- import "../chunk-BALBBVI3.js";
3
+ } from "../chunk-KYEBWN3E.js";
4
+ import "../chunk-4HWN7734.js";
5
+ import "../chunk-YEBIVZM2.js";
6
+ import "../chunk-VXONXP2G.js";
7
7
  import "../chunk-YK4WAAS4.js";
8
8
  import "../chunk-XUUY2L2D.js";
9
- import "../chunk-HYUYZEAJ.js";
10
- import "../chunk-MELYDKWQ.js";
9
+ import "../chunk-2NDQHV7V.js";
10
+ import "../chunk-4BGN6PYF.js";
11
11
  import "../chunk-2ODTMRHP.js";
12
12
  import "../chunk-UQX6EXWG.js";
13
13
  import "../chunk-MRQLEK2B.js";
14
+ import "../chunk-Q25IWLBT.js";
15
+ import "../chunk-MC3CCKEB.js";
16
+ import {
17
+ Usage
18
+ } from "../chunk-ADH7NNCS.js";
19
+ import "../chunk-WQKHFADH.js";
14
20
  import {
15
21
  cosineSimilarity,
16
22
  embedText
@@ -18,12 +24,6 @@ import {
18
24
  import {
19
25
  mapWithConcurrency
20
26
  } from "../chunk-OIMLU4SF.js";
21
- import "../chunk-Q25IWLBT.js";
22
- import "../chunk-MC3CCKEB.js";
23
- import {
24
- Usage
25
- } from "../chunk-ADH7NNCS.js";
26
- import "../chunk-WQKHFADH.js";
27
27
  import "../chunk-CWUJUSOS.js";
28
28
 
29
29
  // src/evals/advanced-metrics.ts
@@ -100,6 +100,9 @@ var EvalOutcome = {
100
100
  if (options.metadata !== void 0) {
101
101
  outcome.metadata = options.metadata;
102
102
  }
103
+ if (options.usage !== void 0) {
104
+ outcome.usage = options.usage;
105
+ }
103
106
  return outcome;
104
107
  },
105
108
  fail(score, options = {}) {
@@ -115,6 +118,9 @@ var EvalOutcome = {
115
118
  if (options.metadata !== void 0) {
116
119
  outcome.metadata = options.metadata;
117
120
  }
121
+ if (options.usage !== void 0) {
122
+ outcome.usage = options.usage;
123
+ }
118
124
  return outcome;
119
125
  },
120
126
  invalid(reason, options = {}) {
@@ -131,11 +137,20 @@ var EvalOutcome = {
131
137
  if (options.metadata !== void 0) {
132
138
  outcome.metadata = options.metadata;
133
139
  }
140
+ if (options.usage !== void 0) {
141
+ outcome.usage = options.usage;
142
+ }
134
143
  return outcome;
135
144
  }
136
145
  };
137
146
 
138
147
  // src/evals/selectors.ts
148
+ function selectPromptOutput(args) {
149
+ if (typeof args.output !== "object" || args.output === null || !("output" in args.output) || typeof args.output.output !== "string") {
150
+ throw new TypeError("selectPromptOutput requires an output object with a string output field.");
151
+ }
152
+ return args.output.output;
153
+ }
139
154
  async function resolveActual(selector, args) {
140
155
  return selector === void 0 ? defaultOutputValue(args.output) : selector(args);
141
156
  }
@@ -188,9 +203,14 @@ var binaryVerdictSchema = z.object({
188
203
  reason: z.string()
189
204
  });
190
205
  var reasonSchema = z.object({ reason: z.string() });
206
+ var abstentionJudgmentSchema = z.object({
207
+ behavior: z.enum(["abstention", "confident_answer"]),
208
+ grounded: z.boolean(),
209
+ reason: z.string()
210
+ });
191
211
  function answerRelevancy(options) {
192
212
  const config = metricConfig(options, "answer_relevancy");
193
- return numericMetric(config.name, async (args) => {
213
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
194
214
  try {
195
215
  const input = await resolveInput(options.input, args);
196
216
  const actual = await resolveActualText(options.actual, args);
@@ -245,7 +265,7 @@ function promptAlignment(options) {
245
265
  throw new TypeError("promptAlignment requires at least one prompt instruction.");
246
266
  }
247
267
  const config = metricConfig(options, "prompt_alignment");
248
- return numericMetric(config.name, async (args) => {
268
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
249
269
  try {
250
270
  const input = await resolveInput(options.input, args);
251
271
  const actual = await resolveActualText(options.actual, args);
@@ -285,56 +305,65 @@ function jsonCorrectness(options) {
285
305
  const threshold = validateThreshold(options.threshold ?? 0.5);
286
306
  const retries = validateRetries(options.retries ?? 0);
287
307
  const includeReason = options.includeReason ?? true;
288
- return numericMetric(options.name ?? "json_correctness", async (args) => {
289
- try {
290
- const actual = await resolveActualText(options.actual, args);
291
- let parsed;
292
- let validationError;
308
+ const strictMode = options.strictMode ?? true;
309
+ return numericMetric(
310
+ options.name ?? "json_correctness",
311
+ {
312
+ threshold: strictMode ? 1 : threshold,
313
+ required: options.required ?? true
314
+ },
315
+ "higher_is_better",
316
+ async (args) => {
293
317
  try {
294
- parsed = JSON.parse(actual);
295
- const result = options.schema.safeParse(parsed);
296
- if (!result.success) {
297
- validationError = z.prettifyError(result.error);
318
+ const actual = await resolveActualText(options.actual, args);
319
+ let parsed;
320
+ let validationError;
321
+ try {
322
+ parsed = JSON.parse(actual);
323
+ const result = options.schema.safeParse(parsed);
324
+ if (!result.success) {
325
+ validationError = z.prettifyError(result.error);
326
+ }
327
+ } catch (error) {
328
+ validationError = errorMessage(error);
298
329
  }
299
- } catch (error) {
300
- validationError = errorMessage(error);
301
- }
302
- const score = validationError === void 0 ? 1 : 0;
303
- let comment;
304
- let usage = Usage.empty();
305
- if (includeReason) {
306
- if (score === 1) {
307
- comment = "The generated JSON is syntactically valid and matches the expected schema.";
308
- } else if (options.model === void 0) {
309
- comment = validationError;
310
- } else {
311
- const reasonResult = await runJudge({
312
- model: options.model,
313
- schema: reasonSchema,
314
- instructions: "Briefly explain why the generated JSON does not match the expected schema. Focus on actionable syntax, field, and type problems.",
315
- prompt: jsonPrompt({ actual, validationError }),
316
- retries
317
- });
318
- comment = reasonResult.data.reason;
319
- usage = reasonResult.usage;
330
+ const score = validationError === void 0 ? 1 : 0;
331
+ let comment;
332
+ let usage = Usage.empty();
333
+ if (includeReason) {
334
+ if (score === 1) {
335
+ comment = "The generated JSON is syntactically valid and matches the expected schema.";
336
+ } else if (options.model === void 0) {
337
+ comment = validationError;
338
+ } else {
339
+ const reasonResult = await runJudge({
340
+ model: options.model,
341
+ schema: reasonSchema,
342
+ instructions: "Briefly explain why the generated JSON does not match the expected schema. Focus on actionable syntax, field, and type problems.",
343
+ prompt: jsonPrompt({ actual, validationError }),
344
+ retries
345
+ });
346
+ comment = reasonResult.data.reason;
347
+ usage = reasonResult.usage;
348
+ }
320
349
  }
350
+ return higherOutcome({
351
+ score,
352
+ threshold,
353
+ strictMode,
354
+ comment,
355
+ details: validationError === void 0 ? {} : { validationError },
356
+ usage
357
+ });
358
+ } catch (error) {
359
+ return EvalOutcome.invalid(errorMessage(error));
321
360
  }
322
- return higherOutcome({
323
- score,
324
- threshold,
325
- strictMode: options.strictMode ?? true,
326
- comment,
327
- details: validationError === void 0 ? {} : { validationError },
328
- usage
329
- });
330
- } catch (error) {
331
- return EvalOutcome.invalid(errorMessage(error));
332
361
  }
333
- });
362
+ );
334
363
  }
335
364
  function hallucination(options) {
336
365
  const config = metricConfig(options, "hallucination");
337
- return numericMetric(config.name, async (args) => {
366
+ return numericMetric(config.name, config, "lower_is_better", async (args) => {
338
367
  try {
339
368
  const actual = await resolveActualText(options.actual, args);
340
369
  const context = await resolveStringList(options.context, args.case.context, args, "context");
@@ -376,7 +405,7 @@ function faithfulness(options) {
376
405
  options.truthsExtractionLimit,
377
406
  "truthsExtractionLimit"
378
407
  );
379
- return numericMetric(config.name, async (args) => {
408
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
380
409
  try {
381
410
  const actual = await resolveActualText(options.actual, args);
382
411
  const retrievalContext = await resolveStringList(
@@ -445,6 +474,70 @@ ${actual}`,
445
474
  }
446
475
  });
447
476
  }
477
+ function abstention(options) {
478
+ const retries = validateRetries(options.retries ?? 0);
479
+ return {
480
+ name: options.name ?? "abstention",
481
+ required: options.required ?? true,
482
+ dataType: "CATEGORICAL",
483
+ async evaluate(args) {
484
+ try {
485
+ const actual = await resolveActualText(options.actual, args);
486
+ const shouldAbstain = await resolveExpected(options.shouldAbstain, args);
487
+ if (typeof shouldAbstain !== "boolean") {
488
+ return EvalOutcome.invalid("abstention shouldAbstain must resolve to a boolean.");
489
+ }
490
+ const context = await resolveAbstentionContext(options.context, args);
491
+ if (!shouldAbstain && context.length === 0) {
492
+ return EvalOutcome.invalid(
493
+ "abstention context must be non-empty when shouldAbstain is false."
494
+ );
495
+ }
496
+ const judgment = await runJudge({
497
+ model: options.model,
498
+ schema: abstentionJudgmentSchema,
499
+ instructions: "Classify whether the answer abstains or gives a confident answer. For a confident answer, grounded is true only when every substantive factual claim is supported by the supplied context. For an abstention, set grounded to false. Return a concise evidence-based reason.",
500
+ prompt: jsonPrompt({ actual, context }),
501
+ retries
502
+ });
503
+ const category = abstentionCategory(
504
+ shouldAbstain,
505
+ judgment.data.behavior,
506
+ judgment.data.grounded
507
+ );
508
+ const outcomeOptions = {
509
+ comment: options.includeReason === false ? void 0 : judgment.data.reason,
510
+ metadata: evaluationMetadata(
511
+ {
512
+ behavior: judgment.data.behavior,
513
+ grounded: judgment.data.grounded,
514
+ shouldAbstain
515
+ },
516
+ judgment.usage
517
+ ),
518
+ usage: judgment.usage
519
+ };
520
+ return category === "correct_abstention" || category === "correct_grounded_answer" ? EvalOutcome.pass(category, outcomeOptions) : EvalOutcome.fail(category, outcomeOptions);
521
+ } catch (error) {
522
+ return EvalOutcome.invalid(errorMessage(error));
523
+ }
524
+ }
525
+ };
526
+ }
527
+ async function resolveAbstentionContext(selectorOrValue, args) {
528
+ const context = selectorOrValue === void 0 ? args.case.retrievalContext ?? [] : typeof selectorOrValue === "function" ? await selectorOrValue(args) : selectorOrValue;
529
+ if (!Array.isArray(context) || context.some((value) => typeof value !== "string")) {
530
+ throw new TypeError("abstention context must be an array of strings.");
531
+ }
532
+ return context;
533
+ }
534
+ function abstentionCategory(shouldAbstain, behavior, grounded) {
535
+ if (behavior === "abstention") {
536
+ return shouldAbstain ? "correct_abstention" : "unnecessary_abstention";
537
+ }
538
+ if (shouldAbstain || !grounded) return "unsupported_confident_answer";
539
+ return "correct_grounded_answer";
540
+ }
448
541
  function summarization(options) {
449
542
  const config = metricConfig(options, "summarization");
450
543
  const questionCount = validatePositiveInteger(options.questionCount ?? 5, "questionCount");
@@ -453,7 +546,7 @@ function summarization(options) {
453
546
  "truthsExtractionLimit"
454
547
  );
455
548
  const suppliedQuestions = options.assessmentQuestions !== void 0 && options.assessmentQuestions.length > 0 ? [...options.assessmentQuestions] : void 0;
456
- return numericMetric(config.name, async (args) => {
549
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
457
550
  try {
458
551
  const input = await resolveInput(options.input, args);
459
552
  const actual = await resolveActualText(options.actual, args);
@@ -615,7 +708,7 @@ function gEval(options) {
615
708
  generatedUsageClaimed = true;
616
709
  return { steps: result.data.steps, usage };
617
710
  }
618
- return numericMetric(config.name, async (args) => {
711
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
619
712
  try {
620
713
  const parameters = await resolveGEvalParameters(options, args);
621
714
  const stepsResult = await resolveSteps();
@@ -662,7 +755,7 @@ function turnRelevancy(options) {
662
755
  const config = metricConfig(options, "turn_relevancy");
663
756
  const windowSize = validatePositiveInteger(options.windowSize ?? 10, "windowSize");
664
757
  const concurrency = validatePositiveInteger(options.concurrency ?? 4, "concurrency");
665
- return numericMetric(config.name, async (args) => {
758
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
666
759
  try {
667
760
  const turns = await resolveTurns(options.turns, args);
668
761
  const interactions = unitInteractions(turns);
@@ -710,7 +803,7 @@ function turnRelevancy(options) {
710
803
  function knowledgeRetention(options) {
711
804
  const config = metricConfig(options, "knowledge_retention");
712
805
  const concurrency = validatePositiveInteger(options.concurrency ?? 4, "concurrency");
713
- return numericMetric(config.name, async (args) => {
806
+ return numericMetric(config.name, config, "higher_is_better", async (args) => {
714
807
  try {
715
808
  const turns = await resolveTurns(options.turns, args);
716
809
  const userTurns = turns.map((turn, index) => ({ turn, index })).filter((entry) => entry.turn.role === "user");
@@ -779,8 +872,15 @@ function knowledgeRetention(options) {
779
872
  }
780
873
  });
781
874
  }
782
- function numericMetric(name, evaluate) {
783
- return { name, dataType: "NUMERIC", evaluate };
875
+ function numericMetric(name, config, direction, evaluate) {
876
+ return {
877
+ name,
878
+ required: config.required,
879
+ direction,
880
+ threshold: config.strictMode === true ? direction === "higher_is_better" ? 1 : 0 : config.threshold,
881
+ dataType: "NUMERIC",
882
+ evaluate
883
+ };
784
884
  }
785
885
  function metricConfig(options, defaultName) {
786
886
  return {
@@ -788,7 +888,8 @@ function metricConfig(options, defaultName) {
788
888
  threshold: validateThreshold(options.threshold ?? 0.5),
789
889
  strictMode: options.strictMode ?? false,
790
890
  includeReason: options.includeReason ?? true,
791
- retries: validateRetries(options.retries ?? 0)
891
+ retries: validateRetries(options.retries ?? 0),
892
+ required: options.required ?? true
792
893
  };
793
894
  }
794
895
  function higherOutcome(args) {
@@ -796,6 +897,7 @@ function higherOutcome(args) {
796
897
  const threshold = args.strictMode ? 1 : args.threshold;
797
898
  const options = {
798
899
  comment: args.comment,
900
+ usage: args.usage,
799
901
  metadata: evaluationMetadata(
800
902
  {
801
903
  ...args.details,
@@ -813,6 +915,7 @@ function lowerOutcome(args) {
813
915
  const threshold = args.strictMode ? 0 : args.threshold;
814
916
  const options = {
815
917
  comment: args.comment,
918
+ usage: args.usage,
816
919
  metadata: evaluationMetadata(
817
920
  {
818
921
  ...args.details,
@@ -1021,122 +1124,9 @@ function agentEvalTarget(agent, options = {}) {
1021
1124
  };
1022
1125
  }
1023
1126
 
1024
- // src/evals/metric.ts
1025
- function defineMetric(metric) {
1026
- return metric;
1027
- }
1028
-
1029
- // src/evals/metrics.ts
1030
- import { z as z2 } from "zod";
1031
- function exactMatch(options = {}) {
1032
- return {
1033
- name: options.name ?? "exact_match",
1034
- async evaluate(args) {
1035
- const actual = await resolveActual(options.actual, args);
1036
- const expected = await resolveExpected(options.expected, args);
1037
- if (expected === void 0) {
1038
- return EvalOutcome.invalid("No expected value provided for exact match.");
1039
- }
1040
- const passed = stableComparable(actual) === stableComparable(expected);
1041
- return passed ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Expected ${formatValue(expected)}.` });
1042
- }
1043
- };
1044
- }
1045
- function contains(options = {}) {
1046
- return {
1047
- name: options.name ?? "contains",
1048
- async evaluate(args) {
1049
- const actual = await resolveActualText(options.actual, args);
1050
- const expected = await resolveExpected(options.expected, args);
1051
- if (expected === void 0) {
1052
- return EvalOutcome.invalid("No expected value provided for contains.");
1053
- }
1054
- if (typeof expected !== "string" && !(expected instanceof RegExp)) {
1055
- return EvalOutcome.invalid("Contains expected value must be a string or RegExp.");
1056
- }
1057
- const passed = expected instanceof RegExp ? regexMatches(expected, actual) : actual.includes(expected);
1058
- return passed ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Output did not contain ${String(expected)}.` });
1059
- }
1060
- };
1061
- }
1062
- function regexMatches(pattern, text) {
1063
- pattern.lastIndex = 0;
1064
- const matched = pattern.test(text);
1065
- pattern.lastIndex = 0;
1066
- return matched;
1067
- }
1068
- function semanticSimilarity(options) {
1069
- return {
1070
- name: options.name ?? "semantic_similarity",
1071
- async evaluate(args) {
1072
- const actual = await resolveActualText(options.actual, args);
1073
- const expected = await resolveExpected(options.expected, args);
1074
- if (expected === void 0) {
1075
- return EvalOutcome.invalid("No expected value provided for semantic similarity.");
1076
- }
1077
- if (typeof expected !== "string") {
1078
- return EvalOutcome.invalid("Semantic similarity expected value must be a string.");
1079
- }
1080
- const [actualEmbedding, expectedEmbedding] = await Promise.all([
1081
- embedText(options.model, actual),
1082
- embedText(options.model, expected)
1083
- ]);
1084
- const score = cosineSimilarity(actualEmbedding.vector, expectedEmbedding.vector);
1085
- return score >= options.threshold ? EvalOutcome.pass(score) : EvalOutcome.fail(score, { comment: `Similarity below threshold ${options.threshold}.` });
1086
- }
1087
- };
1088
- }
1089
- function llmJudge(options) {
1090
- const extractor = new ExtractorBuilder(options.model, options.schema).instructions(
1091
- options.instructions ?? "Judge the eval case by the requested schema. Submit the judgment using the schema."
1092
- ).retries(options.retries ?? 0).build();
1093
- return {
1094
- name: options.name ?? "llm_judge",
1095
- async evaluate(args) {
1096
- try {
1097
- const judgment = await extractor.extract(await resolveJudgePrompt(options.prompt, args));
1098
- return options.passes(judgment) ? EvalOutcome.pass(judgment) : EvalOutcome.fail(judgment);
1099
- } catch (error) {
1100
- return EvalOutcome.invalid(errorMessage(error));
1101
- }
1102
- }
1103
- };
1104
- }
1105
- function llmScore(options) {
1106
- const criteria = Array.isArray(options.criteria) ? options.criteria.join("\n") : options.criteria;
1107
- const extractor = new ExtractorBuilder(
1108
- options.model,
1109
- z2.object({
1110
- score: z2.number(),
1111
- feedback: z2.string()
1112
- })
1113
- ).instructions(
1114
- options.instructions ?? `Score the eval case against these criteria:
1115
- ${criteria}
1116
-
1117
- Return a score between 0 and 1 and brief feedback.`
1118
- ).retries(options.retries ?? 0).build();
1119
- return {
1120
- name: options.name ?? "llm_score",
1121
- async evaluate(args) {
1122
- try {
1123
- const score = await extractor.extract(await resolveJudgePrompt(options.prompt, args));
1124
- if (score.score < 0 || score.score > 1) {
1125
- return EvalOutcome.invalid(`Score ${score.score} outside valid range [0, 1].`, {
1126
- score
1127
- });
1128
- }
1129
- return score.score >= options.threshold ? EvalOutcome.pass(score, { comment: score.feedback }) : EvalOutcome.fail(score, { comment: score.feedback });
1130
- } catch (error) {
1131
- return EvalOutcome.invalid(errorMessage(error));
1132
- }
1133
- }
1134
- };
1135
- }
1136
-
1137
1127
  // src/evals/reporting.ts
1138
- function projectEvalOutcome(outcome, dataType) {
1139
- const value = projectScoreValue(outcome, dataType);
1128
+ function projectEvalOutcome(outcome, dataType, projectScore) {
1129
+ const value = projectScoreValue(outcome, dataType, projectScore);
1140
1130
  const projection = {
1141
1131
  outcome: outcome.outcome,
1142
1132
  value,
@@ -1158,8 +1148,13 @@ function defaultEvalTraceSelector(args) {
1158
1148
  metadata: args.case.metadata
1159
1149
  });
1160
1150
  }
1161
- function projectScoreValue(outcome, dataType) {
1151
+ function projectScoreValue(outcome, dataType, projectScore) {
1162
1152
  const score = outcome.score;
1153
+ if (score !== void 0 && projectScore !== void 0) {
1154
+ const projected = projectScore(score);
1155
+ if (typeof projected === "boolean") return projected ? 1 : 0;
1156
+ return projected;
1157
+ }
1163
1158
  if (dataType === "CATEGORICAL") {
1164
1159
  if (typeof score === "string") return score;
1165
1160
  if (typeof score === "number") return String(score);
@@ -1211,21 +1206,105 @@ function readTraceRef(value) {
1211
1206
 
1212
1207
  // src/evals/runner.ts
1213
1208
  async function runEvalSuite(options) {
1214
- const startedAt = Date.now();
1215
- const results = await mapWithConcurrency(
1216
- options.cases,
1217
- Math.max(1, Math.trunc(options.concurrency ?? 1)),
1218
- (testCase) => runEvalCase(options, testCase)
1219
- );
1220
- const counts = countOutcomes(results);
1221
- return {
1209
+ validateSuiteOptions(options);
1210
+ const startedAtMs = Date.now();
1211
+ const run = resolveRun(options, startedAtMs);
1212
+ const reporters = options.reporters ?? [];
1213
+ const lifecycle = {
1214
+ run,
1215
+ suiteName: options.name,
1216
+ caseCount: options.cases.length,
1217
+ metricNames: options.metrics.map((metric) => metric.name)
1218
+ };
1219
+ let reporterErrors;
1220
+ try {
1221
+ reporterErrors = await notifyRunStart(
1222
+ reporters,
1223
+ lifecycle,
1224
+ options.failOnReporterError === true
1225
+ );
1226
+ } catch (error) {
1227
+ await notifyRunEnd(reporters, {
1228
+ ...lifecycle,
1229
+ status: "failed",
1230
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1231
+ durationMs: Date.now() - startedAtMs,
1232
+ error
1233
+ });
1234
+ throw error;
1235
+ }
1236
+ let results;
1237
+ let aggregates;
1238
+ try {
1239
+ results = await runEvalCases(options, run);
1240
+ aggregates = await aggregateResult(options, results);
1241
+ } catch (error) {
1242
+ await notifyRunEnd(reporters, {
1243
+ ...lifecycle,
1244
+ status: "failed",
1245
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1246
+ durationMs: Date.now() - startedAtMs,
1247
+ error
1248
+ });
1249
+ throw error;
1250
+ }
1251
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
1252
+ const result = {
1222
1253
  name: options.name,
1254
+ run: { ...run, completedAt },
1223
1255
  results,
1224
- ...counts,
1225
- durationMs: Date.now() - startedAt
1256
+ metrics: aggregates.metrics,
1257
+ cases: aggregates.cases,
1258
+ usage: aggregates.usage,
1259
+ ...aggregates.cost === void 0 ? {} : { cost: aggregates.cost },
1260
+ durationMs: Date.now() - startedAtMs,
1261
+ reporterErrors
1226
1262
  };
1263
+ result.reporterErrors.push(
1264
+ ...await notifyRunEnd(
1265
+ reporters,
1266
+ {
1267
+ ...lifecycle,
1268
+ status: "completed",
1269
+ completedAt,
1270
+ durationMs: result.durationMs,
1271
+ metrics: result.metrics,
1272
+ cases: result.cases,
1273
+ usage: result.usage,
1274
+ ...result.cost === void 0 ? {} : { cost: result.cost }
1275
+ },
1276
+ options.failOnReporterError === true
1277
+ )
1278
+ );
1279
+ return result;
1280
+ }
1281
+ async function runEvalCases(options, run) {
1282
+ const concurrency = Math.max(1, Math.trunc(options.concurrency ?? 1));
1283
+ const results = new Array(options.cases.length);
1284
+ let nextIndex = 0;
1285
+ let failure;
1286
+ async function worker() {
1287
+ while (failure === void 0 && nextIndex < options.cases.length) {
1288
+ const index = nextIndex;
1289
+ nextIndex += 1;
1290
+ try {
1291
+ results[index] = await runEvalCase(
1292
+ options,
1293
+ options.cases[index],
1294
+ run
1295
+ );
1296
+ } catch (error) {
1297
+ failure ??= { error };
1298
+ }
1299
+ }
1300
+ }
1301
+ await Promise.all(
1302
+ Array.from({ length: Math.min(concurrency, options.cases.length) }, () => worker())
1303
+ );
1304
+ if (failure !== void 0) throw failure.error;
1305
+ return results;
1227
1306
  }
1228
- async function runEvalCase(options, testCase) {
1307
+ async function runEvalCase(options, testCase, run) {
1229
1308
  let output;
1230
1309
  let targetError;
1231
1310
  try {
@@ -1238,6 +1317,7 @@ async function runEvalCase(options, testCase) {
1238
1317
  for (const metric of options.metrics) {
1239
1318
  const outcome = targetError === void 0 ? await safeEvaluate(options.name, testCase, output, metric) : EvalOutcome.invalid(`Target failed: ${errorMessage(targetError)}`);
1240
1319
  const reporterErrors = await reportOutcome({
1320
+ run,
1241
1321
  suiteName: options.name,
1242
1322
  testCase,
1243
1323
  output,
@@ -1249,11 +1329,24 @@ async function runEvalCase(options, testCase) {
1249
1329
  reporters: options.reporters ?? [],
1250
1330
  failOnReporterError: options.failOnReporterError === true
1251
1331
  });
1252
- metrics.push({ metricName: metric.name, outcome, reporterErrors });
1332
+ const metricResult = {
1333
+ metricName: metric.name,
1334
+ required: metric.required ?? true,
1335
+ outcome,
1336
+ reporterErrors
1337
+ };
1338
+ if (metric.direction !== void 0) metricResult.direction = metric.direction;
1339
+ if (metric.threshold !== void 0) metricResult.threshold = metric.threshold;
1340
+ metrics.push(metricResult);
1253
1341
  }
1342
+ const scores = Object.fromEntries(
1343
+ metrics.map((metric) => [metric.metricName, metric.outcome])
1344
+ );
1254
1345
  const result = {
1255
1346
  case: testCase,
1256
- metrics
1347
+ outcome: caseOutcome(targetError, metrics),
1348
+ metrics,
1349
+ scores
1257
1350
  };
1258
1351
  if (output !== void 0) {
1259
1352
  result.output = output;
@@ -1294,6 +1387,7 @@ async function reportOutcome(args) {
1294
1387
  for (const reporter of args.reporters) {
1295
1388
  try {
1296
1389
  await reporter.report({
1390
+ run: args.run,
1297
1391
  suiteName: args.suiteName,
1298
1392
  case: args.testCase,
1299
1393
  output: args.output,
@@ -1311,26 +1405,695 @@ async function reportOutcome(args) {
1311
1405
  }
1312
1406
  return errors;
1313
1407
  }
1314
- function countOutcomes(results) {
1315
- let passed = 0;
1316
- let failed = 0;
1317
- let invalid = 0;
1408
+ function resolveRun(options, startedAtMs) {
1409
+ const id = options.run?.id ?? globalThis.crypto.randomUUID();
1410
+ if (id.trim().length === 0 || id.length > 128) {
1411
+ throw new TypeError("Evaluation run id must contain 1 to 128 characters");
1412
+ }
1413
+ for (const [label, value] of [
1414
+ ["dataset name", options.run?.datasetName],
1415
+ ["dataset version", options.run?.datasetVersion]
1416
+ ]) {
1417
+ if (value !== void 0 && (value.trim().length === 0 || value.length > 256)) {
1418
+ throw new TypeError(`Evaluation run ${label} must contain 1 to 256 characters`);
1419
+ }
1420
+ }
1421
+ return {
1422
+ id,
1423
+ startedAt: new Date(startedAtMs).toISOString(),
1424
+ ...options.run?.datasetName === void 0 ? {} : { datasetName: options.run.datasetName },
1425
+ ...options.run?.datasetVersion === void 0 ? {} : { datasetVersion: options.run.datasetVersion },
1426
+ ...options.run?.metadata === void 0 ? {} : { metadata: options.run.metadata }
1427
+ };
1428
+ }
1429
+ async function notifyRunStart(reporters, args, failOnReporterError) {
1430
+ const errors = [];
1431
+ for (const reporter of reporters) {
1432
+ if (reporter.onRunStart === void 0) continue;
1433
+ try {
1434
+ await reporter.onRunStart(args);
1435
+ } catch (error) {
1436
+ if (failOnReporterError) throw error;
1437
+ errors.push(error);
1438
+ }
1439
+ }
1440
+ return errors;
1441
+ }
1442
+ async function notifyRunEnd(reporters, args, failOnReporterError = false) {
1443
+ const errors = [];
1444
+ for (const reporter of reporters) {
1445
+ if (reporter.onRunEnd === void 0) continue;
1446
+ try {
1447
+ await reporter.onRunEnd(args);
1448
+ } catch (error) {
1449
+ if (failOnReporterError) throw error;
1450
+ errors.push(error);
1451
+ }
1452
+ }
1453
+ return errors;
1454
+ }
1455
+ function countMetricOutcomes(results) {
1456
+ const totals = emptyTotals();
1318
1457
  for (const result of results) {
1319
1458
  for (const metric of result.metrics) {
1320
- if (metric.outcome.outcome === "pass") passed += 1;
1321
- if (metric.outcome.outcome === "fail") failed += 1;
1322
- if (metric.outcome.outcome === "invalid") invalid += 1;
1459
+ totals.total += 1;
1460
+ totals[statusKey(metric.outcome.outcome)] += 1;
1461
+ }
1462
+ }
1463
+ return totals;
1464
+ }
1465
+ function countCaseOutcomes(results) {
1466
+ const totals = emptyTotals();
1467
+ for (const result of results) {
1468
+ totals.total += 1;
1469
+ totals[statusKey(result.outcome)] += 1;
1470
+ }
1471
+ return totals;
1472
+ }
1473
+ function emptyTotals() {
1474
+ return { total: 0, passed: 0, failed: 0, invalid: 0 };
1475
+ }
1476
+ function statusKey(status) {
1477
+ if (status === "pass") return "passed";
1478
+ if (status === "fail") return "failed";
1479
+ return "invalid";
1480
+ }
1481
+ function caseOutcome(targetError, metrics) {
1482
+ if (targetError !== void 0) return "invalid";
1483
+ const required = metrics.filter((metric) => metric.required);
1484
+ if (required.some((metric) => metric.outcome.outcome === "invalid")) return "invalid";
1485
+ if (required.some((metric) => metric.outcome.outcome === "fail")) return "fail";
1486
+ return "pass";
1487
+ }
1488
+ async function aggregateResult(options, results) {
1489
+ let targetUsage = Usage.empty();
1490
+ let evaluationUsage = Usage.empty();
1491
+ let targetCost = 0;
1492
+ let evaluationCost = 0;
1493
+ for (const result of results) {
1494
+ if (result.output !== void 0) {
1495
+ const usage2 = await resolveTargetUsage(options, result.case, result.output);
1496
+ if (usage2 !== void 0) {
1497
+ targetUsage = Usage.add(targetUsage, usage2);
1498
+ if (options.cost !== void 0) {
1499
+ targetCost += await calculateCost(
1500
+ options.cost.calculate({
1501
+ kind: "target",
1502
+ suiteName: options.name,
1503
+ case: result.case,
1504
+ output: result.output,
1505
+ usage: usage2
1506
+ })
1507
+ );
1508
+ }
1509
+ }
1510
+ }
1511
+ for (const metricResult of result.metrics) {
1512
+ const usage2 = metricResult.outcome.usage;
1513
+ if (usage2 === void 0) continue;
1514
+ assertUsage(usage2, `Evaluation usage for metric ${metricResult.metricName}`);
1515
+ evaluationUsage = Usage.add(evaluationUsage, usage2);
1516
+ if (options.cost !== void 0 && result.output !== void 0) {
1517
+ const metric = options.metrics.find(
1518
+ (candidate) => candidate.name === metricResult.metricName
1519
+ );
1520
+ if (metric !== void 0) {
1521
+ evaluationCost += await calculateCost(
1522
+ options.cost.calculate({
1523
+ kind: "evaluation",
1524
+ suiteName: options.name,
1525
+ case: result.case,
1526
+ output: result.output,
1527
+ metric,
1528
+ usage: usage2
1529
+ })
1530
+ );
1531
+ }
1532
+ }
1323
1533
  }
1324
1534
  }
1325
- return { passed, failed, invalid };
1535
+ const usage = {
1536
+ target: targetUsage,
1537
+ evaluation: evaluationUsage,
1538
+ total: Usage.add(targetUsage, evaluationUsage)
1539
+ };
1540
+ const aggregates = {
1541
+ metrics: countMetricOutcomes(results),
1542
+ cases: countCaseOutcomes(results),
1543
+ usage
1544
+ };
1545
+ if (options.cost !== void 0) {
1546
+ aggregates.cost = {
1547
+ currency: options.cost.currency,
1548
+ target: targetCost,
1549
+ evaluation: evaluationCost,
1550
+ total: targetCost + evaluationCost
1551
+ };
1552
+ }
1553
+ return aggregates;
1554
+ }
1555
+ async function resolveTargetUsage(options, testCase, output) {
1556
+ const usage = options.targetUsage === void 0 ? usageFromOutput(output) : await options.targetUsage({ suiteName: options.name, case: testCase, output });
1557
+ if (usage !== void 0) assertUsage(usage, `Target usage for case ${testCase.id}`);
1558
+ return usage;
1559
+ }
1560
+ function usageFromOutput(output) {
1561
+ if (typeof output !== "object" || output === null || !("usage" in output)) return void 0;
1562
+ return output.usage;
1563
+ }
1564
+ function assertUsage(usage, label) {
1565
+ for (const [key, value] of Object.entries(usage)) {
1566
+ if (key === "details") continue;
1567
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1568
+ throw new TypeError(`${label} must contain finite, non-negative token counts`);
1569
+ }
1570
+ }
1571
+ }
1572
+ async function calculateCost(value) {
1573
+ const cost = await value;
1574
+ if (!Number.isFinite(cost) || cost < 0) {
1575
+ throw new TypeError("Evaluation cost calculator must return a finite, non-negative number");
1576
+ }
1577
+ return cost;
1578
+ }
1579
+ function validateSuiteOptions(options) {
1580
+ assertUnique(
1581
+ options.cases.map((testCase) => testCase.id),
1582
+ "Evaluation case id"
1583
+ );
1584
+ assertUnique(
1585
+ options.metrics.map((metric) => metric.name),
1586
+ "Evaluation metric name"
1587
+ );
1588
+ if (options.cost !== void 0 && options.cost.currency.trim().length === 0) {
1589
+ throw new TypeError("Evaluation cost currency must not be empty");
1590
+ }
1591
+ }
1592
+ function assertUnique(values, label) {
1593
+ const seen = /* @__PURE__ */ new Set();
1594
+ for (const value of values) {
1595
+ if (seen.has(value)) throw new TypeError(`${label} must be unique: ${value}`);
1596
+ seen.add(value);
1597
+ }
1598
+ }
1599
+
1600
+ // src/evals/cli.ts
1601
+ var EvalAssertionError = class extends Error {
1602
+ mismatches;
1603
+ constructor(message, mismatches) {
1604
+ super(`${message}
1605
+ ${mismatches.map((mismatch) => `- ${mismatch}`).join("\n")}`);
1606
+ this.name = "EvalAssertionError";
1607
+ this.mismatches = mismatches;
1608
+ }
1609
+ };
1610
+ function printEvalResult(result, options = {}) {
1611
+ const format = options.format ?? "pretty";
1612
+ if (format === "quiet") return;
1613
+ const write = options.output?.stdout ?? ((text) => process.stdout.write(text));
1614
+ write(`${format === "json" ? jsonResult(result) : prettyResult(result)}
1615
+ `);
1616
+ }
1617
+ function evalExitCode(result, expectations) {
1618
+ const hasExpectations = expectations?.totals !== void 0 || expectations?.outcomes !== void 0;
1619
+ const mismatches = hasExpectations ? expectationMismatches(result, expectations) : [];
1620
+ if (hasExpectations && mismatches.length === 0) return 0;
1621
+ if (hasExpectations && hasUnexpectedInvalid(result, expectations)) return 2;
1622
+ if (hasExpectations) return 1;
1623
+ if (requiredMetricCount(result, "invalid") > 0) return 2;
1624
+ return requiredMetricCount(result, "fail") > 0 ? 1 : 0;
1625
+ }
1626
+ function assertEvalTotals(result, expected) {
1627
+ const mismatches = totalMismatches(result, expected);
1628
+ if (mismatches.length > 0) {
1629
+ throw new EvalAssertionError("Evaluation totals did not match expectations.", mismatches);
1630
+ }
1631
+ }
1632
+ function assertEvalOutcomes(result, expected) {
1633
+ const mismatches = outcomeMismatches(result, expected);
1634
+ if (mismatches.length > 0) {
1635
+ throw new EvalAssertionError("Evaluation outcomes did not match expectations.", mismatches);
1636
+ }
1637
+ }
1638
+ async function runEvalCli(options) {
1639
+ const { format, exitCode, expectations, output, ...suiteOptions } = options;
1640
+ const result = await runEvalSuite(
1641
+ suiteOptions
1642
+ );
1643
+ printEvalResult(result, { format, output });
1644
+ const code = evalExitCode(result, expectations);
1645
+ const mismatches = expectationMismatches(result, expectations);
1646
+ if (mismatches.length > 0 && format !== "quiet") {
1647
+ const write = output?.stderr ?? ((text) => process.stderr.write(text));
1648
+ write(
1649
+ `Evaluation expectation mismatches:
1650
+ ${mismatches.map((value) => `- ${value}`).join("\n")}
1651
+ `
1652
+ );
1653
+ }
1654
+ const currentExitCode = typeof process.exitCode === "number" ? process.exitCode : Number(process.exitCode ?? 0);
1655
+ if (exitCode === true && code > currentExitCode) process.exitCode = code;
1656
+ return result;
1657
+ }
1658
+ function prettyResult(result) {
1659
+ const lines = [
1660
+ `${result.name} (${result.run.id})`,
1661
+ `Cases: ${totalsText(result.cases)}`,
1662
+ `Metrics: ${totalsText(result.metrics)}`
1663
+ ];
1664
+ for (const caseResult of result.results) {
1665
+ lines.push(``, `[${caseResult.outcome.toUpperCase()}] ${caseResult.case.id}`);
1666
+ if (caseResult.output !== void 0) lines.push(` output: ${displayValue(caseResult.output)}`);
1667
+ if (caseResult.targetError !== void 0) {
1668
+ lines.push(` target error: ${errorText(caseResult.targetError)}`);
1669
+ }
1670
+ for (const metric of caseResult.metrics) {
1671
+ const parts = [` - ${metric.metricName}: ${metric.outcome.outcome}`];
1672
+ if (metric.outcome.score !== void 0)
1673
+ parts.push(`score=${displayValue(metric.outcome.score)}`);
1674
+ if (metric.threshold !== void 0) parts.push(`threshold=${metric.threshold}`);
1675
+ if (metric.direction !== void 0) parts.push(`direction=${metric.direction}`);
1676
+ if (!metric.required) parts.push("optional");
1677
+ lines.push(parts.join(" | "));
1678
+ const explanation = metric.outcome.comment ?? (metric.outcome.outcome === "invalid" ? metric.outcome.reason : void 0);
1679
+ if (explanation !== void 0) lines.push(` ${explanation}`);
1680
+ }
1681
+ }
1682
+ lines.push(
1683
+ ``,
1684
+ `Usage: target=${result.usage.target.totalTokens} evaluation=${result.usage.evaluation.totalTokens} total=${result.usage.total.totalTokens} tokens`
1685
+ );
1686
+ if (result.cost !== void 0) {
1687
+ lines.push(
1688
+ `Cost: target=${result.cost.target} evaluation=${result.cost.evaluation} total=${result.cost.total} ${result.cost.currency}`
1689
+ );
1690
+ }
1691
+ lines.push(`Duration: ${result.durationMs}ms`);
1692
+ return lines.join("\n");
1693
+ }
1694
+ function jsonResult(result) {
1695
+ return JSON.stringify(
1696
+ result,
1697
+ (_key, value) => {
1698
+ if (value instanceof Error) {
1699
+ return { name: value.name, message: value.message, stack: value.stack };
1700
+ }
1701
+ if (value instanceof RegExp) return String(value);
1702
+ return value;
1703
+ },
1704
+ 2
1705
+ );
1706
+ }
1707
+ function expectationMismatches(result, expectations) {
1708
+ if (expectations === void 0) return [];
1709
+ return [
1710
+ ...expectations.totals === void 0 ? [] : totalMismatches(result, expectations.totals),
1711
+ ...expectations.outcomes === void 0 ? [] : outcomeMismatches(result, expectations.outcomes)
1712
+ ];
1713
+ }
1714
+ function totalMismatches(result, expected) {
1715
+ const directMetrics = {
1716
+ ...expected.total === void 0 ? {} : { total: expected.total },
1717
+ ...expected.passed === void 0 ? {} : { passed: expected.passed },
1718
+ ...expected.failed === void 0 ? {} : { failed: expected.failed },
1719
+ ...expected.invalid === void 0 ? {} : { invalid: expected.invalid }
1720
+ };
1721
+ return [
1722
+ ...totalsGroupMismatches("metrics", result.metrics, {
1723
+ ...directMetrics,
1724
+ ...expected.metrics
1725
+ }),
1726
+ ...totalsGroupMismatches("cases", result.cases, expected.cases)
1727
+ ];
1728
+ }
1729
+ function totalsGroupMismatches(label, actual, expected) {
1730
+ if (expected === void 0) return [];
1731
+ const mismatches = [];
1732
+ for (const key of ["total", "passed", "failed", "invalid"]) {
1733
+ if (expected[key] !== void 0 && actual[key] !== expected[key]) {
1734
+ mismatches.push(`${label}.${key}: expected ${expected[key]}, received ${actual[key]}`);
1735
+ }
1736
+ }
1737
+ return mismatches;
1738
+ }
1739
+ function outcomeMismatches(result, expected) {
1740
+ const mismatches = [];
1741
+ const actualCases = new Map(result.results.map((caseResult) => [caseResult.case.id, caseResult]));
1742
+ for (const [caseId, metrics] of Object.entries(expected)) {
1743
+ const caseResult = actualCases.get(caseId);
1744
+ if (caseResult === void 0) {
1745
+ mismatches.push(`${caseId}: expected case was not present`);
1746
+ continue;
1747
+ }
1748
+ for (const [metricName, expectedOutcome] of Object.entries(metrics)) {
1749
+ const metric = caseResult.metrics.find((candidate) => candidate.metricName === metricName);
1750
+ if (metric === void 0) {
1751
+ mismatches.push(`${caseId}.${metricName}: expected metric was not present`);
1752
+ } else if (metric.outcome.outcome !== expectedOutcome) {
1753
+ mismatches.push(
1754
+ `${caseId}.${metricName}: expected ${expectedOutcome}, received ${metric.outcome.outcome}`
1755
+ );
1756
+ }
1757
+ }
1758
+ }
1759
+ for (const caseResult of result.results) {
1760
+ for (const metric of caseResult.metrics) {
1761
+ if (!metric.required) continue;
1762
+ const expectedOutcome = expected[caseResult.case.id]?.[metric.metricName] ?? "pass";
1763
+ if (metric.outcome.outcome !== expectedOutcome) {
1764
+ const message = `${caseResult.case.id}.${metric.metricName}: expected ${expectedOutcome}, received ${metric.outcome.outcome}`;
1765
+ if (!mismatches.includes(message)) mismatches.push(message);
1766
+ }
1767
+ }
1768
+ }
1769
+ return mismatches;
1770
+ }
1771
+ function hasUnexpectedInvalid(result, expectations) {
1772
+ if (expectations?.outcomes !== void 0) {
1773
+ return result.results.some(
1774
+ (caseResult) => caseResult.metrics.some(
1775
+ (metric) => metric.required && metric.outcome.outcome === "invalid" && expectations.outcomes?.[caseResult.case.id]?.[metric.metricName] !== "invalid"
1776
+ )
1777
+ );
1778
+ }
1779
+ const expectedInvalid = expectations?.totals?.metrics?.invalid ?? expectations?.totals?.invalid;
1780
+ return result.metrics.invalid > (expectedInvalid ?? 0);
1781
+ }
1782
+ function requiredMetricCount(result, status) {
1783
+ return result.results.reduce(
1784
+ (total, caseResult) => total + caseResult.metrics.filter((metric) => metric.required && metric.outcome.outcome === status).length,
1785
+ 0
1786
+ );
1787
+ }
1788
+ function totalsText(totals) {
1789
+ return `${totals.total} total / ${totals.passed} pass / ${totals.failed} fail / ${totals.invalid} invalid`;
1790
+ }
1791
+ function displayValue(value) {
1792
+ if (typeof value === "string") return value;
1793
+ try {
1794
+ return JSON.stringify(value);
1795
+ } catch {
1796
+ return String(value);
1797
+ }
1798
+ }
1799
+ function errorText(error) {
1800
+ return error instanceof Error ? error.message : displayValue(error);
1801
+ }
1802
+
1803
+ // src/evals/metric.ts
1804
+ function defineMetric(metric) {
1805
+ return metric;
1806
+ }
1807
+
1808
+ // src/evals/metrics.ts
1809
+ import { z as z2 } from "zod";
1810
+ function exactMatch(options = {}) {
1811
+ return {
1812
+ name: options.name ?? "exact_match",
1813
+ required: options.required ?? true,
1814
+ dataType: "BOOLEAN",
1815
+ direction: "higher_is_better",
1816
+ threshold: 1,
1817
+ async evaluate(args) {
1818
+ const actual = await resolveActual(options.actual, args);
1819
+ const expected = await resolveExpected(options.expected, args);
1820
+ if (expected === void 0) {
1821
+ return EvalOutcome.invalid("No expected value provided for exact match.");
1822
+ }
1823
+ const passed = stableComparable(actual) === stableComparable(expected);
1824
+ return passed ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Expected ${formatValue(expected)}.` });
1825
+ }
1826
+ };
1827
+ }
1828
+ function contains(options = {}) {
1829
+ return {
1830
+ name: options.name ?? "contains",
1831
+ required: options.required ?? true,
1832
+ dataType: "BOOLEAN",
1833
+ direction: "higher_is_better",
1834
+ threshold: 1,
1835
+ async evaluate(args) {
1836
+ const actual = await resolveActualText(options.actual, args);
1837
+ const expected = await resolveExpected(options.expected, args);
1838
+ if (expected === void 0) {
1839
+ return EvalOutcome.invalid("No expected value provided for contains.");
1840
+ }
1841
+ if (typeof expected !== "string" && !(expected instanceof RegExp)) {
1842
+ return EvalOutcome.invalid("Contains expected value must be a string or RegExp.");
1843
+ }
1844
+ const passed = expected instanceof RegExp ? regexMatches(expected, actual) : actual.includes(expected);
1845
+ return passed ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Output did not contain ${String(expected)}.` });
1846
+ }
1847
+ };
1848
+ }
1849
+ function notContains(options = {}) {
1850
+ return {
1851
+ name: options.name ?? "not_contains",
1852
+ required: options.required ?? true,
1853
+ dataType: "BOOLEAN",
1854
+ direction: "higher_is_better",
1855
+ threshold: 1,
1856
+ async evaluate(args) {
1857
+ const actual = await resolveActualText(options.actual, args);
1858
+ const expected = await resolveExpected(options.expected, args);
1859
+ if (expected === void 0) {
1860
+ return EvalOutcome.invalid("No expected value provided for notContains.");
1861
+ }
1862
+ if (typeof expected !== "string" && !(expected instanceof RegExp)) {
1863
+ return EvalOutcome.invalid("notContains expected value must be a string or RegExp.");
1864
+ }
1865
+ const found = textExpectationMatches(expected, actual);
1866
+ return found ? EvalOutcome.fail(false, {
1867
+ comment: `Output contained forbidden value ${String(expected)}.`
1868
+ }) : EvalOutcome.pass(true);
1869
+ }
1870
+ };
1871
+ }
1872
+ function containsAll(options) {
1873
+ return containsListMetric("contains_all", "all", options);
1874
+ }
1875
+ function containsAny(options) {
1876
+ return containsListMetric("contains_any", "any", options);
1877
+ }
1878
+ function containsListMetric(defaultName, mode, options) {
1879
+ return {
1880
+ name: options.name ?? defaultName,
1881
+ required: options.required ?? true,
1882
+ dataType: "BOOLEAN",
1883
+ direction: "higher_is_better",
1884
+ threshold: 1,
1885
+ async evaluate(args) {
1886
+ const actual = await resolveActualText(options.actual, args);
1887
+ const expected = await resolveExpected(options.expected, args);
1888
+ if (!Array.isArray(expected) || expected.length === 0) {
1889
+ return EvalOutcome.invalid(`${defaultName} expected value must be a non-empty array.`);
1890
+ }
1891
+ if (expected.some((value) => typeof value !== "string" && !(value instanceof RegExp))) {
1892
+ return EvalOutcome.invalid(`${defaultName} expected values must be strings or RegExp.`);
1893
+ }
1894
+ const matches2 = expected.map((value) => textExpectationMatches(value, actual));
1895
+ const passed = mode === "all" ? matches2.every(Boolean) : matches2.some(Boolean);
1896
+ if (passed) return EvalOutcome.pass(true);
1897
+ const missing = expected.filter((_, index) => !matches2[index]).map(String);
1898
+ const comment = mode === "all" ? `Output was missing: ${missing.join(", ")}.` : `Output matched none of: ${expected.map(String).join(", ")}.`;
1899
+ return EvalOutcome.fail(false, { comment });
1900
+ }
1901
+ };
1902
+ }
1903
+ function matches(options) {
1904
+ return regexMetric("matches", false, options);
1905
+ }
1906
+ function doesNotMatch(options) {
1907
+ return regexMetric("does_not_match", true, options);
1908
+ }
1909
+ function regexMetric(defaultName, negate, options) {
1910
+ return {
1911
+ name: options.name ?? defaultName,
1912
+ required: options.required ?? true,
1913
+ dataType: "BOOLEAN",
1914
+ direction: "higher_is_better",
1915
+ threshold: 1,
1916
+ async evaluate(args) {
1917
+ const actual = await resolveActualText(options.actual, args);
1918
+ const expected = await resolveExpected(options.expected, args);
1919
+ if (!(expected instanceof RegExp)) {
1920
+ return EvalOutcome.invalid(`${defaultName} expected value must be a RegExp.`);
1921
+ }
1922
+ const matched = regexMatches(expected, actual);
1923
+ const passed = negate ? !matched : matched;
1924
+ return passed ? EvalOutcome.pass(true) : EvalOutcome.fail(false, {
1925
+ comment: negate ? `Output matched forbidden pattern ${String(expected)}.` : `Output did not match ${String(expected)}.`
1926
+ });
1927
+ }
1928
+ };
1929
+ }
1930
+ function maxLength(options) {
1931
+ return {
1932
+ name: options.name ?? "max_length",
1933
+ required: options.required ?? true,
1934
+ dataType: "BOOLEAN",
1935
+ direction: "higher_is_better",
1936
+ threshold: 1,
1937
+ async evaluate(args) {
1938
+ const actual = await resolveActualText(options.actual, args);
1939
+ const max = await resolveOption(options.max, args);
1940
+ if (!Number.isInteger(max) || max < 0) {
1941
+ return EvalOutcome.invalid("maxLength max must be a non-negative integer.");
1942
+ }
1943
+ const length = Array.from(actual).length;
1944
+ return length <= max ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Output length ${length} exceeded maximum ${max}.` });
1945
+ }
1946
+ };
1947
+ }
1948
+ function requiredFields(options) {
1949
+ return {
1950
+ name: options.name ?? "required_fields",
1951
+ required: options.required ?? true,
1952
+ dataType: "BOOLEAN",
1953
+ direction: "higher_is_better",
1954
+ threshold: 1,
1955
+ async evaluate(args) {
1956
+ const actual = await resolveActual(options.actual, args);
1957
+ const expected = await resolveOption(options.expected, args);
1958
+ if (!Array.isArray(expected) || expected.length === 0 || expected.some((field) => typeof field !== "string" || field.length === 0)) {
1959
+ return EvalOutcome.invalid(
1960
+ "requiredFields expected value must be a non-empty string array."
1961
+ );
1962
+ }
1963
+ if (typeof actual !== "object" || actual === null || Array.isArray(actual)) {
1964
+ return EvalOutcome.invalid("requiredFields actual value must be an object.");
1965
+ }
1966
+ const missing = expected.filter((field) => !Object.hasOwn(actual, field));
1967
+ return missing.length === 0 ? EvalOutcome.pass(true) : EvalOutcome.fail(false, { comment: `Missing required fields: ${missing.join(", ")}.` });
1968
+ }
1969
+ };
1970
+ }
1971
+ function regexMatches(pattern, text) {
1972
+ pattern.lastIndex = 0;
1973
+ const matched = pattern.test(text);
1974
+ pattern.lastIndex = 0;
1975
+ return matched;
1976
+ }
1977
+ function textExpectationMatches(expected, actual) {
1978
+ return expected instanceof RegExp ? regexMatches(expected, actual) : actual.includes(expected);
1979
+ }
1980
+ async function resolveOption(value, args) {
1981
+ return typeof value === "function" ? value(args) : value;
1982
+ }
1983
+ function semanticSimilarity(options) {
1984
+ return {
1985
+ name: options.name ?? "semantic_similarity",
1986
+ required: options.required ?? true,
1987
+ dataType: "NUMERIC",
1988
+ direction: "higher_is_better",
1989
+ threshold: options.threshold,
1990
+ async evaluate(args) {
1991
+ const actual = await resolveActualText(options.actual, args);
1992
+ const expected = await resolveExpected(options.expected, args);
1993
+ if (expected === void 0) {
1994
+ return EvalOutcome.invalid("No expected value provided for semantic similarity.");
1995
+ }
1996
+ if (typeof expected !== "string") {
1997
+ return EvalOutcome.invalid("Semantic similarity expected value must be a string.");
1998
+ }
1999
+ const [actualEmbedding, expectedEmbedding] = await Promise.all([
2000
+ embedText(options.model, actual),
2001
+ embedText(options.model, expected)
2002
+ ]);
2003
+ const score = cosineSimilarity(actualEmbedding.vector, expectedEmbedding.vector);
2004
+ return score >= options.threshold ? EvalOutcome.pass(score) : EvalOutcome.fail(score, { comment: `Similarity below threshold ${options.threshold}.` });
2005
+ }
2006
+ };
2007
+ }
2008
+ function llmJudge(options) {
2009
+ const extractor = new ExtractorBuilder(options.model, options.schema).instructions(
2010
+ options.instructions ?? "Judge the eval case by the requested schema. Submit the judgment using the schema."
2011
+ ).retries(options.retries ?? 0).build();
2012
+ return {
2013
+ name: options.name ?? "llm_judge",
2014
+ required: options.required ?? true,
2015
+ async evaluate(args) {
2016
+ try {
2017
+ const result = await extractor.extractWithUsage(
2018
+ await resolveJudgePrompt(options.prompt, args)
2019
+ );
2020
+ return options.passes(result.data) ? EvalOutcome.pass(result.data, { usage: result.usage }) : EvalOutcome.fail(result.data, { usage: result.usage });
2021
+ } catch (error) {
2022
+ return EvalOutcome.invalid(errorMessage(error));
2023
+ }
2024
+ }
2025
+ };
2026
+ }
2027
+ function llmScore(options) {
2028
+ const criteria = Array.isArray(options.criteria) ? options.criteria.join("\n") : options.criteria;
2029
+ const extractor = new ExtractorBuilder(
2030
+ options.model,
2031
+ z2.object({
2032
+ score: z2.number(),
2033
+ feedback: z2.string()
2034
+ })
2035
+ ).instructions(
2036
+ options.instructions ?? `Score the eval case against these criteria:
2037
+ ${criteria}
2038
+
2039
+ Return a score between 0 and 1 and brief feedback.`
2040
+ ).retries(options.retries ?? 0).build();
2041
+ return {
2042
+ name: options.name ?? "llm_score",
2043
+ required: options.required ?? true,
2044
+ dataType: "NUMERIC",
2045
+ projectScore: (score) => score.score,
2046
+ direction: "higher_is_better",
2047
+ threshold: options.threshold,
2048
+ async evaluate(args) {
2049
+ try {
2050
+ const result = await extractor.extractWithUsage(
2051
+ await resolveJudgePrompt(options.prompt, args)
2052
+ );
2053
+ const score = result.data;
2054
+ if (score.score < 0 || score.score > 1) {
2055
+ return EvalOutcome.invalid(`Score ${score.score} outside valid range [0, 1].`, {
2056
+ score,
2057
+ usage: result.usage
2058
+ });
2059
+ }
2060
+ return score.score >= options.threshold ? EvalOutcome.pass(score, { comment: score.feedback, usage: result.usage }) : EvalOutcome.fail(score, { comment: score.feedback, usage: result.usage });
2061
+ } catch (error) {
2062
+ return EvalOutcome.invalid(errorMessage(error));
2063
+ }
2064
+ }
2065
+ };
2066
+ }
2067
+
2068
+ // src/evals/suite.ts
2069
+ function defineEvalCases(cases) {
2070
+ return cases;
2071
+ }
2072
+ function defineEvalSuite(options) {
2073
+ if (options !== void 0) return options;
2074
+ return {
2075
+ defineMetric(metric) {
2076
+ return metric;
2077
+ }
2078
+ };
1326
2079
  }
1327
2080
  export {
2081
+ EvalAssertionError,
1328
2082
  EvalOutcome,
2083
+ abstention,
1329
2084
  agentEvalTarget,
1330
2085
  answerRelevancy,
2086
+ assertEvalOutcomes,
2087
+ assertEvalTotals,
1331
2088
  contains,
2089
+ containsAll,
2090
+ containsAny,
1332
2091
  defaultEvalTraceSelector,
2092
+ defineEvalCases,
2093
+ defineEvalSuite,
1333
2094
  defineMetric,
2095
+ doesNotMatch,
2096
+ evalExitCode,
1334
2097
  exactMatch,
1335
2098
  faithfulness,
1336
2099
  gEval,
@@ -1339,10 +2102,17 @@ export {
1339
2102
  knowledgeRetention,
1340
2103
  llmJudge,
1341
2104
  llmScore,
2105
+ matches,
2106
+ maxLength,
2107
+ notContains,
2108
+ printEvalResult,
1342
2109
  projectEvalOutcome,
1343
2110
  promptAlignment,
2111
+ requiredFields,
1344
2112
  resolveEvalTraceRef,
2113
+ runEvalCli,
1345
2114
  runEvalSuite,
2115
+ selectPromptOutput,
1346
2116
  semanticSimilarity,
1347
2117
  summarization,
1348
2118
  turnRelevancy