@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.
- package/CHANGELOG.md +18 -0
- package/package.json +19 -6
- package/.turbo/turbo-build.log +0 -4
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -2
- package/src/storage/domains/legacy-evals/index.ts +0 -156
- package/src/storage/domains/memory/index.ts +0 -1000
- package/src/storage/domains/operations/index.ts +0 -489
- package/src/storage/domains/scores/index.ts +0 -243
- package/src/storage/domains/traces/index.ts +0 -212
- package/src/storage/domains/utils.ts +0 -158
- package/src/storage/domains/workflows/index.ts +0 -245
- package/src/storage/index.test.ts +0 -10
- package/src/storage/index.ts +0 -494
- package/src/vector/filter.test.ts +0 -295
- package/src/vector/filter.ts +0 -443
- package/src/vector/index.test.ts +0 -1493
- package/src/vector/index.ts +0 -941
- package/src/vector/types.ts +0 -16
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
package/src/vector/index.ts
DELETED
|
@@ -1,941 +0,0 @@
|
|
|
1
|
-
import { connect, Index } from '@lancedb/lancedb';
|
|
2
|
-
import type { Connection, ConnectionOptions, CreateTableOptions, Table, TableLike } from '@lancedb/lancedb';
|
|
3
|
-
|
|
4
|
-
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
5
|
-
import type {
|
|
6
|
-
CreateIndexParams,
|
|
7
|
-
DeleteIndexParams,
|
|
8
|
-
DeleteVectorParams,
|
|
9
|
-
DescribeIndexParams,
|
|
10
|
-
IndexStats,
|
|
11
|
-
QueryResult,
|
|
12
|
-
QueryVectorParams,
|
|
13
|
-
UpdateVectorParams,
|
|
14
|
-
UpsertVectorParams,
|
|
15
|
-
} from '@mastra/core/vector';
|
|
16
|
-
|
|
17
|
-
import { MastraVector } from '@mastra/core/vector';
|
|
18
|
-
import type { LanceVectorFilter } from './filter';
|
|
19
|
-
import { LanceFilterTranslator } from './filter';
|
|
20
|
-
import type { IndexConfig } from './types';
|
|
21
|
-
|
|
22
|
-
interface LanceCreateIndexParams extends CreateIndexParams {
|
|
23
|
-
indexConfig?: LanceIndexConfig;
|
|
24
|
-
tableName?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface LanceIndexConfig extends IndexConfig {
|
|
28
|
-
numPartitions?: number;
|
|
29
|
-
numSubVectors?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface LanceUpsertVectorParams extends UpsertVectorParams {
|
|
33
|
-
tableName: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface LanceQueryVectorParams extends QueryVectorParams<LanceVectorFilter> {
|
|
37
|
-
tableName: string;
|
|
38
|
-
columns?: string[];
|
|
39
|
-
includeAllColumns?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class LanceVectorStore extends MastraVector<LanceVectorFilter> {
|
|
43
|
-
private lanceClient!: Connection;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Creates a new instance of LanceVectorStore
|
|
47
|
-
* @param uri The URI to connect to LanceDB
|
|
48
|
-
* @param options connection options
|
|
49
|
-
*
|
|
50
|
-
* Usage:
|
|
51
|
-
*
|
|
52
|
-
* Connect to a local database
|
|
53
|
-
* ```ts
|
|
54
|
-
* const store = await LanceVectorStore.create('/path/to/db');
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* Connect to a LanceDB cloud database
|
|
58
|
-
* ```ts
|
|
59
|
-
* const store = await LanceVectorStore.create('db://host:port');
|
|
60
|
-
* ```
|
|
61
|
-
*
|
|
62
|
-
* Connect to a cloud database
|
|
63
|
-
* ```ts
|
|
64
|
-
* const store = await LanceVectorStore.create('s3://bucket/db', { storageOptions: { timeout: '60s' } });
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
public static async create(uri: string, options?: ConnectionOptions): Promise<LanceVectorStore> {
|
|
68
|
-
const instance = new LanceVectorStore();
|
|
69
|
-
try {
|
|
70
|
-
instance.lanceClient = await connect(uri, options);
|
|
71
|
-
return instance;
|
|
72
|
-
} catch (e) {
|
|
73
|
-
throw new MastraError(
|
|
74
|
-
{
|
|
75
|
-
id: 'STORAGE_LANCE_VECTOR_CONNECT_FAILED',
|
|
76
|
-
domain: ErrorDomain.STORAGE,
|
|
77
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
78
|
-
details: { uri },
|
|
79
|
-
},
|
|
80
|
-
e,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* @internal
|
|
87
|
-
* Private constructor to enforce using the create factory method
|
|
88
|
-
*/
|
|
89
|
-
private constructor() {
|
|
90
|
-
super();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
close() {
|
|
94
|
-
if (this.lanceClient) {
|
|
95
|
-
this.lanceClient.close();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async query({
|
|
100
|
-
tableName,
|
|
101
|
-
queryVector,
|
|
102
|
-
filter,
|
|
103
|
-
includeVector = false,
|
|
104
|
-
topK = 10,
|
|
105
|
-
columns = [],
|
|
106
|
-
includeAllColumns = false,
|
|
107
|
-
}: LanceQueryVectorParams): Promise<QueryResult[]> {
|
|
108
|
-
try {
|
|
109
|
-
if (!this.lanceClient) {
|
|
110
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!tableName) {
|
|
114
|
-
throw new Error('tableName is required');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!queryVector) {
|
|
118
|
-
throw new Error('queryVector is required');
|
|
119
|
-
}
|
|
120
|
-
} catch (error) {
|
|
121
|
-
throw new MastraError(
|
|
122
|
-
{
|
|
123
|
-
id: 'STORAGE_LANCE_VECTOR_QUERY_FAILED_INVALID_ARGS',
|
|
124
|
-
domain: ErrorDomain.STORAGE,
|
|
125
|
-
category: ErrorCategory.USER,
|
|
126
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
127
|
-
details: { tableName },
|
|
128
|
-
},
|
|
129
|
-
error,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
// Open the table
|
|
135
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
136
|
-
|
|
137
|
-
// Prepare the list of columns to select
|
|
138
|
-
const selectColumns = [...columns];
|
|
139
|
-
if (!selectColumns.includes('id')) {
|
|
140
|
-
selectColumns.push('id');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Create the query builder
|
|
144
|
-
let query = table.search(queryVector);
|
|
145
|
-
|
|
146
|
-
// Add filter if provided
|
|
147
|
-
if (filter && Object.keys(filter).length > 0) {
|
|
148
|
-
const whereClause = this.filterTranslator(filter);
|
|
149
|
-
this.logger.debug(`Where clause generated: ${whereClause}`);
|
|
150
|
-
query = query.where(whereClause);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Apply column selection and limit
|
|
154
|
-
if (!includeAllColumns && selectColumns.length > 0) {
|
|
155
|
-
query = query.select(selectColumns);
|
|
156
|
-
}
|
|
157
|
-
query = query.limit(topK);
|
|
158
|
-
|
|
159
|
-
// Execute the query
|
|
160
|
-
const results = await query.toArray();
|
|
161
|
-
|
|
162
|
-
return results.map(result => {
|
|
163
|
-
// Collect all metadata_ prefixed fields
|
|
164
|
-
const flatMetadata: Record<string, any> = {};
|
|
165
|
-
|
|
166
|
-
// Get all keys from the result object
|
|
167
|
-
Object.keys(result).forEach(key => {
|
|
168
|
-
// Skip reserved keys (id, score, and the vector column)
|
|
169
|
-
if (key !== 'id' && key !== 'score' && key !== 'vector' && key !== '_distance') {
|
|
170
|
-
if (key.startsWith('metadata_')) {
|
|
171
|
-
// Remove the prefix and add to flat metadata
|
|
172
|
-
const metadataKey = key.substring('metadata_'.length);
|
|
173
|
-
flatMetadata[metadataKey] = result[key];
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Reconstruct nested metadata object
|
|
179
|
-
const metadata = this.unflattenObject(flatMetadata);
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
id: String(result.id || ''),
|
|
183
|
-
metadata,
|
|
184
|
-
vector:
|
|
185
|
-
includeVector && result.vector
|
|
186
|
-
? Array.isArray(result.vector)
|
|
187
|
-
? result.vector
|
|
188
|
-
: Array.from(result.vector as any[])
|
|
189
|
-
: undefined,
|
|
190
|
-
document: result.document,
|
|
191
|
-
score: result._distance,
|
|
192
|
-
};
|
|
193
|
-
});
|
|
194
|
-
} catch (error: any) {
|
|
195
|
-
throw new MastraError(
|
|
196
|
-
{
|
|
197
|
-
id: 'STORAGE_LANCE_VECTOR_QUERY_FAILED',
|
|
198
|
-
domain: ErrorDomain.STORAGE,
|
|
199
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
200
|
-
details: { tableName, includeVector, columnsCount: columns?.length, includeAllColumns },
|
|
201
|
-
},
|
|
202
|
-
error,
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private filterTranslator(filter: LanceVectorFilter): string {
|
|
208
|
-
// Add metadata_ prefix to filter keys if they don't already have it
|
|
209
|
-
const processFilterKeys = (filterObj: Record<string, any>): Record<string, any> => {
|
|
210
|
-
const result: Record<string, any> = {};
|
|
211
|
-
|
|
212
|
-
Object.entries(filterObj).forEach(([key, value]) => {
|
|
213
|
-
// Don't add prefix to logical operators
|
|
214
|
-
if (key === '$or' || key === '$and' || key === '$not' || key === '$in') {
|
|
215
|
-
// For logical operators, process their array contents
|
|
216
|
-
if (Array.isArray(value)) {
|
|
217
|
-
result[key] = value.map(item =>
|
|
218
|
-
typeof item === 'object' && item !== null ? processFilterKeys(item as Record<string, any>) : item,
|
|
219
|
-
);
|
|
220
|
-
} else {
|
|
221
|
-
result[key] = value;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Don't add prefix if it already has metadata_ prefix
|
|
225
|
-
else if (key.startsWith('metadata_')) {
|
|
226
|
-
result[key] = value;
|
|
227
|
-
}
|
|
228
|
-
// Add metadata_ prefix to regular field keys
|
|
229
|
-
else {
|
|
230
|
-
// Convert dot notation to underscore notation for nested fields
|
|
231
|
-
if (key.includes('.')) {
|
|
232
|
-
const convertedKey = `metadata_${key.replace(/\./g, '_')}`;
|
|
233
|
-
result[convertedKey] = value;
|
|
234
|
-
} else {
|
|
235
|
-
result[`metadata_${key}`] = value;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return result;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const prefixedFilter = filter && typeof filter === 'object' ? processFilterKeys(filter as Record<string, any>) : {};
|
|
244
|
-
|
|
245
|
-
const translator = new LanceFilterTranslator();
|
|
246
|
-
return translator.translate(prefixedFilter);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async upsert({ tableName, vectors, metadata = [], ids = [] }: LanceUpsertVectorParams): Promise<string[]> {
|
|
250
|
-
try {
|
|
251
|
-
if (!this.lanceClient) {
|
|
252
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!tableName) {
|
|
256
|
-
throw new Error('tableName is required');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!vectors || !Array.isArray(vectors) || vectors.length === 0) {
|
|
260
|
-
throw new Error('vectors array is required and must not be empty');
|
|
261
|
-
}
|
|
262
|
-
} catch (error) {
|
|
263
|
-
throw new MastraError(
|
|
264
|
-
{
|
|
265
|
-
id: 'STORAGE_LANCE_VECTOR_UPSERT_FAILED_INVALID_ARGS',
|
|
266
|
-
domain: ErrorDomain.STORAGE,
|
|
267
|
-
category: ErrorCategory.USER,
|
|
268
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
269
|
-
details: { tableName },
|
|
270
|
-
},
|
|
271
|
-
error,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const tables = await this.lanceClient.tableNames();
|
|
277
|
-
if (!tables.includes(tableName)) {
|
|
278
|
-
throw new Error(`Table ${tableName} does not exist`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
282
|
-
|
|
283
|
-
// Generate IDs if not provided
|
|
284
|
-
const vectorIds = ids.length === vectors.length ? ids : vectors.map((_, i) => ids[i] || crypto.randomUUID());
|
|
285
|
-
|
|
286
|
-
// Create data with metadata fields expanded at the top level
|
|
287
|
-
const data = vectors.map((vector, i) => {
|
|
288
|
-
const id = String(vectorIds[i]);
|
|
289
|
-
const metadataItem = metadata[i] || {};
|
|
290
|
-
|
|
291
|
-
// Create the base object with id and vector
|
|
292
|
-
const rowData: Record<string, any> = {
|
|
293
|
-
id,
|
|
294
|
-
vector: vector,
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// Flatten the metadata object and prefix all keys with 'metadata_'
|
|
298
|
-
if (Object.keys(metadataItem).length > 0) {
|
|
299
|
-
const flattenedMetadata = this.flattenObject(metadataItem, 'metadata');
|
|
300
|
-
// Add all flattened metadata properties to the row data object
|
|
301
|
-
Object.entries(flattenedMetadata).forEach(([key, value]) => {
|
|
302
|
-
rowData[key] = value;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return rowData;
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
await table.add(data, { mode: 'overwrite' });
|
|
310
|
-
|
|
311
|
-
return vectorIds;
|
|
312
|
-
} catch (error: any) {
|
|
313
|
-
throw new MastraError(
|
|
314
|
-
{
|
|
315
|
-
id: 'STORAGE_LANCE_VECTOR_UPSERT_FAILED',
|
|
316
|
-
domain: ErrorDomain.STORAGE,
|
|
317
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
318
|
-
details: { tableName, vectorCount: vectors.length, metadataCount: metadata.length, idsCount: ids.length },
|
|
319
|
-
},
|
|
320
|
-
error,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Flattens a nested object, creating new keys with underscores for nested properties.
|
|
327
|
-
* Example: { metadata: { text: 'test' } } → { metadata_text: 'test' }
|
|
328
|
-
*/
|
|
329
|
-
private flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {
|
|
330
|
-
return Object.keys(obj).reduce((acc: Record<string, unknown>, k: string) => {
|
|
331
|
-
const pre = prefix.length ? `${prefix}_` : '';
|
|
332
|
-
if (typeof obj[k] === 'object' && obj[k] !== null && !Array.isArray(obj[k])) {
|
|
333
|
-
Object.assign(acc, this.flattenObject(obj[k] as Record<string, unknown>, pre + k));
|
|
334
|
-
} else {
|
|
335
|
-
acc[pre + k] = obj[k];
|
|
336
|
-
}
|
|
337
|
-
return acc;
|
|
338
|
-
}, {});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async createTable(
|
|
342
|
-
tableName: string,
|
|
343
|
-
data: Record<string, unknown>[] | TableLike,
|
|
344
|
-
options?: Partial<CreateTableOptions>,
|
|
345
|
-
): Promise<Table> {
|
|
346
|
-
if (!this.lanceClient) {
|
|
347
|
-
throw new MastraError({
|
|
348
|
-
id: 'STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED_INVALID_ARGS',
|
|
349
|
-
domain: ErrorDomain.STORAGE,
|
|
350
|
-
category: ErrorCategory.USER,
|
|
351
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
352
|
-
details: { tableName },
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Flatten nested objects if data is an array of records
|
|
357
|
-
if (Array.isArray(data)) {
|
|
358
|
-
data = data.map(record => this.flattenObject(record));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
return await this.lanceClient.createTable(tableName, data, options);
|
|
363
|
-
} catch (error: any) {
|
|
364
|
-
throw new MastraError(
|
|
365
|
-
{
|
|
366
|
-
id: 'STORAGE_LANCE_VECTOR_CREATE_TABLE_FAILED',
|
|
367
|
-
domain: ErrorDomain.STORAGE,
|
|
368
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
369
|
-
details: { tableName },
|
|
370
|
-
},
|
|
371
|
-
error,
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async listTables(): Promise<string[]> {
|
|
377
|
-
if (!this.lanceClient) {
|
|
378
|
-
throw new MastraError({
|
|
379
|
-
id: 'STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED_INVALID_ARGS',
|
|
380
|
-
domain: ErrorDomain.STORAGE,
|
|
381
|
-
category: ErrorCategory.USER,
|
|
382
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
383
|
-
details: { methodName: 'listTables' },
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
try {
|
|
387
|
-
return await this.lanceClient.tableNames();
|
|
388
|
-
} catch (error) {
|
|
389
|
-
throw new MastraError(
|
|
390
|
-
{
|
|
391
|
-
id: 'STORAGE_LANCE_VECTOR_LIST_TABLES_FAILED',
|
|
392
|
-
domain: ErrorDomain.STORAGE,
|
|
393
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
394
|
-
},
|
|
395
|
-
error,
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async getTableSchema(tableName: string): Promise<any> {
|
|
401
|
-
if (!this.lanceClient) {
|
|
402
|
-
throw new MastraError({
|
|
403
|
-
id: 'STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED_INVALID_ARGS',
|
|
404
|
-
domain: ErrorDomain.STORAGE,
|
|
405
|
-
category: ErrorCategory.USER,
|
|
406
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
407
|
-
details: { tableName },
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
413
|
-
return await table.schema();
|
|
414
|
-
} catch (error) {
|
|
415
|
-
throw new MastraError(
|
|
416
|
-
{
|
|
417
|
-
id: 'STORAGE_LANCE_VECTOR_GET_TABLE_SCHEMA_FAILED',
|
|
418
|
-
domain: ErrorDomain.STORAGE,
|
|
419
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
420
|
-
details: { tableName },
|
|
421
|
-
},
|
|
422
|
-
error,
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* indexName is actually a column name in a table in lanceDB
|
|
429
|
-
*/
|
|
430
|
-
async createIndex({
|
|
431
|
-
tableName,
|
|
432
|
-
indexName,
|
|
433
|
-
dimension,
|
|
434
|
-
metric = 'cosine',
|
|
435
|
-
indexConfig = {},
|
|
436
|
-
}: LanceCreateIndexParams): Promise<void> {
|
|
437
|
-
try {
|
|
438
|
-
if (!this.lanceClient) {
|
|
439
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (!tableName) {
|
|
443
|
-
throw new Error('tableName is required');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (!indexName) {
|
|
447
|
-
throw new Error('indexName is required');
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (typeof dimension !== 'number' || dimension <= 0) {
|
|
451
|
-
throw new Error('dimension must be a positive number');
|
|
452
|
-
}
|
|
453
|
-
} catch (err) {
|
|
454
|
-
throw new MastraError(
|
|
455
|
-
{
|
|
456
|
-
id: 'STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED_INVALID_ARGS',
|
|
457
|
-
domain: ErrorDomain.STORAGE,
|
|
458
|
-
category: ErrorCategory.USER,
|
|
459
|
-
details: { tableName: tableName || '', indexName, dimension, metric },
|
|
460
|
-
},
|
|
461
|
-
err,
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
try {
|
|
466
|
-
const tables = await this.lanceClient.tableNames();
|
|
467
|
-
if (!tables.includes(tableName)) {
|
|
468
|
-
throw new Error(
|
|
469
|
-
`Table ${tableName} does not exist. Please create the table first by calling createTable() method.`,
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
474
|
-
|
|
475
|
-
// Convert metric to LanceDB metric
|
|
476
|
-
type LanceMetric = 'cosine' | 'l2' | 'dot';
|
|
477
|
-
let metricType: LanceMetric | undefined;
|
|
478
|
-
if (metric === 'euclidean') {
|
|
479
|
-
metricType = 'l2';
|
|
480
|
-
} else if (metric === 'dotproduct') {
|
|
481
|
-
metricType = 'dot';
|
|
482
|
-
} else if (metric === 'cosine') {
|
|
483
|
-
metricType = 'cosine';
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (indexConfig.type === 'ivfflat') {
|
|
487
|
-
await table.createIndex(indexName, {
|
|
488
|
-
config: Index.ivfPq({
|
|
489
|
-
numPartitions: indexConfig.numPartitions || 128,
|
|
490
|
-
numSubVectors: indexConfig.numSubVectors || 16,
|
|
491
|
-
distanceType: metricType,
|
|
492
|
-
}),
|
|
493
|
-
});
|
|
494
|
-
} else {
|
|
495
|
-
// Default to HNSW PQ index
|
|
496
|
-
this.logger.debug('Creating HNSW PQ index with config:', indexConfig);
|
|
497
|
-
await table.createIndex(indexName, {
|
|
498
|
-
config: Index.hnswPq({
|
|
499
|
-
m: indexConfig?.hnsw?.m || 16,
|
|
500
|
-
efConstruction: indexConfig?.hnsw?.efConstruction || 100,
|
|
501
|
-
distanceType: metricType,
|
|
502
|
-
}),
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
} catch (error: any) {
|
|
506
|
-
throw new MastraError(
|
|
507
|
-
{
|
|
508
|
-
id: 'STORAGE_LANCE_VECTOR_CREATE_INDEX_FAILED',
|
|
509
|
-
domain: ErrorDomain.STORAGE,
|
|
510
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
511
|
-
details: { tableName: tableName || '', indexName, dimension },
|
|
512
|
-
},
|
|
513
|
-
error,
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async listIndexes(): Promise<string[]> {
|
|
519
|
-
if (!this.lanceClient) {
|
|
520
|
-
throw new MastraError({
|
|
521
|
-
id: 'STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED_INVALID_ARGS',
|
|
522
|
-
domain: ErrorDomain.STORAGE,
|
|
523
|
-
category: ErrorCategory.USER,
|
|
524
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
525
|
-
details: { methodName: 'listIndexes' },
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
try {
|
|
530
|
-
const tables = await this.lanceClient.tableNames();
|
|
531
|
-
const allIndices: string[] = [];
|
|
532
|
-
|
|
533
|
-
for (const tableName of tables) {
|
|
534
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
535
|
-
const tableIndices = await table.listIndices();
|
|
536
|
-
allIndices.push(...tableIndices.map(index => index.name));
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return allIndices;
|
|
540
|
-
} catch (error: any) {
|
|
541
|
-
throw new MastraError(
|
|
542
|
-
{
|
|
543
|
-
id: 'STORAGE_LANCE_VECTOR_LIST_INDEXES_FAILED',
|
|
544
|
-
domain: ErrorDomain.STORAGE,
|
|
545
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
546
|
-
},
|
|
547
|
-
error,
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
|
|
553
|
-
try {
|
|
554
|
-
if (!this.lanceClient) {
|
|
555
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
if (!indexName) {
|
|
559
|
-
throw new Error('indexName is required');
|
|
560
|
-
}
|
|
561
|
-
} catch (err) {
|
|
562
|
-
throw new MastraError(
|
|
563
|
-
{
|
|
564
|
-
id: 'STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED_INVALID_ARGS',
|
|
565
|
-
domain: ErrorDomain.STORAGE,
|
|
566
|
-
category: ErrorCategory.USER,
|
|
567
|
-
details: { indexName },
|
|
568
|
-
},
|
|
569
|
-
err,
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
try {
|
|
574
|
-
const tables = await this.lanceClient.tableNames();
|
|
575
|
-
|
|
576
|
-
for (const tableName of tables) {
|
|
577
|
-
this.logger.debug('Checking table:' + tableName);
|
|
578
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
579
|
-
const tableIndices = await table.listIndices();
|
|
580
|
-
const foundIndex = tableIndices.find(index => index.name === indexName);
|
|
581
|
-
|
|
582
|
-
if (foundIndex) {
|
|
583
|
-
const stats = await table.indexStats(foundIndex.name);
|
|
584
|
-
|
|
585
|
-
if (!stats) {
|
|
586
|
-
throw new Error(`Index stats not found for index: ${indexName}`);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const schema = await table.schema();
|
|
590
|
-
const vectorCol = foundIndex.columns[0] || 'vector';
|
|
591
|
-
|
|
592
|
-
// Find the vector column in the schema
|
|
593
|
-
const vectorField = schema.fields.find(field => field.name === vectorCol);
|
|
594
|
-
const dimension = vectorField?.type?.['listSize'] || 0;
|
|
595
|
-
|
|
596
|
-
return {
|
|
597
|
-
dimension: dimension,
|
|
598
|
-
metric: stats.distanceType as 'cosine' | 'euclidean' | 'dotproduct' | undefined,
|
|
599
|
-
count: stats.numIndexedRows,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
throw new Error(`IndexName: ${indexName} not found`);
|
|
605
|
-
} catch (error: any) {
|
|
606
|
-
throw new MastraError(
|
|
607
|
-
{
|
|
608
|
-
id: 'STORAGE_LANCE_VECTOR_DESCRIBE_INDEX_FAILED',
|
|
609
|
-
domain: ErrorDomain.STORAGE,
|
|
610
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
611
|
-
details: { indexName },
|
|
612
|
-
},
|
|
613
|
-
error,
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
|
|
619
|
-
try {
|
|
620
|
-
if (!this.lanceClient) {
|
|
621
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (!indexName) {
|
|
625
|
-
throw new Error('indexName is required');
|
|
626
|
-
}
|
|
627
|
-
} catch (err) {
|
|
628
|
-
throw new MastraError(
|
|
629
|
-
{
|
|
630
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED_INVALID_ARGS',
|
|
631
|
-
domain: ErrorDomain.STORAGE,
|
|
632
|
-
category: ErrorCategory.USER,
|
|
633
|
-
details: { indexName },
|
|
634
|
-
},
|
|
635
|
-
err,
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
try {
|
|
639
|
-
const tables = await this.lanceClient.tableNames();
|
|
640
|
-
|
|
641
|
-
for (const tableName of tables) {
|
|
642
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
643
|
-
const tableIndices = await table.listIndices();
|
|
644
|
-
const foundIndex = tableIndices.find(index => index.name === indexName);
|
|
645
|
-
|
|
646
|
-
if (foundIndex) {
|
|
647
|
-
await table.dropIndex(indexName);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
throw new Error(`Index ${indexName} not found`);
|
|
653
|
-
} catch (error: any) {
|
|
654
|
-
throw new MastraError(
|
|
655
|
-
{
|
|
656
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_INDEX_FAILED',
|
|
657
|
-
domain: ErrorDomain.STORAGE,
|
|
658
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
659
|
-
details: { indexName },
|
|
660
|
-
},
|
|
661
|
-
error,
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Deletes all tables in the database
|
|
668
|
-
*/
|
|
669
|
-
async deleteAllTables(): Promise<void> {
|
|
670
|
-
if (!this.lanceClient) {
|
|
671
|
-
throw new MastraError({
|
|
672
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED_INVALID_ARGS',
|
|
673
|
-
domain: ErrorDomain.STORAGE,
|
|
674
|
-
category: ErrorCategory.USER,
|
|
675
|
-
details: { methodName: 'deleteAllTables' },
|
|
676
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
try {
|
|
680
|
-
await this.lanceClient.dropAllTables();
|
|
681
|
-
} catch (error) {
|
|
682
|
-
throw new MastraError(
|
|
683
|
-
{
|
|
684
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_ALL_TABLES_FAILED',
|
|
685
|
-
domain: ErrorDomain.STORAGE,
|
|
686
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
687
|
-
details: { methodName: 'deleteAllTables' },
|
|
688
|
-
},
|
|
689
|
-
error,
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
async deleteTable(tableName: string): Promise<void> {
|
|
695
|
-
if (!this.lanceClient) {
|
|
696
|
-
throw new MastraError({
|
|
697
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED_INVALID_ARGS',
|
|
698
|
-
domain: ErrorDomain.STORAGE,
|
|
699
|
-
category: ErrorCategory.USER,
|
|
700
|
-
details: { tableName },
|
|
701
|
-
text: 'LanceDB client not initialized. Use LanceVectorStore.create() to create an instance',
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
try {
|
|
706
|
-
await this.lanceClient.dropTable(tableName);
|
|
707
|
-
} catch (error: any) {
|
|
708
|
-
// throw new Error(`Failed to delete tables: ${error.message}`);
|
|
709
|
-
throw new MastraError(
|
|
710
|
-
{
|
|
711
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_TABLE_FAILED',
|
|
712
|
-
domain: ErrorDomain.STORAGE,
|
|
713
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
714
|
-
details: { tableName },
|
|
715
|
-
},
|
|
716
|
-
error,
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
|
|
722
|
-
try {
|
|
723
|
-
if (!this.lanceClient) {
|
|
724
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (!indexName) {
|
|
728
|
-
throw new Error('indexName is required');
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (!id) {
|
|
732
|
-
throw new Error('id is required');
|
|
733
|
-
}
|
|
734
|
-
} catch (err) {
|
|
735
|
-
throw new MastraError(
|
|
736
|
-
{
|
|
737
|
-
id: 'STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED_INVALID_ARGS',
|
|
738
|
-
domain: ErrorDomain.STORAGE,
|
|
739
|
-
category: ErrorCategory.USER,
|
|
740
|
-
details: { indexName, id },
|
|
741
|
-
},
|
|
742
|
-
err,
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
try {
|
|
747
|
-
// In LanceDB, the indexName is actually a column name in a table
|
|
748
|
-
// We need to find which table has this column as an index
|
|
749
|
-
const tables = await this.lanceClient.tableNames();
|
|
750
|
-
|
|
751
|
-
for (const tableName of tables) {
|
|
752
|
-
this.logger.debug('Checking table:' + tableName);
|
|
753
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
754
|
-
|
|
755
|
-
try {
|
|
756
|
-
const schema = await table.schema();
|
|
757
|
-
const hasColumn = schema.fields.some(field => field.name === indexName);
|
|
758
|
-
|
|
759
|
-
if (hasColumn) {
|
|
760
|
-
this.logger.debug(`Found column ${indexName} in table ${tableName}`);
|
|
761
|
-
|
|
762
|
-
// First, query the existing record to preserve values that aren't being updated
|
|
763
|
-
const existingRecord = await table
|
|
764
|
-
.query()
|
|
765
|
-
.where(`id = '${id}'`)
|
|
766
|
-
.select(schema.fields.map(field => field.name))
|
|
767
|
-
.limit(1)
|
|
768
|
-
.toArray();
|
|
769
|
-
|
|
770
|
-
if (existingRecord.length === 0) {
|
|
771
|
-
throw new Error(`Record with id '${id}' not found in table ${tableName}`);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Create a clean data object for update
|
|
775
|
-
const rowData: Record<string, any> = {
|
|
776
|
-
id,
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
// Copy all existing field values except special fields
|
|
780
|
-
Object.entries(existingRecord[0]).forEach(([key, value]) => {
|
|
781
|
-
// Skip special fields
|
|
782
|
-
if (key !== 'id' && key !== '_distance') {
|
|
783
|
-
// Handle vector field specially to avoid nested properties
|
|
784
|
-
if (key === indexName) {
|
|
785
|
-
// If we're about to update this vector anyway, skip copying
|
|
786
|
-
if (!update.vector) {
|
|
787
|
-
// Ensure vector is a plain array
|
|
788
|
-
if (Array.isArray(value)) {
|
|
789
|
-
rowData[key] = [...value];
|
|
790
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
791
|
-
// Handle vector objects by converting to array if needed
|
|
792
|
-
rowData[key] = Array.from(value as any[]);
|
|
793
|
-
} else {
|
|
794
|
-
rowData[key] = value;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
} else {
|
|
798
|
-
rowData[key] = value;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
// Apply the vector update if provided
|
|
804
|
-
if (update.vector) {
|
|
805
|
-
rowData[indexName] = update.vector;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// Apply metadata updates if provided
|
|
809
|
-
if (update.metadata) {
|
|
810
|
-
Object.entries(update.metadata).forEach(([key, value]) => {
|
|
811
|
-
rowData[`metadata_${key}`] = value;
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Update the record
|
|
816
|
-
await table.add([rowData], { mode: 'overwrite' });
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
} catch (err) {
|
|
820
|
-
this.logger.error(`Error checking schema for table ${tableName}:` + err);
|
|
821
|
-
// Continue to the next table if there's an error
|
|
822
|
-
continue;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
throw new Error(`No table found with column/index '${indexName}'`);
|
|
827
|
-
} catch (error: any) {
|
|
828
|
-
throw new MastraError(
|
|
829
|
-
{
|
|
830
|
-
id: 'STORAGE_LANCE_VECTOR_UPDATE_VECTOR_FAILED',
|
|
831
|
-
domain: ErrorDomain.STORAGE,
|
|
832
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
833
|
-
details: { indexName, id, hasVector: !!update.vector, hasMetadata: !!update.metadata },
|
|
834
|
-
},
|
|
835
|
-
error,
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
|
|
841
|
-
try {
|
|
842
|
-
if (!this.lanceClient) {
|
|
843
|
-
throw new Error('LanceDB client not initialized. Use LanceVectorStore.create() to create an instance');
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
if (!indexName) {
|
|
847
|
-
throw new Error('indexName is required');
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (!id) {
|
|
851
|
-
throw new Error('id is required');
|
|
852
|
-
}
|
|
853
|
-
} catch (err) {
|
|
854
|
-
throw new MastraError(
|
|
855
|
-
{
|
|
856
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED_INVALID_ARGS',
|
|
857
|
-
domain: ErrorDomain.STORAGE,
|
|
858
|
-
category: ErrorCategory.USER,
|
|
859
|
-
details: { indexName, id },
|
|
860
|
-
},
|
|
861
|
-
err,
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
try {
|
|
866
|
-
// In LanceDB, the indexName is actually a column name in a table
|
|
867
|
-
// We need to find which table has this column as an index
|
|
868
|
-
const tables = await this.lanceClient.tableNames();
|
|
869
|
-
|
|
870
|
-
for (const tableName of tables) {
|
|
871
|
-
this.logger.debug('Checking table:' + tableName);
|
|
872
|
-
const table = await this.lanceClient.openTable(tableName);
|
|
873
|
-
|
|
874
|
-
try {
|
|
875
|
-
// Try to get the schema to check if this table has the column we're looking for
|
|
876
|
-
const schema = await table.schema();
|
|
877
|
-
const hasColumn = schema.fields.some(field => field.name === indexName);
|
|
878
|
-
|
|
879
|
-
if (hasColumn) {
|
|
880
|
-
this.logger.debug(`Found column ${indexName} in table ${tableName}`);
|
|
881
|
-
await table.delete(`id = '${id}'`);
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
} catch (err) {
|
|
885
|
-
this.logger.error(`Error checking schema for table ${tableName}:` + err);
|
|
886
|
-
// Continue to the next table if there's an error
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
throw new Error(`No table found with column/index '${indexName}'`);
|
|
892
|
-
} catch (error: any) {
|
|
893
|
-
throw new MastraError(
|
|
894
|
-
{
|
|
895
|
-
id: 'STORAGE_LANCE_VECTOR_DELETE_VECTOR_FAILED',
|
|
896
|
-
domain: ErrorDomain.STORAGE,
|
|
897
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
898
|
-
details: { indexName, id },
|
|
899
|
-
},
|
|
900
|
-
error,
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Converts a flattened object with keys using underscore notation back to a nested object.
|
|
907
|
-
* Example: { name: 'test', details_text: 'test' } → { name: 'test', details: { text: 'test' } }
|
|
908
|
-
*/
|
|
909
|
-
private unflattenObject(obj: Record<string, any>): Record<string, any> {
|
|
910
|
-
const result: Record<string, any> = {};
|
|
911
|
-
|
|
912
|
-
Object.keys(obj).forEach(key => {
|
|
913
|
-
const value = obj[key];
|
|
914
|
-
const parts = key.split('_');
|
|
915
|
-
|
|
916
|
-
// Start with the result object
|
|
917
|
-
let current = result;
|
|
918
|
-
|
|
919
|
-
// Process all parts except the last one
|
|
920
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
921
|
-
const part = parts[i];
|
|
922
|
-
// Skip empty parts
|
|
923
|
-
if (!part) continue;
|
|
924
|
-
|
|
925
|
-
// Create nested object if it doesn't exist
|
|
926
|
-
if (!current[part] || typeof current[part] !== 'object') {
|
|
927
|
-
current[part] = {};
|
|
928
|
-
}
|
|
929
|
-
current = current[part];
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// Set the value at the last part
|
|
933
|
-
const lastPart = parts[parts.length - 1];
|
|
934
|
-
if (lastPart) {
|
|
935
|
-
current[lastPart] = value;
|
|
936
|
-
}
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
return result;
|
|
940
|
-
}
|
|
941
|
-
}
|