@axiom-lattice/local-stores 2.0.4 → 2.0.6
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +94 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -4
- package/dist/index.mjs.map +1 -1
- package/jest.config.js +19 -0
- package/package.json +9 -2
- package/src/__tests__/LocalEvalStore.test.ts +96 -0
- package/src/stores/LocalEvalStore.ts +97 -7
package/jest.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: "ts-jest",
|
|
3
|
+
testEnvironment: "node",
|
|
4
|
+
roots: ["<rootDir>/src"],
|
|
5
|
+
testMatch: ["**/__tests__/**/*.test.ts"],
|
|
6
|
+
transform: {
|
|
7
|
+
"^.+\\.ts$": [
|
|
8
|
+
"ts-jest",
|
|
9
|
+
{
|
|
10
|
+
isolatedModules: true,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
moduleNameMapper: {
|
|
15
|
+
"^@axiom-lattice/protocols$": "<rootDir>/../protocols/src/index.ts",
|
|
16
|
+
},
|
|
17
|
+
transformIgnorePatterns: [],
|
|
18
|
+
testTimeout: 10000,
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/local-stores",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Local SQLite-based stores for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,13 +25,19 @@
|
|
|
25
25
|
"@types/sql.js": "^1.4.11",
|
|
26
26
|
"sql.js": "^1.14.1",
|
|
27
27
|
"uuid": "^14.0.1",
|
|
28
|
-
"@axiom-lattice/core": "3.0.
|
|
28
|
+
"@axiom-lattice/core": "3.0.6",
|
|
29
29
|
"@axiom-lattice/protocols": "3.0.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@types/jest": "^29.5.14",
|
|
32
33
|
"@types/node": "^20.11.24",
|
|
33
34
|
"@types/uuid": "^9.0.8",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
36
|
+
"@typescript-eslint/parser": "^7.2.0",
|
|
37
|
+
"eslint": "^8.57.0",
|
|
38
|
+
"jest": "^29.7.0",
|
|
34
39
|
"rimraf": "^5.0.5",
|
|
40
|
+
"ts-jest": "^29.4.0",
|
|
35
41
|
"tsup": "^8.0.1",
|
|
36
42
|
"typescript": "^5.4.2"
|
|
37
43
|
},
|
|
@@ -39,6 +45,7 @@
|
|
|
39
45
|
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap",
|
|
40
46
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
41
47
|
"lint": "eslint src",
|
|
48
|
+
"test": "jest",
|
|
42
49
|
"clean": "rimraf dist"
|
|
43
50
|
}
|
|
44
51
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalEvalStore project name uniqueness tests
|
|
3
|
+
*
|
|
4
|
+
* Verifies the per-tenant unique name constraint enforced by the
|
|
5
|
+
* `idx_lt_eval_projects_tenant_name` index (eval-{agent-id} convention).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it, beforeEach, afterEach } from "@jest/globals";
|
|
9
|
+
import { tmpdir } from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { initDatabase, closeDatabase, ensureTable } from "../database";
|
|
12
|
+
import { LocalEvalStore, DDL } from "../stores/LocalEvalStore";
|
|
13
|
+
|
|
14
|
+
// sql.js persists to disk via DatabaseWrapper.save(), so each test needs a
|
|
15
|
+
// unique file path (":memory:" is treated as a plain relative path).
|
|
16
|
+
let dbPath = "";
|
|
17
|
+
const nextDbPath = () => {
|
|
18
|
+
dbPath = path.join(tmpdir(), `lt-eval-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
19
|
+
return dbPath;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("LocalEvalStore project name uniqueness", () => {
|
|
23
|
+
let store: LocalEvalStore;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
const db = await initDatabase({ dbPath: nextDbPath() });
|
|
27
|
+
store = new LocalEvalStore(db);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
closeDatabase();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("rejects a duplicate project name in the same tenant", async () => {
|
|
35
|
+
await store.createProject("tenant-1", "uuid-1", {
|
|
36
|
+
name: "eval-a1",
|
|
37
|
+
judgeModelConfig: {},
|
|
38
|
+
targetServerConfig: {},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
store.createProject("tenant-1", "uuid-2", {
|
|
43
|
+
name: "eval-a1",
|
|
44
|
+
judgeModelConfig: {},
|
|
45
|
+
targetServerConfig: {},
|
|
46
|
+
})
|
|
47
|
+
).rejects.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("allows the same name in a different tenant", async () => {
|
|
51
|
+
await store.createProject("tenant-1", "uuid-1", {
|
|
52
|
+
name: "eval-a1",
|
|
53
|
+
judgeModelConfig: {},
|
|
54
|
+
targetServerConfig: {},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await expect(
|
|
58
|
+
store.createProject("tenant-2", "uuid-2", {
|
|
59
|
+
name: "eval-a1",
|
|
60
|
+
judgeModelConfig: {},
|
|
61
|
+
targetServerConfig: {},
|
|
62
|
+
})
|
|
63
|
+
).resolves.toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("deduplicates existing duplicate rows on construction and keeps child history", async () => {
|
|
67
|
+
closeDatabase();
|
|
68
|
+
const db = await initDatabase({ dbPath: nextDbPath() });
|
|
69
|
+
// Recreate the schema WITHOUT the unique index, simulating an old install
|
|
70
|
+
// that already holds duplicate rows.
|
|
71
|
+
ensureTable(db, DDL);
|
|
72
|
+
db.exec(`
|
|
73
|
+
INSERT INTO lt_eval_projects (id, tenant_id, name, judge_model_config, target_server_config, concurrency, created_at, updated_at)
|
|
74
|
+
VALUES
|
|
75
|
+
('dup-old', 'tenant-1', 'eval-a1', '{}', '{}', 3, '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z'),
|
|
76
|
+
('dup-new', 'tenant-1', 'eval-a1', '{}', '{}', 3, '2026-01-02T00:00:00.000Z', '2026-01-02T00:00:00.000Z');
|
|
77
|
+
INSERT INTO lt_eval_suites (id, tenant_id, project_id, name, created_at, updated_at)
|
|
78
|
+
VALUES ('suite-1', 'tenant-1', 'dup-old', 'refund-path', '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z');
|
|
79
|
+
INSERT INTO lt_eval_runs (id, project_id, tenant_id, status, concurrency, total_cases, created_at)
|
|
80
|
+
VALUES ('run-1', 'dup-old', 'tenant-1', 'completed', 1, 1, '2026-01-01T00:00:00.000Z');
|
|
81
|
+
`);
|
|
82
|
+
|
|
83
|
+
const cleaned = new LocalEvalStore(db);
|
|
84
|
+
const projects = await cleaned.getProjectsByTenant("tenant-1");
|
|
85
|
+
|
|
86
|
+
// Kept the newest duplicate (dup-new, created 2026-01-02).
|
|
87
|
+
expect(projects).toHaveLength(1);
|
|
88
|
+
expect(projects[0].id).toBe("dup-new");
|
|
89
|
+
|
|
90
|
+
// Child history was repointed to the kept project, not deleted.
|
|
91
|
+
const suites = db.prepare(`SELECT project_id FROM lt_eval_suites WHERE id = 'suite-1'`).get() as { project_id: string };
|
|
92
|
+
expect(suites.project_id).toBe("dup-new");
|
|
93
|
+
const runs = db.prepare(`SELECT project_id FROM lt_eval_runs WHERE id = 'run-1'`).get() as { project_id: string };
|
|
94
|
+
expect(runs.project_id).toBe("dup-new");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
CreateEvalRunRequest,
|
|
16
16
|
EvalRunResult,
|
|
17
17
|
EvalProjectReport,
|
|
18
|
+
InterruptPolicy,
|
|
18
19
|
} from "@axiom-lattice/protocols";
|
|
19
20
|
import { ensureTable, nowISO, parseISO } from "../database";
|
|
20
21
|
import { randomUUID } from "crypto";
|
|
21
22
|
|
|
22
|
-
const DDL = `
|
|
23
|
+
export const DDL = `
|
|
23
24
|
CREATE TABLE IF NOT EXISTS lt_eval_projects (
|
|
24
25
|
id TEXT NOT NULL,
|
|
25
26
|
tenant_id TEXT NOT NULL,
|
|
@@ -56,6 +57,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_cases (
|
|
|
56
57
|
output_type TEXT NOT NULL DEFAULT 'message_content',
|
|
57
58
|
content_assertion TEXT NOT NULL DEFAULT '',
|
|
58
59
|
rubrics TEXT,
|
|
60
|
+
interrupt_policy TEXT,
|
|
59
61
|
created_at TEXT NOT NULL,
|
|
60
62
|
updated_at TEXT NOT NULL,
|
|
61
63
|
PRIMARY KEY (tenant_id, id)
|
|
@@ -71,6 +73,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_runs (
|
|
|
71
73
|
total_cases INTEGER NOT NULL DEFAULT 0,
|
|
72
74
|
passed_cases INTEGER NOT NULL DEFAULT 0,
|
|
73
75
|
failed_cases INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
interrupted_cases INTEGER NOT NULL DEFAULT 0,
|
|
74
77
|
avg_score REAL NOT NULL DEFAULT 0,
|
|
75
78
|
error TEXT,
|
|
76
79
|
holdout INTEGER NOT NULL DEFAULT 0,
|
|
@@ -96,6 +99,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
|
|
|
96
99
|
messages TEXT DEFAULT '[]',
|
|
97
100
|
logs TEXT DEFAULT '[]',
|
|
98
101
|
error TEXT,
|
|
102
|
+
interrupted INTEGER NOT NULL DEFAULT 0,
|
|
99
103
|
created_at TEXT NOT NULL,
|
|
100
104
|
PRIMARY KEY (run_id, id)
|
|
101
105
|
);
|
|
@@ -123,6 +127,85 @@ export class LocalEvalStore implements EvalStore {
|
|
|
123
127
|
constructor(db: DatabaseWrapper) {
|
|
124
128
|
this.db = db;
|
|
125
129
|
ensureTable(db, DDL);
|
|
130
|
+
// Existing installs: add HITL interrupt tracking columns.
|
|
131
|
+
// sql.js bundles an older SQLite that does not support
|
|
132
|
+
// `ADD COLUMN IF NOT EXISTS`, so check via PRAGMA instead.
|
|
133
|
+
this.ensureColumn("lt_eval_runs", "interrupted_cases", "interrupted_cases INTEGER NOT NULL DEFAULT 0");
|
|
134
|
+
this.ensureColumn("lt_eval_run_results", "interrupted", "interrupted INTEGER NOT NULL DEFAULT 0");
|
|
135
|
+
this.ensureColumn("lt_eval_cases", "interrupt_policy", "interrupt_policy TEXT");
|
|
136
|
+
// Project name must be unique per tenant (eval-{agent-id} convention).
|
|
137
|
+
// Repoint child rows (suites, runs) of duplicate projects onto the kept
|
|
138
|
+
// project (newest by created_at, tie-break rowid — mirrors the pg
|
|
139
|
+
// migration's (created_at, id) DESC policy) BEFORE deleting, so eval
|
|
140
|
+
// history is preserved. SQLite has no FK cascade here, so this is manual.
|
|
141
|
+
this.db.exec(`
|
|
142
|
+
UPDATE lt_eval_suites
|
|
143
|
+
SET project_id = (
|
|
144
|
+
SELECT p2.id FROM lt_eval_projects p2
|
|
145
|
+
WHERE p2.tenant_id = (
|
|
146
|
+
SELECT p.tenant_id FROM lt_eval_projects p WHERE p.id = lt_eval_suites.project_id
|
|
147
|
+
)
|
|
148
|
+
AND p2.name = (
|
|
149
|
+
SELECT p.name FROM lt_eval_projects p WHERE p.id = lt_eval_suites.project_id
|
|
150
|
+
)
|
|
151
|
+
ORDER BY p2.created_at DESC, p2.rowid DESC
|
|
152
|
+
LIMIT 1
|
|
153
|
+
)
|
|
154
|
+
WHERE project_id IN (
|
|
155
|
+
SELECT p3.id FROM lt_eval_projects p3
|
|
156
|
+
WHERE p3.id <> (
|
|
157
|
+
SELECT p2.id FROM lt_eval_projects p2
|
|
158
|
+
WHERE p2.tenant_id = p3.tenant_id AND p2.name = p3.name
|
|
159
|
+
ORDER BY p2.created_at DESC, p2.rowid DESC
|
|
160
|
+
LIMIT 1
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
`);
|
|
164
|
+
this.db.exec(`
|
|
165
|
+
UPDATE lt_eval_runs
|
|
166
|
+
SET project_id = (
|
|
167
|
+
SELECT p2.id FROM lt_eval_projects p2
|
|
168
|
+
WHERE p2.tenant_id = (
|
|
169
|
+
SELECT p.tenant_id FROM lt_eval_projects p WHERE p.id = lt_eval_runs.project_id
|
|
170
|
+
)
|
|
171
|
+
AND p2.name = (
|
|
172
|
+
SELECT p.name FROM lt_eval_projects p WHERE p.id = lt_eval_runs.project_id
|
|
173
|
+
)
|
|
174
|
+
ORDER BY p2.created_at DESC, p2.rowid DESC
|
|
175
|
+
LIMIT 1
|
|
176
|
+
)
|
|
177
|
+
WHERE project_id IN (
|
|
178
|
+
SELECT p3.id FROM lt_eval_projects p3
|
|
179
|
+
WHERE p3.id <> (
|
|
180
|
+
SELECT p2.id FROM lt_eval_projects p2
|
|
181
|
+
WHERE p2.tenant_id = p3.tenant_id AND p2.name = p3.name
|
|
182
|
+
ORDER BY p2.created_at DESC, p2.rowid DESC
|
|
183
|
+
LIMIT 1
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
`);
|
|
187
|
+
this.db.exec(`
|
|
188
|
+
DELETE FROM lt_eval_projects
|
|
189
|
+
WHERE id <> (
|
|
190
|
+
SELECT p2.id FROM lt_eval_projects p2
|
|
191
|
+
WHERE p2.tenant_id = lt_eval_projects.tenant_id
|
|
192
|
+
AND p2.name = lt_eval_projects.name
|
|
193
|
+
ORDER BY p2.created_at DESC, p2.rowid DESC
|
|
194
|
+
LIMIT 1
|
|
195
|
+
)
|
|
196
|
+
`);
|
|
197
|
+
this.db.exec(`
|
|
198
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_eval_projects_tenant_name
|
|
199
|
+
ON lt_eval_projects(tenant_id, name)
|
|
200
|
+
`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Add a column if it does not exist (SQLite version compatible). */
|
|
204
|
+
private ensureColumn(table: string, column: string, ddl: string): void {
|
|
205
|
+
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
206
|
+
if (!cols.some((c) => c.name === column)) {
|
|
207
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
|
|
208
|
+
}
|
|
126
209
|
}
|
|
127
210
|
|
|
128
211
|
// -------------------------------------------------------------------------
|
|
@@ -252,13 +335,14 @@ export class LocalEvalStore implements EvalStore {
|
|
|
252
335
|
async createCase(tenantId: string, suiteId: string, id: string, data: CreateEvalCaseRequest): Promise<EvalCase> {
|
|
253
336
|
const actualId = id || randomUUID();
|
|
254
337
|
this.db.prepare(
|
|
255
|
-
`INSERT INTO lt_eval_cases (id, tenant_id, suite_id, input_message, input_files, steps, output_type, content_assertion, rubrics, created_at, updated_at)
|
|
256
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
338
|
+
`INSERT INTO lt_eval_cases (id, tenant_id, suite_id, input_message, input_files, steps, output_type, content_assertion, rubrics, interrupt_policy, created_at, updated_at)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
257
340
|
).run(actualId, tenantId, suiteId, data.inputMessage,
|
|
258
341
|
data.inputFiles ? JSON.stringify(data.inputFiles) : null,
|
|
259
342
|
JSON.stringify(data.steps), data.outputType || "message_content",
|
|
260
343
|
data.contentAssertion || "",
|
|
261
344
|
data.rubrics ? JSON.stringify(data.rubrics) : null,
|
|
345
|
+
data.interruptPolicy ? JSON.stringify(data.interruptPolicy) : null,
|
|
262
346
|
nowISO(), nowISO());
|
|
263
347
|
return (await this.getCaseById(tenantId, actualId))!;
|
|
264
348
|
}
|
|
@@ -275,6 +359,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
275
359
|
if (updates.outputType !== undefined) { set.push("output_type = ?"); vals.push(updates.outputType); }
|
|
276
360
|
if (updates.contentAssertion !== undefined) { set.push("content_assertion = ?"); vals.push(updates.contentAssertion); }
|
|
277
361
|
if (updates.rubrics !== undefined) { set.push("rubrics = ?"); vals.push(JSON.stringify(updates.rubrics)); }
|
|
362
|
+
if (updates.interruptPolicy !== undefined) { set.push("interrupt_policy = ?"); vals.push(updates.interruptPolicy ? JSON.stringify(updates.interruptPolicy) : null); }
|
|
278
363
|
set.push("updated_at = ?"); vals.push(nowISO());
|
|
279
364
|
vals.push(tenantId, id);
|
|
280
365
|
|
|
@@ -320,7 +405,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
320
405
|
}
|
|
321
406
|
|
|
322
407
|
async updateRunStatus(tenantId: string, id: string, updates: {
|
|
323
|
-
status?: EvalRun["status"]; passedCases?: number; failedCases?: number; avgScore?: number; error?: string; completedAt?: Date;
|
|
408
|
+
status?: EvalRun["status"]; passedCases?: number; failedCases?: number; interruptedCases?: number; avgScore?: number; error?: string; completedAt?: Date;
|
|
324
409
|
}): Promise<EvalRun | null> {
|
|
325
410
|
const existing = await this.getRunById(tenantId, id);
|
|
326
411
|
if (!existing) return null;
|
|
@@ -330,6 +415,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
330
415
|
if (updates.status !== undefined) { set.push("status = ?"); vals.push(updates.status); }
|
|
331
416
|
if (updates.passedCases !== undefined) { set.push("passed_cases = ?"); vals.push(updates.passedCases); }
|
|
332
417
|
if (updates.failedCases !== undefined) { set.push("failed_cases = ?"); vals.push(updates.failedCases); }
|
|
418
|
+
if (updates.interruptedCases !== undefined) { set.push("interrupted_cases = ?"); vals.push(updates.interruptedCases); }
|
|
333
419
|
if (updates.avgScore !== undefined) { set.push("avg_score = ?"); vals.push(updates.avgScore); }
|
|
334
420
|
if (updates.error !== undefined) { set.push("error = ?"); vals.push(updates.error || null); }
|
|
335
421
|
if (updates.completedAt !== undefined) { set.push("completed_at = ?"); vals.push(updates.completedAt ? updates.completedAt.toISOString() : null); }
|
|
@@ -371,13 +457,13 @@ export class LocalEvalStore implements EvalStore {
|
|
|
371
457
|
async createRunResult(tenantId: string, runId: string, id: string, data: Omit<EvalRunResult, "id" | "runId" | "createdAt">): Promise<EvalRunResult> {
|
|
372
458
|
const actualId = id || randomUUID();
|
|
373
459
|
this.db.prepare(
|
|
374
|
-
`INSERT INTO lt_eval_run_results (id, run_id, suite_name, case_id, pass, score, summary, dimension_results, duration_ms, messages, logs, error, created_at)
|
|
375
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
460
|
+
`INSERT INTO lt_eval_run_results (id, run_id, suite_name, case_id, pass, score, summary, dimension_results, duration_ms, messages, logs, error, interrupted, created_at)
|
|
461
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
376
462
|
).run(actualId, runId, data.suiteName, data.caseId || null,
|
|
377
463
|
data.pass ? 1 : 0, data.score, data.summary || null,
|
|
378
464
|
JSON.stringify(data.dimensionResults || []), data.durationMs ?? null,
|
|
379
465
|
JSON.stringify(data.messages || []), JSON.stringify(data.logs || []),
|
|
380
|
-
data.error || null, nowISO());
|
|
466
|
+
data.error || null, data.interrupted ? 1 : 0, nowISO());
|
|
381
467
|
return (await this.getRunResultById(tenantId, actualId))!;
|
|
382
468
|
}
|
|
383
469
|
|
|
@@ -396,6 +482,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
396
482
|
if (updates.messages !== undefined) { set.push("messages = ?"); vals.push(JSON.stringify(updates.messages)); }
|
|
397
483
|
if (updates.logs !== undefined) { set.push("logs = ?"); vals.push(JSON.stringify(updates.logs)); }
|
|
398
484
|
if (updates.error !== undefined) { set.push("error = ?"); vals.push(updates.error || null); }
|
|
485
|
+
if (updates.interrupted !== undefined) { set.push("interrupted = ?"); vals.push(updates.interrupted ? 1 : 0); }
|
|
399
486
|
if (set.length === 0) return this.getRunResultById(tenantId, id);
|
|
400
487
|
|
|
401
488
|
vals.push(id, tenantId);
|
|
@@ -478,6 +565,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
478
565
|
outputType: (row.output_type as EvalCase["outputType"]) || "message_content",
|
|
479
566
|
contentAssertion: (row.content_assertion as string) || "",
|
|
480
567
|
rubrics: parseOptionalJson<Array<{ name: string; weight: number; description: string }>>(row.rubrics),
|
|
568
|
+
interruptPolicy: parseOptionalJson<InterruptPolicy>(row.interrupt_policy),
|
|
481
569
|
createdAt: parseISO(row.created_at as string),
|
|
482
570
|
updatedAt: parseISO(row.updated_at as string),
|
|
483
571
|
};
|
|
@@ -493,6 +581,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
493
581
|
totalCases: (row.total_cases as number) ?? 0,
|
|
494
582
|
passedCases: (row.passed_cases as number) ?? 0,
|
|
495
583
|
failedCases: (row.failed_cases as number) ?? 0,
|
|
584
|
+
interruptedCases: (row.interrupted_cases as number) ?? 0,
|
|
496
585
|
avgScore: (row.avg_score as number) ?? 0,
|
|
497
586
|
error: row.error as string | undefined,
|
|
498
587
|
holdout: row.holdout ? Boolean(row.holdout) : undefined,
|
|
@@ -518,6 +607,7 @@ export class LocalEvalStore implements EvalStore {
|
|
|
518
607
|
messages: parseOptionalJson<Array<{ role: string; content: string; id?: string }>>(row.messages),
|
|
519
608
|
logs: parseOptionalJson<Array<{ timestamp: string; level: string; message: string; data?: unknown }>>(row.logs),
|
|
520
609
|
error: row.error as string | undefined,
|
|
610
|
+
interrupted: (row.interrupted as number) === 1,
|
|
521
611
|
createdAt: parseISO(row.created_at as string),
|
|
522
612
|
};
|
|
523
613
|
}
|