@mastra/lance 0.2.9 → 0.2.11-alpha.0

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.
@@ -1,243 +0,0 @@
1
- import type { Connection } from '@lancedb/lancedb';
2
- import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
- import type { ScoreRowData, ScoringSource } from '@mastra/core/scores';
4
- import { ScoresStorage, TABLE_SCORERS } from '@mastra/core/storage';
5
- import type { PaginationInfo, StoragePagination } from '@mastra/core/storage';
6
- import { getTableSchema, processResultWithTypeConversion } from '../utils';
7
-
8
- export class StoreScoresLance extends ScoresStorage {
9
- private client: Connection;
10
- constructor({ client }: { client: Connection }) {
11
- super();
12
- this.client = client;
13
- }
14
-
15
- async saveScore(score: ScoreRowData): Promise<{ score: ScoreRowData }> {
16
- try {
17
- const id = crypto.randomUUID();
18
- const table = await this.client.openTable(TABLE_SCORERS);
19
- // Fetch schema fields for mastra_scorers
20
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
21
- const allowedFields = new Set(schema.fields.map((f: any) => f.name));
22
- // Filter out fields not in schema
23
- const filteredScore: Record<string, any> = {};
24
- (Object.keys(score) as (keyof ScoreRowData)[]).forEach(key => {
25
- if (allowedFields.has(key)) {
26
- filteredScore[key] = score[key];
27
- }
28
- });
29
- // Convert any object fields to JSON strings for storage
30
- for (const key in filteredScore) {
31
- if (
32
- filteredScore[key] !== null &&
33
- typeof filteredScore[key] === 'object' &&
34
- !(filteredScore[key] instanceof Date)
35
- ) {
36
- filteredScore[key] = JSON.stringify(filteredScore[key]);
37
- }
38
- }
39
-
40
- filteredScore.id = id;
41
- await table.add([filteredScore], { mode: 'append' });
42
- return { score };
43
- } catch (error: any) {
44
- throw new MastraError(
45
- {
46
- id: 'LANCE_STORAGE_SAVE_SCORE_FAILED',
47
- text: 'Failed to save score in LanceStorage',
48
- domain: ErrorDomain.STORAGE,
49
- category: ErrorCategory.THIRD_PARTY,
50
- details: { error: error?.message },
51
- },
52
- error,
53
- );
54
- }
55
- }
56
-
57
- async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
58
- try {
59
- const table = await this.client.openTable(TABLE_SCORERS);
60
-
61
- const query = table.query().where(`id = '${id}'`).limit(1);
62
-
63
- const records = await query.toArray();
64
-
65
- if (records.length === 0) return null;
66
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
67
- return processResultWithTypeConversion(records[0], schema) as ScoreRowData;
68
- } catch (error: any) {
69
- throw new MastraError(
70
- {
71
- id: 'LANCE_STORAGE_GET_SCORE_BY_ID_FAILED',
72
- text: 'Failed to get score by id in LanceStorage',
73
- domain: ErrorDomain.STORAGE,
74
- category: ErrorCategory.THIRD_PARTY,
75
- details: { error: error?.message },
76
- },
77
- error,
78
- );
79
- }
80
- }
81
-
82
- async getScoresByScorerId({
83
- scorerId,
84
- pagination,
85
- entityId,
86
- entityType,
87
- source,
88
- }: {
89
- scorerId: string;
90
- pagination: StoragePagination;
91
- entityId?: string;
92
- entityType?: string;
93
- source?: ScoringSource;
94
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
95
- try {
96
- const table = await this.client.openTable(TABLE_SCORERS);
97
- // Use zero-based pagination (default page = 0)
98
- const { page = 0, perPage = 10 } = pagination || {};
99
- const offset = page * perPage;
100
-
101
- let query = table.query().where(`\`scorerId\` = '${scorerId}'`);
102
-
103
- if (source) {
104
- query = query.where(`\`source\` = '${source}'`);
105
- }
106
-
107
- if (entityId) {
108
- query = query.where(`\`entityId\` = '${entityId}'`);
109
- }
110
- if (entityType) {
111
- query = query.where(`\`entityType\` = '${entityType}'`);
112
- }
113
-
114
- query = query.limit(perPage);
115
- if (offset > 0) query.offset(offset);
116
- const records = await query.toArray();
117
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
118
- const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
119
-
120
- let totalQuery = table.query().where(`\`scorerId\` = '${scorerId}'`);
121
- if (source) {
122
- totalQuery = totalQuery.where(`\`source\` = '${source}'`);
123
- }
124
- const allRecords = await totalQuery.toArray();
125
- const total = allRecords.length;
126
-
127
- return {
128
- pagination: {
129
- page,
130
- perPage,
131
- total,
132
- hasMore: offset + scores.length < total,
133
- },
134
- scores,
135
- };
136
- } catch (error: any) {
137
- throw new MastraError(
138
- {
139
- id: 'LANCE_STORAGE_GET_SCORES_BY_SCORER_ID_FAILED',
140
- text: 'Failed to get scores by scorerId in LanceStorage',
141
- domain: ErrorDomain.STORAGE,
142
- category: ErrorCategory.THIRD_PARTY,
143
- details: { error: error?.message },
144
- },
145
- error,
146
- );
147
- }
148
- }
149
-
150
- async getScoresByRunId({
151
- runId,
152
- pagination,
153
- }: {
154
- runId: string;
155
- pagination: StoragePagination;
156
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
157
- try {
158
- const table = await this.client.openTable(TABLE_SCORERS);
159
- const { page = 0, perPage = 10 } = pagination || {};
160
- const offset = page * perPage;
161
- // Query for scores with the given runId
162
- const query = table.query().where(`\`runId\` = '${runId}'`).limit(perPage);
163
- if (offset > 0) query.offset(offset);
164
- const records = await query.toArray();
165
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
166
- const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
167
- // Get total count for pagination
168
- const allRecords = await table.query().where(`\`runId\` = '${runId}'`).toArray();
169
- const total = allRecords.length;
170
- return {
171
- pagination: {
172
- page,
173
- perPage,
174
- total,
175
- hasMore: offset + scores.length < total,
176
- },
177
- scores,
178
- };
179
- } catch (error: any) {
180
- throw new MastraError(
181
- {
182
- id: 'LANCE_STORAGE_GET_SCORES_BY_RUN_ID_FAILED',
183
- text: 'Failed to get scores by runId in LanceStorage',
184
- domain: ErrorDomain.STORAGE,
185
- category: ErrorCategory.THIRD_PARTY,
186
- details: { error: error?.message },
187
- },
188
- error,
189
- );
190
- }
191
- }
192
-
193
- async getScoresByEntityId({
194
- entityId,
195
- entityType,
196
- pagination,
197
- }: {
198
- pagination: StoragePagination;
199
- entityId: string;
200
- entityType: string;
201
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
202
- try {
203
- const table = await this.client.openTable(TABLE_SCORERS);
204
- const { page = 0, perPage = 10 } = pagination || {};
205
- const offset = page * perPage;
206
- // Query for scores with the given entityId and entityType
207
- const query = table
208
- .query()
209
- .where(`\`entityId\` = '${entityId}' AND \`entityType\` = '${entityType}'`)
210
- .limit(perPage);
211
- if (offset > 0) query.offset(offset);
212
- const records = await query.toArray();
213
- const schema = await getTableSchema({ tableName: TABLE_SCORERS, client: this.client });
214
- const scores = processResultWithTypeConversion(records, schema) as ScoreRowData[];
215
- // Get total count for pagination
216
- const allRecords = await table
217
- .query()
218
- .where(`\`entityId\` = '${entityId}' AND \`entityType\` = '${entityType}'`)
219
- .toArray();
220
- const total = allRecords.length;
221
- return {
222
- pagination: {
223
- page,
224
- perPage,
225
- total,
226
- hasMore: offset + scores.length < total,
227
- },
228
- scores,
229
- };
230
- } catch (error: any) {
231
- throw new MastraError(
232
- {
233
- id: 'LANCE_STORAGE_GET_SCORES_BY_ENTITY_ID_FAILED',
234
- text: 'Failed to get scores by entityId and entityType in LanceStorage',
235
- domain: ErrorDomain.STORAGE,
236
- category: ErrorCategory.THIRD_PARTY,
237
- details: { error: error?.message },
238
- },
239
- error,
240
- );
241
- }
242
- }
243
- }
@@ -1,212 +0,0 @@
1
- import type { Connection } from '@lancedb/lancedb';
2
- import { MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
3
- import type { TraceType } from '@mastra/core/memory';
4
- import { TABLE_TRACES, TracesStorage } from '@mastra/core/storage';
5
- import type { PaginationInfo, StorageGetTracesPaginatedArg } from '@mastra/core/storage';
6
- import type { Trace } from '@mastra/core/telemetry';
7
- import type { StoreOperationsLance } from '../operations';
8
-
9
- export class StoreTracesLance extends TracesStorage {
10
- private client: Connection;
11
- private operations: StoreOperationsLance;
12
- constructor({ client, operations }: { client: Connection; operations: StoreOperationsLance }) {
13
- super();
14
- this.client = client;
15
- this.operations = operations;
16
- }
17
-
18
- async saveTrace({ trace }: { trace: TraceType }): Promise<TraceType> {
19
- try {
20
- const table = await this.client.openTable(TABLE_TRACES);
21
- const record = {
22
- ...trace,
23
- attributes: JSON.stringify(trace.attributes),
24
- status: JSON.stringify(trace.status),
25
- events: JSON.stringify(trace.events),
26
- links: JSON.stringify(trace.links),
27
- other: JSON.stringify(trace.other),
28
- };
29
- await table.add([record], { mode: 'append' });
30
- return trace;
31
- } catch (error: any) {
32
- throw new MastraError(
33
- {
34
- id: 'LANCE_STORE_SAVE_TRACE_FAILED',
35
- domain: ErrorDomain.STORAGE,
36
- category: ErrorCategory.THIRD_PARTY,
37
- },
38
- error,
39
- );
40
- }
41
- }
42
-
43
- async getTraceById({ traceId }: { traceId: string }): Promise<TraceType> {
44
- try {
45
- const table = await this.client.openTable(TABLE_TRACES);
46
- const query = table.query().where(`id = '${traceId}'`);
47
- const records = await query.toArray();
48
- return records[0] as TraceType;
49
- } catch (error: any) {
50
- throw new MastraError(
51
- {
52
- id: 'LANCE_STORE_GET_TRACE_BY_ID_FAILED',
53
- domain: ErrorDomain.STORAGE,
54
- category: ErrorCategory.THIRD_PARTY,
55
- },
56
- error,
57
- );
58
- }
59
- }
60
-
61
- async getTraces({
62
- name,
63
- scope,
64
- page = 1,
65
- perPage = 10,
66
- attributes,
67
- }: {
68
- name?: string;
69
- scope?: string;
70
- page: number;
71
- perPage: number;
72
- attributes?: Record<string, string>;
73
- }): Promise<Trace[]> {
74
- try {
75
- const table = await this.client.openTable(TABLE_TRACES);
76
- const query = table.query();
77
- if (name) {
78
- query.where(`name = '${name}'`);
79
- }
80
- if (scope) {
81
- query.where(`scope = '${scope}'`);
82
- }
83
- if (attributes) {
84
- query.where(`attributes = '${JSON.stringify(attributes)}'`);
85
- }
86
- // Calculate offset based on page and perPage
87
- const offset = (page - 1) * perPage;
88
- query.limit(perPage);
89
- if (offset > 0) {
90
- query.offset(offset);
91
- }
92
- const records = await query.toArray();
93
- return records.map(record => {
94
- const processed = {
95
- ...record,
96
- attributes: record.attributes ? JSON.parse(record.attributes) : {},
97
- status: record.status ? JSON.parse(record.status) : {},
98
- events: record.events ? JSON.parse(record.events) : [],
99
- links: record.links ? JSON.parse(record.links) : [],
100
- other: record.other ? JSON.parse(record.other) : {},
101
- startTime: new Date(record.startTime),
102
- endTime: new Date(record.endTime),
103
- createdAt: new Date(record.createdAt),
104
- };
105
- if (processed.parentSpanId === null || processed.parentSpanId === undefined) {
106
- processed.parentSpanId = '';
107
- } else {
108
- processed.parentSpanId = String(processed.parentSpanId);
109
- }
110
- return processed as Trace;
111
- });
112
- } catch (error: any) {
113
- throw new MastraError(
114
- {
115
- id: 'LANCE_STORE_GET_TRACES_FAILED',
116
- domain: ErrorDomain.STORAGE,
117
- category: ErrorCategory.THIRD_PARTY,
118
- details: { name: name ?? '', scope: scope ?? '' },
119
- },
120
- error,
121
- );
122
- }
123
- }
124
-
125
- async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
126
- try {
127
- const table = await this.client.openTable(TABLE_TRACES);
128
- const query = table.query();
129
- const conditions: string[] = [];
130
- if (args.name) {
131
- conditions.push(`name = '${args.name}'`);
132
- }
133
- if (args.scope) {
134
- conditions.push(`scope = '${args.scope}'`);
135
- }
136
- if (args.attributes) {
137
- const attributesStr = JSON.stringify(args.attributes);
138
- conditions.push(`attributes LIKE '%${attributesStr.replace(/"/g, '\\"')}%'`);
139
- }
140
- if (args.dateRange?.start) {
141
- conditions.push(`\`createdAt\` >= ${args.dateRange.start.getTime()}`);
142
- }
143
- if (args.dateRange?.end) {
144
- conditions.push(`\`createdAt\` <= ${args.dateRange.end.getTime()}`);
145
- }
146
- if (conditions.length > 0) {
147
- const whereClause = conditions.join(' AND ');
148
- query.where(whereClause);
149
- }
150
- let total = 0;
151
- if (conditions.length > 0) {
152
- const countQuery = table.query().where(conditions.join(' AND '));
153
- const allRecords = await countQuery.toArray();
154
- total = allRecords.length;
155
- } else {
156
- total = await table.countRows();
157
- }
158
- const page = args.page || 0;
159
- const perPage = args.perPage || 10;
160
- const offset = page * perPage;
161
- query.limit(perPage);
162
- if (offset > 0) {
163
- query.offset(offset);
164
- }
165
- const records = await query.toArray();
166
- const traces = records.map(record => {
167
- const processed = {
168
- ...record,
169
- attributes: record.attributes ? JSON.parse(record.attributes) : {},
170
- status: record.status ? JSON.parse(record.status) : {},
171
- events: record.events ? JSON.parse(record.events) : [],
172
- links: record.links ? JSON.parse(record.links) : [],
173
- other: record.other ? JSON.parse(record.other) : {},
174
- startTime: new Date(record.startTime),
175
- endTime: new Date(record.endTime),
176
- createdAt: new Date(record.createdAt),
177
- };
178
- if (processed.parentSpanId === null || processed.parentSpanId === undefined) {
179
- processed.parentSpanId = '';
180
- } else {
181
- processed.parentSpanId = String(processed.parentSpanId);
182
- }
183
- return processed as Trace;
184
- });
185
- return {
186
- traces,
187
- total,
188
- page,
189
- perPage,
190
- hasMore: total > (page + 1) * perPage,
191
- };
192
- } catch (error: any) {
193
- throw new MastraError(
194
- {
195
- id: 'LANCE_STORE_GET_TRACES_PAGINATED_FAILED',
196
- domain: ErrorDomain.STORAGE,
197
- category: ErrorCategory.THIRD_PARTY,
198
- details: { name: args.name ?? '', scope: args.scope ?? '' },
199
- },
200
- error,
201
- );
202
- }
203
- }
204
-
205
- async batchTraceInsert({ records }: { records: Record<string, any>[] }): Promise<void> {
206
- this.logger.debug('Batch inserting traces', { count: records.length });
207
- await this.operations.batchInsert({
208
- tableName: TABLE_TRACES,
209
- records,
210
- });
211
- }
212
- }
@@ -1,158 +0,0 @@
1
- import type { Connection, FieldLike, SchemaLike } from '@lancedb/lancedb';
2
- import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
- import { TABLE_EVALS, TABLE_WORKFLOW_SNAPSHOT } from '@mastra/core/storage';
4
- import type { TABLE_NAMES } from '@mastra/core/storage';
5
-
6
- export function getPrimaryKeys(tableName: TABLE_NAMES): string[] {
7
- let primaryId: string[] = ['id'];
8
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
9
- primaryId = ['workflow_name', 'run_id'];
10
- } else if (tableName === TABLE_EVALS) {
11
- primaryId = ['agent_name', 'metric_name', 'run_id'];
12
- }
13
-
14
- return primaryId;
15
- }
16
-
17
- export function validateKeyTypes(keys: Record<string, any>, tableSchema: SchemaLike): void {
18
- // Create a map of field names to their expected types
19
- const fieldTypes = new Map(
20
- tableSchema.fields.map((field: any) => [field.name, field.type?.toString().toLowerCase()]),
21
- );
22
-
23
- for (const [key, value] of Object.entries(keys)) {
24
- const fieldType = fieldTypes.get(key);
25
-
26
- if (!fieldType) {
27
- throw new Error(`Field '${key}' does not exist in table schema`);
28
- }
29
-
30
- // Type validation
31
- if (value !== null) {
32
- if ((fieldType.includes('int') || fieldType.includes('bigint')) && typeof value !== 'number') {
33
- throw new Error(`Expected numeric value for field '${key}', got ${typeof value}`);
34
- }
35
-
36
- if (fieldType.includes('utf8') && typeof value !== 'string') {
37
- throw new Error(`Expected string value for field '${key}', got ${typeof value}`);
38
- }
39
-
40
- if (fieldType.includes('timestamp') && !(value instanceof Date) && typeof value !== 'string') {
41
- throw new Error(`Expected Date or string value for field '${key}', got ${typeof value}`);
42
- }
43
- }
44
- }
45
- }
46
-
47
- export function processResultWithTypeConversion(
48
- rawResult: Record<string, any> | Record<string, any>[],
49
- tableSchema: SchemaLike,
50
- ): Record<string, any> | Record<string, any>[] {
51
- // Build a map of field names to their schema types
52
- const fieldTypeMap = new Map();
53
- tableSchema.fields.forEach((field: any) => {
54
- const fieldName = field.name;
55
- const fieldTypeStr = field.type.toString().toLowerCase();
56
- fieldTypeMap.set(fieldName, fieldTypeStr);
57
- });
58
-
59
- // Handle array case
60
- if (Array.isArray(rawResult)) {
61
- return rawResult.map(item => processResultWithTypeConversion(item, tableSchema));
62
- }
63
-
64
- // Handle single record case
65
- const processedResult = { ...rawResult };
66
-
67
- // Convert each field according to its schema type
68
- for (const key in processedResult) {
69
- const fieldTypeStr = fieldTypeMap.get(key);
70
- if (!fieldTypeStr) continue;
71
-
72
- // Skip conversion for ID fields - preserve their original format
73
- // if (key === 'id') {
74
- // continue;
75
- // }
76
-
77
- // Only try to convert string values
78
- if (typeof processedResult[key] === 'string') {
79
- // Numeric types
80
- if (fieldTypeStr.includes('int32') || fieldTypeStr.includes('float32')) {
81
- if (!isNaN(Number(processedResult[key]))) {
82
- processedResult[key] = Number(processedResult[key]);
83
- }
84
- } else if (fieldTypeStr.includes('int64')) {
85
- processedResult[key] = Number(processedResult[key]);
86
- } else if (fieldTypeStr.includes('utf8') && key !== 'id') {
87
- try {
88
- const parsed = JSON.parse(processedResult[key]);
89
- if (typeof parsed === 'object') {
90
- processedResult[key] = JSON.parse(processedResult[key]);
91
- }
92
- } catch {}
93
- }
94
- } else if (typeof processedResult[key] === 'bigint') {
95
- // Convert BigInt values to regular numbers for application layer
96
- processedResult[key] = Number(processedResult[key]);
97
- } else if (fieldTypeStr.includes('float64') && ['createdAt', 'updatedAt'].includes(key)) {
98
- processedResult[key] = new Date(processedResult[key]);
99
- }
100
-
101
- console.log(key, 'processedResult', processedResult);
102
- }
103
-
104
- return processedResult;
105
- }
106
-
107
- export async function getTableSchema({
108
- tableName,
109
- client,
110
- }: {
111
- tableName: TABLE_NAMES;
112
- client: Connection;
113
- }): Promise<SchemaLike> {
114
- try {
115
- if (!client) {
116
- throw new Error('LanceDB client not initialized. Call LanceStorage.create() first.');
117
- }
118
- if (!tableName) {
119
- throw new Error('tableName is required for getTableSchema.');
120
- }
121
- } catch (validationError: any) {
122
- throw new MastraError(
123
- {
124
- id: 'STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_INVALID_ARGS',
125
- domain: ErrorDomain.STORAGE,
126
- category: ErrorCategory.USER,
127
- text: validationError.message,
128
- details: { tableName },
129
- },
130
- validationError,
131
- );
132
- }
133
-
134
- try {
135
- const table = await client.openTable(tableName);
136
- const rawSchema = await table.schema();
137
- const fields = rawSchema.fields as FieldLike[];
138
-
139
- // Convert schema to SchemaLike format
140
- return {
141
- fields,
142
- metadata: new Map<string, string>(),
143
- get names() {
144
- return fields.map((field: FieldLike) => field.name);
145
- },
146
- };
147
- } catch (error: any) {
148
- throw new MastraError(
149
- {
150
- id: 'STORAGE_LANCE_STORAGE_GET_TABLE_SCHEMA_FAILED',
151
- domain: ErrorDomain.STORAGE,
152
- category: ErrorCategory.THIRD_PARTY,
153
- details: { tableName },
154
- },
155
- error,
156
- );
157
- }
158
- }