@mastra/dynamodb 0.13.0 → 0.13.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/_tsup-dts-rollup.d.cts +998 -182
- package/dist/_tsup-dts-rollup.d.ts +998 -182
- package/dist/index.cjs +1979 -542
- package/dist/index.js +1980 -543
- package/package.json +5 -5
- package/src/entities/index.ts +5 -1
- package/src/entities/resource.ts +57 -0
- package/src/entities/score.ts +285 -0
- package/src/storage/domains/legacy-evals/index.ts +243 -0
- package/src/storage/domains/memory/index.ts +894 -0
- package/src/storage/domains/operations/index.ts +433 -0
- package/src/storage/domains/score/index.ts +285 -0
- package/src/storage/domains/traces/index.ts +286 -0
- package/src/storage/domains/workflows/index.ts +297 -0
- package/src/storage/index.test.ts +1346 -1409
- package/src/storage/index.ts +161 -1062
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/dynamodb",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "DynamoDB storage adapter for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"@vitest/coverage-v8": "3.2.3",
|
|
37
37
|
"@vitest/ui": "3.2.3",
|
|
38
38
|
"axios": "^1.10.0",
|
|
39
|
-
"eslint": "^9.
|
|
39
|
+
"eslint": "^9.30.1",
|
|
40
40
|
"tsup": "^8.5.0",
|
|
41
41
|
"typescript": "^5.8.3",
|
|
42
42
|
"vitest": "^3.2.4",
|
|
43
|
-
"@internal/
|
|
44
|
-
"@internal/
|
|
45
|
-
"@mastra/core": "0.
|
|
43
|
+
"@internal/storage-test-utils": "0.0.17",
|
|
44
|
+
"@internal/lint": "0.0.21",
|
|
45
|
+
"@mastra/core": "0.11.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
package/src/entities/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
|
2
2
|
import { Service } from 'electrodb';
|
|
3
3
|
import { evalEntity } from './eval';
|
|
4
4
|
import { messageEntity } from './message';
|
|
5
|
+
import { resourceEntity } from './resource';
|
|
6
|
+
import { scoreEntity } from './score';
|
|
5
7
|
import { threadEntity } from './thread';
|
|
6
8
|
import { traceEntity } from './trace';
|
|
7
9
|
import { workflowSnapshotEntity } from './workflow-snapshot';
|
|
@@ -13,7 +15,9 @@ export function getElectroDbService(client: DynamoDBDocumentClient, tableName: s
|
|
|
13
15
|
message: messageEntity,
|
|
14
16
|
eval: evalEntity,
|
|
15
17
|
trace: traceEntity,
|
|
16
|
-
|
|
18
|
+
workflow_snapshot: workflowSnapshotEntity,
|
|
19
|
+
resource: resourceEntity,
|
|
20
|
+
score: scoreEntity,
|
|
17
21
|
},
|
|
18
22
|
{
|
|
19
23
|
client,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Entity } from 'electrodb';
|
|
2
|
+
import { baseAttributes } from './utils';
|
|
3
|
+
|
|
4
|
+
export const resourceEntity = new Entity({
|
|
5
|
+
model: {
|
|
6
|
+
entity: 'resource',
|
|
7
|
+
version: '1',
|
|
8
|
+
service: 'mastra',
|
|
9
|
+
},
|
|
10
|
+
attributes: {
|
|
11
|
+
entity: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
...baseAttributes,
|
|
16
|
+
id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
workingMemory: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
24
|
+
metadata: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: false,
|
|
27
|
+
// Stringify content object on set if it's not already a string
|
|
28
|
+
set: (value?: string | void) => {
|
|
29
|
+
if (value && typeof value !== 'string') {
|
|
30
|
+
return JSON.stringify(value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
},
|
|
34
|
+
// Parse JSON string to object on get ONLY if it looks like JSON
|
|
35
|
+
get: (value?: string) => {
|
|
36
|
+
if (value && typeof value === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
// Attempt to parse only if it might be JSON (e.g., starts with { or [)
|
|
39
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
40
|
+
return JSON.parse(value);
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Ignore parse error, return original string
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
indexes: {
|
|
52
|
+
primary: {
|
|
53
|
+
pk: { field: 'pk', composite: ['entity', 'id'] },
|
|
54
|
+
sk: { field: 'sk', composite: ['entity'] },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Entity } from 'electrodb';
|
|
2
|
+
import { baseAttributes } from './utils';
|
|
3
|
+
|
|
4
|
+
export const scoreEntity = new Entity({
|
|
5
|
+
model: {
|
|
6
|
+
entity: 'score',
|
|
7
|
+
version: '1',
|
|
8
|
+
service: 'mastra',
|
|
9
|
+
},
|
|
10
|
+
attributes: {
|
|
11
|
+
entity: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
...baseAttributes,
|
|
16
|
+
id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
scorerId: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
traceId: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
runId: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
scorer: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
required: true,
|
|
35
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
36
|
+
if (value && typeof value !== 'string') {
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
},
|
|
41
|
+
get: (value?: string) => {
|
|
42
|
+
if (value && typeof value === 'string') {
|
|
43
|
+
try {
|
|
44
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
45
|
+
return JSON.parse(value);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
extractStepResult: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
required: false,
|
|
57
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
58
|
+
if (value && typeof value !== 'string') {
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
},
|
|
63
|
+
get: (value?: string) => {
|
|
64
|
+
if (value && typeof value === 'string') {
|
|
65
|
+
try {
|
|
66
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
67
|
+
return JSON.parse(value);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
analyzeStepResult: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
required: false,
|
|
79
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
80
|
+
if (value && typeof value !== 'string') {
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
},
|
|
85
|
+
get: (value?: string) => {
|
|
86
|
+
if (value && typeof value === 'string') {
|
|
87
|
+
try {
|
|
88
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
89
|
+
return JSON.parse(value);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
score: {
|
|
99
|
+
type: 'number',
|
|
100
|
+
required: true,
|
|
101
|
+
},
|
|
102
|
+
reason: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
required: false,
|
|
105
|
+
},
|
|
106
|
+
extractPrompt: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
required: false,
|
|
109
|
+
},
|
|
110
|
+
analyzePrompt: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
required: false,
|
|
113
|
+
},
|
|
114
|
+
reasonPrompt: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
required: false,
|
|
117
|
+
},
|
|
118
|
+
input: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
required: true,
|
|
121
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
122
|
+
if (value && typeof value !== 'string') {
|
|
123
|
+
return JSON.stringify(value);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
},
|
|
127
|
+
get: (value?: string) => {
|
|
128
|
+
if (value && typeof value === 'string') {
|
|
129
|
+
try {
|
|
130
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
131
|
+
return JSON.parse(value);
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
output: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
required: true,
|
|
143
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
144
|
+
if (value && typeof value !== 'string') {
|
|
145
|
+
return JSON.stringify(value);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
},
|
|
149
|
+
get: (value?: string) => {
|
|
150
|
+
if (value && typeof value === 'string') {
|
|
151
|
+
try {
|
|
152
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
153
|
+
return JSON.parse(value);
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
additionalContext: {
|
|
163
|
+
type: 'string',
|
|
164
|
+
required: false,
|
|
165
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
166
|
+
if (value && typeof value !== 'string') {
|
|
167
|
+
return JSON.stringify(value);
|
|
168
|
+
}
|
|
169
|
+
return value;
|
|
170
|
+
},
|
|
171
|
+
get: (value?: string) => {
|
|
172
|
+
if (value && typeof value === 'string') {
|
|
173
|
+
try {
|
|
174
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
175
|
+
return JSON.parse(value);
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
runtimeContext: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
required: false,
|
|
187
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
188
|
+
if (value && typeof value !== 'string') {
|
|
189
|
+
return JSON.stringify(value);
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
},
|
|
193
|
+
get: (value?: string) => {
|
|
194
|
+
if (value && typeof value === 'string') {
|
|
195
|
+
try {
|
|
196
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
197
|
+
return JSON.parse(value);
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
entityType: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
required: false,
|
|
209
|
+
},
|
|
210
|
+
entityData: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
required: false,
|
|
213
|
+
set: (value?: Record<string, unknown> | string) => {
|
|
214
|
+
if (value && typeof value !== 'string') {
|
|
215
|
+
return JSON.stringify(value);
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
},
|
|
219
|
+
get: (value?: string) => {
|
|
220
|
+
if (value && typeof value === 'string') {
|
|
221
|
+
try {
|
|
222
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
223
|
+
return JSON.parse(value);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return value;
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
entityId: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
required: false,
|
|
235
|
+
},
|
|
236
|
+
source: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
required: true,
|
|
239
|
+
},
|
|
240
|
+
resourceId: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
required: false,
|
|
243
|
+
},
|
|
244
|
+
threadId: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
required: false,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
indexes: {
|
|
250
|
+
primary: {
|
|
251
|
+
pk: { field: 'pk', composite: ['entity', 'id'] },
|
|
252
|
+
sk: { field: 'sk', composite: ['entity'] },
|
|
253
|
+
},
|
|
254
|
+
byScorer: {
|
|
255
|
+
index: 'gsi1',
|
|
256
|
+
pk: { field: 'gsi1pk', composite: ['entity', 'scorerId'] },
|
|
257
|
+
sk: { field: 'gsi1sk', composite: ['createdAt'] },
|
|
258
|
+
},
|
|
259
|
+
byRun: {
|
|
260
|
+
index: 'gsi2',
|
|
261
|
+
pk: { field: 'gsi2pk', composite: ['entity', 'runId'] },
|
|
262
|
+
sk: { field: 'gsi2sk', composite: ['createdAt'] },
|
|
263
|
+
},
|
|
264
|
+
byTrace: {
|
|
265
|
+
index: 'gsi3',
|
|
266
|
+
pk: { field: 'gsi3pk', composite: ['entity', 'traceId'] },
|
|
267
|
+
sk: { field: 'gsi3sk', composite: ['createdAt'] },
|
|
268
|
+
},
|
|
269
|
+
byEntityData: {
|
|
270
|
+
index: 'gsi4',
|
|
271
|
+
pk: { field: 'gsi4pk', composite: ['entity', 'entityId'] },
|
|
272
|
+
sk: { field: 'gsi4sk', composite: ['createdAt'] },
|
|
273
|
+
},
|
|
274
|
+
byResource: {
|
|
275
|
+
index: 'gsi5',
|
|
276
|
+
pk: { field: 'gsi5pk', composite: ['entity', 'resourceId'] },
|
|
277
|
+
sk: { field: 'gsi5sk', composite: ['createdAt'] },
|
|
278
|
+
},
|
|
279
|
+
byThread: {
|
|
280
|
+
index: 'gsi6',
|
|
281
|
+
pk: { field: 'gsi6pk', composite: ['entity', 'threadId'] },
|
|
282
|
+
sk: { field: 'gsi6sk', composite: ['createdAt'] },
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
2
|
+
import type { EvalRow, PaginationArgs, PaginationInfo } from '@mastra/core/storage';
|
|
3
|
+
import { LegacyEvalsStorage } from '@mastra/core/storage';
|
|
4
|
+
import type { Service } from 'electrodb';
|
|
5
|
+
|
|
6
|
+
export class LegacyEvalsDynamoDB extends LegacyEvalsStorage {
|
|
7
|
+
service: Service<Record<string, any>>;
|
|
8
|
+
tableName: string;
|
|
9
|
+
|
|
10
|
+
constructor({ service, tableName }: { service: Service<Record<string, any>>; tableName: string }) {
|
|
11
|
+
super();
|
|
12
|
+
this.service = service;
|
|
13
|
+
this.tableName = tableName;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Eval operations
|
|
17
|
+
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
18
|
+
this.logger.debug('Getting evals for agent', { agentName, type });
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Query evals by agent name using the GSI
|
|
22
|
+
// Provide *all* composite key components for the 'byAgent' index ('entity', 'agent_name')
|
|
23
|
+
const query = this.service.entities.eval.query.byAgent({ entity: 'eval', agent_name: agentName });
|
|
24
|
+
|
|
25
|
+
// Fetch potentially all items in descending order, using the correct 'order' option
|
|
26
|
+
const results = await query.go({ order: 'desc', limit: 100 }); // Use order: 'desc'
|
|
27
|
+
|
|
28
|
+
if (!results.data.length) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Filter by type if specified
|
|
33
|
+
let filteredData = results.data;
|
|
34
|
+
if (type) {
|
|
35
|
+
filteredData = filteredData.filter((evalRecord: Record<string, any>) => {
|
|
36
|
+
try {
|
|
37
|
+
// Need to handle potential parse errors for test_info
|
|
38
|
+
const testInfo =
|
|
39
|
+
evalRecord.test_info && typeof evalRecord.test_info === 'string'
|
|
40
|
+
? JSON.parse(evalRecord.test_info)
|
|
41
|
+
: undefined;
|
|
42
|
+
|
|
43
|
+
if (type === 'test' && !testInfo) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (type === 'live' && testInfo) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
this.logger.warn('Failed to parse test_info during filtering', { record: evalRecord, error: e });
|
|
51
|
+
// Decide how to handle parse errors - exclude or include? Including for now.
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Format the results - ElectroDB transforms most attributes, but we need to map/parse
|
|
58
|
+
return filteredData.map((evalRecord: Record<string, any>) => {
|
|
59
|
+
try {
|
|
60
|
+
return {
|
|
61
|
+
input: evalRecord.input,
|
|
62
|
+
output: evalRecord.output,
|
|
63
|
+
// Safely parse result and test_info
|
|
64
|
+
result:
|
|
65
|
+
evalRecord.result && typeof evalRecord.result === 'string' ? JSON.parse(evalRecord.result) : undefined,
|
|
66
|
+
agentName: evalRecord.agent_name,
|
|
67
|
+
createdAt: evalRecord.created_at, // Keep as string from DDB?
|
|
68
|
+
metricName: evalRecord.metric_name,
|
|
69
|
+
instructions: evalRecord.instructions,
|
|
70
|
+
runId: evalRecord.run_id,
|
|
71
|
+
globalRunId: evalRecord.global_run_id,
|
|
72
|
+
testInfo:
|
|
73
|
+
evalRecord.test_info && typeof evalRecord.test_info === 'string'
|
|
74
|
+
? JSON.parse(evalRecord.test_info)
|
|
75
|
+
: undefined,
|
|
76
|
+
} as EvalRow;
|
|
77
|
+
} catch (parseError) {
|
|
78
|
+
this.logger.error('Failed to parse eval record', { record: evalRecord, error: parseError });
|
|
79
|
+
// Return a partial record or null/undefined on error?
|
|
80
|
+
// Returning partial for now, might need adjustment based on requirements.
|
|
81
|
+
return {
|
|
82
|
+
agentName: evalRecord.agent_name,
|
|
83
|
+
createdAt: evalRecord.created_at,
|
|
84
|
+
runId: evalRecord.run_id,
|
|
85
|
+
globalRunId: evalRecord.global_run_id,
|
|
86
|
+
} as Partial<EvalRow> as EvalRow; // Cast needed for return type
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new MastraError(
|
|
91
|
+
{
|
|
92
|
+
id: 'STORAGE_DYNAMODB_STORE_GET_EVALS_BY_AGENT_NAME_FAILED',
|
|
93
|
+
domain: ErrorDomain.STORAGE,
|
|
94
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
95
|
+
details: { agentName },
|
|
96
|
+
},
|
|
97
|
+
error,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getEvals(
|
|
103
|
+
options: {
|
|
104
|
+
agentName?: string;
|
|
105
|
+
type?: 'test' | 'live';
|
|
106
|
+
} & PaginationArgs = {},
|
|
107
|
+
): Promise<PaginationInfo & { evals: EvalRow[] }> {
|
|
108
|
+
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
109
|
+
|
|
110
|
+
this.logger.debug('Getting evals with pagination', { agentName, type, page, perPage, dateRange });
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
let query;
|
|
114
|
+
|
|
115
|
+
if (agentName) {
|
|
116
|
+
// Query by specific agent name
|
|
117
|
+
query = this.service.entities.eval.query.byAgent({ entity: 'eval', agent_name: agentName });
|
|
118
|
+
} else {
|
|
119
|
+
// Query all evals using the primary index
|
|
120
|
+
query = this.service.entities.eval.query.byEntity({ entity: 'eval' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// For DynamoDB, we need to fetch all data and apply pagination in memory
|
|
124
|
+
// since DynamoDB doesn't support traditional offset-based pagination
|
|
125
|
+
const results = await query.go({
|
|
126
|
+
order: 'desc',
|
|
127
|
+
pages: 'all', // Get all pages to apply filtering and pagination
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!results.data.length) {
|
|
131
|
+
return {
|
|
132
|
+
evals: [],
|
|
133
|
+
total: 0,
|
|
134
|
+
page,
|
|
135
|
+
perPage,
|
|
136
|
+
hasMore: false,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Filter by type if specified
|
|
141
|
+
let filteredData = results.data;
|
|
142
|
+
if (type) {
|
|
143
|
+
filteredData = filteredData.filter((evalRecord: Record<string, any>) => {
|
|
144
|
+
try {
|
|
145
|
+
const testInfo =
|
|
146
|
+
evalRecord.test_info && typeof evalRecord.test_info === 'string'
|
|
147
|
+
? JSON.parse(evalRecord.test_info)
|
|
148
|
+
: undefined;
|
|
149
|
+
|
|
150
|
+
if (type === 'test' && !testInfo) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (type === 'live' && testInfo) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
this.logger.warn('Failed to parse test_info during filtering', { record: evalRecord, error: e });
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Apply date range filtering if specified
|
|
164
|
+
if (dateRange) {
|
|
165
|
+
const fromDate = dateRange.start;
|
|
166
|
+
const toDate = dateRange.end;
|
|
167
|
+
|
|
168
|
+
filteredData = filteredData.filter((evalRecord: Record<string, any>) => {
|
|
169
|
+
const recordDate = new Date(evalRecord.created_at);
|
|
170
|
+
|
|
171
|
+
if (fromDate && recordDate < fromDate) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (toDate && recordDate > toDate) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Apply pagination
|
|
182
|
+
const total = filteredData.length;
|
|
183
|
+
const start = page * perPage;
|
|
184
|
+
const end = start + perPage;
|
|
185
|
+
const paginatedData = filteredData.slice(start, end);
|
|
186
|
+
|
|
187
|
+
// Transform to EvalRow format
|
|
188
|
+
const evals = paginatedData.map((evalRecord: Record<string, any>) => {
|
|
189
|
+
try {
|
|
190
|
+
return {
|
|
191
|
+
input: evalRecord.input,
|
|
192
|
+
output: evalRecord.output,
|
|
193
|
+
result:
|
|
194
|
+
evalRecord.result && typeof evalRecord.result === 'string' ? JSON.parse(evalRecord.result) : undefined,
|
|
195
|
+
agentName: evalRecord.agent_name,
|
|
196
|
+
createdAt: evalRecord.created_at,
|
|
197
|
+
metricName: evalRecord.metric_name,
|
|
198
|
+
instructions: evalRecord.instructions,
|
|
199
|
+
runId: evalRecord.run_id,
|
|
200
|
+
globalRunId: evalRecord.global_run_id,
|
|
201
|
+
testInfo:
|
|
202
|
+
evalRecord.test_info && typeof evalRecord.test_info === 'string'
|
|
203
|
+
? JSON.parse(evalRecord.test_info)
|
|
204
|
+
: undefined,
|
|
205
|
+
} as EvalRow;
|
|
206
|
+
} catch (parseError) {
|
|
207
|
+
this.logger.error('Failed to parse eval record', { record: evalRecord, error: parseError });
|
|
208
|
+
return {
|
|
209
|
+
agentName: evalRecord.agent_name,
|
|
210
|
+
createdAt: evalRecord.created_at,
|
|
211
|
+
runId: evalRecord.run_id,
|
|
212
|
+
globalRunId: evalRecord.global_run_id,
|
|
213
|
+
} as Partial<EvalRow> as EvalRow;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const hasMore = end < total;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
evals,
|
|
221
|
+
total,
|
|
222
|
+
page,
|
|
223
|
+
perPage,
|
|
224
|
+
hasMore,
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw new MastraError(
|
|
228
|
+
{
|
|
229
|
+
id: 'STORAGE_DYNAMODB_STORE_GET_EVALS_FAILED',
|
|
230
|
+
domain: ErrorDomain.STORAGE,
|
|
231
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
232
|
+
details: {
|
|
233
|
+
agentName: agentName || 'all',
|
|
234
|
+
type: type || 'all',
|
|
235
|
+
page,
|
|
236
|
+
perPage,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
error,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|