@deepagents/evals 7.0.2 → 7.1.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/index.js CHANGED
@@ -51,6 +51,15 @@ async function fetchPage(url) {
51
51
  );
52
52
  }
53
53
  const text = await response.text();
54
+ const page = parseJson(text, url);
55
+ if (!isHfRowsPage(page)) {
56
+ throw new Error(
57
+ `HuggingFace API returned an unexpected rows page from ${url}: ${text.slice(0, 200)}`
58
+ );
59
+ }
60
+ return page;
61
+ }
62
+ function parseJson(text, url) {
54
63
  try {
55
64
  return JSON.parse(text);
56
65
  } catch {
@@ -59,6 +68,14 @@ async function fetchPage(url) {
59
68
  );
60
69
  }
61
70
  }
71
+ function isRecord(value) {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
+ }
74
+ function isHfRowsPage(value) {
75
+ return isRecord(value) && typeof value.num_rows_total === "number" && Array.isArray(value.rows) && value.rows.every(
76
+ (entry) => isRecord(entry) && typeof entry.row_idx === "number" && isRecord(entry.row)
77
+ );
78
+ }
62
79
 
63
80
  // packages/evals/src/dataset/record-selection.ts
64
81
  function parsePositiveInt(token) {
@@ -250,6 +267,9 @@ function parseCSVLine(line) {
250
267
  fields.push(current);
251
268
  return fields;
252
269
  }
270
+ function isRecord2(value) {
271
+ return typeof value === "object" && value !== null && !Array.isArray(value);
272
+ }
253
273
  function loadJSON(filePath) {
254
274
  return async function* () {
255
275
  const content = await readFile(filePath, "utf-8");
@@ -257,7 +277,14 @@ function loadJSON(filePath) {
257
277
  if (!Array.isArray(data)) {
258
278
  throw new Error(`JSON file "${filePath}" does not contain an array`);
259
279
  }
260
- yield* data;
280
+ for (const [index, row] of data.entries()) {
281
+ if (!isRecord2(row)) {
282
+ throw new Error(
283
+ `JSON file "${filePath}" has a non-object row at index ${index}`
284
+ );
285
+ }
286
+ yield row;
287
+ }
261
288
  };
262
289
  }
263
290
  function loadJSONL(filePath) {
@@ -269,9 +296,14 @@ function loadJSONL(filePath) {
269
296
  try {
270
297
  for await (const line of rl) {
271
298
  const trimmed = line.trim();
272
- if (trimmed) {
273
- yield JSON.parse(trimmed);
299
+ if (!trimmed) continue;
300
+ const row = JSON.parse(trimmed);
301
+ if (!isRecord2(row)) {
302
+ throw new Error(
303
+ `JSONL file "${filePath}" has a non-object row: ${trimmed.slice(0, 200)}`
304
+ );
274
305
  }
306
+ yield row;
275
307
  }
276
308
  } finally {
277
309
  rl.close();
@@ -410,6 +442,9 @@ var LevenshteinSimilarity = class {
410
442
  return previous[target.length];
411
443
  }
412
444
  };
445
+ function isRecord3(value) {
446
+ return typeof value === "object" && value !== null && !Array.isArray(value);
447
+ }
413
448
  function deepEqual(a, b) {
414
449
  if (a === b) return true;
415
450
  if (a == null || b == null) return false;
@@ -418,15 +453,13 @@ function deepEqual(a, b) {
418
453
  if (!Array.isArray(b) || a.length !== b.length) return false;
419
454
  return a.every((val, i) => deepEqual(val, b[i]));
420
455
  }
421
- if (typeof a === "object") {
456
+ if (isRecord3(a)) {
457
+ if (!isRecord3(b)) return false;
422
458
  const keysA = Object.keys(a).sort();
423
459
  const keysB = Object.keys(b).sort();
424
460
  if (keysA.length !== keysB.length) return false;
425
461
  return keysA.every(
426
- (key, i) => keysB[i] === key && deepEqual(
427
- a[key],
428
- b[key]
429
- )
462
+ (key, i) => keysB[i] === key && deepEqual(a[key], b[key])
430
463
  );
431
464
  }
432
465
  return false;
@@ -544,6 +577,11 @@ var LlmJudge = class _LlmJudge {
544
577
  throw new Error(`Unexpected judge tool call: ${toolCall.function.name}`);
545
578
  }
546
579
  const payload = JSON.parse(toolCall.function.arguments);
580
+ if (!isRecord3(payload)) {
581
+ throw new Error(
582
+ `Judge sent select_choice arguments that are not an object: ${toolCall.function.arguments}`
583
+ );
584
+ }
547
585
  const choice = typeof payload.choice === "string" ? payload.choice.trim() : "";
548
586
  const score = this.#choices[choice];
549
587
  if (score === void 0) {
@@ -609,12 +647,135 @@ function weighted(config) {
609
647
  // packages/evals/src/store/index.ts
610
648
  import { mkdirSync } from "node:fs";
611
649
  import { dirname } from "node:path";
612
- import { DatabaseSync } from "node:sqlite";
650
+ import {
651
+ DatabaseSync
652
+ } from "node:sqlite";
613
653
 
614
654
  // packages/evals/src/store/ddl.sqlite.sql
615
655
  var ddl_sqlite_default = "PRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS suites (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)\n);\n\nCREATE TABLE IF NOT EXISTS runs (\n id TEXT PRIMARY KEY,\n suite_id TEXT NOT NULL,\n name TEXT NOT NULL,\n model TEXT NOT NULL,\n config TEXT,\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed')),\n summary TEXT,\n FOREIGN KEY (suite_id) REFERENCES suites(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_suite_id ON runs(suite_id);\nCREATE INDEX IF NOT EXISTS idx_runs_started_at ON runs(started_at);\n\nCREATE TABLE IF NOT EXISTS cases (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n input TEXT NOT NULL,\n output TEXT,\n expected TEXT,\n latency_ms INTEGER,\n tokens_in INTEGER,\n tokens_out INTEGER,\n error TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_cases_run_id ON cases(run_id);\n\nCREATE TABLE IF NOT EXISTS scores (\n id TEXT PRIMARY KEY,\n case_id TEXT NOT NULL,\n scorer_name TEXT NOT NULL,\n score REAL NOT NULL,\n reason TEXT,\n FOREIGN KEY (case_id) REFERENCES cases(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_scores_case_id ON scores(case_id);\n\nCREATE TABLE IF NOT EXISTS prompts (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n content TEXT NOT NULL,\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_prompts_created_at ON prompts(created_at);\n";
616
656
 
617
657
  // packages/evals/src/store/index.ts
658
+ var RUN_STATUSES = ["running", "completed", "failed"];
659
+ function isRunStatus(value) {
660
+ return RUN_STATUSES.some((status) => status === value);
661
+ }
662
+ function matchesColumn(value, type) {
663
+ switch (type) {
664
+ case "text":
665
+ return typeof value === "string";
666
+ case "text?":
667
+ return value === null || typeof value === "string";
668
+ case "number":
669
+ return typeof value === "number";
670
+ case "number?":
671
+ return value === null || typeof value === "number";
672
+ }
673
+ }
674
+ function mismatchedColumns(row, columns) {
675
+ return Object.entries(columns).filter(([name, type]) => !matchesColumn(row[name], type)).map(([name]) => name);
676
+ }
677
+ function hasColumns(row, columns) {
678
+ return mismatchedColumns(row, columns).length === 0;
679
+ }
680
+ function readRow(row, columns, sql2) {
681
+ if (hasColumns(row, columns)) return row;
682
+ throw new TypeError(
683
+ `Unexpected values in columns ${mismatchedColumns(row, columns).join(", ")} of a row from: ${oneLine(sql2)}`
684
+ );
685
+ }
686
+ function oneLine(sql2) {
687
+ return sql2.replace(/\s+/g, " ").trim();
688
+ }
689
+ function isRecord4(value) {
690
+ return typeof value === "object" && value !== null && !Array.isArray(value);
691
+ }
692
+ function isRunSummary(value) {
693
+ return isRecord4(value) && typeof value.totalCases === "number" && typeof value.passCount === "number" && typeof value.failCount === "number" && typeof value.totalLatencyMs === "number" && typeof value.totalTokensIn === "number" && typeof value.totalTokensOut === "number" && isRecord4(value.meanScores) && Object.values(value.meanScores).every((score) => typeof score === "number");
694
+ }
695
+ function parseJson2(text, guard, source) {
696
+ const value = JSON.parse(text);
697
+ if (guard(value)) return value;
698
+ throw new TypeError(`Unexpected JSON in ${source}`);
699
+ }
700
+ var suiteColumns = {
701
+ id: "text",
702
+ name: "text",
703
+ created_at: "number"
704
+ };
705
+ var runColumns = {
706
+ id: "text",
707
+ suite_id: "text",
708
+ name: "text",
709
+ model: "text",
710
+ config: "text?",
711
+ started_at: "number",
712
+ finished_at: "number?",
713
+ status: "text",
714
+ summary: "text?"
715
+ };
716
+ var caseColumns = {
717
+ id: "text",
718
+ run_id: "text",
719
+ idx: "number",
720
+ input: "text",
721
+ output: "text?",
722
+ expected: "text?",
723
+ latency_ms: "number?",
724
+ tokens_in: "number?",
725
+ tokens_out: "number?",
726
+ error: "text?"
727
+ };
728
+ var promptColumns = {
729
+ id: "text",
730
+ name: "text",
731
+ version: "number",
732
+ content: "text",
733
+ created_at: "number"
734
+ };
735
+ function toSuite(row) {
736
+ return { id: row.id, name: row.name, created_at: row.created_at };
737
+ }
738
+ function toRun(row) {
739
+ if (!isRunStatus(row.status)) {
740
+ throw new TypeError(`Unexpected status "${row.status}" of run ${row.id}`);
741
+ }
742
+ return {
743
+ id: row.id,
744
+ suite_id: row.suite_id,
745
+ name: row.name,
746
+ model: row.model,
747
+ config: row.config ? parseJson2(row.config, isRecord4, `config of run ${row.id}`) : null,
748
+ started_at: row.started_at,
749
+ finished_at: row.finished_at,
750
+ status: row.status,
751
+ summary: row.summary ? parseJson2(row.summary, isRunSummary, `summary of run ${row.id}`) : null
752
+ };
753
+ }
754
+ function toCase(row) {
755
+ const input = JSON.parse(row.input);
756
+ const expected = row.expected ? JSON.parse(row.expected) : null;
757
+ return {
758
+ id: row.id,
759
+ run_id: row.run_id,
760
+ idx: row.idx,
761
+ input,
762
+ output: row.output,
763
+ expected,
764
+ latency_ms: row.latency_ms,
765
+ tokens_in: row.tokens_in,
766
+ tokens_out: row.tokens_out,
767
+ error: row.error
768
+ };
769
+ }
770
+ function toPrompt(row) {
771
+ return {
772
+ id: row.id,
773
+ name: row.name,
774
+ version: row.version,
775
+ content: row.content,
776
+ created_at: row.created_at
777
+ };
778
+ }
618
779
  var RunStore = class {
619
780
  #db;
620
781
  #statements = /* @__PURE__ */ new Map();
@@ -626,6 +787,21 @@ var RunStore = class {
626
787
  }
627
788
  return stmt;
628
789
  }
790
+ #get(columns, sql2, ...params) {
791
+ const row = this.#stmt(sql2).get(...params);
792
+ return row === void 0 ? void 0 : readRow(row, columns, sql2);
793
+ }
794
+ /** Reads an aggregate query, which returns exactly one row. */
795
+ #one(columns, sql2, ...params) {
796
+ const row = this.#get(columns, sql2, ...params);
797
+ if (row === void 0) {
798
+ throw new Error(`Expected one row from: ${oneLine(sql2)}`);
799
+ }
800
+ return row;
801
+ }
802
+ #all(columns, sql2, ...params) {
803
+ return this.#stmt(sql2).all(...params).map((row) => readRow(row, columns, sql2));
804
+ }
629
805
  #transaction(fn) {
630
806
  this.#db.exec("BEGIN TRANSACTION");
631
807
  try {
@@ -653,7 +829,7 @@ var RunStore = class {
653
829
  );
654
830
  }
655
831
  #migratePromptsTableIfNeeded() {
656
- const columns = this.#stmt("PRAGMA table_info(prompts)").all();
832
+ const columns = this.#all({ name: "text" }, "PRAGMA table_info(prompts)");
657
833
  if (columns.length === 0) return;
658
834
  if (columns.some((column) => column.name === "version")) return;
659
835
  this.#transaction(() => {
@@ -683,13 +859,17 @@ var RunStore = class {
683
859
  });
684
860
  }
685
861
  #migrateRunsTableToSuiteRequired() {
686
- const runColumns = this.#stmt("PRAGMA table_info(runs)").all();
687
- if (runColumns.length === 0) return;
688
- const suiteColumn = runColumns.find((column) => column.name === "suite_id");
862
+ const columns = this.#all(
863
+ { name: "text", notnull: "number" },
864
+ "PRAGMA table_info(runs)"
865
+ );
866
+ if (columns.length === 0) return;
867
+ const suiteColumn = columns.find((column) => column.name === "suite_id");
689
868
  const hasNonNullSuite = suiteColumn?.notnull === 1;
690
- const runForeignKeys = this.#stmt(
869
+ const runForeignKeys = this.#all(
870
+ { from: "text", on_delete: "text", table: "text" },
691
871
  "PRAGMA foreign_key_list(runs)"
692
- ).all();
872
+ );
693
873
  const suiteForeignKey = runForeignKeys.find(
694
874
  (fk) => fk.from === "suite_id" && fk.table === "suites"
695
875
  );
@@ -738,8 +918,12 @@ var RunStore = class {
738
918
  return { id, name, created_at: now };
739
919
  }
740
920
  getSuite(id) {
741
- const row = this.#stmt("SELECT * FROM suites WHERE id = ?").get(id);
742
- return row ?? void 0;
921
+ const row = this.#get(
922
+ suiteColumns,
923
+ "SELECT * FROM suites WHERE id = ?",
924
+ id
925
+ );
926
+ return row ? toSuite(row) : void 0;
743
927
  }
744
928
  renameSuite(id, name) {
745
929
  this.#stmt("UPDATE suites SET name = ? WHERE id = ?").run(name, id);
@@ -799,77 +983,45 @@ var RunStore = class {
799
983
  });
800
984
  }
801
985
  getRun(runId) {
802
- const row = this.#stmt("SELECT * FROM runs WHERE id = ?").get(runId);
803
- if (!row) return void 0;
804
- return {
805
- id: row.id,
806
- suite_id: row.suite_id,
807
- name: row.name,
808
- model: row.model,
809
- config: row.config ? JSON.parse(row.config) : null,
810
- started_at: row.started_at,
811
- finished_at: row.finished_at,
812
- status: row.status,
813
- summary: row.summary ? JSON.parse(row.summary) : null
814
- };
986
+ const row = this.#get(runColumns, "SELECT * FROM runs WHERE id = ?", runId);
987
+ return row ? toRun(row) : void 0;
815
988
  }
816
989
  listRuns(suiteId) {
817
- const sql2 = suiteId ? "SELECT * FROM runs WHERE suite_id = ? ORDER BY started_at" : "SELECT * FROM runs ORDER BY started_at";
818
- const rows = suiteId ? this.#stmt(sql2).all(suiteId) : this.#stmt(sql2).all();
819
- return rows.map((row) => ({
820
- id: row.id,
821
- suite_id: row.suite_id,
822
- name: row.name,
823
- model: row.model,
824
- config: row.config ? JSON.parse(row.config) : null,
825
- started_at: row.started_at,
826
- finished_at: row.finished_at,
827
- status: row.status,
828
- summary: row.summary ? JSON.parse(row.summary) : null
829
- }));
990
+ const rows = suiteId ? this.#all(
991
+ runColumns,
992
+ "SELECT * FROM runs WHERE suite_id = ? ORDER BY started_at",
993
+ suiteId
994
+ ) : this.#all(runColumns, "SELECT * FROM runs ORDER BY started_at");
995
+ return rows.map(toRun);
830
996
  }
831
997
  getCases(runId) {
832
- const rows = this.#stmt(
833
- "SELECT * FROM cases WHERE run_id = ? ORDER BY idx"
834
- ).all(runId);
835
- return rows.map((row) => ({
836
- id: row.id,
837
- run_id: row.run_id,
838
- idx: row.idx,
839
- input: JSON.parse(row.input),
840
- output: row.output,
841
- expected: row.expected ? JSON.parse(row.expected) : null,
842
- latency_ms: row.latency_ms,
843
- tokens_in: row.tokens_in,
844
- tokens_out: row.tokens_out,
845
- error: row.error
846
- }));
998
+ return this.#all(
999
+ caseColumns,
1000
+ "SELECT * FROM cases WHERE run_id = ? ORDER BY idx",
1001
+ runId
1002
+ ).map(toCase);
847
1003
  }
848
1004
  getFailingCases(runId, threshold = 0.5) {
849
- const rows = this.#stmt(
1005
+ const rows = this.#all(
1006
+ {
1007
+ ...caseColumns,
1008
+ scorer_name: "text",
1009
+ score: "number",
1010
+ score_reason: "text?"
1011
+ },
850
1012
  `SELECT c.*, s.scorer_name, s.score, s.reason as score_reason
851
1013
  FROM cases c
852
1014
  JOIN scores s ON s.case_id = c.id
853
1015
  WHERE c.run_id = ? AND s.score < ?
854
- ORDER BY c.idx`
855
- ).all(runId, threshold);
1016
+ ORDER BY c.idx`,
1017
+ runId,
1018
+ threshold
1019
+ );
856
1020
  const caseMap = /* @__PURE__ */ new Map();
857
1021
  for (const row of rows) {
858
1022
  let c = caseMap.get(row.id);
859
1023
  if (!c) {
860
- c = {
861
- id: row.id,
862
- run_id: row.run_id,
863
- idx: row.idx,
864
- input: JSON.parse(row.input),
865
- output: row.output,
866
- expected: row.expected ? JSON.parse(row.expected) : null,
867
- latency_ms: row.latency_ms,
868
- tokens_in: row.tokens_in,
869
- tokens_out: row.tokens_out,
870
- error: row.error,
871
- scores: []
872
- };
1024
+ c = { ...toCase(row), scores: [] };
873
1025
  caseMap.set(row.id, c);
874
1026
  }
875
1027
  c.scores.push({
@@ -881,33 +1033,44 @@ var RunStore = class {
881
1033
  return Array.from(caseMap.values());
882
1034
  }
883
1035
  getRunSummary(runId, threshold = 0.5) {
884
- const totals = this.#stmt(
1036
+ const totals = this.#one(
1037
+ {
1038
+ totalCases: "number",
1039
+ totalLatencyMs: "number",
1040
+ totalTokensIn: "number",
1041
+ totalTokensOut: "number"
1042
+ },
885
1043
  `SELECT
886
1044
  COUNT(DISTINCT c.id) as totalCases,
887
1045
  COALESCE(SUM(c.latency_ms), 0) as totalLatencyMs,
888
1046
  COALESCE(SUM(c.tokens_in), 0) as totalTokensIn,
889
1047
  COALESCE(SUM(c.tokens_out), 0) as totalTokensOut
890
- FROM cases c WHERE c.run_id = ?`
891
- ).get(runId);
892
- const scorerMeans = this.#stmt(
1048
+ FROM cases c WHERE c.run_id = ?`,
1049
+ runId
1050
+ );
1051
+ const scorerMeans = this.#all(
1052
+ { scorer_name: "text", meanScore: "number" },
893
1053
  `SELECT s.scorer_name, AVG(s.score) as meanScore
894
1054
  FROM scores s
895
1055
  JOIN cases c ON c.id = s.case_id
896
1056
  WHERE c.run_id = ?
897
- GROUP BY s.scorer_name`
898
- ).all(runId);
1057
+ GROUP BY s.scorer_name`,
1058
+ runId
1059
+ );
899
1060
  const meanScores = {};
900
1061
  for (const row of scorerMeans) {
901
1062
  meanScores[row.scorer_name] = row.meanScore;
902
1063
  }
903
- const passFail = this.#stmt(
1064
+ const passFail = this.#all(
1065
+ { id: "text", minScore: "number" },
904
1066
  `SELECT c.id,
905
1067
  MIN(s.score) as minScore
906
1068
  FROM cases c
907
1069
  JOIN scores s ON s.case_id = c.id
908
1070
  WHERE c.run_id = ?
909
- GROUP BY c.id`
910
- ).all(runId);
1071
+ GROUP BY c.id`,
1072
+ runId
1073
+ );
911
1074
  let passCount = 0;
912
1075
  let failCount = 0;
913
1076
  for (const row of passFail) {
@@ -925,71 +1088,61 @@ var RunStore = class {
925
1088
  };
926
1089
  }
927
1090
  findSuiteByName(name) {
928
- const row = this.#stmt(
929
- "SELECT * FROM suites WHERE name = ? ORDER BY created_at DESC LIMIT 1"
930
- ).get(name);
931
- return row ?? void 0;
1091
+ const row = this.#get(
1092
+ suiteColumns,
1093
+ "SELECT * FROM suites WHERE name = ? ORDER BY created_at DESC LIMIT 1",
1094
+ name
1095
+ );
1096
+ return row ? toSuite(row) : void 0;
932
1097
  }
933
1098
  getLatestCompletedRun(suiteId, model) {
934
- const sql2 = model ? "SELECT * FROM runs WHERE suite_id = ? AND status = ? AND model = ? ORDER BY started_at DESC LIMIT 1" : "SELECT * FROM runs WHERE suite_id = ? AND status = ? ORDER BY started_at DESC LIMIT 1";
935
- const row = model ? this.#stmt(sql2).get(suiteId, "completed", model) : this.#stmt(sql2).get(suiteId, "completed");
936
- if (!row) return void 0;
937
- return {
938
- id: row.id,
939
- suite_id: row.suite_id,
940
- name: row.name,
941
- model: row.model,
942
- config: row.config ? JSON.parse(row.config) : null,
943
- started_at: row.started_at,
944
- finished_at: row.finished_at,
945
- status: row.status,
946
- summary: row.summary ? JSON.parse(row.summary) : null
947
- };
1099
+ const row = model ? this.#get(
1100
+ runColumns,
1101
+ "SELECT * FROM runs WHERE suite_id = ? AND status = ? AND model = ? ORDER BY started_at DESC LIMIT 1",
1102
+ suiteId,
1103
+ "completed",
1104
+ model
1105
+ ) : this.#get(
1106
+ runColumns,
1107
+ "SELECT * FROM runs WHERE suite_id = ? AND status = ? ORDER BY started_at DESC LIMIT 1",
1108
+ suiteId,
1109
+ "completed"
1110
+ );
1111
+ return row ? toRun(row) : void 0;
948
1112
  }
949
1113
  listSuites() {
950
- const rows = this.#stmt(
1114
+ return this.#all(
1115
+ suiteColumns,
951
1116
  "SELECT * FROM suites ORDER BY created_at DESC"
952
- ).all();
953
- return rows.map((row) => ({
954
- id: row.id,
955
- name: row.name,
956
- created_at: row.created_at
957
- }));
1117
+ ).map(toSuite);
958
1118
  }
959
1119
  createPrompt(name, content) {
960
1120
  const id = crypto.randomUUID();
961
1121
  const now = Date.now();
962
- const latest = this.#stmt(
963
- "SELECT MAX(version) as latestVersion FROM prompts WHERE name = ?"
964
- ).get(name);
965
- const version = (latest?.latestVersion ?? 0) + 1;
1122
+ const { latestVersion } = this.#one(
1123
+ { latestVersion: "number?" },
1124
+ "SELECT MAX(version) as latestVersion FROM prompts WHERE name = ?",
1125
+ name
1126
+ );
1127
+ const version = (latestVersion ?? 0) + 1;
966
1128
  this.#stmt(
967
1129
  "INSERT INTO prompts (id, name, version, content, created_at) VALUES (?, ?, ?, ?, ?)"
968
1130
  ).run(id, name, version, content, now);
969
1131
  return { id, name, version, content, created_at: now };
970
1132
  }
971
1133
  listPrompts() {
972
- const rows = this.#stmt(
1134
+ return this.#all(
1135
+ promptColumns,
973
1136
  "SELECT * FROM prompts ORDER BY name COLLATE NOCASE ASC, version DESC"
974
- ).all();
975
- return rows.map((row) => ({
976
- id: row.id,
977
- name: row.name,
978
- version: row.version,
979
- content: row.content,
980
- created_at: row.created_at
981
- }));
1137
+ ).map(toPrompt);
982
1138
  }
983
1139
  getPrompt(id) {
984
- const row = this.#stmt("SELECT * FROM prompts WHERE id = ?").get(id);
985
- if (!row) return void 0;
986
- return {
987
- id: row.id,
988
- name: row.name,
989
- version: row.version,
990
- content: row.content,
991
- created_at: row.created_at
992
- };
1140
+ const row = this.#get(
1141
+ promptColumns,
1142
+ "SELECT * FROM prompts WHERE id = ?",
1143
+ id
1144
+ );
1145
+ return row ? toPrompt(row) : void 0;
993
1146
  }
994
1147
  deletePrompt(id) {
995
1148
  this.#stmt("DELETE FROM prompts WHERE id = ?").run(id);
@@ -1114,6 +1267,9 @@ async function wrapTask(task, input, timeoutMs) {
1114
1267
  };
1115
1268
  }
1116
1269
  }
1270
+ function expectedOf(input) {
1271
+ return typeof input === "object" && input !== null && "expected" in input ? input.expected : void 0;
1272
+ }
1117
1273
  function clampScore(score, scorerName) {
1118
1274
  if (score < 0 || score > 1) {
1119
1275
  console.warn(
@@ -1161,6 +1317,7 @@ async function runEval(config) {
1161
1317
  await semaphore.acquire();
1162
1318
  try {
1163
1319
  emitter.emit("case:start", { runId, index, input });
1320
+ const expected = expectedOf(input);
1164
1321
  let finalResult;
1165
1322
  let finalScores;
1166
1323
  if (trials > 1) {
@@ -1178,7 +1335,7 @@ async function runEval(config) {
1178
1335
  const sr = await scorer({
1179
1336
  input,
1180
1337
  output: result.output,
1181
- expected: input.expected
1338
+ expected
1182
1339
  });
1183
1340
  scores[sName] = {
1184
1341
  score: clampScore(sr.score, sName),
@@ -1223,7 +1380,7 @@ async function runEval(config) {
1223
1380
  const sr = await scorer({
1224
1381
  input,
1225
1382
  output: finalResult.output,
1226
- expected: input.expected
1383
+ expected
1227
1384
  });
1228
1385
  finalScores[sName] = {
1229
1386
  score: clampScore(sr.score, sName),
@@ -1240,7 +1397,7 @@ async function runEval(config) {
1240
1397
  idx: index,
1241
1398
  input,
1242
1399
  output: finalResult.output || null,
1243
- expected: input.expected,
1400
+ expected,
1244
1401
  latency_ms: finalResult.latencyMs,
1245
1402
  tokens_in: finalResult.tokensIn,
1246
1403
  tokens_out: finalResult.tokensOut,
@@ -1276,7 +1433,7 @@ async function runEval(config) {
1276
1433
  index,
1277
1434
  input,
1278
1435
  output: finalResult.output,
1279
- expected: input.expected,
1436
+ expected,
1280
1437
  scores: finalScores,
1281
1438
  error: finalResult.error,
1282
1439
  latencyMs: finalResult.latencyMs,
@@ -1721,21 +1878,31 @@ function renderFailuresByScorer(cases, threshold) {
1721
1878
  }
1722
1879
 
1723
1880
  // packages/evals/src/reporters/json.ts
1724
- import { appendFile, mkdir as mkdir2 } from "node:fs/promises";
1881
+ import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
1725
1882
  function jsonReporter(options) {
1726
1883
  const outputDir = resolveOutputDir(options?.outputDir);
1727
1884
  const pretty = options?.pretty ?? true;
1728
- let streamFilename = "";
1885
+ const streamFiles = /* @__PURE__ */ new Map();
1729
1886
  return {
1730
- async onRunStart(data) {
1731
- await mkdir2(outputDir, { recursive: true });
1732
- streamFilename = getReportPath(outputDir, data.name, data.runId, "jsonl");
1887
+ onRunStart(data) {
1888
+ mkdirSync2(outputDir, { recursive: true });
1889
+ streamFiles.set(
1890
+ data.runId,
1891
+ getReportPath(outputDir, data.name, data.runId, "jsonl")
1892
+ );
1733
1893
  },
1734
- async onCaseEnd(data) {
1894
+ onCaseEnd(data) {
1895
+ const streamFile = streamFiles.get(data.runId);
1896
+ if (streamFile === void 0) {
1897
+ throw new Error(
1898
+ `jsonReporter received a case for run ${data.runId} before its onRunStart`
1899
+ );
1900
+ }
1735
1901
  const line = stringifyUnknown(data, { space: 0, fallback: "null" });
1736
- await appendFile(streamFilename, line + "\n", "utf-8");
1902
+ appendFileSync(streamFile, line + "\n", "utf-8");
1737
1903
  },
1738
1904
  async onRunEnd(data) {
1905
+ streamFiles.delete(data.runId);
1739
1906
  const content = stringifyUnknown(data, {
1740
1907
  space: pretty ? 2 : 0,
1741
1908
  fallback: "null"
@@ -2015,12 +2182,11 @@ function resolveFailedIndexes(store, suiteName, model, threshold) {
2015
2182
  return new Set(failingCases.map((c) => c.idx));
2016
2183
  }
2017
2184
  var EvalBuilder = class {
2018
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2019
- #options;
2185
+ #plan;
2020
2186
  #selection = { type: "all" };
2021
2187
  #shouldAssert = false;
2022
- constructor(options) {
2023
- this.#options = options;
2188
+ constructor(plan) {
2189
+ this.#plan = plan;
2024
2190
  }
2025
2191
  #setSelection(selection) {
2026
2192
  if (this.#selection.type !== "all") {
@@ -2052,77 +2218,76 @@ var EvalBuilder = class {
2052
2218
  return this.#execute().then(onfulfilled, onrejected);
2053
2219
  }
2054
2220
  async #execute() {
2055
- if ("models" in this.#options) {
2056
- return this.#executeMulti();
2057
- }
2058
- return this.#executeSingle();
2059
- }
2060
- #applyDatasetFilter(ds) {
2061
- switch (this.#selection.type) {
2062
- case "all":
2063
- return ds;
2064
- case "cases":
2065
- return this.#selection.indexes.size > 0 ? filterRecordsByIndex(ds, this.#selection.indexes) : ds;
2066
- case "sample":
2067
- return dataset(ds).sample(this.#selection.count);
2068
- case "failed":
2069
- return ds;
2070
- }
2071
- }
2072
- async #executeSingle() {
2073
- const options = this.#options;
2074
- let ds = options.dataset;
2075
- if (this.#selection.type === "failed") {
2076
- const indexes = resolveFailedIndexes(
2077
- options.store,
2078
- options.name,
2079
- options.model,
2080
- options.threshold
2081
- );
2082
- if (indexes.size > 0) {
2083
- ds = filterRecordsByIndex(ds, indexes);
2084
- }
2085
- } else {
2086
- ds = this.#applyDatasetFilter(ds);
2087
- }
2088
- const result = await evaluateSingle({ ...options, dataset: ds });
2089
- if (this.#shouldAssert && result.failCount > 0) {
2221
+ const result = await this.#plan.run(this.#selection);
2222
+ if (this.#shouldAssert && this.#plan.hasFailures(result)) {
2090
2223
  throw new EvalAssertionError(result);
2091
2224
  }
2092
2225
  return result;
2093
2226
  }
2094
- async #executeMulti() {
2095
- const options = this.#options;
2096
- let result;
2097
- if (this.#selection.type === "failed") {
2098
- const perModelIndexes = /* @__PURE__ */ new Map();
2099
- for (const variant of options.models) {
2100
- perModelIndexes.set(
2101
- variant.name,
2102
- resolveFailedIndexes(
2103
- options.store,
2104
- options.name,
2105
- variant.name,
2106
- options.threshold
2107
- )
2108
- );
2109
- }
2110
- result = await evaluateEach(options, perModelIndexes);
2111
- } else {
2112
- const filtered = this.#applyDatasetFilter(options.dataset);
2113
- result = await evaluateEach({ ...options, dataset: filtered });
2114
- }
2115
- if (this.#shouldAssert && result.some((s) => s.failCount > 0)) {
2116
- throw new EvalAssertionError(result);
2227
+ };
2228
+ function selectCases(ds, selection) {
2229
+ switch (selection.type) {
2230
+ case "all":
2231
+ return ds;
2232
+ case "cases":
2233
+ return selection.indexes.size > 0 ? filterRecordsByIndex(ds, selection.indexes) : ds;
2234
+ case "sample":
2235
+ return dataset(ds).sample(selection.count);
2236
+ }
2237
+ }
2238
+ var SingleModelEval = class {
2239
+ #options;
2240
+ constructor(options) {
2241
+ this.#options = options;
2242
+ }
2243
+ run(selection) {
2244
+ return evaluateSingle({
2245
+ ...this.#options,
2246
+ dataset: this.#cases(selection)
2247
+ });
2248
+ }
2249
+ hasFailures(summary) {
2250
+ return summary.failCount > 0;
2251
+ }
2252
+ #cases(selection) {
2253
+ const { dataset: ds, store, name, model, threshold } = this.#options;
2254
+ if (selection.type !== "failed") return selectCases(ds, selection);
2255
+ const indexes = resolveFailedIndexes(store, name, model, threshold);
2256
+ return indexes.size > 0 ? filterRecordsByIndex(ds, indexes) : ds;
2257
+ }
2258
+ };
2259
+ var EachModelEval = class {
2260
+ #options;
2261
+ constructor(options) {
2262
+ this.#options = options;
2263
+ }
2264
+ run(selection) {
2265
+ if (selection.type === "failed") {
2266
+ return evaluateEach(this.#options, this.#failedIndexesPerModel());
2117
2267
  }
2118
- return result;
2268
+ return evaluateEach({
2269
+ ...this.#options,
2270
+ dataset: selectCases(this.#options.dataset, selection)
2271
+ });
2272
+ }
2273
+ hasFailures(summaries) {
2274
+ return summaries.some((s) => s.failCount > 0);
2275
+ }
2276
+ #failedIndexesPerModel() {
2277
+ const { models, store, name, threshold } = this.#options;
2278
+ return new Map(
2279
+ models.map((variant) => [
2280
+ variant.name,
2281
+ resolveFailedIndexes(store, name, variant.name, threshold)
2282
+ ])
2283
+ );
2119
2284
  }
2120
2285
  };
2121
2286
  function evaluate(options) {
2122
2287
  if ("models" in options) {
2123
- return new EvalBuilder(options);
2288
+ return new EvalBuilder(new EachModelEval(options));
2124
2289
  }
2125
- return new EvalBuilder(options);
2290
+ return new EvalBuilder(new SingleModelEval(options));
2126
2291
  }
2127
2292
  function wireReporters(reporters) {
2128
2293
  const emitter = new EvalEmitter();