@mastra/qdrant 0.1.6-alpha.1 → 0.1.6-alpha.4

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.
@@ -1,18 +1,23 @@
1
1
 
2
- > @mastra/qdrant@0.1.6-alpha.1 build /home/runner/work/mastra/mastra/stores/qdrant
3
- > tsup src/index.ts --format esm --experimental-dts --clean --treeshake
2
+ > @mastra/qdrant@0.1.6-alpha.4 build /home/runner/work/mastra/mastra/stores/qdrant
3
+ > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.3.6
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 8031ms
9
+ TSC ⚡️ Build success in 7774ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.7.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/qdrant/dist/_tsup-dts-rollup.d.ts
14
- DTS ⚡️ Build success in 5790ms
14
+ Analysis will use the bundled TypeScript version 5.7.3
15
+ Writing package typings: /home/runner/work/mastra/mastra/stores/qdrant/dist/_tsup-dts-rollup.d.cts
16
+ DTS ⚡️ Build success in 9281ms
15
17
  CLI Cleaning output folder
16
18
  ESM Build start
17
- ESM dist/index.js 10.31 KB
18
- ESM ⚡️ Build success in 433ms
19
+ CJS Build start
20
+ ESM dist/index.js 10.58 KB
21
+ ESM ⚡️ Build success in 672ms
22
+ CJS dist/index.cjs 10.62 KB
23
+ CJS ⚡️ Build success in 678ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @mastra/qdrant
2
2
 
3
+ ## 0.1.6-alpha.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [dabecf4]
8
+ - @mastra/core@0.4.3-alpha.4
9
+
10
+ ## 0.1.6-alpha.3
11
+
12
+ ### Patch Changes
13
+
14
+ - 0fd78ac: Update vector store functions to use object params
15
+ - fd14a3f: Updating filter location from @mastra/core/filter to @mastra/core/vector/filter
16
+ - 4d4e1e1: Updated vector tests and pinecone
17
+ - bb4f447: Add support for commonjs
18
+ - Updated dependencies [0fd78ac]
19
+ - Updated dependencies [0d25b75]
20
+ - Updated dependencies [fd14a3f]
21
+ - Updated dependencies [3f369a2]
22
+ - Updated dependencies [4d4e1e1]
23
+ - Updated dependencies [bb4f447]
24
+ - @mastra/core@0.4.3-alpha.3
25
+
26
+ ## 0.1.6-alpha.2
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [2512a93]
31
+ - Updated dependencies [e62de74]
32
+ - @mastra/core@0.4.3-alpha.2
33
+
3
34
  ## 0.1.6-alpha.1
4
35
 
5
36
  ### Patch Changes
package/README.md CHANGED
@@ -20,21 +20,21 @@ const vectorStore = new QdrantVector(
20
20
  );
21
21
 
22
22
  // Create a new collection
23
- await vectorStore.createIndex('my-collection', 1536, 'cosine');
23
+ await vectorStore.createIndex({ indexName: 'my-collection', dimension: 1536, metric: 'cosine' });
24
24
 
25
25
  // Add vectors
26
26
  const vectors = [[0.1, 0.2, ...], [0.3, 0.4, ...]];
27
27
  const metadata = [{ text: 'doc1' }, { text: 'doc2' }];
28
- const ids = await vectorStore.upsert('my-collection', vectors, metadata);
28
+ const ids = await vectorStore.upsert({ indexName: 'my-collection', vectors, metadata });
29
29
 
30
30
  // Query vectors
31
- const results = await vectorStore.query(
32
- 'my-collection',
33
- [0.1, 0.2, ...],
34
- 10, // topK
35
- { text: { $eq: 'doc1' } }, // optional filter
36
- false // includeVector
37
- );
31
+ const results = await vectorStore.query({
32
+ indexName: 'my-collection',
33
+ queryVector: [0.1, 0.2, ...],
34
+ topK: 10, // topK
35
+ filter: { text: { $eq: 'doc1' } }, // optional filter
36
+ includeVector: false // includeVector
37
+ });
38
38
  ```
39
39
 
40
40
  ## Configuration
@@ -69,9 +69,9 @@ The following distance metrics are supported:
69
69
 
70
70
  ## Methods
71
71
 
72
- - `createIndex(indexName, dimension, metric?)`: Create a new collection
73
- - `upsert(indexName, vectors, metadata?, ids?)`: Add or update vectors
74
- - `query(indexName, queryVector, topK?, filter?, includeVector?)`: Search for similar vectors
72
+ - `createIndex({ indexName, dimension, metric? })`: Create a new collection
73
+ - `upsert({ indexName, vectors, metadata?, ids? })`: Add or update vectors
74
+ - `query({ indexName, queryVector, topK?, filter?, includeVector? })`: Search for similar vectors
75
75
  - `listIndexes()`: List all collections
76
76
  - `describeIndex(indexName)`: Get collection statistics
77
77
  - `deleteIndex(indexName)`: Delete a collection
@@ -0,0 +1,62 @@
1
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
+ import type { CreateIndexParams } from '@mastra/core/vector';
3
+ import type { IndexStats } from '@mastra/core/vector';
4
+ import type { LogicalOperator } from '@mastra/core/vector/filter';
5
+ import { MastraVector } from '@mastra/core/vector';
6
+ import type { OperatorSupport } from '@mastra/core/vector/filter';
7
+ import type { ParamsToArgs } from '@mastra/core/vector';
8
+ import type { QueryResult } from '@mastra/core/vector';
9
+ import type { QueryVectorParams } from '@mastra/core/vector';
10
+ import type { UpsertVectorParams } from '@mastra/core/vector';
11
+ import type { VectorFilter } from '@mastra/core/vector/filter';
12
+
13
+ /**
14
+ * Translates MongoDB-style filters to Qdrant compatible filters.
15
+ *
16
+ * Key transformations:
17
+ * - $and -> must
18
+ * - $or -> should
19
+ * - $not -> must_not
20
+ * - { field: { $op: value } } -> { key: field, match/range: { value/gt/lt: value } }
21
+ *
22
+ * Custom operators (Qdrant-specific):
23
+ * - $count -> values_count (array length/value count)
24
+ * - $geo -> geo filters (box, radius, polygon)
25
+ * - $hasId -> has_id filter
26
+ * - $nested -> nested object filters
27
+ * - $hasVector -> vector existence check
28
+ * - $datetime -> RFC 3339 datetime range
29
+ * - $null -> is_null check
30
+ * - $empty -> is_empty check
31
+ */
32
+ export declare class QdrantFilterTranslator extends BaseFilterTranslator {
33
+ protected isLogicalOperator(key: string): key is LogicalOperator;
34
+ protected getSupportedOperators(): OperatorSupport;
35
+ translate(filter?: VectorFilter): VectorFilter;
36
+ private createCondition;
37
+ private translateNode;
38
+ private buildFinalConditions;
39
+ private handleLogicalOperators;
40
+ private handleFieldConditions;
41
+ private translateCustomOperator;
42
+ private getQdrantLogicalOp;
43
+ private translateOperatorValue;
44
+ private translateGeoFilter;
45
+ private normalizeDatetimeRange;
46
+ }
47
+
48
+ declare class QdrantVector extends MastraVector {
49
+ private client;
50
+ constructor(url: string, apiKey?: string, https?: boolean);
51
+ upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]>;
52
+ createIndex(...args: ParamsToArgs<CreateIndexParams>): Promise<void>;
53
+ transformFilter(filter?: VectorFilter): VectorFilter;
54
+ query(...args: ParamsToArgs<QueryVectorParams>): Promise<QueryResult[]>;
55
+ listIndexes(): Promise<string[]>;
56
+ describeIndex(indexName: string): Promise<IndexStats>;
57
+ deleteIndex(indexName: string): Promise<void>;
58
+ }
59
+ export { QdrantVector }
60
+ export { QdrantVector as QdrantVector_alias_1 }
61
+
62
+ export { }
@@ -1,10 +1,14 @@
1
- import { BaseFilterTranslator } from '@mastra/core/filter';
2
- import type { Filter } from '@mastra/core/filter';
1
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
+ import type { CreateIndexParams } from '@mastra/core/vector';
3
3
  import type { IndexStats } from '@mastra/core/vector';
4
- import type { LogicalOperator } from '@mastra/core/filter';
4
+ import type { LogicalOperator } from '@mastra/core/vector/filter';
5
5
  import { MastraVector } from '@mastra/core/vector';
6
- import type { OperatorSupport } from '@mastra/core/filter';
6
+ import type { OperatorSupport } from '@mastra/core/vector/filter';
7
+ import type { ParamsToArgs } from '@mastra/core/vector';
7
8
  import type { QueryResult } from '@mastra/core/vector';
9
+ import type { QueryVectorParams } from '@mastra/core/vector';
10
+ import type { UpsertVectorParams } from '@mastra/core/vector';
11
+ import type { VectorFilter } from '@mastra/core/vector/filter';
8
12
 
9
13
  /**
10
14
  * Translates MongoDB-style filters to Qdrant compatible filters.
@@ -28,7 +32,7 @@ import type { QueryResult } from '@mastra/core/vector';
28
32
  export declare class QdrantFilterTranslator extends BaseFilterTranslator {
29
33
  protected isLogicalOperator(key: string): key is LogicalOperator;
30
34
  protected getSupportedOperators(): OperatorSupport;
31
- translate(filter?: Filter): Filter | undefined;
35
+ translate(filter?: VectorFilter): VectorFilter;
32
36
  private createCondition;
33
37
  private translateNode;
34
38
  private buildFinalConditions;
@@ -44,10 +48,10 @@ export declare class QdrantFilterTranslator extends BaseFilterTranslator {
44
48
  declare class QdrantVector extends MastraVector {
45
49
  private client;
46
50
  constructor(url: string, apiKey?: string, https?: boolean);
47
- upsert(indexName: string, vectors: number[][], metadata?: Record<string, any>[], ids?: string[]): Promise<string[]>;
48
- createIndex(indexName: string, dimension: number, metric?: 'cosine' | 'euclidean' | 'dotproduct'): Promise<void>;
49
- transformFilter(filter?: Filter): Filter | undefined;
50
- query(indexName: string, queryVector: number[], topK?: number, filter?: Filter, includeVector?: boolean): Promise<QueryResult[]>;
51
+ upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]>;
52
+ createIndex(...args: ParamsToArgs<CreateIndexParams>): Promise<void>;
53
+ transformFilter(filter?: VectorFilter): VectorFilter;
54
+ query(...args: ParamsToArgs<QueryVectorParams>): Promise<QueryResult[]>;
51
55
  listIndexes(): Promise<string[]>;
52
56
  describeIndex(indexName: string): Promise<IndexStats>;
53
57
  deleteIndex(indexName: string): Promise<void>;
package/dist/index.cjs ADDED
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ var vector = require('@mastra/core/vector');
4
+ var jsClientRest = require('@qdrant/js-client-rest');
5
+ var filter = require('@mastra/core/vector/filter');
6
+
7
+ // src/vector/index.ts
8
+ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
9
+ isLogicalOperator(key) {
10
+ return super.isLogicalOperator(key) || key === "$hasId" || key === "$hasVector";
11
+ }
12
+ getSupportedOperators() {
13
+ return {
14
+ ...filter.BaseFilterTranslator.DEFAULT_OPERATORS,
15
+ logical: ["$and", "$or", "$not"],
16
+ array: ["$in", "$nin"],
17
+ regex: ["$regex"],
18
+ custom: ["$count", "$geo", "$nested", "$datetime", "$null", "$empty", "$hasId", "$hasVector"]
19
+ };
20
+ }
21
+ translate(filter) {
22
+ if (this.isEmpty(filter)) return filter;
23
+ this.validateFilter(filter);
24
+ return this.translateNode(filter);
25
+ }
26
+ createCondition(type, value, fieldKey) {
27
+ const condition = { [type]: value };
28
+ return fieldKey ? { key: fieldKey, ...condition } : condition;
29
+ }
30
+ translateNode(node, isNested = false, fieldKey) {
31
+ if (!this.isEmpty(node) && typeof node === "object" && "must" in node) {
32
+ return node;
33
+ }
34
+ if (this.isPrimitive(node)) {
35
+ if (node === null) {
36
+ return { is_null: { key: fieldKey } };
37
+ }
38
+ return this.createCondition("match", { value: this.normalizeComparisonValue(node) }, fieldKey);
39
+ }
40
+ if (this.isRegex(node)) {
41
+ throw new Error("Direct regex pattern format is not supported in Qdrant");
42
+ }
43
+ if (Array.isArray(node)) {
44
+ return node.length === 0 ? { is_empty: { key: fieldKey } } : this.createCondition("match", { any: this.normalizeArrayValues(node) }, fieldKey);
45
+ }
46
+ const entries = Object.entries(node);
47
+ const logicalResult = this.handleLogicalOperators(entries, isNested);
48
+ if (logicalResult) {
49
+ return logicalResult;
50
+ }
51
+ const { conditions, range, matchCondition } = this.handleFieldConditions(entries, fieldKey);
52
+ if (Object.keys(range).length > 0) {
53
+ conditions.push({ key: fieldKey, range });
54
+ }
55
+ if (matchCondition) {
56
+ conditions.push({ key: fieldKey, match: matchCondition });
57
+ }
58
+ return this.buildFinalConditions(conditions, isNested);
59
+ }
60
+ buildFinalConditions(conditions, isNested) {
61
+ if (conditions.length === 0) {
62
+ return {};
63
+ } else if (conditions.length === 1 && isNested) {
64
+ return conditions[0];
65
+ } else {
66
+ return { must: conditions };
67
+ }
68
+ }
69
+ handleLogicalOperators(entries, isNested) {
70
+ const firstKey = entries[0]?.[0];
71
+ if (firstKey && this.isLogicalOperator(firstKey) && !this.isCustomOperator(firstKey)) {
72
+ const [key, value] = entries[0];
73
+ const qdrantOp = this.getQdrantLogicalOp(key);
74
+ return {
75
+ [qdrantOp]: Array.isArray(value) ? value.map((v) => this.translateNode(v, true)) : [this.translateNode(value, true)]
76
+ };
77
+ }
78
+ if (entries.length > 1 && !isNested && entries.every(([key]) => !this.isOperator(key) && !this.isCustomOperator(key))) {
79
+ return {
80
+ must: entries.map(([key, value]) => this.translateNode(value, true, key))
81
+ };
82
+ }
83
+ return null;
84
+ }
85
+ handleFieldConditions(entries, fieldKey) {
86
+ const conditions = [];
87
+ let range = {};
88
+ let matchCondition = null;
89
+ for (const [key, value] of entries) {
90
+ if (this.isCustomOperator(key)) {
91
+ const customOp = this.translateCustomOperator(key, value, fieldKey);
92
+ conditions.push(customOp);
93
+ } else if (this.isOperator(key)) {
94
+ const opResult = this.translateOperatorValue(key, value);
95
+ if (opResult.range) {
96
+ Object.assign(range, opResult.range);
97
+ } else {
98
+ matchCondition = opResult;
99
+ }
100
+ } else {
101
+ const nestedKey = fieldKey ? `${fieldKey}.${key}` : key;
102
+ const nestedCondition = this.translateNode(value, true, nestedKey);
103
+ if (nestedCondition.must) {
104
+ conditions.push(...nestedCondition.must);
105
+ } else if (!this.isEmpty(nestedCondition)) {
106
+ conditions.push(nestedCondition);
107
+ }
108
+ }
109
+ }
110
+ return { conditions, range, matchCondition };
111
+ }
112
+ translateCustomOperator(op, value, fieldKey) {
113
+ switch (op) {
114
+ case "$count":
115
+ const countConditions = Object.entries(value).reduce(
116
+ (acc, [k, v]) => ({
117
+ ...acc,
118
+ [k.replace("$", "")]: v
119
+ }),
120
+ {}
121
+ );
122
+ return { key: fieldKey, values_count: countConditions };
123
+ case "$geo":
124
+ const geoOp = this.translateGeoFilter(value.type, value);
125
+ return { key: fieldKey, ...geoOp };
126
+ case "$hasId":
127
+ return { has_id: Array.isArray(value) ? value : [value] };
128
+ case "$nested":
129
+ return {
130
+ nested: {
131
+ key: fieldKey,
132
+ filter: this.translateNode(value)
133
+ }
134
+ };
135
+ case "$hasVector":
136
+ return { has_vector: value };
137
+ case "$datetime":
138
+ return {
139
+ key: fieldKey,
140
+ range: this.normalizeDatetimeRange(value.range)
141
+ };
142
+ case "$null":
143
+ return { is_null: { key: fieldKey } };
144
+ case "$empty":
145
+ return { is_empty: { key: fieldKey } };
146
+ default:
147
+ throw new Error(`Unsupported custom operator: ${op}`);
148
+ }
149
+ }
150
+ getQdrantLogicalOp(op) {
151
+ switch (op) {
152
+ case "$and":
153
+ return "must";
154
+ case "$or":
155
+ return "should";
156
+ case "$not":
157
+ return "must_not";
158
+ default:
159
+ throw new Error(`Unsupported logical operator: ${op}`);
160
+ }
161
+ }
162
+ translateOperatorValue(operator, value) {
163
+ const normalizedValue = this.normalizeComparisonValue(value);
164
+ switch (operator) {
165
+ case "$eq":
166
+ return { value: normalizedValue };
167
+ case "$ne":
168
+ return { except: [normalizedValue] };
169
+ case "$gt":
170
+ return { range: { gt: normalizedValue } };
171
+ case "$gte":
172
+ return { range: { gte: normalizedValue } };
173
+ case "$lt":
174
+ return { range: { lt: normalizedValue } };
175
+ case "$lte":
176
+ return { range: { lte: normalizedValue } };
177
+ case "$in":
178
+ return { any: this.normalizeArrayValues(value) };
179
+ case "$nin":
180
+ return { except: this.normalizeArrayValues(value) };
181
+ case "$regex":
182
+ return { text: value };
183
+ case "exists":
184
+ return value ? {
185
+ must_not: [{ is_null: { key: value } }, { is_empty: { key: value } }]
186
+ } : {
187
+ is_empty: { key: value }
188
+ };
189
+ default:
190
+ throw new Error(`Unsupported operator: ${operator}`);
191
+ }
192
+ }
193
+ translateGeoFilter(type, value) {
194
+ switch (type) {
195
+ case "box":
196
+ return {
197
+ geo_bounding_box: {
198
+ top_left: value.top_left,
199
+ bottom_right: value.bottom_right
200
+ }
201
+ };
202
+ case "radius":
203
+ return {
204
+ geo_radius: {
205
+ center: value.center,
206
+ radius: value.radius
207
+ }
208
+ };
209
+ case "polygon":
210
+ return {
211
+ geo_polygon: {
212
+ exterior: value.exterior,
213
+ interiors: value.interiors
214
+ }
215
+ };
216
+ default:
217
+ throw new Error(`Unsupported geo filter type: ${type}`);
218
+ }
219
+ }
220
+ normalizeDatetimeRange(value) {
221
+ const range = {};
222
+ for (const [op, val] of Object.entries(value)) {
223
+ if (val instanceof Date) {
224
+ range[op] = val.toISOString();
225
+ } else if (typeof val === "string") {
226
+ range[op] = val;
227
+ }
228
+ }
229
+ return range;
230
+ }
231
+ };
232
+
233
+ // src/vector/index.ts
234
+ var BATCH_SIZE = 256;
235
+ var DISTANCE_MAPPING = {
236
+ cosine: "Cosine",
237
+ euclidean: "Euclid",
238
+ dotproduct: "Dot"
239
+ };
240
+ var QdrantVector = class extends vector.MastraVector {
241
+ client;
242
+ constructor(url, apiKey, https) {
243
+ super();
244
+ const baseClient = new jsClientRest.QdrantClient({
245
+ url,
246
+ apiKey,
247
+ https
248
+ });
249
+ const telemetry = this.__getTelemetry();
250
+ this.client = telemetry?.traceClass(baseClient, {
251
+ spanNamePrefix: "qdrant-vector",
252
+ attributes: {
253
+ "vector.type": "qdrant"
254
+ }
255
+ }) ?? baseClient;
256
+ }
257
+ async upsert(...args) {
258
+ const params = this.normalizeArgs("upsert", args);
259
+ const { indexName, vectors, metadata, ids } = params;
260
+ const pointIds = ids || vectors.map(() => crypto.randomUUID());
261
+ const records = vectors.map((vector, i) => ({
262
+ id: pointIds[i],
263
+ vector,
264
+ payload: metadata?.[i] || {}
265
+ }));
266
+ for (let i = 0; i < records.length; i += BATCH_SIZE) {
267
+ const batch = records.slice(i, i + BATCH_SIZE);
268
+ await this.client.upsert(indexName, {
269
+ // @ts-expect-error
270
+ points: batch,
271
+ wait: true
272
+ });
273
+ }
274
+ return pointIds;
275
+ }
276
+ async createIndex(...args) {
277
+ const params = this.normalizeArgs("createIndex", args);
278
+ const { indexName, dimension, metric = "cosine" } = params;
279
+ if (!Number.isInteger(dimension) || dimension <= 0) {
280
+ throw new Error("Dimension must be a positive integer");
281
+ }
282
+ await this.client.createCollection(indexName, {
283
+ vectors: {
284
+ // @ts-expect-error
285
+ size: dimension,
286
+ // @ts-expect-error
287
+ distance: DISTANCE_MAPPING[metric]
288
+ }
289
+ });
290
+ }
291
+ transformFilter(filter) {
292
+ const translator = new QdrantFilterTranslator();
293
+ return translator.translate(filter);
294
+ }
295
+ async query(...args) {
296
+ const params = this.normalizeArgs("query", args);
297
+ const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;
298
+ const translatedFilter = this.transformFilter(filter) ?? {};
299
+ const results = (await this.client.query(indexName, {
300
+ query: queryVector,
301
+ limit: topK,
302
+ filter: translatedFilter,
303
+ with_payload: true,
304
+ with_vector: includeVector
305
+ })).points;
306
+ return results.map((match) => {
307
+ let vector = [];
308
+ if (includeVector) {
309
+ if (Array.isArray(match.vector)) {
310
+ vector = match.vector;
311
+ } else if (typeof match.vector === "object" && match.vector !== null) {
312
+ vector = Object.values(match.vector).filter((v) => typeof v === "number");
313
+ }
314
+ }
315
+ return {
316
+ id: match.id,
317
+ score: match.score || 0,
318
+ metadata: match.payload,
319
+ ...includeVector && { vector }
320
+ };
321
+ });
322
+ }
323
+ async listIndexes() {
324
+ const response = await this.client.getCollections();
325
+ return response.collections.map((collection) => collection.name) || [];
326
+ }
327
+ async describeIndex(indexName) {
328
+ const { config, points_count } = await this.client.getCollection(indexName);
329
+ const distance = config.params.vectors?.distance;
330
+ return {
331
+ dimension: config.params.vectors?.size,
332
+ count: points_count || 0,
333
+ // @ts-expect-error
334
+ metric: Object.keys(DISTANCE_MAPPING).find((key) => DISTANCE_MAPPING[key] === distance)
335
+ };
336
+ }
337
+ async deleteIndex(indexName) {
338
+ await this.client.deleteCollection(indexName);
339
+ }
340
+ };
341
+
342
+ exports.QdrantVector = QdrantVector;
@@ -0,0 +1 @@
1
+ export { QdrantVector } from './_tsup-dts-rollup.cjs';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MastraVector } from '@mastra/core/vector';
2
2
  import { QdrantClient } from '@qdrant/js-client-rest';
3
- import { BaseFilterTranslator } from '@mastra/core/filter';
3
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
4
4
 
5
5
  // src/vector/index.ts
6
6
  var QdrantFilterTranslator = class extends BaseFilterTranslator {
@@ -252,7 +252,9 @@ var QdrantVector = class extends MastraVector {
252
252
  }
253
253
  }) ?? baseClient;
254
254
  }
255
- async upsert(indexName, vectors, metadata, ids) {
255
+ async upsert(...args) {
256
+ const params = this.normalizeArgs("upsert", args);
257
+ const { indexName, vectors, metadata, ids } = params;
256
258
  const pointIds = ids || vectors.map(() => crypto.randomUUID());
257
259
  const records = vectors.map((vector, i) => ({
258
260
  id: pointIds[i],
@@ -269,7 +271,9 @@ var QdrantVector = class extends MastraVector {
269
271
  }
270
272
  return pointIds;
271
273
  }
272
- async createIndex(indexName, dimension, metric = "cosine") {
274
+ async createIndex(...args) {
275
+ const params = this.normalizeArgs("createIndex", args);
276
+ const { indexName, dimension, metric = "cosine" } = params;
273
277
  if (!Number.isInteger(dimension) || dimension <= 0) {
274
278
  throw new Error("Dimension must be a positive integer");
275
279
  }
@@ -286,8 +290,10 @@ var QdrantVector = class extends MastraVector {
286
290
  const translator = new QdrantFilterTranslator();
287
291
  return translator.translate(filter);
288
292
  }
289
- async query(indexName, queryVector, topK = 10, filter, includeVector = false) {
290
- const translatedFilter = this.transformFilter(filter);
293
+ async query(...args) {
294
+ const params = this.normalizeArgs("query", args);
295
+ const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;
296
+ const translatedFilter = this.transformFilter(filter) ?? {};
291
297
  const results = (await this.client.query(indexName, {
292
298
  query: queryVector,
293
299
  limit: topK,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/qdrant",
3
- "version": "0.1.6-alpha.1",
3
+ "version": "0.1.6-alpha.4",
4
4
  "description": "Qdrant vector store provider for Mastra",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,13 +10,17 @@
10
10
  "import": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
13
17
  }
14
18
  },
15
19
  "./package.json": "./package.json"
16
20
  },
17
21
  "dependencies": {
18
22
  "@qdrant/js-client-rest": "^1.12.0",
19
- "@mastra/core": "^0.4.3-alpha.1"
23
+ "@mastra/core": "^0.4.3-alpha.4"
20
24
  },
21
25
  "devDependencies": {
22
26
  "@microsoft/api-extractor": "^7.49.2",
@@ -28,7 +32,7 @@
28
32
  "@internal/lint": "0.0.0"
29
33
  },
30
34
  "scripts": {
31
- "build": "tsup src/index.ts --format esm --experimental-dts --clean --treeshake",
35
+ "build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake",
32
36
  "build:watch": "pnpm build --watch",
33
37
  "test": "vitest run",
34
38
  "lint": "eslint ."
@@ -1,5 +1,5 @@
1
- import { BaseFilterTranslator } from '@mastra/core/filter';
2
- import type { FieldCondition, Filter, LogicalOperator, OperatorSupport } from '@mastra/core/filter';
1
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
+ import type { FieldCondition, VectorFilter, LogicalOperator, OperatorSupport } from '@mastra/core/vector/filter';
3
3
 
4
4
  /**
5
5
  * Translates MongoDB-style filters to Qdrant compatible filters.
@@ -35,9 +35,9 @@ export class QdrantFilterTranslator extends BaseFilterTranslator {
35
35
  };
36
36
  }
37
37
 
38
- translate(filter?: Filter): Filter | undefined {
38
+ translate(filter?: VectorFilter): VectorFilter {
39
39
  if (this.isEmpty(filter)) return filter;
40
- this.validateFilter(filter as Filter);
40
+ this.validateFilter(filter);
41
41
  return this.translateNode(filter);
42
42
  }
43
43
 
@@ -46,7 +46,7 @@ export class QdrantFilterTranslator extends BaseFilterTranslator {
46
46
  return fieldKey ? { key: fieldKey, ...condition } : condition;
47
47
  }
48
48
 
49
- private translateNode(node: Filter | FieldCondition, isNested: boolean = false, fieldKey?: string): any {
49
+ private translateNode(node: VectorFilter | FieldCondition, isNested: boolean = false, fieldKey?: string): any {
50
50
  if (!this.isEmpty(node) && typeof node === 'object' && 'must' in node) {
51
51
  return node;
52
52
  }
@@ -1,6 +1,6 @@
1
1
  // To setup a Qdrant server, run:
2
2
  // docker run -p 6333:6333 qdrant/qdrant
3
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3
+ import { describe, it, expect, beforeAll, afterAll, afterEach, vi, beforeEach } from 'vitest';
4
4
 
5
5
  import { QdrantVector } from './index';
6
6
 
@@ -13,7 +13,7 @@ describe('QdrantVector', () => {
13
13
  describe('Index Operations', () => {
14
14
  beforeAll(async () => {
15
15
  qdrant = new QdrantVector('http://localhost:6333/');
16
- await qdrant.createIndex(testCollectionName, dimension);
16
+ await qdrant.createIndex({ indexName: testCollectionName, dimension });
17
17
  });
18
18
 
19
19
  afterAll(async () => {
@@ -36,7 +36,7 @@ describe('QdrantVector', () => {
36
36
  describe('Vector Operations', () => {
37
37
  beforeAll(async () => {
38
38
  qdrant = new QdrantVector('http://localhost:6333/');
39
- await qdrant.createIndex(testCollectionName, dimension);
39
+ await qdrant.createIndex({ indexName: testCollectionName, dimension });
40
40
  });
41
41
 
42
42
  afterAll(async () => {
@@ -52,13 +52,13 @@ describe('QdrantVector', () => {
52
52
  let vectorIds: string[];
53
53
 
54
54
  it('should upsert vectors with metadata', async () => {
55
- vectorIds = await qdrant.upsert(testCollectionName, testVectors, testMetadata);
55
+ vectorIds = await qdrant.upsert({ indexName: testCollectionName, vectors: testVectors, metadata: testMetadata });
56
56
  expect(vectorIds).toHaveLength(3);
57
57
  }, 50000);
58
58
 
59
59
  it('should query vectors and return nearest neighbors', async () => {
60
60
  const queryVector = [1.0, 0.1, 0.1];
61
- const results = await qdrant.query(testCollectionName, queryVector, 3);
61
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector, topK: 3 });
62
62
 
63
63
  expect(results).toHaveLength(3);
64
64
  expect(results?.[0]?.score).toBeGreaterThan(0);
@@ -67,7 +67,7 @@ describe('QdrantVector', () => {
67
67
 
68
68
  it('should query vectors and return vector in results', async () => {
69
69
  const queryVector = [1.0, 0.1, 0.1];
70
- const results = await qdrant.query(testCollectionName, queryVector, 3, undefined, true);
70
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector, topK: 3, includeVector: true });
71
71
 
72
72
  expect(results).toHaveLength(3);
73
73
  expect(results?.[0]?.vector).toBeDefined();
@@ -80,7 +80,7 @@ describe('QdrantVector', () => {
80
80
  label: 'y-axis',
81
81
  };
82
82
 
83
- const results = await qdrant.query(testCollectionName, queryVector, 1, filter);
83
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector, topK: 1, filter });
84
84
 
85
85
  expect(results).toHaveLength(1);
86
86
  expect(results?.[0]?.metadata?.label).toBe('y-axis');
@@ -184,8 +184,8 @@ describe('QdrantVector', () => {
184
184
 
185
185
  beforeAll(async () => {
186
186
  qdrant = new QdrantVector('http://localhost:6333/');
187
- await qdrant.createIndex(testCollectionName, dimension);
188
- await qdrant.upsert(testCollectionName, filterTestVectors, filterTestMetadata);
187
+ await qdrant.createIndex({ indexName: testCollectionName, dimension });
188
+ await qdrant.upsert({ indexName: testCollectionName, vectors: filterTestVectors, metadata: filterTestMetadata });
189
189
  });
190
190
 
191
191
  afterAll(async () => {
@@ -195,21 +195,21 @@ describe('QdrantVector', () => {
195
195
  describe('Basic Operators', () => {
196
196
  it('should filter by exact value match', async () => {
197
197
  const filter = { name: 'item1' };
198
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
198
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
199
199
  expect(results).toHaveLength(1);
200
200
  expect(results[0]?.metadata?.name).toBe('item1');
201
201
  });
202
202
 
203
203
  it('should filter using comparison operators', async () => {
204
204
  const filter = { price: { $gt: 100, $lt: 600 } };
205
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
205
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
206
206
  expect(results).toHaveLength(1);
207
207
  expect(results[0]?.metadata?.price).toBe(500);
208
208
  });
209
209
 
210
210
  it('should filter using array operators', async () => {
211
211
  const filter = { tags: { $in: ['premium', 'bestseller'] } };
212
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
212
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
213
213
  expect(results).toHaveLength(2);
214
214
  const tags = results.flatMap(r => r.metadata?.tags || []);
215
215
  expect(tags).toContain('bestseller');
@@ -218,7 +218,7 @@ describe('QdrantVector', () => {
218
218
 
219
219
  it('should handle null values', async () => {
220
220
  const filter = { price: null };
221
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
221
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
222
222
  expect(results).toHaveLength(1);
223
223
  expect(results[0]?.metadata?.price).toBeNull();
224
224
  });
@@ -227,7 +227,7 @@ describe('QdrantVector', () => {
227
227
  const filter = {
228
228
  tags: [],
229
229
  };
230
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
230
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
231
231
  const resultsWithMetadata = results.filter(r => Object.keys(r?.metadata || {}).length > 0);
232
232
  expect(resultsWithMetadata).toHaveLength(1);
233
233
  expect(resultsWithMetadata[0]?.metadata?.tags).toHaveLength(0);
@@ -239,7 +239,7 @@ describe('QdrantVector', () => {
239
239
  const filter = {
240
240
  $and: [{ tags: { $in: ['electronics'] } }, { price: { $gt: 700 } }],
241
241
  };
242
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
242
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
243
243
  expect(results).toHaveLength(1);
244
244
  expect(results[0]?.metadata?.price).toBeGreaterThan(700);
245
245
  expect(results[0]?.metadata?.tags).toContain('electronics');
@@ -249,7 +249,7 @@ describe('QdrantVector', () => {
249
249
  const filter = {
250
250
  $or: [{ price: { $gt: 900 } }, { tags: { $in: ['bestseller'] } }],
251
251
  };
252
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
252
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
253
253
  expect(results).toHaveLength(2);
254
254
  results.forEach(result => {
255
255
  expect(result.metadata?.price > 900 || result.metadata?.tags?.includes('bestseller')).toBe(true);
@@ -260,7 +260,7 @@ describe('QdrantVector', () => {
260
260
  const filter = {
261
261
  $not: { tags: { $in: ['electronics'] } },
262
262
  };
263
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
263
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
264
264
  const resultsWithMetadata = results.filter(r => Object.keys(r?.metadata || {}).length > 0);
265
265
  expect(resultsWithMetadata).toHaveLength(2);
266
266
  resultsWithMetadata.forEach(result => {
@@ -277,7 +277,7 @@ describe('QdrantVector', () => {
277
277
  },
278
278
  ],
279
279
  };
280
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
280
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
281
281
  expect(results).toHaveLength(2);
282
282
  results.forEach(result => {
283
283
  expect(result.metadata?.details?.weight).toBeLessThan(2.0);
@@ -287,7 +287,7 @@ describe('QdrantVector', () => {
287
287
 
288
288
  it('should handle empty logical operators', async () => {
289
289
  const filter = { $and: [] };
290
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
290
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
291
291
  expect(results.length).toBeGreaterThan(0);
292
292
  });
293
293
  });
@@ -295,7 +295,7 @@ describe('QdrantVector', () => {
295
295
  describe('Custom Operators', () => {
296
296
  it('should filter using $count operator', async () => {
297
297
  const filter = { 'stock.locations': { $count: { $gt: 1 } } };
298
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
298
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
299
299
  expect(results).toHaveLength(2);
300
300
  results.forEach(result => {
301
301
  expect(result.metadata?.stock?.locations?.length).toBeGreaterThan(1);
@@ -312,7 +312,7 @@ describe('QdrantVector', () => {
312
312
  },
313
313
  },
314
314
  };
315
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
315
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
316
316
  expect(results).toHaveLength(1);
317
317
  expect(results[0]?.metadata?.location?.lat).toBe(52.5);
318
318
  expect(results[0]?.metadata?.location?.lon).toBe(13.4);
@@ -328,7 +328,7 @@ describe('QdrantVector', () => {
328
328
  },
329
329
  },
330
330
  };
331
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
331
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
332
332
  expect(results).toHaveLength(1);
333
333
  expect(results[0]?.metadata?.location?.lat).toBe(52.5);
334
334
  expect(results[0]?.metadata?.location?.lon).toBe(13.4);
@@ -351,7 +351,7 @@ describe('QdrantVector', () => {
351
351
  },
352
352
  },
353
353
  };
354
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
354
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
355
355
  expect(results).toHaveLength(1);
356
356
  expect(results[0]?.metadata?.location?.lat).toBe(52.5);
357
357
  expect(results[0]?.metadata?.location?.lon).toBe(13.4);
@@ -359,11 +359,11 @@ describe('QdrantVector', () => {
359
359
 
360
360
  it('should filter using $hasId operator', async () => {
361
361
  // First get some IDs from a regular query
362
- const allResults = await qdrant.query(testCollectionName, [1, 0, 0], 2);
362
+ const allResults = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], topK: 2 });
363
363
  const targetIds = allResults.map(r => r.id);
364
364
 
365
365
  const filter = { $hasId: targetIds };
366
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
366
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
367
367
  expect(results).toHaveLength(2);
368
368
  results.forEach(result => {
369
369
  expect(targetIds).toContain(result.id);
@@ -372,7 +372,7 @@ describe('QdrantVector', () => {
372
372
 
373
373
  it('should filter using $hasVector operator', async () => {
374
374
  const filter = { $hasVector: '' };
375
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
375
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
376
376
  expect(results.length).toBeGreaterThan(0);
377
377
  });
378
378
 
@@ -385,7 +385,7 @@ describe('QdrantVector', () => {
385
385
  const metadata = {
386
386
  created_at: now.toISOString(),
387
387
  };
388
- await qdrant.upsert(testCollectionName, [vector], [metadata]);
388
+ await qdrant.upsert({ indexName: testCollectionName, vectors: [vector], metadata: [metadata] });
389
389
 
390
390
  const filter = {
391
391
  created_at: {
@@ -397,7 +397,7 @@ describe('QdrantVector', () => {
397
397
  },
398
398
  },
399
399
  };
400
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
400
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
401
401
  expect(results.length).toBeGreaterThan(0);
402
402
  results.forEach(result => {
403
403
  expect(new Date(result.metadata?.created_at).getTime()).toBeGreaterThan(now.getTime() - 1000);
@@ -408,39 +408,41 @@ describe('QdrantVector', () => {
408
408
 
409
409
  describe('Special Cases', () => {
410
410
  it('handles regex patterns in queries', async () => {
411
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, {
412
- name: { $regex: 'item' },
411
+ const results = await qdrant.query({
412
+ indexName: testCollectionName,
413
+ queryVector: [1, 0, 0],
414
+ filter: { name: { $regex: 'item' } },
413
415
  });
414
416
  expect(results.length).toBe(4);
415
417
  });
416
418
 
417
419
  it('handles array operators in queries', async () => {
418
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, {
419
- tags: { $in: ['electronics', 'books'] },
420
+ const results = await qdrant.query({
421
+ indexName: testCollectionName,
422
+ queryVector: [1, 0, 0],
423
+ filter: { tags: { $in: ['electronics', 'books'] } },
420
424
  });
421
425
  expect(results.length).toBe(3);
422
426
  });
423
427
 
424
428
  it('handles nested array queries', async () => {
425
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, {
426
- 'stock.locations[]': {
427
- $nested: {
428
- warehouse: 'A',
429
- count: { $gt: 20 },
430
- },
431
- },
429
+ const results = await qdrant.query({
430
+ indexName: testCollectionName,
431
+ queryVector: [1, 0, 0],
432
+ filter: { 'stock.locations[]': { $nested: { warehouse: 'A', count: { $gt: 20 } } } },
432
433
  });
433
434
  expect(results.length).toBe(2);
434
435
  });
435
436
 
436
437
  it('handles collection-wide operators', async () => {
437
438
  // First get some actual IDs from our collection
438
- const searchResults = await qdrant.query(testCollectionName, [1, 0, 0], 2);
439
+ const searchResults = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], topK: 2 });
439
440
  const ids = searchResults.map(r => r.id);
440
441
 
441
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, {
442
- $hasId: ids,
443
- $hasVector: '', // Use the default vector name
442
+ const results = await qdrant.query({
443
+ indexName: testCollectionName,
444
+ queryVector: [1, 0, 0],
445
+ filter: { $hasId: ids, $hasVector: '' },
444
446
  });
445
447
  expect(results.length).toBe(2);
446
448
  });
@@ -449,7 +451,7 @@ describe('QdrantVector', () => {
449
451
  'details.color': 'red',
450
452
  'stock.quantity': { $gt: 0 },
451
453
  };
452
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
454
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
453
455
  expect(results).toHaveLength(1);
454
456
  expect(results[0]?.metadata?.details?.color).toBe('red');
455
457
  expect(results[0]?.metadata?.stock?.quantity).toBeGreaterThan(0);
@@ -459,7 +461,7 @@ describe('QdrantVector', () => {
459
461
  const filter = {
460
462
  price: { $gt: 20, $lt: 30 },
461
463
  };
462
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
464
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
463
465
  expect(results).toHaveLength(1);
464
466
  expect(results[0]?.metadata?.price).toBe(25);
465
467
  });
@@ -474,7 +476,7 @@ describe('QdrantVector', () => {
474
476
  { $not: { tags: { $in: ['basic'] } } },
475
477
  ],
476
478
  };
477
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
479
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
478
480
  expect(results).toHaveLength(2);
479
481
  results.forEach(result => {
480
482
  expect(result.metadata?.details?.weight).toBeLessThan(3.0);
@@ -487,7 +489,7 @@ describe('QdrantVector', () => {
487
489
  const filter = {
488
490
  'stock.locations[].warehouse': { $in: ['A'] },
489
491
  };
490
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
492
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
491
493
  expect(results).toHaveLength(2);
492
494
  results.forEach(result => {
493
495
  expect(result.metadata?.stock?.locations?.some((loc: any) => loc.warehouse === 'A')).toBe(true);
@@ -498,7 +500,7 @@ describe('QdrantVector', () => {
498
500
  const filter = {
499
501
  $and: [{ 'stock.locations[].warehouse': { $in: ['A'] } }, { 'stock.locations[].count': { $gt: 20 } }],
500
502
  };
501
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
503
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
502
504
  expect(results).toHaveLength(2);
503
505
  results.forEach(result => {
504
506
  const locations = result.metadata?.stock?.locations || [];
@@ -517,7 +519,7 @@ describe('QdrantVector', () => {
517
519
  updated: new Date(now.getTime() + 1000).toISOString(),
518
520
  },
519
521
  };
520
- await qdrant.upsert(testCollectionName, [vector], [metadata]);
522
+ await qdrant.upsert({ indexName: testCollectionName, vectors: [vector], metadata: [metadata] });
521
523
 
522
524
  const filter = {
523
525
  $and: [
@@ -533,7 +535,7 @@ describe('QdrantVector', () => {
533
535
  },
534
536
  ],
535
537
  };
536
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
538
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
537
539
  expect(results.length).toBeGreaterThan(0);
538
540
  });
539
541
 
@@ -557,7 +559,7 @@ describe('QdrantVector', () => {
557
559
  },
558
560
  ],
559
561
  };
560
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
562
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
561
563
  expect(results.length).toBeGreaterThan(0);
562
564
  results.forEach(result => {
563
565
  const metadata = result.metadata || {};
@@ -580,7 +582,7 @@ describe('QdrantVector', () => {
580
582
  $or: [{ 'details.weight': { $lt: 2.0 } }, { 'stock.quantity': { $gt: 0 } }],
581
583
  })),
582
584
  };
583
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, filter);
585
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
584
586
  const duration = Date.now() - start;
585
587
  expect(duration).toBeLessThan(1000); // Should complete within 1 second
586
588
  expect(results.length).toBeGreaterThan(0);
@@ -590,7 +592,7 @@ describe('QdrantVector', () => {
590
592
  const filters = [{ price: { $gt: 500 } }, { tags: { $in: ['electronics'] } }, { 'stock.quantity': { $gt: 0 } }];
591
593
  const start = Date.now();
592
594
  const results = await Promise.all(
593
- filters.map(filter => qdrant.query(testCollectionName, [1, 0, 0], 10, filter)),
595
+ filters.map(filter => qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter })),
594
596
  );
595
597
  const duration = Date.now() - start;
596
598
  expect(duration).toBeLessThan(3000); // Should complete within 3 seconds
@@ -603,12 +605,12 @@ describe('QdrantVector', () => {
603
605
  describe('Error Handling', () => {
604
606
  it('should handle non-existent index query gracefully', async () => {
605
607
  const nonExistentIndex = 'non-existent-index';
606
- await expect(qdrant.query(nonExistentIndex, [1, 0, 0])).rejects.toThrow();
608
+ await expect(qdrant.query({ indexName: nonExistentIndex, queryVector: [1, 0, 0] })).rejects.toThrow();
607
609
  }, 50000);
608
610
 
609
611
  it('should handle incorrect dimension vectors', async () => {
610
612
  const wrongDimVector = [[1, 0]]; // 2D vector for 3D index
611
- await expect(qdrant.upsert(testCollectionName, wrongDimVector)).rejects.toThrow();
613
+ await expect(qdrant.upsert({ indexName: testCollectionName, vectors: wrongDimVector })).rejects.toThrow();
612
614
  }, 50000);
613
615
  });
614
616
 
@@ -649,30 +651,30 @@ describe('QdrantVector', () => {
649
651
 
650
652
  beforeAll(async () => {
651
653
  qdrant = new QdrantVector('http://localhost:6333/');
652
- await qdrant.createIndex(testCollectionName, dimension);
653
- await qdrant.upsert(testCollectionName, filterTestVectors, filterTestMetadata);
654
+ await qdrant.createIndex({ indexName: testCollectionName, dimension });
655
+ await qdrant.upsert({ indexName: testCollectionName, vectors: filterTestVectors, metadata: filterTestMetadata });
654
656
  });
655
657
 
656
658
  afterAll(async () => {
657
659
  await qdrant.deleteIndex(testCollectionName);
658
660
  }, 50000);
659
661
  it('should handle undefined filter', async () => {
660
- const results1 = await qdrant.query(testCollectionName, [1, 0, 0], 10, undefined);
661
- const results2 = await qdrant.query(testCollectionName, [1, 0, 0], 10);
662
+ const results1 = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter: undefined });
663
+ const results2 = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0] });
662
664
  expect(results1).toEqual(results2);
663
665
  expect(results1.length).toBeGreaterThan(0);
664
666
  });
665
667
 
666
668
  it('should handle empty object filter', async () => {
667
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, {});
668
- const results2 = await qdrant.query(testCollectionName, [1, 0, 0], 10);
669
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter: {} });
670
+ const results2 = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0] });
669
671
  expect(results).toEqual(results2);
670
672
  expect(results.length).toBeGreaterThan(0);
671
673
  });
672
674
 
673
675
  it('should handle null filter', async () => {
674
- const results = await qdrant.query(testCollectionName, [1, 0, 0], 10, null as any);
675
- const results2 = await qdrant.query(testCollectionName, [1, 0, 0], 10);
676
+ const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter: null });
677
+ const results2 = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0] });
676
678
  expect(results).toEqual(results2);
677
679
  expect(results.length).toBeGreaterThan(0);
678
680
  });
@@ -681,7 +683,7 @@ describe('QdrantVector', () => {
681
683
  describe('Performance Tests', () => {
682
684
  beforeAll(async () => {
683
685
  qdrant = new QdrantVector('http://localhost:6333/');
684
- await qdrant.createIndex(testCollectionName, dimension);
686
+ await qdrant.createIndex({ indexName: testCollectionName, dimension });
685
687
  });
686
688
 
687
689
  afterAll(async () => {
@@ -700,7 +702,7 @@ describe('QdrantVector', () => {
700
702
  const metadata = vectors.map((_, i) => ({ id: i }));
701
703
 
702
704
  const start = Date.now();
703
- const ids = await qdrant.upsert(testCollectionName, vectors, metadata);
705
+ const ids = await qdrant.upsert({ indexName: testCollectionName, vectors, metadata });
704
706
  const duration = Date.now() - start;
705
707
 
706
708
  expect(ids).toHaveLength(batchSize);
@@ -714,7 +716,7 @@ describe('QdrantVector', () => {
714
716
  const start = Date.now();
715
717
  const promises = Array(numQueries)
716
718
  .fill(null)
717
- .map(() => qdrant.query(testCollectionName, queryVector));
719
+ .map(() => qdrant.query({ indexName: testCollectionName, queryVector }));
718
720
 
719
721
  const results = await Promise.all(promises);
720
722
  const duration = Date.now() - start;
@@ -723,4 +725,101 @@ describe('QdrantVector', () => {
723
725
  console.log(`${numQueries} concurrent queries took ${duration}ms`);
724
726
  }, 50000);
725
727
  });
728
+ describe('Deprecation Warnings', () => {
729
+ const indexName = 'testdeprecationwarnings';
730
+
731
+ const indexName2 = 'testdeprecationwarnings2';
732
+
733
+ let warnSpy;
734
+
735
+ beforeAll(async () => {
736
+ await qdrant.createIndex({ indexName: indexName, dimension: 3 });
737
+ });
738
+
739
+ afterAll(async () => {
740
+ await qdrant.deleteIndex(indexName);
741
+ await qdrant.deleteIndex(indexName2);
742
+ });
743
+
744
+ beforeEach(async () => {
745
+ warnSpy = vi.spyOn(qdrant['logger'], 'warn');
746
+ });
747
+
748
+ afterEach(async () => {
749
+ warnSpy.mockRestore();
750
+ await qdrant.deleteIndex(indexName2);
751
+ });
752
+
753
+ it('should show deprecation warning when using individual args for createIndex', async () => {
754
+ await qdrant.createIndex(indexName2, 3, 'cosine');
755
+
756
+ expect(warnSpy).toHaveBeenCalledWith(
757
+ expect.stringContaining('Deprecation Warning: Passing individual arguments to createIndex() is deprecated'),
758
+ );
759
+ });
760
+
761
+ it('should show deprecation warning when using individual args for upsert', async () => {
762
+ await qdrant.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
763
+
764
+ expect(warnSpy).toHaveBeenCalledWith(
765
+ expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
766
+ );
767
+ });
768
+
769
+ it('should show deprecation warning when using individual args for query', async () => {
770
+ await qdrant.query(indexName, [1, 2, 3], 5);
771
+
772
+ expect(warnSpy).toHaveBeenCalledWith(
773
+ expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
774
+ );
775
+ });
776
+
777
+ it('should not show deprecation warning when using object param for query', async () => {
778
+ await qdrant.query({
779
+ indexName,
780
+ queryVector: [1, 2, 3],
781
+ topK: 5,
782
+ });
783
+
784
+ expect(warnSpy).not.toHaveBeenCalled();
785
+ });
786
+
787
+ it('should not show deprecation warning when using object param for createIndex', async () => {
788
+ await qdrant.createIndex({
789
+ indexName: indexName2,
790
+ dimension: 3,
791
+ metric: 'cosine',
792
+ });
793
+
794
+ expect(warnSpy).not.toHaveBeenCalled();
795
+ });
796
+
797
+ it('should not show deprecation warning when using object param for upsert', async () => {
798
+ await qdrant.upsert({
799
+ indexName,
800
+ vectors: [[1, 2, 3]],
801
+ metadata: [{ test: 'data' }],
802
+ });
803
+
804
+ expect(warnSpy).not.toHaveBeenCalled();
805
+ });
806
+
807
+ it('should maintain backward compatibility with individual args', async () => {
808
+ // Query
809
+ const queryResults = await qdrant.query(indexName, [1, 2, 3], 5);
810
+ expect(Array.isArray(queryResults)).toBe(true);
811
+
812
+ // CreateIndex
813
+ await expect(qdrant.createIndex(indexName2, 3, 'cosine')).resolves.not.toThrow();
814
+
815
+ // Upsert
816
+ const upsertResults = await qdrant.upsert({
817
+ indexName,
818
+ vectors: [[1, 2, 3]],
819
+ metadata: [{ test: 'data' }],
820
+ });
821
+ expect(Array.isArray(upsertResults)).toBe(true);
822
+ expect(upsertResults).toHaveLength(1);
823
+ });
824
+ });
726
825
  });
@@ -1,6 +1,13 @@
1
- import type { Filter } from '@mastra/core/filter';
2
1
  import { MastraVector } from '@mastra/core/vector';
3
- import type { QueryResult, IndexStats } from '@mastra/core/vector';
2
+ import type {
3
+ QueryResult,
4
+ IndexStats,
5
+ CreateIndexParams,
6
+ UpsertVectorParams,
7
+ QueryVectorParams,
8
+ ParamsToArgs,
9
+ } from '@mastra/core/vector';
10
+ import type { VectorFilter } from '@mastra/core/vector/filter';
4
11
  import { QdrantClient } from '@qdrant/js-client-rest';
5
12
  import type { Schemas } from '@qdrant/js-client-rest';
6
13
 
@@ -35,12 +42,11 @@ export class QdrantVector extends MastraVector {
35
42
  }) ?? baseClient;
36
43
  }
37
44
 
38
- async upsert(
39
- indexName: string,
40
- vectors: number[][],
41
- metadata?: Record<string, any>[],
42
- ids?: string[],
43
- ): Promise<string[]> {
45
+ async upsert(...args: ParamsToArgs<UpsertVectorParams>): Promise<string[]> {
46
+ const params = this.normalizeArgs<UpsertVectorParams>('upsert', args);
47
+
48
+ const { indexName, vectors, metadata, ids } = params;
49
+
44
50
  const pointIds = ids || vectors.map(() => crypto.randomUUID());
45
51
 
46
52
  const records = vectors.map((vector, i) => ({
@@ -61,11 +67,11 @@ export class QdrantVector extends MastraVector {
61
67
  return pointIds;
62
68
  }
63
69
 
64
- async createIndex(
65
- indexName: string,
66
- dimension: number,
67
- metric: 'cosine' | 'euclidean' | 'dotproduct' = 'cosine',
68
- ): Promise<void> {
70
+ async createIndex(...args: ParamsToArgs<CreateIndexParams>): Promise<void> {
71
+ const params = this.normalizeArgs<CreateIndexParams>('createIndex', args);
72
+
73
+ const { indexName, dimension, metric = 'cosine' } = params;
74
+
69
75
  if (!Number.isInteger(dimension) || dimension <= 0) {
70
76
  throw new Error('Dimension must be a positive integer');
71
77
  }
@@ -79,19 +85,17 @@ export class QdrantVector extends MastraVector {
79
85
  });
80
86
  }
81
87
 
82
- transformFilter(filter?: Filter) {
88
+ transformFilter(filter?: VectorFilter) {
83
89
  const translator = new QdrantFilterTranslator();
84
90
  return translator.translate(filter);
85
91
  }
86
92
 
87
- async query(
88
- indexName: string,
89
- queryVector: number[],
90
- topK: number = 10,
91
- filter?: Filter,
92
- includeVector: boolean = false,
93
- ): Promise<QueryResult[]> {
94
- const translatedFilter = this.transformFilter(filter);
93
+ async query(...args: ParamsToArgs<QueryVectorParams>): Promise<QueryResult[]> {
94
+ const params = this.normalizeArgs<QueryVectorParams>('query', args);
95
+
96
+ const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;
97
+
98
+ const translatedFilter = this.transformFilter(filter) ?? {};
95
99
 
96
100
  const results = (
97
101
  await this.client.query(indexName, {