@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.
- package/.turbo/turbo-build.log +11 -6
- package/CHANGELOG.md +31 -0
- package/README.md +12 -12
- package/dist/_tsup-dts-rollup.d.cts +62 -0
- package/dist/_tsup-dts-rollup.d.ts +13 -9
- package/dist/index.cjs +342 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.js +11 -5
- package/package.json +7 -3
- package/src/vector/filter.ts +5 -5
- package/src/vector/index.test.ts +165 -66
- package/src/vector/index.ts +26 -22
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/qdrant@0.1.6-alpha.
|
|
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
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.3.6
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 7774ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.7.3
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/qdrant/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
|
-
|
|
14
|
+
Analysis will use the bundled TypeScript version 5.7.3
|
|
15
|
+
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/qdrant/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 9281ms
|
|
15
17
|
[34mCLI[39m Cleaning output folder
|
|
16
18
|
[34mESM[39m Build start
|
|
17
|
-
[
|
|
18
|
-
[32mESM[39m
|
|
19
|
+
[34mCJS[39m Build start
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m10.58 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 672ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m10.62 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ 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 {
|
|
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?:
|
|
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(
|
|
48
|
-
createIndex(
|
|
49
|
-
transformFilter(filter?:
|
|
50
|
-
query(
|
|
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;
|
package/dist/index.d.cts
ADDED
|
@@ -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(
|
|
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(
|
|
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(
|
|
290
|
-
const
|
|
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.
|
|
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.
|
|
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 ."
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseFilterTranslator } from '@mastra/core/filter';
|
|
2
|
-
import type { FieldCondition,
|
|
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?:
|
|
38
|
+
translate(filter?: VectorFilter): VectorFilter {
|
|
39
39
|
if (this.isEmpty(filter)) return filter;
|
|
40
|
-
this.validateFilter(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:
|
|
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
|
}
|
package/src/vector/index.test.ts
CHANGED
|
@@ -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,
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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(
|
|
412
|
-
|
|
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(
|
|
419
|
-
|
|
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(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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(
|
|
442
|
-
|
|
443
|
-
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
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],
|
|
661
|
-
const results2 = await qdrant.query(testCollectionName, [1, 0, 0]
|
|
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],
|
|
668
|
-
const results2 = await qdrant.query(testCollectionName, [1, 0, 0]
|
|
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],
|
|
675
|
-
const results2 = await qdrant.query(testCollectionName, [1, 0, 0]
|
|
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
|
});
|
package/src/vector/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type { Filter } from '@mastra/core/filter';
|
|
2
1
|
import { MastraVector } from '@mastra/core/vector';
|
|
3
|
-
import type {
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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?:
|
|
88
|
+
transformFilter(filter?: VectorFilter) {
|
|
83
89
|
const translator = new QdrantFilterTranslator();
|
|
84
90
|
return translator.translate(filter);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
async query(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
topK
|
|
91
|
-
|
|
92
|
-
|
|
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, {
|