@mastra/qdrant 0.10.4-alpha.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +41 -0
- package/dist/_tsup-dts-rollup.d.cts +80 -4
- package/dist/_tsup-dts-rollup.d.ts +80 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/src/vector/filter.test.ts +40 -40
- package/src/vector/filter.ts +103 -5
- package/src/vector/index.test.ts +32 -27
- package/src/vector/index.ts +5 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/qdrant@0.
|
|
2
|
+
> @mastra/qdrant@0.11.0-alpha.1 build /home/runner/work/mastra/mastra/stores/qdrant
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
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.5.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 6681ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.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.8.3
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/qdrant/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 8341ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m21.00 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 768ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m21.23 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 775ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @mastra/qdrant
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8a3bfd2: Update peerdeps to latest core
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 0e17048: Throw mastra errors in storage packages
|
|
12
|
+
- Updated dependencies [15e9d26]
|
|
13
|
+
- Updated dependencies [d1baedb]
|
|
14
|
+
- Updated dependencies [d8f2d19]
|
|
15
|
+
- Updated dependencies [4d21bf2]
|
|
16
|
+
- Updated dependencies [07d6d88]
|
|
17
|
+
- Updated dependencies [9d52b17]
|
|
18
|
+
- Updated dependencies [2097952]
|
|
19
|
+
- Updated dependencies [792c4c0]
|
|
20
|
+
- Updated dependencies [5d74aab]
|
|
21
|
+
- Updated dependencies [a8b194f]
|
|
22
|
+
- Updated dependencies [4fb0cc2]
|
|
23
|
+
- Updated dependencies [d2a7a31]
|
|
24
|
+
- Updated dependencies [502fe05]
|
|
25
|
+
- Updated dependencies [144eb0b]
|
|
26
|
+
- Updated dependencies [8ba1b51]
|
|
27
|
+
- Updated dependencies [4efcfa0]
|
|
28
|
+
- Updated dependencies [0e17048]
|
|
29
|
+
- @mastra/core@0.10.7
|
|
30
|
+
|
|
31
|
+
## 0.11.0-alpha.1
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- 8a3bfd2: Update peerdeps to latest core
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies [792c4c0]
|
|
40
|
+
- Updated dependencies [502fe05]
|
|
41
|
+
- Updated dependencies [4efcfa0]
|
|
42
|
+
- @mastra/core@0.10.7-alpha.3
|
|
43
|
+
|
|
3
44
|
## 0.10.4-alpha.0
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
+
import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
|
|
2
3
|
import type { CreateIndexParams } from '@mastra/core/vector';
|
|
3
4
|
import type { DeleteIndexParams } from '@mastra/core/vector';
|
|
4
5
|
import type { DeleteVectorParams } from '@mastra/core/vector';
|
|
5
6
|
import type { DescribeIndexParams } from '@mastra/core/vector';
|
|
6
7
|
import type { IndexStats } from '@mastra/core/vector';
|
|
7
8
|
import type { LogicalOperator } from '@mastra/core/vector/filter';
|
|
9
|
+
import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
|
|
8
10
|
import { MastraVector } from '@mastra/core/vector';
|
|
9
11
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
12
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
10
13
|
import type { QueryResult } from '@mastra/core/vector';
|
|
11
14
|
import type { QueryVectorParams } from '@mastra/core/vector';
|
|
12
15
|
import type { UpdateVectorParams } from '@mastra/core/vector';
|
|
@@ -21,6 +24,8 @@ declare const QDRANT_PROMPT = "When querying Qdrant, you can ONLY use the operat
|
|
|
21
24
|
export { QDRANT_PROMPT }
|
|
22
25
|
export { QDRANT_PROMPT as QDRANT_PROMPT_alias_1 }
|
|
23
26
|
|
|
27
|
+
declare type QdrantBlacklistedRootOperators = BlacklistedRootOperators | '$count' | '$geo' | '$nested' | '$datetime' | '$null' | '$empty';
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Translates MongoDB-style filters to Qdrant compatible filters.
|
|
26
31
|
*
|
|
@@ -40,10 +45,10 @@ export { QDRANT_PROMPT as QDRANT_PROMPT_alias_1 }
|
|
|
40
45
|
* - $null -> is_null check
|
|
41
46
|
* - $empty -> is_empty check
|
|
42
47
|
*/
|
|
43
|
-
export declare class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
48
|
+
export declare class QdrantFilterTranslator extends BaseFilterTranslator<QdrantVectorFilter> {
|
|
44
49
|
protected isLogicalOperator(key: string): key is LogicalOperator;
|
|
45
50
|
protected getSupportedOperators(): OperatorSupport;
|
|
46
|
-
translate(filter?:
|
|
51
|
+
translate(filter?: QdrantVectorFilter): QdrantVectorFilter;
|
|
47
52
|
private createCondition;
|
|
48
53
|
private translateNode;
|
|
49
54
|
private buildFinalConditions;
|
|
@@ -56,6 +61,75 @@ export declare class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
|
56
61
|
private normalizeDatetimeRange;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
declare type QdrantLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
65
|
+
|
|
66
|
+
declare type QdrantOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch' | '$all'> & {
|
|
67
|
+
/**
|
|
68
|
+
* $count: Filter by array length or value count.
|
|
69
|
+
* Example: { tags: { $count: { gt: 2 } } }
|
|
70
|
+
*/
|
|
71
|
+
$count: {
|
|
72
|
+
$gt?: number;
|
|
73
|
+
$gte?: number;
|
|
74
|
+
$lt?: number;
|
|
75
|
+
$lte?: number;
|
|
76
|
+
$eq?: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* $geo: Geospatial filter.
|
|
80
|
+
* Example: { location: { $geo: { type: 'geo_radius', center: [lon, lat], radius: 1000 } } }
|
|
81
|
+
*/
|
|
82
|
+
$geo: {
|
|
83
|
+
type: string;
|
|
84
|
+
[key: string]: any;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* $hasId: Filter by point IDs.
|
|
88
|
+
* Allowed at root level.
|
|
89
|
+
* Example: { $hasId: '123' } or { $hasId: ['123', '456'] }
|
|
90
|
+
*/
|
|
91
|
+
$hasId: string | string[];
|
|
92
|
+
/**
|
|
93
|
+
* $nested: Nested object filter.
|
|
94
|
+
* Example: { metadata: { $nested: { key: 'foo', filter: { $eq: 'bar' } } } }
|
|
95
|
+
*/
|
|
96
|
+
$nested: {
|
|
97
|
+
[key: string]: any;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* $hasVector: Filter by vector existence or field.
|
|
101
|
+
* Allowed at root level.
|
|
102
|
+
* Example: { $hasVector: true } or { $hasVector: 'vector_field' }
|
|
103
|
+
*/
|
|
104
|
+
$hasVector: boolean | string;
|
|
105
|
+
/**
|
|
106
|
+
* $datetime: RFC 3339 datetime range.
|
|
107
|
+
* Example: { createdAt: { $datetime: { gte: '2024-01-01T00:00:00Z' } } }
|
|
108
|
+
*/
|
|
109
|
+
$datetime: {
|
|
110
|
+
key?: string;
|
|
111
|
+
range?: {
|
|
112
|
+
gt?: Date | string;
|
|
113
|
+
gte?: Date | string;
|
|
114
|
+
lt?: Date | string;
|
|
115
|
+
lte?: Date | string;
|
|
116
|
+
eq?: Date | string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* $null: Check if a field is null.
|
|
121
|
+
* Example: { metadata: { $null: true } }
|
|
122
|
+
*/
|
|
123
|
+
$null: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* $empty: Check if an array or object field is empty.
|
|
126
|
+
* Example: { tags: { $empty: true } }
|
|
127
|
+
*/
|
|
128
|
+
$empty: boolean;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
declare type QdrantQueryVectorParams = QueryVectorParams<QdrantVectorFilter>;
|
|
132
|
+
|
|
59
133
|
declare class QdrantVector extends MastraVector {
|
|
60
134
|
private client;
|
|
61
135
|
/**
|
|
@@ -71,8 +145,8 @@ declare class QdrantVector extends MastraVector {
|
|
|
71
145
|
});
|
|
72
146
|
upsert({ indexName, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]>;
|
|
73
147
|
createIndex({ indexName, dimension, metric }: CreateIndexParams): Promise<void>;
|
|
74
|
-
transformFilter(filter?:
|
|
75
|
-
query({ indexName, queryVector, topK, filter, includeVector, }:
|
|
148
|
+
transformFilter(filter?: QdrantVectorFilter): QdrantVectorFilter;
|
|
149
|
+
query({ indexName, queryVector, topK, filter, includeVector, }: QdrantQueryVectorParams): Promise<QueryResult[]>;
|
|
76
150
|
listIndexes(): Promise<string[]>;
|
|
77
151
|
/**
|
|
78
152
|
* Retrieves statistics about a vector index.
|
|
@@ -135,4 +209,6 @@ declare class QdrantVector extends MastraVector {
|
|
|
135
209
|
export { QdrantVector }
|
|
136
210
|
export { QdrantVector as QdrantVector_alias_1 }
|
|
137
211
|
|
|
212
|
+
export declare type QdrantVectorFilter = VectorFilter<keyof QdrantOperatorValueMap, QdrantOperatorValueMap, QdrantLogicalOperatorValueMap, QdrantBlacklistedRootOperators>;
|
|
213
|
+
|
|
138
214
|
export { }
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
+
import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
|
|
2
3
|
import type { CreateIndexParams } from '@mastra/core/vector';
|
|
3
4
|
import type { DeleteIndexParams } from '@mastra/core/vector';
|
|
4
5
|
import type { DeleteVectorParams } from '@mastra/core/vector';
|
|
5
6
|
import type { DescribeIndexParams } from '@mastra/core/vector';
|
|
6
7
|
import type { IndexStats } from '@mastra/core/vector';
|
|
7
8
|
import type { LogicalOperator } from '@mastra/core/vector/filter';
|
|
9
|
+
import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
|
|
8
10
|
import { MastraVector } from '@mastra/core/vector';
|
|
9
11
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
12
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
10
13
|
import type { QueryResult } from '@mastra/core/vector';
|
|
11
14
|
import type { QueryVectorParams } from '@mastra/core/vector';
|
|
12
15
|
import type { UpdateVectorParams } from '@mastra/core/vector';
|
|
@@ -21,6 +24,8 @@ declare const QDRANT_PROMPT = "When querying Qdrant, you can ONLY use the operat
|
|
|
21
24
|
export { QDRANT_PROMPT }
|
|
22
25
|
export { QDRANT_PROMPT as QDRANT_PROMPT_alias_1 }
|
|
23
26
|
|
|
27
|
+
declare type QdrantBlacklistedRootOperators = BlacklistedRootOperators | '$count' | '$geo' | '$nested' | '$datetime' | '$null' | '$empty';
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Translates MongoDB-style filters to Qdrant compatible filters.
|
|
26
31
|
*
|
|
@@ -40,10 +45,10 @@ export { QDRANT_PROMPT as QDRANT_PROMPT_alias_1 }
|
|
|
40
45
|
* - $null -> is_null check
|
|
41
46
|
* - $empty -> is_empty check
|
|
42
47
|
*/
|
|
43
|
-
export declare class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
48
|
+
export declare class QdrantFilterTranslator extends BaseFilterTranslator<QdrantVectorFilter> {
|
|
44
49
|
protected isLogicalOperator(key: string): key is LogicalOperator;
|
|
45
50
|
protected getSupportedOperators(): OperatorSupport;
|
|
46
|
-
translate(filter?:
|
|
51
|
+
translate(filter?: QdrantVectorFilter): QdrantVectorFilter;
|
|
47
52
|
private createCondition;
|
|
48
53
|
private translateNode;
|
|
49
54
|
private buildFinalConditions;
|
|
@@ -56,6 +61,75 @@ export declare class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
|
56
61
|
private normalizeDatetimeRange;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
declare type QdrantLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
65
|
+
|
|
66
|
+
declare type QdrantOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch' | '$all'> & {
|
|
67
|
+
/**
|
|
68
|
+
* $count: Filter by array length or value count.
|
|
69
|
+
* Example: { tags: { $count: { gt: 2 } } }
|
|
70
|
+
*/
|
|
71
|
+
$count: {
|
|
72
|
+
$gt?: number;
|
|
73
|
+
$gte?: number;
|
|
74
|
+
$lt?: number;
|
|
75
|
+
$lte?: number;
|
|
76
|
+
$eq?: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* $geo: Geospatial filter.
|
|
80
|
+
* Example: { location: { $geo: { type: 'geo_radius', center: [lon, lat], radius: 1000 } } }
|
|
81
|
+
*/
|
|
82
|
+
$geo: {
|
|
83
|
+
type: string;
|
|
84
|
+
[key: string]: any;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* $hasId: Filter by point IDs.
|
|
88
|
+
* Allowed at root level.
|
|
89
|
+
* Example: { $hasId: '123' } or { $hasId: ['123', '456'] }
|
|
90
|
+
*/
|
|
91
|
+
$hasId: string | string[];
|
|
92
|
+
/**
|
|
93
|
+
* $nested: Nested object filter.
|
|
94
|
+
* Example: { metadata: { $nested: { key: 'foo', filter: { $eq: 'bar' } } } }
|
|
95
|
+
*/
|
|
96
|
+
$nested: {
|
|
97
|
+
[key: string]: any;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* $hasVector: Filter by vector existence or field.
|
|
101
|
+
* Allowed at root level.
|
|
102
|
+
* Example: { $hasVector: true } or { $hasVector: 'vector_field' }
|
|
103
|
+
*/
|
|
104
|
+
$hasVector: boolean | string;
|
|
105
|
+
/**
|
|
106
|
+
* $datetime: RFC 3339 datetime range.
|
|
107
|
+
* Example: { createdAt: { $datetime: { gte: '2024-01-01T00:00:00Z' } } }
|
|
108
|
+
*/
|
|
109
|
+
$datetime: {
|
|
110
|
+
key?: string;
|
|
111
|
+
range?: {
|
|
112
|
+
gt?: Date | string;
|
|
113
|
+
gte?: Date | string;
|
|
114
|
+
lt?: Date | string;
|
|
115
|
+
lte?: Date | string;
|
|
116
|
+
eq?: Date | string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* $null: Check if a field is null.
|
|
121
|
+
* Example: { metadata: { $null: true } }
|
|
122
|
+
*/
|
|
123
|
+
$null: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* $empty: Check if an array or object field is empty.
|
|
126
|
+
* Example: { tags: { $empty: true } }
|
|
127
|
+
*/
|
|
128
|
+
$empty: boolean;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
declare type QdrantQueryVectorParams = QueryVectorParams<QdrantVectorFilter>;
|
|
132
|
+
|
|
59
133
|
declare class QdrantVector extends MastraVector {
|
|
60
134
|
private client;
|
|
61
135
|
/**
|
|
@@ -71,8 +145,8 @@ declare class QdrantVector extends MastraVector {
|
|
|
71
145
|
});
|
|
72
146
|
upsert({ indexName, vectors, metadata, ids }: UpsertVectorParams): Promise<string[]>;
|
|
73
147
|
createIndex({ indexName, dimension, metric }: CreateIndexParams): Promise<void>;
|
|
74
|
-
transformFilter(filter?:
|
|
75
|
-
query({ indexName, queryVector, topK, filter, includeVector, }:
|
|
148
|
+
transformFilter(filter?: QdrantVectorFilter): QdrantVectorFilter;
|
|
149
|
+
query({ indexName, queryVector, topK, filter, includeVector, }: QdrantQueryVectorParams): Promise<QueryResult[]>;
|
|
76
150
|
listIndexes(): Promise<string[]>;
|
|
77
151
|
/**
|
|
78
152
|
* Retrieves statistics about a vector index.
|
|
@@ -135,4 +209,6 @@ declare class QdrantVector extends MastraVector {
|
|
|
135
209
|
export { QdrantVector }
|
|
136
210
|
export { QdrantVector as QdrantVector_alias_1 }
|
|
137
211
|
|
|
212
|
+
export declare type QdrantVectorFilter = VectorFilter<keyof QdrantOperatorValueMap, QdrantOperatorValueMap, QdrantLogicalOperatorValueMap, QdrantBlacklistedRootOperators>;
|
|
213
|
+
|
|
138
214
|
export { }
|
package/dist/index.cjs
CHANGED
|
@@ -29,7 +29,7 @@ var QdrantFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
29
29
|
return fieldKey ? { key: fieldKey, ...condition } : condition;
|
|
30
30
|
}
|
|
31
31
|
translateNode(node, isNested = false, fieldKey) {
|
|
32
|
-
if (!this.isEmpty(node) && typeof node === "object" && "must" in node) {
|
|
32
|
+
if (!this.isEmpty(node) && !!node && typeof node === "object" && "must" in node) {
|
|
33
33
|
return node;
|
|
34
34
|
}
|
|
35
35
|
if (this.isPrimitive(node)) {
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var QdrantFilterTranslator = class extends BaseFilterTranslator {
|
|
|
27
27
|
return fieldKey ? { key: fieldKey, ...condition } : condition;
|
|
28
28
|
}
|
|
29
29
|
translateNode(node, isNested = false, fieldKey) {
|
|
30
|
-
if (!this.isEmpty(node) && typeof node === "object" && "must" in node) {
|
|
30
|
+
if (!this.isEmpty(node) && !!node && typeof node === "object" && "must" in node) {
|
|
31
31
|
return node;
|
|
32
32
|
}
|
|
33
33
|
if (this.isPrimitive(node)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/qdrant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Qdrant vector store provider for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@microsoft/api-extractor": "^7.52.8",
|
|
27
27
|
"@types/node": "^20.19.0",
|
|
28
|
-
"eslint": "^9.
|
|
28
|
+
"eslint": "^9.29.0",
|
|
29
29
|
"tsup": "^8.5.0",
|
|
30
30
|
"typescript": "^5.8.3",
|
|
31
31
|
"vitest": "^3.2.3",
|
|
32
|
-
"@internal/lint": "0.0.
|
|
33
|
-
"@mastra/core": "0.10.7
|
|
32
|
+
"@internal/lint": "0.0.14",
|
|
33
|
+
"@mastra/core": "0.10.7"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@mastra/core": ">=0.10.
|
|
36
|
+
"@mastra/core": ">=0.10.7-0 <0.11.0-0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
2
|
+
import type { QdrantVectorFilter } from './filter';
|
|
3
3
|
import { QdrantFilterTranslator } from './filter';
|
|
4
4
|
|
|
5
5
|
describe('QdrantFilterTranslator', () => {
|
|
@@ -7,7 +7,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
7
7
|
|
|
8
8
|
describe('Basic Operators', () => {
|
|
9
9
|
it('should translate direct value match', () => {
|
|
10
|
-
const filter = { field: 'value' };
|
|
10
|
+
const filter: QdrantVectorFilter = { field: 'value' };
|
|
11
11
|
const expected = { must: [{ key: 'field', match: { value: 'value' } }] };
|
|
12
12
|
expect(translator.translate(filter)).toEqual(expected);
|
|
13
13
|
});
|
|
@@ -63,7 +63,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it('should handle multiple comparison operators on same field', () => {
|
|
66
|
-
const filter = { field: { $gt: 10, $lt: 20 } };
|
|
66
|
+
const filter: QdrantVectorFilter = { field: { $gt: 10, $lt: 20 } };
|
|
67
67
|
const expected = { must: [{ key: 'field', range: { gt: 10, lt: 20 } }] };
|
|
68
68
|
expect(translator.translate(filter)).toEqual(expected);
|
|
69
69
|
});
|
|
@@ -71,7 +71,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
71
71
|
|
|
72
72
|
describe('Logical Operators', () => {
|
|
73
73
|
it('should translate $and operator', () => {
|
|
74
|
-
const filter = {
|
|
74
|
+
const filter: QdrantVectorFilter = {
|
|
75
75
|
$and: [{ field1: 'value1' }, { field2: { $gt: 100 } }],
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -86,7 +86,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('should translate $or operator', () => {
|
|
89
|
-
const filter = {
|
|
89
|
+
const filter: QdrantVectorFilter = {
|
|
90
90
|
$or: [{ field1: 'value1' }, { field2: { $lt: 100 } }],
|
|
91
91
|
};
|
|
92
92
|
|
|
@@ -101,7 +101,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('should translate $not operator', () => {
|
|
104
|
-
const filter = {
|
|
104
|
+
const filter: QdrantVectorFilter = {
|
|
105
105
|
$not: { field: 'value' },
|
|
106
106
|
};
|
|
107
107
|
|
|
@@ -113,7 +113,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it('should handle nested logical operators', () => {
|
|
116
|
-
const filter = {
|
|
116
|
+
const filter: QdrantVectorFilter = {
|
|
117
117
|
$and: [
|
|
118
118
|
{ 'user.age': { $gte: 18 } },
|
|
119
119
|
{
|
|
@@ -155,7 +155,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
it('should handle nested must_not operators', () => {
|
|
158
|
-
const filter = {
|
|
158
|
+
const filter: QdrantVectorFilter = {
|
|
159
159
|
$not: {
|
|
160
160
|
$not: { field: 'value' },
|
|
161
161
|
},
|
|
@@ -171,7 +171,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
it('should handle complex logical combinations with ranges', () => {
|
|
174
|
-
const filter = {
|
|
174
|
+
const filter: QdrantVectorFilter = {
|
|
175
175
|
$or: [
|
|
176
176
|
{ $and: [{ price: { $gte: 100, $lt: 200 } }, { stock: { $gt: 0 } }] },
|
|
177
177
|
{ $and: [{ price: { $lt: 100 } }, { featured: true }] },
|
|
@@ -199,13 +199,13 @@ describe('QdrantFilterTranslator', () => {
|
|
|
199
199
|
|
|
200
200
|
describe('Custom Operators', () => {
|
|
201
201
|
it('should translate $count operator', () => {
|
|
202
|
-
const filter = { field: { $count: { $gt: 5 } } };
|
|
202
|
+
const filter: QdrantVectorFilter = { field: { $count: { $gt: 5 } } };
|
|
203
203
|
const expected = { must: [{ key: 'field', values_count: { gt: 5 } }] };
|
|
204
204
|
expect(translator.translate(filter)).toEqual(expected);
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
it('should translate $geo operator with radius', () => {
|
|
208
|
-
const filter = {
|
|
208
|
+
const filter: QdrantVectorFilter = {
|
|
209
209
|
location: {
|
|
210
210
|
$geo: {
|
|
211
211
|
type: 'radius',
|
|
@@ -229,7 +229,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
229
229
|
});
|
|
230
230
|
|
|
231
231
|
it('should translate $geo operator with bounding box', () => {
|
|
232
|
-
const filter = {
|
|
232
|
+
const filter: QdrantVectorFilter = {
|
|
233
233
|
location: {
|
|
234
234
|
$geo: {
|
|
235
235
|
type: 'box',
|
|
@@ -255,7 +255,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
it('should translate $geo operator with polygon', () => {
|
|
258
|
-
const filter = {
|
|
258
|
+
const filter: QdrantVectorFilter = {
|
|
259
259
|
location: {
|
|
260
260
|
$geo: {
|
|
261
261
|
type: 'polygon',
|
|
@@ -286,7 +286,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
it('should translate $nested operator', () => {
|
|
289
|
-
const filter = {
|
|
289
|
+
const filter: QdrantVectorFilter = {
|
|
290
290
|
diet: {
|
|
291
291
|
$nested: {
|
|
292
292
|
food: 'meat',
|
|
@@ -322,7 +322,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
322
322
|
|
|
323
323
|
it('should translate $datetime operator', () => {
|
|
324
324
|
const now = new Date();
|
|
325
|
-
const filter = {
|
|
325
|
+
const filter: QdrantVectorFilter = {
|
|
326
326
|
timestamp: {
|
|
327
327
|
$datetime: {
|
|
328
328
|
range: {
|
|
@@ -357,7 +357,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
357
357
|
});
|
|
358
358
|
|
|
359
359
|
it('should handle nested $count with multiple conditions', () => {
|
|
360
|
-
const filter = { 'array.items': { $count: { $gt: 5, $lt: 10 } } };
|
|
360
|
+
const filter: QdrantVectorFilter = { 'array.items': { $count: { $gt: 5, $lt: 10 } } };
|
|
361
361
|
const expected = {
|
|
362
362
|
must: [
|
|
363
363
|
{
|
|
@@ -370,7 +370,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
370
370
|
});
|
|
371
371
|
|
|
372
372
|
it('should translate $nested operator with complex conditions', () => {
|
|
373
|
-
const filter = {
|
|
373
|
+
const filter: QdrantVectorFilter = {
|
|
374
374
|
nested_field: {
|
|
375
375
|
$nested: {
|
|
376
376
|
inner_field: { $gt: 100 },
|
|
@@ -399,7 +399,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
399
399
|
|
|
400
400
|
it('should translate $datetime operator with multiple range conditions', () => {
|
|
401
401
|
const now = new Date();
|
|
402
|
-
const filter = {
|
|
402
|
+
const filter: QdrantVectorFilter = {
|
|
403
403
|
timestamp: {
|
|
404
404
|
$datetime: {
|
|
405
405
|
range: {
|
|
@@ -430,7 +430,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
430
430
|
});
|
|
431
431
|
|
|
432
432
|
it('should translate $datetime operator with string dates', () => {
|
|
433
|
-
const filter = {
|
|
433
|
+
const filter: QdrantVectorFilter = {
|
|
434
434
|
timestamp: {
|
|
435
435
|
$datetime: {
|
|
436
436
|
key: 'timestamp',
|
|
@@ -458,7 +458,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
458
458
|
});
|
|
459
459
|
|
|
460
460
|
it('should translate complex $nested operator', () => {
|
|
461
|
-
const filter = {
|
|
461
|
+
const filter: QdrantVectorFilter = {
|
|
462
462
|
diet: {
|
|
463
463
|
$nested: {
|
|
464
464
|
food: { $in: ['meat', 'fish'] },
|
|
@@ -491,19 +491,19 @@ describe('QdrantFilterTranslator', () => {
|
|
|
491
491
|
|
|
492
492
|
describe('Special Cases', () => {
|
|
493
493
|
it('should handle nested paths', () => {
|
|
494
|
-
const filter = { 'obj.field': 'value' };
|
|
494
|
+
const filter: QdrantVectorFilter = { 'obj.field': 'value' };
|
|
495
495
|
const expected = { must: [{ key: 'obj.field', match: { value: 'value' } }] };
|
|
496
496
|
expect(translator.translate(filter)).toEqual(expected);
|
|
497
497
|
});
|
|
498
498
|
|
|
499
499
|
it('should handle deep nested paths', () => {
|
|
500
|
-
const filter = { 'a.b.c.d': { $gt: 100 } };
|
|
500
|
+
const filter: QdrantVectorFilter = { 'a.b.c.d': { $gt: 100 } };
|
|
501
501
|
const expected = { must: [{ key: 'a.b.c.d', range: { gt: 100 } }] };
|
|
502
502
|
expect(translator.translate(filter)).toEqual(expected);
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
it('should handle complex combinations', () => {
|
|
506
|
-
const filter = {
|
|
506
|
+
const filter: QdrantVectorFilter = {
|
|
507
507
|
$and: [
|
|
508
508
|
{ 'user.age': { $gte: 18 } },
|
|
509
509
|
{
|
|
@@ -534,7 +534,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
534
534
|
});
|
|
535
535
|
|
|
536
536
|
it('should handle multiple nested paths with same prefix', () => {
|
|
537
|
-
const filter = {
|
|
537
|
+
const filter: QdrantVectorFilter = {
|
|
538
538
|
$and: [{ 'user.profile.age': { $gte: 18 } }, { 'user.profile.name': 'John' }],
|
|
539
539
|
};
|
|
540
540
|
|
|
@@ -549,7 +549,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
549
549
|
});
|
|
550
550
|
|
|
551
551
|
it('should handle mixed array and object paths', () => {
|
|
552
|
-
const filter = {
|
|
552
|
+
const filter: QdrantVectorFilter = {
|
|
553
553
|
'items[].category': { $in: ['A', 'B'] },
|
|
554
554
|
'items[].details.price': { $gt: 100 },
|
|
555
555
|
};
|
|
@@ -565,7 +565,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
565
565
|
});
|
|
566
566
|
|
|
567
567
|
it('should handle empty logical operators in combination', () => {
|
|
568
|
-
const filter = {
|
|
568
|
+
const filter: QdrantVectorFilter = {
|
|
569
569
|
$and: [{ $or: [] }, { field: 'value' }],
|
|
570
570
|
};
|
|
571
571
|
const expected = {
|
|
@@ -575,7 +575,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
575
575
|
});
|
|
576
576
|
|
|
577
577
|
it('should handle deeply nested paths with array notation', () => {
|
|
578
|
-
const filter = {
|
|
578
|
+
const filter: QdrantVectorFilter = {
|
|
579
579
|
'users[].addresses[].geo.location': {
|
|
580
580
|
$geo: {
|
|
581
581
|
type: 'radius',
|
|
@@ -599,7 +599,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
599
599
|
});
|
|
600
600
|
|
|
601
601
|
it('should handle array paths with multiple levels', () => {
|
|
602
|
-
const filter = {
|
|
602
|
+
const filter: QdrantVectorFilter = {
|
|
603
603
|
'users[].addresses[].location[].coordinates': { $gt: 100 },
|
|
604
604
|
};
|
|
605
605
|
const expected = {
|
|
@@ -614,7 +614,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
614
614
|
});
|
|
615
615
|
|
|
616
616
|
it('should handle combination of array and object paths', () => {
|
|
617
|
-
const filter = {
|
|
617
|
+
const filter: QdrantVectorFilter = {
|
|
618
618
|
'users[].profile.addresses[].location.coordinates': { $gt: 100 },
|
|
619
619
|
};
|
|
620
620
|
const expected = {
|
|
@@ -629,7 +629,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
629
629
|
});
|
|
630
630
|
|
|
631
631
|
it('should handle multiple conditions with same array path', () => {
|
|
632
|
-
const filter = {
|
|
632
|
+
const filter: QdrantVectorFilter = {
|
|
633
633
|
$and: [
|
|
634
634
|
{ 'items[].price': { $gt: 100 } },
|
|
635
635
|
{ 'items[].quantity': { $gt: 0 } },
|
|
@@ -654,7 +654,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
654
654
|
});
|
|
655
655
|
|
|
656
656
|
it('should throw error for invalid geo filter type', () => {
|
|
657
|
-
const filter = {
|
|
657
|
+
const filter: QdrantVectorFilter = {
|
|
658
658
|
location: {
|
|
659
659
|
$geo: {
|
|
660
660
|
type: 'invalid',
|
|
@@ -667,7 +667,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
667
667
|
});
|
|
668
668
|
|
|
669
669
|
it('should throw error for invalid custom operator', () => {
|
|
670
|
-
const filter = { $invalidCustom: 'value' };
|
|
670
|
+
const filter: QdrantVectorFilter = { $invalidCustom: 'value' };
|
|
671
671
|
expect(() => translator.translate(filter)).toThrow();
|
|
672
672
|
});
|
|
673
673
|
});
|
|
@@ -686,7 +686,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
686
686
|
});
|
|
687
687
|
|
|
688
688
|
it('should validate array operator values', () => {
|
|
689
|
-
const invalidFilters = [
|
|
689
|
+
const invalidFilters: any = [
|
|
690
690
|
{ field: { $in: 'not-an-array' } }, // Should be array
|
|
691
691
|
{ field: { $nin: 123 } }, // Should be array
|
|
692
692
|
{ field: { $in: {} } }, // Should be array
|
|
@@ -697,14 +697,14 @@ describe('QdrantFilterTranslator', () => {
|
|
|
697
697
|
});
|
|
698
698
|
});
|
|
699
699
|
it('throws error for non-logical operators at top level', () => {
|
|
700
|
-
const invalidFilters = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
|
|
700
|
+
const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
|
|
701
701
|
|
|
702
702
|
invalidFilters.forEach(filter => {
|
|
703
703
|
expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
|
|
704
704
|
});
|
|
705
705
|
});
|
|
706
706
|
it('allows logical operators at top level', () => {
|
|
707
|
-
const validFilters = [{ $and: [{ field: 'value' }] }, { $or: [{ field: 'value' }] }];
|
|
707
|
+
const validFilters: QdrantVectorFilter[] = [{ $and: [{ field: 'value' }] }, { $or: [{ field: 'value' }] }];
|
|
708
708
|
|
|
709
709
|
validFilters.forEach(filter => {
|
|
710
710
|
expect(() => translator.translate(filter)).not.toThrow();
|
|
@@ -732,7 +732,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
732
732
|
});
|
|
733
733
|
|
|
734
734
|
it('should wrap multiple field conditions in single must', () => {
|
|
735
|
-
const filter = {
|
|
735
|
+
const filter: QdrantVectorFilter = {
|
|
736
736
|
field1: 'value1',
|
|
737
737
|
field2: 'value2',
|
|
738
738
|
};
|
|
@@ -746,7 +746,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
746
746
|
});
|
|
747
747
|
|
|
748
748
|
it('should wrap complex single field conditions', () => {
|
|
749
|
-
const filter = {
|
|
749
|
+
const filter: QdrantVectorFilter = {
|
|
750
750
|
field: {
|
|
751
751
|
$gt: 10,
|
|
752
752
|
$lt: 20,
|
|
@@ -771,7 +771,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
771
771
|
|
|
772
772
|
describe('No Must Wrapper Cases', () => {
|
|
773
773
|
it('should not wrap nested logical operators', () => {
|
|
774
|
-
const filter = {
|
|
774
|
+
const filter: QdrantVectorFilter = {
|
|
775
775
|
$and: [{ field1: 'value1' }, { field2: 'value2' }],
|
|
776
776
|
};
|
|
777
777
|
const expected = {
|
|
@@ -791,7 +791,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
791
791
|
});
|
|
792
792
|
|
|
793
793
|
it('should preserve existing logical structure', () => {
|
|
794
|
-
const filter = {
|
|
794
|
+
const filter: QdrantVectorFilter = {
|
|
795
795
|
$or: [{ field1: 'value1' }, { $and: [{ field2: 'value2' }, { field3: 'value3' }] }],
|
|
796
796
|
};
|
|
797
797
|
const expected = {
|
|
@@ -826,7 +826,7 @@ describe('QdrantFilterTranslator', () => {
|
|
|
826
826
|
});
|
|
827
827
|
|
|
828
828
|
it('should handle regex in nested conditions', () => {
|
|
829
|
-
const filter = {
|
|
829
|
+
const filter: QdrantVectorFilter = {
|
|
830
830
|
diet: {
|
|
831
831
|
$nested: {
|
|
832
832
|
food: { $regex: 'meat' },
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
VectorFilter,
|
|
4
|
+
LogicalOperator,
|
|
5
|
+
OperatorSupport,
|
|
6
|
+
OperatorValueMap,
|
|
7
|
+
LogicalOperatorValueMap,
|
|
8
|
+
BlacklistedRootOperators,
|
|
9
|
+
} from '@mastra/core/vector/filter';
|
|
10
|
+
|
|
11
|
+
type QdrantOperatorValueMap = Omit<OperatorValueMap, '$options' | '$elemMatch' | '$all'> & {
|
|
12
|
+
/**
|
|
13
|
+
* $count: Filter by array length or value count.
|
|
14
|
+
* Example: { tags: { $count: { gt: 2 } } }
|
|
15
|
+
*/
|
|
16
|
+
$count: {
|
|
17
|
+
$gt?: number;
|
|
18
|
+
$gte?: number;
|
|
19
|
+
$lt?: number;
|
|
20
|
+
$lte?: number;
|
|
21
|
+
$eq?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* $geo: Geospatial filter.
|
|
26
|
+
* Example: { location: { $geo: { type: 'geo_radius', center: [lon, lat], radius: 1000 } } }
|
|
27
|
+
*/
|
|
28
|
+
$geo: {
|
|
29
|
+
type: string;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* $hasId: Filter by point IDs.
|
|
35
|
+
* Allowed at root level.
|
|
36
|
+
* Example: { $hasId: '123' } or { $hasId: ['123', '456'] }
|
|
37
|
+
*/
|
|
38
|
+
$hasId: string | string[];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* $nested: Nested object filter.
|
|
42
|
+
* Example: { metadata: { $nested: { key: 'foo', filter: { $eq: 'bar' } } } }
|
|
43
|
+
*/
|
|
44
|
+
$nested: {
|
|
45
|
+
// Additional properties depend on the nested object structure
|
|
46
|
+
[key: string]: any;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* $hasVector: Filter by vector existence or field.
|
|
51
|
+
* Allowed at root level.
|
|
52
|
+
* Example: { $hasVector: true } or { $hasVector: 'vector_field' }
|
|
53
|
+
*/
|
|
54
|
+
$hasVector: boolean | string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* $datetime: RFC 3339 datetime range.
|
|
58
|
+
* Example: { createdAt: { $datetime: { gte: '2024-01-01T00:00:00Z' } } }
|
|
59
|
+
*/
|
|
60
|
+
$datetime: {
|
|
61
|
+
key?: string;
|
|
62
|
+
range?: {
|
|
63
|
+
gt?: Date | string;
|
|
64
|
+
gte?: Date | string;
|
|
65
|
+
lt?: Date | string;
|
|
66
|
+
lte?: Date | string;
|
|
67
|
+
eq?: Date | string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* $null: Check if a field is null.
|
|
73
|
+
* Example: { metadata: { $null: true } }
|
|
74
|
+
*/
|
|
75
|
+
$null: boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* $empty: Check if an array or object field is empty.
|
|
79
|
+
* Example: { tags: { $empty: true } }
|
|
80
|
+
*/
|
|
81
|
+
$empty: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type QdrantLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
85
|
+
|
|
86
|
+
type QdrantBlacklistedRootOperators =
|
|
87
|
+
| BlacklistedRootOperators
|
|
88
|
+
| '$count'
|
|
89
|
+
| '$geo'
|
|
90
|
+
| '$nested'
|
|
91
|
+
| '$datetime'
|
|
92
|
+
| '$null'
|
|
93
|
+
| '$empty';
|
|
94
|
+
|
|
95
|
+
export type QdrantVectorFilter = VectorFilter<
|
|
96
|
+
keyof QdrantOperatorValueMap,
|
|
97
|
+
QdrantOperatorValueMap,
|
|
98
|
+
QdrantLogicalOperatorValueMap,
|
|
99
|
+
QdrantBlacklistedRootOperators
|
|
100
|
+
>;
|
|
3
101
|
|
|
4
102
|
/**
|
|
5
103
|
* Translates MongoDB-style filters to Qdrant compatible filters.
|
|
@@ -20,7 +118,7 @@ import type { FieldCondition, VectorFilter, LogicalOperator, OperatorSupport } f
|
|
|
20
118
|
* - $null -> is_null check
|
|
21
119
|
* - $empty -> is_empty check
|
|
22
120
|
*/
|
|
23
|
-
export class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
121
|
+
export class QdrantFilterTranslator extends BaseFilterTranslator<QdrantVectorFilter> {
|
|
24
122
|
protected override isLogicalOperator(key: string): key is LogicalOperator {
|
|
25
123
|
return super.isLogicalOperator(key) || key === '$hasId' || key === '$hasVector';
|
|
26
124
|
}
|
|
@@ -35,7 +133,7 @@ export class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
|
35
133
|
};
|
|
36
134
|
}
|
|
37
135
|
|
|
38
|
-
translate(filter?:
|
|
136
|
+
translate(filter?: QdrantVectorFilter): QdrantVectorFilter {
|
|
39
137
|
if (this.isEmpty(filter)) return filter;
|
|
40
138
|
this.validateFilter(filter);
|
|
41
139
|
return this.translateNode(filter);
|
|
@@ -46,8 +144,8 @@ export class QdrantFilterTranslator extends BaseFilterTranslator {
|
|
|
46
144
|
return fieldKey ? { key: fieldKey, ...condition } : condition;
|
|
47
145
|
}
|
|
48
146
|
|
|
49
|
-
private translateNode(node:
|
|
50
|
-
if (!this.isEmpty(node) && typeof node === 'object' && 'must' in node) {
|
|
147
|
+
private translateNode(node: QdrantVectorFilter, isNested: boolean = false, fieldKey?: string): any {
|
|
148
|
+
if (!this.isEmpty(node) && !!node && typeof node === 'object' && 'must' in node) {
|
|
51
149
|
return node;
|
|
52
150
|
}
|
|
53
151
|
|
package/src/vector/index.test.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { QueryResult } from '@mastra/core';
|
|
4
4
|
import { describe, it, expect, beforeAll, afterAll, afterEach, vi, beforeEach } from 'vitest';
|
|
5
5
|
|
|
6
|
+
import type { QdrantVectorFilter } from './filter';
|
|
6
7
|
import { QdrantVector } from './index';
|
|
7
8
|
|
|
8
9
|
const dimension = 3;
|
|
@@ -77,7 +78,7 @@ describe('QdrantVector', () => {
|
|
|
77
78
|
|
|
78
79
|
it('should query vectors with metadata filter', async () => {
|
|
79
80
|
const queryVector = [0.0, 1.0, 0.0];
|
|
80
|
-
const filter = {
|
|
81
|
+
const filter: QdrantVectorFilter = {
|
|
81
82
|
label: 'y-axis',
|
|
82
83
|
};
|
|
83
84
|
|
|
@@ -350,21 +351,21 @@ describe('QdrantVector', () => {
|
|
|
350
351
|
|
|
351
352
|
describe('Basic Operators', () => {
|
|
352
353
|
it('should filter by exact value match', async () => {
|
|
353
|
-
const filter = { name: 'item1' };
|
|
354
|
+
const filter: QdrantVectorFilter = { name: 'item1' };
|
|
354
355
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
355
356
|
expect(results).toHaveLength(1);
|
|
356
357
|
expect(results[0]?.metadata?.name).toBe('item1');
|
|
357
358
|
});
|
|
358
359
|
|
|
359
360
|
it('should filter using comparison operators', async () => {
|
|
360
|
-
const filter = { price: { $gt: 100, $lt: 600 } };
|
|
361
|
+
const filter: QdrantVectorFilter = { price: { $gt: 100, $lt: 600 } };
|
|
361
362
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
362
363
|
expect(results).toHaveLength(1);
|
|
363
364
|
expect(results[0]?.metadata?.price).toBe(500);
|
|
364
365
|
});
|
|
365
366
|
|
|
366
367
|
it('should filter using array operators', async () => {
|
|
367
|
-
const filter = { tags: { $in: ['premium', 'bestseller'] } };
|
|
368
|
+
const filter: QdrantVectorFilter = { tags: { $in: ['premium', 'bestseller'] } };
|
|
368
369
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
369
370
|
expect(results).toHaveLength(2);
|
|
370
371
|
const tags = results.flatMap(r => r.metadata?.tags || []);
|
|
@@ -373,14 +374,14 @@ describe('QdrantVector', () => {
|
|
|
373
374
|
});
|
|
374
375
|
|
|
375
376
|
it('should handle null values', async () => {
|
|
376
|
-
const filter = { price: null };
|
|
377
|
+
const filter: QdrantVectorFilter = { price: null };
|
|
377
378
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
378
379
|
expect(results).toHaveLength(1);
|
|
379
380
|
expect(results[0]?.metadata?.price).toBeNull();
|
|
380
381
|
});
|
|
381
382
|
|
|
382
383
|
it('should handle empty arrays', async () => {
|
|
383
|
-
const filter = {
|
|
384
|
+
const filter: QdrantVectorFilter = {
|
|
384
385
|
tags: [],
|
|
385
386
|
};
|
|
386
387
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -392,7 +393,7 @@ describe('QdrantVector', () => {
|
|
|
392
393
|
|
|
393
394
|
describe('Logical Operators', () => {
|
|
394
395
|
it('should combine conditions with $and', async () => {
|
|
395
|
-
const filter = {
|
|
396
|
+
const filter: QdrantVectorFilter = {
|
|
396
397
|
$and: [{ tags: { $in: ['electronics'] } }, { price: { $gt: 700 } }],
|
|
397
398
|
};
|
|
398
399
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -402,7 +403,7 @@ describe('QdrantVector', () => {
|
|
|
402
403
|
});
|
|
403
404
|
|
|
404
405
|
it('should combine conditions with $or', async () => {
|
|
405
|
-
const filter = {
|
|
406
|
+
const filter: QdrantVectorFilter = {
|
|
406
407
|
$or: [{ price: { $gt: 900 } }, { tags: { $in: ['bestseller'] } }],
|
|
407
408
|
};
|
|
408
409
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -413,7 +414,7 @@ describe('QdrantVector', () => {
|
|
|
413
414
|
});
|
|
414
415
|
|
|
415
416
|
it('should handle $not operator', async () => {
|
|
416
|
-
const filter = {
|
|
417
|
+
const filter: QdrantVectorFilter = {
|
|
417
418
|
$not: { tags: { $in: ['electronics'] } },
|
|
418
419
|
};
|
|
419
420
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -425,7 +426,7 @@ describe('QdrantVector', () => {
|
|
|
425
426
|
});
|
|
426
427
|
|
|
427
428
|
it('should handle nested logical operators', async () => {
|
|
428
|
-
const filter = {
|
|
429
|
+
const filter: QdrantVectorFilter = {
|
|
429
430
|
$and: [
|
|
430
431
|
{ 'details.weight': { $lt: 2.0 } },
|
|
431
432
|
{
|
|
@@ -442,7 +443,7 @@ describe('QdrantVector', () => {
|
|
|
442
443
|
});
|
|
443
444
|
|
|
444
445
|
it('should handle empty logical operators', async () => {
|
|
445
|
-
const filter = { $and: [] };
|
|
446
|
+
const filter: QdrantVectorFilter = { $and: [] };
|
|
446
447
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
447
448
|
expect(results.length).toBeGreaterThan(0);
|
|
448
449
|
});
|
|
@@ -450,7 +451,7 @@ describe('QdrantVector', () => {
|
|
|
450
451
|
|
|
451
452
|
describe('Custom Operators', () => {
|
|
452
453
|
it('should filter using $count operator', async () => {
|
|
453
|
-
const filter = { 'stock.locations': { $count: { $gt: 1 } } };
|
|
454
|
+
const filter: QdrantVectorFilter = { 'stock.locations': { $count: { $gt: 1 } } };
|
|
454
455
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
455
456
|
expect(results).toHaveLength(2);
|
|
456
457
|
results.forEach(result => {
|
|
@@ -459,7 +460,7 @@ describe('QdrantVector', () => {
|
|
|
459
460
|
});
|
|
460
461
|
|
|
461
462
|
it('should filter using $geo radius operator', async () => {
|
|
462
|
-
const filter = {
|
|
463
|
+
const filter: QdrantVectorFilter = {
|
|
463
464
|
location: {
|
|
464
465
|
$geo: {
|
|
465
466
|
type: 'radius',
|
|
@@ -475,7 +476,7 @@ describe('QdrantVector', () => {
|
|
|
475
476
|
});
|
|
476
477
|
|
|
477
478
|
it('should filter using $geo box operator', async () => {
|
|
478
|
-
const filter = {
|
|
479
|
+
const filter: QdrantVectorFilter = {
|
|
479
480
|
location: {
|
|
480
481
|
$geo: {
|
|
481
482
|
type: 'box',
|
|
@@ -491,7 +492,7 @@ describe('QdrantVector', () => {
|
|
|
491
492
|
});
|
|
492
493
|
|
|
493
494
|
it('should filter using $geo polygon operator', async () => {
|
|
494
|
-
const filter = {
|
|
495
|
+
const filter: QdrantVectorFilter = {
|
|
495
496
|
location: {
|
|
496
497
|
$geo: {
|
|
497
498
|
type: 'polygon',
|
|
@@ -518,7 +519,7 @@ describe('QdrantVector', () => {
|
|
|
518
519
|
const allResults = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], topK: 2 });
|
|
519
520
|
const targetIds = allResults.map(r => r.id);
|
|
520
521
|
|
|
521
|
-
const filter = { $hasId: targetIds };
|
|
522
|
+
const filter: QdrantVectorFilter = { $hasId: targetIds };
|
|
522
523
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
523
524
|
expect(results).toHaveLength(2);
|
|
524
525
|
results.forEach(result => {
|
|
@@ -527,7 +528,7 @@ describe('QdrantVector', () => {
|
|
|
527
528
|
});
|
|
528
529
|
|
|
529
530
|
it('should filter using $hasVector operator', async () => {
|
|
530
|
-
const filter = { $hasVector: '' };
|
|
531
|
+
const filter: QdrantVectorFilter = { $hasVector: '' };
|
|
531
532
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
532
533
|
expect(results.length).toBeGreaterThan(0);
|
|
533
534
|
});
|
|
@@ -543,7 +544,7 @@ describe('QdrantVector', () => {
|
|
|
543
544
|
};
|
|
544
545
|
await qdrant.upsert({ indexName: testCollectionName, vectors: [vector], metadata: [metadata] });
|
|
545
546
|
|
|
546
|
-
const filter = {
|
|
547
|
+
const filter: QdrantVectorFilter = {
|
|
547
548
|
created_at: {
|
|
548
549
|
$datetime: {
|
|
549
550
|
range: {
|
|
@@ -603,7 +604,7 @@ describe('QdrantVector', () => {
|
|
|
603
604
|
expect(results.length).toBe(2);
|
|
604
605
|
});
|
|
605
606
|
it('should handle nested paths', async () => {
|
|
606
|
-
const filter = {
|
|
607
|
+
const filter: QdrantVectorFilter = {
|
|
607
608
|
'details.color': 'red',
|
|
608
609
|
'stock.quantity': { $gt: 0 },
|
|
609
610
|
};
|
|
@@ -614,7 +615,7 @@ describe('QdrantVector', () => {
|
|
|
614
615
|
});
|
|
615
616
|
|
|
616
617
|
it('should handle multiple conditions on same field', async () => {
|
|
617
|
-
const filter = {
|
|
618
|
+
const filter: QdrantVectorFilter = {
|
|
618
619
|
price: { $gt: 20, $lt: 30 },
|
|
619
620
|
};
|
|
620
621
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -623,7 +624,7 @@ describe('QdrantVector', () => {
|
|
|
623
624
|
});
|
|
624
625
|
|
|
625
626
|
it('should handle complex combinations', async () => {
|
|
626
|
-
const filter = {
|
|
627
|
+
const filter: QdrantVectorFilter = {
|
|
627
628
|
$and: [
|
|
628
629
|
{ 'details.weight': { $lt: 3.0 } },
|
|
629
630
|
{
|
|
@@ -642,7 +643,7 @@ describe('QdrantVector', () => {
|
|
|
642
643
|
});
|
|
643
644
|
|
|
644
645
|
it('should handle array paths with nested objects', async () => {
|
|
645
|
-
const filter = {
|
|
646
|
+
const filter: QdrantVectorFilter = {
|
|
646
647
|
'stock.locations[].warehouse': { $in: ['A'] },
|
|
647
648
|
};
|
|
648
649
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -653,7 +654,7 @@ describe('QdrantVector', () => {
|
|
|
653
654
|
});
|
|
654
655
|
|
|
655
656
|
it('should handle multiple nested paths with array notation', async () => {
|
|
656
|
-
const filter = {
|
|
657
|
+
const filter: QdrantVectorFilter = {
|
|
657
658
|
$and: [{ 'stock.locations[].warehouse': { $in: ['A'] } }, { 'stock.locations[].count': { $gt: 20 } }],
|
|
658
659
|
};
|
|
659
660
|
const results = await qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter });
|
|
@@ -677,7 +678,7 @@ describe('QdrantVector', () => {
|
|
|
677
678
|
};
|
|
678
679
|
await qdrant.upsert({ indexName: testCollectionName, vectors: [vector], metadata: [metadata] });
|
|
679
680
|
|
|
680
|
-
const filter = {
|
|
681
|
+
const filter: QdrantVectorFilter = {
|
|
681
682
|
$and: [
|
|
682
683
|
{
|
|
683
684
|
'timestamps.created': {
|
|
@@ -696,7 +697,7 @@ describe('QdrantVector', () => {
|
|
|
696
697
|
});
|
|
697
698
|
|
|
698
699
|
it('should handle complex combinations with custom operators', async () => {
|
|
699
|
-
const filter = {
|
|
700
|
+
const filter: QdrantVectorFilter = {
|
|
700
701
|
$and: [
|
|
701
702
|
{ 'stock.locations': { $count: { $gt: 0 } } },
|
|
702
703
|
{
|
|
@@ -731,7 +732,7 @@ describe('QdrantVector', () => {
|
|
|
731
732
|
describe('Performance Cases', () => {
|
|
732
733
|
it('should handle deep nesting efficiently', async () => {
|
|
733
734
|
const start = Date.now();
|
|
734
|
-
const filter = {
|
|
735
|
+
const filter: QdrantVectorFilter = {
|
|
735
736
|
$and: Array(5)
|
|
736
737
|
.fill(null)
|
|
737
738
|
.map(() => ({
|
|
@@ -745,7 +746,11 @@ describe('QdrantVector', () => {
|
|
|
745
746
|
});
|
|
746
747
|
|
|
747
748
|
it('should handle multiple concurrent filtered queries', async () => {
|
|
748
|
-
const filters
|
|
749
|
+
const filters: QdrantVectorFilter[] = [
|
|
750
|
+
{ price: { $gt: 500 } },
|
|
751
|
+
{ tags: { $in: ['electronics'] } },
|
|
752
|
+
{ 'stock.quantity': { $gt: 0 } },
|
|
753
|
+
];
|
|
749
754
|
const start = Date.now();
|
|
750
755
|
const results = await Promise.all(
|
|
751
756
|
filters.map(filter => qdrant.query({ indexName: testCollectionName, queryVector: [1, 0, 0], filter })),
|
package/src/vector/index.ts
CHANGED
|
@@ -11,11 +11,11 @@ import type {
|
|
|
11
11
|
DeleteVectorParams,
|
|
12
12
|
UpdateVectorParams,
|
|
13
13
|
} from '@mastra/core/vector';
|
|
14
|
-
import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
15
14
|
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
16
15
|
import type { Schemas } from '@qdrant/js-client-rest';
|
|
17
16
|
|
|
18
17
|
import { QdrantFilterTranslator } from './filter';
|
|
18
|
+
import type { QdrantVectorFilter } from './filter';
|
|
19
19
|
|
|
20
20
|
const BATCH_SIZE = 256;
|
|
21
21
|
const DISTANCE_MAPPING: Record<string, Schemas['Distance']> = {
|
|
@@ -24,6 +24,8 @@ const DISTANCE_MAPPING: Record<string, Schemas['Distance']> = {
|
|
|
24
24
|
dotproduct: 'Dot',
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
type QdrantQueryVectorParams = QueryVectorParams<QdrantVectorFilter>;
|
|
28
|
+
|
|
27
29
|
export class QdrantVector extends MastraVector {
|
|
28
30
|
private client: QdrantClient;
|
|
29
31
|
|
|
@@ -131,7 +133,7 @@ export class QdrantVector extends MastraVector {
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
transformFilter(filter?:
|
|
136
|
+
transformFilter(filter?: QdrantVectorFilter) {
|
|
135
137
|
const translator = new QdrantFilterTranslator();
|
|
136
138
|
return translator.translate(filter);
|
|
137
139
|
}
|
|
@@ -142,7 +144,7 @@ export class QdrantVector extends MastraVector {
|
|
|
142
144
|
topK = 10,
|
|
143
145
|
filter,
|
|
144
146
|
includeVector = false,
|
|
145
|
-
}:
|
|
147
|
+
}: QdrantQueryVectorParams): Promise<QueryResult[]> {
|
|
146
148
|
const translatedFilter = this.transformFilter(filter) ?? {};
|
|
147
149
|
|
|
148
150
|
try {
|