@axlsdk/eval 0.13.8 → 0.15.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/dist/cli.cjs CHANGED
@@ -311,7 +311,36 @@ function parseCost(cost) {
311
311
  if (!match) throw new Error(`Invalid cost format: "${cost}"`);
312
312
  return parseFloat(match[1]);
313
313
  }
314
- async function runEval(config, executeWorkflow, runtime) {
314
+ function extractUserCost(result, label = "executeWorkflow") {
315
+ if (result === null || typeof result !== "object") return void 0;
316
+ const raw = result.cost;
317
+ if (raw === void 0) return void 0;
318
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0) return raw;
319
+ console.warn(
320
+ `[axl-eval] Ignoring invalid \`cost\` from ${label} return: expected non-negative finite number, got ${typeof raw === "number" ? String(raw) : typeof raw}. Falling back to tracked cost (or undefined).`
321
+ );
322
+ return void 0;
323
+ }
324
+ function extractUserMetadata(result, label = "executeWorkflow") {
325
+ if (result === null || typeof result !== "object") return void 0;
326
+ const meta = result.metadata;
327
+ if (meta === void 0) return void 0;
328
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta)) {
329
+ console.warn(
330
+ `[axl-eval] Ignoring invalid \`metadata\` from ${label} return: expected plain object, got ${meta === null ? "null" : Array.isArray(meta) ? "array" : typeof meta}.`
331
+ );
332
+ return void 0;
333
+ }
334
+ const proto = Object.getPrototypeOf(meta);
335
+ if (proto !== Object.prototype && proto !== null) {
336
+ console.warn(
337
+ `[axl-eval] Ignoring invalid \`metadata\` from ${label} return: expected plain object, got ${proto?.constructor?.name ?? "exotic object"}.`
338
+ );
339
+ return void 0;
340
+ }
341
+ return meta;
342
+ }
343
+ async function runEval(config, executeWorkflow, runtime, options) {
315
344
  const startTime = Date.now();
316
345
  const id = (0, import_node_crypto.randomUUID)();
317
346
  const items = await config.dataset.getItems();
@@ -331,6 +360,17 @@ async function runEval(config, executeWorkflow, runtime) {
331
360
  let totalCost = 0;
332
361
  let budgetExceeded = false;
333
362
  async function processItem(item, itemIndex) {
363
+ if (options?.signal?.aborted) {
364
+ evalItems[itemIndex] = {
365
+ input: item.input,
366
+ annotations: item.annotations,
367
+ output: null,
368
+ error: "Cancelled",
369
+ scores: {}
370
+ };
371
+ options?.onProgress?.({ type: "item_done", itemIndex, totalItems: items.length });
372
+ return;
373
+ }
334
374
  if (budgetExceeded) {
335
375
  evalItems[itemIndex] = {
336
376
  input: item.input,
@@ -339,6 +379,7 @@ async function runEval(config, executeWorkflow, runtime) {
339
379
  error: "Budget exceeded",
340
380
  scores: {}
341
381
  };
382
+ options?.onProgress?.({ type: "item_done", itemIndex, totalItems: items.length });
342
383
  return;
343
384
  }
344
385
  const evalItem = {
@@ -349,18 +390,43 @@ async function runEval(config, executeWorkflow, runtime) {
349
390
  };
350
391
  const itemStart = Date.now();
351
392
  try {
352
- const result = await executeWorkflow(item.input, runtime);
353
- evalItem.duration = Date.now() - itemStart;
354
- evalItem.output = result.output;
355
- evalItem.cost = result.cost;
356
- if (result.metadata) evalItem.metadata = result.metadata;
357
- if (result.cost != null) {
358
- totalCost += result.cost;
393
+ if (options?.captureTraces) {
394
+ const tracked = await runtime.trackExecution(
395
+ async () => executeWorkflow(item.input, runtime),
396
+ { captureTraces: true }
397
+ );
398
+ evalItem.duration = Date.now() - itemStart;
399
+ evalItem.output = tracked.result.output;
400
+ evalItem.cost = extractUserCost(tracked.result) ?? tracked.cost;
401
+ evalItem.metadata = extractUserMetadata(tracked.result) ?? tracked.metadata;
402
+ if (tracked.traces && tracked.traces.length > 0) {
403
+ evalItem.traces = tracked.traces;
404
+ }
405
+ if (evalItem.cost != null) {
406
+ totalCost += evalItem.cost;
407
+ }
408
+ } else {
409
+ const result = await executeWorkflow(item.input, runtime);
410
+ evalItem.duration = Date.now() - itemStart;
411
+ evalItem.output = result.output;
412
+ evalItem.cost = extractUserCost(result);
413
+ const meta = extractUserMetadata(result);
414
+ if (meta) evalItem.metadata = meta;
415
+ if (evalItem.cost != null) {
416
+ totalCost += evalItem.cost;
417
+ }
359
418
  }
360
419
  } catch (err) {
361
420
  evalItem.duration = Date.now() - itemStart;
362
421
  evalItem.error = err instanceof Error ? err.message : String(err);
422
+ if (options?.captureTraces) {
423
+ const captured = err.axlCapturedTraces;
424
+ if (Array.isArray(captured) && captured.length > 0) {
425
+ evalItem.traces = captured;
426
+ }
427
+ }
363
428
  evalItems[itemIndex] = evalItem;
429
+ options?.onProgress?.({ type: "item_done", itemIndex, totalItems: items.length });
364
430
  return;
365
431
  }
366
432
  if (budgetLimit != null && totalCost > budgetLimit) {
@@ -369,6 +435,7 @@ async function runEval(config, executeWorkflow, runtime) {
369
435
  evalItem.scoreDetails = {};
370
436
  let itemScorerCost = 0;
371
437
  for (const scorer of config.scorers) {
438
+ if (options?.signal?.aborted) break;
372
439
  const scorerStart = Date.now();
373
440
  try {
374
441
  const raw = await scorer.score(
@@ -424,6 +491,7 @@ async function runEval(config, executeWorkflow, runtime) {
424
491
  }
425
492
  evalItem.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
426
493
  evalItems[itemIndex] = evalItem;
494
+ options?.onProgress?.({ type: "item_done", itemIndex, totalItems: items.length });
427
495
  }
428
496
  let index = 0;
429
497
  async function runNext() {
@@ -470,14 +538,42 @@ async function runEval(config, executeWorkflow, runtime) {
470
538
  modelsMeta.models = sorted.map(([m]) => m);
471
539
  modelsMeta.modelCounts = Object.fromEntries(sorted);
472
540
  }
541
+ const totalWorkflowCalls = /* @__PURE__ */ new Map();
542
+ for (const item of evalItems) {
543
+ const itemCounts = item.metadata?.workflowCallCounts;
544
+ if (itemCounts && typeof itemCounts === "object") {
545
+ for (const [w, count] of Object.entries(itemCounts)) {
546
+ if (typeof count === "number")
547
+ totalWorkflowCalls.set(w, (totalWorkflowCalls.get(w) ?? 0) + count);
548
+ }
549
+ } else {
550
+ const itemWorkflows = item.metadata?.workflows;
551
+ if (Array.isArray(itemWorkflows)) {
552
+ for (const w of itemWorkflows) {
553
+ if (typeof w === "string")
554
+ totalWorkflowCalls.set(w, (totalWorkflowCalls.get(w) ?? 0) + 1);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ const workflowsMeta = {};
560
+ if (totalWorkflowCalls.size > 0) {
561
+ const sorted = [...totalWorkflowCalls.entries()].sort((a, b) => b[1] - a[1]);
562
+ workflowsMeta.workflows = sorted.map(([w]) => w);
563
+ workflowsMeta.workflowCounts = Object.fromEntries(sorted);
564
+ } else if (config.workflow) {
565
+ workflowsMeta.workflows = [config.workflow];
566
+ workflowsMeta.workflowCounts = { [config.workflow]: items.length };
567
+ }
568
+ options?.onProgress?.({ type: "run_done", totalItems: items.length, failures });
473
569
  return {
474
570
  id,
475
- workflow: config.workflow,
476
571
  dataset: config.dataset.name,
477
572
  metadata: {
478
573
  ...config.metadata,
479
574
  scorerTypes,
480
- ...modelsMeta
575
+ ...modelsMeta,
576
+ ...workflowsMeta
481
577
  },
482
578
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
483
579
  totalCost,
@@ -520,6 +616,7 @@ async function rescore(result, scorers, runtime, options) {
520
616
  annotations: original.annotations,
521
617
  output: original.output,
522
618
  metadata: original.metadata,
619
+ traces: original.traces,
523
620
  scores: {},
524
621
  scoreDetails: {}
525
622
  };
@@ -601,11 +698,23 @@ async function rescore(result, scorers, runtime, options) {
601
698
  }
602
699
  return {
603
700
  id: (0, import_node_crypto2.randomUUID)(),
604
- workflow: result.workflow,
605
701
  dataset: result.dataset,
606
702
  metadata: (() => {
607
703
  const { runGroupId: _, runIndex: __, ...rest } = result.metadata;
608
- return { ...rest, rescored: true, originalId: result.id, scorerTypes };
704
+ const merged = {
705
+ ...rest,
706
+ rescored: true,
707
+ originalId: result.id,
708
+ scorerTypes
709
+ };
710
+ if (!Array.isArray(merged.workflows)) {
711
+ const legacyWorkflow = result.workflow;
712
+ if (typeof legacyWorkflow === "string" && legacyWorkflow) {
713
+ merged.workflows = [legacyWorkflow];
714
+ merged.workflowCounts = { [legacyWorkflow]: result.items.length };
715
+ }
716
+ }
717
+ return merged;
609
718
  })(),
610
719
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
611
720
  totalCost,
@@ -625,7 +734,20 @@ function std(values, mean) {
625
734
  function aggregateRuns(runs) {
626
735
  if (runs.length === 0) throw new Error("Cannot aggregate zero runs");
627
736
  const runGroupId = runs[0].metadata.runGroupId ?? (0, import_node_crypto3.randomUUID)();
628
- const { workflow, dataset } = runs[0];
737
+ const { dataset } = runs[0];
738
+ const seenWorkflows = /* @__PURE__ */ new Set();
739
+ const workflows = [];
740
+ for (const run of runs) {
741
+ const list = run.metadata.workflows;
742
+ if (Array.isArray(list)) {
743
+ for (const w of list) {
744
+ if (typeof w === "string" && !seenWorkflows.has(w)) {
745
+ seenWorkflows.add(w);
746
+ workflows.push(w);
747
+ }
748
+ }
749
+ }
750
+ }
629
751
  const totalCost = runs.reduce((sum, r) => sum + r.totalCost, 0);
630
752
  const totalDuration = runs.reduce((sum, r) => sum + r.duration, 0);
631
753
  const scorerNames = Object.keys(runs[0].summary.scorers);
@@ -654,7 +776,7 @@ function aggregateRuns(runs) {
654
776
  return {
655
777
  runGroupId,
656
778
  runCount: runs.length,
657
- workflow,
779
+ workflows,
658
780
  dataset,
659
781
  totalCost: round3(totalCost),
660
782
  totalDuration: round3(totalDuration),
@@ -733,7 +855,8 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
733
855
  "--conditions",
734
856
  "--fail-on-regression",
735
857
  "--threshold",
736
- "--runs"
858
+ "--runs",
859
+ "--capture-traces"
737
860
  ]);
738
861
  async function main() {
739
862
  const args = process.argv.slice(2);
@@ -745,6 +868,10 @@ Usage:
745
868
  axl-eval <path> --output <file> Save results to JSON
746
869
  axl-eval <path> --config <file> Use config file for runtime
747
870
  axl-eval <path> --conditions <list> Node.js import conditions (comma-separated)
871
+ axl-eval <path> --capture-traces Populate EvalItem.traces on every item
872
+ (success + failure). Adds memory
873
+ overhead proportional to dataset size
874
+ x turns x agents; off by default.
748
875
  axl-eval rescore <results> <eval-file> Re-run scorers on saved outputs
749
876
  axl-eval compare <a> <b> Compare two eval result files
750
877
  axl-eval compare <a> <b> --threshold <v> Set regression threshold (global or per-scorer)
@@ -911,12 +1038,18 @@ function collectEvalFiles(p) {
911
1038
  return [resolved];
912
1039
  }
913
1040
  }
1041
+ function formatWorkflows(workflows) {
1042
+ if (!Array.isArray(workflows) || workflows.length === 0) return "(unknown)";
1043
+ return workflows.filter((w) => typeof w === "string").join(", ");
1044
+ }
914
1045
  function formatTable(result) {
915
1046
  const lines = [];
916
1047
  const scorerNames = Object.keys(result.summary.scorers);
917
1048
  const maxNameLen = Math.max(...scorerNames.map((n) => n.length), "Scorer".length);
918
1049
  const colWidth = 8;
919
- lines.push(`Eval: ${result.workflow} x ${result.dataset} (${result.summary.count} items)`);
1050
+ lines.push(
1051
+ `Eval: ${formatWorkflows(result.metadata.workflows)} x ${result.dataset} (${result.summary.count} items)`
1052
+ );
920
1053
  lines.push(
921
1054
  ` ${"Scorer".padEnd(maxNameLen)} ${"Mean".padStart(colWidth)} ${"Min".padStart(colWidth)} ${"Max".padStart(colWidth)} ${"p50".padStart(colWidth)} ${"p95".padStart(colWidth)}`
922
1055
  );
@@ -968,7 +1101,9 @@ function formatMultiRunTable(summary) {
968
1101
  const scorerNames = Object.keys(summary.scorers);
969
1102
  const maxNameLen = Math.max(...scorerNames.map((n) => n.length), "Scorer".length);
970
1103
  const colWidth = 16;
971
- lines.push(`Eval: ${summary.workflow} x ${summary.dataset} \u2014 ${summary.runCount} runs`);
1104
+ lines.push(
1105
+ `Eval: ${formatWorkflows(summary.workflows)} x ${summary.dataset} \u2014 ${summary.runCount} runs`
1106
+ );
972
1107
  lines.push(
973
1108
  ` ${"Scorer".padEnd(maxNameLen)} ${"Mean \xB1 Std".padStart(colWidth)} ${"Min".padStart(8)} ${"Max".padStart(8)}`
974
1109
  );
@@ -1057,6 +1192,7 @@ function parseEvalArgs(args) {
1057
1192
  let configArg;
1058
1193
  let conditions = [];
1059
1194
  let runs = 1;
1195
+ let captureTraces = false;
1060
1196
  const paths = [];
1061
1197
  for (let i = 0; i < args.length; i++) {
1062
1198
  const arg = args[i];
@@ -1071,6 +1207,8 @@ function parseEvalArgs(args) {
1071
1207
  else if (arg === "--runs") runs = Math.max(1, parseInt(value, 10) || 1);
1072
1208
  else
1073
1209
  conditions = value.split(",").map((c) => c.trim()).filter(Boolean);
1210
+ } else if (arg === "--capture-traces") {
1211
+ captureTraces = true;
1074
1212
  } else if (arg.startsWith("--")) {
1075
1213
  if (!KNOWN_FLAGS.has(arg)) {
1076
1214
  console.error(`Unknown flag: ${arg}`);
@@ -1080,10 +1218,10 @@ function parseEvalArgs(args) {
1080
1218
  paths.push(arg);
1081
1219
  }
1082
1220
  }
1083
- return { outputPath, configArg, conditions, runs, paths };
1221
+ return { outputPath, configArg, conditions, runs, captureTraces, paths };
1084
1222
  }
1085
1223
  async function runEvalCommand(args) {
1086
- const { outputPath, configArg, conditions, runs, paths } = parseEvalArgs(args);
1224
+ const { outputPath, configArg, conditions, runs, captureTraces, paths } = parseEvalArgs(args);
1087
1225
  if (paths.length === 0) {
1088
1226
  console.error("Error: No eval file path provided");
1089
1227
  process.exit(1);
@@ -1138,13 +1276,14 @@ async function runEvalCommand(args) {
1138
1276
  );
1139
1277
  executeWorkflow = async (input) => ({ output: input });
1140
1278
  }
1279
+ const runOptions = captureTraces ? { captureTraces: true } : void 0;
1141
1280
  if (runs > 1) {
1142
1281
  const { randomUUID: randomUUID4 } = await import("crypto");
1143
1282
  const runGroupId = randomUUID4();
1144
1283
  const runResults = [];
1145
1284
  for (let r = 0; r < runs; r++) {
1146
1285
  console.error(`[axl-eval] Run ${r + 1}/${runs}...`);
1147
- const result = await runEval(evalConfig, executeWorkflow, runtime);
1286
+ const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);
1148
1287
  result.metadata.runGroupId = runGroupId;
1149
1288
  result.metadata.runIndex = r;
1150
1289
  runResults.push(result);
@@ -1153,7 +1292,7 @@ async function runEvalCommand(args) {
1153
1292
  const summary = aggregateRuns(runResults);
1154
1293
  console.log("\n" + formatMultiRunTable(summary) + "\n");
1155
1294
  } else {
1156
- const result = await runEval(evalConfig, executeWorkflow, runtime);
1295
+ const result = await runEval(evalConfig, executeWorkflow, runtime, runOptions);
1157
1296
  results.push(result);
1158
1297
  console.log("\n" + formatTable(result) + "\n");
1159
1298
  }