@mastra/qdrant 0.0.0-commonjs-20250227130920

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.
@@ -0,0 +1,147 @@
1
+ import type { Filter } from '@mastra/core/filter';
2
+ import { MastraVector } from '@mastra/core/vector';
3
+ import type { QueryResult, IndexStats } from '@mastra/core/vector';
4
+ import { QdrantClient } from '@qdrant/js-client-rest';
5
+ import type { Schemas } from '@qdrant/js-client-rest';
6
+
7
+ import { QdrantFilterTranslator } from './filter';
8
+
9
+ const BATCH_SIZE = 256;
10
+ const DISTANCE_MAPPING: Record<string, Schemas['Distance']> = {
11
+ cosine: 'Cosine',
12
+ euclidean: 'Euclid',
13
+ dotproduct: 'Dot',
14
+ };
15
+
16
+ export class QdrantVector extends MastraVector {
17
+ private client: QdrantClient;
18
+
19
+ constructor(url: string, apiKey?: string, https?: boolean) {
20
+ super();
21
+
22
+ const baseClient = new QdrantClient({
23
+ url,
24
+ apiKey,
25
+ https,
26
+ });
27
+
28
+ const telemetry = this.__getTelemetry();
29
+ this.client =
30
+ telemetry?.traceClass(baseClient, {
31
+ spanNamePrefix: 'qdrant-vector',
32
+ attributes: {
33
+ 'vector.type': 'qdrant',
34
+ },
35
+ }) ?? baseClient;
36
+ }
37
+
38
+ async upsert(
39
+ indexName: string,
40
+ vectors: number[][],
41
+ metadata?: Record<string, any>[],
42
+ ids?: string[],
43
+ ): Promise<string[]> {
44
+ const pointIds = ids || vectors.map(() => crypto.randomUUID());
45
+
46
+ const records = vectors.map((vector, i) => ({
47
+ id: pointIds[i],
48
+ vector: vector,
49
+ payload: metadata?.[i] || {},
50
+ }));
51
+
52
+ for (let i = 0; i < records.length; i += BATCH_SIZE) {
53
+ const batch = records.slice(i, i + BATCH_SIZE);
54
+ await this.client.upsert(indexName, {
55
+ // @ts-expect-error
56
+ points: batch,
57
+ wait: true,
58
+ });
59
+ }
60
+
61
+ return pointIds;
62
+ }
63
+
64
+ async createIndex(
65
+ indexName: string,
66
+ dimension: number,
67
+ metric: 'cosine' | 'euclidean' | 'dotproduct' = 'cosine',
68
+ ): Promise<void> {
69
+ if (!Number.isInteger(dimension) || dimension <= 0) {
70
+ throw new Error('Dimension must be a positive integer');
71
+ }
72
+ await this.client.createCollection(indexName, {
73
+ vectors: {
74
+ // @ts-expect-error
75
+ size: dimension,
76
+ // @ts-expect-error
77
+ distance: DISTANCE_MAPPING[metric],
78
+ },
79
+ });
80
+ }
81
+
82
+ transformFilter(filter?: Filter) {
83
+ const translator = new QdrantFilterTranslator();
84
+ return translator.translate(filter);
85
+ }
86
+
87
+ async query(
88
+ indexName: string,
89
+ queryVector: number[],
90
+ topK: number = 10,
91
+ filter?: Filter,
92
+ includeVector: boolean = false,
93
+ ): Promise<QueryResult[]> {
94
+ const translatedFilter = this.transformFilter(filter);
95
+
96
+ const results = (
97
+ await this.client.query(indexName, {
98
+ query: queryVector,
99
+ limit: topK,
100
+ filter: translatedFilter,
101
+ with_payload: true,
102
+ with_vector: includeVector,
103
+ })
104
+ ).points;
105
+
106
+ return results.map(match => {
107
+ let vector: number[] = [];
108
+ if (includeVector) {
109
+ if (Array.isArray(match.vector)) {
110
+ // If it's already an array of numbers
111
+ vector = match.vector as number[];
112
+ } else if (typeof match.vector === 'object' && match.vector !== null) {
113
+ // If it's an object with vector data
114
+ vector = Object.values(match.vector).filter(v => typeof v === 'number');
115
+ }
116
+ }
117
+
118
+ return {
119
+ id: match.id as string,
120
+ score: match.score || 0,
121
+ metadata: match.payload as Record<string, any>,
122
+ ...(includeVector && { vector }),
123
+ };
124
+ });
125
+ }
126
+
127
+ async listIndexes(): Promise<string[]> {
128
+ const response = await this.client.getCollections();
129
+ return response.collections.map(collection => collection.name) || [];
130
+ }
131
+
132
+ async describeIndex(indexName: string): Promise<IndexStats> {
133
+ const { config, points_count } = await this.client.getCollection(indexName);
134
+
135
+ const distance = config.params.vectors?.distance as Schemas['Distance'];
136
+ return {
137
+ dimension: config.params.vectors?.size as number,
138
+ count: points_count || 0,
139
+ // @ts-expect-error
140
+ metric: Object.keys(DISTANCE_MAPPING).find(key => DISTANCE_MAPPING[key] === distance),
141
+ };
142
+ }
143
+
144
+ async deleteIndex(indexName: string): Promise<void> {
145
+ await this.client.deleteCollection(indexName);
146
+ }
147
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.node.json",
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", "**/*.test.ts"]
5
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ include: ['src/**/*.test.ts'],
7
+ coverage: {
8
+ reporter: ['text', 'json', 'html'],
9
+ },
10
+ },
11
+ });