@deepagents/evals 7.0.2 → 7.1.1
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 +375 -207
- 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 +241 -123
- 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 {
|
|
@@ -633,7 +809,10 @@ var RunStore = class {
|
|
|
633
809
|
this.#db.exec("COMMIT");
|
|
634
810
|
return result;
|
|
635
811
|
} catch (error) {
|
|
636
|
-
|
|
812
|
+
try {
|
|
813
|
+
this.#db.exec("ROLLBACK");
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
637
816
|
throw error;
|
|
638
817
|
}
|
|
639
818
|
}
|
|
@@ -653,7 +832,7 @@ var RunStore = class {
|
|
|
653
832
|
);
|
|
654
833
|
}
|
|
655
834
|
#migratePromptsTableIfNeeded() {
|
|
656
|
-
const columns = this.#
|
|
835
|
+
const columns = this.#all({ name: "text" }, "PRAGMA table_info(prompts)");
|
|
657
836
|
if (columns.length === 0) return;
|
|
658
837
|
if (columns.some((column) => column.name === "version")) return;
|
|
659
838
|
this.#transaction(() => {
|
|
@@ -683,13 +862,17 @@ var RunStore = class {
|
|
|
683
862
|
});
|
|
684
863
|
}
|
|
685
864
|
#migrateRunsTableToSuiteRequired() {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
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");
|
|
689
871
|
const hasNonNullSuite = suiteColumn?.notnull === 1;
|
|
690
|
-
const runForeignKeys = this.#
|
|
872
|
+
const runForeignKeys = this.#all(
|
|
873
|
+
{ from: "text", on_delete: "text", table: "text" },
|
|
691
874
|
"PRAGMA foreign_key_list(runs)"
|
|
692
|
-
)
|
|
875
|
+
);
|
|
693
876
|
const suiteForeignKey = runForeignKeys.find(
|
|
694
877
|
(fk) => fk.from === "suite_id" && fk.table === "suites"
|
|
695
878
|
);
|
|
@@ -738,8 +921,12 @@ var RunStore = class {
|
|
|
738
921
|
return { id, name, created_at: now };
|
|
739
922
|
}
|
|
740
923
|
getSuite(id) {
|
|
741
|
-
const row = this.#
|
|
742
|
-
|
|
924
|
+
const row = this.#get(
|
|
925
|
+
suiteColumns,
|
|
926
|
+
"SELECT * FROM suites WHERE id = ?",
|
|
927
|
+
id
|
|
928
|
+
);
|
|
929
|
+
return row ? toSuite(row) : void 0;
|
|
743
930
|
}
|
|
744
931
|
renameSuite(id, name) {
|
|
745
932
|
this.#stmt("UPDATE suites SET name = ? WHERE id = ?").run(name, id);
|
|
@@ -799,77 +986,45 @@ var RunStore = class {
|
|
|
799
986
|
});
|
|
800
987
|
}
|
|
801
988
|
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
|
-
};
|
|
989
|
+
const row = this.#get(runColumns, "SELECT * FROM runs WHERE id = ?", runId);
|
|
990
|
+
return row ? toRun(row) : void 0;
|
|
815
991
|
}
|
|
816
992
|
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
|
-
}));
|
|
993
|
+
const rows = suiteId ? this.#all(
|
|
994
|
+
runColumns,
|
|
995
|
+
"SELECT * FROM runs WHERE suite_id = ? ORDER BY started_at",
|
|
996
|
+
suiteId
|
|
997
|
+
) : this.#all(runColumns, "SELECT * FROM runs ORDER BY started_at");
|
|
998
|
+
return rows.map(toRun);
|
|
830
999
|
}
|
|
831
1000
|
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
|
-
}));
|
|
1001
|
+
return this.#all(
|
|
1002
|
+
caseColumns,
|
|
1003
|
+
"SELECT * FROM cases WHERE run_id = ? ORDER BY idx",
|
|
1004
|
+
runId
|
|
1005
|
+
).map(toCase);
|
|
847
1006
|
}
|
|
848
1007
|
getFailingCases(runId, threshold = 0.5) {
|
|
849
|
-
const rows = this.#
|
|
1008
|
+
const rows = this.#all(
|
|
1009
|
+
{
|
|
1010
|
+
...caseColumns,
|
|
1011
|
+
scorer_name: "text",
|
|
1012
|
+
score: "number",
|
|
1013
|
+
score_reason: "text?"
|
|
1014
|
+
},
|
|
850
1015
|
`SELECT c.*, s.scorer_name, s.score, s.reason as score_reason
|
|
851
1016
|
FROM cases c
|
|
852
1017
|
JOIN scores s ON s.case_id = c.id
|
|
853
1018
|
WHERE c.run_id = ? AND s.score < ?
|
|
854
|
-
ORDER BY c.idx
|
|
855
|
-
|
|
1019
|
+
ORDER BY c.idx`,
|
|
1020
|
+
runId,
|
|
1021
|
+
threshold
|
|
1022
|
+
);
|
|
856
1023
|
const caseMap = /* @__PURE__ */ new Map();
|
|
857
1024
|
for (const row of rows) {
|
|
858
1025
|
let c = caseMap.get(row.id);
|
|
859
1026
|
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
|
-
};
|
|
1027
|
+
c = { ...toCase(row), scores: [] };
|
|
873
1028
|
caseMap.set(row.id, c);
|
|
874
1029
|
}
|
|
875
1030
|
c.scores.push({
|
|
@@ -881,33 +1036,44 @@ var RunStore = class {
|
|
|
881
1036
|
return Array.from(caseMap.values());
|
|
882
1037
|
}
|
|
883
1038
|
getRunSummary(runId, threshold = 0.5) {
|
|
884
|
-
const totals = this.#
|
|
1039
|
+
const totals = this.#one(
|
|
1040
|
+
{
|
|
1041
|
+
totalCases: "number",
|
|
1042
|
+
totalLatencyMs: "number",
|
|
1043
|
+
totalTokensIn: "number",
|
|
1044
|
+
totalTokensOut: "number"
|
|
1045
|
+
},
|
|
885
1046
|
`SELECT
|
|
886
1047
|
COUNT(DISTINCT c.id) as totalCases,
|
|
887
1048
|
COALESCE(SUM(c.latency_ms), 0) as totalLatencyMs,
|
|
888
1049
|
COALESCE(SUM(c.tokens_in), 0) as totalTokensIn,
|
|
889
1050
|
COALESCE(SUM(c.tokens_out), 0) as totalTokensOut
|
|
890
|
-
FROM cases c WHERE c.run_id =
|
|
891
|
-
|
|
892
|
-
|
|
1051
|
+
FROM cases c WHERE c.run_id = ?`,
|
|
1052
|
+
runId
|
|
1053
|
+
);
|
|
1054
|
+
const scorerMeans = this.#all(
|
|
1055
|
+
{ scorer_name: "text", meanScore: "number" },
|
|
893
1056
|
`SELECT s.scorer_name, AVG(s.score) as meanScore
|
|
894
1057
|
FROM scores s
|
|
895
1058
|
JOIN cases c ON c.id = s.case_id
|
|
896
1059
|
WHERE c.run_id = ?
|
|
897
|
-
GROUP BY s.scorer_name
|
|
898
|
-
|
|
1060
|
+
GROUP BY s.scorer_name`,
|
|
1061
|
+
runId
|
|
1062
|
+
);
|
|
899
1063
|
const meanScores = {};
|
|
900
1064
|
for (const row of scorerMeans) {
|
|
901
1065
|
meanScores[row.scorer_name] = row.meanScore;
|
|
902
1066
|
}
|
|
903
|
-
const passFail = this.#
|
|
1067
|
+
const passFail = this.#all(
|
|
1068
|
+
{ id: "text", minScore: "number" },
|
|
904
1069
|
`SELECT c.id,
|
|
905
1070
|
MIN(s.score) as minScore
|
|
906
1071
|
FROM cases c
|
|
907
1072
|
JOIN scores s ON s.case_id = c.id
|
|
908
1073
|
WHERE c.run_id = ?
|
|
909
|
-
GROUP BY c.id
|
|
910
|
-
|
|
1074
|
+
GROUP BY c.id`,
|
|
1075
|
+
runId
|
|
1076
|
+
);
|
|
911
1077
|
let passCount = 0;
|
|
912
1078
|
let failCount = 0;
|
|
913
1079
|
for (const row of passFail) {
|
|
@@ -925,71 +1091,61 @@ var RunStore = class {
|
|
|
925
1091
|
};
|
|
926
1092
|
}
|
|
927
1093
|
findSuiteByName(name) {
|
|
928
|
-
const row = this.#
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1094
|
+
const row = this.#get(
|
|
1095
|
+
suiteColumns,
|
|
1096
|
+
"SELECT * FROM suites WHERE name = ? ORDER BY created_at DESC LIMIT 1",
|
|
1097
|
+
name
|
|
1098
|
+
);
|
|
1099
|
+
return row ? toSuite(row) : void 0;
|
|
932
1100
|
}
|
|
933
1101
|
getLatestCompletedRun(suiteId, model) {
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
};
|
|
1102
|
+
const row = model ? this.#get(
|
|
1103
|
+
runColumns,
|
|
1104
|
+
"SELECT * FROM runs WHERE suite_id = ? AND status = ? AND model = ? ORDER BY started_at DESC LIMIT 1",
|
|
1105
|
+
suiteId,
|
|
1106
|
+
"completed",
|
|
1107
|
+
model
|
|
1108
|
+
) : this.#get(
|
|
1109
|
+
runColumns,
|
|
1110
|
+
"SELECT * FROM runs WHERE suite_id = ? AND status = ? ORDER BY started_at DESC LIMIT 1",
|
|
1111
|
+
suiteId,
|
|
1112
|
+
"completed"
|
|
1113
|
+
);
|
|
1114
|
+
return row ? toRun(row) : void 0;
|
|
948
1115
|
}
|
|
949
1116
|
listSuites() {
|
|
950
|
-
|
|
1117
|
+
return this.#all(
|
|
1118
|
+
suiteColumns,
|
|
951
1119
|
"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
|
-
}));
|
|
1120
|
+
).map(toSuite);
|
|
958
1121
|
}
|
|
959
1122
|
createPrompt(name, content) {
|
|
960
1123
|
const id = crypto.randomUUID();
|
|
961
1124
|
const now = Date.now();
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1125
|
+
const { latestVersion } = this.#one(
|
|
1126
|
+
{ latestVersion: "number?" },
|
|
1127
|
+
"SELECT MAX(version) as latestVersion FROM prompts WHERE name = ?",
|
|
1128
|
+
name
|
|
1129
|
+
);
|
|
1130
|
+
const version = (latestVersion ?? 0) + 1;
|
|
966
1131
|
this.#stmt(
|
|
967
1132
|
"INSERT INTO prompts (id, name, version, content, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
968
1133
|
).run(id, name, version, content, now);
|
|
969
1134
|
return { id, name, version, content, created_at: now };
|
|
970
1135
|
}
|
|
971
1136
|
listPrompts() {
|
|
972
|
-
|
|
1137
|
+
return this.#all(
|
|
1138
|
+
promptColumns,
|
|
973
1139
|
"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
|
-
}));
|
|
1140
|
+
).map(toPrompt);
|
|
982
1141
|
}
|
|
983
1142
|
getPrompt(id) {
|
|
984
|
-
const row = this.#
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
id
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
content: row.content,
|
|
991
|
-
created_at: row.created_at
|
|
992
|
-
};
|
|
1143
|
+
const row = this.#get(
|
|
1144
|
+
promptColumns,
|
|
1145
|
+
"SELECT * FROM prompts WHERE id = ?",
|
|
1146
|
+
id
|
|
1147
|
+
);
|
|
1148
|
+
return row ? toPrompt(row) : void 0;
|
|
993
1149
|
}
|
|
994
1150
|
deletePrompt(id) {
|
|
995
1151
|
this.#stmt("DELETE FROM prompts WHERE id = ?").run(id);
|
|
@@ -1114,6 +1270,9 @@ async function wrapTask(task, input, timeoutMs) {
|
|
|
1114
1270
|
};
|
|
1115
1271
|
}
|
|
1116
1272
|
}
|
|
1273
|
+
function expectedOf(input) {
|
|
1274
|
+
return typeof input === "object" && input !== null && "expected" in input ? input.expected : void 0;
|
|
1275
|
+
}
|
|
1117
1276
|
function clampScore(score, scorerName) {
|
|
1118
1277
|
if (score < 0 || score > 1) {
|
|
1119
1278
|
console.warn(
|
|
@@ -1161,6 +1320,7 @@ async function runEval(config) {
|
|
|
1161
1320
|
await semaphore.acquire();
|
|
1162
1321
|
try {
|
|
1163
1322
|
emitter.emit("case:start", { runId, index, input });
|
|
1323
|
+
const expected = expectedOf(input);
|
|
1164
1324
|
let finalResult;
|
|
1165
1325
|
let finalScores;
|
|
1166
1326
|
if (trials > 1) {
|
|
@@ -1178,7 +1338,7 @@ async function runEval(config) {
|
|
|
1178
1338
|
const sr = await scorer({
|
|
1179
1339
|
input,
|
|
1180
1340
|
output: result.output,
|
|
1181
|
-
expected
|
|
1341
|
+
expected
|
|
1182
1342
|
});
|
|
1183
1343
|
scores[sName] = {
|
|
1184
1344
|
score: clampScore(sr.score, sName),
|
|
@@ -1223,7 +1383,7 @@ async function runEval(config) {
|
|
|
1223
1383
|
const sr = await scorer({
|
|
1224
1384
|
input,
|
|
1225
1385
|
output: finalResult.output,
|
|
1226
|
-
expected
|
|
1386
|
+
expected
|
|
1227
1387
|
});
|
|
1228
1388
|
finalScores[sName] = {
|
|
1229
1389
|
score: clampScore(sr.score, sName),
|
|
@@ -1240,7 +1400,7 @@ async function runEval(config) {
|
|
|
1240
1400
|
idx: index,
|
|
1241
1401
|
input,
|
|
1242
1402
|
output: finalResult.output || null,
|
|
1243
|
-
expected
|
|
1403
|
+
expected,
|
|
1244
1404
|
latency_ms: finalResult.latencyMs,
|
|
1245
1405
|
tokens_in: finalResult.tokensIn,
|
|
1246
1406
|
tokens_out: finalResult.tokensOut,
|
|
@@ -1276,7 +1436,7 @@ async function runEval(config) {
|
|
|
1276
1436
|
index,
|
|
1277
1437
|
input,
|
|
1278
1438
|
output: finalResult.output,
|
|
1279
|
-
expected
|
|
1439
|
+
expected,
|
|
1280
1440
|
scores: finalScores,
|
|
1281
1441
|
error: finalResult.error,
|
|
1282
1442
|
latencyMs: finalResult.latencyMs,
|
|
@@ -1721,21 +1881,31 @@ function renderFailuresByScorer(cases, threshold) {
|
|
|
1721
1881
|
}
|
|
1722
1882
|
|
|
1723
1883
|
// packages/evals/src/reporters/json.ts
|
|
1724
|
-
import {
|
|
1884
|
+
import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
1725
1885
|
function jsonReporter(options) {
|
|
1726
1886
|
const outputDir = resolveOutputDir(options?.outputDir);
|
|
1727
1887
|
const pretty = options?.pretty ?? true;
|
|
1728
|
-
|
|
1888
|
+
const streamFiles = /* @__PURE__ */ new Map();
|
|
1729
1889
|
return {
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1890
|
+
onRunStart(data) {
|
|
1891
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
1892
|
+
streamFiles.set(
|
|
1893
|
+
data.runId,
|
|
1894
|
+
getReportPath(outputDir, data.name, data.runId, "jsonl")
|
|
1895
|
+
);
|
|
1733
1896
|
},
|
|
1734
|
-
|
|
1897
|
+
onCaseEnd(data) {
|
|
1898
|
+
const streamFile = streamFiles.get(data.runId);
|
|
1899
|
+
if (streamFile === void 0) {
|
|
1900
|
+
throw new Error(
|
|
1901
|
+
`jsonReporter received a case for run ${data.runId} before its onRunStart`
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1735
1904
|
const line = stringifyUnknown(data, { space: 0, fallback: "null" });
|
|
1736
|
-
|
|
1905
|
+
appendFileSync(streamFile, line + "\n", "utf-8");
|
|
1737
1906
|
},
|
|
1738
1907
|
async onRunEnd(data) {
|
|
1908
|
+
streamFiles.delete(data.runId);
|
|
1739
1909
|
const content = stringifyUnknown(data, {
|
|
1740
1910
|
space: pretty ? 2 : 0,
|
|
1741
1911
|
fallback: "null"
|
|
@@ -2015,12 +2185,11 @@ function resolveFailedIndexes(store, suiteName, model, threshold) {
|
|
|
2015
2185
|
return new Set(failingCases.map((c) => c.idx));
|
|
2016
2186
|
}
|
|
2017
2187
|
var EvalBuilder = class {
|
|
2018
|
-
|
|
2019
|
-
#options;
|
|
2188
|
+
#plan;
|
|
2020
2189
|
#selection = { type: "all" };
|
|
2021
2190
|
#shouldAssert = false;
|
|
2022
|
-
constructor(
|
|
2023
|
-
this.#
|
|
2191
|
+
constructor(plan) {
|
|
2192
|
+
this.#plan = plan;
|
|
2024
2193
|
}
|
|
2025
2194
|
#setSelection(selection) {
|
|
2026
2195
|
if (this.#selection.type !== "all") {
|
|
@@ -2052,77 +2221,76 @@ var EvalBuilder = class {
|
|
|
2052
2221
|
return this.#execute().then(onfulfilled, onrejected);
|
|
2053
2222
|
}
|
|
2054
2223
|
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) {
|
|
2224
|
+
const result = await this.#plan.run(this.#selection);
|
|
2225
|
+
if (this.#shouldAssert && this.#plan.hasFailures(result)) {
|
|
2090
2226
|
throw new EvalAssertionError(result);
|
|
2091
2227
|
}
|
|
2092
2228
|
return result;
|
|
2093
2229
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2230
|
+
};
|
|
2231
|
+
function selectCases(ds, selection) {
|
|
2232
|
+
switch (selection.type) {
|
|
2233
|
+
case "all":
|
|
2234
|
+
return ds;
|
|
2235
|
+
case "cases":
|
|
2236
|
+
return selection.indexes.size > 0 ? filterRecordsByIndex(ds, selection.indexes) : ds;
|
|
2237
|
+
case "sample":
|
|
2238
|
+
return dataset(ds).sample(selection.count);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
var SingleModelEval = class {
|
|
2242
|
+
#options;
|
|
2243
|
+
constructor(options) {
|
|
2244
|
+
this.#options = options;
|
|
2245
|
+
}
|
|
2246
|
+
run(selection) {
|
|
2247
|
+
return evaluateSingle({
|
|
2248
|
+
...this.#options,
|
|
2249
|
+
dataset: this.#cases(selection)
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
hasFailures(summary) {
|
|
2253
|
+
return summary.failCount > 0;
|
|
2254
|
+
}
|
|
2255
|
+
#cases(selection) {
|
|
2256
|
+
const { dataset: ds, store, name, model, threshold } = this.#options;
|
|
2257
|
+
if (selection.type !== "failed") return selectCases(ds, selection);
|
|
2258
|
+
const indexes = resolveFailedIndexes(store, name, model, threshold);
|
|
2259
|
+
return indexes.size > 0 ? filterRecordsByIndex(ds, indexes) : ds;
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
var EachModelEval = class {
|
|
2263
|
+
#options;
|
|
2264
|
+
constructor(options) {
|
|
2265
|
+
this.#options = options;
|
|
2266
|
+
}
|
|
2267
|
+
run(selection) {
|
|
2268
|
+
if (selection.type === "failed") {
|
|
2269
|
+
return evaluateEach(this.#options, this.#failedIndexesPerModel());
|
|
2117
2270
|
}
|
|
2118
|
-
return
|
|
2271
|
+
return evaluateEach({
|
|
2272
|
+
...this.#options,
|
|
2273
|
+
dataset: selectCases(this.#options.dataset, selection)
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
hasFailures(summaries) {
|
|
2277
|
+
return summaries.some((s) => s.failCount > 0);
|
|
2278
|
+
}
|
|
2279
|
+
#failedIndexesPerModel() {
|
|
2280
|
+
const { models, store, name, threshold } = this.#options;
|
|
2281
|
+
return new Map(
|
|
2282
|
+
models.map((variant) => [
|
|
2283
|
+
variant.name,
|
|
2284
|
+
resolveFailedIndexes(store, name, variant.name, threshold)
|
|
2285
|
+
])
|
|
2286
|
+
);
|
|
2119
2287
|
}
|
|
2120
2288
|
};
|
|
2121
2289
|
function evaluate(options) {
|
|
2122
2290
|
if ("models" in options) {
|
|
2123
|
-
return new EvalBuilder(options);
|
|
2291
|
+
return new EvalBuilder(new EachModelEval(options));
|
|
2124
2292
|
}
|
|
2125
|
-
return new EvalBuilder(options);
|
|
2293
|
+
return new EvalBuilder(new SingleModelEval(options));
|
|
2126
2294
|
}
|
|
2127
2295
|
function wireReporters(reporters) {
|
|
2128
2296
|
const emitter = new EvalEmitter();
|