nodebb-plugin-integration 0.4.2 → 0.4.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/src/ai/qdrant.js CHANGED
@@ -1,564 +1,161 @@
1
1
  "use strict";
2
2
 
3
- /*
4
-
5
- NodeBB Integration Layer
6
-
7
-
8
- Qdrant Vector Database Adapter
9
-
10
-
11
-
12
- */
13
-
14
-
15
-
16
-
17
- const {
18
-
19
- QdrantClient
20
-
21
- } = require("@qdrant/js-client-rest");
22
-
23
-
24
-
25
-
26
- const Config =
27
- require("../config");
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
3
+ const { QdrantClient } = require("@qdrant/js-client-rest");
4
+ const Config = require("../config");
40
5
 
41
6
  let client = null;
7
+ let clientUrl = null;
42
8
 
9
+ async function getClient() {
10
+ const settings = await Config.get();
43
11
 
12
+ if (settings.aiProvider !== "openai") {
13
+ const error = new Error(
14
+ "Qdrant is disabled because AI provider is set to fake"
15
+ );
16
+ error.code = "AI_DISABLED";
17
+ throw error;
18
+ }
44
19
 
20
+ if (!settings.qdrantUrl) {
21
+ throw new Error("Qdrant URL is not configured");
22
+ }
45
23
 
24
+ if (client && clientUrl === settings.qdrantUrl) {
25
+ return client;
26
+ }
46
27
 
28
+ client = new QdrantClient({
29
+ url: settings.qdrantUrl,
30
+ timeout: 30000
31
+ });
47
32
 
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
- function getClient() {
56
-
57
-
58
-
59
-
60
- if(client) {
61
-
33
+ clientUrl = settings.qdrantUrl;
62
34
 
63
35
  return client;
64
-
65
-
66
36
  }
67
37
 
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
- client = new QdrantClient({
76
-
77
-
78
- url:
79
-
80
- Config.qdrant.url
81
-
82
-
83
-
84
- });
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
- return client;
93
-
94
-
95
-
96
-
97
- }
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
- function isTestMode() {
112
-
113
-
114
-
115
-
116
- return (
117
-
118
- process.env.NODE_ENV === "test"
119
-
120
- );
121
-
122
-
123
-
124
-
38
+ async function collectionExists(qdrant, collection) {
39
+ const result = await qdrant.collectionExists(collection);
40
+ return result.exists === true;
125
41
  }
126
42
 
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
43
  async function ensureCollection() {
140
-
141
-
142
-
143
-
144
- if(isTestMode()) {
145
-
146
-
147
-
148
- console.log(
149
-
150
- "[Qdrant] test mode skip"
151
-
152
- );
153
-
154
-
155
- return true;
156
-
157
-
158
- }
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
- const qdrant =
168
-
169
- getClient();
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
- try {
179
-
180
-
181
-
182
- const collections =
183
-
184
- await qdrant.getCollections();
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
- const exists =
194
-
195
- collections.collections.some(
196
-
197
- c =>
198
-
199
- c.name === Config.qdrant.collection
200
-
201
- );
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
- if(!exists) {
211
-
212
-
213
-
214
- await qdrant.createCollection(
215
-
216
- Config.qdrant.collection,
217
-
218
- {
219
-
220
-
221
- vectors:
222
-
223
- {
224
-
225
-
226
- size:
227
-
228
- Config.embeddings.size,
229
-
230
-
231
- distance:
232
-
233
- "Cosine"
234
-
235
-
236
- }
237
-
238
-
44
+ const settings = await Config.get();
45
+ const qdrant = await getClient();
46
+
47
+ if (!(await collectionExists(qdrant, settings.collection))) {
48
+ await qdrant.createCollection(settings.collection, {
49
+ vectors: {
50
+ size: settings.embeddingSize,
51
+ distance: "Cosine"
239
52
  }
53
+ });
240
54
 
241
- );
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
- console.log(
251
-
252
- "[Qdrant] collection created"
55
+ return;
56
+ }
253
57
 
58
+ const info = await qdrant.getCollection(settings.collection);
59
+ const vectors = info.config?.params?.vectors;
60
+
61
+ if (
62
+ !vectors ||
63
+ typeof vectors.size !== "number" ||
64
+ vectors.size !== settings.embeddingSize ||
65
+ vectors.distance !== "Cosine"
66
+ ) {
67
+ throw new Error(
68
+ "Qdrant collection has an incompatible vector configuration"
254
69
  );
255
-
256
-
257
-
258
70
  }
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
- return true;
268
-
269
-
270
-
271
71
  }
272
72
 
73
+ async function upsert(points) {
74
+ if (!Array.isArray(points) || !points.length) {
75
+ return;
76
+ }
273
77
 
274
- catch(error) {
275
-
276
-
277
-
278
- console.error(
279
-
280
- "[Qdrant] collection error",
281
-
282
- error.message
283
-
284
- );
285
-
286
-
287
-
288
- return false;
289
-
290
-
291
-
292
- }
293
-
294
-
295
-
296
-
297
- }
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
- async function upsert(
312
-
313
- id,
314
-
315
- vector,
316
-
317
- payload = {}
318
-
319
- ) {
320
-
321
-
322
-
323
-
324
- if(isTestMode()) {
325
-
326
-
327
- return true;
328
-
329
-
330
- }
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
- const qdrant =
340
-
341
- getClient();
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
- try {
351
-
352
-
353
-
354
- await qdrant.upsert(
355
-
356
- Config.qdrant.collection,
357
-
358
- {
359
-
360
-
361
- wait: true,
362
-
363
-
364
- points:
365
-
366
- [
367
-
368
-
369
- {
370
-
371
-
372
- id,
373
-
374
-
375
- vector,
376
-
377
-
378
- payload
379
-
380
-
381
-
382
- }
383
-
384
-
385
- ]
386
-
387
-
388
-
389
- }
390
-
391
- );
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
- return true;
401
-
402
-
403
-
404
- }
405
-
406
-
407
- catch(error) {
408
-
409
-
410
-
411
- console.error(
412
-
413
- "[Qdrant] upsert error",
414
-
415
- error.message
416
-
417
- );
418
-
419
-
420
-
421
- return false;
422
-
423
-
424
-
425
- }
426
-
427
-
428
-
429
-
430
- }
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
- async function search(
445
-
446
- vector,
447
-
448
- limit = 5
449
-
450
- ) {
451
-
452
-
453
-
454
-
455
- if(isTestMode()) {
456
-
457
-
458
-
459
- return [];
460
-
78
+ const settings = await Config.get();
79
+ const qdrant = await getClient();
461
80
 
81
+ await qdrant.upsert(settings.collection, {
82
+ wait: true,
83
+ points
84
+ });
462
85
  }
463
86
 
87
+ async function search(vector, limit = 20) {
88
+ if (!Array.isArray(vector)) {
89
+ throw new TypeError("Search vector must be an array");
90
+ }
464
91
 
92
+ const settings = await Config.get();
465
93
 
94
+ if (vector.length !== settings.embeddingSize) {
95
+ throw new Error("Search vector dimension mismatch");
96
+ }
466
97
 
98
+ const qdrant = await getClient();
467
99
 
100
+ if (!(await collectionExists(qdrant, settings.collection))) {
101
+ return [];
102
+ }
468
103
 
104
+ const result = await qdrant.query(settings.collection, {
105
+ query: vector,
106
+ limit,
107
+ with_payload: true,
108
+ with_vector: false
109
+ });
469
110
 
470
-
471
- const qdrant =
472
-
473
- getClient();
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
482
- try {
483
-
484
-
485
-
486
- return await qdrant.search(
487
-
488
- Config.qdrant.collection,
489
-
490
- {
491
-
492
-
493
- vector,
494
-
495
-
496
- limit,
497
-
498
-
499
- with_payload: true
500
-
501
-
502
-
503
- }
504
-
505
- );
506
-
507
-
508
-
111
+ return Array.isArray(result.points) ? result.points : [];
509
112
  }
510
113
 
114
+ async function deletePoints(ids) {
115
+ if (!Array.isArray(ids) || !ids.length) {
116
+ return;
117
+ }
511
118
 
512
- catch(error) {
513
-
514
-
515
-
516
- console.error(
517
-
518
- "[Qdrant] search error",
519
-
520
- error.message
521
-
522
- );
523
-
524
-
525
-
526
- return [];
119
+ const settings = await Config.get();
120
+ const qdrant = await getClient();
527
121
 
122
+ if (!(await collectionExists(qdrant, settings.collection))) {
123
+ return;
124
+ }
528
125
 
126
+ await qdrant.delete(settings.collection, {
127
+ wait: true,
128
+ points: ids
129
+ });
529
130
  }
530
131
 
132
+ async function deleteByFilter(filter) {
133
+ if (
134
+ !filter ||
135
+ typeof filter !== "object" ||
136
+ Array.isArray(filter)
137
+ ) {
138
+ throw new TypeError("Qdrant filter is required");
139
+ }
531
140
 
141
+ const settings = await Config.get();
142
+ const qdrant = await getClient();
532
143
 
144
+ if (!(await collectionExists(qdrant, settings.collection))) {
145
+ return;
146
+ }
533
147
 
148
+ await qdrant.delete(settings.collection, {
149
+ wait: true,
150
+ filter
151
+ });
534
152
  }
535
153
 
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
154
  module.exports = {
549
-
550
- getClient,
551
-
552
-
553
- ensureCollection,
554
-
555
-
556
- upsert,
557
-
558
-
559
- search
560
-
561
-
562
-
563
-
564
- };
155
+ getClient,
156
+ ensureCollection,
157
+ upsert,
158
+ search,
159
+ deletePoints,
160
+ deleteByFilter
161
+ };