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