@retrivora-ai/rag-engine 1.0.9 → 1.1.1
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/dist/{QdrantProvider-NGFXVDCF.mjs → QdrantProvider-NMXOULCU.mjs} +1 -1
- package/dist/chunk-63HITIWC.mjs +245 -0
- package/dist/{chunk-7S2SRQGL.mjs → chunk-JZ4H7EP6.mjs} +147 -3
- package/dist/handlers/index.js +263 -48
- package/dist/handlers/index.mjs +1 -1
- package/dist/server.d.mts +12 -6
- package/dist/server.d.ts +12 -6
- package/dist/server.js +263 -48
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/core/Pipeline.ts +2 -0
- package/src/llm/providers/AnthropicProvider.ts +29 -0
- package/src/llm/providers/GeminiProvider.ts +31 -0
- package/src/llm/providers/OllamaProvider.ts +21 -2
- package/src/llm/providers/OpenAIProvider.ts +35 -0
- package/src/providers/vectordb/QdrantProvider.ts +154 -60
- package/dist/chunk-NT5VP7MT.mjs +0 -174
|
@@ -6,9 +6,14 @@ import { VectorMatch, UpsertDocument } from '../../types';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* QdrantProvider — implementation for Qdrant using its REST API.
|
|
9
|
+
* Fully dynamic: automatically discovers and indexes schema from available data.
|
|
9
10
|
*/
|
|
10
11
|
export class QdrantProvider extends BaseVectorProvider {
|
|
11
12
|
private http: AxiosInstance;
|
|
13
|
+
private contentField: string;
|
|
14
|
+
private metadataField: string;
|
|
15
|
+
private isFlatPayload: boolean;
|
|
16
|
+
private schemaDiscovered = false;
|
|
12
17
|
|
|
13
18
|
constructor(config: VectorDBConfig) {
|
|
14
19
|
super(config);
|
|
@@ -16,8 +21,13 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
16
21
|
const baseUrl = opts.baseUrl as string;
|
|
17
22
|
if (!baseUrl) throw new Error('[QdrantProvider] baseUrl is required');
|
|
18
23
|
|
|
24
|
+
this.contentField = (opts.contentField as string) || 'content';
|
|
25
|
+
this.metadataField = (opts.metadataField as string) || 'metadata';
|
|
26
|
+
this.isFlatPayload = !!opts.flatPayload;
|
|
27
|
+
|
|
19
28
|
this.http = axios.create({
|
|
20
29
|
baseURL: baseUrl,
|
|
30
|
+
timeout: 15000, // 15s timeout for vector DB operations
|
|
21
31
|
headers: {
|
|
22
32
|
'Content-Type': 'application/json',
|
|
23
33
|
...(opts.apiKey ? { 'api-key': opts.apiKey as string } : {}),
|
|
@@ -26,9 +36,76 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
async initialize(): Promise<void> {
|
|
39
|
+
if (this.schemaDiscovered) return;
|
|
40
|
+
|
|
29
41
|
await this.ping();
|
|
30
42
|
await this.ensureCollection();
|
|
31
|
-
|
|
43
|
+
|
|
44
|
+
// 1. Discover schema from existing data
|
|
45
|
+
console.log(`[QdrantProvider] 🔍 Discovering schema for collection "${this.indexName}"...`);
|
|
46
|
+
const discoveredFields = await this.discoverSchema();
|
|
47
|
+
|
|
48
|
+
// 2. Add client-provided fields (if any) as fallback/override
|
|
49
|
+
const opts = this.config.options as Record<string, unknown>;
|
|
50
|
+
const configFields = (opts.filterableFields as string[]) || [];
|
|
51
|
+
|
|
52
|
+
const allFields = [...new Set([...discoveredFields, ...configFields])];
|
|
53
|
+
|
|
54
|
+
if (allFields.length > 0) {
|
|
55
|
+
console.log(`[QdrantProvider] 🛠 Ensuring indexes for ${allFields.length} discovered fields...`);
|
|
56
|
+
// Parallelize index creation for much faster startup
|
|
57
|
+
await Promise.all(
|
|
58
|
+
allFields.map(async (fieldInfo) => {
|
|
59
|
+
const [fieldName, schemaType] = fieldInfo.split(':');
|
|
60
|
+
return this.ensureIndex(fieldName, (schemaType as any) || 'keyword');
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(`[QdrantProvider] ℹ️ No fields discovered for indexing.`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.schemaDiscovered = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Samples points from the collection to discover available payload fields.
|
|
72
|
+
*/
|
|
73
|
+
private async discoverSchema(): Promise<string[]> {
|
|
74
|
+
try {
|
|
75
|
+
// Fetch 20 points to get a representative sample of the schema
|
|
76
|
+
const { data } = await this.http.post(`/collections/${this.indexName}/points/scroll`, {
|
|
77
|
+
limit: 20,
|
|
78
|
+
with_payload: true,
|
|
79
|
+
with_vector: false
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const points = data.result?.points || [];
|
|
83
|
+
const keys = new Set<string>();
|
|
84
|
+
|
|
85
|
+
for (const point of points) {
|
|
86
|
+
const payload = point.payload || {};
|
|
87
|
+
|
|
88
|
+
// Extract root keys
|
|
89
|
+
Object.keys(payload).forEach(k => {
|
|
90
|
+
if (k !== 'namespace' && k !== this.contentField && k !== this.metadataField) {
|
|
91
|
+
keys.add(k);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Extract nested metadata keys if applicable
|
|
96
|
+
if (!this.isFlatPayload && payload[this.metadataField]) {
|
|
97
|
+
const meta = payload[this.metadataField];
|
|
98
|
+
if (typeof meta === 'object' && meta !== null) {
|
|
99
|
+
Object.keys(meta).forEach(k => keys.add(k));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Array.from(keys);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.warn(`[QdrantProvider] ⚠️ Schema discovery failed:`, err instanceof Error ? err.message : String(err));
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
32
109
|
}
|
|
33
110
|
|
|
34
111
|
/**
|
|
@@ -58,32 +135,25 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
58
135
|
}
|
|
59
136
|
|
|
60
137
|
/**
|
|
61
|
-
* Ensures that
|
|
62
|
-
* Qdrant requires this for search filters to work in many configurations.
|
|
138
|
+
* Ensures that a payload field has an index.
|
|
63
139
|
*/
|
|
64
|
-
private async ensureIndex(): Promise<void> {
|
|
140
|
+
private async ensureIndex(fieldName: string, schema: string = 'keyword'): Promise<void> {
|
|
141
|
+
let fullPath = fieldName;
|
|
142
|
+
if (!this.isFlatPayload && !fieldName.includes('.') && fieldName !== 'namespace' && fieldName !== this.contentField) {
|
|
143
|
+
fullPath = `${this.metadataField}.${fieldName}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
65
146
|
try {
|
|
66
147
|
await this.http.put(`/collections/${this.indexName}/index`, {
|
|
67
|
-
field_name:
|
|
68
|
-
field_schema:
|
|
148
|
+
field_name: fullPath,
|
|
149
|
+
field_schema: schema,
|
|
69
150
|
});
|
|
70
|
-
console.log(`[QdrantProvider] ✅ Ensured
|
|
151
|
+
console.log(`[QdrantProvider] ✅ Ensured ${schema} index for "${fullPath}"`);
|
|
71
152
|
} catch (err) {
|
|
72
153
|
let status: number | undefined;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
status = err.response?.status;
|
|
77
|
-
data = err.response?.data;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 409 means it already exists, which is fine.
|
|
81
|
-
if (status === 409) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
86
|
-
console.warn(`[QdrantProvider] ⚠️ Could not ensure namespace index (Status: ${status}):`, JSON.stringify(data || errorMessage));
|
|
154
|
+
if (axios.isAxiosError(err)) status = err.response?.status;
|
|
155
|
+
if (status === 409) return;
|
|
156
|
+
console.warn(`[QdrantProvider] ⚠️ Could not ensure index for "${fullPath}":`, err instanceof Error ? err.message : String(err));
|
|
87
157
|
}
|
|
88
158
|
}
|
|
89
159
|
|
|
@@ -93,15 +163,24 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
93
163
|
|
|
94
164
|
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
95
165
|
const payload = {
|
|
96
|
-
points: docs.map(doc =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
payload: {
|
|
100
|
-
content: doc.content,
|
|
101
|
-
metadata: doc.metadata || {},
|
|
166
|
+
points: docs.map(doc => {
|
|
167
|
+
const pointPayload: Record<string, any> = {
|
|
168
|
+
[this.contentField]: doc.content,
|
|
102
169
|
...(namespace ? { namespace } : {}),
|
|
103
|
-
}
|
|
104
|
-
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (this.isFlatPayload) {
|
|
173
|
+
Object.assign(pointPayload, doc.metadata || {});
|
|
174
|
+
} else {
|
|
175
|
+
pointPayload[this.metadataField] = doc.metadata || {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
id: this.normalizeId(doc.id),
|
|
180
|
+
vector: doc.vector,
|
|
181
|
+
payload: pointPayload,
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
105
184
|
};
|
|
106
185
|
await this.http.put(`/collections/${this.indexName}/points`, payload);
|
|
107
186
|
}
|
|
@@ -113,12 +192,18 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
113
192
|
must.push({ key: 'namespace', match: { value: namespace } });
|
|
114
193
|
}
|
|
115
194
|
|
|
116
|
-
// 1. Apply metadata filters
|
|
117
195
|
const sanitizedFilter = this.sanitizeFilter(_filter);
|
|
118
196
|
for (const [key, value] of Object.entries(sanitizedFilter)) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
197
|
+
let filterKey = key;
|
|
198
|
+
if (!this.isFlatPayload && !key.includes('.') && key !== 'namespace' && key !== this.contentField) {
|
|
199
|
+
filterKey = `${this.metadataField}.${key}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
must.push({ key: filterKey, match: { any: value } });
|
|
204
|
+
} else {
|
|
205
|
+
must.push({ key: filterKey, match: { value: value } });
|
|
206
|
+
}
|
|
122
207
|
}
|
|
123
208
|
|
|
124
209
|
const payload = {
|
|
@@ -132,31 +217,45 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
132
217
|
filter: must.length > 0 ? { must } : undefined,
|
|
133
218
|
};
|
|
134
219
|
|
|
135
|
-
console.log(`[QdrantProvider] 🔍 Searching "${this.indexName}" | Namespace: ${namespace || 'default'} | TopK: ${topK}`);
|
|
136
|
-
if (must.length > (namespace ? 1 : 0)) {
|
|
137
|
-
console.log(`[QdrantProvider] 🛠 Filters:`, JSON.stringify(must.filter(m => m.key !== 'namespace')));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
220
|
const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
|
|
141
221
|
|
|
142
|
-
const results = (data.result || []).map((res: Record<string,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
222
|
+
const results = (data.result || []).map((res: Record<string, any>) => {
|
|
223
|
+
const p = res.payload || {};
|
|
224
|
+
let content = p[this.contentField] || '';
|
|
225
|
+
|
|
226
|
+
if (!content) {
|
|
227
|
+
const stringFields = Object.entries(p)
|
|
228
|
+
.filter(([_, v]) => typeof v === 'string')
|
|
229
|
+
.map(([k, v]) => ({ key: k, val: v as string }));
|
|
230
|
+
|
|
231
|
+
if (stringFields.length > 0) {
|
|
232
|
+
const bestMatch = stringFields.sort((a, b) => b.val.length - a.val.length)[0];
|
|
233
|
+
content = bestMatch.val;
|
|
234
|
+
} else {
|
|
235
|
+
content = JSON.stringify(p);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let metadata: Record<string, any> = {};
|
|
240
|
+
if (this.isFlatPayload) {
|
|
241
|
+
metadata = { ...p };
|
|
242
|
+
delete metadata[this.contentField];
|
|
243
|
+
delete metadata['namespace'];
|
|
244
|
+
} else {
|
|
245
|
+
metadata = p[this.metadataField] || p;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
id: res.id as string,
|
|
250
|
+
score: res.score as number,
|
|
251
|
+
content,
|
|
252
|
+
metadata,
|
|
253
|
+
};
|
|
254
|
+
});
|
|
154
255
|
|
|
155
256
|
return results;
|
|
156
257
|
}
|
|
157
258
|
|
|
158
|
-
|
|
159
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
160
259
|
async delete(id: string | number, _namespace?: string): Promise<void> {
|
|
161
260
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
162
261
|
points: [this.normalizeId(id)],
|
|
@@ -180,21 +279,16 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
180
279
|
}
|
|
181
280
|
}
|
|
182
281
|
|
|
183
|
-
/**
|
|
184
|
-
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
185
|
-
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
186
|
-
*/
|
|
187
282
|
private normalizeId(id: string | number): string | number {
|
|
188
283
|
if (typeof id === 'number') return id;
|
|
189
|
-
|
|
190
|
-
// Check if already a valid UUID
|
|
191
284
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
192
285
|
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
193
|
-
|
|
194
|
-
// Hash string to deterministic UUID
|
|
195
286
|
const hash = crypto.createHash('md5').update(id).digest('hex');
|
|
196
287
|
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
197
288
|
}
|
|
198
289
|
|
|
199
290
|
async disconnect(): Promise<void> {}
|
|
200
291
|
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
package/dist/chunk-NT5VP7MT.mjs
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BaseVectorProvider
|
|
3
|
-
} from "./chunk-IMP6FUCY.mjs";
|
|
4
|
-
import {
|
|
5
|
-
__spreadValues
|
|
6
|
-
} from "./chunk-X4TOT24V.mjs";
|
|
7
|
-
|
|
8
|
-
// src/providers/vectordb/QdrantProvider.ts
|
|
9
|
-
import axios from "axios";
|
|
10
|
-
import crypto from "crypto";
|
|
11
|
-
var QdrantProvider = class extends BaseVectorProvider {
|
|
12
|
-
constructor(config) {
|
|
13
|
-
super(config);
|
|
14
|
-
const opts = config.options;
|
|
15
|
-
const baseUrl = opts.baseUrl;
|
|
16
|
-
if (!baseUrl) throw new Error("[QdrantProvider] baseUrl is required");
|
|
17
|
-
this.http = axios.create({
|
|
18
|
-
baseURL: baseUrl,
|
|
19
|
-
headers: __spreadValues({
|
|
20
|
-
"Content-Type": "application/json"
|
|
21
|
-
}, opts.apiKey ? { "api-key": opts.apiKey } : {})
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
async initialize() {
|
|
25
|
-
await this.ping();
|
|
26
|
-
await this.ensureCollection();
|
|
27
|
-
await this.ensureIndex();
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Ensures the collection exists. Creates it if missing.
|
|
31
|
-
*/
|
|
32
|
-
async ensureCollection() {
|
|
33
|
-
var _a;
|
|
34
|
-
try {
|
|
35
|
-
await this.http.get(`/collections/${this.indexName}`);
|
|
36
|
-
console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
if (axios.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
39
|
-
const opts = this.config.options;
|
|
40
|
-
const dimensionsForCreate = opts.dimensions || 1536;
|
|
41
|
-
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
|
|
42
|
-
await this.http.put(`/collections/${this.indexName}`, {
|
|
43
|
-
vectors: {
|
|
44
|
-
size: dimensionsForCreate,
|
|
45
|
-
distance: "Cosine"
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
49
|
-
} else {
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
56
|
-
* Qdrant requires this for search filters to work in many configurations.
|
|
57
|
-
*/
|
|
58
|
-
async ensureIndex() {
|
|
59
|
-
var _a, _b;
|
|
60
|
-
try {
|
|
61
|
-
await this.http.put(`/collections/${this.indexName}/index`, {
|
|
62
|
-
field_name: "namespace",
|
|
63
|
-
field_schema: "keyword"
|
|
64
|
-
});
|
|
65
|
-
console.log(`[QdrantProvider] \u2705 Ensured keyword index for "namespace" on collection "${this.indexName}"`);
|
|
66
|
-
} catch (err) {
|
|
67
|
-
let status;
|
|
68
|
-
let data;
|
|
69
|
-
if (axios.isAxiosError(err)) {
|
|
70
|
-
status = (_a = err.response) == null ? void 0 : _a.status;
|
|
71
|
-
data = (_b = err.response) == null ? void 0 : _b.data;
|
|
72
|
-
}
|
|
73
|
-
if (status === 409) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
77
|
-
console.warn(`[QdrantProvider] \u26A0\uFE0F Could not ensure namespace index (Status: ${status}):`, JSON.stringify(data || errorMessage));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async upsert(doc, namespace) {
|
|
81
|
-
await this.batchUpsert([doc], namespace);
|
|
82
|
-
}
|
|
83
|
-
async batchUpsert(docs, namespace) {
|
|
84
|
-
const payload = {
|
|
85
|
-
points: docs.map((doc) => ({
|
|
86
|
-
id: this.normalizeId(doc.id),
|
|
87
|
-
vector: doc.vector,
|
|
88
|
-
payload: __spreadValues({
|
|
89
|
-
content: doc.content,
|
|
90
|
-
metadata: doc.metadata || {}
|
|
91
|
-
}, namespace ? { namespace } : {})
|
|
92
|
-
}))
|
|
93
|
-
};
|
|
94
|
-
await this.http.put(`/collections/${this.indexName}/points`, payload);
|
|
95
|
-
}
|
|
96
|
-
async query(vector, topK, namespace, _filter) {
|
|
97
|
-
const must = [];
|
|
98
|
-
if (namespace) {
|
|
99
|
-
must.push({ key: "namespace", match: { value: namespace } });
|
|
100
|
-
}
|
|
101
|
-
const sanitizedFilter = this.sanitizeFilter(_filter);
|
|
102
|
-
for (const [key, value] of Object.entries(sanitizedFilter)) {
|
|
103
|
-
must.push({ key: `metadata.${key}`, match: { value } });
|
|
104
|
-
}
|
|
105
|
-
const payload = {
|
|
106
|
-
vector,
|
|
107
|
-
limit: topK,
|
|
108
|
-
with_payload: true,
|
|
109
|
-
params: {
|
|
110
|
-
hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
|
|
111
|
-
exact: false
|
|
112
|
-
},
|
|
113
|
-
filter: must.length > 0 ? { must } : void 0
|
|
114
|
-
};
|
|
115
|
-
console.log(`[QdrantProvider] \u{1F50D} Searching "${this.indexName}" | Namespace: ${namespace || "default"} | TopK: ${topK}`);
|
|
116
|
-
if (must.length > (namespace ? 1 : 0)) {
|
|
117
|
-
console.log(`[QdrantProvider] \u{1F6E0} Filters:`, JSON.stringify(must.filter((m) => m.key !== "namespace")));
|
|
118
|
-
}
|
|
119
|
-
const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
|
|
120
|
-
const results = (data.result || []).map((res) => {
|
|
121
|
-
var _a, _b;
|
|
122
|
-
return {
|
|
123
|
-
id: res["id"],
|
|
124
|
-
score: res["score"],
|
|
125
|
-
content: ((_a = res["payload"]) == null ? void 0 : _a.content) || "",
|
|
126
|
-
metadata: ((_b = res["payload"]) == null ? void 0 : _b.metadata) || {}
|
|
127
|
-
};
|
|
128
|
-
});
|
|
129
|
-
if (results.length === 0) {
|
|
130
|
-
console.warn(`[QdrantProvider] \u26A0\uFE0F Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
|
|
131
|
-
} else {
|
|
132
|
-
console.log(`[QdrantProvider] \u2705 Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
|
|
133
|
-
}
|
|
134
|
-
return results;
|
|
135
|
-
}
|
|
136
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
137
|
-
async delete(id, _namespace) {
|
|
138
|
-
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
139
|
-
points: [this.normalizeId(id)]
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
async deleteNamespace(_namespace) {
|
|
143
|
-
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
144
|
-
filter: {
|
|
145
|
-
must: [{ key: "namespace", match: { value: _namespace } }]
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
async ping() {
|
|
150
|
-
try {
|
|
151
|
-
await this.http.get("/healthz");
|
|
152
|
-
return true;
|
|
153
|
-
} catch (e) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
159
|
-
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
160
|
-
*/
|
|
161
|
-
normalizeId(id) {
|
|
162
|
-
if (typeof id === "number") return id;
|
|
163
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
164
|
-
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
165
|
-
const hash = crypto.createHash("md5").update(id).digest("hex");
|
|
166
|
-
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
167
|
-
}
|
|
168
|
-
async disconnect() {
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
export {
|
|
173
|
-
QdrantProvider
|
|
174
|
-
};
|