@botpress/evals 1.19.0-beta.9 → 2.0.0-alpha.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/client.d.ts +21 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +103 -36
- package/dist/client.js.map +4 -4
- package/dist/definition.d.ts +9 -2
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js.map +2 -2
- package/dist/graders/index.js +21 -74
- package/dist/graders/index.js.map +2 -2
- package/dist/graders/llm.d.ts +12 -12
- package/dist/graders/llm.d.ts.map +1 -1
- package/dist/graders/llm.js +30 -75
- package/dist/graders/llm.js.map +2 -2
- package/dist/graders/response.js +16 -65
- package/dist/graders/response.js.map +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +3 -3
- package/dist/loader.js.map +2 -2
- package/dist/manifest.d.ts +13 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +316 -183
- package/dist/runner.js.map +4 -4
- package/dist/spans/index.d.ts +5 -0
- package/dist/spans/index.d.ts.map +1 -0
- package/dist/spans/index.js +725 -0
- package/dist/spans/index.js.map +7 -0
- package/dist/spans/local-span-source.d.ts +19 -0
- package/dist/spans/local-span-source.d.ts.map +1 -0
- package/dist/spans/local-span-source.js +543 -0
- package/dist/spans/local-span-source.js.map +7 -0
- package/dist/spans/span-source.d.ts +40 -0
- package/dist/spans/span-source.d.ts.map +1 -0
- package/dist/spans/span-source.js +1 -0
- package/dist/{sse-collector.d.ts → spans/sse-collector.d.ts} +44 -5
- package/dist/spans/sse-collector.d.ts.map +1 -0
- package/dist/{sse-collector.js → spans/sse-collector.js} +97 -20
- package/dist/spans/sse-collector.js.map +7 -0
- package/dist/spans/trace.d.ts.map +1 -0
- package/dist/spans/trace.js.map +7 -0
- package/dist/spans/vortex-span-source.d.ts +42 -0
- package/dist/spans/vortex-span-source.d.ts.map +1 -0
- package/dist/spans/vortex-span-source.js +184 -0
- package/dist/spans/vortex-span-source.js.map +7 -0
- package/dist/stores/eval-definition-loader.d.ts +4 -0
- package/dist/stores/eval-definition-loader.d.ts.map +1 -0
- package/dist/stores/eval-definition-loader.js +142 -0
- package/dist/stores/eval-definition-loader.js.map +7 -0
- package/dist/stores/eval-store.d.ts +61 -0
- package/dist/stores/eval-store.d.ts.map +1 -0
- package/dist/stores/eval-store.js +1 -0
- package/dist/stores/eval-store.js.map +7 -0
- package/dist/stores/index.d.ts +8 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +1156 -0
- package/dist/stores/index.js.map +7 -0
- package/dist/stores/local-eval-store.d.ts +66 -0
- package/dist/stores/local-eval-store.d.ts.map +1 -0
- package/dist/stores/local-eval-store.js +595 -0
- package/dist/stores/local-eval-store.js.map +7 -0
- package/dist/stores/vortex-eval-store-entry.d.ts +6 -0
- package/dist/stores/vortex-eval-store-entry.d.ts.map +1 -0
- package/dist/stores/vortex-eval-store-entry.js +702 -0
- package/dist/stores/vortex-eval-store-entry.js.map +7 -0
- package/dist/stores/vortex-eval-store.d.ts +78 -0
- package/dist/stores/vortex-eval-store.d.ts.map +1 -0
- package/dist/stores/vortex-eval-store.js +562 -0
- package/dist/stores/vortex-eval-store.js.map +7 -0
- package/dist/transformer.d.ts +2 -2
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +1 -1
- package/dist/transformer.js.map +2 -2
- package/dist/types.d.ts +38 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +2 -2
- package/package.json +66 -6
- package/dist/sse-collector.d.ts.map +0 -1
- package/dist/sse-collector.js.map +0 -7
- package/dist/trace.d.ts.map +0 -1
- /package/dist/{trace.js.map → spans/span-source.js.map} +0 -0
- /package/dist/{trace.d.ts → spans/trace.d.ts} +0 -0
- /package/dist/{trace.js → spans/trace.js} +0 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
// src/stores/local-eval-store.ts
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync, renameSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
// src/loader.ts
|
|
7
|
+
import { readdirSync, existsSync } from "fs";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
|
|
10
|
+
// src/definition.ts
|
|
11
|
+
var Eval = class {
|
|
12
|
+
name;
|
|
13
|
+
description;
|
|
14
|
+
tags;
|
|
15
|
+
type;
|
|
16
|
+
setup;
|
|
17
|
+
conversation;
|
|
18
|
+
outcome;
|
|
19
|
+
options;
|
|
20
|
+
constructor(def) {
|
|
21
|
+
this.name = def.name;
|
|
22
|
+
this.conversation = def.conversation;
|
|
23
|
+
if (def.description !== void 0) this.description = def.description;
|
|
24
|
+
if (def.tags !== void 0) this.tags = def.tags;
|
|
25
|
+
if (def.type !== void 0) this.type = def.type;
|
|
26
|
+
if (def.setup !== void 0) this.setup = def.setup;
|
|
27
|
+
if (def.outcome !== void 0) this.outcome = def.outcome;
|
|
28
|
+
if (def.options !== void 0) this.options = def.options;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ../analytics/src/errors.ts
|
|
33
|
+
var AdkError = class extends Error {
|
|
34
|
+
/**
|
|
35
|
+
* Marker for cross-module-instance checks — `instanceof` breaks when a
|
|
36
|
+
* package is duplicated in the dependency graph.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
static __IS_ADK_BASE_ERROR = true;
|
|
40
|
+
/**
|
|
41
|
+
* Stable SCREAMING_SNAKE_CASE identifier, e.g. 'BOT_NOT_FOUND'. Subclasses
|
|
42
|
+
* pin `Code` to a literal union (`class EvalRunnerError extends
|
|
43
|
+
* AdkError<EvalErrorCode>`) so typos are compile errors and `code` narrows
|
|
44
|
+
* in switches.
|
|
45
|
+
*/
|
|
46
|
+
code;
|
|
47
|
+
/**
|
|
48
|
+
* True for user/environment conditions the developer can fix themselves
|
|
49
|
+
* (validation, auth, connectivity). False for internal bugs. Defaults to
|
|
50
|
+
* false so unclassified errors surface as bugs until someone triages them.
|
|
51
|
+
*/
|
|
52
|
+
expected;
|
|
53
|
+
/** Structured context. Sanitized before any telemetry leaves the machine. */
|
|
54
|
+
details;
|
|
55
|
+
/** Actionable next step shown to the user, e.g. "Run 'adk login' first." */
|
|
56
|
+
suggestion;
|
|
57
|
+
constructor(opts) {
|
|
58
|
+
super(opts.message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
59
|
+
this.name = this.constructor.name;
|
|
60
|
+
this.code = opts.code;
|
|
61
|
+
this.expected = opts.expected ?? false;
|
|
62
|
+
if (opts.details !== void 0) {
|
|
63
|
+
this.details = opts.details;
|
|
64
|
+
}
|
|
65
|
+
if (opts.suggestion !== void 0) {
|
|
66
|
+
this.suggestion = opts.suggestion;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/errors.ts
|
|
72
|
+
var EvalRunnerError = class extends AdkError {
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/loader.ts
|
|
76
|
+
function isEvalDefinition(value) {
|
|
77
|
+
return value !== null && typeof value === "object" && typeof value.name === "string" && value.name !== "" && Array.isArray(value.conversation);
|
|
78
|
+
}
|
|
79
|
+
async function loadEvalFile(filePath) {
|
|
80
|
+
const absPath = resolve(filePath);
|
|
81
|
+
let mod;
|
|
82
|
+
try {
|
|
83
|
+
mod = await import(absPath);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
86
|
+
throw new EvalRunnerError({
|
|
87
|
+
code: "EVAL_LOAD_FAILED",
|
|
88
|
+
message: `Failed to load eval file ${filePath}: ${msg}
|
|
89
|
+
|
|
90
|
+
Make sure your eval file:
|
|
91
|
+
- Has no syntax or type errors
|
|
92
|
+
- Exports one or more \`new Eval({...})\` instances
|
|
93
|
+
- Has all dependencies installed (\`bun install\`)`,
|
|
94
|
+
expected: true,
|
|
95
|
+
cause: err
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const [key, value] of Object.entries(mod)) {
|
|
100
|
+
if (key === "__esModule") continue;
|
|
101
|
+
if (value instanceof Eval || isEvalDefinition(value)) {
|
|
102
|
+
results.push(value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (results.length === 0) {
|
|
106
|
+
throw new EvalRunnerError({
|
|
107
|
+
code: "EVAL_FILE_EMPTY",
|
|
108
|
+
message: `Invalid eval file ${filePath}: no valid evals found. Export one or more \`new Eval({...})\` instances (as default or named exports).`,
|
|
109
|
+
expected: true
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
async function loadEvalsFromDir(dirPath) {
|
|
115
|
+
const absDir = resolve(dirPath);
|
|
116
|
+
if (!existsSync(absDir)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const files = readdirSync(absDir).filter((f) => f.endsWith(".eval.ts"));
|
|
120
|
+
const evals = [];
|
|
121
|
+
for (const f of files) {
|
|
122
|
+
const defs = await loadEvalFile(`${absDir}/${f}`);
|
|
123
|
+
evals.push(...defs);
|
|
124
|
+
}
|
|
125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
126
|
+
for (const e of evals) {
|
|
127
|
+
if (seen.has(e.name)) {
|
|
128
|
+
throw new EvalRunnerError({
|
|
129
|
+
code: "EVAL_DUPLICATE_NAME",
|
|
130
|
+
message: `Duplicate eval name "${e.name}" found in ${dirPath} \u2014 names must be unique across the evals directory.`,
|
|
131
|
+
expected: true,
|
|
132
|
+
details: { name: e.name }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
seen.add(e.name);
|
|
136
|
+
}
|
|
137
|
+
return evals;
|
|
138
|
+
}
|
|
139
|
+
function filterEvals(evals, filter) {
|
|
140
|
+
if (!filter) return evals;
|
|
141
|
+
return evals.filter((e) => {
|
|
142
|
+
if (filter.names && filter.names.length > 0) {
|
|
143
|
+
if (!filter.names.includes(e.name)) return false;
|
|
144
|
+
}
|
|
145
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
146
|
+
if (!e.tags || !filter.tags.some((t) => e.tags.includes(t))) return false;
|
|
147
|
+
}
|
|
148
|
+
if (filter.type) {
|
|
149
|
+
if (e.type !== filter.type) return false;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/stores/eval-definition-loader.ts
|
|
156
|
+
function createDiskEvalLoader(evalsDir) {
|
|
157
|
+
return () => loadEvalsFromDir(evalsDir);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/stores/local-eval-store.ts
|
|
161
|
+
var errText = (err) => err instanceof Error ? err.message : String(err);
|
|
162
|
+
var SCHEMA_VERSION = "2";
|
|
163
|
+
var SCHEMA = `
|
|
164
|
+
CREATE TABLE IF NOT EXISTS eval_runs (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
timestamp TEXT NOT NULL,
|
|
167
|
+
started_at INTEGER NOT NULL,
|
|
168
|
+
passed INTEGER NOT NULL,
|
|
169
|
+
failed INTEGER NOT NULL,
|
|
170
|
+
total INTEGER NOT NULL,
|
|
171
|
+
duration REAL NOT NULL,
|
|
172
|
+
bot_duration REAL NOT NULL DEFAULT 0,
|
|
173
|
+
eval_duration REAL NOT NULL DEFAULT 0,
|
|
174
|
+
aborted INTEGER NOT NULL DEFAULT 0,
|
|
175
|
+
filter_json TEXT,
|
|
176
|
+
eval_names TEXT NOT NULL DEFAULT '[]'
|
|
177
|
+
);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_eval_runs_started ON eval_runs(started_at DESC);
|
|
179
|
+
|
|
180
|
+
CREATE TABLE IF NOT EXISTS eval_reports (
|
|
181
|
+
run_id TEXT NOT NULL REFERENCES eval_runs(id) ON DELETE CASCADE,
|
|
182
|
+
seq INTEGER NOT NULL,
|
|
183
|
+
name TEXT NOT NULL,
|
|
184
|
+
started_at INTEGER NOT NULL,
|
|
185
|
+
pass INTEGER NOT NULL,
|
|
186
|
+
duration REAL NOT NULL,
|
|
187
|
+
type TEXT,
|
|
188
|
+
error TEXT,
|
|
189
|
+
report_json TEXT NOT NULL,
|
|
190
|
+
PRIMARY KEY (run_id, seq)
|
|
191
|
+
);
|
|
192
|
+
CREATE INDEX IF NOT EXISTS idx_eval_reports_name_time ON eval_reports(name, started_at DESC);
|
|
193
|
+
|
|
194
|
+
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
195
|
+
key TEXT PRIMARY KEY,
|
|
196
|
+
value TEXT NOT NULL
|
|
197
|
+
);
|
|
198
|
+
`;
|
|
199
|
+
function aggregateTurnTimings(report) {
|
|
200
|
+
let botDuration = 0;
|
|
201
|
+
let evalDuration = 0;
|
|
202
|
+
for (const e of report.evals) {
|
|
203
|
+
for (const t of e.turns) {
|
|
204
|
+
botDuration += t.botDuration;
|
|
205
|
+
evalDuration += t.evalDuration;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { botDuration, evalDuration };
|
|
209
|
+
}
|
|
210
|
+
function toEvalSummary(e) {
|
|
211
|
+
return {
|
|
212
|
+
name: e.name,
|
|
213
|
+
description: e.description,
|
|
214
|
+
tags: e.tags || [],
|
|
215
|
+
type: e.type || "capability",
|
|
216
|
+
turnCount: e.conversation.length,
|
|
217
|
+
hasOutcome: !!e.outcome
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
var LocalEvalStore = class {
|
|
221
|
+
db = null;
|
|
222
|
+
dbPath;
|
|
223
|
+
evalsDir;
|
|
224
|
+
insertRunStmt = null;
|
|
225
|
+
insertReportStmt = null;
|
|
226
|
+
_getActiveRunId;
|
|
227
|
+
_loadEvalDefinitions;
|
|
228
|
+
constructor(config) {
|
|
229
|
+
this.evalsDir = join(config.agentPath, ".adk", "evals");
|
|
230
|
+
this.dbPath = join(this.evalsDir, "evals.db");
|
|
231
|
+
this._getActiveRunId = config.getActiveRunId ?? (() => null);
|
|
232
|
+
this._loadEvalDefinitions = config.loadEvalDefinitions ?? createDiskEvalLoader(join(config.agentPath, "evals"));
|
|
233
|
+
}
|
|
234
|
+
// ── Eval definitions (from disk) ───────────────────────────────────
|
|
235
|
+
async listEvals(filter) {
|
|
236
|
+
const allEvals = await this._loadEvalDefinitions();
|
|
237
|
+
const evals = filter ? filterEvals(allEvals, filter) : allEvals;
|
|
238
|
+
return evals.map(toEvalSummary);
|
|
239
|
+
}
|
|
240
|
+
async getEval(name) {
|
|
241
|
+
const allEvals = await this._loadEvalDefinitions();
|
|
242
|
+
return allEvals.find((e) => e.name === name) ?? null;
|
|
243
|
+
}
|
|
244
|
+
// ── Run lifecycle — write ───────────────────────────────────────────
|
|
245
|
+
async createRun(_runType, _metadata) {
|
|
246
|
+
return crypto.randomUUID().replace(/-/g, "").slice(0, 26);
|
|
247
|
+
}
|
|
248
|
+
async addRunResults(_runId, _evalReport) {
|
|
249
|
+
}
|
|
250
|
+
async completeRun(_runId, report) {
|
|
251
|
+
const db = this.getDb();
|
|
252
|
+
if (!db) return;
|
|
253
|
+
db.run("BEGIN");
|
|
254
|
+
try {
|
|
255
|
+
this.insertRunReport(report);
|
|
256
|
+
db.run("COMMIT");
|
|
257
|
+
} catch (err) {
|
|
258
|
+
db.run("ROLLBACK");
|
|
259
|
+
console.error(`[local-eval-store] completeRun transaction failed (run ${report.id}): ${errText(err)}`);
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ── Run results — read ─────────────────────────────────────────────
|
|
264
|
+
async loadRunResult(runId) {
|
|
265
|
+
const db = this.getDb();
|
|
266
|
+
if (!db) return null;
|
|
267
|
+
const runRow = db.prepare("SELECT * FROM eval_runs WHERE id = ?").get(runId);
|
|
268
|
+
if (!runRow) return null;
|
|
269
|
+
const reportRows = db.prepare("SELECT * FROM eval_reports WHERE run_id = ? ORDER BY seq").all(runRow.id);
|
|
270
|
+
return this.assembleRun(runRow, reportRows);
|
|
271
|
+
}
|
|
272
|
+
async getLatestRun() {
|
|
273
|
+
const db = this.getDb();
|
|
274
|
+
if (!db) return null;
|
|
275
|
+
const runRow = db.prepare("SELECT * FROM eval_runs ORDER BY started_at DESC LIMIT 1").get();
|
|
276
|
+
if (!runRow) return null;
|
|
277
|
+
const reportRows = db.prepare("SELECT * FROM eval_reports WHERE run_id = ? ORDER BY seq").all(runRow.id);
|
|
278
|
+
return this.assembleRun(runRow, reportRows);
|
|
279
|
+
}
|
|
280
|
+
async listRunSummaries(opts) {
|
|
281
|
+
const db = this.getDb();
|
|
282
|
+
if (!db) return [];
|
|
283
|
+
const limit = opts?.limit ?? 50;
|
|
284
|
+
const sinceTs = opts?.since;
|
|
285
|
+
const rows = sinceTs !== void 0 ? db.prepare(
|
|
286
|
+
"SELECT * FROM eval_runs WHERE started_at >= ? ORDER BY started_at DESC LIMIT ?"
|
|
287
|
+
).all(sinceTs, limit) : db.prepare("SELECT * FROM eval_runs ORDER BY started_at DESC LIMIT ?").all(limit);
|
|
288
|
+
return rows.map((r) => ({
|
|
289
|
+
id: r.id,
|
|
290
|
+
timestamp: r.timestamp,
|
|
291
|
+
passed: r.passed,
|
|
292
|
+
failed: r.failed,
|
|
293
|
+
total: r.total,
|
|
294
|
+
duration: r.duration,
|
|
295
|
+
botDuration: r.bot_duration,
|
|
296
|
+
evalDuration: r.eval_duration,
|
|
297
|
+
filter: r.filter_json ? JSON.parse(r.filter_json) : void 0,
|
|
298
|
+
evalNames: JSON.parse(r.eval_names),
|
|
299
|
+
aborted: r.aborted === 1
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
async listEvalReportsByName(evalName, opts) {
|
|
303
|
+
const db = this.getDb();
|
|
304
|
+
if (!db) return [];
|
|
305
|
+
const limit = opts?.limit ?? 20;
|
|
306
|
+
const sinceTs = opts?.since;
|
|
307
|
+
const baseSql = `
|
|
308
|
+
SELECT er.*,
|
|
309
|
+
r.timestamp AS run_timestamp,
|
|
310
|
+
r.total AS run_total,
|
|
311
|
+
r.filter_json AS run_filter_json
|
|
312
|
+
FROM eval_reports er
|
|
313
|
+
JOIN eval_runs r ON r.id = er.run_id
|
|
314
|
+
WHERE er.name = ?
|
|
315
|
+
`;
|
|
316
|
+
const rows = sinceTs !== void 0 ? db.prepare(
|
|
317
|
+
`${baseSql} AND er.started_at >= ? ORDER BY er.started_at DESC LIMIT ?`
|
|
318
|
+
).all(evalName, sinceTs, limit) : db.prepare(`${baseSql} ORDER BY er.started_at DESC LIMIT ?`).all(evalName, limit);
|
|
319
|
+
return rows.map((row) => ({
|
|
320
|
+
runId: row.run_id,
|
|
321
|
+
timestamp: row.run_timestamp,
|
|
322
|
+
report: JSON.parse(row.report_json),
|
|
323
|
+
totalEvalsInRun: row.run_total,
|
|
324
|
+
filter: row.run_filter_json ? JSON.parse(row.run_filter_json) : void 0
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
async listEvalReportsBulk(opts) {
|
|
328
|
+
const db = this.getDb();
|
|
329
|
+
if (!db) return {};
|
|
330
|
+
const perEval = opts?.perEval ?? 20;
|
|
331
|
+
const sinceTs = opts?.since;
|
|
332
|
+
const sinceClause = sinceTs !== void 0 ? "WHERE er.started_at >= ?" : "";
|
|
333
|
+
const sql = `
|
|
334
|
+
WITH ranked AS (
|
|
335
|
+
SELECT er.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY started_at DESC) AS rn
|
|
336
|
+
FROM eval_reports er
|
|
337
|
+
${sinceClause}
|
|
338
|
+
)
|
|
339
|
+
SELECT ranked.run_id, ranked.name, ranked.started_at, ranked.pass,
|
|
340
|
+
ranked.duration, ranked.type, ranked.error, ranked.report_json,
|
|
341
|
+
r.timestamp AS run_timestamp,
|
|
342
|
+
r.total AS run_total,
|
|
343
|
+
r.filter_json AS run_filter_json
|
|
344
|
+
FROM ranked
|
|
345
|
+
JOIN eval_runs r ON r.id = ranked.run_id
|
|
346
|
+
WHERE ranked.rn <= ?
|
|
347
|
+
ORDER BY ranked.name, ranked.started_at DESC
|
|
348
|
+
`;
|
|
349
|
+
const rows = sinceTs !== void 0 ? db.prepare(sql).all(sinceTs, perEval) : db.prepare(sql).all(perEval);
|
|
350
|
+
const out = {};
|
|
351
|
+
for (const row of rows) {
|
|
352
|
+
const entry = {
|
|
353
|
+
runId: row.run_id,
|
|
354
|
+
timestamp: row.run_timestamp,
|
|
355
|
+
report: JSON.parse(row.report_json),
|
|
356
|
+
totalEvalsInRun: row.run_total,
|
|
357
|
+
filter: row.run_filter_json ? JSON.parse(row.run_filter_json) : void 0
|
|
358
|
+
};
|
|
359
|
+
const arr = out[row.name];
|
|
360
|
+
if (arr) arr.push(entry);
|
|
361
|
+
else out[row.name] = [entry];
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
// ── Live run tracking ──────────────────────────────────────────────
|
|
366
|
+
async *watchRun(_signal, _options) {
|
|
367
|
+
}
|
|
368
|
+
// ── Runner state ───────────────────────────────────────────────────
|
|
369
|
+
async getRunnerState() {
|
|
370
|
+
const runId = this._getActiveRunId();
|
|
371
|
+
return { running: runId !== null, runId };
|
|
372
|
+
}
|
|
373
|
+
// ── Extra (not on EvalStore interface) ──────────────────────────────
|
|
374
|
+
async listRunResults(limit = 50, sinceTs) {
|
|
375
|
+
const db = this.getDb();
|
|
376
|
+
if (!db) return [];
|
|
377
|
+
let runRows;
|
|
378
|
+
if (sinceTs !== void 0) {
|
|
379
|
+
runRows = db.prepare(
|
|
380
|
+
"SELECT * FROM eval_runs WHERE started_at >= ? ORDER BY started_at DESC LIMIT ?"
|
|
381
|
+
).all(sinceTs, limit);
|
|
382
|
+
} else {
|
|
383
|
+
runRows = db.prepare("SELECT * FROM eval_runs ORDER BY started_at DESC LIMIT ?").all(limit);
|
|
384
|
+
}
|
|
385
|
+
if (runRows.length === 0) return [];
|
|
386
|
+
const ids = runRows.map((r) => r.id);
|
|
387
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
388
|
+
const reportRows = db.prepare(`SELECT * FROM eval_reports WHERE run_id IN (${placeholders}) ORDER BY seq`).all(...ids);
|
|
389
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
390
|
+
for (const row of reportRows) {
|
|
391
|
+
const arr = byRun.get(row.run_id);
|
|
392
|
+
if (arr) arr.push(row);
|
|
393
|
+
else byRun.set(row.run_id, [row]);
|
|
394
|
+
}
|
|
395
|
+
return runRows.map((row) => this.assembleRun(row, byRun.get(row.id) ?? []));
|
|
396
|
+
}
|
|
397
|
+
// ── Lifecycle ──────────────────────────────────────────────────────
|
|
398
|
+
close() {
|
|
399
|
+
try {
|
|
400
|
+
this.db?.close();
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
this.db = null;
|
|
404
|
+
this.insertRunStmt = null;
|
|
405
|
+
this.insertReportStmt = null;
|
|
406
|
+
}
|
|
407
|
+
getDbPath() {
|
|
408
|
+
return this.dbPath;
|
|
409
|
+
}
|
|
410
|
+
// ── SQLite internals ───────────────────────────────────────────────
|
|
411
|
+
getDb() {
|
|
412
|
+
if (this.db) return this.db;
|
|
413
|
+
mkdirSync(this.evalsDir, { recursive: true });
|
|
414
|
+
try {
|
|
415
|
+
this.db = new Database(this.dbPath);
|
|
416
|
+
this.db.run("PRAGMA journal_mode=WAL");
|
|
417
|
+
this.db.run("PRAGMA synchronous=NORMAL");
|
|
418
|
+
this.db.run("PRAGMA busy_timeout=5000");
|
|
419
|
+
this.db.run("PRAGMA foreign_keys=ON");
|
|
420
|
+
this.db.run(SCHEMA);
|
|
421
|
+
this.setMeta("schema_version", SCHEMA_VERSION);
|
|
422
|
+
this.insertRunStmt = this.db.prepare(`
|
|
423
|
+
INSERT OR REPLACE INTO eval_runs (
|
|
424
|
+
id, timestamp, started_at, passed, failed, total, duration,
|
|
425
|
+
bot_duration, eval_duration, aborted, filter_json, eval_names
|
|
426
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
427
|
+
`);
|
|
428
|
+
this.insertReportStmt = this.db.prepare(`
|
|
429
|
+
INSERT OR REPLACE INTO eval_reports (
|
|
430
|
+
run_id, seq, name, started_at, pass, duration, type, error, report_json
|
|
431
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
432
|
+
`);
|
|
433
|
+
this.importLegacyJsonRunsIfNeeded();
|
|
434
|
+
return this.db;
|
|
435
|
+
} catch (err) {
|
|
436
|
+
console.warn("[local-eval-store] Failed to initialize database:", err);
|
|
437
|
+
try {
|
|
438
|
+
this.db?.close();
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
if (existsSync2(this.dbPath)) {
|
|
443
|
+
renameSync(this.dbPath, `${this.dbPath}.corrupt-${Date.now()}`);
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
this.db = null;
|
|
448
|
+
this.insertRunStmt = null;
|
|
449
|
+
this.insertReportStmt = null;
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
getMeta(key) {
|
|
454
|
+
const db = this.db;
|
|
455
|
+
if (!db) return null;
|
|
456
|
+
const row = db.prepare("SELECT value FROM schema_meta WHERE key = ?").get(key);
|
|
457
|
+
return row?.value ?? null;
|
|
458
|
+
}
|
|
459
|
+
setMeta(key, value) {
|
|
460
|
+
const db = this.db;
|
|
461
|
+
if (!db) return;
|
|
462
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
463
|
+
}
|
|
464
|
+
importLegacyJsonRunsIfNeeded() {
|
|
465
|
+
const db = this.db;
|
|
466
|
+
if (!db) return;
|
|
467
|
+
if (this.getMeta("imported_legacy") === "1") return;
|
|
468
|
+
const legacyDir = join(this.evalsDir, "runs");
|
|
469
|
+
if (!existsSync2(legacyDir)) {
|
|
470
|
+
this.setMeta("imported_legacy", "1");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
let files = [];
|
|
474
|
+
try {
|
|
475
|
+
files = readdirSync2(legacyDir).filter((f) => f.endsWith(".json"));
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.warn("[local-eval-store] Could not read legacy runs dir:", err);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (files.length === 0) {
|
|
481
|
+
this.setMeta("imported_legacy", "1");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
let imported = 0;
|
|
485
|
+
let skipped = 0;
|
|
486
|
+
db.run("BEGIN");
|
|
487
|
+
try {
|
|
488
|
+
for (const file of files) {
|
|
489
|
+
db.run("SAVEPOINT import_file");
|
|
490
|
+
try {
|
|
491
|
+
const raw = readFileSync(join(legacyDir, file), "utf-8");
|
|
492
|
+
const report = JSON.parse(raw);
|
|
493
|
+
this.insertRunReport(report);
|
|
494
|
+
db.run("RELEASE import_file");
|
|
495
|
+
imported++;
|
|
496
|
+
} catch (err) {
|
|
497
|
+
db.run("ROLLBACK TO import_file");
|
|
498
|
+
db.run("RELEASE import_file");
|
|
499
|
+
skipped++;
|
|
500
|
+
console.warn(`[local-eval-store] Skipping unreadable legacy run ${file}:`, err);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this.setMeta("imported_legacy", "1");
|
|
504
|
+
db.run("COMMIT");
|
|
505
|
+
} catch (err) {
|
|
506
|
+
db.run("ROLLBACK");
|
|
507
|
+
console.warn("[local-eval-store] Legacy import failed, will retry on next start:", err);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
renameSync(legacyDir, join(this.evalsDir, "runs.json-migrated"));
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.warn("[local-eval-store] Could not rename legacy runs dir:", err);
|
|
514
|
+
}
|
|
515
|
+
console.log(
|
|
516
|
+
`[local-eval-store] Imported ${imported} legacy eval run${imported === 1 ? "" : "s"}` + (skipped > 0 ? ` (${skipped} skipped)` : "")
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
insertRunReport(report) {
|
|
520
|
+
if (!this.insertRunStmt || !this.insertReportStmt) return;
|
|
521
|
+
const startedAt = Date.parse(report.timestamp);
|
|
522
|
+
const { botDuration, evalDuration } = aggregateTurnTimings(report);
|
|
523
|
+
this.insertRunStmt.run(
|
|
524
|
+
report.id,
|
|
525
|
+
report.timestamp,
|
|
526
|
+
Number.isNaN(startedAt) ? 0 : startedAt,
|
|
527
|
+
report.passed,
|
|
528
|
+
report.failed,
|
|
529
|
+
report.total,
|
|
530
|
+
report.duration,
|
|
531
|
+
botDuration,
|
|
532
|
+
evalDuration,
|
|
533
|
+
report.aborted ? 1 : 0,
|
|
534
|
+
report.filter ? JSON.stringify(report.filter) : null,
|
|
535
|
+
JSON.stringify(report.evals.map((e) => e.name))
|
|
536
|
+
);
|
|
537
|
+
for (let i = 0; i < report.evals.length; i++) {
|
|
538
|
+
const evalReport = report.evals[i];
|
|
539
|
+
this.insertReportStmt.run(
|
|
540
|
+
report.id,
|
|
541
|
+
i,
|
|
542
|
+
evalReport.name,
|
|
543
|
+
Number.isNaN(startedAt) ? 0 : startedAt,
|
|
544
|
+
evalReport.pass ? 1 : 0,
|
|
545
|
+
evalReport.duration,
|
|
546
|
+
evalReport.type ?? null,
|
|
547
|
+
evalReport.error ?? null,
|
|
548
|
+
JSON.stringify(evalReport)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
assembleRun(runRow, reportRows) {
|
|
553
|
+
const evals = reportRows.map((r) => JSON.parse(r.report_json));
|
|
554
|
+
const report = {
|
|
555
|
+
id: runRow.id,
|
|
556
|
+
timestamp: runRow.timestamp,
|
|
557
|
+
evals,
|
|
558
|
+
passed: runRow.passed,
|
|
559
|
+
failed: runRow.failed,
|
|
560
|
+
total: runRow.total,
|
|
561
|
+
duration: runRow.duration
|
|
562
|
+
};
|
|
563
|
+
if (runRow.filter_json) {
|
|
564
|
+
report.filter = JSON.parse(runRow.filter_json);
|
|
565
|
+
}
|
|
566
|
+
if (runRow.aborted === 1) {
|
|
567
|
+
report.aborted = true;
|
|
568
|
+
}
|
|
569
|
+
return report;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var stores = /* @__PURE__ */ new Map();
|
|
573
|
+
function getLocalEvalStore(agentPath, getActiveRunId) {
|
|
574
|
+
let store = stores.get(agentPath);
|
|
575
|
+
if (!store) {
|
|
576
|
+
store = new LocalEvalStore({ agentPath, getActiveRunId });
|
|
577
|
+
stores.set(agentPath, store);
|
|
578
|
+
}
|
|
579
|
+
return store;
|
|
580
|
+
}
|
|
581
|
+
function closeLocalEvalStores(agentPath) {
|
|
582
|
+
if (agentPath) {
|
|
583
|
+
stores.get(agentPath)?.close();
|
|
584
|
+
stores.delete(agentPath);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
for (const store of stores.values()) store.close();
|
|
588
|
+
stores.clear();
|
|
589
|
+
}
|
|
590
|
+
export {
|
|
591
|
+
LocalEvalStore,
|
|
592
|
+
closeLocalEvalStores,
|
|
593
|
+
getLocalEvalStore
|
|
594
|
+
};
|
|
595
|
+
//# sourceMappingURL=local-eval-store.js.map
|