@mastra/qdrant 1.0.0-beta.4 → 1.0.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 +98 -0
- package/README.md +66 -10
- package/dist/docs/README.md +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/SOURCE_MAP.json +1 -1
- package/dist/docs/rag/01-vector-databases.md +3 -3
- package/dist/docs/vectors/01-reference.md +47 -0
- package/dist/index.cjs +196 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +197 -36
- package/dist/index.js.map +1 -1
- package/dist/vector/filter.d.ts +2 -1
- package/dist/vector/filter.d.ts.map +1 -1
- package/dist/vector/index.d.ts +110 -6
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -15,11 +15,15 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
15
15
|
return {
|
|
16
16
|
...filter.BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
17
17
|
logical: ["$and", "$or", "$not"],
|
|
18
|
-
array: ["$in", "$nin"],
|
|
18
|
+
array: ["$in", "$nin", "$all"],
|
|
19
19
|
regex: ["$regex"],
|
|
20
|
+
element: ["$exists"],
|
|
20
21
|
custom: ["$count", "$geo", "$nested", "$datetime", "$null", "$empty", "$hasId", "$hasVector"]
|
|
21
22
|
};
|
|
22
23
|
}
|
|
24
|
+
isOperator(key) {
|
|
25
|
+
return super.isOperator(key) || key === "$not";
|
|
26
|
+
}
|
|
23
27
|
translate(filter) {
|
|
24
28
|
if (this.isEmpty(filter)) return filter;
|
|
25
29
|
this.validateFilter(filter);
|
|
@@ -46,7 +50,7 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
46
50
|
return node.length === 0 ? { is_empty: { key: fieldKey } } : this.createCondition("match", { any: this.normalizeArrayValues(node) }, fieldKey);
|
|
47
51
|
}
|
|
48
52
|
const entries = Object.entries(node);
|
|
49
|
-
const logicalResult = this.handleLogicalOperators(entries, isNested);
|
|
53
|
+
const logicalResult = this.handleLogicalOperators(entries, isNested, fieldKey);
|
|
50
54
|
if (logicalResult) {
|
|
51
55
|
return logicalResult;
|
|
52
56
|
}
|
|
@@ -68,8 +72,11 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
68
72
|
return { must: conditions };
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
handleLogicalOperators(entries, isNested) {
|
|
75
|
+
handleLogicalOperators(entries, isNested, fieldKey) {
|
|
72
76
|
const firstKey = entries[0]?.[0];
|
|
77
|
+
if (firstKey === "$not" && fieldKey) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
73
80
|
if (firstKey && this.isLogicalOperator(firstKey) && !this.isCustomOperator(firstKey)) {
|
|
74
81
|
const [key, value] = entries[0];
|
|
75
82
|
const qdrantOp = this.getQdrantLogicalOp(key);
|
|
@@ -94,7 +101,34 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
94
101
|
conditions.push(customOp);
|
|
95
102
|
} else if (this.isOperator(key)) {
|
|
96
103
|
const opResult = this.translateOperatorValue(key, value);
|
|
97
|
-
if (opResult.
|
|
104
|
+
if (opResult._specialNull) {
|
|
105
|
+
conditions.push({ is_null: { key: fieldKey } });
|
|
106
|
+
} else if (opResult._specialNotNull) {
|
|
107
|
+
conditions.push({ must_not: [{ is_null: { key: fieldKey } }] });
|
|
108
|
+
} else if (opResult._specialNe) {
|
|
109
|
+
conditions.push({
|
|
110
|
+
must_not: [{ key: fieldKey, match: { value: opResult._specialNe } }]
|
|
111
|
+
});
|
|
112
|
+
} else if (opResult._specialNin) {
|
|
113
|
+
conditions.push({
|
|
114
|
+
must_not: [{ key: fieldKey, match: { any: opResult._specialNin } }]
|
|
115
|
+
});
|
|
116
|
+
} else if (opResult._specialAll) {
|
|
117
|
+
for (const val of opResult._specialAll) {
|
|
118
|
+
conditions.push({ key: fieldKey, match: { value: val } });
|
|
119
|
+
}
|
|
120
|
+
} else if (opResult._specialExists) {
|
|
121
|
+
conditions.push({
|
|
122
|
+
must_not: [{ is_null: { key: fieldKey } }, { is_empty: { key: fieldKey } }]
|
|
123
|
+
});
|
|
124
|
+
} else if (opResult._specialNotExists) {
|
|
125
|
+
conditions.push({
|
|
126
|
+
should: [{ is_null: { key: fieldKey } }, { is_empty: { key: fieldKey } }]
|
|
127
|
+
});
|
|
128
|
+
} else if (opResult._specialNot) {
|
|
129
|
+
const innerResult = this.translateNode(opResult._specialNot, true, fieldKey);
|
|
130
|
+
conditions.push({ must_not: [innerResult] });
|
|
131
|
+
} else if (opResult.range) {
|
|
98
132
|
Object.assign(range, opResult.range);
|
|
99
133
|
} else {
|
|
100
134
|
matchCondition = opResult;
|
|
@@ -165,9 +199,15 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
165
199
|
const normalizedValue = this.normalizeComparisonValue(value);
|
|
166
200
|
switch (operator) {
|
|
167
201
|
case "$eq":
|
|
202
|
+
if (value === null) {
|
|
203
|
+
return { _specialNull: true };
|
|
204
|
+
}
|
|
168
205
|
return { value: normalizedValue };
|
|
169
206
|
case "$ne":
|
|
170
|
-
|
|
207
|
+
if (value === null) {
|
|
208
|
+
return { _specialNotNull: true };
|
|
209
|
+
}
|
|
210
|
+
return { _specialNe: normalizedValue };
|
|
171
211
|
case "$gt":
|
|
172
212
|
return { range: { gt: normalizedValue } };
|
|
173
213
|
case "$gte":
|
|
@@ -179,15 +219,15 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
179
219
|
case "$in":
|
|
180
220
|
return { any: this.normalizeArrayValues(value) };
|
|
181
221
|
case "$nin":
|
|
182
|
-
return {
|
|
222
|
+
return { _specialNin: this.normalizeArrayValues(value) };
|
|
183
223
|
case "$regex":
|
|
184
224
|
return { text: value };
|
|
185
|
-
case "
|
|
186
|
-
return value
|
|
187
|
-
|
|
188
|
-
} : {
|
|
189
|
-
|
|
190
|
-
};
|
|
225
|
+
case "$all":
|
|
226
|
+
return { _specialAll: this.normalizeArrayValues(value) };
|
|
227
|
+
case "$exists":
|
|
228
|
+
return value ? { _specialExists: true } : { _specialNotExists: true };
|
|
229
|
+
case "$not":
|
|
230
|
+
return { _specialNot: value };
|
|
191
231
|
default:
|
|
192
232
|
throw new Error(`Unsupported operator: ${operator}`);
|
|
193
233
|
}
|
|
@@ -252,18 +292,55 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
252
292
|
super({ id });
|
|
253
293
|
this.client = new jsClientRest.QdrantClient(qdrantParams);
|
|
254
294
|
}
|
|
255
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Validates that a named vector exists in the collection.
|
|
297
|
+
* @param indexName - The name of the collection to check.
|
|
298
|
+
* @param vectorName - The name of the vector space to validate.
|
|
299
|
+
* @throws Error if the vector name doesn't exist in the collection.
|
|
300
|
+
*/
|
|
301
|
+
async validateVectorName(indexName, vectorName) {
|
|
302
|
+
const { config } = await this.client.getCollection(indexName);
|
|
303
|
+
const vectorsConfig = config.params.vectors;
|
|
304
|
+
const isNamedVectors = vectorsConfig && typeof vectorsConfig === "object" && !("size" in vectorsConfig);
|
|
305
|
+
if (!isNamedVectors || !(vectorName in vectorsConfig)) {
|
|
306
|
+
throw new Error(`Vector name "${vectorName}" does not exist in collection "${indexName}"`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Upserts vectors into the index.
|
|
311
|
+
* @param indexName - The name of the index to upsert into.
|
|
312
|
+
* @param vectors - Array of embedding vectors.
|
|
313
|
+
* @param metadata - Optional metadata for each vector.
|
|
314
|
+
* @param ids - Optional vector IDs (auto-generated if not provided).
|
|
315
|
+
* @param vectorName - Optional name of the vector space when using named vectors.
|
|
316
|
+
*/
|
|
317
|
+
async upsert({ indexName, vectors, metadata, ids, vectorName }) {
|
|
318
|
+
vector.validateUpsertInput("QDRANT", vectors, metadata, ids);
|
|
256
319
|
const pointIds = ids || vectors.map(() => crypto.randomUUID());
|
|
320
|
+
if (vectorName) {
|
|
321
|
+
try {
|
|
322
|
+
await this.validateVectorName(indexName, vectorName);
|
|
323
|
+
} catch (validationError) {
|
|
324
|
+
throw new error.MastraError(
|
|
325
|
+
{
|
|
326
|
+
id: storage.createVectorErrorId("QDRANT", "UPSERT", "INVALID_VECTOR_NAME"),
|
|
327
|
+
domain: error.ErrorDomain.STORAGE,
|
|
328
|
+
category: error.ErrorCategory.USER,
|
|
329
|
+
details: { indexName, vectorName }
|
|
330
|
+
},
|
|
331
|
+
validationError
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
257
335
|
const records = vectors.map((vector, i) => ({
|
|
258
336
|
id: pointIds[i],
|
|
259
|
-
vector,
|
|
337
|
+
vector: vectorName ? { [vectorName]: vector } : vector,
|
|
260
338
|
payload: metadata?.[i] || {}
|
|
261
339
|
}));
|
|
262
340
|
try {
|
|
263
341
|
for (let i = 0; i < records.length; i += BATCH_SIZE) {
|
|
264
342
|
const batch = records.slice(i, i + BATCH_SIZE);
|
|
265
343
|
await this.client.upsert(indexName, {
|
|
266
|
-
// @ts-expect-error
|
|
267
344
|
points: batch,
|
|
268
345
|
wait: true
|
|
269
346
|
});
|
|
@@ -275,19 +352,60 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
275
352
|
id: storage.createVectorErrorId("QDRANT", "UPSERT", "FAILED"),
|
|
276
353
|
domain: error.ErrorDomain.STORAGE,
|
|
277
354
|
category: error.ErrorCategory.THIRD_PARTY,
|
|
278
|
-
details: { indexName, vectorCount: vectors.length }
|
|
355
|
+
details: { indexName, vectorCount: vectors.length, ...vectorName && { vectorName } }
|
|
279
356
|
},
|
|
280
357
|
error$1
|
|
281
358
|
);
|
|
282
359
|
}
|
|
283
360
|
}
|
|
284
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Creates a new index (collection) in Qdrant.
|
|
363
|
+
* Supports both single vector and named vector configurations.
|
|
364
|
+
*
|
|
365
|
+
* @param indexName - The name of the collection to create.
|
|
366
|
+
* @param dimension - Vector dimension (required for single vector mode).
|
|
367
|
+
* @param metric - Distance metric (default: 'cosine').
|
|
368
|
+
* @param namedVectors - Optional named vector configurations for multi-vector collections.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* // Single vector collection
|
|
373
|
+
* await qdrant.createIndex({ indexName: 'docs', dimension: 768, metric: 'cosine' });
|
|
374
|
+
*
|
|
375
|
+
* // Named vectors collection
|
|
376
|
+
* await qdrant.createIndex({
|
|
377
|
+
* indexName: 'multi-modal',
|
|
378
|
+
* dimension: 768, // Used as fallback, can be omitted with namedVectors
|
|
379
|
+
* namedVectors: {
|
|
380
|
+
* text: { size: 768, distance: 'cosine' },
|
|
381
|
+
* image: { size: 512, distance: 'euclidean' },
|
|
382
|
+
* },
|
|
383
|
+
* });
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
async createIndex({ indexName, dimension, metric = "cosine", namedVectors }) {
|
|
285
387
|
try {
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
388
|
+
if (namedVectors) {
|
|
389
|
+
if (Object.keys(namedVectors).length === 0) {
|
|
390
|
+
throw new Error("namedVectors must contain at least one named vector configuration");
|
|
391
|
+
}
|
|
392
|
+
for (const [name, config] of Object.entries(namedVectors)) {
|
|
393
|
+
if (!Number.isInteger(config.size) || config.size <= 0) {
|
|
394
|
+
throw new Error(`Named vector "${name}": size must be a positive integer`);
|
|
395
|
+
}
|
|
396
|
+
if (!DISTANCE_MAPPING[config.distance]) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`Named vector "${name}": invalid distance "${config.distance}". Must be one of: cosine, euclidean, dotproduct`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
404
|
+
throw new Error("Dimension must be a positive integer");
|
|
405
|
+
}
|
|
406
|
+
if (!DISTANCE_MAPPING[metric]) {
|
|
407
|
+
throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
|
|
408
|
+
}
|
|
291
409
|
}
|
|
292
410
|
} catch (validationError) {
|
|
293
411
|
throw new error.MastraError(
|
|
@@ -295,22 +413,49 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
295
413
|
id: storage.createVectorErrorId("QDRANT", "CREATE_INDEX", "INVALID_ARGS"),
|
|
296
414
|
domain: error.ErrorDomain.STORAGE,
|
|
297
415
|
category: error.ErrorCategory.USER,
|
|
298
|
-
details: {
|
|
416
|
+
details: {
|
|
417
|
+
indexName,
|
|
418
|
+
dimension,
|
|
419
|
+
metric,
|
|
420
|
+
...namedVectors && { namedVectorNames: Object.keys(namedVectors).join(", ") }
|
|
421
|
+
}
|
|
299
422
|
},
|
|
300
423
|
validationError
|
|
301
424
|
);
|
|
302
425
|
}
|
|
303
426
|
try {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
427
|
+
if (namedVectors) {
|
|
428
|
+
const namedVectorsConfig = Object.entries(namedVectors).reduce(
|
|
429
|
+
(acc, [name, config]) => {
|
|
430
|
+
acc[name] = {
|
|
431
|
+
size: config.size,
|
|
432
|
+
distance: DISTANCE_MAPPING[config.distance]
|
|
433
|
+
};
|
|
434
|
+
return acc;
|
|
435
|
+
},
|
|
436
|
+
{}
|
|
437
|
+
);
|
|
438
|
+
await this.client.createCollection(indexName, {
|
|
439
|
+
vectors: namedVectorsConfig
|
|
440
|
+
});
|
|
441
|
+
} else {
|
|
442
|
+
await this.client.createCollection(indexName, {
|
|
443
|
+
vectors: {
|
|
444
|
+
size: dimension,
|
|
445
|
+
distance: DISTANCE_MAPPING[metric]
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
310
449
|
} catch (error$1) {
|
|
311
450
|
const message = error$1?.message || error$1?.toString();
|
|
312
451
|
if (error$1?.status === 409 || typeof message === "string" && message.toLowerCase().includes("exists")) {
|
|
313
|
-
|
|
452
|
+
if (!namedVectors) {
|
|
453
|
+
await this.validateExistingIndex(indexName, dimension, metric);
|
|
454
|
+
} else {
|
|
455
|
+
this.logger.info(
|
|
456
|
+
`Collection "${indexName}" already exists. Skipping validation for named vectors configuration.`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
314
459
|
return;
|
|
315
460
|
}
|
|
316
461
|
throw new error.MastraError(
|
|
@@ -328,12 +473,23 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
328
473
|
const translator = new QdrantFilterTranslator();
|
|
329
474
|
return translator.translate(filter);
|
|
330
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Queries the index for similar vectors.
|
|
478
|
+
*
|
|
479
|
+
* @param indexName - The name of the index to query.
|
|
480
|
+
* @param queryVector - The query vector to find similar vectors for.
|
|
481
|
+
* @param topK - Number of results to return (default: 10).
|
|
482
|
+
* @param filter - Optional metadata filter.
|
|
483
|
+
* @param includeVector - Whether to include vectors in results (default: false).
|
|
484
|
+
* @param using - Name of the vector space to query when using named vectors.
|
|
485
|
+
*/
|
|
331
486
|
async query({
|
|
332
487
|
indexName,
|
|
333
488
|
queryVector,
|
|
334
489
|
topK = 10,
|
|
335
490
|
filter,
|
|
336
|
-
includeVector = false
|
|
491
|
+
includeVector = false,
|
|
492
|
+
using
|
|
337
493
|
}) {
|
|
338
494
|
const translatedFilter = this.transformFilter(filter) ?? {};
|
|
339
495
|
try {
|
|
@@ -342,15 +498,20 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
342
498
|
limit: topK,
|
|
343
499
|
filter: translatedFilter,
|
|
344
500
|
with_payload: true,
|
|
345
|
-
with_vector: includeVector
|
|
501
|
+
with_vector: includeVector,
|
|
502
|
+
...using ? { using } : {}
|
|
346
503
|
})).points;
|
|
347
504
|
return results.map((match) => {
|
|
348
505
|
let vector = [];
|
|
349
|
-
if (includeVector) {
|
|
506
|
+
if (includeVector && match.vector != null) {
|
|
350
507
|
if (Array.isArray(match.vector)) {
|
|
351
508
|
vector = match.vector;
|
|
352
509
|
} else if (typeof match.vector === "object" && match.vector !== null) {
|
|
353
|
-
|
|
510
|
+
const namedVectors = match.vector;
|
|
511
|
+
const sourceArray = using && Array.isArray(namedVectors[using]) ? namedVectors[using] : Object.values(namedVectors).find((v) => Array.isArray(v));
|
|
512
|
+
if (sourceArray) {
|
|
513
|
+
vector = sourceArray.filter((v) => typeof v === "number");
|
|
514
|
+
}
|
|
354
515
|
}
|
|
355
516
|
}
|
|
356
517
|
return {
|
|
@@ -366,7 +527,7 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
366
527
|
id: storage.createVectorErrorId("QDRANT", "QUERY", "FAILED"),
|
|
367
528
|
domain: error.ErrorDomain.STORAGE,
|
|
368
529
|
category: error.ErrorCategory.THIRD_PARTY,
|
|
369
|
-
details: { indexName, topK }
|
|
530
|
+
details: { indexName, topK, ...using && { using } }
|
|
370
531
|
},
|
|
371
532
|
error$1
|
|
372
533
|
);
|