@retrivora-ai/rag-engine 0.1.6 → 0.1.7
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-NYGHAA7C.mjs → QdrantProvider-VAED5VA7.mjs} +1 -1
- package/dist/{chunk-HSBXE2WV.mjs → chunk-7YQWGERZ.mjs} +11 -5
- package/dist/{chunk-5K23H7JL.mjs → chunk-CWQQHAF6.mjs} +42 -2
- package/dist/handlers/index.js +53 -7
- package/dist/handlers/index.mjs +1 -1
- package/dist/server.d.mts +9 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +53 -7
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/config/serverConfig.ts +1 -0
- package/src/core/ConfigValidator.ts +12 -4
- package/src/core/ProviderHealthCheck.ts +4 -1
- package/src/providers/vectordb/QdrantProvider.ts +49 -2
|
@@ -67,6 +67,7 @@ function getRagConfig(env = process.env) {
|
|
|
67
67
|
} else if (vectorProvider === "qdrant") {
|
|
68
68
|
vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
|
|
69
69
|
vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
|
|
70
|
+
vectorDbOptions.dimensions = embeddingDimensions;
|
|
70
71
|
}
|
|
71
72
|
const llmApiKeyByProvider = {
|
|
72
73
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
@@ -603,9 +604,11 @@ var ConfigValidator = class {
|
|
|
603
604
|
return errors;
|
|
604
605
|
}
|
|
605
606
|
static isValidCSSColor(color) {
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
607
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
608
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
609
|
+
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
610
|
+
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
611
|
+
return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
609
612
|
}
|
|
610
613
|
/**
|
|
611
614
|
* Throws if there are error-level validation issues.
|
|
@@ -720,7 +723,7 @@ var ProviderRegistry = class {
|
|
|
720
723
|
const { MilvusProvider } = await import("./MilvusProvider-OO6QGZDZ.mjs");
|
|
721
724
|
return new MilvusProvider(config);
|
|
722
725
|
case "qdrant":
|
|
723
|
-
const { QdrantProvider } = await import("./QdrantProvider-
|
|
726
|
+
const { QdrantProvider } = await import("./QdrantProvider-VAED5VA7.mjs");
|
|
724
727
|
return new QdrantProvider(config);
|
|
725
728
|
case "chromadb":
|
|
726
729
|
const { ChromaDBProvider } = await import("./ChromaDBProvider-QNI7UCX4.mjs");
|
|
@@ -1261,7 +1264,10 @@ var ProviderHealthCheck = class {
|
|
|
1261
1264
|
const opts = config.options;
|
|
1262
1265
|
const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
|
|
1263
1266
|
try {
|
|
1264
|
-
const
|
|
1267
|
+
const apiKey = opts.apiKey;
|
|
1268
|
+
const response = await fetch(`${baseUrl}/`, {
|
|
1269
|
+
headers: apiKey ? { "api-key": apiKey } : {}
|
|
1270
|
+
});
|
|
1265
1271
|
if (!response.ok) {
|
|
1266
1272
|
throw new Error(`Health check returned ${response.status}`);
|
|
1267
1273
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/providers/vectordb/QdrantProvider.ts
|
|
9
9
|
import axios from "axios";
|
|
10
|
+
import crypto from "crypto";
|
|
10
11
|
var QdrantProvider = class extends BaseVectorProvider {
|
|
11
12
|
constructor(config) {
|
|
12
13
|
super(config);
|
|
@@ -22,8 +23,36 @@ var QdrantProvider = class extends BaseVectorProvider {
|
|
|
22
23
|
}
|
|
23
24
|
async initialize() {
|
|
24
25
|
await this.ping();
|
|
26
|
+
await this.ensureCollection();
|
|
25
27
|
await this.ensureIndex();
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Ensures the collection exists. Creates it if missing.
|
|
31
|
+
*/
|
|
32
|
+
async ensureCollection() {
|
|
33
|
+
var _a;
|
|
34
|
+
try {
|
|
35
|
+
const opts = this.config.options;
|
|
36
|
+
const dimensions = opts.dimensions || 1536;
|
|
37
|
+
await this.http.get(`/collections/${this.indexName}`);
|
|
38
|
+
console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (axios.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
41
|
+
const opts = this.config.options;
|
|
42
|
+
const dimensions = opts.dimensions || 1536;
|
|
43
|
+
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
|
|
44
|
+
await this.http.put(`/collections/${this.indexName}`, {
|
|
45
|
+
vectors: {
|
|
46
|
+
size: dimensions,
|
|
47
|
+
distance: "Cosine"
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
51
|
+
} else {
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
27
56
|
/**
|
|
28
57
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
29
58
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -56,7 +85,7 @@ var QdrantProvider = class extends BaseVectorProvider {
|
|
|
56
85
|
async batchUpsert(docs, namespace) {
|
|
57
86
|
const payload = {
|
|
58
87
|
points: docs.map((doc) => ({
|
|
59
|
-
id: doc.id,
|
|
88
|
+
id: this.normalizeId(doc.id),
|
|
60
89
|
vector: doc.vector,
|
|
61
90
|
payload: __spreadValues({
|
|
62
91
|
content: doc.content,
|
|
@@ -90,7 +119,7 @@ var QdrantProvider = class extends BaseVectorProvider {
|
|
|
90
119
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
91
120
|
async delete(id, _namespace) {
|
|
92
121
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
93
|
-
points: [id]
|
|
122
|
+
points: [this.normalizeId(id)]
|
|
94
123
|
});
|
|
95
124
|
}
|
|
96
125
|
async deleteNamespace(_namespace) {
|
|
@@ -108,6 +137,17 @@ var QdrantProvider = class extends BaseVectorProvider {
|
|
|
108
137
|
return false;
|
|
109
138
|
}
|
|
110
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
142
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
143
|
+
*/
|
|
144
|
+
normalizeId(id) {
|
|
145
|
+
if (typeof id === "number") return id;
|
|
146
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
147
|
+
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
148
|
+
const hash = crypto.createHash("md5").update(id).digest("hex");
|
|
149
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
150
|
+
}
|
|
111
151
|
async disconnect() {
|
|
112
152
|
}
|
|
113
153
|
};
|
package/dist/handlers/index.js
CHANGED
|
@@ -955,11 +955,12 @@ var QdrantProvider_exports = {};
|
|
|
955
955
|
__export(QdrantProvider_exports, {
|
|
956
956
|
QdrantProvider: () => QdrantProvider
|
|
957
957
|
});
|
|
958
|
-
var import_axios4, QdrantProvider;
|
|
958
|
+
var import_axios4, import_crypto, QdrantProvider;
|
|
959
959
|
var init_QdrantProvider = __esm({
|
|
960
960
|
"src/providers/vectordb/QdrantProvider.ts"() {
|
|
961
961
|
"use strict";
|
|
962
962
|
import_axios4 = __toESM(require("axios"));
|
|
963
|
+
import_crypto = __toESM(require("crypto"));
|
|
963
964
|
init_BaseVectorProvider();
|
|
964
965
|
QdrantProvider = class extends BaseVectorProvider {
|
|
965
966
|
constructor(config) {
|
|
@@ -976,8 +977,36 @@ var init_QdrantProvider = __esm({
|
|
|
976
977
|
}
|
|
977
978
|
async initialize() {
|
|
978
979
|
await this.ping();
|
|
980
|
+
await this.ensureCollection();
|
|
979
981
|
await this.ensureIndex();
|
|
980
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Ensures the collection exists. Creates it if missing.
|
|
985
|
+
*/
|
|
986
|
+
async ensureCollection() {
|
|
987
|
+
var _a;
|
|
988
|
+
try {
|
|
989
|
+
const opts = this.config.options;
|
|
990
|
+
const dimensions = opts.dimensions || 1536;
|
|
991
|
+
await this.http.get(`/collections/${this.indexName}`);
|
|
992
|
+
console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
995
|
+
const opts = this.config.options;
|
|
996
|
+
const dimensions = opts.dimensions || 1536;
|
|
997
|
+
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
|
|
998
|
+
await this.http.put(`/collections/${this.indexName}`, {
|
|
999
|
+
vectors: {
|
|
1000
|
+
size: dimensions,
|
|
1001
|
+
distance: "Cosine"
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
1005
|
+
} else {
|
|
1006
|
+
throw err;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
981
1010
|
/**
|
|
982
1011
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
983
1012
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -1010,7 +1039,7 @@ var init_QdrantProvider = __esm({
|
|
|
1010
1039
|
async batchUpsert(docs, namespace) {
|
|
1011
1040
|
const payload = {
|
|
1012
1041
|
points: docs.map((doc) => ({
|
|
1013
|
-
id: doc.id,
|
|
1042
|
+
id: this.normalizeId(doc.id),
|
|
1014
1043
|
vector: doc.vector,
|
|
1015
1044
|
payload: __spreadValues({
|
|
1016
1045
|
content: doc.content,
|
|
@@ -1044,7 +1073,7 @@ var init_QdrantProvider = __esm({
|
|
|
1044
1073
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1045
1074
|
async delete(id, _namespace) {
|
|
1046
1075
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
1047
|
-
points: [id]
|
|
1076
|
+
points: [this.normalizeId(id)]
|
|
1048
1077
|
});
|
|
1049
1078
|
}
|
|
1050
1079
|
async deleteNamespace(_namespace) {
|
|
@@ -1062,6 +1091,17 @@ var init_QdrantProvider = __esm({
|
|
|
1062
1091
|
return false;
|
|
1063
1092
|
}
|
|
1064
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
1096
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
1097
|
+
*/
|
|
1098
|
+
normalizeId(id) {
|
|
1099
|
+
if (typeof id === "number") return id;
|
|
1100
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1101
|
+
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
1102
|
+
const hash = import_crypto.default.createHash("md5").update(id).digest("hex");
|
|
1103
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
1104
|
+
}
|
|
1065
1105
|
async disconnect() {
|
|
1066
1106
|
}
|
|
1067
1107
|
};
|
|
@@ -1418,6 +1458,7 @@ function getRagConfig(env = process.env) {
|
|
|
1418
1458
|
} else if (vectorProvider === "qdrant") {
|
|
1419
1459
|
vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
|
|
1420
1460
|
vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
|
|
1461
|
+
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1421
1462
|
}
|
|
1422
1463
|
const llmApiKeyByProvider = {
|
|
1423
1464
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
@@ -1954,9 +1995,11 @@ var ConfigValidator = class {
|
|
|
1954
1995
|
return errors;
|
|
1955
1996
|
}
|
|
1956
1997
|
static isValidCSSColor(color) {
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
|
|
1998
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
1999
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2000
|
+
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2001
|
+
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
2002
|
+
return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
1960
2003
|
}
|
|
1961
2004
|
/**
|
|
1962
2005
|
* Throws if there are error-level validation issues.
|
|
@@ -2613,7 +2656,10 @@ var ProviderHealthCheck = class {
|
|
|
2613
2656
|
const opts = config.options;
|
|
2614
2657
|
const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
|
|
2615
2658
|
try {
|
|
2616
|
-
const
|
|
2659
|
+
const apiKey = opts.apiKey;
|
|
2660
|
+
const response = await fetch(`${baseUrl}/`, {
|
|
2661
|
+
headers: apiKey ? { "api-key": apiKey } : {}
|
|
2662
|
+
});
|
|
2617
2663
|
if (!response.ok) {
|
|
2618
2664
|
throw new Error(`Health check returned ${response.status}`);
|
|
2619
2665
|
}
|
package/dist/handlers/index.mjs
CHANGED
package/dist/server.d.mts
CHANGED
|
@@ -266,6 +266,10 @@ declare class QdrantProvider extends BaseVectorProvider {
|
|
|
266
266
|
private http;
|
|
267
267
|
constructor(config: VectorDBConfig);
|
|
268
268
|
initialize(): Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* Ensures the collection exists. Creates it if missing.
|
|
271
|
+
*/
|
|
272
|
+
private ensureCollection;
|
|
269
273
|
/**
|
|
270
274
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
271
275
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -277,6 +281,11 @@ declare class QdrantProvider extends BaseVectorProvider {
|
|
|
277
281
|
delete(id: string | number, _namespace?: string): Promise<void>;
|
|
278
282
|
deleteNamespace(_namespace: string): Promise<void>;
|
|
279
283
|
ping(): Promise<boolean>;
|
|
284
|
+
/**
|
|
285
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
286
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
287
|
+
*/
|
|
288
|
+
private normalizeId;
|
|
280
289
|
disconnect(): Promise<void>;
|
|
281
290
|
}
|
|
282
291
|
|
package/dist/server.d.ts
CHANGED
|
@@ -266,6 +266,10 @@ declare class QdrantProvider extends BaseVectorProvider {
|
|
|
266
266
|
private http;
|
|
267
267
|
constructor(config: VectorDBConfig);
|
|
268
268
|
initialize(): Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* Ensures the collection exists. Creates it if missing.
|
|
271
|
+
*/
|
|
272
|
+
private ensureCollection;
|
|
269
273
|
/**
|
|
270
274
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
271
275
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -277,6 +281,11 @@ declare class QdrantProvider extends BaseVectorProvider {
|
|
|
277
281
|
delete(id: string | number, _namespace?: string): Promise<void>;
|
|
278
282
|
deleteNamespace(_namespace: string): Promise<void>;
|
|
279
283
|
ping(): Promise<boolean>;
|
|
284
|
+
/**
|
|
285
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
286
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
287
|
+
*/
|
|
288
|
+
private normalizeId;
|
|
280
289
|
disconnect(): Promise<void>;
|
|
281
290
|
}
|
|
282
291
|
|
package/dist/server.js
CHANGED
|
@@ -955,11 +955,12 @@ var QdrantProvider_exports = {};
|
|
|
955
955
|
__export(QdrantProvider_exports, {
|
|
956
956
|
QdrantProvider: () => QdrantProvider
|
|
957
957
|
});
|
|
958
|
-
var import_axios4, QdrantProvider;
|
|
958
|
+
var import_axios4, import_crypto, QdrantProvider;
|
|
959
959
|
var init_QdrantProvider = __esm({
|
|
960
960
|
"src/providers/vectordb/QdrantProvider.ts"() {
|
|
961
961
|
"use strict";
|
|
962
962
|
import_axios4 = __toESM(require("axios"));
|
|
963
|
+
import_crypto = __toESM(require("crypto"));
|
|
963
964
|
init_BaseVectorProvider();
|
|
964
965
|
QdrantProvider = class extends BaseVectorProvider {
|
|
965
966
|
constructor(config) {
|
|
@@ -976,8 +977,36 @@ var init_QdrantProvider = __esm({
|
|
|
976
977
|
}
|
|
977
978
|
async initialize() {
|
|
978
979
|
await this.ping();
|
|
980
|
+
await this.ensureCollection();
|
|
979
981
|
await this.ensureIndex();
|
|
980
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Ensures the collection exists. Creates it if missing.
|
|
985
|
+
*/
|
|
986
|
+
async ensureCollection() {
|
|
987
|
+
var _a;
|
|
988
|
+
try {
|
|
989
|
+
const opts = this.config.options;
|
|
990
|
+
const dimensions = opts.dimensions || 1536;
|
|
991
|
+
await this.http.get(`/collections/${this.indexName}`);
|
|
992
|
+
console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
995
|
+
const opts = this.config.options;
|
|
996
|
+
const dimensions = opts.dimensions || 1536;
|
|
997
|
+
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
|
|
998
|
+
await this.http.put(`/collections/${this.indexName}`, {
|
|
999
|
+
vectors: {
|
|
1000
|
+
size: dimensions,
|
|
1001
|
+
distance: "Cosine"
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
1005
|
+
} else {
|
|
1006
|
+
throw err;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
981
1010
|
/**
|
|
982
1011
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
983
1012
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -1010,7 +1039,7 @@ var init_QdrantProvider = __esm({
|
|
|
1010
1039
|
async batchUpsert(docs, namespace) {
|
|
1011
1040
|
const payload = {
|
|
1012
1041
|
points: docs.map((doc) => ({
|
|
1013
|
-
id: doc.id,
|
|
1042
|
+
id: this.normalizeId(doc.id),
|
|
1014
1043
|
vector: doc.vector,
|
|
1015
1044
|
payload: __spreadValues({
|
|
1016
1045
|
content: doc.content,
|
|
@@ -1044,7 +1073,7 @@ var init_QdrantProvider = __esm({
|
|
|
1044
1073
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1045
1074
|
async delete(id, _namespace) {
|
|
1046
1075
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
1047
|
-
points: [id]
|
|
1076
|
+
points: [this.normalizeId(id)]
|
|
1048
1077
|
});
|
|
1049
1078
|
}
|
|
1050
1079
|
async deleteNamespace(_namespace) {
|
|
@@ -1062,6 +1091,17 @@ var init_QdrantProvider = __esm({
|
|
|
1062
1091
|
return false;
|
|
1063
1092
|
}
|
|
1064
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
1096
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
1097
|
+
*/
|
|
1098
|
+
normalizeId(id) {
|
|
1099
|
+
if (typeof id === "number") return id;
|
|
1100
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1101
|
+
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
1102
|
+
const hash = import_crypto.default.createHash("md5").update(id).digest("hex");
|
|
1103
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
1104
|
+
}
|
|
1065
1105
|
async disconnect() {
|
|
1066
1106
|
}
|
|
1067
1107
|
};
|
|
@@ -1436,6 +1476,7 @@ function getRagConfig(env = process.env) {
|
|
|
1436
1476
|
} else if (vectorProvider === "qdrant") {
|
|
1437
1477
|
vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
|
|
1438
1478
|
vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
|
|
1479
|
+
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1439
1480
|
}
|
|
1440
1481
|
const llmApiKeyByProvider = {
|
|
1441
1482
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
@@ -1972,9 +2013,11 @@ var ConfigValidator = class {
|
|
|
1972
2013
|
return errors;
|
|
1973
2014
|
}
|
|
1974
2015
|
static isValidCSSColor(color) {
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
|
|
2016
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
2017
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2018
|
+
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2019
|
+
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
2020
|
+
return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
1978
2021
|
}
|
|
1979
2022
|
/**
|
|
1980
2023
|
* Throws if there are error-level validation issues.
|
|
@@ -2631,7 +2674,10 @@ var ProviderHealthCheck = class {
|
|
|
2631
2674
|
const opts = config.options;
|
|
2632
2675
|
const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
|
|
2633
2676
|
try {
|
|
2634
|
-
const
|
|
2677
|
+
const apiKey = opts.apiKey;
|
|
2678
|
+
const response = await fetch(`${baseUrl}/`, {
|
|
2679
|
+
headers: apiKey ? { "api-key": apiKey } : {}
|
|
2680
|
+
});
|
|
2635
2681
|
if (!response.ok) {
|
|
2636
2682
|
throw new Error(`Health check returned ${response.status}`);
|
|
2637
2683
|
}
|
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
QdrantProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CWQQHAF6.mjs";
|
|
4
4
|
import {
|
|
5
5
|
ChromaDBProvider
|
|
6
6
|
} from "./chunk-HUGLYKD6.mjs";
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
createIngestHandler,
|
|
22
22
|
createUploadHandler,
|
|
23
23
|
getRagConfig
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7YQWGERZ.mjs";
|
|
25
25
|
import {
|
|
26
26
|
AnthropicProvider,
|
|
27
27
|
LLMFactory,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -76,6 +76,7 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
76
76
|
} else if (vectorProvider === 'qdrant') {
|
|
77
77
|
vectorDbOptions.baseUrl = readString(env, 'QDRANT_URL') ?? 'http://localhost:6333';
|
|
78
78
|
vectorDbOptions.apiKey = readString(env, 'QDRANT_API_KEY');
|
|
79
|
+
vectorDbOptions.dimensions = embeddingDimensions;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
const llmApiKeyByProvider: Record<string, string | undefined> = {
|
|
@@ -526,10 +526,18 @@ export class ConfigValidator {
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
private static isValidCSSColor(color: string): boolean {
|
|
529
|
-
//
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
529
|
+
// Basic regex for hex, rgb, rgba, hsl, hsla
|
|
530
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
531
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
532
|
+
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
533
|
+
const namedColors = ['red', 'blue', 'green', 'black', 'white', 'transparent']; // basic set
|
|
534
|
+
|
|
535
|
+
return (
|
|
536
|
+
hexRegex.test(color) ||
|
|
537
|
+
rgbRegex.test(color) ||
|
|
538
|
+
hslRegex.test(color) ||
|
|
539
|
+
namedColors.includes(color.toLowerCase())
|
|
540
|
+
);
|
|
533
541
|
}
|
|
534
542
|
|
|
535
543
|
/**
|
|
@@ -235,7 +235,10 @@ export class ProviderHealthCheck {
|
|
|
235
235
|
const baseUrl = (opts.baseUrl as string) || (opts.url as string) || 'http://localhost:6333';
|
|
236
236
|
|
|
237
237
|
try {
|
|
238
|
-
const
|
|
238
|
+
const apiKey = opts.apiKey as string | undefined;
|
|
239
|
+
const response = await fetch(`${baseUrl}/`, {
|
|
240
|
+
headers: apiKey ? { 'api-key': apiKey } : {},
|
|
241
|
+
});
|
|
239
242
|
|
|
240
243
|
if (!response.ok) {
|
|
241
244
|
throw new Error(`Health check returned ${response.status}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import crypto from 'crypto';
|
|
2
3
|
import { VectorDBConfig } from '../../config/RagConfig';
|
|
3
4
|
import { BaseVectorProvider } from './BaseVectorProvider';
|
|
4
5
|
import { VectorMatch, UpsertDocument } from '../../types';
|
|
@@ -26,9 +27,39 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
26
27
|
|
|
27
28
|
async initialize(): Promise<void> {
|
|
28
29
|
await this.ping();
|
|
30
|
+
await this.ensureCollection();
|
|
29
31
|
await this.ensureIndex();
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Ensures the collection exists. Creates it if missing.
|
|
36
|
+
*/
|
|
37
|
+
private async ensureCollection(): Promise<void> {
|
|
38
|
+
try {
|
|
39
|
+
const opts = this.config.options as Record<string, unknown>;
|
|
40
|
+
const dimensions = (opts.dimensions as number) || 1536;
|
|
41
|
+
|
|
42
|
+
await this.http.get(`/collections/${this.indexName}`);
|
|
43
|
+
console.log(`[QdrantProvider] ✅ Collection "${this.indexName}" already exists.`);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
|
46
|
+
const opts = this.config.options as Record<string, unknown>;
|
|
47
|
+
const dimensions = (opts.dimensions as number) || 1536;
|
|
48
|
+
|
|
49
|
+
console.log(`[QdrantProvider] ⏳ Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
|
|
50
|
+
await this.http.put(`/collections/${this.indexName}`, {
|
|
51
|
+
vectors: {
|
|
52
|
+
size: dimensions,
|
|
53
|
+
distance: 'Cosine',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
console.log(`[QdrantProvider] ✅ Created collection "${this.indexName}"`);
|
|
57
|
+
} else {
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
32
63
|
/**
|
|
33
64
|
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
34
65
|
* Qdrant requires this for search filters to work in many configurations.
|
|
@@ -66,7 +97,7 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
66
97
|
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
67
98
|
const payload = {
|
|
68
99
|
points: docs.map(doc => ({
|
|
69
|
-
id: doc.id,
|
|
100
|
+
id: this.normalizeId(doc.id),
|
|
70
101
|
vector: doc.vector,
|
|
71
102
|
payload: {
|
|
72
103
|
content: doc.content,
|
|
@@ -100,7 +131,7 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
100
131
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
101
132
|
async delete(id: string | number, _namespace?: string): Promise<void> {
|
|
102
133
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
103
|
-
points: [id],
|
|
134
|
+
points: [this.normalizeId(id)],
|
|
104
135
|
});
|
|
105
136
|
}
|
|
106
137
|
|
|
@@ -121,5 +152,21 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
121
152
|
}
|
|
122
153
|
}
|
|
123
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
157
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
158
|
+
*/
|
|
159
|
+
private normalizeId(id: string | number): string | number {
|
|
160
|
+
if (typeof id === 'number') return id;
|
|
161
|
+
|
|
162
|
+
// Check if already a valid UUID
|
|
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
|
+
|
|
166
|
+
// Hash string to deterministic UUID
|
|
167
|
+
const hash = crypto.createHash('md5').update(id).digest('hex');
|
|
168
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
124
171
|
async disconnect(): Promise<void> {}
|
|
125
172
|
}
|