@mastra/chroma 0.0.0-vnextWorkflows-20250422142014 → 0.0.0-workflow-deno-20250616115451
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 +23 -0
- package/CHANGELOG.md +454 -2
- package/dist/_tsup-dts-rollup.d.cts +42 -17
- package/dist/_tsup-dts-rollup.d.ts +42 -17
- package/dist/index.cjs +142 -32
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +142 -33
- package/package.json +13 -10
- package/src/index.ts +1 -0
- package/src/vector/index.test.ts +85 -134
- package/src/vector/index.ts +79 -51
- package/src/vector/prompt.ts +72 -0
package/src/vector/index.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
16
16
|
beforeEach(async () => {
|
|
17
17
|
// Clean up any existing test index
|
|
18
18
|
try {
|
|
19
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
19
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
20
20
|
} catch {
|
|
21
21
|
// Ignore errors if index doesn't exist
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
26
26
|
afterEach(async () => {
|
|
27
27
|
// Cleanup after tests
|
|
28
28
|
try {
|
|
29
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
29
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
30
30
|
} catch {
|
|
31
31
|
// Ignore cleanup errors
|
|
32
32
|
}
|
|
@@ -39,14 +39,14 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('should describe index correctly', async () => {
|
|
42
|
-
const stats: IndexStats = await vectorDB.describeIndex(testIndexName);
|
|
42
|
+
const stats: IndexStats = await vectorDB.describeIndex({ indexName: testIndexName });
|
|
43
43
|
expect(stats.dimension).toBe(dimension);
|
|
44
44
|
expect(stats.count).toBe(0);
|
|
45
45
|
expect(stats.metric).toBe('cosine');
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('should delete index', async () => {
|
|
49
|
-
await vectorDB.deleteIndex(testIndexName);
|
|
49
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
50
50
|
const indexes = await vectorDB.listIndexes();
|
|
51
51
|
expect(indexes).not.toContain(testIndexName);
|
|
52
52
|
});
|
|
@@ -58,10 +58,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
58
58
|
const testIndex = `test-index-${metric}`;
|
|
59
59
|
await vectorDB.createIndex({ indexName: testIndex, dimension, metric });
|
|
60
60
|
|
|
61
|
-
const stats = await vectorDB.describeIndex(testIndex);
|
|
61
|
+
const stats = await vectorDB.describeIndex({ indexName: testIndex });
|
|
62
62
|
expect(stats.metric).toBe(metric);
|
|
63
63
|
|
|
64
|
-
await vectorDB.deleteIndex(testIndex);
|
|
64
|
+
await vectorDB.deleteIndex({ indexName: testIndex });
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
});
|
|
@@ -80,14 +80,14 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
80
80
|
expect(ids).toHaveLength(testVectors.length);
|
|
81
81
|
ids.forEach(id => expect(typeof id).toBe('string'));
|
|
82
82
|
|
|
83
|
-
const stats = await vectorDB.describeIndex(testIndexName);
|
|
83
|
+
const stats = await vectorDB.describeIndex({ indexName: testIndexName });
|
|
84
84
|
expect(stats.count).toBe(testVectors.length);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
it('should upsert vectors with provided ids and metadata', async () => {
|
|
88
88
|
await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
|
|
89
89
|
|
|
90
|
-
const stats = await vectorDB.describeIndex(testIndexName);
|
|
90
|
+
const stats = await vectorDB.describeIndex({ indexName: testIndexName });
|
|
91
91
|
expect(stats.count).toBe(testVectors.length);
|
|
92
92
|
|
|
93
93
|
// Query each vector to verify metadata
|
|
@@ -133,7 +133,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
133
133
|
metadata: newMetaData,
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
-
await vectorDB.
|
|
136
|
+
await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
|
|
137
137
|
|
|
138
138
|
const results: QueryResult[] = await vectorDB.query({
|
|
139
139
|
indexName: testIndexName,
|
|
@@ -159,7 +159,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
159
159
|
metadata: newMetaData,
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
-
await vectorDB.
|
|
162
|
+
await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
|
|
163
163
|
|
|
164
164
|
const results: QueryResult[] = await vectorDB.query({
|
|
165
165
|
indexName: testIndexName,
|
|
@@ -183,7 +183,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
183
183
|
vector: newVector,
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
await vectorDB.
|
|
186
|
+
await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
|
|
187
187
|
|
|
188
188
|
const results: QueryResult[] = await vectorDB.query({
|
|
189
189
|
indexName: testIndexName,
|
|
@@ -196,7 +196,9 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
it('should throw exception when no updates are given', async () => {
|
|
199
|
-
await expect(vectorDB.
|
|
199
|
+
await expect(vectorDB.updateVector({ indexName: testIndexName, id: 'id', update: {} })).rejects.toThrow(
|
|
200
|
+
'No updates provided',
|
|
201
|
+
);
|
|
200
202
|
});
|
|
201
203
|
|
|
202
204
|
it('should delete the vector by id', async () => {
|
|
@@ -204,7 +206,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
204
206
|
expect(ids).toHaveLength(3);
|
|
205
207
|
const idToBeDeleted = ids[0];
|
|
206
208
|
|
|
207
|
-
await vectorDB.
|
|
209
|
+
await vectorDB.deleteVector({ indexName: testIndexName, id: idToBeDeleted });
|
|
208
210
|
|
|
209
211
|
const results: QueryResult[] = await vectorDB.query({
|
|
210
212
|
indexName: testIndexName,
|
|
@@ -273,8 +275,17 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
273
275
|
});
|
|
274
276
|
|
|
275
277
|
describe('Error Handling', () => {
|
|
278
|
+
const testIndexName = 'test_index_error';
|
|
279
|
+
beforeAll(async () => {
|
|
280
|
+
await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
afterAll(async () => {
|
|
284
|
+
await vectorDB.deleteIndex({ indexName: testIndexName });
|
|
285
|
+
});
|
|
286
|
+
|
|
276
287
|
it('should handle non-existent index queries', async () => {
|
|
277
|
-
await expect(vectorDB.query({ indexName: 'non-existent-index
|
|
288
|
+
await expect(vectorDB.query({ indexName: 'non-existent-index', queryVector: [1, 2, 3] })).rejects.toThrow();
|
|
278
289
|
});
|
|
279
290
|
|
|
280
291
|
it('should handle invalid dimension vectors', async () => {
|
|
@@ -282,10 +293,59 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
282
293
|
await expect(vectorDB.upsert({ indexName: testIndexName, vectors: [invalidVector] })).rejects.toThrow();
|
|
283
294
|
});
|
|
284
295
|
|
|
285
|
-
it('should handle
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
296
|
+
it('should handle duplicate index creation gracefully', async () => {
|
|
297
|
+
const infoSpy = vi.spyOn(vectorDB['logger'], 'info');
|
|
298
|
+
const warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
|
|
299
|
+
|
|
300
|
+
const duplicateIndexName = `duplicate-test`;
|
|
301
|
+
const dimension = 768;
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Create index first time
|
|
305
|
+
await vectorDB.createIndex({
|
|
306
|
+
indexName: duplicateIndexName,
|
|
307
|
+
dimension,
|
|
308
|
+
metric: 'cosine',
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Try to create with same dimensions - should not throw
|
|
312
|
+
await expect(
|
|
313
|
+
vectorDB.createIndex({
|
|
314
|
+
indexName: duplicateIndexName,
|
|
315
|
+
dimension,
|
|
316
|
+
metric: 'cosine',
|
|
317
|
+
}),
|
|
318
|
+
).resolves.not.toThrow();
|
|
319
|
+
|
|
320
|
+
expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('already exists with'));
|
|
321
|
+
|
|
322
|
+
// Try to create with same dimensions and different metric - should not throw
|
|
323
|
+
await expect(
|
|
324
|
+
vectorDB.createIndex({
|
|
325
|
+
indexName: duplicateIndexName,
|
|
326
|
+
dimension,
|
|
327
|
+
metric: 'euclidean',
|
|
328
|
+
}),
|
|
329
|
+
).resolves.not.toThrow();
|
|
330
|
+
|
|
331
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Attempted to create index with metric'));
|
|
332
|
+
|
|
333
|
+
// Try to create with different dimensions - should throw
|
|
334
|
+
await expect(
|
|
335
|
+
vectorDB.createIndex({
|
|
336
|
+
indexName: duplicateIndexName,
|
|
337
|
+
dimension: dimension + 1,
|
|
338
|
+
metric: 'cosine',
|
|
339
|
+
}),
|
|
340
|
+
).rejects.toThrow(
|
|
341
|
+
`Index "${duplicateIndexName}" already exists with ${dimension} dimensions, but ${dimension + 1} dimensions were requested`,
|
|
342
|
+
);
|
|
343
|
+
} finally {
|
|
344
|
+
infoSpy.mockRestore();
|
|
345
|
+
warnSpy.mockRestore();
|
|
346
|
+
// Cleanup
|
|
347
|
+
await vectorDB.deleteIndex({ indexName: duplicateIndexName });
|
|
348
|
+
}
|
|
289
349
|
});
|
|
290
350
|
});
|
|
291
351
|
|
|
@@ -461,7 +521,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
461
521
|
// Set up test vectors and metadata
|
|
462
522
|
beforeAll(async () => {
|
|
463
523
|
try {
|
|
464
|
-
await vectorDB.deleteIndex(testIndexName2);
|
|
524
|
+
await vectorDB.deleteIndex({ indexName: testIndexName2 });
|
|
465
525
|
} catch {
|
|
466
526
|
// Ignore errors if index doesn't exist
|
|
467
527
|
}
|
|
@@ -509,7 +569,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
509
569
|
afterAll(async () => {
|
|
510
570
|
// Cleanup after tests
|
|
511
571
|
try {
|
|
512
|
-
await vectorDB.deleteIndex(testIndexName2);
|
|
572
|
+
await vectorDB.deleteIndex({ indexName: testIndexName2 });
|
|
513
573
|
} catch {
|
|
514
574
|
// Ignore cleanup errors
|
|
515
575
|
}
|
|
@@ -1180,7 +1240,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
1180
1240
|
|
|
1181
1241
|
beforeAll(async () => {
|
|
1182
1242
|
try {
|
|
1183
|
-
await vectorDB.deleteIndex(testIndexName3);
|
|
1243
|
+
await vectorDB.deleteIndex({ indexName: testIndexName3 });
|
|
1184
1244
|
} catch {
|
|
1185
1245
|
// Ignore errors if index doesn't exist
|
|
1186
1246
|
}
|
|
@@ -1214,7 +1274,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
1214
1274
|
afterAll(async () => {
|
|
1215
1275
|
// Cleanup after tests
|
|
1216
1276
|
try {
|
|
1217
|
-
await vectorDB.deleteIndex(testIndexName3);
|
|
1277
|
+
await vectorDB.deleteIndex({ indexName: testIndexName3 });
|
|
1218
1278
|
} catch {
|
|
1219
1279
|
// Ignore cleanup errors
|
|
1220
1280
|
}
|
|
@@ -1402,122 +1462,13 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
1402
1462
|
});
|
|
1403
1463
|
});
|
|
1404
1464
|
});
|
|
1405
|
-
describe('Deprecation Warnings', () => {
|
|
1406
|
-
const indexName = 'testdeprecationwarnings';
|
|
1407
|
-
|
|
1408
|
-
const indexName2 = 'testdeprecationwarnings2';
|
|
1409
|
-
|
|
1410
|
-
let warnSpy;
|
|
1411
|
-
|
|
1412
|
-
beforeAll(async () => {
|
|
1413
|
-
await vectorDB.createIndex({ indexName: indexName, dimension: 3 });
|
|
1414
|
-
});
|
|
1415
|
-
|
|
1416
|
-
afterAll(async () => {
|
|
1417
|
-
try {
|
|
1418
|
-
await vectorDB.deleteIndex(indexName);
|
|
1419
|
-
} catch {
|
|
1420
|
-
// Ignore errors if index doesn't exist
|
|
1421
|
-
}
|
|
1422
|
-
try {
|
|
1423
|
-
await vectorDB.deleteIndex(indexName2);
|
|
1424
|
-
} catch {
|
|
1425
|
-
// Ignore errors if index doesn't exist
|
|
1426
|
-
}
|
|
1427
|
-
});
|
|
1428
|
-
|
|
1429
|
-
beforeEach(async () => {
|
|
1430
|
-
warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
afterEach(async () => {
|
|
1434
|
-
warnSpy.mockRestore();
|
|
1435
|
-
try {
|
|
1436
|
-
await vectorDB.deleteIndex(indexName2);
|
|
1437
|
-
} catch {
|
|
1438
|
-
// Ignore errors if index doesn't exist
|
|
1439
|
-
}
|
|
1440
|
-
});
|
|
1441
|
-
|
|
1442
|
-
it('should show deprecation warning when using individual args for createIndex', async () => {
|
|
1443
|
-
await vectorDB.createIndex(indexName2, 3, 'cosine');
|
|
1444
|
-
|
|
1445
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
1446
|
-
expect.stringContaining('Deprecation Warning: Passing individual arguments to createIndex() is deprecated'),
|
|
1447
|
-
);
|
|
1448
|
-
});
|
|
1449
|
-
|
|
1450
|
-
it('should show deprecation warning when using individual args for upsert', async () => {
|
|
1451
|
-
await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
|
|
1452
|
-
|
|
1453
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
1454
|
-
expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
|
|
1455
|
-
);
|
|
1456
|
-
});
|
|
1457
|
-
|
|
1458
|
-
it('should show deprecation warning when using individual args for query', async () => {
|
|
1459
|
-
await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1460
|
-
|
|
1461
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
1462
|
-
expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
|
|
1463
|
-
);
|
|
1464
|
-
});
|
|
1465
|
-
|
|
1466
|
-
it('should not show deprecation warning when using object param for query', async () => {
|
|
1467
|
-
await vectorDB.query({
|
|
1468
|
-
indexName,
|
|
1469
|
-
queryVector: [1, 2, 3],
|
|
1470
|
-
topK: 5,
|
|
1471
|
-
});
|
|
1472
|
-
|
|
1473
|
-
expect(warnSpy).not.toHaveBeenCalled();
|
|
1474
|
-
});
|
|
1475
|
-
|
|
1476
|
-
it('should not show deprecation warning when using object param for createIndex', async () => {
|
|
1477
|
-
await vectorDB.createIndex({
|
|
1478
|
-
indexName: indexName2,
|
|
1479
|
-
dimension: 3,
|
|
1480
|
-
metric: 'cosine',
|
|
1481
|
-
});
|
|
1482
|
-
|
|
1483
|
-
expect(warnSpy).not.toHaveBeenCalled();
|
|
1484
|
-
});
|
|
1485
|
-
|
|
1486
|
-
it('should not show deprecation warning when using object param for upsert', async () => {
|
|
1487
|
-
await vectorDB.upsert({
|
|
1488
|
-
indexName,
|
|
1489
|
-
vectors: [[1, 2, 3]],
|
|
1490
|
-
metadata: [{ test: 'data' }],
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
expect(warnSpy).not.toHaveBeenCalled();
|
|
1494
|
-
});
|
|
1495
|
-
|
|
1496
|
-
it('should maintain backward compatibility with individual args', async () => {
|
|
1497
|
-
// Query
|
|
1498
|
-
const queryResults = await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1499
|
-
expect(Array.isArray(queryResults)).toBe(true);
|
|
1500
|
-
|
|
1501
|
-
// CreateIndex
|
|
1502
|
-
await expect(vectorDB.createIndex(indexName2, 3, 'cosine')).resolves.not.toThrow();
|
|
1503
|
-
|
|
1504
|
-
// Upsert
|
|
1505
|
-
const upsertResults = await vectorDB.upsert({
|
|
1506
|
-
indexName,
|
|
1507
|
-
vectors: [[1, 2, 3]],
|
|
1508
|
-
metadata: [{ test: 'data' }],
|
|
1509
|
-
});
|
|
1510
|
-
expect(Array.isArray(upsertResults)).toBe(true);
|
|
1511
|
-
expect(upsertResults).toHaveLength(1);
|
|
1512
|
-
});
|
|
1513
|
-
});
|
|
1514
1465
|
|
|
1515
1466
|
describe('Performance and Concurrency', () => {
|
|
1516
1467
|
const perfTestIndex = 'perf-test-index';
|
|
1517
1468
|
|
|
1518
1469
|
beforeEach(async () => {
|
|
1519
1470
|
try {
|
|
1520
|
-
await vectorDB.deleteIndex(perfTestIndex);
|
|
1471
|
+
await vectorDB.deleteIndex({ indexName: perfTestIndex });
|
|
1521
1472
|
} catch {
|
|
1522
1473
|
// Ignore errors if index doesn't exist
|
|
1523
1474
|
}
|
|
@@ -1526,7 +1477,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
1526
1477
|
|
|
1527
1478
|
afterEach(async () => {
|
|
1528
1479
|
try {
|
|
1529
|
-
await vectorDB.deleteIndex(perfTestIndex);
|
|
1480
|
+
await vectorDB.deleteIndex({ indexName: perfTestIndex });
|
|
1530
1481
|
} catch {
|
|
1531
1482
|
// Ignore cleanup errors
|
|
1532
1483
|
}
|
|
@@ -1569,7 +1520,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
1569
1520
|
});
|
|
1570
1521
|
|
|
1571
1522
|
// Verify all vectors were inserted
|
|
1572
|
-
const stats = await vectorDB.describeIndex(perfTestIndex);
|
|
1523
|
+
const stats = await vectorDB.describeIndex({ indexName: perfTestIndex });
|
|
1573
1524
|
expect(stats.count).toBe(batchSize);
|
|
1574
1525
|
|
|
1575
1526
|
const results = await vectorDB.query({
|
package/src/vector/index.ts
CHANGED
|
@@ -5,9 +5,10 @@ import type {
|
|
|
5
5
|
CreateIndexParams,
|
|
6
6
|
UpsertVectorParams,
|
|
7
7
|
QueryVectorParams,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
DescribeIndexParams,
|
|
9
|
+
DeleteIndexParams,
|
|
10
|
+
DeleteVectorParams,
|
|
11
|
+
UpdateVectorParams,
|
|
11
12
|
} from '@mastra/core/vector';
|
|
12
13
|
|
|
13
14
|
import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
@@ -19,14 +20,10 @@ interface ChromaUpsertVectorParams extends UpsertVectorParams {
|
|
|
19
20
|
documents?: string[];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
type ChromaUpsertArgs = [...UpsertVectorArgs, string[]?];
|
|
23
|
-
|
|
24
23
|
interface ChromaQueryVectorParams extends QueryVectorParams {
|
|
25
24
|
documentFilter?: VectorFilter;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
type ChromaQueryArgs = [...QueryVectorArgs, VectorFilter?];
|
|
29
|
-
|
|
30
27
|
export class ChromaVector extends MastraVector {
|
|
31
28
|
private client: ChromaClient;
|
|
32
29
|
private collections: Map<string, any>;
|
|
@@ -72,15 +69,11 @@ export class ChromaVector extends MastraVector {
|
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
async upsert(
|
|
76
|
-
const params = this.normalizeArgs<ChromaUpsertVectorParams, ChromaUpsertArgs>('upsert', args, ['documents']);
|
|
77
|
-
|
|
78
|
-
const { indexName, vectors, metadata, ids, documents } = params;
|
|
79
|
-
|
|
72
|
+
async upsert({ indexName, vectors, metadata, ids, documents }: ChromaUpsertVectorParams): Promise<string[]> {
|
|
80
73
|
const collection = await this.getCollection(indexName);
|
|
81
74
|
|
|
82
75
|
// Get index stats to check dimension
|
|
83
|
-
const stats = await this.describeIndex(indexName);
|
|
76
|
+
const stats = await this.describeIndex({ indexName });
|
|
84
77
|
|
|
85
78
|
// Validate vector dimensions
|
|
86
79
|
this.validateVectorDimensions(vectors, stats.dimension);
|
|
@@ -109,11 +102,7 @@ export class ChromaVector extends MastraVector {
|
|
|
109
102
|
ip: 'dotproduct',
|
|
110
103
|
};
|
|
111
104
|
|
|
112
|
-
async createIndex(
|
|
113
|
-
const params = this.normalizeArgs<CreateIndexParams>('createIndex', args);
|
|
114
|
-
|
|
115
|
-
const { indexName, dimension, metric = 'cosine' } = params;
|
|
116
|
-
|
|
105
|
+
async createIndex({ indexName, dimension, metric = 'cosine' }: CreateIndexParams): Promise<void> {
|
|
117
106
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
118
107
|
throw new Error('Dimension must be a positive integer');
|
|
119
108
|
}
|
|
@@ -121,24 +110,38 @@ export class ChromaVector extends MastraVector {
|
|
|
121
110
|
if (!['cosine', 'l2', 'ip'].includes(hnswSpace)) {
|
|
122
111
|
throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
|
|
123
112
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
try {
|
|
114
|
+
await this.client.createCollection({
|
|
115
|
+
name: indexName,
|
|
116
|
+
metadata: {
|
|
117
|
+
dimension,
|
|
118
|
+
'hnsw:space': hnswSpace,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
} catch (error: any) {
|
|
122
|
+
// Check for 'already exists' error
|
|
123
|
+
const message = error?.message || error?.toString();
|
|
124
|
+
if (message && message.toLowerCase().includes('already exists')) {
|
|
125
|
+
// Fetch collection info and check dimension
|
|
126
|
+
await this.validateExistingIndex(indexName, dimension, metric);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
transformFilter(filter?: VectorFilter) {
|
|
134
134
|
const translator = new ChromaFilterTranslator();
|
|
135
135
|
return translator.translate(filter);
|
|
136
136
|
}
|
|
137
|
-
async query(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
async query({
|
|
138
|
+
indexName,
|
|
139
|
+
queryVector,
|
|
140
|
+
topK = 10,
|
|
141
|
+
filter,
|
|
142
|
+
includeVector = false,
|
|
143
|
+
documentFilter,
|
|
144
|
+
}: ChromaQueryVectorParams): Promise<QueryResult[]> {
|
|
142
145
|
const collection = await this.getCollection(indexName, true);
|
|
143
146
|
|
|
144
147
|
const defaultInclude = ['documents', 'metadatas', 'distances'];
|
|
@@ -167,7 +170,13 @@ export class ChromaVector extends MastraVector {
|
|
|
167
170
|
return collections.map(collection => collection);
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves statistics about a vector index.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} indexName - The name of the index to describe
|
|
177
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
178
|
+
*/
|
|
179
|
+
async describeIndex({ indexName }: DescribeIndexParams): Promise<IndexStats> {
|
|
171
180
|
const collection = await this.getCollection(indexName);
|
|
172
181
|
const count = await collection.count();
|
|
173
182
|
const metadata = collection.metadata;
|
|
@@ -181,41 +190,60 @@ export class ChromaVector extends MastraVector {
|
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
async deleteIndex(indexName:
|
|
193
|
+
async deleteIndex({ indexName }: DeleteIndexParams): Promise<void> {
|
|
185
194
|
await this.client.deleteCollection({ name: indexName });
|
|
186
195
|
this.collections.delete(indexName);
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
200
|
+
* @param indexName - The name of the index containing the vector.
|
|
201
|
+
* @param id - The ID of the vector to update.
|
|
202
|
+
* @param update - An object containing the vector and/or metadata to update.
|
|
203
|
+
* @param update.vector - An optional array of numbers representing the new vector.
|
|
204
|
+
* @param update.metadata - An optional record containing the new metadata.
|
|
205
|
+
* @returns A promise that resolves when the update is complete.
|
|
206
|
+
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
207
|
+
*/
|
|
208
|
+
async updateVector({ indexName, id, update }: UpdateVectorParams): Promise<void> {
|
|
209
|
+
try {
|
|
210
|
+
if (!update.vector && !update.metadata) {
|
|
211
|
+
throw new Error('No updates provided');
|
|
212
|
+
}
|
|
197
213
|
|
|
198
|
-
|
|
214
|
+
const collection: Collection = await this.getCollection(indexName, true);
|
|
199
215
|
|
|
200
|
-
|
|
216
|
+
const updateOptions: UpdateRecordsParams = { ids: [id] };
|
|
201
217
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
if (update?.vector) {
|
|
219
|
+
const stats = await this.describeIndex({ indexName });
|
|
220
|
+
this.validateVectorDimensions([update.vector], stats.dimension);
|
|
221
|
+
updateOptions.embeddings = [update.vector];
|
|
222
|
+
}
|
|
205
223
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
if (update?.metadata) {
|
|
225
|
+
updateOptions.metadatas = [update.metadata];
|
|
226
|
+
}
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
return await collection.update(updateOptions);
|
|
229
|
+
} catch (error: any) {
|
|
230
|
+
throw new Error(`Failed to update vector by id: ${id} for index name: ${indexName}: ${error.message}`);
|
|
231
|
+
}
|
|
211
232
|
}
|
|
212
233
|
|
|
213
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Deletes a vector by its ID.
|
|
236
|
+
* @param indexName - The name of the index containing the vector.
|
|
237
|
+
* @param id - The ID of the vector to delete.
|
|
238
|
+
* @returns A promise that resolves when the deletion is complete.
|
|
239
|
+
* @throws Will throw an error if the deletion operation fails.
|
|
240
|
+
*/
|
|
241
|
+
async deleteVector({ indexName, id }: DeleteVectorParams): Promise<void> {
|
|
214
242
|
try {
|
|
215
243
|
const collection: Collection = await this.getCollection(indexName, true);
|
|
216
244
|
await collection.delete({ ids: [id] });
|
|
217
245
|
} catch (error: any) {
|
|
218
|
-
throw new Error(`Failed to delete
|
|
246
|
+
throw new Error(`Failed to delete vector by id: ${id} for index name: ${indexName}: ${error.message}`);
|
|
219
247
|
}
|
|
220
248
|
}
|
|
221
249
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector store specific prompt that details supported operators and examples.
|
|
3
|
+
* This prompt helps users construct valid filters for Chroma Vector.
|
|
4
|
+
*/
|
|
5
|
+
export const CHROMA_PROMPT = `When querying Chroma, you can ONLY use the operators listed below. Any other operators will be rejected.
|
|
6
|
+
Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
|
|
7
|
+
If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
|
|
8
|
+
|
|
9
|
+
Basic Comparison Operators:
|
|
10
|
+
- $eq: Exact match (default when using field: value)
|
|
11
|
+
Example: { "category": "electronics" }
|
|
12
|
+
- $ne: Not equal
|
|
13
|
+
Example: { "category": { "$ne": "electronics" } }
|
|
14
|
+
- $gt: Greater than
|
|
15
|
+
Example: { "price": { "$gt": 100 } }
|
|
16
|
+
- $gte: Greater than or equal
|
|
17
|
+
Example: { "price": { "$gte": 100 } }
|
|
18
|
+
- $lt: Less than
|
|
19
|
+
Example: { "price": { "$lt": 100 } }
|
|
20
|
+
- $lte: Less than or equal
|
|
21
|
+
Example: { "price": { "$lte": 100 } }
|
|
22
|
+
|
|
23
|
+
Array Operators:
|
|
24
|
+
- $in: Match any value in array
|
|
25
|
+
Example: { "category": { "$in": ["electronics", "books"] } }
|
|
26
|
+
- $nin: Does not match any value in array
|
|
27
|
+
Example: { "category": { "$nin": ["electronics", "books"] } }
|
|
28
|
+
|
|
29
|
+
Logical Operators:
|
|
30
|
+
- $and: Logical AND
|
|
31
|
+
Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
|
|
32
|
+
- $or: Logical OR
|
|
33
|
+
Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
|
|
34
|
+
|
|
35
|
+
Restrictions:
|
|
36
|
+
- Regex patterns are not supported
|
|
37
|
+
- Element operators are not supported
|
|
38
|
+
- Only $and and $or logical operators are supported
|
|
39
|
+
- Nested fields are supported using dot notation
|
|
40
|
+
- Multiple conditions on the same field are supported with both implicit and explicit $and
|
|
41
|
+
- Empty arrays in $in/$nin will return no results
|
|
42
|
+
- If multiple top-level fields exist, they're wrapped in $and
|
|
43
|
+
- Only logical operators ($and, $or) can be used at the top level
|
|
44
|
+
- All other operators must be used within a field condition
|
|
45
|
+
Valid: { "field": { "$gt": 100 } }
|
|
46
|
+
Valid: { "$and": [...] }
|
|
47
|
+
Invalid: { "$gt": 100 }
|
|
48
|
+
Invalid: { "$in": [...] }
|
|
49
|
+
- Logical operators must contain field conditions, not direct operators
|
|
50
|
+
Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
51
|
+
Invalid: { "$and": [{ "$gt": 100 }] }
|
|
52
|
+
- Logical operators ($and, $or):
|
|
53
|
+
- Can only be used at top level or nested within other logical operators
|
|
54
|
+
- Can not be used on a field level, or be nested inside a field
|
|
55
|
+
- Can not be used inside an operator
|
|
56
|
+
- Valid: { "$and": [{ "field": { "$gt": 100 } }] }
|
|
57
|
+
- Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
|
|
58
|
+
- Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
|
|
59
|
+
- Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
|
|
60
|
+
- Invalid: { "field": { "$gt": { "$and": [{...}] } } }
|
|
61
|
+
|
|
62
|
+
Example Complex Query:
|
|
63
|
+
{
|
|
64
|
+
"$and": [
|
|
65
|
+
{ "category": { "$in": ["electronics", "computers"] } },
|
|
66
|
+
{ "price": { "$gte": 100, "$lte": 1000 } },
|
|
67
|
+
{ "$or": [
|
|
68
|
+
{ "inStock": true },
|
|
69
|
+
{ "preorder": true }
|
|
70
|
+
]}
|
|
71
|
+
]
|
|
72
|
+
}`;
|