@mastra/qdrant 0.1.9-alpha.0 → 0.2.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 +29 -0
- package/dist/_tsup-dts-rollup.d.cts +35 -0
- package/dist/_tsup-dts-rollup.d.ts +35 -0
- package/dist/index.cjs +78 -0
- package/dist/index.js +78 -0
- package/docker-compose.yaml +17 -0
- package/package.json +2 -2
- package/src/vector/index.test.ts +152 -0
- package/src/vector/index.ts +101 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/qdrant@0.
|
|
2
|
+
> @mastra/qdrant@0.2.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.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 7393ms
|
|
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
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 7651ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
21
|
-
[32mCJS[39m ⚡️ Build success in
|
|
22
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
23
|
-
[32mESM[39m ⚡️ Build success in
|
|
20
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m13.17 KB[39m
|
|
21
|
+
[32mCJS[39m ⚡️ Build success in 556ms
|
|
22
|
+
[32mESM[39m [1mdist/index.js [22m[32m13.14 KB[39m
|
|
23
|
+
[32mESM[39m ⚡️ Build success in 564ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @mastra/qdrant
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6cbf6bb: Added new operation implementations for MastraVector in qdrant store
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [16b98d9]
|
|
12
|
+
- Updated dependencies [1c8cda4]
|
|
13
|
+
- Updated dependencies [95b4144]
|
|
14
|
+
- Updated dependencies [3729dbd]
|
|
15
|
+
- Updated dependencies [c2144f4]
|
|
16
|
+
- @mastra/core@0.6.0
|
|
17
|
+
|
|
18
|
+
## 0.2.0-alpha.1
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 6cbf6bb: Added new operation implementations for MastraVector in qdrant store
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [16b98d9]
|
|
27
|
+
- Updated dependencies [1c8cda4]
|
|
28
|
+
- Updated dependencies [95b4144]
|
|
29
|
+
- Updated dependencies [c2144f4]
|
|
30
|
+
- @mastra/core@0.6.0-alpha.1
|
|
31
|
+
|
|
3
32
|
## 0.1.9-alpha.0
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
|
@@ -55,6 +55,41 @@ declare class QdrantVector extends MastraVector {
|
|
|
55
55
|
listIndexes(): Promise<string[]>;
|
|
56
56
|
describeIndex(indexName: string): Promise<IndexStats>;
|
|
57
57
|
deleteIndex(indexName: string): Promise<void>;
|
|
58
|
+
updateIndexById(indexName: string, id: string, update: {
|
|
59
|
+
vector?: number[];
|
|
60
|
+
metadata?: Record<string, any>;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
deleteIndexById(indexName: string, id: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Parses and converts a string ID to the appropriate type (string or number) for Qdrant point operations.
|
|
65
|
+
*
|
|
66
|
+
* Qdrant supports both numeric and string IDs. This helper method ensures IDs are in the correct format
|
|
67
|
+
* before sending them to the Qdrant client API.
|
|
68
|
+
*
|
|
69
|
+
* @param id - The ID string to parse
|
|
70
|
+
* @returns The parsed ID as either a number (if string contains only digits) or the original string
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Numeric ID strings are converted to numbers
|
|
74
|
+
* parsePointId("123") => 123
|
|
75
|
+
* parsePointId("42") => 42
|
|
76
|
+
* parsePointId("0") => 0
|
|
77
|
+
*
|
|
78
|
+
* // String IDs containing any non-digit characters remain as strings
|
|
79
|
+
* parsePointId("doc-123") => "doc-123"
|
|
80
|
+
* parsePointId("user_42") => "user_42"
|
|
81
|
+
* parsePointId("abc123") => "abc123"
|
|
82
|
+
* parsePointId("123abc") => "123abc"
|
|
83
|
+
* parsePointId("") => ""
|
|
84
|
+
* parsePointId("uuid-5678-xyz") => "uuid-5678-xyz"
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* - This conversion is important because Qdrant treats numeric and string IDs differently
|
|
88
|
+
* - Only positive integers are converted to numbers (negative numbers with minus signs remain strings)
|
|
89
|
+
* - The method uses base-10 parsing, so leading zeros will be dropped in numeric conversions
|
|
90
|
+
* - reference: https://qdrant.tech/documentation/concepts/points/?q=qdrant+point+id#point-ids
|
|
91
|
+
*/
|
|
92
|
+
private parsePointId;
|
|
58
93
|
}
|
|
59
94
|
export { QdrantVector }
|
|
60
95
|
export { QdrantVector as QdrantVector_alias_1 }
|
|
@@ -55,6 +55,41 @@ declare class QdrantVector extends MastraVector {
|
|
|
55
55
|
listIndexes(): Promise<string[]>;
|
|
56
56
|
describeIndex(indexName: string): Promise<IndexStats>;
|
|
57
57
|
deleteIndex(indexName: string): Promise<void>;
|
|
58
|
+
updateIndexById(indexName: string, id: string, update: {
|
|
59
|
+
vector?: number[];
|
|
60
|
+
metadata?: Record<string, any>;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
deleteIndexById(indexName: string, id: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Parses and converts a string ID to the appropriate type (string or number) for Qdrant point operations.
|
|
65
|
+
*
|
|
66
|
+
* Qdrant supports both numeric and string IDs. This helper method ensures IDs are in the correct format
|
|
67
|
+
* before sending them to the Qdrant client API.
|
|
68
|
+
*
|
|
69
|
+
* @param id - The ID string to parse
|
|
70
|
+
* @returns The parsed ID as either a number (if string contains only digits) or the original string
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Numeric ID strings are converted to numbers
|
|
74
|
+
* parsePointId("123") => 123
|
|
75
|
+
* parsePointId("42") => 42
|
|
76
|
+
* parsePointId("0") => 0
|
|
77
|
+
*
|
|
78
|
+
* // String IDs containing any non-digit characters remain as strings
|
|
79
|
+
* parsePointId("doc-123") => "doc-123"
|
|
80
|
+
* parsePointId("user_42") => "user_42"
|
|
81
|
+
* parsePointId("abc123") => "abc123"
|
|
82
|
+
* parsePointId("123abc") => "123abc"
|
|
83
|
+
* parsePointId("") => ""
|
|
84
|
+
* parsePointId("uuid-5678-xyz") => "uuid-5678-xyz"
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* - This conversion is important because Qdrant treats numeric and string IDs differently
|
|
88
|
+
* - Only positive integers are converted to numbers (negative numbers with minus signs remain strings)
|
|
89
|
+
* - The method uses base-10 parsing, so leading zeros will be dropped in numeric conversions
|
|
90
|
+
* - reference: https://qdrant.tech/documentation/concepts/points/?q=qdrant+point+id#point-ids
|
|
91
|
+
*/
|
|
92
|
+
private parsePointId;
|
|
58
93
|
}
|
|
59
94
|
export { QdrantVector }
|
|
60
95
|
export { QdrantVector as QdrantVector_alias_1 }
|
package/dist/index.cjs
CHANGED
|
@@ -337,6 +337,84 @@ var QdrantVector = class extends vector.MastraVector {
|
|
|
337
337
|
async deleteIndex(indexName) {
|
|
338
338
|
await this.client.deleteCollection(indexName);
|
|
339
339
|
}
|
|
340
|
+
async updateIndexById(indexName, id, update) {
|
|
341
|
+
if (!update.vector && !update.metadata) {
|
|
342
|
+
throw new Error("No updates provided");
|
|
343
|
+
}
|
|
344
|
+
const pointId = this.parsePointId(id);
|
|
345
|
+
try {
|
|
346
|
+
if (update.metadata && !update.vector) {
|
|
347
|
+
await this.client.setPayload(indexName, { payload: update.metadata, points: [pointId] });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (update.vector && !update.metadata) {
|
|
351
|
+
await this.client.updateVectors(indexName, {
|
|
352
|
+
points: [
|
|
353
|
+
{
|
|
354
|
+
id: pointId,
|
|
355
|
+
vector: update.vector
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (update.vector && update.metadata) {
|
|
362
|
+
const point = {
|
|
363
|
+
id: pointId,
|
|
364
|
+
vector: update.vector,
|
|
365
|
+
payload: update.metadata
|
|
366
|
+
};
|
|
367
|
+
await this.client.upsert(indexName, {
|
|
368
|
+
points: [point]
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error("Error updating point in Qdrant:", error);
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async deleteIndexById(indexName, id) {
|
|
378
|
+
const pointId = this.parsePointId(id);
|
|
379
|
+
await this.client.delete(indexName, {
|
|
380
|
+
points: [pointId]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Parses and converts a string ID to the appropriate type (string or number) for Qdrant point operations.
|
|
385
|
+
*
|
|
386
|
+
* Qdrant supports both numeric and string IDs. This helper method ensures IDs are in the correct format
|
|
387
|
+
* before sending them to the Qdrant client API.
|
|
388
|
+
*
|
|
389
|
+
* @param id - The ID string to parse
|
|
390
|
+
* @returns The parsed ID as either a number (if string contains only digits) or the original string
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* // Numeric ID strings are converted to numbers
|
|
394
|
+
* parsePointId("123") => 123
|
|
395
|
+
* parsePointId("42") => 42
|
|
396
|
+
* parsePointId("0") => 0
|
|
397
|
+
*
|
|
398
|
+
* // String IDs containing any non-digit characters remain as strings
|
|
399
|
+
* parsePointId("doc-123") => "doc-123"
|
|
400
|
+
* parsePointId("user_42") => "user_42"
|
|
401
|
+
* parsePointId("abc123") => "abc123"
|
|
402
|
+
* parsePointId("123abc") => "123abc"
|
|
403
|
+
* parsePointId("") => ""
|
|
404
|
+
* parsePointId("uuid-5678-xyz") => "uuid-5678-xyz"
|
|
405
|
+
*
|
|
406
|
+
* @remarks
|
|
407
|
+
* - This conversion is important because Qdrant treats numeric and string IDs differently
|
|
408
|
+
* - Only positive integers are converted to numbers (negative numbers with minus signs remain strings)
|
|
409
|
+
* - The method uses base-10 parsing, so leading zeros will be dropped in numeric conversions
|
|
410
|
+
* - reference: https://qdrant.tech/documentation/concepts/points/?q=qdrant+point+id#point-ids
|
|
411
|
+
*/
|
|
412
|
+
parsePointId(id) {
|
|
413
|
+
if (/^\d+$/.test(id)) {
|
|
414
|
+
return parseInt(id, 10);
|
|
415
|
+
}
|
|
416
|
+
return id;
|
|
417
|
+
}
|
|
340
418
|
};
|
|
341
419
|
|
|
342
420
|
exports.QdrantVector = QdrantVector;
|
package/dist/index.js
CHANGED
|
@@ -335,6 +335,84 @@ var QdrantVector = class extends MastraVector {
|
|
|
335
335
|
async deleteIndex(indexName) {
|
|
336
336
|
await this.client.deleteCollection(indexName);
|
|
337
337
|
}
|
|
338
|
+
async updateIndexById(indexName, id, update) {
|
|
339
|
+
if (!update.vector && !update.metadata) {
|
|
340
|
+
throw new Error("No updates provided");
|
|
341
|
+
}
|
|
342
|
+
const pointId = this.parsePointId(id);
|
|
343
|
+
try {
|
|
344
|
+
if (update.metadata && !update.vector) {
|
|
345
|
+
await this.client.setPayload(indexName, { payload: update.metadata, points: [pointId] });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (update.vector && !update.metadata) {
|
|
349
|
+
await this.client.updateVectors(indexName, {
|
|
350
|
+
points: [
|
|
351
|
+
{
|
|
352
|
+
id: pointId,
|
|
353
|
+
vector: update.vector
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (update.vector && update.metadata) {
|
|
360
|
+
const point = {
|
|
361
|
+
id: pointId,
|
|
362
|
+
vector: update.vector,
|
|
363
|
+
payload: update.metadata
|
|
364
|
+
};
|
|
365
|
+
await this.client.upsert(indexName, {
|
|
366
|
+
points: [point]
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("Error updating point in Qdrant:", error);
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async deleteIndexById(indexName, id) {
|
|
376
|
+
const pointId = this.parsePointId(id);
|
|
377
|
+
await this.client.delete(indexName, {
|
|
378
|
+
points: [pointId]
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Parses and converts a string ID to the appropriate type (string or number) for Qdrant point operations.
|
|
383
|
+
*
|
|
384
|
+
* Qdrant supports both numeric and string IDs. This helper method ensures IDs are in the correct format
|
|
385
|
+
* before sending them to the Qdrant client API.
|
|
386
|
+
*
|
|
387
|
+
* @param id - The ID string to parse
|
|
388
|
+
* @returns The parsed ID as either a number (if string contains only digits) or the original string
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* // Numeric ID strings are converted to numbers
|
|
392
|
+
* parsePointId("123") => 123
|
|
393
|
+
* parsePointId("42") => 42
|
|
394
|
+
* parsePointId("0") => 0
|
|
395
|
+
*
|
|
396
|
+
* // String IDs containing any non-digit characters remain as strings
|
|
397
|
+
* parsePointId("doc-123") => "doc-123"
|
|
398
|
+
* parsePointId("user_42") => "user_42"
|
|
399
|
+
* parsePointId("abc123") => "abc123"
|
|
400
|
+
* parsePointId("123abc") => "123abc"
|
|
401
|
+
* parsePointId("") => ""
|
|
402
|
+
* parsePointId("uuid-5678-xyz") => "uuid-5678-xyz"
|
|
403
|
+
*
|
|
404
|
+
* @remarks
|
|
405
|
+
* - This conversion is important because Qdrant treats numeric and string IDs differently
|
|
406
|
+
* - Only positive integers are converted to numbers (negative numbers with minus signs remain strings)
|
|
407
|
+
* - The method uses base-10 parsing, so leading zeros will be dropped in numeric conversions
|
|
408
|
+
* - reference: https://qdrant.tech/documentation/concepts/points/?q=qdrant+point+id#point-ids
|
|
409
|
+
*/
|
|
410
|
+
parsePointId(id) {
|
|
411
|
+
if (/^\d+$/.test(id)) {
|
|
412
|
+
return parseInt(id, 10);
|
|
413
|
+
}
|
|
414
|
+
return id;
|
|
415
|
+
}
|
|
338
416
|
};
|
|
339
417
|
|
|
340
418
|
export { QdrantVector };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
qdrant:
|
|
5
|
+
image: qdrant/qdrant:latest
|
|
6
|
+
ports:
|
|
7
|
+
- '6333:6333' # REST API port
|
|
8
|
+
- '6334:6334' # gRPC port
|
|
9
|
+
volumes:
|
|
10
|
+
- qdrant_data:/qdrant/storage
|
|
11
|
+
environment:
|
|
12
|
+
- QDRANT_LOG_LEVEL=INFO
|
|
13
|
+
restart: always
|
|
14
|
+
|
|
15
|
+
volumes:
|
|
16
|
+
qdrant_data:
|
|
17
|
+
driver: local
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/qdrant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Qdrant vector store provider for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@qdrant/js-client-rest": "^1.13.0",
|
|
23
|
-
"@mastra/core": "^0.
|
|
23
|
+
"@mastra/core": "^0.6.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@microsoft/api-extractor": "^7.52.1",
|
package/src/vector/index.test.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { describe, it, expect, beforeAll, afterAll, afterEach, vi, beforeEach } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { QdrantVector } from './index';
|
|
6
|
+
import type { QueryResult } from '@mastra/core';
|
|
6
7
|
|
|
7
8
|
const dimension = 3;
|
|
8
9
|
|
|
@@ -87,6 +88,157 @@ describe('QdrantVector', () => {
|
|
|
87
88
|
}, 50000);
|
|
88
89
|
});
|
|
89
90
|
|
|
91
|
+
describe('Vector update operations', () => {
|
|
92
|
+
const testVectors = [
|
|
93
|
+
[1, 2, 3],
|
|
94
|
+
[4, 5, 6],
|
|
95
|
+
[7, 8, 9],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
beforeEach(async () => {
|
|
99
|
+
await qdrant.createIndex({ indexName: testCollectionName, dimension: 3 });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
await qdrant.deleteIndex(testCollectionName);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should update the vector by id', async () => {
|
|
107
|
+
const ids = await qdrant.upsert({ indexName: testCollectionName, vectors: testVectors });
|
|
108
|
+
expect(ids).toHaveLength(3);
|
|
109
|
+
|
|
110
|
+
const idToBeUpdated = ids[0];
|
|
111
|
+
const newVector = [1, 2, 3];
|
|
112
|
+
const newMetaData = {
|
|
113
|
+
test: 'updates',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const update = {
|
|
117
|
+
vector: newVector,
|
|
118
|
+
metadata: newMetaData,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
await qdrant.updateIndexById(testCollectionName, idToBeUpdated, update);
|
|
122
|
+
|
|
123
|
+
const results: QueryResult[] = await qdrant.query({
|
|
124
|
+
indexName: testCollectionName,
|
|
125
|
+
queryVector: newVector,
|
|
126
|
+
topK: 2,
|
|
127
|
+
includeVector: true,
|
|
128
|
+
});
|
|
129
|
+
console.log(results);
|
|
130
|
+
expect(results[0]?.id).toBe(idToBeUpdated);
|
|
131
|
+
// not matching the vector in results list because, the stored vector is stored in a normalized form inside qdrant
|
|
132
|
+
// expect(results[0]?.vector).toEqual(newVector);
|
|
133
|
+
expect(results[0]?.metadata).toEqual(newMetaData);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should only update the metadata by id', async () => {
|
|
137
|
+
const ids = await qdrant.upsert({ indexName: testCollectionName, vectors: testVectors });
|
|
138
|
+
expect(ids).toHaveLength(3);
|
|
139
|
+
|
|
140
|
+
const idToBeUpdated = ids[0];
|
|
141
|
+
const newMetaData = {
|
|
142
|
+
test: 'updates',
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const update = {
|
|
146
|
+
metadata: newMetaData,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
await qdrant.updateIndexById(testCollectionName, idToBeUpdated, update);
|
|
150
|
+
|
|
151
|
+
const results: QueryResult[] = await qdrant.query({
|
|
152
|
+
indexName: testCollectionName,
|
|
153
|
+
queryVector: testVectors[0],
|
|
154
|
+
topK: 2,
|
|
155
|
+
includeVector: true,
|
|
156
|
+
});
|
|
157
|
+
expect(results[0]?.id).toBe(idToBeUpdated);
|
|
158
|
+
// not matching the vector in results list because, the stored vector is stored in a normalized form inside qdrant
|
|
159
|
+
// expect(results[0]?.vector).toEqual(testVectors[0]);
|
|
160
|
+
expect(results[0]?.metadata).toEqual(newMetaData);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should only update vector embeddings by id', async () => {
|
|
164
|
+
const ids = await qdrant.upsert({ indexName: testCollectionName, vectors: testVectors });
|
|
165
|
+
expect(ids).toHaveLength(3);
|
|
166
|
+
|
|
167
|
+
const idToBeUpdated = ids[0];
|
|
168
|
+
const newVector = [1, 2, 3];
|
|
169
|
+
|
|
170
|
+
const update = {
|
|
171
|
+
vector: newVector,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
await qdrant.updateIndexById(testCollectionName, idToBeUpdated, update);
|
|
175
|
+
|
|
176
|
+
const results: QueryResult[] = await qdrant.query({
|
|
177
|
+
indexName: testCollectionName,
|
|
178
|
+
queryVector: newVector,
|
|
179
|
+
topK: 2,
|
|
180
|
+
includeVector: true,
|
|
181
|
+
});
|
|
182
|
+
expect(results[0]?.id).toBe(idToBeUpdated);
|
|
183
|
+
// not matching the vector in results list because, the stored vector is stored in a normalized form inside qdrant
|
|
184
|
+
// expect(results[0]?.vector).toEqual(newVector);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should throw exception when no updates are given', () => {
|
|
188
|
+
expect(qdrant.updateIndexById(testCollectionName, 'id', {})).rejects.toThrow('No updates provided');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should throw error for non-existent index', async () => {
|
|
192
|
+
const nonExistentIndex = 'non-existent-index';
|
|
193
|
+
await expect(qdrant.updateIndexById(nonExistentIndex, 'test-id', { vector: [1, 2, 3] })).rejects.toThrow();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should throw error for invalid vector dimension', async () => {
|
|
197
|
+
const [id] = await qdrant.upsert({
|
|
198
|
+
indexName: testCollectionName,
|
|
199
|
+
vectors: [[1, 2, 3]],
|
|
200
|
+
metadata: [{ test: 'initial' }],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await expect(
|
|
204
|
+
qdrant.updateIndexById(testCollectionName, id, { vector: [1, 2] }), // Wrong dimension
|
|
205
|
+
).rejects.toThrow();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('Vector delete operations', () => {
|
|
210
|
+
const testVectors = [
|
|
211
|
+
[1, 2, 3],
|
|
212
|
+
[4, 5, 6],
|
|
213
|
+
[7, 8, 9],
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
beforeEach(async () => {
|
|
217
|
+
await qdrant.createIndex({ indexName: testCollectionName, dimension: 3 });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
afterEach(async () => {
|
|
221
|
+
await qdrant.deleteIndex(testCollectionName);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should delete the vector by id', async () => {
|
|
225
|
+
const ids = await qdrant.upsert({ indexName: testCollectionName, vectors: testVectors });
|
|
226
|
+
expect(ids).toHaveLength(3);
|
|
227
|
+
const idToBeDeleted = ids[0];
|
|
228
|
+
|
|
229
|
+
await qdrant.deleteIndexById(testCollectionName, idToBeDeleted);
|
|
230
|
+
|
|
231
|
+
const results: QueryResult[] = await qdrant.query({
|
|
232
|
+
indexName: testCollectionName,
|
|
233
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
234
|
+
topK: 2,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(results).toHaveLength(2);
|
|
238
|
+
expect(results.map(res => res.id)).not.toContain(idToBeDeleted);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
90
242
|
describe('Filter Queries', () => {
|
|
91
243
|
const filterTestVectors = Array(10)
|
|
92
244
|
.fill(null)
|
package/src/vector/index.ts
CHANGED
|
@@ -148,4 +148,105 @@ export class QdrantVector extends MastraVector {
|
|
|
148
148
|
async deleteIndex(indexName: string): Promise<void> {
|
|
149
149
|
await this.client.deleteCollection(indexName);
|
|
150
150
|
}
|
|
151
|
+
|
|
152
|
+
async updateIndexById(
|
|
153
|
+
indexName: string,
|
|
154
|
+
id: string,
|
|
155
|
+
update: {
|
|
156
|
+
vector?: number[];
|
|
157
|
+
metadata?: Record<string, any>;
|
|
158
|
+
},
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
if (!update.vector && !update.metadata) {
|
|
161
|
+
throw new Error('No updates provided');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const pointId = this.parsePointId(id);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Handle metadata-only update
|
|
168
|
+
if (update.metadata && !update.vector) {
|
|
169
|
+
// For metadata-only updates, use the setPayload method
|
|
170
|
+
await this.client.setPayload(indexName, { payload: update.metadata, points: [pointId] });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle vector-only update
|
|
175
|
+
if (update.vector && !update.metadata) {
|
|
176
|
+
await this.client.updateVectors(indexName, {
|
|
177
|
+
points: [
|
|
178
|
+
{
|
|
179
|
+
id: pointId,
|
|
180
|
+
vector: update.vector,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Handle both vector and metadata update
|
|
188
|
+
if (update.vector && update.metadata) {
|
|
189
|
+
const point = {
|
|
190
|
+
id: pointId,
|
|
191
|
+
vector: update.vector,
|
|
192
|
+
payload: update.metadata,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await this.client.upsert(indexName, {
|
|
196
|
+
points: [point],
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('Error updating point in Qdrant:', error);
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async deleteIndexById(indexName: string, id: string): Promise<void> {
|
|
207
|
+
// Parse the ID - Qdrant supports both string and numeric IDs
|
|
208
|
+
const pointId = this.parsePointId(id);
|
|
209
|
+
|
|
210
|
+
// Use the Qdrant client to delete the point from the collection
|
|
211
|
+
await this.client.delete(indexName, {
|
|
212
|
+
points: [pointId],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Parses and converts a string ID to the appropriate type (string or number) for Qdrant point operations.
|
|
218
|
+
*
|
|
219
|
+
* Qdrant supports both numeric and string IDs. This helper method ensures IDs are in the correct format
|
|
220
|
+
* before sending them to the Qdrant client API.
|
|
221
|
+
*
|
|
222
|
+
* @param id - The ID string to parse
|
|
223
|
+
* @returns The parsed ID as either a number (if string contains only digits) or the original string
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // Numeric ID strings are converted to numbers
|
|
227
|
+
* parsePointId("123") => 123
|
|
228
|
+
* parsePointId("42") => 42
|
|
229
|
+
* parsePointId("0") => 0
|
|
230
|
+
*
|
|
231
|
+
* // String IDs containing any non-digit characters remain as strings
|
|
232
|
+
* parsePointId("doc-123") => "doc-123"
|
|
233
|
+
* parsePointId("user_42") => "user_42"
|
|
234
|
+
* parsePointId("abc123") => "abc123"
|
|
235
|
+
* parsePointId("123abc") => "123abc"
|
|
236
|
+
* parsePointId("") => ""
|
|
237
|
+
* parsePointId("uuid-5678-xyz") => "uuid-5678-xyz"
|
|
238
|
+
*
|
|
239
|
+
* @remarks
|
|
240
|
+
* - This conversion is important because Qdrant treats numeric and string IDs differently
|
|
241
|
+
* - Only positive integers are converted to numbers (negative numbers with minus signs remain strings)
|
|
242
|
+
* - The method uses base-10 parsing, so leading zeros will be dropped in numeric conversions
|
|
243
|
+
* - reference: https://qdrant.tech/documentation/concepts/points/?q=qdrant+point+id#point-ids
|
|
244
|
+
*/
|
|
245
|
+
private parsePointId(id: string): string | number {
|
|
246
|
+
// Try to parse as number if it looks like one
|
|
247
|
+
if (/^\d+$/.test(id)) {
|
|
248
|
+
return parseInt(id, 10);
|
|
249
|
+
}
|
|
250
|
+
return id;
|
|
251
|
+
}
|
|
151
252
|
}
|