@graphorin/evals 0.5.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +157 -0
  4. package/dist/cli/index.d.ts +43 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +102 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/index.d.ts +66 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +64 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/loaders/csv.d.ts +23 -0
  13. package/dist/loaders/csv.d.ts.map +1 -0
  14. package/dist/loaders/csv.js +116 -0
  15. package/dist/loaders/csv.js.map +1 -0
  16. package/dist/loaders/dmr.d.ts +31 -0
  17. package/dist/loaders/dmr.d.ts.map +1 -0
  18. package/dist/loaders/dmr.js +121 -0
  19. package/dist/loaders/dmr.js.map +1 -0
  20. package/dist/loaders/from-traces.d.ts +32 -0
  21. package/dist/loaders/from-traces.d.ts.map +1 -0
  22. package/dist/loaders/from-traces.js +66 -0
  23. package/dist/loaders/from-traces.js.map +1 -0
  24. package/dist/loaders/index.d.ts +9 -0
  25. package/dist/loaders/index.js +9 -0
  26. package/dist/loaders/iterable.d.ts +12 -0
  27. package/dist/loaders/iterable.d.ts.map +1 -0
  28. package/dist/loaders/iterable.js +16 -0
  29. package/dist/loaders/iterable.js.map +1 -0
  30. package/dist/loaders/jsonl.d.ts +34 -0
  31. package/dist/loaders/jsonl.d.ts.map +1 -0
  32. package/dist/loaders/jsonl.js +64 -0
  33. package/dist/loaders/jsonl.js.map +1 -0
  34. package/dist/loaders/locomo.d.ts +32 -0
  35. package/dist/loaders/locomo.d.ts.map +1 -0
  36. package/dist/loaders/locomo.js +155 -0
  37. package/dist/loaders/locomo.js.map +1 -0
  38. package/dist/loaders/longmemeval.d.ts +36 -0
  39. package/dist/loaders/longmemeval.d.ts.map +1 -0
  40. package/dist/loaders/longmemeval.js +135 -0
  41. package/dist/loaders/longmemeval.js.map +1 -0
  42. package/dist/loaders/memory-eval.d.ts +67 -0
  43. package/dist/loaders/memory-eval.d.ts.map +1 -0
  44. package/dist/regression.d.ts +13 -0
  45. package/dist/regression.d.ts.map +1 -0
  46. package/dist/regression.js +63 -0
  47. package/dist/regression.js.map +1 -0
  48. package/dist/reporters/html.d.ts +11 -0
  49. package/dist/reporters/html.d.ts.map +1 -0
  50. package/dist/reporters/html.js +60 -0
  51. package/dist/reporters/html.js.map +1 -0
  52. package/dist/reporters/index.d.ts +6 -0
  53. package/dist/reporters/index.js +7 -0
  54. package/dist/reporters/json.d.ts +11 -0
  55. package/dist/reporters/json.d.ts.map +1 -0
  56. package/dist/reporters/json.js +10 -0
  57. package/dist/reporters/json.js.map +1 -0
  58. package/dist/reporters/junit.d.ts +11 -0
  59. package/dist/reporters/junit.d.ts.map +1 -0
  60. package/dist/reporters/junit.js +29 -0
  61. package/dist/reporters/junit.js.map +1 -0
  62. package/dist/reporters/markdown.d.ts +9 -0
  63. package/dist/reporters/markdown.d.ts.map +1 -0
  64. package/dist/reporters/markdown.js +59 -0
  65. package/dist/reporters/markdown.js.map +1 -0
  66. package/dist/reporters/terminal.d.ts +9 -0
  67. package/dist/reporters/terminal.d.ts.map +1 -0
  68. package/dist/reporters/terminal.js +38 -0
  69. package/dist/reporters/terminal.js.map +1 -0
  70. package/dist/runner.d.ts +11 -0
  71. package/dist/runner.d.ts.map +1 -0
  72. package/dist/runner.js +181 -0
  73. package/dist/runner.js.map +1 -0
  74. package/dist/scorers/code/exact-match.d.ts +22 -0
  75. package/dist/scorers/code/exact-match.d.ts.map +1 -0
  76. package/dist/scorers/code/exact-match.js +67 -0
  77. package/dist/scorers/code/exact-match.js.map +1 -0
  78. package/dist/scorers/code/json-path.d.ts +18 -0
  79. package/dist/scorers/code/json-path.d.ts.map +1 -0
  80. package/dist/scorers/code/json-path.js +71 -0
  81. package/dist/scorers/code/json-path.js.map +1 -0
  82. package/dist/scorers/code/predicate.d.ts +18 -0
  83. package/dist/scorers/code/predicate.d.ts.map +1 -0
  84. package/dist/scorers/code/predicate.js +19 -0
  85. package/dist/scorers/code/predicate.js.map +1 -0
  86. package/dist/scorers/code/regex.d.ts +15 -0
  87. package/dist/scorers/code/regex.d.ts.map +1 -0
  88. package/dist/scorers/code/regex.js +37 -0
  89. package/dist/scorers/code/regex.js.map +1 -0
  90. package/dist/scorers/index.d.ts +13 -0
  91. package/dist/scorers/index.js +13 -0
  92. package/dist/scorers/llm/judge.d.ts +57 -0
  93. package/dist/scorers/llm/judge.d.ts.map +1 -0
  94. package/dist/scorers/llm/judge.js +108 -0
  95. package/dist/scorers/llm/judge.js.map +1 -0
  96. package/dist/scorers/prebuilt/index.d.ts +22 -0
  97. package/dist/scorers/prebuilt/index.d.ts.map +1 -0
  98. package/dist/scorers/prebuilt/index.js +49 -0
  99. package/dist/scorers/prebuilt/index.js.map +1 -0
  100. package/dist/scorers/trajectory/argument-validity.d.ts +25 -0
  101. package/dist/scorers/trajectory/argument-validity.d.ts.map +1 -0
  102. package/dist/scorers/trajectory/argument-validity.js +46 -0
  103. package/dist/scorers/trajectory/argument-validity.js.map +1 -0
  104. package/dist/scorers/trajectory/correct-tool-selected.d.ts +19 -0
  105. package/dist/scorers/trajectory/correct-tool-selected.d.ts.map +1 -0
  106. package/dist/scorers/trajectory/correct-tool-selected.js +45 -0
  107. package/dist/scorers/trajectory/correct-tool-selected.js.map +1 -0
  108. package/dist/scorers/trajectory/final-state-correct.d.ts +21 -0
  109. package/dist/scorers/trajectory/final-state-correct.d.ts.map +1 -0
  110. package/dist/scorers/trajectory/final-state-correct.js +42 -0
  111. package/dist/scorers/trajectory/final-state-correct.js.map +1 -0
  112. package/dist/scorers/trajectory/recovery-after-error.d.ts +15 -0
  113. package/dist/scorers/trajectory/recovery-after-error.d.ts.map +1 -0
  114. package/dist/scorers/trajectory/recovery-after-error.js +37 -0
  115. package/dist/scorers/trajectory/recovery-after-error.js.map +1 -0
  116. package/dist/scorers/trajectory/redundant-call-detection.d.ts +19 -0
  117. package/dist/scorers/trajectory/redundant-call-detection.d.ts.map +1 -0
  118. package/dist/scorers/trajectory/redundant-call-detection.js +39 -0
  119. package/dist/scorers/trajectory/redundant-call-detection.js.map +1 -0
  120. package/dist/scorers/trajectory/types.d.ts +49 -0
  121. package/dist/scorers/trajectory/types.d.ts.map +1 -0
  122. package/dist/scorers/trajectory/util.js +71 -0
  123. package/dist/scorers/trajectory/util.js.map +1 -0
  124. package/dist/types.d.ts +98 -0
  125. package/dist/types.d.ts.map +1 -0
  126. package/package.json +81 -0
@@ -0,0 +1,59 @@
1
+ //#region src/reporters/markdown.ts
2
+ /** @stable */
3
+ function renderMarkdownReport(report) {
4
+ const lines = [];
5
+ lines.push("# graphorin/evals report");
6
+ lines.push("");
7
+ lines.push("## Summary");
8
+ lines.push("");
9
+ lines.push("| Metric | Value |");
10
+ lines.push("| --- | --- |");
11
+ lines.push(`| Total cases | ${report.summary.total} |`);
12
+ lines.push(`| Passed | ${report.summary.passed} |`);
13
+ lines.push(`| Failed | ${report.summary.failed} |`);
14
+ lines.push(`| Avg duration (ms) | ${report.summary.avgDurationMs.toFixed(2)} |`);
15
+ lines.push("");
16
+ lines.push("## Per-scorer");
17
+ lines.push("");
18
+ lines.push("| Scorer | Pass | Fail | Avg score |");
19
+ lines.push("| --- | ---: | ---: | ---: |");
20
+ for (const [scorer, row] of Object.entries(report.summary.byScorer)) {
21
+ const avg = row.avgScore === null ? "n/a" : row.avgScore.toFixed(4);
22
+ lines.push(`| \`${scorer}\` | ${row.passed} | ${row.failed} | ${avg} |`);
23
+ }
24
+ lines.push("");
25
+ lines.push("## Failures");
26
+ lines.push("");
27
+ let any = false;
28
+ for (const r of report.results) {
29
+ const failures = r.scores.filter((s) => !s.result.pass);
30
+ if (failures.length === 0) continue;
31
+ any = true;
32
+ lines.push(`### \`${r.caseId}\` _(\`${r.durationMs}\` ms)_`);
33
+ lines.push("");
34
+ for (const f of failures) {
35
+ const reason = escapeMarkdownInline(f.result.reason ?? "_no reason_");
36
+ lines.push(`- **${f.scorer}** — ${reason}`);
37
+ }
38
+ lines.push("");
39
+ }
40
+ if (!any) {
41
+ lines.push("_No failures._");
42
+ lines.push("");
43
+ }
44
+ return lines.join("\n");
45
+ }
46
+ /**
47
+ * Escape characters that would break a Markdown table row when rendered
48
+ * inline. Backslashes must be escaped first so that an existing trailing
49
+ * `\` in the input cannot combine with our inserted `\|` to form a
50
+ * literal pipe. Newlines are flattened to spaces because table cells
51
+ * cannot contain hard line breaks.
52
+ */
53
+ function escapeMarkdownInline(value) {
54
+ return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
55
+ }
56
+
57
+ //#endregion
58
+ export { renderMarkdownReport };
59
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","names":["lines: string[]"],"sources":["../../src/reporters/markdown.ts"],"sourcesContent":["/**\n * Markdown reporter. Renders an {@link EvalReport} as a markdown\n * document suitable for embedding in pull-request descriptions or\n * documentation sites.\n *\n * @packageDocumentation\n */\n\nimport type { EvalReport } from '@graphorin/observability/eval';\n\n/** @stable */\nexport function renderMarkdownReport<I, O>(report: EvalReport<I, O>): string {\n const lines: string[] = [];\n lines.push('# graphorin/evals report');\n lines.push('');\n lines.push('## Summary');\n lines.push('');\n lines.push('| Metric | Value |');\n lines.push('| --- | --- |');\n lines.push(`| Total cases | ${report.summary.total} |`);\n lines.push(`| Passed | ${report.summary.passed} |`);\n lines.push(`| Failed | ${report.summary.failed} |`);\n lines.push(`| Avg duration (ms) | ${report.summary.avgDurationMs.toFixed(2)} |`);\n lines.push('');\n lines.push('## Per-scorer');\n lines.push('');\n lines.push('| Scorer | Pass | Fail | Avg score |');\n lines.push('| --- | ---: | ---: | ---: |');\n for (const [scorer, row] of Object.entries(report.summary.byScorer)) {\n const avg = row.avgScore === null ? 'n/a' : row.avgScore.toFixed(4);\n lines.push(`| \\`${scorer}\\` | ${row.passed} | ${row.failed} | ${avg} |`);\n }\n lines.push('');\n lines.push('## Failures');\n lines.push('');\n let any = false;\n for (const r of report.results) {\n const failures = r.scores.filter((s) => !s.result.pass);\n if (failures.length === 0) continue;\n any = true;\n lines.push(`### \\`${r.caseId}\\` _(\\`${r.durationMs}\\` ms)_`);\n lines.push('');\n for (const f of failures) {\n const reason = escapeMarkdownInline(f.result.reason ?? '_no reason_');\n lines.push(`- **${f.scorer}** — ${reason}`);\n }\n lines.push('');\n }\n if (!any) {\n lines.push('_No failures._');\n lines.push('');\n }\n return lines.join('\\n');\n}\n\n/**\n * Escape characters that would break a Markdown table row when rendered\n * inline. Backslashes must be escaped first so that an existing trailing\n * `\\` in the input cannot combine with our inserted `\\|` to form a\n * literal pipe. Newlines are flattened to spaces because table cells\n * cannot contain hard line breaks.\n */\nfunction escapeMarkdownInline(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\\|/g, '\\\\|').replace(/\\r?\\n/g, ' ');\n}\n"],"mappings":";;AAWA,SAAgB,qBAA2B,QAAkC;CAC3E,MAAMA,QAAkB,EAAE;AAC1B,OAAM,KAAK,2BAA2B;AACtC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,aAAa;AACxB,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,qBAAqB;AAChC,OAAM,KAAK,gBAAgB;AAC3B,OAAM,KAAK,mBAAmB,OAAO,QAAQ,MAAM,IAAI;AACvD,OAAM,KAAK,cAAc,OAAO,QAAQ,OAAO,IAAI;AACnD,OAAM,KAAK,cAAc,OAAO,QAAQ,OAAO,IAAI;AACnD,OAAM,KAAK,yBAAyB,OAAO,QAAQ,cAAc,QAAQ,EAAE,CAAC,IAAI;AAChF,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,gBAAgB;AAC3B,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,uCAAuC;AAClD,OAAM,KAAK,+BAA+B;AAC1C,MAAK,MAAM,CAAC,QAAQ,QAAQ,OAAO,QAAQ,OAAO,QAAQ,SAAS,EAAE;EACnE,MAAM,MAAM,IAAI,aAAa,OAAO,QAAQ,IAAI,SAAS,QAAQ,EAAE;AACnE,QAAM,KAAK,OAAO,OAAO,OAAO,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,IAAI;;AAE1E,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,cAAc;AACzB,OAAM,KAAK,GAAG;CACd,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,OAAO,SAAS;EAC9B,MAAM,WAAW,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,OAAO,KAAK;AACvD,MAAI,SAAS,WAAW,EAAG;AAC3B,QAAM;AACN,QAAM,KAAK,SAAS,EAAE,OAAO,SAAS,EAAE,WAAW,SAAS;AAC5D,QAAM,KAAK,GAAG;AACd,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,SAAS,qBAAqB,EAAE,OAAO,UAAU,cAAc;AACrE,SAAM,KAAK,OAAO,EAAE,OAAO,OAAO,SAAS;;AAE7C,QAAM,KAAK,GAAG;;AAEhB,KAAI,CAAC,KAAK;AACR,QAAM,KAAK,iBAAiB;AAC5B,QAAM,KAAK,GAAG;;AAEhB,QAAO,MAAM,KAAK,KAAK;;;;;;;;;AAUzB,SAAS,qBAAqB,OAAuB;AACnD,QAAO,MAAM,QAAQ,OAAO,OAAO,CAAC,QAAQ,OAAO,MAAM,CAAC,QAAQ,UAAU,IAAI"}
@@ -0,0 +1,9 @@
1
+ import { EvalReport } from "@graphorin/observability/eval";
2
+
3
+ //#region src/reporters/terminal.d.ts
4
+
5
+ /** @stable */
6
+ declare function renderTerminalReport<I, O>(report: EvalReport<I, O>): string;
7
+ //#endregion
8
+ export { renderTerminalReport };
9
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","names":[],"sources":["../../src/reporters/terminal.ts"],"sourcesContent":[],"mappings":";;;;;iBAUgB,mCAAmC,WAAW,GAAG"}
@@ -0,0 +1,38 @@
1
+ //#region src/reporters/terminal.ts
2
+ /** @stable */
3
+ function renderTerminalReport(report) {
4
+ const lines = [];
5
+ const { summary } = report;
6
+ lines.push("graphorin/evals — terminal report");
7
+ lines.push("=================================");
8
+ lines.push(`total: ${summary.total}`);
9
+ lines.push(`passed: ${summary.passed}`);
10
+ lines.push(`failed: ${summary.failed}`);
11
+ lines.push(`avg ms: ${summary.avgDurationMs.toFixed(2)}`);
12
+ lines.push("");
13
+ lines.push("per-scorer:");
14
+ for (const [scorer, row] of Object.entries(summary.byScorer)) {
15
+ const avg = row.avgScore === null ? "n/a" : row.avgScore.toFixed(4);
16
+ lines.push(` ${scorer.padEnd(24)} pass=${row.passed.toString().padStart(4)} fail=${row.failed.toString().padStart(4)} avg=${avg}`);
17
+ }
18
+ lines.push("");
19
+ lines.push("failures (first 10):");
20
+ let failuresShown = 0;
21
+ for (const r of report.results) {
22
+ if (failuresShown >= 10) break;
23
+ const failures = r.scores.filter((s) => !s.result.pass);
24
+ if (failures.length === 0) continue;
25
+ failuresShown += 1;
26
+ lines.push(` • ${r.caseId} (${r.durationMs} ms)`);
27
+ for (const f of failures) {
28
+ const reason = f.result.reason ?? "<no reason>";
29
+ lines.push(` ${f.scorer}: ${reason}`);
30
+ }
31
+ }
32
+ if (failuresShown === 0) lines.push(" (none)");
33
+ return lines.join("\n");
34
+ }
35
+
36
+ //#endregion
37
+ export { renderTerminalReport };
38
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","names":["lines: string[]"],"sources":["../../src/reporters/terminal.ts"],"sourcesContent":["/**\n * Terminal reporter. Renders an {@link EvalReport} as plain ANSI-free\n * text suitable for stdout / CI logs.\n *\n * @packageDocumentation\n */\n\nimport type { EvalReport } from '@graphorin/observability/eval';\n\n/** @stable */\nexport function renderTerminalReport<I, O>(report: EvalReport<I, O>): string {\n const lines: string[] = [];\n const { summary } = report;\n lines.push('graphorin/evals — terminal report');\n lines.push('=================================');\n lines.push(`total: ${summary.total}`);\n lines.push(`passed: ${summary.passed}`);\n lines.push(`failed: ${summary.failed}`);\n lines.push(`avg ms: ${summary.avgDurationMs.toFixed(2)}`);\n lines.push('');\n lines.push('per-scorer:');\n for (const [scorer, row] of Object.entries(summary.byScorer)) {\n const avg = row.avgScore === null ? 'n/a' : row.avgScore.toFixed(4);\n lines.push(\n ` ${scorer.padEnd(24)} pass=${row.passed.toString().padStart(4)} fail=${row.failed.toString().padStart(4)} avg=${avg}`,\n );\n }\n lines.push('');\n lines.push('failures (first 10):');\n let failuresShown = 0;\n for (const r of report.results) {\n if (failuresShown >= 10) break;\n const failures = r.scores.filter((s) => !s.result.pass);\n if (failures.length === 0) continue;\n failuresShown += 1;\n lines.push(` • ${r.caseId} (${r.durationMs} ms)`);\n for (const f of failures) {\n const reason = f.result.reason ?? '<no reason>';\n lines.push(` ${f.scorer}: ${reason}`);\n }\n }\n if (failuresShown === 0) {\n lines.push(' (none)');\n }\n return lines.join('\\n');\n}\n"],"mappings":";;AAUA,SAAgB,qBAA2B,QAAkC;CAC3E,MAAMA,QAAkB,EAAE;CAC1B,MAAM,EAAE,YAAY;AACpB,OAAM,KAAK,oCAAoC;AAC/C,OAAM,KAAK,oCAAoC;AAC/C,OAAM,KAAK,YAAY,QAAQ,QAAQ;AACvC,OAAM,KAAK,YAAY,QAAQ,SAAS;AACxC,OAAM,KAAK,YAAY,QAAQ,SAAS;AACxC,OAAM,KAAK,YAAY,QAAQ,cAAc,QAAQ,EAAE,GAAG;AAC1D,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,cAAc;AACzB,MAAK,MAAM,CAAC,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,SAAS,EAAE;EAC5D,MAAM,MAAM,IAAI,aAAa,OAAO,QAAQ,IAAI,SAAS,QAAQ,EAAE;AACnE,QAAM,KACJ,KAAK,OAAO,OAAO,GAAG,CAAC,SAAS,IAAI,OAAO,UAAU,CAAC,SAAS,EAAE,CAAC,SAAS,IAAI,OAAO,UAAU,CAAC,SAAS,EAAE,CAAC,QAAQ,MACtH;;AAEH,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,uBAAuB;CAClC,IAAI,gBAAgB;AACpB,MAAK,MAAM,KAAK,OAAO,SAAS;AAC9B,MAAI,iBAAiB,GAAI;EACzB,MAAM,WAAW,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,OAAO,KAAK;AACvD,MAAI,SAAS,WAAW,EAAG;AAC3B,mBAAiB;AACjB,QAAM,KAAK,OAAO,EAAE,OAAO,IAAI,EAAE,WAAW,MAAM;AAClD,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,SAAS,EAAE,OAAO,UAAU;AAClC,SAAM,KAAK,SAAS,EAAE,OAAO,IAAI,SAAS;;;AAG9C,KAAI,kBAAkB,EACpB,OAAM,KAAK,WAAW;AAExB,QAAO,MAAM,KAAK,KAAK"}
@@ -0,0 +1,11 @@
1
+ import { EvalReport, RunOptions } from "./types.js";
2
+
3
+ //#region src/runner.d.ts
4
+
5
+ /**
6
+ * @stable
7
+ */
8
+ declare function runEvals<I, O>(opts: RunOptions<I, O>): Promise<EvalReport<I, O>>;
9
+ //#endregion
10
+ export { runEvals };
11
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","names":[],"sources":["../src/runner.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAsBsB,qBAAqB,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG"}
package/dist/runner.js ADDED
@@ -0,0 +1,181 @@
1
+ //#region src/runner.ts
2
+ /**
3
+ * @stable
4
+ */
5
+ async function runEvals(opts) {
6
+ const iterations = Math.max(1, opts.iterations ?? 1);
7
+ const concurrency = Math.max(1, opts.concurrency ?? 1);
8
+ const signal = opts.signal;
9
+ const cases = opts.dataset.cases;
10
+ const total = cases.length * iterations;
11
+ const queue = [];
12
+ for (let iter = 0; iter < iterations; iter++) for (let idx = 0; idx < cases.length; idx++) {
13
+ const sample = cases[idx];
14
+ if (sample === void 0) continue;
15
+ queue.push({
16
+ idx,
17
+ iter,
18
+ sample
19
+ });
20
+ }
21
+ const results = new Array(total);
22
+ let nextWorkIndex = 0;
23
+ let completed = 0;
24
+ async function worker() {
25
+ while (true) {
26
+ if (isAborted(signal)) return;
27
+ const myIndex = nextWorkIndex++;
28
+ if (myIndex >= queue.length) return;
29
+ const item = queue[myIndex];
30
+ if (item === void 0) return;
31
+ const baseId = item.sample.id ?? `case-${item.idx}`;
32
+ const caseId = iterations === 1 ? baseId : `${baseId}-iter-${item.iter}`;
33
+ const startedAt = Date.now();
34
+ let output;
35
+ try {
36
+ output = await opts.agent.run(item.sample.input, signal !== void 0 ? { signal } : void 0);
37
+ } catch (err) {
38
+ if (isAborted(signal)) return;
39
+ const durationMs$1 = Date.now() - startedAt;
40
+ results[myIndex] = {
41
+ caseId,
42
+ input: item.sample.input,
43
+ output: void 0,
44
+ durationMs: durationMs$1,
45
+ scores: opts.scorers.map((s) => ({
46
+ scorer: s.name,
47
+ result: {
48
+ pass: false,
49
+ reason: `agent.run threw: ${err instanceof Error ? err.message : String(err)}`
50
+ }
51
+ }))
52
+ };
53
+ completed += 1;
54
+ opts.onProgress?.({
55
+ index: completed,
56
+ total,
57
+ caseId,
58
+ durationMs: durationMs$1,
59
+ passed: false
60
+ });
61
+ continue;
62
+ }
63
+ const durationMs = Date.now() - startedAt;
64
+ const scores = [];
65
+ let allPassed = true;
66
+ for (const scorer of opts.scorers) {
67
+ const result = await safeScore(scorer, item.sample, output, durationMs);
68
+ scores.push({
69
+ scorer: scorer.name,
70
+ result
71
+ });
72
+ if (!result.pass) allPassed = false;
73
+ }
74
+ results[myIndex] = {
75
+ caseId,
76
+ input: item.sample.input,
77
+ output,
78
+ durationMs,
79
+ scores
80
+ };
81
+ completed += 1;
82
+ opts.onProgress?.({
83
+ index: completed,
84
+ total,
85
+ caseId,
86
+ durationMs,
87
+ passed: allPassed
88
+ });
89
+ }
90
+ }
91
+ const workerCount = Math.min(concurrency, queue.length);
92
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
93
+ if (isAborted(signal)) {
94
+ if (opts.throwOnAbort === true) throwIfAborted(signal);
95
+ return {
96
+ ...summarize(results.filter((r) => r !== void 0), opts.scorers),
97
+ aborted: true
98
+ };
99
+ }
100
+ return summarize(results, opts.scorers);
101
+ }
102
+ async function safeScore(scorer, sampleCase, output, durationMs) {
103
+ try {
104
+ return await scorer.score({
105
+ case: sampleCase,
106
+ output,
107
+ durationMs
108
+ });
109
+ } catch (err) {
110
+ return {
111
+ pass: false,
112
+ reason: `Scorer "${scorer.name}" threw: ${err instanceof Error ? err.message : String(err)}`
113
+ };
114
+ }
115
+ }
116
+ function summarize(results, scorers) {
117
+ const total = results.length;
118
+ let passed = 0;
119
+ let failed = 0;
120
+ let durationSum = 0;
121
+ const byScorer = {};
122
+ for (const scorer of scorers) byScorer[scorer.name] = {
123
+ passed: 0,
124
+ failed: 0,
125
+ scoreSum: 0,
126
+ scoreCount: 0
127
+ };
128
+ for (const r of results) {
129
+ durationSum += r.durationMs;
130
+ let passEntire = true;
131
+ for (const { scorer, result } of r.scores) {
132
+ const bucket = byScorer[scorer] ?? {
133
+ passed: 0,
134
+ failed: 0,
135
+ scoreSum: 0,
136
+ scoreCount: 0
137
+ };
138
+ if (result.pass) bucket.passed += 1;
139
+ else {
140
+ bucket.failed += 1;
141
+ passEntire = false;
142
+ }
143
+ if (typeof result.score === "number" && Number.isFinite(result.score)) {
144
+ bucket.scoreSum += result.score;
145
+ bucket.scoreCount += 1;
146
+ }
147
+ byScorer[scorer] = bucket;
148
+ }
149
+ if (passEntire) passed += 1;
150
+ else failed += 1;
151
+ }
152
+ const summaryByScorer = {};
153
+ for (const [scorer, b] of Object.entries(byScorer)) summaryByScorer[scorer] = {
154
+ passed: b.passed,
155
+ failed: b.failed,
156
+ avgScore: b.scoreCount === 0 ? null : b.scoreSum / b.scoreCount
157
+ };
158
+ return {
159
+ results,
160
+ summary: {
161
+ total,
162
+ passed,
163
+ failed,
164
+ avgDurationMs: total === 0 ? 0 : durationSum / total,
165
+ byScorer: Object.freeze(summaryByScorer)
166
+ }
167
+ };
168
+ }
169
+ function isAborted(signal) {
170
+ return signal?.aborted === true;
171
+ }
172
+ function throwIfAborted(signal) {
173
+ if (signal?.aborted === true) {
174
+ const reason = signal.reason;
175
+ throw reason instanceof Error ? reason : /* @__PURE__ */ new Error("Eval run aborted");
176
+ }
177
+ }
178
+
179
+ //#endregion
180
+ export { runEvals };
181
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","names":["queue: WorkItem[]","results: EvalCaseResult<I, O>[]","output: O","durationMs","scores: EvalCaseResult<I, O>['scores'][number][]","byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n >","summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n >"],"sources":["../src/runner.ts"],"sourcesContent":["/**\n * Parallel eval runner. Walks every case in the dataset (across N\n * iterations), executes them with bounded concurrency, applies every\n * scorer to each output, and returns an aggregated {@link EvalReport}.\n *\n * The runner respects:\n *\n * - `concurrency` — bounded worker pool. Default `1` (sequential).\n * - `signal` — propagated to `agent.run(...)` if the agent accepts a\n * second `{ signal }` argument; the runner stops issuing new work\n * on the next loop iteration.\n * - `onProgress` — invoked after every case with a heartbeat so\n * long-running terminals can paint a progress bar.\n *\n * @packageDocumentation\n */\n\nimport type { Case, EvalCaseResult, EvalReport, RunOptions, ScoreResult, Scorer } from './types.js';\n\n/**\n * @stable\n */\nexport async function runEvals<I, O>(opts: RunOptions<I, O>): Promise<EvalReport<I, O>> {\n const iterations = Math.max(1, opts.iterations ?? 1);\n const concurrency = Math.max(1, opts.concurrency ?? 1);\n const signal = opts.signal;\n const cases = opts.dataset.cases;\n const total = cases.length * iterations;\n\n type WorkItem = { idx: number; iter: number; sample: Case<I, O> };\n const queue: WorkItem[] = [];\n for (let iter = 0; iter < iterations; iter++) {\n for (let idx = 0; idx < cases.length; idx++) {\n const sample = cases[idx];\n if (sample === undefined) continue;\n queue.push({ idx, iter, sample });\n }\n }\n\n const results: EvalCaseResult<I, O>[] = new Array(total);\n let nextWorkIndex = 0;\n let completed = 0;\n\n async function worker(): Promise<void> {\n while (true) {\n // EB-14: stop dispatching new work on abort, but don't throw — whatever\n // already completed must survive into a partial report.\n if (isAborted(signal)) return;\n const myIndex = nextWorkIndex++;\n if (myIndex >= queue.length) return;\n const item = queue[myIndex];\n if (item === undefined) return;\n // EB-6: a caller-provided id must still be disambiguated per iteration —\n // otherwise iterations>1 emits multiple results under one caseId and\n // JUnit/HTML reporters render indistinguishable testcases.\n const baseId = item.sample.id ?? `case-${item.idx}`;\n const caseId = iterations === 1 ? baseId : `${baseId}-iter-${item.iter}`;\n const startedAt = Date.now();\n let output: O;\n try {\n output = await opts.agent.run(\n item.sample.input,\n signal !== undefined ? { signal } : undefined,\n );\n } catch (err) {\n // An abort-induced agent failure is not a real scorer failure — drop it\n // so it doesn't pollute the partial report with spurious fails (EB-14).\n if (isAborted(signal)) return;\n const durationMs = Date.now() - startedAt;\n const failResult: EvalCaseResult<I, O> = {\n caseId,\n input: item.sample.input,\n output: undefined as unknown as O,\n durationMs,\n scores: opts.scorers.map((s) => ({\n scorer: s.name,\n result: {\n pass: false,\n reason: `agent.run threw: ${err instanceof Error ? err.message : String(err)}`,\n },\n })),\n };\n results[myIndex] = failResult;\n completed += 1;\n opts.onProgress?.({\n index: completed,\n total,\n caseId,\n durationMs,\n passed: false,\n });\n continue;\n }\n const durationMs = Date.now() - startedAt;\n const scores: EvalCaseResult<I, O>['scores'][number][] = [];\n let allPassed = true;\n for (const scorer of opts.scorers) {\n // No mid-case abort check: once agent.run completes, finish scoring so\n // the case lands intact in the partial report rather than half-done.\n const result = await safeScore(scorer, item.sample, output, durationMs);\n scores.push({ scorer: scorer.name, result });\n if (!result.pass) allPassed = false;\n }\n results[myIndex] = {\n caseId,\n input: item.sample.input,\n output,\n durationMs,\n scores,\n };\n completed += 1;\n opts.onProgress?.({\n index: completed,\n total,\n caseId,\n durationMs,\n passed: allPassed,\n });\n }\n }\n\n const workerCount = Math.min(concurrency, queue.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n\n // EB-14: an aborted run resolves with a PARTIAL report (only the cases that\n // finished) marked `aborted: true`, instead of discarding everything — a\n // long judged run shouldn't lose all completed work to a Ctrl+C. Callers who\n // prefer the old throw-on-abort opt in with `throwOnAbort`.\n if (isAborted(signal)) {\n if (opts.throwOnAbort === true) throwIfAborted(signal);\n const done = results.filter((r): r is EvalCaseResult<I, O> => r !== undefined);\n return { ...summarize(done, opts.scorers), aborted: true };\n }\n return summarize(results, opts.scorers);\n}\n\nasync function safeScore<I, O>(\n scorer: Scorer<I, O>,\n sampleCase: Case<I, O>,\n output: O,\n durationMs: number,\n): Promise<ScoreResult> {\n try {\n return await scorer.score({ case: sampleCase, output, durationMs });\n } catch (err) {\n return {\n pass: false,\n reason: `Scorer \"${scorer.name}\" threw: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\nfunction summarize<I, O>(\n results: ReadonlyArray<EvalCaseResult<I, O>>,\n scorers: ReadonlyArray<Scorer<I, O>>,\n): EvalReport<I, O> {\n const total = results.length;\n let passed = 0;\n let failed = 0;\n let durationSum = 0;\n const byScorer: Record<\n string,\n { passed: number; failed: number; scoreSum: number; scoreCount: number }\n > = {};\n for (const scorer of scorers) {\n byScorer[scorer.name] = { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n }\n for (const r of results) {\n durationSum += r.durationMs;\n let passEntire = true;\n for (const { scorer, result } of r.scores) {\n const bucket = byScorer[scorer] ?? { passed: 0, failed: 0, scoreSum: 0, scoreCount: 0 };\n if (result.pass) bucket.passed += 1;\n else {\n bucket.failed += 1;\n passEntire = false;\n }\n if (typeof result.score === 'number' && Number.isFinite(result.score)) {\n bucket.scoreSum += result.score;\n bucket.scoreCount += 1;\n }\n byScorer[scorer] = bucket;\n }\n if (passEntire) passed += 1;\n else failed += 1;\n }\n const summaryByScorer: Record<\n string,\n { passed: number; failed: number; avgScore: number | null }\n > = {};\n for (const [scorer, b] of Object.entries(byScorer)) {\n summaryByScorer[scorer] = {\n passed: b.passed,\n failed: b.failed,\n avgScore: b.scoreCount === 0 ? null : b.scoreSum / b.scoreCount,\n };\n }\n return {\n results,\n summary: {\n total,\n passed,\n failed,\n avgDurationMs: total === 0 ? 0 : durationSum / total,\n byScorer: Object.freeze(summaryByScorer),\n },\n };\n}\n\n// A plain function call so TypeScript re-reads `signal.aborted` each time —\n// the flag flips during an `await` (external mutation) and inline\n// `signal?.aborted === true` checks would otherwise be narrowed away.\nfunction isAborted(signal: AbortSignal | undefined): boolean {\n return signal?.aborted === true;\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) {\n const reason = signal.reason;\n throw reason instanceof Error ? reason : new Error('Eval run aborted');\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { Scorer } from "@graphorin/observability/eval";
2
+
3
+ //#region src/scorers/code/exact-match.d.ts
4
+
5
+ /** @stable */
6
+ interface ExactMatchOptions {
7
+ /** Optional name override. Default `'exact-match'`. */
8
+ readonly name?: string;
9
+ /** When `true`, treat strings case-insensitively. Default `false`. */
10
+ readonly caseInsensitive?: boolean;
11
+ /** When `true`, trim whitespace before comparing strings. Default `false`. */
12
+ readonly trim?: boolean;
13
+ }
14
+ /**
15
+ * Build an exact-match scorer.
16
+ *
17
+ * @stable
18
+ */
19
+ declare function exactMatch<I = unknown, O = unknown>(options?: ExactMatchOptions): Scorer<I, O>;
20
+ //#endregion
21
+ export { ExactMatchOptions, exactMatch };
22
+ //# sourceMappingURL=exact-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-match.d.ts","names":[],"sources":["../../../src/scorers/code/exact-match.ts"],"sourcesContent":[],"mappings":";;;;;AA2BS,UAhBQ,iBAAA,CAgBR;;;;;;;;;;;;;iBAFO,+CACL,oBACR,OAAO,GAAG"}
@@ -0,0 +1,67 @@
1
+ //#region src/scorers/code/exact-match.ts
2
+ /**
3
+ * Build an exact-match scorer.
4
+ *
5
+ * @stable
6
+ */
7
+ function exactMatch(options = {}) {
8
+ return {
9
+ name: options.name ?? "exact-match",
10
+ async score({ case: c, output }) {
11
+ const expected = c.expected;
12
+ if (expected === void 0) return {
13
+ pass: false,
14
+ reason: "exactMatch requires a `case.expected` value."
15
+ };
16
+ const a = normalize(output, options);
17
+ const b = normalize(expected, options);
18
+ const pass = deepEqual(a, b);
19
+ if (pass) return {
20
+ pass,
21
+ score: 1
22
+ };
23
+ return {
24
+ pass,
25
+ score: 0,
26
+ reason: `expected ${truncate(JSON.stringify(b))}, received ${truncate(JSON.stringify(a))}`
27
+ };
28
+ }
29
+ };
30
+ }
31
+ function normalize(value, opts) {
32
+ if (typeof value === "string") {
33
+ let v = value;
34
+ if (opts.trim === true) v = v.trim();
35
+ if (opts.caseInsensitive === true) v = v.toLowerCase();
36
+ return v;
37
+ }
38
+ return value;
39
+ }
40
+ function deepEqual(a, b) {
41
+ if (a === b) return true;
42
+ if (typeof a !== typeof b) return false;
43
+ if (a === null || b === null) return a === b;
44
+ if (Array.isArray(a) && Array.isArray(b)) {
45
+ if (a.length !== b.length) return false;
46
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
47
+ return true;
48
+ }
49
+ if (typeof a === "object" && typeof b === "object") {
50
+ const aKeys = Object.keys(a).sort();
51
+ const bKeys = Object.keys(b).sort();
52
+ if (aKeys.length !== bKeys.length) return false;
53
+ for (let i = 0; i < aKeys.length; i++) {
54
+ if (aKeys[i] !== bKeys[i]) return false;
55
+ if (!deepEqual(a[aKeys[i]], b[bKeys[i]])) return false;
56
+ }
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ function truncate(s, max = 80) {
62
+ return s.length > max ? `${s.slice(0, max)}…` : s;
63
+ }
64
+
65
+ //#endregion
66
+ export { exactMatch };
67
+ //# sourceMappingURL=exact-match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-match.js","names":[],"sources":["../../../src/scorers/code/exact-match.ts"],"sourcesContent":["/**\n * `exactMatch` — passes when `output` deeply equals `case.expected`.\n * Useful for deterministic outputs (numeric / parsed JSON / classifier\n * label).\n *\n * @packageDocumentation\n */\n\nimport type { Scorer } from '@graphorin/observability/eval';\n\n/** @stable */\nexport interface ExactMatchOptions {\n /** Optional name override. Default `'exact-match'`. */\n readonly name?: string;\n /** When `true`, treat strings case-insensitively. Default `false`. */\n readonly caseInsensitive?: boolean;\n /** When `true`, trim whitespace before comparing strings. Default `false`. */\n readonly trim?: boolean;\n}\n\n/**\n * Build an exact-match scorer.\n *\n * @stable\n */\nexport function exactMatch<I = unknown, O = unknown>(\n options: ExactMatchOptions = {},\n): Scorer<I, O> {\n const name = options.name ?? 'exact-match';\n return {\n name,\n async score({ case: c, output }) {\n const expected = c.expected;\n if (expected === undefined) {\n return {\n pass: false,\n reason: 'exactMatch requires a `case.expected` value.',\n };\n }\n const a = normalize(output, options);\n const b = normalize(expected, options);\n const pass = deepEqual(a, b);\n if (pass) return { pass, score: 1 };\n return {\n pass,\n score: 0,\n reason: `expected ${truncate(JSON.stringify(b))}, received ${truncate(JSON.stringify(a))}`,\n };\n },\n };\n}\n\nfunction normalize(value: unknown, opts: ExactMatchOptions): unknown {\n if (typeof value === 'string') {\n let v = value;\n if (opts.trim === true) v = v.trim();\n if (opts.caseInsensitive === true) v = v.toLowerCase();\n return v;\n }\n return value;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a as Record<string, unknown>).sort();\n const bKeys = Object.keys(b as Record<string, unknown>).sort();\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i++) {\n if (aKeys[i] !== bKeys[i]) return false;\n if (\n !deepEqual(\n (a as Record<string, unknown>)[aKeys[i] as string],\n (b as Record<string, unknown>)[bKeys[i] as string],\n )\n )\n return false;\n }\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string, max: number = 80): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n"],"mappings":";;;;;;AAyBA,SAAgB,WACd,UAA6B,EAAE,EACjB;AAEd,QAAO;EACL,MAFW,QAAQ,QAAQ;EAG3B,MAAM,MAAM,EAAE,MAAM,GAAG,UAAU;GAC/B,MAAM,WAAW,EAAE;AACnB,OAAI,aAAa,OACf,QAAO;IACL,MAAM;IACN,QAAQ;IACT;GAEH,MAAM,IAAI,UAAU,QAAQ,QAAQ;GACpC,MAAM,IAAI,UAAU,UAAU,QAAQ;GACtC,MAAM,OAAO,UAAU,GAAG,EAAE;AAC5B,OAAI,KAAM,QAAO;IAAE;IAAM,OAAO;IAAG;AACnC,UAAO;IACL;IACA,OAAO;IACP,QAAQ,YAAY,SAAS,KAAK,UAAU,EAAE,CAAC,CAAC,aAAa,SAAS,KAAK,UAAU,EAAE,CAAC;IACzF;;EAEJ;;AAGH,SAAS,UAAU,OAAgB,MAAkC;AACnE,KAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,IAAI;AACR,MAAI,KAAK,SAAS,KAAM,KAAI,EAAE,MAAM;AACpC,MAAI,KAAK,oBAAoB,KAAM,KAAI,EAAE,aAAa;AACtD,SAAO;;AAET,QAAO;;AAGT,SAAS,UAAU,GAAY,GAAqB;AAClD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAClC,KAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAE,QAAO;AAErC,SAAO;;AAET,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;EAClD,MAAM,QAAQ,OAAO,KAAK,EAA6B,CAAC,MAAM;EAC9D,MAAM,QAAQ,OAAO,KAAK,EAA6B,CAAC,MAAM;AAC9D,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,OAAI,MAAM,OAAO,MAAM,GAAI,QAAO;AAClC,OACE,CAAC,UACE,EAA8B,MAAM,KACpC,EAA8B,MAAM,IACtC,CAED,QAAO;;AAEX,SAAO;;AAET,QAAO;;AAGT,SAAS,SAAS,GAAW,MAAc,IAAY;AACrD,QAAO,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK"}
@@ -0,0 +1,18 @@
1
+ import { Scorer } from "@graphorin/observability/eval";
2
+
3
+ //#region src/scorers/code/json-path.d.ts
4
+
5
+ /** @stable */
6
+ interface JsonPathOptions {
7
+ /** Dot-separated path (`'data.user.id'`). */
8
+ readonly path: string;
9
+ /** Expected value. Compared by deep-equality. */
10
+ readonly equals: unknown;
11
+ /** Optional name override. */
12
+ readonly name?: string;
13
+ }
14
+ /** @stable */
15
+ declare function jsonPath<I = unknown>(options: JsonPathOptions): Scorer<I, unknown>;
16
+ //#endregion
17
+ export { JsonPathOptions, jsonPath };
18
+ //# sourceMappingURL=json-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-path.d.ts","names":[],"sources":["../../../src/scorers/code/json-path.ts"],"sourcesContent":[],"mappings":";;;;;UAWiB,eAAA;;;;;;;;;iBAUD,+BAA+B,kBAAkB,OAAO"}
@@ -0,0 +1,71 @@
1
+ //#region src/scorers/code/json-path.ts
2
+ /** @stable */
3
+ function jsonPath(options) {
4
+ return {
5
+ name: options.name ?? `json-path:${options.path}`,
6
+ async score({ output }) {
7
+ const actual = readPath(output, options.path);
8
+ const pass = deepEqual(actual, options.equals);
9
+ if (pass) return {
10
+ pass,
11
+ score: 1
12
+ };
13
+ return {
14
+ pass,
15
+ score: 0,
16
+ reason: `at '${options.path}' expected ${truncate(stringifySafe(options.equals))}, received ${truncate(stringifySafe(actual))}.`
17
+ };
18
+ }
19
+ };
20
+ }
21
+ function readPath(root, path) {
22
+ if (path.length === 0) return root;
23
+ const segments = path.split(".");
24
+ let cursor = root;
25
+ for (const segment of segments) {
26
+ if (cursor === null || cursor === void 0) return void 0;
27
+ if (Array.isArray(cursor)) {
28
+ const idx = Number.parseInt(segment, 10);
29
+ if (!Number.isFinite(idx)) return void 0;
30
+ cursor = cursor[idx];
31
+ } else if (typeof cursor === "object") cursor = cursor[segment];
32
+ else return;
33
+ }
34
+ return cursor;
35
+ }
36
+ function deepEqual(a, b) {
37
+ if (a === b) return true;
38
+ if (typeof a !== typeof b) return false;
39
+ if (a === null || b === null) return a === b;
40
+ if (Array.isArray(a) && Array.isArray(b)) {
41
+ if (a.length !== b.length) return false;
42
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
43
+ return true;
44
+ }
45
+ if (typeof a === "object" && typeof b === "object") {
46
+ const aKeys = Object.keys(a).sort();
47
+ const bKeys = Object.keys(b).sort();
48
+ if (aKeys.length !== bKeys.length) return false;
49
+ for (let i = 0; i < aKeys.length; i++) {
50
+ if (aKeys[i] !== bKeys[i]) return false;
51
+ if (!deepEqual(a[aKeys[i]], b[bKeys[i]])) return false;
52
+ }
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+ function truncate(s, max = 80) {
58
+ return s.length > max ? `${s.slice(0, max)}…` : s;
59
+ }
60
+ function stringifySafe(value) {
61
+ if (value === void 0) return "undefined";
62
+ try {
63
+ return JSON.stringify(value) ?? String(value);
64
+ } catch {
65
+ return String(value);
66
+ }
67
+ }
68
+
69
+ //#endregion
70
+ export { jsonPath };
71
+ //# sourceMappingURL=json-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-path.js","names":["cursor: unknown"],"sources":["../../../src/scorers/code/json-path.ts"],"sourcesContent":["/**\n * `jsonPath` — passes when the value at a JSON-pointer-shaped path\n * deep-equals the caller-supplied target. Supports the dot-notation\n * subset (`a.b.c`) and array indices (`a.0.name`).\n *\n * @packageDocumentation\n */\n\nimport type { Scorer } from '@graphorin/observability/eval';\n\n/** @stable */\nexport interface JsonPathOptions {\n /** Dot-separated path (`'data.user.id'`). */\n readonly path: string;\n /** Expected value. Compared by deep-equality. */\n readonly equals: unknown;\n /** Optional name override. */\n readonly name?: string;\n}\n\n/** @stable */\nexport function jsonPath<I = unknown>(options: JsonPathOptions): Scorer<I, unknown> {\n const name = options.name ?? `json-path:${options.path}`;\n return {\n name,\n async score({ output }) {\n const actual = readPath(output, options.path);\n const pass = deepEqual(actual, options.equals);\n if (pass) return { pass, score: 1 };\n return {\n pass,\n score: 0,\n reason:\n `at '${options.path}' expected ${truncate(stringifySafe(options.equals))}, ` +\n `received ${truncate(stringifySafe(actual))}.`,\n };\n },\n };\n}\n\nfunction readPath(root: unknown, path: string): unknown {\n if (path.length === 0) return root;\n const segments = path.split('.');\n let cursor: unknown = root;\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined;\n if (Array.isArray(cursor)) {\n const idx = Number.parseInt(segment, 10);\n if (!Number.isFinite(idx)) return undefined;\n cursor = cursor[idx];\n } else if (typeof cursor === 'object') {\n cursor = (cursor as Record<string, unknown>)[segment];\n } else {\n return undefined;\n }\n }\n return cursor;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a as Record<string, unknown>).sort();\n const bKeys = Object.keys(b as Record<string, unknown>).sort();\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i++) {\n if (aKeys[i] !== bKeys[i]) return false;\n if (\n !deepEqual(\n (a as Record<string, unknown>)[aKeys[i] as string],\n (b as Record<string, unknown>)[bKeys[i] as string],\n )\n )\n return false;\n }\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string, max: number = 80): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n\nfunction stringifySafe(value: unknown): string {\n if (value === undefined) return 'undefined';\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":";;AAqBA,SAAgB,SAAsB,SAA8C;AAElF,QAAO;EACL,MAFW,QAAQ,QAAQ,aAAa,QAAQ;EAGhD,MAAM,MAAM,EAAE,UAAU;GACtB,MAAM,SAAS,SAAS,QAAQ,QAAQ,KAAK;GAC7C,MAAM,OAAO,UAAU,QAAQ,QAAQ,OAAO;AAC9C,OAAI,KAAM,QAAO;IAAE;IAAM,OAAO;IAAG;AACnC,UAAO;IACL;IACA,OAAO;IACP,QACE,OAAO,QAAQ,KAAK,aAAa,SAAS,cAAc,QAAQ,OAAO,CAAC,CAAC,aAC7D,SAAS,cAAc,OAAO,CAAC,CAAC;IAC/C;;EAEJ;;AAGH,SAAS,SAAS,MAAe,MAAuB;AACtD,KAAI,KAAK,WAAW,EAAG,QAAO;CAC9B,MAAM,WAAW,KAAK,MAAM,IAAI;CAChC,IAAIA,SAAkB;AACtB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,WAAW,QAAQ,WAAW,OAAW,QAAO;AACpD,MAAI,MAAM,QAAQ,OAAO,EAAE;GACzB,MAAM,MAAM,OAAO,SAAS,SAAS,GAAG;AACxC,OAAI,CAAC,OAAO,SAAS,IAAI,CAAE,QAAO;AAClC,YAAS,OAAO;aACP,OAAO,WAAW,SAC3B,UAAU,OAAmC;MAE7C;;AAGJ,QAAO;;AAGT,SAAS,UAAU,GAAY,GAAqB;AAClD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAClC,KAAI,MAAM,QAAQ,MAAM,KAAM,QAAO,MAAM;AAC3C,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAE,QAAO;AAErC,SAAO;;AAET,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;EAClD,MAAM,QAAQ,OAAO,KAAK,EAA6B,CAAC,MAAM;EAC9D,MAAM,QAAQ,OAAO,KAAK,EAA6B,CAAC,MAAM;AAC9D,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,OAAI,MAAM,OAAO,MAAM,GAAI,QAAO;AAClC,OACE,CAAC,UACE,EAA8B,MAAM,KACpC,EAA8B,MAAM,IACtC,CAED,QAAO;;AAEX,SAAO;;AAET,QAAO;;AAGT,SAAS,SAAS,GAAW,MAAc,IAAY;AACrD,QAAO,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK;;AAGlD,SAAS,cAAc,OAAwB;AAC7C,KAAI,UAAU,OAAW,QAAO;AAChC,KAAI;AACF,SAAO,KAAK,UAAU,MAAM,IAAI,OAAO,MAAM;SACvC;AACN,SAAO,OAAO,MAAM"}
@@ -0,0 +1,18 @@
1
+ import { Case, ScoreResult, Scorer } from "@graphorin/observability/eval";
2
+
3
+ //#region src/scorers/code/predicate.d.ts
4
+
5
+ /** @stable */
6
+ interface PredicateOptions<I, O> {
7
+ readonly name: string;
8
+ readonly check: (args: {
9
+ readonly case: Case<I, O>;
10
+ readonly output: O;
11
+ readonly durationMs: number;
12
+ }) => Promise<boolean | ScoreResult> | boolean | ScoreResult;
13
+ }
14
+ /** @stable */
15
+ declare function predicate<I = unknown, O = unknown>(options: PredicateOptions<I, O>): Scorer<I, O>;
16
+ //#endregion
17
+ export { PredicateOptions, predicate };
18
+ //# sourceMappingURL=predicate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate.d.ts","names":[],"sources":["../../../src/scorers/code/predicate.ts"],"sourcesContent":[],"mappings":";;;;;AAgB0B,UANT,gBAMS,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;EAAlB,SAAA,IAAA,EAAA,MAAA;EAA2C,SAAA,KAAA,EAAA,CAAA,IAAA,EAAA;IAAW,SAAA,IAAA,EAH3C,IAG2C,CAHtC,CAGsC,EAHnC,CAGmC,CAAA;IAI9C,SAAS,MAAA,EANJ,CAMI;IAAqD,SAAA,UAAA,EAAA,MAAA;EAAG,CAAA,EAAA,GAJzE,OAIyE,CAAA,OAAA,GAJvD,WAIuD,CAAA,GAAA,OAAA,GAJ9B,WAI8B;;;AAAe,iBAAhF,SAAgF,CAAA,IAAA,OAAA,EAAA,IAAA,OAAA,CAAA,CAAA,OAAA,EAAnC,gBAAmC,CAAlB,CAAkB,EAAf,CAAe,CAAA,CAAA,EAAV,MAAU,CAAH,CAAG,EAAA,CAAA,CAAA"}
@@ -0,0 +1,19 @@
1
+ //#region src/scorers/code/predicate.ts
2
+ /** @stable */
3
+ function predicate(options) {
4
+ return {
5
+ name: options.name,
6
+ async score(args) {
7
+ const result = await options.check(args);
8
+ if (typeof result === "boolean") return {
9
+ pass: result,
10
+ score: result ? 1 : 0
11
+ };
12
+ return result;
13
+ }
14
+ };
15
+ }
16
+
17
+ //#endregion
18
+ export { predicate };
19
+ //# sourceMappingURL=predicate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate.js","names":[],"sources":["../../../src/scorers/code/predicate.ts"],"sourcesContent":["/**\n * `predicate` — passes when the caller-supplied predicate returns\n * truthy. The escape hatch for ad-hoc, project-specific scoring rules.\n *\n * @packageDocumentation\n */\n\nimport type { Case, ScoreResult, Scorer } from '@graphorin/observability/eval';\n\n/** @stable */\nexport interface PredicateOptions<I, O> {\n readonly name: string;\n readonly check: (args: {\n readonly case: Case<I, O>;\n readonly output: O;\n readonly durationMs: number;\n }) => Promise<boolean | ScoreResult> | boolean | ScoreResult;\n}\n\n/** @stable */\nexport function predicate<I = unknown, O = unknown>(options: PredicateOptions<I, O>): Scorer<I, O> {\n return {\n name: options.name,\n async score(args) {\n const result = await options.check(args);\n if (typeof result === 'boolean') {\n return { pass: result, score: result ? 1 : 0 };\n }\n return result;\n },\n };\n}\n"],"mappings":";;AAoBA,SAAgB,UAAoC,SAA+C;AACjG,QAAO;EACL,MAAM,QAAQ;EACd,MAAM,MAAM,MAAM;GAChB,MAAM,SAAS,MAAM,QAAQ,MAAM,KAAK;AACxC,OAAI,OAAO,WAAW,UACpB,QAAO;IAAE,MAAM;IAAQ,OAAO,SAAS,IAAI;IAAG;AAEhD,UAAO;;EAEV"}
@@ -0,0 +1,15 @@
1
+ import { Scorer } from "@graphorin/observability/eval";
2
+
3
+ //#region src/scorers/code/regex.d.ts
4
+
5
+ /** @stable */
6
+ interface RegexMatchOptions {
7
+ readonly pattern: RegExp;
8
+ /** Optional name override. Default `'regex-match'`. */
9
+ readonly name?: string;
10
+ }
11
+ /** @stable */
12
+ declare function regexMatch<I = unknown>(options: RegexMatchOptions): Scorer<I, unknown>;
13
+ //#endregion
14
+ export { RegexMatchOptions, regexMatch };
15
+ //# sourceMappingURL=regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex.d.ts","names":[],"sources":["../../../src/scorers/code/regex.ts"],"sourcesContent":[],"mappings":";;;;;AAiB2E,UAP1D,iBAAA,CAO0D;oBANvD;;;;;iBAMJ,iCAAiC,oBAAoB,OAAO"}