@mastra/chroma 1.1.1 → 1.1.2

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/index.js CHANGED
@@ -1,634 +1,517 @@
1
- import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { createVectorErrorId } from '@mastra/core/storage';
3
- import { MastraVector, validateUpsert, validateTopK } from '@mastra/core/vector';
4
- import { CloudClient, ChromaClient } from 'chromadb';
5
- import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
-
7
- // src/vector/index.ts
8
-
9
- // src/vector/distance-to-score.ts
1
+ import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
2
+ import { createVectorErrorId } from "@mastra/core/storage";
3
+ import { MastraVector, validateTopK, validateUpsert } from "@mastra/core/vector";
4
+ import { ChromaClient, CloudClient } from "chromadb";
5
+ import { BaseFilterTranslator } from "@mastra/core/vector/filter";
6
+ //#region src/vector/distance-to-score.ts
10
7
  function distanceToScore(distance, metric) {
11
- switch (metric) {
12
- case "euclidean":
13
- return 1 / (1 + Math.sqrt(distance));
14
- case "dotproduct":
15
- case "cosine":
16
- default:
17
- return 1 - distance;
18
- }
8
+ switch (metric) {
9
+ case "euclidean": return 1 / (1 + Math.sqrt(distance));
10
+ default: return 1 - distance;
11
+ }
19
12
  }
13
+ //#endregion
14
+ //#region src/vector/filter.ts
15
+ /**
16
+ * Translator for Chroma filter queries.
17
+ * Maintains MongoDB-compatible syntax while ensuring proper validation
18
+ * and normalization of values.
19
+ */
20
20
  var ChromaFilterTranslator = class extends BaseFilterTranslator {
21
- getSupportedOperators() {
22
- return {
23
- ...BaseFilterTranslator.DEFAULT_OPERATORS,
24
- logical: ["$and", "$or"],
25
- array: ["$in", "$nin"],
26
- element: [],
27
- regex: [],
28
- custom: []
29
- };
30
- }
31
- translate(filter) {
32
- if (this.isEmpty(filter)) return filter;
33
- this.validateFilter(filter);
34
- return this.translateNode(filter);
35
- }
36
- translateNode(node, currentPath = "") {
37
- if (this.isRegex(node)) {
38
- throw new Error("Regex is supported in Chroma via the `documentFilter` argument");
39
- }
40
- if (this.isPrimitive(node)) return this.normalizeComparisonValue(node);
41
- if (Array.isArray(node)) return { $in: this.normalizeArrayValues(node) };
42
- const entries = Object.entries(node);
43
- const firstEntry = entries[0];
44
- if (entries.length === 1 && firstEntry && this.isOperator(firstEntry[0])) {
45
- const [operator, value] = firstEntry;
46
- const translated = this.translateOperator(operator, value);
47
- if (this.isLogicalOperator(operator) && Array.isArray(translated) && translated.length === 1) {
48
- return translated[0];
49
- }
50
- return this.isLogicalOperator(operator) ? { [operator]: translated } : translated;
51
- }
52
- const result = {};
53
- const multiOperatorConditions = [];
54
- for (const [key, value] of entries) {
55
- const newPath = currentPath ? `${currentPath}.${key}` : key;
56
- if (this.isOperator(key)) {
57
- result[key] = this.translateOperator(key, value);
58
- continue;
59
- }
60
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
61
- const valueEntries = Object.entries(value);
62
- if (valueEntries.every(([op]) => this.isOperator(op)) && valueEntries.length > 1) {
63
- valueEntries.forEach(([op, opValue]) => {
64
- multiOperatorConditions.push({
65
- [newPath]: { [op]: this.normalizeComparisonValue(opValue) }
66
- });
67
- });
68
- continue;
69
- }
70
- if (Object.keys(value).length === 0) {
71
- result[newPath] = this.translateNode(value);
72
- } else {
73
- const hasOperators = Object.keys(value).some((k) => this.isOperator(k));
74
- if (hasOperators) {
75
- const normalizedValue = {};
76
- for (const [op, opValue] of Object.entries(value)) {
77
- normalizedValue[op] = this.isOperator(op) ? this.translateOperator(op, opValue) : opValue;
78
- }
79
- result[newPath] = normalizedValue;
80
- } else {
81
- Object.assign(result, this.translateNode(value, newPath));
82
- }
83
- }
84
- } else {
85
- result[newPath] = this.translateNode(value);
86
- }
87
- }
88
- if (multiOperatorConditions.length > 0) {
89
- const resultConditions = Object.entries(result).map(([key, value]) => ({ [key]: value }));
90
- const allConditions = [...multiOperatorConditions, ...resultConditions];
91
- if (allConditions.length === 1) {
92
- return allConditions[0];
93
- }
94
- return { $and: allConditions };
95
- }
96
- if (Object.keys(result).length > 1 && !currentPath) {
97
- return {
98
- $and: Object.entries(result).map(([key, value]) => ({ [key]: value }))
99
- };
100
- }
101
- return result;
102
- }
103
- translateOperator(operator, value) {
104
- if (this.isLogicalOperator(operator)) {
105
- return Array.isArray(value) ? value.map((item) => this.translateNode(item)) : this.translateNode(value);
106
- }
107
- return this.normalizeComparisonValue(value);
108
- }
21
+ getSupportedOperators() {
22
+ return {
23
+ ...BaseFilterTranslator.DEFAULT_OPERATORS,
24
+ logical: ["$and", "$or"],
25
+ array: ["$in", "$nin"],
26
+ element: [],
27
+ regex: [],
28
+ custom: []
29
+ };
30
+ }
31
+ translate(filter) {
32
+ if (this.isEmpty(filter)) return filter;
33
+ this.validateFilter(filter);
34
+ return this.translateNode(filter);
35
+ }
36
+ translateNode(node, currentPath = "") {
37
+ if (this.isRegex(node)) throw new Error("Regex is supported in Chroma via the `documentFilter` argument");
38
+ if (this.isPrimitive(node)) return this.normalizeComparisonValue(node);
39
+ if (Array.isArray(node)) return { $in: this.normalizeArrayValues(node) };
40
+ const entries = Object.entries(node);
41
+ const firstEntry = entries[0];
42
+ if (entries.length === 1 && firstEntry && this.isOperator(firstEntry[0])) {
43
+ const [operator, value] = firstEntry;
44
+ const translated = this.translateOperator(operator, value);
45
+ if (this.isLogicalOperator(operator) && Array.isArray(translated) && translated.length === 1) return translated[0];
46
+ return this.isLogicalOperator(operator) ? { [operator]: translated } : translated;
47
+ }
48
+ const result = {};
49
+ const multiOperatorConditions = [];
50
+ for (const [key, value] of entries) {
51
+ const newPath = currentPath ? `${currentPath}.${key}` : key;
52
+ if (this.isOperator(key)) {
53
+ result[key] = this.translateOperator(key, value);
54
+ continue;
55
+ }
56
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
57
+ const valueEntries = Object.entries(value);
58
+ if (valueEntries.every(([op]) => this.isOperator(op)) && valueEntries.length > 1) {
59
+ valueEntries.forEach(([op, opValue]) => {
60
+ multiOperatorConditions.push({ [newPath]: { [op]: this.normalizeComparisonValue(opValue) } });
61
+ });
62
+ continue;
63
+ }
64
+ if (Object.keys(value).length === 0) result[newPath] = this.translateNode(value);
65
+ else if (Object.keys(value).some((k) => this.isOperator(k))) {
66
+ const normalizedValue = {};
67
+ for (const [op, opValue] of Object.entries(value)) normalizedValue[op] = this.isOperator(op) ? this.translateOperator(op, opValue) : opValue;
68
+ result[newPath] = normalizedValue;
69
+ } else Object.assign(result, this.translateNode(value, newPath));
70
+ } else result[newPath] = this.translateNode(value);
71
+ }
72
+ if (multiOperatorConditions.length > 0) {
73
+ const resultConditions = Object.entries(result).map(([key, value]) => ({ [key]: value }));
74
+ const allConditions = [...multiOperatorConditions, ...resultConditions];
75
+ if (allConditions.length === 1) return allConditions[0];
76
+ return { $and: allConditions };
77
+ }
78
+ if (Object.keys(result).length > 1 && !currentPath) return { $and: Object.entries(result).map(([key, value]) => ({ [key]: value })) };
79
+ return result;
80
+ }
81
+ translateOperator(operator, value) {
82
+ if (this.isLogicalOperator(operator)) return Array.isArray(value) ? value.map((item) => this.translateNode(item)) : this.translateNode(value);
83
+ return this.normalizeComparisonValue(value);
84
+ }
109
85
  };
110
-
111
- // src/vector/index.ts
112
- var spaceMappings = {
113
- cosine: "cosine",
114
- euclidean: "l2",
115
- dotproduct: "ip",
116
- l2: "euclidean",
117
- ip: "dotproduct"
86
+ //#endregion
87
+ //#region src/vector/index.ts
88
+ const spaceMappings = {
89
+ cosine: "cosine",
90
+ euclidean: "l2",
91
+ dotproduct: "ip",
92
+ l2: "euclidean",
93
+ ip: "dotproduct"
118
94
  };
119
95
  var ChromaVector = class extends MastraVector {
120
- client;
121
- collections;
122
- constructor({ id, ...chromaClientArgs }) {
123
- super({ id });
124
- if (chromaClientArgs?.apiKey) {
125
- this.client = new CloudClient({
126
- apiKey: chromaClientArgs.apiKey,
127
- tenant: chromaClientArgs.tenant,
128
- database: chromaClientArgs.database
129
- });
130
- } else {
131
- this.client = new ChromaClient(chromaClientArgs);
132
- }
133
- this.collections = /* @__PURE__ */ new Map();
134
- }
135
- async getCollection({ indexName, forceUpdate = false }) {
136
- let collection = this.collections.get(indexName);
137
- if (forceUpdate || !collection) {
138
- try {
139
- collection = await this.client.getCollection({ name: indexName });
140
- this.collections.set(indexName, collection);
141
- return collection;
142
- } catch {
143
- throw new MastraError({
144
- id: createVectorErrorId("CHROMA", "GET_COLLECTION", "FAILED"),
145
- domain: ErrorDomain.MASTRA_VECTOR,
146
- category: ErrorCategory.THIRD_PARTY,
147
- details: { indexName }
148
- });
149
- }
150
- }
151
- return collection;
152
- }
153
- validateVectorDimensions(vectors, dimension) {
154
- for (let i = 0; i < vectors.length; i++) {
155
- if (vectors?.[i]?.length !== dimension) {
156
- throw new Error(
157
- `Vector at index ${i} has invalid dimension ${vectors?.[i]?.length}. Expected ${dimension} dimensions.`
158
- );
159
- }
160
- }
161
- }
162
- async upsert({ indexName, vectors, metadata, ids, documents }) {
163
- validateUpsert("CHROMA", vectors, metadata, ids, true);
164
- try {
165
- const collection = await this.getCollection({ indexName });
166
- const stats = await this.describeIndex({ indexName });
167
- this.validateVectorDimensions(vectors, stats.dimension);
168
- const generatedIds = ids || vectors.map(() => crypto.randomUUID());
169
- await collection.upsert({
170
- ids: generatedIds,
171
- embeddings: vectors,
172
- metadatas: metadata,
173
- documents
174
- });
175
- return generatedIds;
176
- } catch (error) {
177
- if (error instanceof MastraError) throw error;
178
- throw new MastraError(
179
- {
180
- id: createVectorErrorId("CHROMA", "UPSERT", "FAILED"),
181
- domain: ErrorDomain.MASTRA_VECTOR,
182
- category: ErrorCategory.THIRD_PARTY,
183
- details: { indexName }
184
- },
185
- error
186
- );
187
- }
188
- }
189
- async createIndex({ indexName, dimension, metric = "cosine" }) {
190
- if (!Number.isInteger(dimension) || dimension <= 0) {
191
- throw new MastraError({
192
- id: createVectorErrorId("CHROMA", "CREATE_INDEX", "INVALID_DIMENSION"),
193
- text: "Dimension must be a positive integer",
194
- domain: ErrorDomain.MASTRA_VECTOR,
195
- category: ErrorCategory.USER,
196
- details: { dimension }
197
- });
198
- }
199
- const hnswSpace = spaceMappings[metric];
200
- if (!hnswSpace || !["cosine", "l2", "ip"].includes(hnswSpace)) {
201
- throw new MastraError({
202
- id: createVectorErrorId("CHROMA", "CREATE_INDEX", "INVALID_METRIC"),
203
- text: `Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`,
204
- domain: ErrorDomain.MASTRA_VECTOR,
205
- category: ErrorCategory.USER,
206
- details: { metric }
207
- });
208
- }
209
- try {
210
- const collection = await this.client.createCollection({
211
- name: indexName,
212
- metadata: { dimension },
213
- configuration: { hnsw: { space: hnswSpace } },
214
- embeddingFunction: null
215
- });
216
- this.collections.set(indexName, collection);
217
- } catch (error) {
218
- const message = error?.message || error?.toString();
219
- if (message && message.toLowerCase().includes("already exists")) {
220
- await this.validateExistingIndex(indexName, dimension, metric);
221
- return;
222
- }
223
- throw new MastraError(
224
- {
225
- id: createVectorErrorId("CHROMA", "CREATE_INDEX", "FAILED"),
226
- domain: ErrorDomain.MASTRA_VECTOR,
227
- category: ErrorCategory.THIRD_PARTY,
228
- details: { indexName }
229
- },
230
- error
231
- );
232
- }
233
- }
234
- transformFilter(filter) {
235
- const translator = new ChromaFilterTranslator();
236
- const translatedFilter = translator.translate(filter);
237
- return translatedFilter ? translatedFilter : void 0;
238
- }
239
- async query({
240
- indexName,
241
- queryVector,
242
- topK = 10,
243
- filter,
244
- includeVector = false,
245
- documentFilter
246
- }) {
247
- if (!queryVector) {
248
- throw new MastraError({
249
- id: createVectorErrorId("CHROMA", "QUERY", "MISSING_VECTOR"),
250
- text: "queryVector is required for Chroma queries. Metadata-only queries are not supported by this vector store.",
251
- domain: ErrorDomain.MASTRA_VECTOR,
252
- category: ErrorCategory.USER,
253
- details: { indexName }
254
- });
255
- }
256
- validateTopK("CHROMA", topK);
257
- try {
258
- const collection = await this.getCollection({ indexName });
259
- const defaultInclude = ["documents", "metadatas", "distances"];
260
- const translatedFilter = this.transformFilter(filter);
261
- const results = await collection.query({
262
- queryEmbeddings: [queryVector],
263
- nResults: topK,
264
- where: translatedFilter ?? void 0,
265
- whereDocument: documentFilter ?? void 0,
266
- include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
267
- });
268
- const space = collection.configuration?.hnsw?.space || collection.configuration?.spann?.space || "cosine";
269
- const metric = spaceMappings[space];
270
- return (results.ids[0] || []).map((id, index) => {
271
- const distance = results.distances?.[0]?.[index];
272
- const score = distance == null ? 0 : distanceToScore(distance, metric);
273
- return {
274
- id,
275
- score,
276
- metadata: results.metadatas?.[0]?.[index] || {},
277
- document: results.documents?.[0]?.[index] ?? void 0,
278
- ...includeVector && { vector: results.embeddings?.[0]?.[index] || [] }
279
- };
280
- });
281
- } catch (error) {
282
- if (error instanceof MastraError) throw error;
283
- throw new MastraError(
284
- {
285
- id: createVectorErrorId("CHROMA", "QUERY", "FAILED"),
286
- domain: ErrorDomain.MASTRA_VECTOR,
287
- category: ErrorCategory.THIRD_PARTY,
288
- details: { indexName }
289
- },
290
- error
291
- );
292
- }
293
- }
294
- async hybridSearch({ indexName, search }) {
295
- try {
296
- const collection = await this.getCollection({ indexName });
297
- const results = await collection.search(search);
298
- return (results.rows()[0] ?? []).map((record) => ({
299
- id: record.id,
300
- score: record.score ?? 0,
301
- metadata: record.metadata ?? void 0,
302
- vector: record.embedding ?? void 0,
303
- document: record.document ?? void 0
304
- }));
305
- } catch (error) {
306
- if (error instanceof MastraError) throw error;
307
- throw new MastraError(
308
- {
309
- id: createVectorErrorId("CHROMA", "SEARCH_API", "FAILED"),
310
- domain: ErrorDomain.MASTRA_VECTOR,
311
- category: ErrorCategory.THIRD_PARTY,
312
- details: { indexName }
313
- },
314
- error
315
- );
316
- }
317
- }
318
- async get({
319
- indexName,
320
- ids,
321
- filter,
322
- includeVector = false,
323
- documentFilter,
324
- offset,
325
- limit
326
- }) {
327
- try {
328
- const collection = await this.getCollection({ indexName });
329
- const defaultInclude = ["documents", "metadatas"];
330
- const translatedFilter = this.transformFilter(filter);
331
- const result = await collection.get({
332
- ids,
333
- where: translatedFilter ?? void 0,
334
- whereDocument: documentFilter ?? void 0,
335
- offset,
336
- limit,
337
- include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
338
- });
339
- return result.rows();
340
- } catch (error) {
341
- if (error instanceof MastraError) throw error;
342
- throw new MastraError(
343
- {
344
- id: createVectorErrorId("CHROMA", "GET", "FAILED"),
345
- domain: ErrorDomain.MASTRA_VECTOR,
346
- category: ErrorCategory.THIRD_PARTY,
347
- details: { indexName }
348
- },
349
- error
350
- );
351
- }
352
- }
353
- async listIndexes() {
354
- try {
355
- const collections = await this.client.listCollections();
356
- return collections.map((collection) => collection.name);
357
- } catch (error) {
358
- throw new MastraError(
359
- {
360
- id: createVectorErrorId("CHROMA", "LIST_INDEXES", "FAILED"),
361
- domain: ErrorDomain.MASTRA_VECTOR,
362
- category: ErrorCategory.THIRD_PARTY
363
- },
364
- error
365
- );
366
- }
367
- }
368
- /**
369
- * Retrieves statistics about a vector index.
370
- *
371
- * @param {string} indexName - The name of the index to describe
372
- * @returns A promise that resolves to the index statistics including dimension, count and metric
373
- */
374
- async describeIndex({ indexName }) {
375
- try {
376
- const collection = await this.getCollection({ indexName });
377
- const count = await collection.count();
378
- const metadata = collection.metadata;
379
- const space = collection.configuration.hnsw?.space || collection.configuration.spann?.space || void 0;
380
- return {
381
- dimension: metadata?.dimension || 0,
382
- count,
383
- metric: space ? spaceMappings[space] : void 0
384
- };
385
- } catch (error) {
386
- if (error instanceof MastraError) throw error;
387
- throw new MastraError(
388
- {
389
- id: createVectorErrorId("CHROMA", "DESCRIBE_INDEX", "FAILED"),
390
- domain: ErrorDomain.MASTRA_VECTOR,
391
- category: ErrorCategory.THIRD_PARTY,
392
- details: { indexName }
393
- },
394
- error
395
- );
396
- }
397
- }
398
- async deleteIndex({ indexName }) {
399
- try {
400
- await this.client.deleteCollection({ name: indexName });
401
- this.collections.delete(indexName);
402
- } catch (error) {
403
- throw new MastraError(
404
- {
405
- id: createVectorErrorId("CHROMA", "DELETE_INDEX", "FAILED"),
406
- domain: ErrorDomain.MASTRA_VECTOR,
407
- category: ErrorCategory.THIRD_PARTY,
408
- details: { indexName }
409
- },
410
- error
411
- );
412
- }
413
- }
414
- async forkIndex({ indexName, newIndexName }) {
415
- try {
416
- const collection = await this.getCollection({ indexName, forceUpdate: true });
417
- const forkedCollection = await collection.fork({ name: newIndexName });
418
- this.collections.set(newIndexName, forkedCollection);
419
- } catch (error) {
420
- if (error instanceof MastraError) throw error;
421
- throw new MastraError(
422
- {
423
- id: createVectorErrorId("CHROMA", "FORK_INDEX", "FAILED"),
424
- domain: ErrorDomain.MASTRA_VECTOR,
425
- category: ErrorCategory.THIRD_PARTY,
426
- details: { indexName }
427
- },
428
- error
429
- );
430
- }
431
- }
432
- /**
433
- * Updates a vector by its ID or multiple vectors matching a filter.
434
- * @param indexName - The name of the index containing the vector(s).
435
- * @param id - The ID of the vector to update (mutually exclusive with filter).
436
- * @param filter - Filter to match multiple vectors to update (mutually exclusive with id).
437
- * @param update - An object containing the vector and/or metadata to update.
438
- * @param update.vector - An optional array of numbers representing the new vector.
439
- * @param update.metadata - An optional record containing the new metadata.
440
- * @returns A promise that resolves when the update is complete.
441
- * @throws Will throw an error if no updates are provided or if the update operation fails.
442
- */
443
- async updateVector({ indexName, id, filter, update }) {
444
- if (id && filter) {
445
- throw new MastraError({
446
- id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
447
- text: "Cannot specify both id and filter - they are mutually exclusive",
448
- domain: ErrorDomain.MASTRA_VECTOR,
449
- category: ErrorCategory.USER,
450
- details: { indexName }
451
- });
452
- }
453
- if (!id && !filter) {
454
- throw new MastraError({
455
- id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "NO_TARGET"),
456
- text: "Either id or filter must be provided",
457
- domain: ErrorDomain.MASTRA_VECTOR,
458
- category: ErrorCategory.USER,
459
- details: { indexName }
460
- });
461
- }
462
- if (!update.vector && !update.metadata) {
463
- throw new MastraError({
464
- id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "NO_PAYLOAD"),
465
- text: "No updates provided",
466
- domain: ErrorDomain.MASTRA_VECTOR,
467
- category: ErrorCategory.USER,
468
- details: {
469
- indexName,
470
- ...id && { id }
471
- }
472
- });
473
- }
474
- if (filter && Object.keys(filter).length === 0) {
475
- throw new MastraError({
476
- id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "EMPTY_FILTER"),
477
- text: "Filter cannot be an empty filter object",
478
- domain: ErrorDomain.MASTRA_VECTOR,
479
- category: ErrorCategory.USER,
480
- details: { indexName }
481
- });
482
- }
483
- try {
484
- const collection = await this.getCollection({ indexName });
485
- if (update?.vector) {
486
- const stats = await this.describeIndex({ indexName });
487
- this.validateVectorDimensions([update.vector], stats.dimension);
488
- }
489
- if (id) {
490
- const updateRecordSet = { ids: [id] };
491
- if (update?.vector) {
492
- updateRecordSet.embeddings = [update.vector];
493
- }
494
- if (update?.metadata) {
495
- updateRecordSet.metadatas = [update.metadata];
496
- }
497
- return await collection.update(updateRecordSet);
498
- } else if (filter) {
499
- const translatedFilter = this.transformFilter(filter);
500
- const matchingVectors = await collection.get({
501
- where: translatedFilter ?? void 0,
502
- include: ["embeddings", "metadatas"]
503
- });
504
- const vectorRows = matchingVectors.rows();
505
- if (vectorRows.length === 0) {
506
- return;
507
- }
508
- const updateRecordSet = {
509
- ids: vectorRows.map((row) => row.id)
510
- };
511
- if (update?.vector) {
512
- updateRecordSet.embeddings = vectorRows.map(() => update.vector);
513
- }
514
- if (update?.metadata) {
515
- updateRecordSet.metadatas = vectorRows.map(() => update.metadata);
516
- }
517
- return await collection.update(updateRecordSet);
518
- }
519
- } catch (error) {
520
- if (error instanceof MastraError) throw error;
521
- throw new MastraError(
522
- {
523
- id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "FAILED"),
524
- domain: ErrorDomain.MASTRA_VECTOR,
525
- category: ErrorCategory.THIRD_PARTY,
526
- details: {
527
- indexName,
528
- ...id && { id },
529
- ...filter && { filter: JSON.stringify(filter) }
530
- }
531
- },
532
- error
533
- );
534
- }
535
- }
536
- async deleteVector({ indexName, id }) {
537
- try {
538
- const collection = await this.getCollection({ indexName });
539
- await collection.delete({ ids: [id] });
540
- } catch (error) {
541
- if (error instanceof MastraError) throw error;
542
- throw new MastraError(
543
- {
544
- id: createVectorErrorId("CHROMA", "DELETE_VECTOR", "FAILED"),
545
- domain: ErrorDomain.MASTRA_VECTOR,
546
- category: ErrorCategory.THIRD_PARTY,
547
- details: {
548
- indexName,
549
- ...id && { id }
550
- }
551
- },
552
- error
553
- );
554
- }
555
- }
556
- /**
557
- * Deletes multiple vectors by IDs or filter.
558
- * @param indexName - The name of the index containing the vectors.
559
- * @param ids - Array of vector IDs to delete (mutually exclusive with filter).
560
- * @param filter - Filter to match vectors to delete (mutually exclusive with ids).
561
- * @returns A promise that resolves when the deletion is complete.
562
- * @throws Will throw an error if both ids and filter are provided, or if neither is provided.
563
- */
564
- async deleteVectors({ indexName, filter, ids }) {
565
- if (ids && filter) {
566
- throw new MastraError({
567
- id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
568
- text: "Cannot specify both ids and filter - they are mutually exclusive",
569
- domain: ErrorDomain.MASTRA_VECTOR,
570
- category: ErrorCategory.USER,
571
- details: { indexName }
572
- });
573
- }
574
- if (!ids && !filter) {
575
- throw new MastraError({
576
- id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "NO_TARGET"),
577
- text: "Either filter or ids must be provided",
578
- domain: ErrorDomain.MASTRA_VECTOR,
579
- category: ErrorCategory.USER,
580
- details: { indexName }
581
- });
582
- }
583
- if (ids && ids.length === 0) {
584
- throw new MastraError({
585
- id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "EMPTY_IDS"),
586
- text: "Cannot delete with empty ids array",
587
- domain: ErrorDomain.MASTRA_VECTOR,
588
- category: ErrorCategory.USER,
589
- details: { indexName }
590
- });
591
- }
592
- if (filter && Object.keys(filter).length === 0) {
593
- throw new MastraError({
594
- id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "EMPTY_FILTER"),
595
- text: "Cannot delete with empty filter object",
596
- domain: ErrorDomain.MASTRA_VECTOR,
597
- category: ErrorCategory.USER,
598
- details: { indexName }
599
- });
600
- }
601
- try {
602
- const collection = await this.getCollection({ indexName });
603
- if (ids) {
604
- await collection.delete({ ids });
605
- } else if (filter) {
606
- const translatedFilter = this.transformFilter(filter);
607
- await collection.delete({
608
- where: translatedFilter ?? void 0
609
- });
610
- }
611
- } catch (error) {
612
- if (error instanceof MastraError) throw error;
613
- throw new MastraError(
614
- {
615
- id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "FAILED"),
616
- domain: ErrorDomain.MASTRA_VECTOR,
617
- category: ErrorCategory.THIRD_PARTY,
618
- details: {
619
- indexName,
620
- ...filter && { filter: JSON.stringify(filter) },
621
- ...ids && { idsCount: ids.length }
622
- }
623
- },
624
- error
625
- );
626
- }
627
- }
96
+ client;
97
+ collections;
98
+ constructor({ id, ...chromaClientArgs }) {
99
+ super({ id });
100
+ if (chromaClientArgs?.apiKey) this.client = new CloudClient({
101
+ apiKey: chromaClientArgs.apiKey,
102
+ tenant: chromaClientArgs.tenant,
103
+ database: chromaClientArgs.database
104
+ });
105
+ else this.client = new ChromaClient(chromaClientArgs);
106
+ this.collections = /* @__PURE__ */ new Map();
107
+ }
108
+ async getCollection({ indexName, forceUpdate = false }) {
109
+ let collection = this.collections.get(indexName);
110
+ if (forceUpdate || !collection) try {
111
+ collection = await this.client.getCollection({ name: indexName });
112
+ this.collections.set(indexName, collection);
113
+ return collection;
114
+ } catch {
115
+ throw new MastraError({
116
+ id: createVectorErrorId("CHROMA", "GET_COLLECTION", "FAILED"),
117
+ domain: ErrorDomain.MASTRA_VECTOR,
118
+ category: ErrorCategory.THIRD_PARTY,
119
+ details: { indexName }
120
+ });
121
+ }
122
+ return collection;
123
+ }
124
+ validateVectorDimensions(vectors, dimension) {
125
+ for (let i = 0; i < vectors.length; i++) if (vectors?.[i]?.length !== dimension) throw new Error(`Vector at index ${i} has invalid dimension ${vectors?.[i]?.length}. Expected ${dimension} dimensions.`);
126
+ }
127
+ async upsert({ indexName, vectors, metadata, ids, documents }) {
128
+ validateUpsert("CHROMA", vectors, metadata, ids, true);
129
+ try {
130
+ const collection = await this.getCollection({ indexName });
131
+ const stats = await this.describeIndex({ indexName });
132
+ this.validateVectorDimensions(vectors, stats.dimension);
133
+ const generatedIds = ids || vectors.map(() => crypto.randomUUID());
134
+ await collection.upsert({
135
+ ids: generatedIds,
136
+ embeddings: vectors,
137
+ metadatas: metadata,
138
+ documents
139
+ });
140
+ return generatedIds;
141
+ } catch (error) {
142
+ if (error instanceof MastraError) throw error;
143
+ throw new MastraError({
144
+ id: createVectorErrorId("CHROMA", "UPSERT", "FAILED"),
145
+ domain: ErrorDomain.MASTRA_VECTOR,
146
+ category: ErrorCategory.THIRD_PARTY,
147
+ details: { indexName }
148
+ }, error);
149
+ }
150
+ }
151
+ async createIndex({ indexName, dimension, metric = "cosine" }) {
152
+ if (!Number.isInteger(dimension) || dimension <= 0) throw new MastraError({
153
+ id: createVectorErrorId("CHROMA", "CREATE_INDEX", "INVALID_DIMENSION"),
154
+ text: "Dimension must be a positive integer",
155
+ domain: ErrorDomain.MASTRA_VECTOR,
156
+ category: ErrorCategory.USER,
157
+ details: { dimension }
158
+ });
159
+ const hnswSpace = spaceMappings[metric];
160
+ if (!hnswSpace || ![
161
+ "cosine",
162
+ "l2",
163
+ "ip"
164
+ ].includes(hnswSpace)) throw new MastraError({
165
+ id: createVectorErrorId("CHROMA", "CREATE_INDEX", "INVALID_METRIC"),
166
+ text: `Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`,
167
+ domain: ErrorDomain.MASTRA_VECTOR,
168
+ category: ErrorCategory.USER,
169
+ details: { metric }
170
+ });
171
+ try {
172
+ const collection = await this.client.createCollection({
173
+ name: indexName,
174
+ metadata: { dimension },
175
+ configuration: { hnsw: { space: hnswSpace } },
176
+ embeddingFunction: null
177
+ });
178
+ this.collections.set(indexName, collection);
179
+ } catch (error) {
180
+ const message = error?.message || error?.toString();
181
+ if (message && message.toLowerCase().includes("already exists")) {
182
+ await this.validateExistingIndex(indexName, dimension, metric);
183
+ return;
184
+ }
185
+ throw new MastraError({
186
+ id: createVectorErrorId("CHROMA", "CREATE_INDEX", "FAILED"),
187
+ domain: ErrorDomain.MASTRA_VECTOR,
188
+ category: ErrorCategory.THIRD_PARTY,
189
+ details: { indexName }
190
+ }, error);
191
+ }
192
+ }
193
+ transformFilter(filter) {
194
+ const translatedFilter = new ChromaFilterTranslator().translate(filter);
195
+ return translatedFilter ? translatedFilter : void 0;
196
+ }
197
+ async query({ indexName, queryVector, topK = 10, filter, includeVector = false, documentFilter }) {
198
+ if (!queryVector) throw new MastraError({
199
+ id: createVectorErrorId("CHROMA", "QUERY", "MISSING_VECTOR"),
200
+ text: "queryVector is required for Chroma queries. Metadata-only queries are not supported by this vector store.",
201
+ domain: ErrorDomain.MASTRA_VECTOR,
202
+ category: ErrorCategory.USER,
203
+ details: { indexName }
204
+ });
205
+ validateTopK("CHROMA", topK);
206
+ try {
207
+ const collection = await this.getCollection({ indexName });
208
+ const defaultInclude = [
209
+ "documents",
210
+ "metadatas",
211
+ "distances"
212
+ ];
213
+ const translatedFilter = this.transformFilter(filter);
214
+ const results = await collection.query({
215
+ queryEmbeddings: [queryVector],
216
+ nResults: topK,
217
+ where: translatedFilter ?? void 0,
218
+ whereDocument: documentFilter ?? void 0,
219
+ include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
220
+ });
221
+ const space = collection.configuration?.hnsw?.space || collection.configuration?.spann?.space || "cosine";
222
+ const metric = spaceMappings[space];
223
+ return (results.ids[0] || []).map((id, index) => {
224
+ const distance = results.distances?.[0]?.[index];
225
+ return {
226
+ id,
227
+ score: distance == null ? 0 : distanceToScore(distance, metric),
228
+ metadata: results.metadatas?.[0]?.[index] || {},
229
+ document: results.documents?.[0]?.[index] ?? void 0,
230
+ ...includeVector && { vector: results.embeddings?.[0]?.[index] || [] }
231
+ };
232
+ });
233
+ } catch (error) {
234
+ if (error instanceof MastraError) throw error;
235
+ throw new MastraError({
236
+ id: createVectorErrorId("CHROMA", "QUERY", "FAILED"),
237
+ domain: ErrorDomain.MASTRA_VECTOR,
238
+ category: ErrorCategory.THIRD_PARTY,
239
+ details: { indexName }
240
+ }, error);
241
+ }
242
+ }
243
+ async hybridSearch({ indexName, search }) {
244
+ try {
245
+ return ((await (await this.getCollection({ indexName })).search(search)).rows()[0] ?? []).map((record) => ({
246
+ id: record.id,
247
+ score: record.score ?? 0,
248
+ metadata: record.metadata ?? void 0,
249
+ vector: record.embedding ?? void 0,
250
+ document: record.document ?? void 0
251
+ }));
252
+ } catch (error) {
253
+ if (error instanceof MastraError) throw error;
254
+ throw new MastraError({
255
+ id: createVectorErrorId("CHROMA", "SEARCH_API", "FAILED"),
256
+ domain: ErrorDomain.MASTRA_VECTOR,
257
+ category: ErrorCategory.THIRD_PARTY,
258
+ details: { indexName }
259
+ }, error);
260
+ }
261
+ }
262
+ async get({ indexName, ids, filter, includeVector = false, documentFilter, offset, limit }) {
263
+ try {
264
+ const collection = await this.getCollection({ indexName });
265
+ const defaultInclude = ["documents", "metadatas"];
266
+ const translatedFilter = this.transformFilter(filter);
267
+ return (await collection.get({
268
+ ids,
269
+ where: translatedFilter ?? void 0,
270
+ whereDocument: documentFilter ?? void 0,
271
+ offset,
272
+ limit,
273
+ include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
274
+ })).rows();
275
+ } catch (error) {
276
+ if (error instanceof MastraError) throw error;
277
+ throw new MastraError({
278
+ id: createVectorErrorId("CHROMA", "GET", "FAILED"),
279
+ domain: ErrorDomain.MASTRA_VECTOR,
280
+ category: ErrorCategory.THIRD_PARTY,
281
+ details: { indexName }
282
+ }, error);
283
+ }
284
+ }
285
+ async listIndexes() {
286
+ try {
287
+ return (await this.client.listCollections()).map((collection) => collection.name);
288
+ } catch (error) {
289
+ throw new MastraError({
290
+ id: createVectorErrorId("CHROMA", "LIST_INDEXES", "FAILED"),
291
+ domain: ErrorDomain.MASTRA_VECTOR,
292
+ category: ErrorCategory.THIRD_PARTY
293
+ }, error);
294
+ }
295
+ }
296
+ /**
297
+ * Retrieves statistics about a vector index.
298
+ *
299
+ * @param {string} indexName - The name of the index to describe
300
+ * @returns A promise that resolves to the index statistics including dimension, count and metric
301
+ */
302
+ async describeIndex({ indexName }) {
303
+ try {
304
+ const collection = await this.getCollection({ indexName });
305
+ const count = await collection.count();
306
+ const metadata = collection.metadata;
307
+ const space = collection.configuration.hnsw?.space || collection.configuration.spann?.space || void 0;
308
+ return {
309
+ dimension: metadata?.dimension || 0,
310
+ count,
311
+ metric: space ? spaceMappings[space] : void 0
312
+ };
313
+ } catch (error) {
314
+ if (error instanceof MastraError) throw error;
315
+ throw new MastraError({
316
+ id: createVectorErrorId("CHROMA", "DESCRIBE_INDEX", "FAILED"),
317
+ domain: ErrorDomain.MASTRA_VECTOR,
318
+ category: ErrorCategory.THIRD_PARTY,
319
+ details: { indexName }
320
+ }, error);
321
+ }
322
+ }
323
+ async deleteIndex({ indexName }) {
324
+ try {
325
+ await this.client.deleteCollection({ name: indexName });
326
+ this.collections.delete(indexName);
327
+ } catch (error) {
328
+ throw new MastraError({
329
+ id: createVectorErrorId("CHROMA", "DELETE_INDEX", "FAILED"),
330
+ domain: ErrorDomain.MASTRA_VECTOR,
331
+ category: ErrorCategory.THIRD_PARTY,
332
+ details: { indexName }
333
+ }, error);
334
+ }
335
+ }
336
+ async forkIndex({ indexName, newIndexName }) {
337
+ try {
338
+ const forkedCollection = await (await this.getCollection({
339
+ indexName,
340
+ forceUpdate: true
341
+ })).fork({ name: newIndexName });
342
+ this.collections.set(newIndexName, forkedCollection);
343
+ } catch (error) {
344
+ if (error instanceof MastraError) throw error;
345
+ throw new MastraError({
346
+ id: createVectorErrorId("CHROMA", "FORK_INDEX", "FAILED"),
347
+ domain: ErrorDomain.MASTRA_VECTOR,
348
+ category: ErrorCategory.THIRD_PARTY,
349
+ details: { indexName }
350
+ }, error);
351
+ }
352
+ }
353
+ /**
354
+ * Updates a vector by its ID or multiple vectors matching a filter.
355
+ * @param indexName - The name of the index containing the vector(s).
356
+ * @param id - The ID of the vector to update (mutually exclusive with filter).
357
+ * @param filter - Filter to match multiple vectors to update (mutually exclusive with id).
358
+ * @param update - An object containing the vector and/or metadata to update.
359
+ * @param update.vector - An optional array of numbers representing the new vector.
360
+ * @param update.metadata - An optional record containing the new metadata.
361
+ * @returns A promise that resolves when the update is complete.
362
+ * @throws Will throw an error if no updates are provided or if the update operation fails.
363
+ */
364
+ async updateVector({ indexName, id, filter, update }) {
365
+ if (id && filter) throw new MastraError({
366
+ id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
367
+ text: "Cannot specify both id and filter - they are mutually exclusive",
368
+ domain: ErrorDomain.MASTRA_VECTOR,
369
+ category: ErrorCategory.USER,
370
+ details: { indexName }
371
+ });
372
+ if (!id && !filter) throw new MastraError({
373
+ id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "NO_TARGET"),
374
+ text: "Either id or filter must be provided",
375
+ domain: ErrorDomain.MASTRA_VECTOR,
376
+ category: ErrorCategory.USER,
377
+ details: { indexName }
378
+ });
379
+ if (!update.vector && !update.metadata) throw new MastraError({
380
+ id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "NO_PAYLOAD"),
381
+ text: "No updates provided",
382
+ domain: ErrorDomain.MASTRA_VECTOR,
383
+ category: ErrorCategory.USER,
384
+ details: {
385
+ indexName,
386
+ ...id && { id }
387
+ }
388
+ });
389
+ if (filter && Object.keys(filter).length === 0) throw new MastraError({
390
+ id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "EMPTY_FILTER"),
391
+ text: "Filter cannot be an empty filter object",
392
+ domain: ErrorDomain.MASTRA_VECTOR,
393
+ category: ErrorCategory.USER,
394
+ details: { indexName }
395
+ });
396
+ try {
397
+ const collection = await this.getCollection({ indexName });
398
+ if (update?.vector) {
399
+ const stats = await this.describeIndex({ indexName });
400
+ this.validateVectorDimensions([update.vector], stats.dimension);
401
+ }
402
+ if (id) {
403
+ const updateRecordSet = { ids: [id] };
404
+ if (update?.vector) updateRecordSet.embeddings = [update.vector];
405
+ if (update?.metadata) updateRecordSet.metadatas = [update.metadata];
406
+ return await collection.update(updateRecordSet);
407
+ } else if (filter) {
408
+ const translatedFilter = this.transformFilter(filter);
409
+ const vectorRows = (await collection.get({
410
+ where: translatedFilter ?? void 0,
411
+ include: ["embeddings", "metadatas"]
412
+ })).rows();
413
+ if (vectorRows.length === 0) return;
414
+ const updateRecordSet = { ids: vectorRows.map((row) => row.id) };
415
+ if (update?.vector) updateRecordSet.embeddings = vectorRows.map(() => update.vector);
416
+ if (update?.metadata) updateRecordSet.metadatas = vectorRows.map(() => update.metadata);
417
+ return await collection.update(updateRecordSet);
418
+ }
419
+ } catch (error) {
420
+ if (error instanceof MastraError) throw error;
421
+ throw new MastraError({
422
+ id: createVectorErrorId("CHROMA", "UPDATE_VECTOR", "FAILED"),
423
+ domain: ErrorDomain.MASTRA_VECTOR,
424
+ category: ErrorCategory.THIRD_PARTY,
425
+ details: {
426
+ indexName,
427
+ ...id && { id },
428
+ ...filter && { filter: JSON.stringify(filter) }
429
+ }
430
+ }, error);
431
+ }
432
+ }
433
+ async deleteVector({ indexName, id }) {
434
+ try {
435
+ await (await this.getCollection({ indexName })).delete({ ids: [id] });
436
+ } catch (error) {
437
+ if (error instanceof MastraError) throw error;
438
+ throw new MastraError({
439
+ id: createVectorErrorId("CHROMA", "DELETE_VECTOR", "FAILED"),
440
+ domain: ErrorDomain.MASTRA_VECTOR,
441
+ category: ErrorCategory.THIRD_PARTY,
442
+ details: {
443
+ indexName,
444
+ ...id && { id }
445
+ }
446
+ }, error);
447
+ }
448
+ }
449
+ /**
450
+ * Deletes multiple vectors by IDs or filter.
451
+ * @param indexName - The name of the index containing the vectors.
452
+ * @param ids - Array of vector IDs to delete (mutually exclusive with filter).
453
+ * @param filter - Filter to match vectors to delete (mutually exclusive with ids).
454
+ * @returns A promise that resolves when the deletion is complete.
455
+ * @throws Will throw an error if both ids and filter are provided, or if neither is provided.
456
+ */
457
+ async deleteVectors({ indexName, filter, ids }) {
458
+ if (ids && filter) throw new MastraError({
459
+ id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
460
+ text: "Cannot specify both ids and filter - they are mutually exclusive",
461
+ domain: ErrorDomain.MASTRA_VECTOR,
462
+ category: ErrorCategory.USER,
463
+ details: { indexName }
464
+ });
465
+ if (!ids && !filter) throw new MastraError({
466
+ id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "NO_TARGET"),
467
+ text: "Either filter or ids must be provided",
468
+ domain: ErrorDomain.MASTRA_VECTOR,
469
+ category: ErrorCategory.USER,
470
+ details: { indexName }
471
+ });
472
+ if (ids && ids.length === 0) throw new MastraError({
473
+ id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "EMPTY_IDS"),
474
+ text: "Cannot delete with empty ids array",
475
+ domain: ErrorDomain.MASTRA_VECTOR,
476
+ category: ErrorCategory.USER,
477
+ details: { indexName }
478
+ });
479
+ if (filter && Object.keys(filter).length === 0) throw new MastraError({
480
+ id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "EMPTY_FILTER"),
481
+ text: "Cannot delete with empty filter object",
482
+ domain: ErrorDomain.MASTRA_VECTOR,
483
+ category: ErrorCategory.USER,
484
+ details: { indexName }
485
+ });
486
+ try {
487
+ const collection = await this.getCollection({ indexName });
488
+ if (ids) await collection.delete({ ids });
489
+ else if (filter) {
490
+ const translatedFilter = this.transformFilter(filter);
491
+ await collection.delete({ where: translatedFilter ?? void 0 });
492
+ }
493
+ } catch (error) {
494
+ if (error instanceof MastraError) throw error;
495
+ throw new MastraError({
496
+ id: createVectorErrorId("CHROMA", "DELETE_VECTORS", "FAILED"),
497
+ domain: ErrorDomain.MASTRA_VECTOR,
498
+ category: ErrorCategory.THIRD_PARTY,
499
+ details: {
500
+ indexName,
501
+ ...filter && { filter: JSON.stringify(filter) },
502
+ ...ids && { idsCount: ids.length }
503
+ }
504
+ }, error);
505
+ }
506
+ }
628
507
  };
629
-
630
- // src/vector/prompt.ts
631
- var CHROMA_PROMPT = `When querying Chroma, you can ONLY use the operators listed below. Any other operators will be rejected.
508
+ //#endregion
509
+ //#region src/vector/prompt.ts
510
+ /**
511
+ * Vector store specific prompt that details supported operators and examples.
512
+ * This prompt helps users construct valid filters for Chroma Vector.
513
+ */
514
+ const CHROMA_PROMPT = `When querying Chroma, you can ONLY use the operators listed below. Any other operators will be rejected.
632
515
  Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
633
516
  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.
634
517
 
@@ -696,7 +579,7 @@ Example Complex Query:
696
579
  ]}
697
580
  ]
698
581
  }`;
699
-
582
+ //#endregion
700
583
  export { CHROMA_PROMPT, ChromaVector };
701
- //# sourceMappingURL=index.js.map
584
+
702
585
  //# sourceMappingURL=index.js.map