@deepagents/evals 7.1.1 → 7.1.3

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
@@ -363,6 +363,7 @@ function dataset(source) {
363
363
 
364
364
  // packages/evals/src/scorers/index.ts
365
365
  import OpenAI from "openai";
366
+ import pMemoize from "p-memoize";
366
367
  var exactMatch = async ({ output, expected }) => {
367
368
  const exp = expected == null ? "" : String(expected);
368
369
  if (output === exp) return { score: 1 };
@@ -484,10 +485,14 @@ var LlmJudge = class _LlmJudge {
484
485
  #model;
485
486
  #choices;
486
487
  #criterion;
488
+ /** The configured client, or one OpenAI client made on first use. */
487
489
  #client;
488
490
  constructor(config, choices, criterion) {
489
491
  this.#model = config.model;
490
- this.#client = config.client;
492
+ const configured = config.client;
493
+ this.#client = configured ? async () => configured : pMemoize(
494
+ async () => new OpenAI({ apiKey: process.env["OPENAI_API_KEY"] })
495
+ );
491
496
  this.#choices = choices;
492
497
  this.#criterion = criterion;
493
498
  }
@@ -520,10 +525,8 @@ var LlmJudge = class _LlmJudge {
520
525
  return (args) => this.#score(args);
521
526
  }
522
527
  async #score({ input, output, expected }) {
523
- this.#client ??= new OpenAI({
524
- apiKey: process.env["OPENAI_API_KEY"]
525
- });
526
- const response = await this.#client.chat.completions.create({
528
+ const client = await this.#client();
529
+ const response = await client.chat.completions.create({
527
530
  model: this.#model,
528
531
  messages: [
529
532
  {
@@ -645,6 +648,8 @@ function weighted(config) {
645
648
  }
646
649
 
647
650
  // packages/evals/src/store/index.ts
651
+ import { Declaration } from "@zukhruf/sqlite-drift";
652
+ import { transaction } from "@zukhruf/sqlite-transaction";
648
653
  import { mkdirSync } from "node:fs";
649
654
  import { dirname } from "node:path";
650
655
  import {
@@ -652,9 +657,10 @@ import {
652
657
  } from "node:sqlite";
653
658
 
654
659
  // packages/evals/src/store/ddl.sqlite.sql
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";
660
+ var ddl_sqlite_default = "CREATE 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,\n version INTEGER NOT NULL DEFAULT 1,\n content TEXT NOT NULL,\n created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),\n UNIQUE(name, version)\n);\n\nCREATE INDEX IF NOT EXISTS idx_prompts_created_at ON prompts(created_at);\nCREATE INDEX IF NOT EXISTS idx_prompts_name_version ON prompts(name, version DESC);\n";
656
661
 
657
662
  // packages/evals/src/store/index.ts
663
+ var schema = new Declaration([ddl_sqlite_default], { rows: "keep" });
658
664
  var RUN_STATUSES = ["running", "completed", "failed"];
659
665
  function isRunStatus(value) {
660
666
  return RUN_STATUSES.some((status) => status === value);
@@ -802,20 +808,6 @@ var RunStore = class {
802
808
  #all(columns, sql2, ...params) {
803
809
  return this.#stmt(sql2).all(...params).map((row) => readRow(row, columns, sql2));
804
810
  }
805
- #transaction(fn) {
806
- this.#db.exec("BEGIN TRANSACTION");
807
- try {
808
- const result = fn();
809
- this.#db.exec("COMMIT");
810
- return result;
811
- } catch (error) {
812
- try {
813
- this.#db.exec("ROLLBACK");
814
- } catch {
815
- }
816
- throw error;
817
- }
818
- }
819
811
  constructor(pathOrDb) {
820
812
  if (pathOrDb instanceof DatabaseSync) {
821
813
  this.#db = pathOrDb;
@@ -824,93 +816,22 @@ var RunStore = class {
824
816
  mkdirSync(dirname(dbPath), { recursive: true });
825
817
  this.#db = new DatabaseSync(dbPath);
826
818
  }
827
- this.#db.exec(ddl_sqlite_default);
828
- this.#migrateRunsTableToSuiteRequired();
829
- this.#migratePromptsTableIfNeeded();
830
- this.#db.exec(
831
- "CREATE INDEX IF NOT EXISTS idx_prompts_name_version ON prompts(name, version DESC)"
819
+ this.#db.exec(`
820
+ PRAGMA journal_mode = WAL;
821
+ PRAGMA synchronous = NORMAL;
822
+ PRAGMA foreign_keys = ON;
823
+ PRAGMA busy_timeout = 5000;
824
+ `);
825
+ const runsTable = this.#get(
826
+ { name: "text" },
827
+ "SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'runs'"
832
828
  );
833
- }
834
- #migratePromptsTableIfNeeded() {
835
- const columns = this.#all({ name: "text" }, "PRAGMA table_info(prompts)");
836
- if (columns.length === 0) return;
837
- if (columns.some((column) => column.name === "version")) return;
838
- this.#transaction(() => {
839
- this.#db.exec("ALTER TABLE prompts RENAME TO prompts_legacy");
840
- this.#db.exec(`
841
- CREATE TABLE prompts (
842
- id TEXT PRIMARY KEY,
843
- name TEXT NOT NULL,
844
- version INTEGER NOT NULL,
845
- content TEXT NOT NULL,
846
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
847
- UNIQUE(name, version)
848
- )
849
- `);
850
- this.#db.exec(`
851
- INSERT INTO prompts (id, name, version, content, created_at)
852
- SELECT id, name, 1, content, created_at
853
- FROM prompts_legacy
854
- `);
855
- this.#db.exec("DROP TABLE prompts_legacy");
829
+ if (runsTable !== void 0) {
856
830
  this.#db.exec(
857
- "CREATE INDEX IF NOT EXISTS idx_prompts_created_at ON prompts(created_at)"
831
+ "DELETE FROM runs WHERE suite_id IS NULL OR NOT EXISTS (SELECT 1 FROM suites WHERE suites.id = runs.suite_id)"
858
832
  );
859
- this.#db.exec(
860
- "CREATE INDEX IF NOT EXISTS idx_prompts_name_version ON prompts(name, version DESC)"
861
- );
862
- });
863
- }
864
- #migrateRunsTableToSuiteRequired() {
865
- const columns = this.#all(
866
- { name: "text", notnull: "number" },
867
- "PRAGMA table_info(runs)"
868
- );
869
- if (columns.length === 0) return;
870
- const suiteColumn = columns.find((column) => column.name === "suite_id");
871
- const hasNonNullSuite = suiteColumn?.notnull === 1;
872
- const runForeignKeys = this.#all(
873
- { from: "text", on_delete: "text", table: "text" },
874
- "PRAGMA foreign_key_list(runs)"
875
- );
876
- const suiteForeignKey = runForeignKeys.find(
877
- (fk) => fk.from === "suite_id" && fk.table === "suites"
878
- );
879
- const hasCascadeDelete = suiteForeignKey?.on_delete === "CASCADE";
880
- if (hasNonNullSuite && hasCascadeDelete) return;
881
- this.#statements.clear();
882
- this.#transaction(() => {
883
- this.#db.exec(`
884
- CREATE TABLE runs_next (
885
- id TEXT PRIMARY KEY,
886
- suite_id TEXT NOT NULL,
887
- name TEXT NOT NULL,
888
- model TEXT NOT NULL,
889
- config TEXT,
890
- started_at INTEGER NOT NULL,
891
- finished_at INTEGER,
892
- status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed')),
893
- summary TEXT,
894
- FOREIGN KEY (suite_id) REFERENCES suites(id) ON DELETE CASCADE
895
- )
896
- `);
897
- this.#db.exec("DELETE FROM runs WHERE suite_id IS NULL");
898
- this.#db.exec(`
899
- INSERT INTO runs_next (id, suite_id, name, model, config, started_at, finished_at, status, summary)
900
- SELECT r.id, r.suite_id, r.name, r.model, r.config, r.started_at, r.finished_at, r.status, r.summary
901
- FROM runs r
902
- JOIN suites s ON s.id = r.suite_id
903
- `);
904
- this.#db.exec("DROP TABLE runs");
905
- this.#db.exec("ALTER TABLE runs_next RENAME TO runs");
906
- this.#db.exec(
907
- "CREATE INDEX IF NOT EXISTS idx_runs_suite_id ON runs(suite_id)"
908
- );
909
- this.#db.exec(
910
- "CREATE INDEX IF NOT EXISTS idx_runs_started_at ON runs(started_at)"
911
- );
912
- });
913
- this.#statements.clear();
833
+ }
834
+ schema.apply(this.#db);
914
835
  }
915
836
  createSuite(name) {
916
837
  const id = crypto.randomUUID();
@@ -955,35 +876,43 @@ var RunStore = class {
955
876
  ).run(Date.now(), status, summary ? JSON.stringify(summary) : null, runId);
956
877
  }
957
878
  saveCases(cases) {
958
- this.#transaction(() => {
959
- const stmt = this.#stmt(
960
- "INSERT INTO cases (id, run_id, idx, input, output, expected, latency_ms, tokens_in, tokens_out, error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
961
- );
962
- for (const c of cases) {
963
- stmt.run(
964
- c.id,
965
- c.run_id,
966
- c.idx,
967
- JSON.stringify(c.input),
968
- c.output,
969
- c.expected != null ? JSON.stringify(c.expected) : null,
970
- c.latency_ms,
971
- c.tokens_in,
972
- c.tokens_out,
973
- c.error ?? null
879
+ transaction(
880
+ this.#db,
881
+ () => {
882
+ const stmt = this.#stmt(
883
+ "INSERT INTO cases (id, run_id, idx, input, output, expected, latency_ms, tokens_in, tokens_out, error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
974
884
  );
975
- }
976
- });
885
+ for (const c of cases) {
886
+ stmt.run(
887
+ c.id,
888
+ c.run_id,
889
+ c.idx,
890
+ JSON.stringify(c.input),
891
+ c.output,
892
+ c.expected != null ? JSON.stringify(c.expected) : null,
893
+ c.latency_ms,
894
+ c.tokens_in,
895
+ c.tokens_out,
896
+ c.error ?? null
897
+ );
898
+ }
899
+ },
900
+ { mode: "deferred" }
901
+ );
977
902
  }
978
903
  saveScores(scores) {
979
- this.#transaction(() => {
980
- const stmt = this.#stmt(
981
- "INSERT INTO scores (id, case_id, scorer_name, score, reason) VALUES (?, ?, ?, ?, ?)"
982
- );
983
- for (const s of scores) {
984
- stmt.run(s.id, s.case_id, s.scorer_name, s.score, s.reason ?? null);
985
- }
986
- });
904
+ transaction(
905
+ this.#db,
906
+ () => {
907
+ const stmt = this.#stmt(
908
+ "INSERT INTO scores (id, case_id, scorer_name, score, reason) VALUES (?, ?, ?, ?, ?)"
909
+ );
910
+ for (const s of scores) {
911
+ stmt.run(s.id, s.case_id, s.scorer_name, s.score, s.reason ?? null);
912
+ }
913
+ },
914
+ { mode: "deferred" }
915
+ );
987
916
  }
988
917
  getRun(runId) {
989
918
  const row = this.#get(runColumns, "SELECT * FROM runs WHERE id = ?", runId);
@@ -1151,12 +1080,16 @@ var RunStore = class {
1151
1080
  this.#stmt("DELETE FROM prompts WHERE id = ?").run(id);
1152
1081
  }
1153
1082
  resetRun(id) {
1154
- this.#transaction(() => {
1155
- this.#stmt("DELETE FROM cases WHERE run_id = ?").run(id);
1156
- this.#stmt(
1157
- "UPDATE runs SET status = ?, started_at = ?, finished_at = NULL, summary = NULL WHERE id = ?"
1158
- ).run("running", Date.now(), id);
1159
- });
1083
+ transaction(
1084
+ this.#db,
1085
+ () => {
1086
+ this.#stmt("DELETE FROM cases WHERE run_id = ?").run(id);
1087
+ this.#stmt(
1088
+ "UPDATE runs SET status = ?, started_at = ?, finished_at = NULL, summary = NULL WHERE id = ?"
1089
+ ).run("running", Date.now(), id);
1090
+ },
1091
+ { mode: "deferred" }
1092
+ );
1160
1093
  }
1161
1094
  deleteRun(id) {
1162
1095
  this.#stmt("DELETE FROM runs WHERE id = ?").run(id);
@@ -2184,45 +2117,44 @@ function resolveFailedIndexes(store, suiteName, model, threshold) {
2184
2117
  );
2185
2118
  return new Set(failingCases.map((c) => c.idx));
2186
2119
  }
2187
- var EvalBuilder = class {
2120
+ var EVERY_CASE = { selection: { type: "all" }, shouldAssert: false };
2121
+ var EvalBuilder = class _EvalBuilder {
2188
2122
  #plan;
2189
- #selection = { type: "all" };
2190
- #shouldAssert = false;
2191
- constructor(plan) {
2123
+ #run;
2124
+ constructor(plan, run) {
2192
2125
  this.#plan = plan;
2126
+ this.#run = run;
2193
2127
  }
2194
- #setSelection(selection) {
2195
- if (this.#selection.type !== "all") {
2128
+ #select(selection) {
2129
+ if (this.#run.selection.type !== "all") {
2196
2130
  throw new Error(
2197
- `Cannot combine .${this.#selection.type}() with .${selection.type}()`
2131
+ `Cannot combine .${this.#run.selection.type}() with .${selection.type}()`
2198
2132
  );
2199
2133
  }
2200
- this.#selection = selection;
2201
- return this;
2134
+ return new _EvalBuilder(this.#plan, { ...this.#run, selection });
2202
2135
  }
2203
2136
  failed() {
2204
- return this.#setSelection({ type: "failed" });
2137
+ return this.#select({ type: "failed" });
2205
2138
  }
2206
2139
  cases(spec) {
2207
2140
  const { indexes } = parseRecordSelection(spec);
2208
- return this.#setSelection({ type: "cases", indexes });
2141
+ return this.#select({ type: "cases", indexes });
2209
2142
  }
2210
2143
  sample(count) {
2211
2144
  if (count < 1) {
2212
2145
  throw new Error("Sample count must be >= 1");
2213
2146
  }
2214
- return this.#setSelection({ type: "sample", count });
2147
+ return this.#select({ type: "sample", count });
2215
2148
  }
2216
2149
  assert() {
2217
- this.#shouldAssert = true;
2218
- return this;
2150
+ return new _EvalBuilder(this.#plan, { ...this.#run, shouldAssert: true });
2219
2151
  }
2220
2152
  then(onfulfilled, onrejected) {
2221
2153
  return this.#execute().then(onfulfilled, onrejected);
2222
2154
  }
2223
2155
  async #execute() {
2224
- const result = await this.#plan.run(this.#selection);
2225
- if (this.#shouldAssert && this.#plan.hasFailures(result)) {
2156
+ const result = await this.#plan.run(this.#run.selection);
2157
+ if (this.#run.shouldAssert && this.#plan.hasFailures(result)) {
2226
2158
  throw new EvalAssertionError(result);
2227
2159
  }
2228
2160
  return result;
@@ -2288,9 +2220,9 @@ var EachModelEval = class {
2288
2220
  };
2289
2221
  function evaluate(options) {
2290
2222
  if ("models" in options) {
2291
- return new EvalBuilder(new EachModelEval(options));
2223
+ return new EvalBuilder(new EachModelEval(options), EVERY_CASE);
2292
2224
  }
2293
- return new EvalBuilder(new SingleModelEval(options));
2225
+ return new EvalBuilder(new SingleModelEval(options), EVERY_CASE);
2294
2226
  }
2295
2227
  function wireReporters(reporters) {
2296
2228
  const emitter = new EvalEmitter();