@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/dataset/hf.d.ts +1 -1
- package/dist/dataset/hf.d.ts.map +1 -1
- package/dist/dataset/index.d.ts +6 -1
- package/dist/dataset/index.d.ts.map +1 -1
- package/dist/dataset/index.js +35 -3
- package/dist/dataset/index.js.map +3 -3
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +8 -4
- package/dist/engine/index.js.map +2 -2
- package/dist/evaluate/index.d.ts +18 -2
- package/dist/evaluate/index.d.ts.map +1 -1
- package/dist/evaluate/index.js +92 -73
- package/dist/evaluate/index.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -206
- package/dist/index.js.map +3 -3
- package/dist/reporters/index.js +17 -7
- package/dist/reporters/index.js.map +3 -3
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/scorers/index.d.ts +9 -1
- package/dist/scorers/index.d.ts.map +1 -1
- package/dist/scorers/index.js +11 -5
- package/dist/scorers/index.js.map +2 -2
- package/dist/store/index.d.ts +7 -4
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +237 -122
- package/dist/store/index.js.map +2 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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.#
|
|
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
|
|
687
|
-
|
|
688
|
-
|
|
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.#
|
|
869
|
+
const runForeignKeys = this.#all(
|
|
870
|
+
{ from: "text", on_delete: "text", table: "text" },
|
|
691
871
|
"PRAGMA foreign_key_list(runs)"
|
|
692
|
-
)
|
|
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.#
|
|
742
|
-
|
|
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.#
|
|
803
|
-
|
|
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
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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.#
|
|
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
|
-
|
|
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.#
|
|
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
|
-
|
|
892
|
-
|
|
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
|
-
|
|
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.#
|
|
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
|
-
|
|
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.#
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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
|
-
|
|
1114
|
+
return this.#all(
|
|
1115
|
+
suiteColumns,
|
|
951
1116
|
"SELECT * FROM suites ORDER BY created_at DESC"
|
|
952
|
-
).
|
|
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
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
1134
|
+
return this.#all(
|
|
1135
|
+
promptColumns,
|
|
973
1136
|
"SELECT * FROM prompts ORDER BY name COLLATE NOCASE ASC, version DESC"
|
|
974
|
-
).
|
|
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.#
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
id
|
|
988
|
-
|
|
989
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
1885
|
+
const streamFiles = /* @__PURE__ */ new Map();
|
|
1729
1886
|
return {
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2019
|
-
#options;
|
|
2185
|
+
#plan;
|
|
2020
2186
|
#selection = { type: "all" };
|
|
2021
2187
|
#shouldAssert = false;
|
|
2022
|
-
constructor(
|
|
2023
|
-
this.#
|
|
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
|
-
|
|
2056
|
-
|
|
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
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
|
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();
|