@mastra/pg 0.2.7-alpha.1 → 0.2.7-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +28 -0
- package/dist/_tsup-dts-rollup.d.cts +22 -2
- package/dist/_tsup-dts-rollup.d.ts +22 -2
- package/dist/index.cjs +125 -22
- package/dist/index.js +126 -23
- package/package.json +2 -2
- package/src/storage/index.test.ts +277 -6
- package/src/storage/index.ts +178 -21
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'crypto';
|
|
|
2
2
|
import { MastraVector } from '@mastra/core/vector';
|
|
3
3
|
import pg from 'pg';
|
|
4
4
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
5
|
-
import { MastraStorage } from '@mastra/core/storage';
|
|
5
|
+
import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES } from '@mastra/core/storage';
|
|
6
6
|
import pgPromise from 'pg-promise';
|
|
7
7
|
|
|
8
8
|
// src/vector/index.ts
|
|
@@ -701,12 +701,46 @@ var PostgresStore = class extends MastraStorage {
|
|
|
701
701
|
port: config.port,
|
|
702
702
|
database: config.database,
|
|
703
703
|
user: config.user,
|
|
704
|
-
password: config.password
|
|
704
|
+
password: config.password,
|
|
705
|
+
ssl: config.ssl
|
|
705
706
|
}
|
|
706
707
|
);
|
|
707
708
|
}
|
|
708
|
-
getEvalsByAgentName(
|
|
709
|
-
|
|
709
|
+
getEvalsByAgentName(agentName, type) {
|
|
710
|
+
try {
|
|
711
|
+
const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = $1`;
|
|
712
|
+
const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
|
|
713
|
+
const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
|
|
714
|
+
return this.db.manyOrNone(query, [agentName]).then((rows) => rows?.map((row) => this.transformEvalRow(row)) ?? []);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (error instanceof Error && error.message.includes("relation") && error.message.includes("does not exist")) {
|
|
717
|
+
return Promise.resolve([]);
|
|
718
|
+
}
|
|
719
|
+
console.error("Failed to get evals for the specified agent: " + error?.message);
|
|
720
|
+
throw error;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
transformEvalRow(row) {
|
|
724
|
+
let testInfoValue = null;
|
|
725
|
+
if (row.test_info) {
|
|
726
|
+
try {
|
|
727
|
+
testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
|
|
728
|
+
} catch (e) {
|
|
729
|
+
console.warn("Failed to parse test_info:", e);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
agentName: row.agent_name,
|
|
734
|
+
input: row.input,
|
|
735
|
+
output: row.output,
|
|
736
|
+
result: row.result,
|
|
737
|
+
metricName: row.metric_name,
|
|
738
|
+
instructions: row.instructions,
|
|
739
|
+
testInfo: testInfoValue,
|
|
740
|
+
globalRunId: row.global_run_id,
|
|
741
|
+
runId: row.run_id,
|
|
742
|
+
createdAt: row.created_at
|
|
743
|
+
};
|
|
710
744
|
}
|
|
711
745
|
async batchInsert({ tableName, records }) {
|
|
712
746
|
try {
|
|
@@ -726,7 +760,8 @@ var PostgresStore = class extends MastraStorage {
|
|
|
726
760
|
scope,
|
|
727
761
|
page,
|
|
728
762
|
perPage,
|
|
729
|
-
attributes
|
|
763
|
+
attributes,
|
|
764
|
+
filters
|
|
730
765
|
}) {
|
|
731
766
|
let idx = 1;
|
|
732
767
|
const limit = perPage;
|
|
@@ -744,6 +779,11 @@ var PostgresStore = class extends MastraStorage {
|
|
|
744
779
|
conditions.push(`attributes->>'${key}' = $${idx++}`);
|
|
745
780
|
});
|
|
746
781
|
}
|
|
782
|
+
if (filters) {
|
|
783
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
784
|
+
conditions.push(`${key} = $${idx++}`);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
747
787
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
748
788
|
if (name) {
|
|
749
789
|
args.push(name);
|
|
@@ -756,15 +796,17 @@ var PostgresStore = class extends MastraStorage {
|
|
|
756
796
|
args.push(value);
|
|
757
797
|
}
|
|
758
798
|
}
|
|
799
|
+
if (filters) {
|
|
800
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
801
|
+
args.push(value);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
759
804
|
console.log(
|
|
760
805
|
"QUERY",
|
|
761
|
-
`SELECT * FROM ${
|
|
762
|
-
args
|
|
763
|
-
);
|
|
764
|
-
const result = await this.db.manyOrNone(
|
|
765
|
-
`SELECT * FROM ${MastraStorage.TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
806
|
+
`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
766
807
|
args
|
|
767
808
|
);
|
|
809
|
+
const result = await this.db.manyOrNone(`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`, args);
|
|
768
810
|
if (!result) {
|
|
769
811
|
return [];
|
|
770
812
|
}
|
|
@@ -800,7 +842,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
800
842
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
801
843
|
${columns}
|
|
802
844
|
);
|
|
803
|
-
${tableName ===
|
|
845
|
+
${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
|
|
804
846
|
DO $$ BEGIN
|
|
805
847
|
IF NOT EXISTS (
|
|
806
848
|
SELECT 1 FROM pg_constraint WHERE conname = 'mastra_workflow_snapshot_workflow_name_run_id_key'
|
|
@@ -849,7 +891,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
849
891
|
if (!result) {
|
|
850
892
|
return null;
|
|
851
893
|
}
|
|
852
|
-
if (tableName ===
|
|
894
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
853
895
|
const snapshot = result;
|
|
854
896
|
if (typeof snapshot.snapshot === "string") {
|
|
855
897
|
snapshot.snapshot = JSON.parse(snapshot.snapshot);
|
|
@@ -872,7 +914,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
872
914
|
metadata,
|
|
873
915
|
"createdAt",
|
|
874
916
|
"updatedAt"
|
|
875
|
-
FROM "${
|
|
917
|
+
FROM "${TABLE_THREADS}"
|
|
876
918
|
WHERE id = $1`,
|
|
877
919
|
[threadId]
|
|
878
920
|
);
|
|
@@ -900,7 +942,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
900
942
|
metadata,
|
|
901
943
|
"createdAt",
|
|
902
944
|
"updatedAt"
|
|
903
|
-
FROM "${
|
|
945
|
+
FROM "${TABLE_THREADS}"
|
|
904
946
|
WHERE "resourceId" = $1`,
|
|
905
947
|
[resourceId]
|
|
906
948
|
);
|
|
@@ -918,7 +960,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
918
960
|
async saveThread({ thread }) {
|
|
919
961
|
try {
|
|
920
962
|
await this.db.none(
|
|
921
|
-
`INSERT INTO "${
|
|
963
|
+
`INSERT INTO "${TABLE_THREADS}" (
|
|
922
964
|
id,
|
|
923
965
|
"resourceId",
|
|
924
966
|
title,
|
|
@@ -962,7 +1004,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
962
1004
|
...metadata
|
|
963
1005
|
};
|
|
964
1006
|
const thread = await this.db.one(
|
|
965
|
-
`UPDATE "${
|
|
1007
|
+
`UPDATE "${TABLE_THREADS}"
|
|
966
1008
|
SET title = $1,
|
|
967
1009
|
metadata = $2,
|
|
968
1010
|
"updatedAt" = $3
|
|
@@ -984,8 +1026,8 @@ var PostgresStore = class extends MastraStorage {
|
|
|
984
1026
|
async deleteThread({ threadId }) {
|
|
985
1027
|
try {
|
|
986
1028
|
await this.db.tx(async (t) => {
|
|
987
|
-
await t.none(`DELETE FROM "${
|
|
988
|
-
await t.none(`DELETE FROM "${
|
|
1029
|
+
await t.none(`DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = $1`, [threadId]);
|
|
1030
|
+
await t.none(`DELETE FROM "${TABLE_THREADS}" WHERE id = $1`, [threadId]);
|
|
989
1031
|
});
|
|
990
1032
|
} catch (error) {
|
|
991
1033
|
console.error("Error deleting thread:", error);
|
|
@@ -1004,7 +1046,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1004
1046
|
SELECT
|
|
1005
1047
|
*,
|
|
1006
1048
|
ROW_NUMBER() OVER (ORDER BY "createdAt" DESC) as row_num
|
|
1007
|
-
FROM "${
|
|
1049
|
+
FROM "${TABLE_MESSAGES}"
|
|
1008
1050
|
WHERE thread_id = $1
|
|
1009
1051
|
)
|
|
1010
1052
|
SELECT
|
|
@@ -1047,7 +1089,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1047
1089
|
type,
|
|
1048
1090
|
"createdAt",
|
|
1049
1091
|
thread_id AS "threadId"
|
|
1050
|
-
FROM "${
|
|
1092
|
+
FROM "${TABLE_MESSAGES}"
|
|
1051
1093
|
WHERE thread_id = $1
|
|
1052
1094
|
AND id != ALL($2)
|
|
1053
1095
|
ORDER BY "createdAt" DESC
|
|
@@ -1085,7 +1127,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1085
1127
|
await this.db.tx(async (t) => {
|
|
1086
1128
|
for (const message of messages) {
|
|
1087
1129
|
await t.none(
|
|
1088
|
-
`INSERT INTO "${
|
|
1130
|
+
`INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, "createdAt", role, type)
|
|
1089
1131
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
1090
1132
|
[
|
|
1091
1133
|
message.id,
|
|
@@ -1112,7 +1154,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1112
1154
|
try {
|
|
1113
1155
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1114
1156
|
await this.db.none(
|
|
1115
|
-
`INSERT INTO "${
|
|
1157
|
+
`INSERT INTO "${TABLE_WORKFLOW_SNAPSHOT}" (
|
|
1116
1158
|
workflow_name,
|
|
1117
1159
|
run_id,
|
|
1118
1160
|
snapshot,
|
|
@@ -1135,7 +1177,7 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1135
1177
|
}) {
|
|
1136
1178
|
try {
|
|
1137
1179
|
const result = await this.load({
|
|
1138
|
-
tableName:
|
|
1180
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1139
1181
|
keys: {
|
|
1140
1182
|
workflow_name: workflowName,
|
|
1141
1183
|
run_id: runId
|
|
@@ -1150,6 +1192,67 @@ var PostgresStore = class extends MastraStorage {
|
|
|
1150
1192
|
throw error;
|
|
1151
1193
|
}
|
|
1152
1194
|
}
|
|
1195
|
+
async getWorkflowRuns({
|
|
1196
|
+
workflowName,
|
|
1197
|
+
fromDate,
|
|
1198
|
+
toDate,
|
|
1199
|
+
limit,
|
|
1200
|
+
offset
|
|
1201
|
+
} = {}) {
|
|
1202
|
+
const conditions = [];
|
|
1203
|
+
const values = [];
|
|
1204
|
+
let paramIndex = 1;
|
|
1205
|
+
if (workflowName) {
|
|
1206
|
+
conditions.push(`workflow_name = $${paramIndex}`);
|
|
1207
|
+
values.push(workflowName);
|
|
1208
|
+
paramIndex++;
|
|
1209
|
+
}
|
|
1210
|
+
if (fromDate) {
|
|
1211
|
+
conditions.push(`"createdAt" >= $${paramIndex}`);
|
|
1212
|
+
values.push(fromDate);
|
|
1213
|
+
paramIndex++;
|
|
1214
|
+
}
|
|
1215
|
+
if (toDate) {
|
|
1216
|
+
conditions.push(`"createdAt" <= $${paramIndex}`);
|
|
1217
|
+
values.push(toDate);
|
|
1218
|
+
paramIndex++;
|
|
1219
|
+
}
|
|
1220
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1221
|
+
let total = 0;
|
|
1222
|
+
if (limit !== void 0 && offset !== void 0) {
|
|
1223
|
+
const countResult = await this.db.one(
|
|
1224
|
+
`SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
|
|
1225
|
+
values
|
|
1226
|
+
);
|
|
1227
|
+
total = Number(countResult.count);
|
|
1228
|
+
}
|
|
1229
|
+
const query = `
|
|
1230
|
+
SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT}
|
|
1231
|
+
${whereClause}
|
|
1232
|
+
ORDER BY "createdAt" DESC
|
|
1233
|
+
${limit !== void 0 && offset !== void 0 ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
|
|
1234
|
+
`;
|
|
1235
|
+
const queryValues = limit !== void 0 && offset !== void 0 ? [...values, limit, offset] : values;
|
|
1236
|
+
const result = await this.db.manyOrNone(query, queryValues);
|
|
1237
|
+
const runs = (result || []).map((row) => {
|
|
1238
|
+
let parsedSnapshot = row.snapshot;
|
|
1239
|
+
if (typeof parsedSnapshot === "string") {
|
|
1240
|
+
try {
|
|
1241
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
1242
|
+
} catch (e) {
|
|
1243
|
+
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
workflowName: row.workflow_name,
|
|
1248
|
+
runId: row.run_id,
|
|
1249
|
+
snapshot: parsedSnapshot,
|
|
1250
|
+
createdAt: row.createdAt,
|
|
1251
|
+
updatedAt: row.updatedAt
|
|
1252
|
+
};
|
|
1253
|
+
});
|
|
1254
|
+
return { runs, total: total || runs.length };
|
|
1255
|
+
}
|
|
1153
1256
|
async close() {
|
|
1154
1257
|
this.pgp.end();
|
|
1155
1258
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.2.7-alpha.
|
|
3
|
+
"version": "0.2.7-alpha.3",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"pg": "^8.13.3",
|
|
23
23
|
"pg-promise": "^11.11.0",
|
|
24
|
-
"@mastra/core": "^0.8.0-alpha.
|
|
24
|
+
"@mastra/core": "^0.8.0-alpha.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.1",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
+
import type { MetricResult } from '@mastra/core/eval';
|
|
3
|
+
import { TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, TABLE_THREADS, TABLE_EVALS } from '@mastra/core/storage';
|
|
2
4
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
3
5
|
import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
|
|
4
6
|
|
|
@@ -33,6 +35,49 @@ const createSampleMessage = (threadId: string) =>
|
|
|
33
35
|
createdAt: new Date(),
|
|
34
36
|
}) as any;
|
|
35
37
|
|
|
38
|
+
const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
|
|
39
|
+
const runId = `run-${randomUUID()}`;
|
|
40
|
+
const stepId = `step-${randomUUID()}`;
|
|
41
|
+
const timestamp = createdAt || new Date();
|
|
42
|
+
const snapshot = {
|
|
43
|
+
result: { success: true },
|
|
44
|
+
value: {},
|
|
45
|
+
context: {
|
|
46
|
+
steps: {
|
|
47
|
+
[stepId]: {
|
|
48
|
+
status,
|
|
49
|
+
payload: {},
|
|
50
|
+
error: undefined,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
triggerData: {},
|
|
54
|
+
attempts: {},
|
|
55
|
+
},
|
|
56
|
+
activePaths: [],
|
|
57
|
+
runId,
|
|
58
|
+
timestamp: timestamp.getTime(),
|
|
59
|
+
} as WorkflowRunState;
|
|
60
|
+
return { snapshot, runId, stepId };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createSampleEval = (agentName: string, isTest = false) => {
|
|
64
|
+
const testInfo = isTest ? { testPath: 'test/path.ts', testName: 'Test Name' } : undefined;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id: randomUUID(),
|
|
68
|
+
agentName,
|
|
69
|
+
input: 'Sample input',
|
|
70
|
+
output: 'Sample output',
|
|
71
|
+
result: { score: 0.8 } as MetricResult,
|
|
72
|
+
metricName: 'sample-metric',
|
|
73
|
+
instructions: 'Sample instructions',
|
|
74
|
+
testInfo,
|
|
75
|
+
globalRunId: `global-${randomUUID()}`,
|
|
76
|
+
runId: `run-${randomUUID()}`,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
36
81
|
describe('PostgresStore', () => {
|
|
37
82
|
let store: PostgresStore;
|
|
38
83
|
|
|
@@ -43,15 +88,15 @@ describe('PostgresStore', () => {
|
|
|
43
88
|
|
|
44
89
|
beforeEach(async () => {
|
|
45
90
|
// Clear tables before each test
|
|
46
|
-
await store.clearTable({ tableName:
|
|
47
|
-
await store.clearTable({ tableName:
|
|
48
|
-
await store.clearTable({ tableName:
|
|
91
|
+
await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
92
|
+
await store.clearTable({ tableName: TABLE_MESSAGES });
|
|
93
|
+
await store.clearTable({ tableName: TABLE_THREADS });
|
|
94
|
+
await store.clearTable({ tableName: TABLE_EVALS });
|
|
49
95
|
});
|
|
50
96
|
|
|
51
97
|
describe('Thread Operations', () => {
|
|
52
98
|
it('should create and retrieve a thread', async () => {
|
|
53
99
|
const thread = createSampleThread();
|
|
54
|
-
console.log('Saving thread:', thread);
|
|
55
100
|
|
|
56
101
|
// Save thread
|
|
57
102
|
const savedThread = await store.__saveThread({ thread });
|
|
@@ -81,7 +126,6 @@ describe('PostgresStore', () => {
|
|
|
81
126
|
|
|
82
127
|
it('should update thread title and metadata', async () => {
|
|
83
128
|
const thread = createSampleThread();
|
|
84
|
-
console.log('Saving thread:', thread);
|
|
85
129
|
await store.__saveThread({ thread });
|
|
86
130
|
|
|
87
131
|
const newMetadata = { newKey: 'newValue' };
|
|
@@ -362,7 +406,7 @@ describe('PostgresStore', () => {
|
|
|
362
406
|
await store.persistWorkflowSnapshot({
|
|
363
407
|
workflowName,
|
|
364
408
|
runId,
|
|
365
|
-
snapshot: complexSnapshot as WorkflowRunState,
|
|
409
|
+
snapshot: complexSnapshot as unknown as WorkflowRunState,
|
|
366
410
|
});
|
|
367
411
|
|
|
368
412
|
const loadedSnapshot = await store.loadWorkflowSnapshot({
|
|
@@ -374,6 +418,233 @@ describe('PostgresStore', () => {
|
|
|
374
418
|
});
|
|
375
419
|
});
|
|
376
420
|
|
|
421
|
+
describe('getWorkflowRuns', () => {
|
|
422
|
+
beforeEach(async () => {
|
|
423
|
+
await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
424
|
+
});
|
|
425
|
+
it('returns empty array when no workflows exist', async () => {
|
|
426
|
+
const { runs, total } = await store.__getWorkflowRuns();
|
|
427
|
+
expect(runs).toEqual([]);
|
|
428
|
+
expect(total).toBe(0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('returns all workflows by default', async () => {
|
|
432
|
+
const workflowName1 = 'default_test_1';
|
|
433
|
+
const workflowName2 = 'default_test_2';
|
|
434
|
+
|
|
435
|
+
const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
|
|
436
|
+
const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
|
|
437
|
+
|
|
438
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
|
|
439
|
+
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
440
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
|
|
441
|
+
|
|
442
|
+
const { runs, total } = await store.__getWorkflowRuns();
|
|
443
|
+
expect(runs).toHaveLength(2);
|
|
444
|
+
expect(total).toBe(2);
|
|
445
|
+
expect(runs[0]!.workflowName).toBe(workflowName2); // Most recent first
|
|
446
|
+
expect(runs[1]!.workflowName).toBe(workflowName1);
|
|
447
|
+
const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
|
|
448
|
+
const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
|
|
449
|
+
expect(firstSnapshot.context?.steps[stepId2]?.status).toBe('running');
|
|
450
|
+
expect(secondSnapshot.context?.steps[stepId1]?.status).toBe('completed');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('filters by workflow name', async () => {
|
|
454
|
+
const workflowName1 = 'filter_test_1';
|
|
455
|
+
const workflowName2 = 'filter_test_2';
|
|
456
|
+
|
|
457
|
+
const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
|
|
458
|
+
const { snapshot: workflow2, runId: runId2 } = createSampleWorkflowSnapshot('failed');
|
|
459
|
+
|
|
460
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
|
|
461
|
+
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
462
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
|
|
463
|
+
|
|
464
|
+
const { runs, total } = await store.__getWorkflowRuns({ workflowName: workflowName1 });
|
|
465
|
+
expect(runs).toHaveLength(1);
|
|
466
|
+
expect(total).toBe(1);
|
|
467
|
+
expect(runs[0]!.workflowName).toBe(workflowName1);
|
|
468
|
+
const snapshot = runs[0]!.snapshot as WorkflowRunState;
|
|
469
|
+
expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('filters by date range', async () => {
|
|
473
|
+
const now = new Date();
|
|
474
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
475
|
+
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
|
|
476
|
+
const workflowName1 = 'date_test_1';
|
|
477
|
+
const workflowName2 = 'date_test_2';
|
|
478
|
+
const workflowName3 = 'date_test_3';
|
|
479
|
+
|
|
480
|
+
const { snapshot: workflow1, runId: runId1 } = createSampleWorkflowSnapshot('completed');
|
|
481
|
+
const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
|
|
482
|
+
const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
|
|
483
|
+
|
|
484
|
+
await store.insert({
|
|
485
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
486
|
+
record: {
|
|
487
|
+
workflow_name: workflowName1,
|
|
488
|
+
run_id: runId1,
|
|
489
|
+
snapshot: workflow1,
|
|
490
|
+
createdAt: twoDaysAgo,
|
|
491
|
+
updatedAt: twoDaysAgo,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
await store.insert({
|
|
495
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
496
|
+
record: {
|
|
497
|
+
workflow_name: workflowName2,
|
|
498
|
+
run_id: runId2,
|
|
499
|
+
snapshot: workflow2,
|
|
500
|
+
createdAt: yesterday,
|
|
501
|
+
updatedAt: yesterday,
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
await store.insert({
|
|
505
|
+
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
506
|
+
record: {
|
|
507
|
+
workflow_name: workflowName3,
|
|
508
|
+
run_id: runId3,
|
|
509
|
+
snapshot: workflow3,
|
|
510
|
+
createdAt: now,
|
|
511
|
+
updatedAt: now,
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const { runs } = await store.__getWorkflowRuns({
|
|
516
|
+
fromDate: yesterday,
|
|
517
|
+
toDate: now,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(runs).toHaveLength(2);
|
|
521
|
+
expect(runs[0]!.workflowName).toBe(workflowName3);
|
|
522
|
+
expect(runs[1]!.workflowName).toBe(workflowName2);
|
|
523
|
+
const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
|
|
524
|
+
const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
|
|
525
|
+
expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
|
|
526
|
+
expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('handles pagination', async () => {
|
|
530
|
+
const workflowName1 = 'page_test_1';
|
|
531
|
+
const workflowName2 = 'page_test_2';
|
|
532
|
+
const workflowName3 = 'page_test_3';
|
|
533
|
+
|
|
534
|
+
const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
|
|
535
|
+
const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
|
|
536
|
+
const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
|
|
537
|
+
|
|
538
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
|
|
539
|
+
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
540
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
|
|
541
|
+
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
|
|
542
|
+
await store.persistWorkflowSnapshot({ workflowName: workflowName3, runId: runId3, snapshot: workflow3 });
|
|
543
|
+
|
|
544
|
+
// Get first page
|
|
545
|
+
const page1 = await store.__getWorkflowRuns({ limit: 2, offset: 0 });
|
|
546
|
+
expect(page1.runs).toHaveLength(2);
|
|
547
|
+
expect(page1.total).toBe(3); // Total count of all records
|
|
548
|
+
expect(page1.runs[0]!.workflowName).toBe(workflowName3);
|
|
549
|
+
expect(page1.runs[1]!.workflowName).toBe(workflowName2);
|
|
550
|
+
const firstSnapshot = page1.runs[0]!.snapshot as WorkflowRunState;
|
|
551
|
+
const secondSnapshot = page1.runs[1]!.snapshot as WorkflowRunState;
|
|
552
|
+
expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
|
|
553
|
+
expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
|
|
554
|
+
|
|
555
|
+
// Get second page
|
|
556
|
+
const page2 = await store.__getWorkflowRuns({ limit: 2, offset: 2 });
|
|
557
|
+
expect(page2.runs).toHaveLength(1);
|
|
558
|
+
expect(page2.total).toBe(3);
|
|
559
|
+
expect(page2.runs[0]!.workflowName).toBe(workflowName1);
|
|
560
|
+
const snapshot = page2.runs[0]!.snapshot as WorkflowRunState;
|
|
561
|
+
expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
describe('Eval Operations', () => {
|
|
566
|
+
it('should retrieve evals by agent name', async () => {
|
|
567
|
+
const agentName = `test-agent-${randomUUID()}`;
|
|
568
|
+
|
|
569
|
+
// Create sample evals
|
|
570
|
+
const liveEval = createSampleEval(agentName, false);
|
|
571
|
+
const testEval = createSampleEval(agentName, true);
|
|
572
|
+
const otherAgentEval = createSampleEval(`other-agent-${randomUUID()}`, false);
|
|
573
|
+
|
|
574
|
+
// Insert evals
|
|
575
|
+
await store.insert({
|
|
576
|
+
tableName: TABLE_EVALS,
|
|
577
|
+
record: {
|
|
578
|
+
agent_name: liveEval.agentName,
|
|
579
|
+
input: liveEval.input,
|
|
580
|
+
output: liveEval.output,
|
|
581
|
+
result: liveEval.result,
|
|
582
|
+
metric_name: liveEval.metricName,
|
|
583
|
+
instructions: liveEval.instructions,
|
|
584
|
+
test_info: null,
|
|
585
|
+
global_run_id: liveEval.globalRunId,
|
|
586
|
+
run_id: liveEval.runId,
|
|
587
|
+
created_at: liveEval.createdAt,
|
|
588
|
+
createdAt: new Date(liveEval.createdAt),
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
await store.insert({
|
|
593
|
+
tableName: TABLE_EVALS,
|
|
594
|
+
record: {
|
|
595
|
+
agent_name: testEval.agentName,
|
|
596
|
+
input: testEval.input,
|
|
597
|
+
output: testEval.output,
|
|
598
|
+
result: testEval.result,
|
|
599
|
+
metric_name: testEval.metricName,
|
|
600
|
+
instructions: testEval.instructions,
|
|
601
|
+
test_info: JSON.stringify(testEval.testInfo),
|
|
602
|
+
global_run_id: testEval.globalRunId,
|
|
603
|
+
run_id: testEval.runId,
|
|
604
|
+
created_at: testEval.createdAt,
|
|
605
|
+
createdAt: new Date(testEval.createdAt),
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
await store.insert({
|
|
610
|
+
tableName: TABLE_EVALS,
|
|
611
|
+
record: {
|
|
612
|
+
agent_name: otherAgentEval.agentName,
|
|
613
|
+
input: otherAgentEval.input,
|
|
614
|
+
output: otherAgentEval.output,
|
|
615
|
+
result: otherAgentEval.result,
|
|
616
|
+
metric_name: otherAgentEval.metricName,
|
|
617
|
+
instructions: otherAgentEval.instructions,
|
|
618
|
+
test_info: null,
|
|
619
|
+
global_run_id: otherAgentEval.globalRunId,
|
|
620
|
+
run_id: otherAgentEval.runId,
|
|
621
|
+
created_at: otherAgentEval.createdAt,
|
|
622
|
+
createdAt: new Date(otherAgentEval.createdAt),
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Test getting all evals for the agent
|
|
627
|
+
const allEvals = await store.getEvalsByAgentName(agentName);
|
|
628
|
+
expect(allEvals).toHaveLength(2);
|
|
629
|
+
expect(allEvals.map(e => e.runId)).toEqual(expect.arrayContaining([liveEval.runId, testEval.runId]));
|
|
630
|
+
|
|
631
|
+
// Test getting only live evals
|
|
632
|
+
const liveEvals = await store.getEvalsByAgentName(agentName, 'live');
|
|
633
|
+
expect(liveEvals).toHaveLength(1);
|
|
634
|
+
expect(liveEvals[0].runId).toBe(liveEval.runId);
|
|
635
|
+
|
|
636
|
+
// Test getting only test evals
|
|
637
|
+
const testEvals = await store.getEvalsByAgentName(agentName, 'test');
|
|
638
|
+
expect(testEvals).toHaveLength(1);
|
|
639
|
+
expect(testEvals[0].runId).toBe(testEval.runId);
|
|
640
|
+
expect(testEvals[0].testInfo).toEqual(testEval.testInfo);
|
|
641
|
+
|
|
642
|
+
// Test getting evals for non-existent agent
|
|
643
|
+
const nonExistentEvals = await store.getEvalsByAgentName('non-existent-agent');
|
|
644
|
+
expect(nonExistentEvals).toHaveLength(0);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
377
648
|
afterAll(async () => {
|
|
378
649
|
await store.close();
|
|
379
650
|
});
|