@retrivora-ai/rag-engine 0.4.5 → 1.0.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/README.md +32 -57
- package/dist/{ChromaDBProvider-GI7TB7GJ.mjs → ChromaDBProvider-MIDOR4FW.mjs} +2 -2
- package/dist/{DocumentChunker-3yElxTO3.d.mts → DocumentChunker-C-sCZPhi.d.mts} +6 -6
- package/dist/{DocumentChunker-3yElxTO3.d.ts → DocumentChunker-C-sCZPhi.d.ts} +6 -6
- package/dist/{MilvusProvider-WDVTFB7D.mjs → MilvusProvider-U7SKC27V.mjs} +2 -2
- package/dist/{MongoDBProvider-RE3Q5S5B.mjs → MongoDBProvider-Z6ALOVDN.mjs} +2 -2
- package/dist/{PineconeProvider-BE2JWSPD.mjs → PineconeProvider-QZNRKTN2.mjs} +2 -2
- package/dist/{PostgreSQLProvider-5HHTK4SU.mjs → PostgreSQLProvider-BMOETDZA.mjs} +2 -2
- package/dist/{QdrantProvider-XVDVBNIG.mjs → QdrantProvider-YNUNEOZH.mjs} +2 -2
- package/dist/{RagConfig-BgRDL9Vy.d.mts → RagConfig-DRJO4hGU.d.mts} +12 -1
- package/dist/{RagConfig-BgRDL9Vy.d.ts → RagConfig-DRJO4hGU.d.ts} +12 -1
- package/dist/{RedisProvider-EK2R2PQH.mjs → RedisProvider-SR65SCKV.mjs} +2 -2
- package/dist/{SimpleGraphProvider-M6T7SE7D.mjs → SimpleGraphProvider-SLOXO4M7.mjs} +1 -1
- package/dist/{UniversalVectorProvider-YIDRX6VT.mjs → UniversalVectorProvider-IN67OS56.mjs} +3 -3
- package/dist/{WeaviateProvider-4CAPQ7UY.mjs → WeaviateProvider-5FWDFITI.mjs} +2 -2
- package/dist/{chunk-5KNBWQM6.mjs → chunk-3DSHW676.mjs} +5 -1
- package/dist/{chunk-PRC5CZIZ.mjs → chunk-5W2YWFT3.mjs} +1248 -1361
- package/dist/{chunk-H6RKMU7W.mjs → chunk-5YGUXK7Z.mjs} +1 -1
- package/dist/{chunk-PQKTC73Y.mjs → chunk-CD6TSNL4.mjs} +67 -6
- package/dist/{chunk-RK2UDJA2.mjs → chunk-CFVEZTBJ.mjs} +1 -1
- package/dist/{chunk-LJWWPTWE.mjs → chunk-FLOSGE6A.mjs} +76 -14
- package/dist/{chunk-GQT5LF4G.mjs → chunk-LR3VMDVK.mjs} +2 -2
- package/dist/{chunk-KTS3LLHY.mjs → chunk-M6JSPGAR.mjs} +5 -5
- package/dist/{chunk-3QWAK3RZ.mjs → chunk-U55XRW3U.mjs} +6 -2
- package/dist/{chunk-XCNXPECE.mjs → chunk-VUQJVIJT.mjs} +55 -1
- package/dist/chunk-X4TOT24V.mjs +89 -0
- package/dist/{chunk-EDLTMSNY.mjs → chunk-YLTMFW4M.mjs} +1 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1488 -1371
- package/dist/handlers/index.mjs +5 -3
- package/dist/index-B2mutkgp.d.ts +116 -0
- package/dist/index-Bjy0es5a.d.mts +116 -0
- package/dist/index.d.mts +17 -11
- package/dist/index.d.ts +17 -11
- package/dist/index.js +253 -363
- package/dist/index.mjs +243 -353
- package/dist/server.d.mts +108 -158
- package/dist/server.d.ts +108 -158
- package/dist/server.js +1452 -1386
- package/dist/server.mjs +12 -12
- package/package.json +5 -1
- package/src/config/RagConfig.ts +7 -0
- package/src/core/ConfigValidator.ts +66 -492
- package/src/core/LangChainAgent.ts +78 -0
- package/src/core/Pipeline.ts +210 -240
- package/src/core/ProviderHealthCheck.ts +35 -406
- package/src/core/ProviderInterfaces.ts +37 -0
- package/src/core/ProviderRegistry.ts +70 -55
- package/src/core/QueryProcessor.ts +173 -0
- package/src/core/VectorPlugin.ts +7 -0
- package/src/handlers/index.ts +45 -0
- package/src/llm/ILLMProvider.ts +10 -0
- package/src/llm/LLMFactory.ts +33 -13
- package/src/llm/providers/AnthropicProvider.ts +55 -15
- package/src/llm/providers/GeminiProvider.ts +51 -0
- package/src/llm/providers/OllamaProvider.ts +100 -15
- package/src/llm/providers/OpenAIProvider.ts +60 -11
- package/src/providers/vectordb/BaseVectorProvider.ts +11 -0
- package/src/providers/vectordb/MilvusProvider.ts +4 -0
- package/src/providers/vectordb/MongoDBProvider.ts +72 -8
- package/src/providers/vectordb/PineconeProvider.ts +60 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +84 -14
- package/src/providers/vectordb/QdrantProvider.ts +4 -0
- package/src/providers/vectordb/WeaviateProvider.ts +8 -4
- package/src/rag/DocumentChunker.ts +15 -19
- package/src/rag/EntityExtractor.ts +3 -0
- package/src/rag/LlamaIndexIngestor.ts +61 -0
- package/src/rag/Reranker.ts +20 -0
- package/src/server.ts +1 -1
- package/src/types/index.ts +9 -0
- package/dist/chunk-FWCSY2DS.mjs +0 -37
- package/dist/index-7qeLTPBL.d.mts +0 -114
- package/dist/index-DowY4_K0.d.ts +0 -114
package/dist/server.js
CHANGED
|
@@ -9,6 +9,10 @@ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
|
9
9
|
var __getProtoOf = Object.getPrototypeOf;
|
|
10
10
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
11
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
13
|
+
var __typeError = (msg) => {
|
|
14
|
+
throw TypeError(msg);
|
|
15
|
+
};
|
|
12
16
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
17
|
var __spreadValues = (a, b) => {
|
|
14
18
|
for (var prop in b || (b = {}))
|
|
@@ -58,6 +62,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
58
62
|
mod
|
|
59
63
|
));
|
|
60
64
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
65
|
+
var __await = function(promise, isYieldStar) {
|
|
66
|
+
this[0] = promise;
|
|
67
|
+
this[1] = isYieldStar;
|
|
68
|
+
};
|
|
69
|
+
var __asyncGenerator = (__this, __arguments, generator) => {
|
|
70
|
+
var resume = (k, v, yes, no) => {
|
|
71
|
+
try {
|
|
72
|
+
var x = generator[k](v), isAwait = (v = x.value) instanceof __await, done = x.done;
|
|
73
|
+
Promise.resolve(isAwait ? v[0] : v).then((y) => isAwait ? resume(k === "return" ? k : "next", v[1] ? { done: y.done, value: y.value } : y, yes, no) : yes({ value: y, done })).catch((e) => resume("throw", e, yes, no));
|
|
74
|
+
} catch (e) {
|
|
75
|
+
no(e);
|
|
76
|
+
}
|
|
77
|
+
}, method = (k, call, wait, clear) => it[k] = (x) => (call = new Promise((yes, no, run) => (run = () => resume(k, x, yes, no), q ? q.then(run) : run())), clear = () => q === wait && (q = 0), q = wait = call.then(clear, clear), call), q, it = {};
|
|
78
|
+
return generator = generator.apply(__this, __arguments), it[__knownSymbol("asyncIterator")] = () => it, method("next"), method("throw"), method("return"), it;
|
|
79
|
+
};
|
|
80
|
+
var __yieldStar = (value) => {
|
|
81
|
+
var obj = value[__knownSymbol("asyncIterator")], isAwait = false, method, it = {};
|
|
82
|
+
if (obj == null) {
|
|
83
|
+
obj = value[__knownSymbol("iterator")]();
|
|
84
|
+
method = (k) => it[k] = (x) => obj[k](x);
|
|
85
|
+
} else {
|
|
86
|
+
obj = obj.call(value);
|
|
87
|
+
method = (k) => it[k] = (v) => {
|
|
88
|
+
if (isAwait) {
|
|
89
|
+
isAwait = false;
|
|
90
|
+
if (k === "throw") throw v;
|
|
91
|
+
return v;
|
|
92
|
+
}
|
|
93
|
+
isAwait = true;
|
|
94
|
+
return {
|
|
95
|
+
done: false,
|
|
96
|
+
value: new __await(new Promise((resolve) => {
|
|
97
|
+
var x = obj[k](v);
|
|
98
|
+
if (!(x instanceof Object)) __typeError("Object expected");
|
|
99
|
+
resolve(x);
|
|
100
|
+
}), 1)
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return it[__knownSymbol("iterator")] = () => it, method("next"), "throw" in obj ? method("throw") : it.throw = (x) => {
|
|
105
|
+
throw x;
|
|
106
|
+
}, "return" in obj && method("return"), it;
|
|
107
|
+
};
|
|
108
|
+
var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
|
|
61
109
|
|
|
62
110
|
// src/utils/templateUtils.ts
|
|
63
111
|
function isRecord(value) {
|
|
@@ -155,6 +203,60 @@ var init_PineconeProvider = __esm({
|
|
|
155
203
|
if (!opts.apiKey) throw new Error("[PineconeProvider] options.apiKey is required");
|
|
156
204
|
this.apiKey = opts.apiKey;
|
|
157
205
|
}
|
|
206
|
+
static getValidator() {
|
|
207
|
+
return {
|
|
208
|
+
validate(config) {
|
|
209
|
+
const errors = [];
|
|
210
|
+
const opts = config.options || {};
|
|
211
|
+
if (!opts.apiKey) {
|
|
212
|
+
errors.push({
|
|
213
|
+
field: "vectorDb.options.apiKey",
|
|
214
|
+
message: "Pinecone API key is required",
|
|
215
|
+
suggestion: "Set PINECONE_API_KEY environment variable",
|
|
216
|
+
severity: "error"
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return errors;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
static getHealthChecker() {
|
|
224
|
+
return {
|
|
225
|
+
async check(config) {
|
|
226
|
+
var _a, _b;
|
|
227
|
+
const opts = config.options || {};
|
|
228
|
+
const indexName = config.indexName;
|
|
229
|
+
const timestamp = Date.now();
|
|
230
|
+
try {
|
|
231
|
+
const { Pinecone: Pinecone2 } = await import("@pinecone-database/pinecone");
|
|
232
|
+
const client = new Pinecone2({ apiKey: opts.apiKey });
|
|
233
|
+
const indexes = await client.listIndexes();
|
|
234
|
+
const indexNames = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
|
|
235
|
+
if (!indexNames.includes(indexName)) {
|
|
236
|
+
return {
|
|
237
|
+
healthy: false,
|
|
238
|
+
provider: "pinecone",
|
|
239
|
+
error: `Index "${indexName}" not found. Available: ${indexNames.join(", ")}`,
|
|
240
|
+
timestamp
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
healthy: true,
|
|
245
|
+
provider: "pinecone",
|
|
246
|
+
capabilities: { indexes: indexNames.length, targetIndex: indexName },
|
|
247
|
+
timestamp
|
|
248
|
+
};
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return {
|
|
251
|
+
healthy: false,
|
|
252
|
+
provider: "pinecone",
|
|
253
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
254
|
+
timestamp
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
158
260
|
async initialize() {
|
|
159
261
|
var _a, _b;
|
|
160
262
|
this.client = new import_pinecone.Pinecone({ apiKey: this.apiKey });
|
|
@@ -253,6 +355,61 @@ var init_PostgreSQLProvider = __esm({
|
|
|
253
355
|
this.connectionString = opts.connectionString;
|
|
254
356
|
this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
|
|
255
357
|
}
|
|
358
|
+
static getValidator() {
|
|
359
|
+
return {
|
|
360
|
+
validate(config) {
|
|
361
|
+
const errors = [];
|
|
362
|
+
const opts = config.options || {};
|
|
363
|
+
if (!opts.connectionString) {
|
|
364
|
+
errors.push({
|
|
365
|
+
field: "vectorDb.options.connectionString",
|
|
366
|
+
message: "PostgreSQL connection string is required",
|
|
367
|
+
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
368
|
+
severity: "error"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
372
|
+
errors.push({
|
|
373
|
+
field: "vectorDb.options.tables",
|
|
374
|
+
message: "PostgreSQL tables must be a string or a string array",
|
|
375
|
+
severity: "error"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return errors;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
static getHealthChecker() {
|
|
383
|
+
return {
|
|
384
|
+
async check(config) {
|
|
385
|
+
const opts = config.options || {};
|
|
386
|
+
const timestamp = Date.now();
|
|
387
|
+
try {
|
|
388
|
+
const { Client } = await import("pg");
|
|
389
|
+
const client = new Client({ connectionString: opts.connectionString });
|
|
390
|
+
await client.connect();
|
|
391
|
+
const result = await client.query(`
|
|
392
|
+
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
393
|
+
`);
|
|
394
|
+
const hasVector = result.rows[0].exists;
|
|
395
|
+
await client.end();
|
|
396
|
+
return {
|
|
397
|
+
healthy: true,
|
|
398
|
+
provider: "postgresql",
|
|
399
|
+
capabilities: { pgvectorInstalled: hasVector },
|
|
400
|
+
timestamp
|
|
401
|
+
};
|
|
402
|
+
} catch (error) {
|
|
403
|
+
return {
|
|
404
|
+
healthy: false,
|
|
405
|
+
provider: "postgresql",
|
|
406
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
407
|
+
timestamp
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
256
413
|
async initialize() {
|
|
257
414
|
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
258
415
|
const client = await this.pool.connect();
|
|
@@ -338,20 +495,27 @@ var init_PostgreSQLProvider = __esm({
|
|
|
338
495
|
}).join(" AND ");
|
|
339
496
|
whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
|
|
340
497
|
}
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
498
|
+
const client = await this.pool.connect();
|
|
499
|
+
try {
|
|
500
|
+
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
501
|
+
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
502
|
+
const result = await client.query(
|
|
503
|
+
`SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
|
|
504
|
+
FROM ${this.tableName}
|
|
505
|
+
${whereClause}
|
|
506
|
+
ORDER BY embedding <=> $1::vector
|
|
507
|
+
LIMIT $2`,
|
|
508
|
+
params
|
|
509
|
+
);
|
|
510
|
+
return result.rows.map((row) => ({
|
|
511
|
+
id: String(row["id"]),
|
|
512
|
+
score: parseFloat(String(row["score"])),
|
|
513
|
+
content: String(row["content"]),
|
|
514
|
+
metadata: row["metadata"]
|
|
515
|
+
}));
|
|
516
|
+
} finally {
|
|
517
|
+
client.release();
|
|
518
|
+
}
|
|
355
519
|
}
|
|
356
520
|
async delete(id, namespace) {
|
|
357
521
|
const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
|
|
@@ -401,6 +565,71 @@ var init_MongoDBProvider = __esm({
|
|
|
401
565
|
this.contentKey = opts.contentKey || "content";
|
|
402
566
|
this.metadataKey = opts.metadataKey || "metadata";
|
|
403
567
|
}
|
|
568
|
+
static getValidator() {
|
|
569
|
+
return {
|
|
570
|
+
validate(config) {
|
|
571
|
+
const errors = [];
|
|
572
|
+
const opts = config.options || {};
|
|
573
|
+
if (!opts.uri) {
|
|
574
|
+
errors.push({
|
|
575
|
+
field: "vectorDb.options.uri",
|
|
576
|
+
message: "MongoDB connection URI is required",
|
|
577
|
+
suggestion: "Set MONGODB_URI environment variable",
|
|
578
|
+
severity: "error"
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (!opts.database) {
|
|
582
|
+
errors.push({
|
|
583
|
+
field: "vectorDb.options.database",
|
|
584
|
+
message: "MongoDB database name is required",
|
|
585
|
+
severity: "error"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (!opts.collection) {
|
|
589
|
+
errors.push({
|
|
590
|
+
field: "vectorDb.options.collection",
|
|
591
|
+
message: "MongoDB collection name is required",
|
|
592
|
+
severity: "error"
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return errors;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
static getHealthChecker() {
|
|
600
|
+
return {
|
|
601
|
+
async check(config) {
|
|
602
|
+
const opts = config.options || {};
|
|
603
|
+
const timestamp = Date.now();
|
|
604
|
+
try {
|
|
605
|
+
const { MongoClient: MongoClient2 } = await import("mongodb");
|
|
606
|
+
const client = new MongoClient2(opts.uri);
|
|
607
|
+
await client.connect();
|
|
608
|
+
const db = client.db(opts.database);
|
|
609
|
+
await db.command({ ping: 1 });
|
|
610
|
+
const collections = await db.listCollections({ name: opts.collection }).toArray();
|
|
611
|
+
await client.close();
|
|
612
|
+
return {
|
|
613
|
+
healthy: true,
|
|
614
|
+
provider: "mongodb",
|
|
615
|
+
capabilities: {
|
|
616
|
+
database: opts.database,
|
|
617
|
+
collection: opts.collection,
|
|
618
|
+
exists: collections.length > 0
|
|
619
|
+
},
|
|
620
|
+
timestamp
|
|
621
|
+
};
|
|
622
|
+
} catch (error) {
|
|
623
|
+
return {
|
|
624
|
+
healthy: false,
|
|
625
|
+
provider: "mongodb",
|
|
626
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
627
|
+
timestamp
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
404
633
|
async initialize() {
|
|
405
634
|
await this.client.connect();
|
|
406
635
|
this.db = this.client.db(this.dbName);
|
|
@@ -439,7 +668,7 @@ var init_MongoDBProvider = __esm({
|
|
|
439
668
|
index: this.config.indexName || "vector_index",
|
|
440
669
|
path: this.embeddingKey,
|
|
441
670
|
queryVector: vector,
|
|
442
|
-
numCandidates: Math.max(topK *
|
|
671
|
+
numCandidates: this.config.options.numCandidates || Math.max(topK * 20, 200),
|
|
443
672
|
limit: topK
|
|
444
673
|
}, Object.keys(publicFilter).length > 0 || namespace ? { filter: __spreadValues(__spreadValues({}, publicFilter), namespace ? { namespace } : {}) } : {})
|
|
445
674
|
},
|
|
@@ -467,10 +696,6 @@ var init_MongoDBProvider = __esm({
|
|
|
467
696
|
async deleteNamespace(namespace) {
|
|
468
697
|
await this.collection.deleteMany({ namespace });
|
|
469
698
|
}
|
|
470
|
-
/**
|
|
471
|
-
* Sanitise and flatten filter for MongoDB.
|
|
472
|
-
* Strips internal engine fields and keywords.
|
|
473
|
-
*/
|
|
474
699
|
sanitizeFilter(filter) {
|
|
475
700
|
const sanitized = super.sanitizeFilter(filter);
|
|
476
701
|
const mongoFilter = {};
|
|
@@ -555,7 +780,11 @@ var init_MilvusProvider = __esm({
|
|
|
555
780
|
vector,
|
|
556
781
|
limit: topK,
|
|
557
782
|
filter: namespace ? `namespace == "${namespace}"` : void 0,
|
|
558
|
-
outputFields: ["content", "metadata"]
|
|
783
|
+
outputFields: ["content", "metadata"],
|
|
784
|
+
searchParams: {
|
|
785
|
+
nprobe: this.config.options.nprobe || 16,
|
|
786
|
+
ef: this.config.options.efSearch || Math.max(topK * 10, 64)
|
|
787
|
+
}
|
|
559
788
|
};
|
|
560
789
|
const { data } = await this.http.post("/v1/vector/search", payload);
|
|
561
790
|
return (data.data || []).map((res) => ({
|
|
@@ -695,6 +924,10 @@ var init_QdrantProvider = __esm({
|
|
|
695
924
|
vector,
|
|
696
925
|
limit: topK,
|
|
697
926
|
with_payload: true,
|
|
927
|
+
params: {
|
|
928
|
+
hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
|
|
929
|
+
exact: false
|
|
930
|
+
},
|
|
698
931
|
filter: namespace ? {
|
|
699
932
|
must: [{ key: "namespace", match: { value: namespace } }]
|
|
700
933
|
} : void 0
|
|
@@ -990,17 +1223,17 @@ var init_WeaviateProvider = __esm({
|
|
|
990
1223
|
};
|
|
991
1224
|
await this.http.post("/v1/batch/objects", payload);
|
|
992
1225
|
}
|
|
993
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
994
1226
|
async query(vector, topK, namespace, _filter) {
|
|
995
1227
|
var _a, _b;
|
|
1228
|
+
const sanitizedFilter = this.sanitizeFilter(_filter);
|
|
1229
|
+
const queryText = sanitizedFilter.queryText;
|
|
1230
|
+
const searchParams = queryText ? `hybrid: { query: ${JSON.stringify(queryText)}, alpha: 0.5 }` : `nearVector: { vector: ${JSON.stringify(vector)} }`;
|
|
996
1231
|
const graphqlQuery = {
|
|
997
1232
|
query: `
|
|
998
1233
|
{
|
|
999
1234
|
Get {
|
|
1000
1235
|
${this.indexName}(
|
|
1001
|
-
|
|
1002
|
-
vector: ${JSON.stringify(vector)}
|
|
1003
|
-
}
|
|
1236
|
+
${searchParams}
|
|
1004
1237
|
limit: ${topK}
|
|
1005
1238
|
${namespace ? `where: { path: ["namespace"], operator: Equal, valueString: "${namespace}" }` : ""}
|
|
1006
1239
|
) {
|
|
@@ -1365,7 +1598,6 @@ var PROVIDERS_WITH_EMBEDDINGS = [
|
|
|
1365
1598
|
"rest",
|
|
1366
1599
|
"universal_rest"
|
|
1367
1600
|
];
|
|
1368
|
-
var UI_VISUAL_STYLES = ["glass", "solid"];
|
|
1369
1601
|
var UI_BORDER_RADIUS_OPTIONS = ["none", "sm", "md", "lg", "xl", "full"];
|
|
1370
1602
|
|
|
1371
1603
|
// src/config/serverConfig.ts
|
|
@@ -1543,657 +1775,68 @@ var ConfigResolver = class {
|
|
|
1543
1775
|
}
|
|
1544
1776
|
};
|
|
1545
1777
|
|
|
1546
|
-
// src/
|
|
1547
|
-
var
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
if (!config.projectId) {
|
|
1555
|
-
errors.push({
|
|
1556
|
-
field: "projectId",
|
|
1557
|
-
message: "projectId is required",
|
|
1558
|
-
severity: "error"
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
errors.push(...this.validateVectorDbConfig(config.vectorDb));
|
|
1562
|
-
errors.push(...this.validateLLMConfig(config.llm));
|
|
1563
|
-
if (config.embedding) {
|
|
1564
|
-
errors.push(...this.validateEmbeddingConfig(config.embedding));
|
|
1565
|
-
} else if (config.llm.provider === "anthropic") {
|
|
1566
|
-
errors.push({
|
|
1567
|
-
field: "embedding",
|
|
1568
|
-
message: "Embedding config is required when using Anthropic LLM",
|
|
1569
|
-
suggestion: 'Provide embedding config with provider (e.g., "openai" or "ollama")',
|
|
1570
|
-
severity: "error"
|
|
1571
|
-
});
|
|
1572
|
-
}
|
|
1573
|
-
if (config.ui) {
|
|
1574
|
-
errors.push(...this.validateUIConfig(config.ui));
|
|
1575
|
-
}
|
|
1576
|
-
if (config.rag) {
|
|
1577
|
-
errors.push(...this.validateRAGConfig(config.rag));
|
|
1578
|
-
}
|
|
1579
|
-
return errors;
|
|
1580
|
-
}
|
|
1581
|
-
static validateVectorDbConfig(config) {
|
|
1582
|
-
const errors = [];
|
|
1583
|
-
if (!config.provider) {
|
|
1584
|
-
errors.push({
|
|
1585
|
-
field: "vectorDb.provider",
|
|
1586
|
-
message: "Vector database provider is required",
|
|
1587
|
-
severity: "error"
|
|
1588
|
-
});
|
|
1589
|
-
return errors;
|
|
1590
|
-
}
|
|
1591
|
-
if (!config.indexName) {
|
|
1592
|
-
errors.push({
|
|
1593
|
-
field: "vectorDb.indexName",
|
|
1594
|
-
message: "Vector database index name is required",
|
|
1595
|
-
severity: "error"
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
switch (config.provider) {
|
|
1599
|
-
case "pinecone":
|
|
1600
|
-
errors.push(...this.validatePineconeConfig(config));
|
|
1601
|
-
break;
|
|
1602
|
-
case "pgvector":
|
|
1603
|
-
case "postgresql":
|
|
1604
|
-
errors.push(...this.validatePostgresConfig(config));
|
|
1605
|
-
break;
|
|
1606
|
-
case "mongodb":
|
|
1607
|
-
errors.push(...this.validateMongoDBConfig(config));
|
|
1608
|
-
break;
|
|
1609
|
-
case "milvus":
|
|
1610
|
-
errors.push(...this.validateMilvusConfig(config));
|
|
1611
|
-
break;
|
|
1612
|
-
case "qdrant":
|
|
1613
|
-
errors.push(...this.validateQdrantConfig(config));
|
|
1614
|
-
break;
|
|
1615
|
-
case "chromadb":
|
|
1616
|
-
errors.push(...this.validateChromaDBConfig(config));
|
|
1617
|
-
break;
|
|
1618
|
-
case "redis":
|
|
1619
|
-
errors.push(...this.validateRedisConfig(config));
|
|
1620
|
-
break;
|
|
1621
|
-
case "weaviate":
|
|
1622
|
-
errors.push(...this.validateWeaviateConfig(config));
|
|
1623
|
-
break;
|
|
1624
|
-
case "universal_rest":
|
|
1625
|
-
case "rest":
|
|
1626
|
-
errors.push(...this.validateRestConfig(config));
|
|
1627
|
-
break;
|
|
1628
|
-
}
|
|
1629
|
-
return errors;
|
|
1630
|
-
}
|
|
1631
|
-
/**
|
|
1632
|
-
* Pinecone — only needs apiKey (environment was removed in SDK v7+).
|
|
1633
|
-
* Options key: { apiKey: string }
|
|
1634
|
-
*/
|
|
1635
|
-
static validatePineconeConfig(config) {
|
|
1636
|
-
const errors = [];
|
|
1637
|
-
const opts = config.options;
|
|
1638
|
-
if (!opts.apiKey) {
|
|
1639
|
-
errors.push({
|
|
1640
|
-
field: "vectorDb.options.apiKey",
|
|
1641
|
-
message: "Pinecone API key is required",
|
|
1642
|
-
suggestion: "Set PINECONE_API_KEY environment variable",
|
|
1643
|
-
severity: "error"
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
return errors;
|
|
1647
|
-
}
|
|
1648
|
-
/**
|
|
1649
|
-
* PostgreSQL / pgvector — needs a connection string.
|
|
1650
|
-
* Options key: { connectionString: string }
|
|
1651
|
-
*/
|
|
1652
|
-
static validatePostgresConfig(config) {
|
|
1653
|
-
const errors = [];
|
|
1654
|
-
const opts = config.options;
|
|
1655
|
-
if (!opts.connectionString) {
|
|
1656
|
-
errors.push({
|
|
1657
|
-
field: "vectorDb.options.connectionString",
|
|
1658
|
-
message: "PostgreSQL connection string is required",
|
|
1659
|
-
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
1660
|
-
severity: "error"
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
1664
|
-
errors.push({
|
|
1665
|
-
field: "vectorDb.options.tables",
|
|
1666
|
-
message: "PostgreSQL tables must be a string or a string array",
|
|
1667
|
-
severity: "error"
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
if (opts.searchFields && typeof opts.searchFields !== "string" && !Array.isArray(opts.searchFields)) {
|
|
1671
|
-
errors.push({
|
|
1672
|
-
field: "vectorDb.options.searchFields",
|
|
1673
|
-
message: "PostgreSQL searchFields must be a string or a string array",
|
|
1674
|
-
severity: "error"
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
return errors;
|
|
1678
|
-
}
|
|
1679
|
-
/**
|
|
1680
|
-
* MongoDB — needs uri, database, and collection.
|
|
1681
|
-
* Options keys: { uri: string, database: string, collection: string }
|
|
1682
|
-
*/
|
|
1683
|
-
static validateMongoDBConfig(config) {
|
|
1684
|
-
const errors = [];
|
|
1685
|
-
const opts = config.options;
|
|
1686
|
-
if (!opts.uri) {
|
|
1687
|
-
errors.push({
|
|
1688
|
-
field: "vectorDb.options.uri",
|
|
1689
|
-
message: "MongoDB connection URI is required",
|
|
1690
|
-
suggestion: "Set MONGODB_URI environment variable",
|
|
1691
|
-
severity: "error"
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
if (!opts.database) {
|
|
1695
|
-
errors.push({
|
|
1696
|
-
field: "vectorDb.options.database",
|
|
1697
|
-
message: "MongoDB database name is required",
|
|
1698
|
-
suggestion: "Set MONGODB_DB environment variable",
|
|
1699
|
-
severity: "error"
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
if (!opts.collection) {
|
|
1703
|
-
errors.push({
|
|
1704
|
-
field: "vectorDb.options.collection",
|
|
1705
|
-
message: "MongoDB collection name is required",
|
|
1706
|
-
suggestion: "Set MONGODB_COLLECTION environment variable",
|
|
1707
|
-
severity: "error"
|
|
1708
|
-
});
|
|
1709
|
-
}
|
|
1710
|
-
return errors;
|
|
1711
|
-
}
|
|
1712
|
-
/**
|
|
1713
|
-
* Milvus — accepts baseUrl OR uri (preferred) OR host+port.
|
|
1714
|
-
* MilvusProvider reads opts.baseUrl ?? opts.uri.
|
|
1715
|
-
* Options keys: { baseUrl?: string, uri?: string, host?: string, port?: number }
|
|
1716
|
-
*/
|
|
1717
|
-
static validateMilvusConfig(config) {
|
|
1718
|
-
const errors = [];
|
|
1719
|
-
const opts = config.options;
|
|
1720
|
-
const hasBaseUrl = Boolean(opts.baseUrl || opts.uri);
|
|
1721
|
-
const hasHostPort = Boolean(opts.host && opts.port);
|
|
1722
|
-
if (!hasBaseUrl && !hasHostPort) {
|
|
1723
|
-
errors.push({
|
|
1724
|
-
field: "vectorDb.options.baseUrl",
|
|
1725
|
-
message: 'Milvus connection is required: provide baseUrl (e.g., "http://localhost:19530") or host + port',
|
|
1726
|
-
suggestion: "Set MILVUS_URL environment variable",
|
|
1727
|
-
severity: "error"
|
|
1728
|
-
});
|
|
1729
|
-
}
|
|
1730
|
-
return errors;
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Qdrant — needs baseUrl (or url alias).
|
|
1734
|
-
* Options keys: { baseUrl: string, apiKey?: string }
|
|
1735
|
-
*/
|
|
1736
|
-
static validateQdrantConfig(config) {
|
|
1737
|
-
const errors = [];
|
|
1738
|
-
const opts = config.options;
|
|
1739
|
-
if (!opts.baseUrl && !opts.url) {
|
|
1740
|
-
errors.push({
|
|
1741
|
-
field: "vectorDb.options.baseUrl",
|
|
1742
|
-
message: "Qdrant base URL is required",
|
|
1743
|
-
suggestion: 'Set QDRANT_URL environment variable (e.g., "http://localhost:6333")',
|
|
1744
|
-
severity: "error"
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
return errors;
|
|
1748
|
-
}
|
|
1749
|
-
/**
|
|
1750
|
-
* ChromaDB — accepts baseUrl OR host.
|
|
1751
|
-
* ChromaDBProvider reads opts.baseUrl.
|
|
1752
|
-
* Options keys: { baseUrl?: string, host?: string, port?: number }
|
|
1753
|
-
*/
|
|
1754
|
-
static validateChromaDBConfig(config) {
|
|
1755
|
-
const errors = [];
|
|
1756
|
-
const opts = config.options;
|
|
1757
|
-
if (!opts.baseUrl && !opts.host) {
|
|
1758
|
-
errors.push({
|
|
1759
|
-
field: "vectorDb.options.baseUrl",
|
|
1760
|
-
message: 'ChromaDB connection is required: provide baseUrl (e.g., "http://localhost:8000") or host',
|
|
1761
|
-
suggestion: "Set CHROMADB_URL environment variable",
|
|
1762
|
-
severity: "error"
|
|
1763
|
-
});
|
|
1764
|
-
}
|
|
1765
|
-
return errors;
|
|
1766
|
-
}
|
|
1767
|
-
/**
|
|
1768
|
-
* Redis — accepts baseUrl OR url OR host+port.
|
|
1769
|
-
* RedisProvider reads opts.baseUrl.
|
|
1770
|
-
* Options keys: { baseUrl?: string, url?: string, host?: string, port?: number, apiKey?: string }
|
|
1771
|
-
*/
|
|
1772
|
-
static validateRedisConfig(config) {
|
|
1773
|
-
const errors = [];
|
|
1774
|
-
const opts = config.options;
|
|
1775
|
-
const hasUrl = Boolean(opts.baseUrl || opts.url);
|
|
1776
|
-
const hasHostPort = Boolean(opts.host && opts.port);
|
|
1777
|
-
if (!hasUrl && !hasHostPort) {
|
|
1778
|
-
errors.push({
|
|
1779
|
-
field: "vectorDb.options.baseUrl",
|
|
1780
|
-
message: "Redis connection is required: provide baseUrl/url or host + port",
|
|
1781
|
-
suggestion: "Set REDIS_URL environment variable",
|
|
1782
|
-
severity: "error"
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
return errors;
|
|
1786
|
-
}
|
|
1787
|
-
/**
|
|
1788
|
-
* Weaviate — accepts baseUrl OR url.
|
|
1789
|
-
* WeaviateProvider reads opts.baseUrl.
|
|
1790
|
-
* Options keys: { baseUrl?: string, url?: string, apiKey?: string }
|
|
1791
|
-
*/
|
|
1792
|
-
static validateWeaviateConfig(config) {
|
|
1793
|
-
const errors = [];
|
|
1794
|
-
const opts = config.options;
|
|
1795
|
-
if (!opts.baseUrl && !opts.url) {
|
|
1796
|
-
errors.push({
|
|
1797
|
-
field: "vectorDb.options.baseUrl",
|
|
1798
|
-
message: "Weaviate instance URL is required",
|
|
1799
|
-
suggestion: "Set WEAVIATE_URL environment variable",
|
|
1800
|
-
severity: "error"
|
|
1801
|
-
});
|
|
1802
|
-
}
|
|
1803
|
-
return errors;
|
|
1804
|
-
}
|
|
1805
|
-
/**
|
|
1806
|
-
* Universal REST / custom REST adapter.
|
|
1807
|
-
* Options key: { baseUrl: string }
|
|
1808
|
-
*/
|
|
1809
|
-
static validateRestConfig(config) {
|
|
1810
|
-
const errors = [];
|
|
1811
|
-
const opts = config.options;
|
|
1812
|
-
if (!opts.baseUrl) {
|
|
1813
|
-
errors.push({
|
|
1814
|
-
field: "vectorDb.options.baseUrl",
|
|
1815
|
-
message: "REST API base URL is required",
|
|
1816
|
-
suggestion: "Set VECTOR_BASE_URL environment variable",
|
|
1817
|
-
severity: "error"
|
|
1818
|
-
});
|
|
1819
|
-
}
|
|
1820
|
-
return errors;
|
|
1778
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
1779
|
+
var import_openai = __toESM(require("openai"));
|
|
1780
|
+
var OpenAIProvider = class {
|
|
1781
|
+
constructor(llmConfig, embeddingConfig) {
|
|
1782
|
+
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
1783
|
+
this.client = new import_openai.default({ apiKey: llmConfig.apiKey });
|
|
1784
|
+
this.llmConfig = llmConfig;
|
|
1785
|
+
this.embeddingConfig = embeddingConfig;
|
|
1821
1786
|
}
|
|
1822
|
-
static
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
message: "LLM provider is required",
|
|
1829
|
-
severity: "error"
|
|
1830
|
-
});
|
|
1831
|
-
return errors;
|
|
1832
|
-
}
|
|
1833
|
-
if (!config.model) {
|
|
1834
|
-
errors.push({
|
|
1835
|
-
field: "llm.model",
|
|
1836
|
-
message: "LLM model name is required",
|
|
1837
|
-
suggestion: `e.g., "gpt-4o" for OpenAI, "claude-3-opus" for Anthropic`,
|
|
1838
|
-
severity: "error"
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
switch (config.provider) {
|
|
1842
|
-
case "openai":
|
|
1787
|
+
static getValidator() {
|
|
1788
|
+
return {
|
|
1789
|
+
validate(config) {
|
|
1790
|
+
const errors = [];
|
|
1791
|
+
const isEmbedding = config.provider === "openai" && "dimensions" in config;
|
|
1792
|
+
const prefix = isEmbedding ? "embedding" : "llm";
|
|
1843
1793
|
if (!config.apiKey) {
|
|
1844
1794
|
errors.push({
|
|
1845
|
-
field:
|
|
1795
|
+
field: `${prefix}.apiKey`,
|
|
1846
1796
|
message: "OpenAI API key is required",
|
|
1847
1797
|
suggestion: "Set OPENAI_API_KEY environment variable",
|
|
1848
1798
|
severity: "error"
|
|
1849
1799
|
});
|
|
1850
1800
|
}
|
|
1851
|
-
|
|
1852
|
-
case "anthropic":
|
|
1853
|
-
if (!config.apiKey) {
|
|
1801
|
+
if (!config.model) {
|
|
1854
1802
|
errors.push({
|
|
1855
|
-
field:
|
|
1856
|
-
message: "
|
|
1857
|
-
suggestion: "
|
|
1803
|
+
field: `${prefix}.model`,
|
|
1804
|
+
message: "OpenAI model name is required",
|
|
1805
|
+
suggestion: isEmbedding ? 'e.g., "text-embedding-3-small"' : 'e.g., "gpt-4o"',
|
|
1858
1806
|
severity: "error"
|
|
1859
1807
|
});
|
|
1860
1808
|
}
|
|
1861
|
-
|
|
1862
|
-
case "gemini":
|
|
1863
|
-
if (!config.apiKey) {
|
|
1864
|
-
errors.push({
|
|
1865
|
-
field: "llm.apiKey",
|
|
1866
|
-
message: "Gemini API key is required",
|
|
1867
|
-
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1868
|
-
severity: "error"
|
|
1869
|
-
});
|
|
1870
|
-
}
|
|
1871
|
-
break;
|
|
1872
|
-
case "ollama":
|
|
1873
|
-
if (!config.baseUrl) {
|
|
1874
|
-
errors.push({
|
|
1875
|
-
field: "llm.baseUrl",
|
|
1876
|
-
message: "Ollama base URL is required",
|
|
1877
|
-
suggestion: 'Set LLM_BASE_URL environment variable (e.g., "http://localhost:11434")',
|
|
1878
|
-
severity: "error"
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
1881
|
-
break;
|
|
1882
|
-
case "rest":
|
|
1883
|
-
case "universal_rest":
|
|
1884
|
-
if (!config.baseUrl && !((_a = config.options) == null ? void 0 : _a.baseUrl)) {
|
|
1885
|
-
errors.push({
|
|
1886
|
-
field: "llm.baseUrl",
|
|
1887
|
-
message: "REST API base URL is required",
|
|
1888
|
-
suggestion: "Set LLM_BASE_URL environment variable",
|
|
1889
|
-
severity: "error"
|
|
1890
|
-
});
|
|
1891
|
-
}
|
|
1892
|
-
break;
|
|
1893
|
-
}
|
|
1894
|
-
if (config.temperature !== void 0) {
|
|
1895
|
-
if (config.temperature < 0 || config.temperature > 2) {
|
|
1896
|
-
errors.push({
|
|
1897
|
-
field: "llm.temperature",
|
|
1898
|
-
message: "Temperature must be between 0 and 2",
|
|
1899
|
-
severity: "error"
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
if (config.maxTokens !== void 0 && config.maxTokens <= 0) {
|
|
1904
|
-
errors.push({
|
|
1905
|
-
field: "llm.maxTokens",
|
|
1906
|
-
message: "maxTokens must be greater than 0",
|
|
1907
|
-
severity: "error"
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
return errors;
|
|
1911
|
-
}
|
|
1912
|
-
static validateEmbeddingConfig(config) {
|
|
1913
|
-
const errors = [];
|
|
1914
|
-
if (!config.provider) {
|
|
1915
|
-
errors.push({
|
|
1916
|
-
field: "embedding.provider",
|
|
1917
|
-
message: "Embedding provider is required",
|
|
1918
|
-
severity: "error"
|
|
1919
|
-
});
|
|
1920
|
-
return errors;
|
|
1921
|
-
}
|
|
1922
|
-
if (!config.model) {
|
|
1923
|
-
errors.push({
|
|
1924
|
-
field: "embedding.model",
|
|
1925
|
-
message: "Embedding model name is required",
|
|
1926
|
-
suggestion: 'e.g., "text-embedding-3-small" for OpenAI, "nomic-embed-text" for Ollama',
|
|
1927
|
-
severity: "error"
|
|
1928
|
-
});
|
|
1929
|
-
}
|
|
1930
|
-
if (config.provider === "openai" && !config.apiKey) {
|
|
1931
|
-
errors.push({
|
|
1932
|
-
field: "embedding.apiKey",
|
|
1933
|
-
message: "OpenAI API key is required for embedding",
|
|
1934
|
-
suggestion: "Set OPENAI_API_KEY environment variable",
|
|
1935
|
-
severity: "error"
|
|
1936
|
-
});
|
|
1937
|
-
}
|
|
1938
|
-
if (config.provider === "gemini" && !config.apiKey) {
|
|
1939
|
-
errors.push({
|
|
1940
|
-
field: "embedding.apiKey",
|
|
1941
|
-
message: "Gemini API key is required for embedding",
|
|
1942
|
-
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1943
|
-
severity: "error"
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
if (config.provider === "ollama" && !config.baseUrl) {
|
|
1947
|
-
errors.push({
|
|
1948
|
-
field: "embedding.baseUrl",
|
|
1949
|
-
message: "Ollama base URL is required for embedding",
|
|
1950
|
-
suggestion: "Set EMBEDDING_BASE_URL environment variable",
|
|
1951
|
-
severity: "error"
|
|
1952
|
-
});
|
|
1953
|
-
}
|
|
1954
|
-
if (config.dimensions !== void 0 && config.dimensions <= 0) {
|
|
1955
|
-
errors.push({
|
|
1956
|
-
field: "embedding.dimensions",
|
|
1957
|
-
message: "Embedding dimensions must be greater than 0",
|
|
1958
|
-
severity: "error"
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
return errors;
|
|
1962
|
-
}
|
|
1963
|
-
static validateUIConfig(config) {
|
|
1964
|
-
const errors = [];
|
|
1965
|
-
if (config.primaryColor) {
|
|
1966
|
-
if (!this.isValidCSSColor(config.primaryColor)) {
|
|
1967
|
-
errors.push({
|
|
1968
|
-
field: "ui.primaryColor",
|
|
1969
|
-
message: "Invalid CSS color format",
|
|
1970
|
-
suggestion: "Use valid CSS colors: hex (#FF0000), rgb, hsl, or named colors",
|
|
1971
|
-
severity: "warning"
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
if (config.borderRadius) {
|
|
1976
|
-
if (!UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius)) {
|
|
1977
|
-
errors.push({
|
|
1978
|
-
field: "ui.borderRadius",
|
|
1979
|
-
message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(", ")}`,
|
|
1980
|
-
severity: "warning"
|
|
1981
|
-
});
|
|
1809
|
+
return errors;
|
|
1982
1810
|
}
|
|
1983
|
-
}
|
|
1984
|
-
if (config.visualStyle) {
|
|
1985
|
-
if (!UI_VISUAL_STYLES.includes(config.visualStyle)) {
|
|
1986
|
-
errors.push({
|
|
1987
|
-
field: "ui.visualStyle",
|
|
1988
|
-
message: `visualStyle must be one of: ${UI_VISUAL_STYLES.join(", ")}`,
|
|
1989
|
-
severity: "warning"
|
|
1990
|
-
});
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
return errors;
|
|
1994
|
-
}
|
|
1995
|
-
static validateRAGConfig(config) {
|
|
1996
|
-
const errors = [];
|
|
1997
|
-
if (config.topK !== void 0) {
|
|
1998
|
-
if (typeof config.topK !== "number" || config.topK <= 0) {
|
|
1999
|
-
errors.push({
|
|
2000
|
-
field: "rag.topK",
|
|
2001
|
-
message: "topK must be a positive integer",
|
|
2002
|
-
severity: "error"
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
if (config.scoreThreshold !== void 0) {
|
|
2007
|
-
if (typeof config.scoreThreshold !== "number" || config.scoreThreshold < 0 || config.scoreThreshold > 1) {
|
|
2008
|
-
errors.push({
|
|
2009
|
-
field: "rag.scoreThreshold",
|
|
2010
|
-
message: "scoreThreshold must be between 0 and 1",
|
|
2011
|
-
severity: "error"
|
|
2012
|
-
});
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
if (config.chunkSize !== void 0) {
|
|
2016
|
-
if (typeof config.chunkSize !== "number" || config.chunkSize <= 0) {
|
|
2017
|
-
errors.push({
|
|
2018
|
-
field: "rag.chunkSize",
|
|
2019
|
-
message: "chunkSize must be a positive integer",
|
|
2020
|
-
severity: "error"
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
if (config.chunkOverlap !== void 0) {
|
|
2025
|
-
if (typeof config.chunkOverlap !== "number" || config.chunkOverlap < 0) {
|
|
2026
|
-
errors.push({
|
|
2027
|
-
field: "rag.chunkOverlap",
|
|
2028
|
-
message: "chunkOverlap must be a non-negative integer",
|
|
2029
|
-
severity: "error"
|
|
2030
|
-
});
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
return errors;
|
|
2034
|
-
}
|
|
2035
|
-
static isValidCSSColor(color) {
|
|
2036
|
-
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
2037
|
-
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2038
|
-
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2039
|
-
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
2040
|
-
return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Throws if there are error-level validation issues.
|
|
2044
|
-
* Logs warnings to console.
|
|
2045
|
-
*/
|
|
2046
|
-
static validateAndThrow(config) {
|
|
2047
|
-
const errors = this.validate(config);
|
|
2048
|
-
const errorItems = errors.filter((e) => e.severity === "error");
|
|
2049
|
-
const warnings = errors.filter((e) => e.severity === "warning");
|
|
2050
|
-
if (warnings.length > 0) {
|
|
2051
|
-
console.warn("[ConfigValidator] Configuration warnings:");
|
|
2052
|
-
warnings.forEach((w) => {
|
|
2053
|
-
console.warn(` ${w.field}: ${w.message}`);
|
|
2054
|
-
if (w.suggestion) console.warn(` Suggestion: ${w.suggestion}`);
|
|
2055
|
-
});
|
|
2056
|
-
}
|
|
2057
|
-
if (errorItems.length > 0) {
|
|
2058
|
-
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
2059
|
-
throw new Error(`[ConfigValidator] Configuration validation failed:
|
|
2060
|
-
${message}`);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
};
|
|
2064
|
-
|
|
2065
|
-
// src/rag/DocumentChunker.ts
|
|
2066
|
-
var DocumentChunker = class {
|
|
2067
|
-
constructor(chunkSize = 1e3, chunkOverlap = 200, separators = ["\n\n", "\n", " ", ""]) {
|
|
2068
|
-
this.chunkSize = chunkSize;
|
|
2069
|
-
this.chunkOverlap = chunkOverlap;
|
|
2070
|
-
this.separators = separators;
|
|
1811
|
+
};
|
|
2071
1812
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
let overlapLen = 0;
|
|
2097
|
-
for (let i = currentChunk.length - 1; i >= 0; i--) {
|
|
2098
|
-
if (overlapLen + currentChunk[i].length <= chunkOverlap) {
|
|
2099
|
-
overlapItems.unshift(currentChunk[i]);
|
|
2100
|
-
overlapLen += currentChunk[i].length;
|
|
2101
|
-
} else {
|
|
2102
|
-
break;
|
|
2103
|
-
}
|
|
1813
|
+
static getHealthChecker() {
|
|
1814
|
+
return {
|
|
1815
|
+
async check(config) {
|
|
1816
|
+
const timestamp = Date.now();
|
|
1817
|
+
const apiKey = config.apiKey;
|
|
1818
|
+
const modelName = config.model;
|
|
1819
|
+
try {
|
|
1820
|
+
const OpenAI2 = await import("openai");
|
|
1821
|
+
const client = new OpenAI2.default({ apiKey });
|
|
1822
|
+
const models = await client.models.list();
|
|
1823
|
+
const hasModel = models.data.some((m) => m.id === modelName);
|
|
1824
|
+
return {
|
|
1825
|
+
healthy: true,
|
|
1826
|
+
provider: "openai",
|
|
1827
|
+
capabilities: { model: modelName, available: hasModel, totalModels: models.data.length },
|
|
1828
|
+
timestamp
|
|
1829
|
+
};
|
|
1830
|
+
} catch (error) {
|
|
1831
|
+
return {
|
|
1832
|
+
healthy: false,
|
|
1833
|
+
provider: "openai",
|
|
1834
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1835
|
+
timestamp
|
|
1836
|
+
};
|
|
2104
1837
|
}
|
|
2105
|
-
currentChunk = overlapItems;
|
|
2106
|
-
currentLength = overlapLen;
|
|
2107
|
-
}
|
|
2108
|
-
currentChunk.push(split);
|
|
2109
|
-
currentLength += split.length;
|
|
2110
|
-
}
|
|
2111
|
-
if (currentChunk.length > 0) {
|
|
2112
|
-
finalChunks.push({
|
|
2113
|
-
id: `${docId}_chunk_${chunkIndex}`,
|
|
2114
|
-
content: currentChunk.join("").trim(),
|
|
2115
|
-
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex })
|
|
2116
|
-
});
|
|
2117
|
-
}
|
|
2118
|
-
return finalChunks;
|
|
2119
|
-
}
|
|
2120
|
-
/**
|
|
2121
|
-
* Recursively split text based on separators.
|
|
2122
|
-
*/
|
|
2123
|
-
recursiveSplit(text, separators, chunkSize) {
|
|
2124
|
-
const finalSplits = [];
|
|
2125
|
-
let separator = separators[separators.length - 1];
|
|
2126
|
-
let nextSeparators = [];
|
|
2127
|
-
for (let i = 0; i < separators.length; i++) {
|
|
2128
|
-
if (text.includes(separators[i])) {
|
|
2129
|
-
separator = separators[i];
|
|
2130
|
-
nextSeparators = separators.slice(i + 1);
|
|
2131
|
-
break;
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
const parts = text.split(separator);
|
|
2135
|
-
for (const part of parts) {
|
|
2136
|
-
if (part.length <= chunkSize) {
|
|
2137
|
-
finalSplits.push(part + separator);
|
|
2138
|
-
} else if (nextSeparators.length > 0) {
|
|
2139
|
-
finalSplits.push(...this.recursiveSplit(part, nextSeparators, chunkSize));
|
|
2140
|
-
} else {
|
|
2141
|
-
finalSplits.push(part);
|
|
2142
1838
|
}
|
|
2143
|
-
}
|
|
2144
|
-
return finalSplits;
|
|
2145
|
-
}
|
|
2146
|
-
/**
|
|
2147
|
-
* Chunk multiple documents at once.
|
|
2148
|
-
*/
|
|
2149
|
-
chunkMany(documents) {
|
|
2150
|
-
return documents.flatMap(
|
|
2151
|
-
(doc) => this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
|
2152
|
-
);
|
|
2153
|
-
}
|
|
2154
|
-
};
|
|
2155
|
-
|
|
2156
|
-
// src/rag/EntityExtractor.ts
|
|
2157
|
-
var EntityExtractor = class {
|
|
2158
|
-
constructor(llm) {
|
|
2159
|
-
this.llm = llm;
|
|
2160
|
-
}
|
|
2161
|
-
/**
|
|
2162
|
-
* Extract nodes and edges from a text chunk.
|
|
2163
|
-
*/
|
|
2164
|
-
async extract(text) {
|
|
2165
|
-
const prompt = `
|
|
2166
|
-
Extract entities and relationships from the following text.
|
|
2167
|
-
Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
|
|
2168
|
-
Use the same ID for the same entity.
|
|
2169
|
-
|
|
2170
|
-
Text:
|
|
2171
|
-
"${text}"
|
|
2172
|
-
|
|
2173
|
-
Output JSON:
|
|
2174
|
-
`;
|
|
2175
|
-
const response = await this.llm.chat([
|
|
2176
|
-
{ role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
|
|
2177
|
-
{ role: "user", content: prompt }
|
|
2178
|
-
], "");
|
|
2179
|
-
try {
|
|
2180
|
-
const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
|
|
2181
|
-
return JSON.parse(cleanJson);
|
|
2182
|
-
} catch (e) {
|
|
2183
|
-
console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
|
|
2184
|
-
return { nodes: [], edges: [] };
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
};
|
|
2188
|
-
|
|
2189
|
-
// src/llm/providers/OpenAIProvider.ts
|
|
2190
|
-
var import_openai = __toESM(require("openai"));
|
|
2191
|
-
var OpenAIProvider = class {
|
|
2192
|
-
constructor(llmConfig, embeddingConfig) {
|
|
2193
|
-
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
2194
|
-
this.client = new import_openai.default({ apiKey: llmConfig.apiKey });
|
|
2195
|
-
this.llmConfig = llmConfig;
|
|
2196
|
-
this.embeddingConfig = embeddingConfig;
|
|
1839
|
+
};
|
|
2197
1840
|
}
|
|
2198
1841
|
async chat(messages, context, options) {
|
|
2199
1842
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -2256,18 +1899,68 @@ var AnthropicProvider = class {
|
|
|
2256
1899
|
this.llmConfig = llmConfig;
|
|
2257
1900
|
this.embeddingConfig = embeddingConfig;
|
|
2258
1901
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
1902
|
+
static getValidator() {
|
|
1903
|
+
return {
|
|
1904
|
+
validate(config) {
|
|
1905
|
+
const errors = [];
|
|
1906
|
+
if (!config.apiKey) {
|
|
1907
|
+
errors.push({
|
|
1908
|
+
field: "llm.apiKey",
|
|
1909
|
+
message: "Anthropic API key is required",
|
|
1910
|
+
suggestion: "Set ANTHROPIC_API_KEY environment variable",
|
|
1911
|
+
severity: "error"
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
if (!config.model) {
|
|
1915
|
+
errors.push({
|
|
1916
|
+
field: "llm.model",
|
|
1917
|
+
message: "Anthropic model name is required",
|
|
1918
|
+
suggestion: 'e.g., "claude-3-5-sonnet-20241022"',
|
|
1919
|
+
severity: "error"
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
return errors;
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
static getHealthChecker() {
|
|
1927
|
+
return {
|
|
1928
|
+
async check(config) {
|
|
1929
|
+
const timestamp = Date.now();
|
|
1930
|
+
const apiKey = config.apiKey;
|
|
1931
|
+
const modelName = config.model;
|
|
1932
|
+
try {
|
|
1933
|
+
const { default: Anthropic2 } = await import("@anthropic-ai/sdk");
|
|
1934
|
+
const client = new Anthropic2({ apiKey });
|
|
1935
|
+
await client.messages.create({
|
|
1936
|
+
model: modelName,
|
|
1937
|
+
max_tokens: 10,
|
|
1938
|
+
messages: [{ role: "user", content: "ping" }]
|
|
1939
|
+
});
|
|
1940
|
+
return { healthy: true, provider: "anthropic", capabilities: { model: modelName }, timestamp };
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
return {
|
|
1943
|
+
healthy: false,
|
|
1944
|
+
provider: "anthropic",
|
|
1945
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1946
|
+
timestamp
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
async chat(messages, context, options) {
|
|
1953
|
+
var _a, _b, _c;
|
|
1954
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
1955
|
+
|
|
1956
|
+
Context:
|
|
1957
|
+
${context}`;
|
|
1958
|
+
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
1959
|
+
|
|
1960
|
+
Context:
|
|
1961
|
+
${context}`;
|
|
1962
|
+
const anthropicMessages = messages.map((m) => ({
|
|
1963
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
2271
1964
|
content: m.content
|
|
2272
1965
|
}));
|
|
2273
1966
|
const response = await this.client.messages.create({
|
|
@@ -2279,11 +1972,6 @@ ${context}`;
|
|
|
2279
1972
|
const block = response.content[0];
|
|
2280
1973
|
return block.type === "text" ? block.text : "";
|
|
2281
1974
|
}
|
|
2282
|
-
/**
|
|
2283
|
-
* Anthropic does not offer an embedding API.
|
|
2284
|
-
* This method throws with a clear error so developers know to configure
|
|
2285
|
-
* a separate embedding provider (OpenAI or Ollama).
|
|
2286
|
-
*/
|
|
2287
1975
|
async embed(text, options) {
|
|
2288
1976
|
void text;
|
|
2289
1977
|
void options;
|
|
@@ -2319,22 +2007,66 @@ var OllamaProvider = class {
|
|
|
2319
2007
|
this.llmConfig = llmConfig;
|
|
2320
2008
|
this.embeddingConfig = embeddingConfig;
|
|
2321
2009
|
}
|
|
2010
|
+
static getValidator() {
|
|
2011
|
+
return {
|
|
2012
|
+
validate(config) {
|
|
2013
|
+
const errors = [];
|
|
2014
|
+
if (!config.model) {
|
|
2015
|
+
const isEmbedding = config.provider === "ollama" && "dimensions" in config;
|
|
2016
|
+
const prefix = isEmbedding ? "embedding" : "llm";
|
|
2017
|
+
errors.push({
|
|
2018
|
+
field: `${prefix}.model`,
|
|
2019
|
+
message: "Ollama model name is required",
|
|
2020
|
+
suggestion: 'e.g., "llama3" or "mistral"',
|
|
2021
|
+
severity: "error"
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
return errors;
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
static getHealthChecker() {
|
|
2029
|
+
return {
|
|
2030
|
+
async check(config) {
|
|
2031
|
+
const timestamp = Date.now();
|
|
2032
|
+
const baseUrl = config.baseUrl || "http://localhost:11434";
|
|
2033
|
+
const modelName = config.model;
|
|
2034
|
+
try {
|
|
2035
|
+
const axios9 = (await import("axios")).default;
|
|
2036
|
+
const { data } = await axios9.get(`${baseUrl}/api/tags`);
|
|
2037
|
+
const models = data.models || [];
|
|
2038
|
+
const hasModel = models.some((m) => m.name === modelName || m.name.startsWith(`${modelName}:`));
|
|
2039
|
+
return {
|
|
2040
|
+
healthy: true,
|
|
2041
|
+
provider: "ollama",
|
|
2042
|
+
capabilities: {
|
|
2043
|
+
baseUrl,
|
|
2044
|
+
model: modelName,
|
|
2045
|
+
available: hasModel,
|
|
2046
|
+
totalModels: models.length
|
|
2047
|
+
},
|
|
2048
|
+
timestamp
|
|
2049
|
+
};
|
|
2050
|
+
} catch (e) {
|
|
2051
|
+
return {
|
|
2052
|
+
healthy: false,
|
|
2053
|
+
provider: "ollama",
|
|
2054
|
+
error: `Ollama server not reachable at ${baseUrl}. Is it running?`,
|
|
2055
|
+
timestamp
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2322
2061
|
async chat(messages, context, options) {
|
|
2323
|
-
var _a, _b, _c, _d
|
|
2324
|
-
const
|
|
2325
|
-
|
|
2326
|
-
Context:
|
|
2327
|
-
${context}`;
|
|
2328
|
-
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2329
|
-
|
|
2330
|
-
Context:
|
|
2331
|
-
${context}`;
|
|
2062
|
+
var _a, _b, _c, _d;
|
|
2063
|
+
const system = this.buildSystemPrompt(context);
|
|
2332
2064
|
const { data } = await this.http.post("/api/chat", {
|
|
2333
2065
|
model: this.llmConfig.model,
|
|
2334
2066
|
stream: false,
|
|
2335
2067
|
options: {
|
|
2336
|
-
temperature: (
|
|
2337
|
-
num_predict: (
|
|
2068
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
|
|
2069
|
+
num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
|
|
2338
2070
|
},
|
|
2339
2071
|
messages: [
|
|
2340
2072
|
{ role: "system", content: system },
|
|
@@ -2343,6 +2075,60 @@ ${context}`;
|
|
|
2343
2075
|
});
|
|
2344
2076
|
return data.message.content;
|
|
2345
2077
|
}
|
|
2078
|
+
chatStream(messages, context, options) {
|
|
2079
|
+
return __asyncGenerator(this, null, function* () {
|
|
2080
|
+
var _a, _b, _c, _d, _e;
|
|
2081
|
+
const system = this.buildSystemPrompt(context);
|
|
2082
|
+
const response = yield new __await(this.http.post("/api/chat", {
|
|
2083
|
+
model: this.llmConfig.model,
|
|
2084
|
+
stream: true,
|
|
2085
|
+
options: {
|
|
2086
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
|
|
2087
|
+
num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
|
|
2088
|
+
},
|
|
2089
|
+
messages: [
|
|
2090
|
+
{ role: "system", content: system },
|
|
2091
|
+
...messages.map((m) => ({ role: m.role, content: m.content }))
|
|
2092
|
+
]
|
|
2093
|
+
}, { responseType: "stream" }));
|
|
2094
|
+
try {
|
|
2095
|
+
for (var iter = __forAwait(response.data), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2096
|
+
const chunk = temp.value;
|
|
2097
|
+
const lines = chunk.toString().split("\n").filter(Boolean);
|
|
2098
|
+
for (const line of lines) {
|
|
2099
|
+
try {
|
|
2100
|
+
const json = JSON.parse(line);
|
|
2101
|
+
if ((_e = json.message) == null ? void 0 : _e.content) {
|
|
2102
|
+
yield json.message.content;
|
|
2103
|
+
}
|
|
2104
|
+
if (json.done) return;
|
|
2105
|
+
} catch (e) {
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
} catch (temp) {
|
|
2110
|
+
error = [temp];
|
|
2111
|
+
} finally {
|
|
2112
|
+
try {
|
|
2113
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
2114
|
+
} finally {
|
|
2115
|
+
if (error)
|
|
2116
|
+
throw error[0];
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
buildSystemPrompt(context) {
|
|
2122
|
+
var _a;
|
|
2123
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2124
|
+
|
|
2125
|
+
Context:
|
|
2126
|
+
${context}`;
|
|
2127
|
+
return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2128
|
+
|
|
2129
|
+
Context:
|
|
2130
|
+
${context}`;
|
|
2131
|
+
}
|
|
2346
2132
|
async embed(text, options) {
|
|
2347
2133
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2348
2134
|
const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
|
|
@@ -2399,6 +2185,52 @@ var GeminiProvider = class {
|
|
|
2399
2185
|
});
|
|
2400
2186
|
}
|
|
2401
2187
|
}
|
|
2188
|
+
static getValidator() {
|
|
2189
|
+
return {
|
|
2190
|
+
validate(config) {
|
|
2191
|
+
const errors = [];
|
|
2192
|
+
if (!config.apiKey && !process.env.GOOGLE_GENAI_API_KEY) {
|
|
2193
|
+
errors.push({
|
|
2194
|
+
field: "llm.apiKey",
|
|
2195
|
+
message: "Gemini API key is required",
|
|
2196
|
+
suggestion: "Set GOOGLE_GENAI_API_KEY environment variable or provide in config",
|
|
2197
|
+
severity: "error"
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
if (!config.model) {
|
|
2201
|
+
errors.push({ field: "llm.model", message: "Gemini model name is required", severity: "error" });
|
|
2202
|
+
}
|
|
2203
|
+
return errors;
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
static getHealthChecker() {
|
|
2208
|
+
return {
|
|
2209
|
+
async check(config) {
|
|
2210
|
+
const timestamp = Date.now();
|
|
2211
|
+
const apiKey = config.apiKey || process.env.GOOGLE_GENAI_API_KEY || "";
|
|
2212
|
+
const modelName = config.model;
|
|
2213
|
+
try {
|
|
2214
|
+
const { GoogleGenAI: GoogleGenAI2 } = await import("@google/genai");
|
|
2215
|
+
const genAI = new GoogleGenAI2({ apiKey });
|
|
2216
|
+
await genAI.models.get({ model: modelName });
|
|
2217
|
+
return {
|
|
2218
|
+
healthy: true,
|
|
2219
|
+
provider: "gemini",
|
|
2220
|
+
capabilities: { model: modelName },
|
|
2221
|
+
timestamp
|
|
2222
|
+
};
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
return {
|
|
2225
|
+
healthy: false,
|
|
2226
|
+
provider: "gemini",
|
|
2227
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2228
|
+
timestamp
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2402
2234
|
sanitizeModel(model) {
|
|
2403
2235
|
if (!model) return model;
|
|
2404
2236
|
return model.split(":")[0];
|
|
@@ -2641,189 +2473,610 @@ ${context != null ? context : "None"}` },
|
|
|
2641
2473
|
if (result === void 0) {
|
|
2642
2474
|
throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
|
|
2643
2475
|
}
|
|
2644
|
-
return String(result);
|
|
2645
|
-
}
|
|
2646
|
-
async embed(text) {
|
|
2647
|
-
var _a, _b;
|
|
2648
|
-
const path = (_a = this.opts.embedPath) != null ? _a : "/embeddings";
|
|
2649
|
-
let payload;
|
|
2650
|
-
if (this.opts.embedPayloadTemplate) {
|
|
2651
|
-
payload = buildPayload(this.opts.embedPayloadTemplate, {
|
|
2652
|
-
model: this.model,
|
|
2653
|
-
input: text
|
|
2476
|
+
return String(result);
|
|
2477
|
+
}
|
|
2478
|
+
async embed(text) {
|
|
2479
|
+
var _a, _b;
|
|
2480
|
+
const path = (_a = this.opts.embedPath) != null ? _a : "/embeddings";
|
|
2481
|
+
let payload;
|
|
2482
|
+
if (this.opts.embedPayloadTemplate) {
|
|
2483
|
+
payload = buildPayload(this.opts.embedPayloadTemplate, {
|
|
2484
|
+
model: this.model,
|
|
2485
|
+
input: text
|
|
2486
|
+
});
|
|
2487
|
+
} else {
|
|
2488
|
+
payload = {
|
|
2489
|
+
model: this.model,
|
|
2490
|
+
input: text
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
const { data } = await this.http.post(path, payload);
|
|
2494
|
+
const extractPath = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
2495
|
+
const vector = resolvePath(data, extractPath);
|
|
2496
|
+
if (!Array.isArray(vector)) {
|
|
2497
|
+
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
2498
|
+
}
|
|
2499
|
+
return vector;
|
|
2500
|
+
}
|
|
2501
|
+
async batchEmbed(texts) {
|
|
2502
|
+
const vectors = [];
|
|
2503
|
+
for (const text of texts) {
|
|
2504
|
+
vectors.push(await this.embed(text));
|
|
2505
|
+
}
|
|
2506
|
+
return vectors;
|
|
2507
|
+
}
|
|
2508
|
+
async ping() {
|
|
2509
|
+
try {
|
|
2510
|
+
if (this.opts.pingPath) {
|
|
2511
|
+
await this.http.get(this.opts.pingPath);
|
|
2512
|
+
}
|
|
2513
|
+
return true;
|
|
2514
|
+
} catch (err) {
|
|
2515
|
+
console.error("[UniversalLLMAdapter] Ping failed:", err);
|
|
2516
|
+
return false;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
|
|
2521
|
+
// src/llm/LLMFactory.ts
|
|
2522
|
+
var LLMFactory = class _LLMFactory {
|
|
2523
|
+
static create(llmConfig, embeddingConfig) {
|
|
2524
|
+
var _a;
|
|
2525
|
+
switch (llmConfig.provider) {
|
|
2526
|
+
case "openai":
|
|
2527
|
+
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
2528
|
+
case "anthropic":
|
|
2529
|
+
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2530
|
+
case "ollama":
|
|
2531
|
+
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2532
|
+
case "gemini":
|
|
2533
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2534
|
+
case "rest":
|
|
2535
|
+
case "universal_rest":
|
|
2536
|
+
case "custom":
|
|
2537
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
2538
|
+
default:
|
|
2539
|
+
if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
|
|
2540
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
2541
|
+
}
|
|
2542
|
+
throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
static getValidator(provider) {
|
|
2546
|
+
const providerClass = this.getProviderClass(provider);
|
|
2547
|
+
return providerClass && providerClass.getValidator ? providerClass.getValidator() : null;
|
|
2548
|
+
}
|
|
2549
|
+
static getHealthChecker(provider) {
|
|
2550
|
+
const providerClass = this.getProviderClass(provider);
|
|
2551
|
+
return providerClass && providerClass.getHealthChecker ? providerClass.getHealthChecker() : null;
|
|
2552
|
+
}
|
|
2553
|
+
static getProviderClass(provider) {
|
|
2554
|
+
switch (provider) {
|
|
2555
|
+
case "openai":
|
|
2556
|
+
return OpenAIProvider;
|
|
2557
|
+
case "anthropic":
|
|
2558
|
+
return AnthropicProvider;
|
|
2559
|
+
case "ollama":
|
|
2560
|
+
return OllamaProvider;
|
|
2561
|
+
case "gemini":
|
|
2562
|
+
return GeminiProvider;
|
|
2563
|
+
case "rest":
|
|
2564
|
+
case "universal_rest":
|
|
2565
|
+
case "custom":
|
|
2566
|
+
return UniversalLLMAdapter;
|
|
2567
|
+
default:
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Creates a dedicated embedding-only provider.
|
|
2573
|
+
*/
|
|
2574
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
2575
|
+
const fakeLLMConfig = {
|
|
2576
|
+
provider: embeddingConfig.provider,
|
|
2577
|
+
model: embeddingConfig.model,
|
|
2578
|
+
apiKey: embeddingConfig.apiKey,
|
|
2579
|
+
baseUrl: embeddingConfig.baseUrl,
|
|
2580
|
+
options: embeddingConfig.options
|
|
2581
|
+
};
|
|
2582
|
+
return _LLMFactory.create(fakeLLMConfig, embeddingConfig);
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
|
|
2586
|
+
// src/core/ProviderRegistry.ts
|
|
2587
|
+
var ProviderRegistry = class {
|
|
2588
|
+
static registerVectorProvider(name, providerClass) {
|
|
2589
|
+
this.vectorProviders[name] = providerClass;
|
|
2590
|
+
if (providerClass.getValidator) {
|
|
2591
|
+
this.vectorValidators[name] = providerClass.getValidator();
|
|
2592
|
+
}
|
|
2593
|
+
if (providerClass.getHealthChecker) {
|
|
2594
|
+
this.vectorHealthCheckers[name] = providerClass.getHealthChecker();
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
static async getVectorValidator(provider) {
|
|
2598
|
+
if (this.vectorValidators[provider]) return this.vectorValidators[provider];
|
|
2599
|
+
try {
|
|
2600
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
2601
|
+
if (providerClass.getValidator) {
|
|
2602
|
+
this.vectorValidators[provider] = providerClass.getValidator();
|
|
2603
|
+
return this.vectorValidators[provider];
|
|
2604
|
+
}
|
|
2605
|
+
} catch (e) {
|
|
2606
|
+
console.warn(`[ProviderRegistry] Failed to load validator for ${provider}:`, e);
|
|
2607
|
+
}
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
static async getVectorHealthChecker(provider) {
|
|
2611
|
+
if (this.vectorHealthCheckers[provider]) return this.vectorHealthCheckers[provider];
|
|
2612
|
+
try {
|
|
2613
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
2614
|
+
if (providerClass.getHealthChecker) {
|
|
2615
|
+
this.vectorHealthCheckers[provider] = providerClass.getHealthChecker();
|
|
2616
|
+
return this.vectorHealthCheckers[provider];
|
|
2617
|
+
}
|
|
2618
|
+
} catch (e) {
|
|
2619
|
+
console.warn(`[ProviderRegistry] Failed to load health checker for ${provider}:`, e);
|
|
2620
|
+
}
|
|
2621
|
+
return null;
|
|
2622
|
+
}
|
|
2623
|
+
static async loadVectorProviderClass(provider) {
|
|
2624
|
+
if (this.vectorProviders[provider]) return this.vectorProviders[provider];
|
|
2625
|
+
switch (provider) {
|
|
2626
|
+
case "pinecone": {
|
|
2627
|
+
const { PineconeProvider: PineconeProvider2 } = await Promise.resolve().then(() => (init_PineconeProvider(), PineconeProvider_exports));
|
|
2628
|
+
return PineconeProvider2;
|
|
2629
|
+
}
|
|
2630
|
+
case "pgvector":
|
|
2631
|
+
case "postgresql": {
|
|
2632
|
+
const { PostgreSQLProvider: PostgreSQLProvider2 } = await Promise.resolve().then(() => (init_PostgreSQLProvider(), PostgreSQLProvider_exports));
|
|
2633
|
+
return PostgreSQLProvider2;
|
|
2634
|
+
}
|
|
2635
|
+
case "mongodb": {
|
|
2636
|
+
const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
|
|
2637
|
+
return MongoDBProvider2;
|
|
2638
|
+
}
|
|
2639
|
+
case "milvus": {
|
|
2640
|
+
const { MilvusProvider: MilvusProvider2 } = await Promise.resolve().then(() => (init_MilvusProvider(), MilvusProvider_exports));
|
|
2641
|
+
return MilvusProvider2;
|
|
2642
|
+
}
|
|
2643
|
+
case "qdrant": {
|
|
2644
|
+
const { QdrantProvider: QdrantProvider2 } = await Promise.resolve().then(() => (init_QdrantProvider(), QdrantProvider_exports));
|
|
2645
|
+
return QdrantProvider2;
|
|
2646
|
+
}
|
|
2647
|
+
case "chromadb": {
|
|
2648
|
+
const { ChromaDBProvider: ChromaDBProvider2 } = await Promise.resolve().then(() => (init_ChromaDBProvider(), ChromaDBProvider_exports));
|
|
2649
|
+
return ChromaDBProvider2;
|
|
2650
|
+
}
|
|
2651
|
+
case "redis": {
|
|
2652
|
+
const { RedisProvider: RedisProvider2 } = await Promise.resolve().then(() => (init_RedisProvider(), RedisProvider_exports));
|
|
2653
|
+
return RedisProvider2;
|
|
2654
|
+
}
|
|
2655
|
+
case "weaviate": {
|
|
2656
|
+
const { WeaviateProvider: WeaviateProvider2 } = await Promise.resolve().then(() => (init_WeaviateProvider(), WeaviateProvider_exports));
|
|
2657
|
+
return WeaviateProvider2;
|
|
2658
|
+
}
|
|
2659
|
+
case "universal_rest":
|
|
2660
|
+
case "rest": {
|
|
2661
|
+
const { UniversalVectorProvider: UniversalVectorProvider2 } = await Promise.resolve().then(() => (init_UniversalVectorProvider(), UniversalVectorProvider_exports));
|
|
2662
|
+
return UniversalVectorProvider2;
|
|
2663
|
+
}
|
|
2664
|
+
default:
|
|
2665
|
+
throw new Error(`Unsupported vector provider: ${provider}`);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
static async createVectorProvider(config) {
|
|
2669
|
+
const providerClass = await this.loadVectorProviderClass(config.provider);
|
|
2670
|
+
return new providerClass(config);
|
|
2671
|
+
}
|
|
2672
|
+
static async createGraphProvider(config) {
|
|
2673
|
+
const { provider } = config;
|
|
2674
|
+
if (this.graphProviders[provider]) {
|
|
2675
|
+
return new this.graphProviders[provider](config);
|
|
2676
|
+
}
|
|
2677
|
+
switch (provider) {
|
|
2678
|
+
case "simple": {
|
|
2679
|
+
const { SimpleGraphProvider: SimpleGraphProvider2 } = await Promise.resolve().then(() => (init_SimpleGraphProvider(), SimpleGraphProvider_exports));
|
|
2680
|
+
return new SimpleGraphProvider2(config);
|
|
2681
|
+
}
|
|
2682
|
+
default:
|
|
2683
|
+
throw new Error(`Unsupported graph provider: ${provider}`);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
static createLLMProvider(llmConfig, embeddingConfig) {
|
|
2687
|
+
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
ProviderRegistry.vectorProviders = {};
|
|
2691
|
+
ProviderRegistry.graphProviders = {};
|
|
2692
|
+
ProviderRegistry.vectorValidators = {};
|
|
2693
|
+
ProviderRegistry.vectorHealthCheckers = {};
|
|
2694
|
+
ProviderRegistry.llmValidators = {};
|
|
2695
|
+
ProviderRegistry.llmHealthCheckers = {};
|
|
2696
|
+
|
|
2697
|
+
// src/core/ConfigValidator.ts
|
|
2698
|
+
var ConfigValidator = class {
|
|
2699
|
+
/**
|
|
2700
|
+
* Validates the entire RagConfig object.
|
|
2701
|
+
*/
|
|
2702
|
+
static async validate(config) {
|
|
2703
|
+
const errors = [];
|
|
2704
|
+
if (!config.projectId) {
|
|
2705
|
+
errors.push({
|
|
2706
|
+
field: "projectId",
|
|
2707
|
+
message: "projectId is required",
|
|
2708
|
+
severity: "error"
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
errors.push(...await this.validateVectorDbConfig(config.vectorDb));
|
|
2712
|
+
errors.push(...await this.validateLLMConfig(config.llm));
|
|
2713
|
+
if (config.embedding) {
|
|
2714
|
+
errors.push(...await this.validateEmbeddingConfig(config.embedding));
|
|
2715
|
+
} else if (config.llm.provider === "anthropic") {
|
|
2716
|
+
errors.push({
|
|
2717
|
+
field: "embedding",
|
|
2718
|
+
message: "Embedding config is required when using Anthropic LLM",
|
|
2719
|
+
suggestion: 'Provide embedding config with provider (e.g., "openai" or "ollama")',
|
|
2720
|
+
severity: "error"
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
if (config.ui) {
|
|
2724
|
+
errors.push(...this.validateUIConfig(config.ui));
|
|
2725
|
+
}
|
|
2726
|
+
if (config.rag) {
|
|
2727
|
+
errors.push(...this.validateRAGConfig(config.rag));
|
|
2728
|
+
}
|
|
2729
|
+
return errors;
|
|
2730
|
+
}
|
|
2731
|
+
static async validateVectorDbConfig(config) {
|
|
2732
|
+
const errors = [];
|
|
2733
|
+
if (!config.provider) {
|
|
2734
|
+
errors.push({ field: "vectorDb.provider", message: "Vector database provider is required", severity: "error" });
|
|
2735
|
+
return errors;
|
|
2736
|
+
}
|
|
2737
|
+
if (!config.indexName) {
|
|
2738
|
+
errors.push({ field: "vectorDb.indexName", message: "Vector database index name is required", severity: "error" });
|
|
2739
|
+
}
|
|
2740
|
+
const validator = await ProviderRegistry.getVectorValidator(config.provider);
|
|
2741
|
+
if (validator) {
|
|
2742
|
+
errors.push(...validator.validate(config));
|
|
2743
|
+
} else {
|
|
2744
|
+
this.fallbackVectorValidation(config, errors);
|
|
2745
|
+
}
|
|
2746
|
+
return errors;
|
|
2747
|
+
}
|
|
2748
|
+
static async validateLLMConfig(config) {
|
|
2749
|
+
const errors = [];
|
|
2750
|
+
if (!config.provider) {
|
|
2751
|
+
errors.push({ field: "llm.provider", message: "LLM provider is required", severity: "error" });
|
|
2752
|
+
return errors;
|
|
2753
|
+
}
|
|
2754
|
+
const validator = LLMFactory.getValidator(config.provider);
|
|
2755
|
+
if (validator) {
|
|
2756
|
+
errors.push(...validator.validate(config));
|
|
2757
|
+
} else {
|
|
2758
|
+
this.fallbackLLMValidation(config, errors);
|
|
2759
|
+
}
|
|
2760
|
+
if (config.temperature !== void 0 && (config.temperature < 0 || config.temperature > 2)) {
|
|
2761
|
+
errors.push({ field: "llm.temperature", message: "Temperature must be between 0 and 2", severity: "error" });
|
|
2762
|
+
}
|
|
2763
|
+
return errors;
|
|
2764
|
+
}
|
|
2765
|
+
static async validateEmbeddingConfig(config) {
|
|
2766
|
+
const errors = [];
|
|
2767
|
+
if (!config.provider) {
|
|
2768
|
+
errors.push({ field: "embedding.provider", message: "Embedding provider is required", severity: "error" });
|
|
2769
|
+
return errors;
|
|
2770
|
+
}
|
|
2771
|
+
const validator = LLMFactory.getValidator(config.provider);
|
|
2772
|
+
if (validator) {
|
|
2773
|
+
errors.push(...validator.validate(config));
|
|
2774
|
+
} else {
|
|
2775
|
+
this.fallbackEmbeddingValidation(config, errors);
|
|
2776
|
+
}
|
|
2777
|
+
return errors;
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Temporary fallbacks for providers not yet migrated to the pluggable architecture.
|
|
2781
|
+
*/
|
|
2782
|
+
static fallbackVectorValidation(config, errors) {
|
|
2783
|
+
const opts = config.options || {};
|
|
2784
|
+
switch (config.provider) {
|
|
2785
|
+
case "milvus":
|
|
2786
|
+
if (!opts.baseUrl && !opts.uri && !(opts.host && opts.port)) {
|
|
2787
|
+
errors.push({ field: "vectorDb.options.baseUrl", message: "Milvus connection info required", severity: "error" });
|
|
2788
|
+
}
|
|
2789
|
+
break;
|
|
2790
|
+
case "qdrant":
|
|
2791
|
+
if (!opts.baseUrl && !opts.url) {
|
|
2792
|
+
errors.push({ field: "vectorDb.options.baseUrl", message: "Qdrant URL required", severity: "error" });
|
|
2793
|
+
}
|
|
2794
|
+
break;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
static fallbackLLMValidation(config, errors) {
|
|
2798
|
+
if (!config.model) {
|
|
2799
|
+
errors.push({ field: "llm.model", message: "LLM model name is required", severity: "error" });
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
static fallbackEmbeddingValidation(config, errors) {
|
|
2803
|
+
if (!config.model) {
|
|
2804
|
+
errors.push({ field: "embedding.model", message: "Embedding model name is required", severity: "error" });
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
static validateUIConfig(config) {
|
|
2808
|
+
const errors = [];
|
|
2809
|
+
if (config.primaryColor && !this.isValidCSSColor(config.primaryColor)) {
|
|
2810
|
+
errors.push({ field: "ui.primaryColor", message: "Invalid CSS color format", severity: "warning" });
|
|
2811
|
+
}
|
|
2812
|
+
if (config.borderRadius && !UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius)) {
|
|
2813
|
+
errors.push({ field: "ui.borderRadius", message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(", ")}`, severity: "warning" });
|
|
2814
|
+
}
|
|
2815
|
+
return errors;
|
|
2816
|
+
}
|
|
2817
|
+
static validateRAGConfig(config) {
|
|
2818
|
+
const errors = [];
|
|
2819
|
+
if (config.topK !== void 0 && (typeof config.topK !== "number" || config.topK <= 0)) {
|
|
2820
|
+
errors.push({ field: "rag.topK", message: "topK must be a positive integer", severity: "error" });
|
|
2821
|
+
}
|
|
2822
|
+
return errors;
|
|
2823
|
+
}
|
|
2824
|
+
static isValidCSSColor(color) {
|
|
2825
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
2826
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2827
|
+
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
2828
|
+
return hexRegex.test(color) || rgbRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
2829
|
+
}
|
|
2830
|
+
static async validateAndThrow(config) {
|
|
2831
|
+
const errors = await this.validate(config);
|
|
2832
|
+
const errorItems = errors.filter((e) => e.severity === "error");
|
|
2833
|
+
if (errorItems.length > 0) {
|
|
2834
|
+
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
2835
|
+
throw new Error(`[ConfigValidator] Configuration validation failed:
|
|
2836
|
+
${message}`);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
// src/rag/DocumentChunker.ts
|
|
2842
|
+
var DocumentChunker = class {
|
|
2843
|
+
constructor(chunkSize = 1e3, chunkOverlap = 200, separators = ["\n# ", "\n## ", "\n### ", "\n#### ", "\n\n", "\n", " ", ""]) {
|
|
2844
|
+
this.chunkSize = chunkSize;
|
|
2845
|
+
this.chunkOverlap = chunkOverlap;
|
|
2846
|
+
this.separators = separators;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Split a single text string into overlapping chunks using a recursive strategy.
|
|
2850
|
+
* Preserves structural boundaries (Markdown headers) where possible.
|
|
2851
|
+
*/
|
|
2852
|
+
chunk(text, options = {}) {
|
|
2853
|
+
const {
|
|
2854
|
+
chunkSize = this.chunkSize,
|
|
2855
|
+
chunkOverlap = this.chunkOverlap,
|
|
2856
|
+
docId = `doc_${Date.now()}`,
|
|
2857
|
+
metadata = {},
|
|
2858
|
+
separators = this.separators
|
|
2859
|
+
} = options;
|
|
2860
|
+
const finalChunks = [];
|
|
2861
|
+
const splits = this.recursiveSplit(text, separators, chunkSize);
|
|
2862
|
+
let currentChunk = [];
|
|
2863
|
+
let currentLength = 0;
|
|
2864
|
+
let chunkIndex = 0;
|
|
2865
|
+
for (const split of splits) {
|
|
2866
|
+
if (currentLength + split.length > chunkSize && currentChunk.length > 0) {
|
|
2867
|
+
finalChunks.push({
|
|
2868
|
+
id: `${docId}_chunk_${chunkIndex++}`,
|
|
2869
|
+
content: currentChunk.join("").trim(),
|
|
2870
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex: chunkIndex - 1 })
|
|
2871
|
+
});
|
|
2872
|
+
const overlapItems = [];
|
|
2873
|
+
let overlapLen = 0;
|
|
2874
|
+
for (let i = currentChunk.length - 1; i >= 0; i--) {
|
|
2875
|
+
if (overlapLen + currentChunk[i].length <= chunkOverlap) {
|
|
2876
|
+
overlapItems.unshift(currentChunk[i]);
|
|
2877
|
+
overlapLen += currentChunk[i].length;
|
|
2878
|
+
} else {
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
currentChunk = overlapItems;
|
|
2883
|
+
currentLength = overlapLen;
|
|
2884
|
+
}
|
|
2885
|
+
currentChunk.push(split);
|
|
2886
|
+
currentLength += split.length;
|
|
2887
|
+
}
|
|
2888
|
+
if (currentChunk.length > 0) {
|
|
2889
|
+
finalChunks.push({
|
|
2890
|
+
id: `${docId}_chunk_${chunkIndex}`,
|
|
2891
|
+
content: currentChunk.join("").trim(),
|
|
2892
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex })
|
|
2654
2893
|
});
|
|
2655
|
-
} else {
|
|
2656
|
-
payload = {
|
|
2657
|
-
model: this.model,
|
|
2658
|
-
input: text
|
|
2659
|
-
};
|
|
2660
|
-
}
|
|
2661
|
-
const { data } = await this.http.post(path, payload);
|
|
2662
|
-
const extractPath = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
2663
|
-
const vector = resolvePath(data, extractPath);
|
|
2664
|
-
if (!Array.isArray(vector)) {
|
|
2665
|
-
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
2666
2894
|
}
|
|
2667
|
-
return
|
|
2895
|
+
return finalChunks;
|
|
2668
2896
|
}
|
|
2669
|
-
|
|
2670
|
-
const
|
|
2671
|
-
|
|
2672
|
-
|
|
2897
|
+
recursiveSplit(text, separators, chunkSize) {
|
|
2898
|
+
const finalSplits = [];
|
|
2899
|
+
let separator = separators[separators.length - 1];
|
|
2900
|
+
let nextSeparators = [];
|
|
2901
|
+
for (let i = 0; i < separators.length; i++) {
|
|
2902
|
+
const sep = separators[i];
|
|
2903
|
+
if (text.includes(sep)) {
|
|
2904
|
+
separator = sep;
|
|
2905
|
+
nextSeparators = separators.slice(i + 1);
|
|
2906
|
+
break;
|
|
2907
|
+
}
|
|
2673
2908
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
if (
|
|
2679
|
-
|
|
2909
|
+
const parts = text.split(separator);
|
|
2910
|
+
for (const part of parts) {
|
|
2911
|
+
if (part.length <= chunkSize) {
|
|
2912
|
+
finalSplits.push(part + (part === parts[parts.length - 1] ? "" : separator));
|
|
2913
|
+
} else if (nextSeparators.length > 0) {
|
|
2914
|
+
finalSplits.push(...this.recursiveSplit(part, nextSeparators, chunkSize));
|
|
2915
|
+
} else {
|
|
2916
|
+
finalSplits.push(part);
|
|
2680
2917
|
}
|
|
2681
|
-
return true;
|
|
2682
|
-
} catch (err) {
|
|
2683
|
-
console.error("[UniversalLLMAdapter] Ping failed:", err);
|
|
2684
|
-
return false;
|
|
2685
2918
|
}
|
|
2919
|
+
return finalSplits;
|
|
2920
|
+
}
|
|
2921
|
+
chunkMany(documents) {
|
|
2922
|
+
return documents.flatMap(
|
|
2923
|
+
(doc) => this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
|
2924
|
+
);
|
|
2686
2925
|
}
|
|
2687
2926
|
};
|
|
2688
2927
|
|
|
2689
|
-
// src/
|
|
2690
|
-
var
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
switch (llmConfig.provider) {
|
|
2694
|
-
case "openai":
|
|
2695
|
-
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
2696
|
-
case "anthropic":
|
|
2697
|
-
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2698
|
-
case "ollama":
|
|
2699
|
-
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2700
|
-
case "gemini":
|
|
2701
|
-
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2702
|
-
case "rest":
|
|
2703
|
-
case "universal_rest":
|
|
2704
|
-
case "custom":
|
|
2705
|
-
return new UniversalLLMAdapter(llmConfig);
|
|
2706
|
-
default:
|
|
2707
|
-
if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
|
|
2708
|
-
return new UniversalLLMAdapter(llmConfig);
|
|
2709
|
-
}
|
|
2710
|
-
throw new Error(
|
|
2711
|
-
`[LLMFactory] Unknown provider "${llmConfig.provider}". Supported: openai | anthropic | ollama | rest | custom`
|
|
2712
|
-
);
|
|
2713
|
-
}
|
|
2928
|
+
// src/rag/EntityExtractor.ts
|
|
2929
|
+
var EntityExtractor = class {
|
|
2930
|
+
constructor(llm) {
|
|
2931
|
+
this.llm = llm;
|
|
2714
2932
|
}
|
|
2715
2933
|
/**
|
|
2716
|
-
*
|
|
2717
|
-
* Useful when the LLM provider (e.g. Anthropic) doesn't support embeddings.
|
|
2934
|
+
* Extract nodes and edges from a text chunk.
|
|
2718
2935
|
*/
|
|
2719
|
-
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2936
|
+
async extract(text) {
|
|
2937
|
+
const prompt = `
|
|
2938
|
+
Extract entities and relationships from the following text.
|
|
2939
|
+
Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
|
|
2940
|
+
Use the same ID for the same entity.
|
|
2941
|
+
|
|
2942
|
+
IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
|
|
2943
|
+
DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
|
|
2944
|
+
|
|
2945
|
+
Text:
|
|
2946
|
+
"${text}"
|
|
2947
|
+
|
|
2948
|
+
Output JSON:
|
|
2949
|
+
`;
|
|
2950
|
+
const response = await this.llm.chat([
|
|
2951
|
+
{ role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
|
|
2952
|
+
{ role: "user", content: prompt }
|
|
2953
|
+
], "");
|
|
2954
|
+
try {
|
|
2955
|
+
const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
|
|
2956
|
+
return JSON.parse(cleanJson);
|
|
2957
|
+
} catch (e) {
|
|
2958
|
+
console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
|
|
2959
|
+
return { nodes: [], edges: [] };
|
|
2960
|
+
}
|
|
2728
2961
|
}
|
|
2729
2962
|
};
|
|
2730
2963
|
|
|
2731
|
-
// src/
|
|
2732
|
-
var
|
|
2733
|
-
static registerVectorProvider(name, providerClass) {
|
|
2734
|
-
this.vectorProviders[name] = providerClass;
|
|
2735
|
-
}
|
|
2964
|
+
// src/rag/Reranker.ts
|
|
2965
|
+
var Reranker = class {
|
|
2736
2966
|
/**
|
|
2737
|
-
*
|
|
2967
|
+
* Re-ranks matches based on a secondary relevance score.
|
|
2968
|
+
* In a production environment, this would call a Cross-Encoder model.
|
|
2969
|
+
* Here we implement a placeholder that filters by score and limits count.
|
|
2738
2970
|
*/
|
|
2739
|
-
|
|
2740
|
-
|
|
2971
|
+
async rerank(matches, query, limit = 5) {
|
|
2972
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
2741
2973
|
}
|
|
2974
|
+
};
|
|
2975
|
+
|
|
2976
|
+
// src/rag/LlamaIndexIngestor.ts
|
|
2977
|
+
var LlamaIndexIngestor = class {
|
|
2742
2978
|
/**
|
|
2743
|
-
*
|
|
2744
|
-
*
|
|
2979
|
+
* Chunks document content using LlamaIndex SentenceSplitter.
|
|
2980
|
+
* This respects sentence and paragraph boundaries much more effectively
|
|
2981
|
+
* than standard character-count splitting.
|
|
2745
2982
|
*/
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
return new MilvusProvider2(config);
|
|
2768
|
-
}
|
|
2769
|
-
case "qdrant": {
|
|
2770
|
-
const { QdrantProvider: QdrantProvider2 } = await Promise.resolve().then(() => (init_QdrantProvider(), QdrantProvider_exports));
|
|
2771
|
-
return new QdrantProvider2(config);
|
|
2772
|
-
}
|
|
2773
|
-
case "chromadb": {
|
|
2774
|
-
const { ChromaDBProvider: ChromaDBProvider2 } = await Promise.resolve().then(() => (init_ChromaDBProvider(), ChromaDBProvider_exports));
|
|
2775
|
-
return new ChromaDBProvider2(config);
|
|
2776
|
-
}
|
|
2777
|
-
case "redis": {
|
|
2778
|
-
const { RedisProvider: RedisProvider2 } = await Promise.resolve().then(() => (init_RedisProvider(), RedisProvider_exports));
|
|
2779
|
-
return new RedisProvider2(config);
|
|
2780
|
-
}
|
|
2781
|
-
case "weaviate": {
|
|
2782
|
-
const { WeaviateProvider: WeaviateProvider2 } = await Promise.resolve().then(() => (init_WeaviateProvider(), WeaviateProvider_exports));
|
|
2783
|
-
return new WeaviateProvider2(config);
|
|
2784
|
-
}
|
|
2785
|
-
case "universal_rest":
|
|
2786
|
-
case "rest": {
|
|
2787
|
-
const { UniversalVectorProvider: UniversalVectorProvider2 } = await Promise.resolve().then(() => (init_UniversalVectorProvider(), UniversalVectorProvider_exports));
|
|
2788
|
-
return new UniversalVectorProvider2(config);
|
|
2789
|
-
}
|
|
2790
|
-
default:
|
|
2791
|
-
throw new Error(
|
|
2792
|
-
`[ProviderRegistry] Unsupported vector provider: "${provider}". Built-in providers: pinecone | pgvector | postgresql | mongodb | milvus | qdrant | chromadb | redis | weaviate | universal_rest. For custom providers, call ProviderRegistry.registerVectorProvider("${provider}", YourClass).`
|
|
2793
|
-
);
|
|
2983
|
+
async chunk(text, options = {}) {
|
|
2984
|
+
var _a, _b;
|
|
2985
|
+
try {
|
|
2986
|
+
const { Document, SentenceSplitter, MetadataMode } = await import(`${"llamaindex"}`);
|
|
2987
|
+
const splitter = new SentenceSplitter({
|
|
2988
|
+
chunkSize: (_a = options.chunkSize) != null ? _a : 1e3,
|
|
2989
|
+
chunkOverlap: (_b = options.chunkOverlap) != null ? _b : 200
|
|
2990
|
+
});
|
|
2991
|
+
const doc = new Document({ text, metadata: options.metadata || {} });
|
|
2992
|
+
const nodes = splitter.getNodesFromDocuments([doc]);
|
|
2993
|
+
return nodes.map((node, index) => ({
|
|
2994
|
+
id: `${options.docId || "doc"}_node_${index}`,
|
|
2995
|
+
content: node.getContent(MetadataMode.ALL),
|
|
2996
|
+
metadata: __spreadProps(__spreadValues(__spreadValues({}, options.metadata), node.metadata), {
|
|
2997
|
+
nodeId: node.id_,
|
|
2998
|
+
chunkIndex: index
|
|
2999
|
+
})
|
|
3000
|
+
}));
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
console.warn("[LlamaIndexIngestor] LlamaIndex package not found or failed. Falling back to default chunker.");
|
|
3003
|
+
throw error;
|
|
2794
3004
|
}
|
|
2795
3005
|
}
|
|
2796
3006
|
/**
|
|
2797
|
-
*
|
|
3007
|
+
* Batch processing for multiple documents.
|
|
2798
3008
|
*/
|
|
2799
|
-
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2802
|
-
|
|
3009
|
+
async chunkMany(documents, options = {}) {
|
|
3010
|
+
const allChunks = [];
|
|
3011
|
+
for (const doc of documents) {
|
|
3012
|
+
const chunks = await this.chunk(doc.content, __spreadProps(__spreadValues({}, options), { docId: doc.docId, metadata: doc.metadata }));
|
|
3013
|
+
allChunks.push(...chunks);
|
|
2803
3014
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
3015
|
+
return allChunks;
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
|
|
3019
|
+
// src/core/LangChainAgent.ts
|
|
3020
|
+
var LangChainAgent = class {
|
|
3021
|
+
constructor(pipeline, config) {
|
|
3022
|
+
this.pipeline = pipeline;
|
|
3023
|
+
this.config = config;
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Initializes the agent with the RAG pipeline as a primary tool.
|
|
3027
|
+
* Dynamically imports LangChain dependencies to avoid build errors if missing.
|
|
3028
|
+
*/
|
|
3029
|
+
async initialize(chatModel) {
|
|
3030
|
+
try {
|
|
3031
|
+
const { DynamicTool } = await import(`${"@langchain/core/tools"}`);
|
|
3032
|
+
const { ChatPromptTemplate, MessagesPlaceholder } = await import(`${"@langchain/core/prompts"}`);
|
|
3033
|
+
const { AgentExecutor, createOpenAIFunctionsAgent } = await import(`${"langchain/agents"}`);
|
|
3034
|
+
const searchTool = new DynamicTool({
|
|
3035
|
+
name: "document_search",
|
|
3036
|
+
description: "Use this tool to search through the knowledge base and document repository. Input should be a specific search query.",
|
|
3037
|
+
func: async (query) => {
|
|
3038
|
+
const response = await this.pipeline.ask(query);
|
|
3039
|
+
return `Search Results:
|
|
3040
|
+
${response.reply}
|
|
3041
|
+
|
|
3042
|
+
Sources Used: ${JSON.stringify(response.sources.map((s) => s.id))}`;
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
const tools = [searchTool];
|
|
3046
|
+
const prompt = ChatPromptTemplate.fromMessages([
|
|
3047
|
+
["system", this.config.llm.systemPrompt || "You are a helpful AI assistant with access to a document search tool."],
|
|
3048
|
+
new MessagesPlaceholder("chat_history"),
|
|
3049
|
+
["human", "{input}"],
|
|
3050
|
+
new MessagesPlaceholder("agent_scratchpad")
|
|
3051
|
+
]);
|
|
3052
|
+
const agent = await createOpenAIFunctionsAgent({
|
|
3053
|
+
llm: chatModel,
|
|
3054
|
+
tools,
|
|
3055
|
+
prompt
|
|
3056
|
+
});
|
|
3057
|
+
this.executor = new AgentExecutor({
|
|
3058
|
+
agent,
|
|
3059
|
+
tools
|
|
3060
|
+
});
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
console.error("[LangChainAgent] Failed to initialize. Ensure 'langchain' and '@langchain/core' are installed.");
|
|
3063
|
+
throw error;
|
|
2816
3064
|
}
|
|
2817
3065
|
}
|
|
2818
3066
|
/**
|
|
2819
|
-
*
|
|
3067
|
+
* Run the agentic flow.
|
|
2820
3068
|
*/
|
|
2821
|
-
|
|
2822
|
-
|
|
3069
|
+
async run(input, chatHistory = []) {
|
|
3070
|
+
if (!this.executor) {
|
|
3071
|
+
throw new Error("[LangChainAgent] Agent not initialized. Call initialize() first.");
|
|
3072
|
+
}
|
|
3073
|
+
const response = await this.executor.invoke({
|
|
3074
|
+
input,
|
|
3075
|
+
chat_history: chatHistory
|
|
3076
|
+
});
|
|
3077
|
+
return response.output;
|
|
2823
3078
|
}
|
|
2824
3079
|
};
|
|
2825
|
-
ProviderRegistry.vectorProviders = {};
|
|
2826
|
-
ProviderRegistry.graphProviders = {};
|
|
2827
3080
|
|
|
2828
3081
|
// src/core/BatchProcessor.ts
|
|
2829
3082
|
function isTransientError(error) {
|
|
@@ -3131,122 +3384,143 @@ var EmbeddingStrategyResolver = class {
|
|
|
3131
3384
|
}
|
|
3132
3385
|
};
|
|
3133
3386
|
|
|
3134
|
-
// src/core/
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3387
|
+
// src/core/QueryProcessor.ts
|
|
3388
|
+
var QueryProcessor = class {
|
|
3389
|
+
/**
|
|
3390
|
+
* Normalizes a string value by collapsing whitespace and trimming.
|
|
3391
|
+
*/
|
|
3392
|
+
static normalizeHintValue(value) {
|
|
3393
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Checks if a string is likely a question word or common prompt phrase.
|
|
3397
|
+
*/
|
|
3398
|
+
static isLikelyPromptPhrase(value) {
|
|
3399
|
+
return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* Scans a natural language question for potential metadata hints and keywords.
|
|
3403
|
+
*/
|
|
3404
|
+
static extractQueryFieldHints(question) {
|
|
3405
|
+
var _a, _b, _c, _d;
|
|
3406
|
+
if (!question.trim()) return [];
|
|
3407
|
+
const hints = /* @__PURE__ */ new Map();
|
|
3408
|
+
const addHint = (value, field) => {
|
|
3409
|
+
const normalizedValue = this.normalizeHintValue(value);
|
|
3410
|
+
if (!normalizedValue) return;
|
|
3411
|
+
const normalizedField = field ? field.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim() : void 0;
|
|
3412
|
+
const key = `${normalizedField != null ? normalizedField : "*"}::${normalizedValue.toLowerCase()}`;
|
|
3413
|
+
if (!hints.has(key)) {
|
|
3414
|
+
hints.set(key, __spreadValues({
|
|
3415
|
+
value: normalizedValue
|
|
3416
|
+
}, normalizedField ? { field: normalizedField } : {}));
|
|
3417
|
+
}
|
|
3418
|
+
};
|
|
3419
|
+
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
3420
|
+
addHint(match[1]);
|
|
3154
3421
|
}
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
if (field && !isLikelyPromptPhrase(field)) {
|
|
3211
|
-
addHint(value, field);
|
|
3212
|
-
} else {
|
|
3213
|
-
addHint(value);
|
|
3422
|
+
const naturalQuestionPatterns = [
|
|
3423
|
+
/\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3424
|
+
/\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3425
|
+
/\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
|
|
3426
|
+
];
|
|
3427
|
+
const personCompanyPatterns = [
|
|
3428
|
+
/\bcompany(?:\s+name)?\s+(?:of|for)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3429
|
+
/\b(?:which|what)\s+company\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi,
|
|
3430
|
+
/\bwhere\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi
|
|
3431
|
+
];
|
|
3432
|
+
for (const pattern of personCompanyPatterns) {
|
|
3433
|
+
for (const match of question.matchAll(pattern)) {
|
|
3434
|
+
const name = match[1];
|
|
3435
|
+
if (name) addHint(name, "name");
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
const universalPatterns = [
|
|
3439
|
+
{ regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
|
|
3440
|
+
{ regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
|
|
3441
|
+
{ regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: "date", group: 1 },
|
|
3442
|
+
{ regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
|
|
3443
|
+
{ regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
|
|
3444
|
+
{ regex: /"([^"]{2,120})"/g, group: 1 },
|
|
3445
|
+
{ regex: /'([^']{2,120})'/g, group: 1 }
|
|
3446
|
+
];
|
|
3447
|
+
for (const p of universalPatterns) {
|
|
3448
|
+
for (const match of question.matchAll(p.regex)) {
|
|
3449
|
+
const val = p.group ? (_a = match[p.group]) != null ? _a : match[0] : match[0];
|
|
3450
|
+
if (!val) continue;
|
|
3451
|
+
if (p.field) addHint(val, p.field);
|
|
3452
|
+
else addHint(val);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
for (const pattern of naturalQuestionPatterns) {
|
|
3456
|
+
for (const match of question.matchAll(pattern)) {
|
|
3457
|
+
const value = (_b = match[2]) != null ? _b : match[1];
|
|
3458
|
+
if (value) addHint(value);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
|
|
3462
|
+
const valuePattern = `([^\\n?.!,]{1,120}?)`;
|
|
3463
|
+
const fieldValuePatterns = [
|
|
3464
|
+
new RegExp(`\\b${fieldPattern}\\s*(?:=|:)\\s*["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
3465
|
+
new RegExp(`\\b${fieldPattern}\\s+(?:is|are|was|were|equals?|equal to|named|called)\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
3466
|
+
new RegExp(`\\bwith\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi")
|
|
3467
|
+
];
|
|
3468
|
+
for (const pattern of fieldValuePatterns) {
|
|
3469
|
+
for (const match of question.matchAll(pattern)) {
|
|
3470
|
+
const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
|
|
3471
|
+
const value = (_d = match[2]) != null ? _d : "";
|
|
3472
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
3473
|
+
addHint(value, field);
|
|
3474
|
+
} else {
|
|
3475
|
+
addHint(value);
|
|
3476
|
+
}
|
|
3214
3477
|
}
|
|
3215
3478
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
addHint(match[0]);
|
|
3219
|
-
}
|
|
3220
|
-
return [...hints.values()];
|
|
3221
|
-
}
|
|
3222
|
-
function buildQueryFilter(question, hints) {
|
|
3223
|
-
const filter = { metadata: {}, keywords: [], queryText: question };
|
|
3224
|
-
for (const hint of hints) {
|
|
3225
|
-
if (hint.field) {
|
|
3226
|
-
filter.metadata[hint.field] = hint.value;
|
|
3227
|
-
} else {
|
|
3228
|
-
filter.keywords.push(hint.value);
|
|
3479
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
3480
|
+
addHint(match[0]);
|
|
3229
3481
|
}
|
|
3482
|
+
return [...hints.values()];
|
|
3230
3483
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3484
|
+
/**
|
|
3485
|
+
* Constructs a QueryFilter object from extracted hints.
|
|
3486
|
+
*/
|
|
3487
|
+
static buildQueryFilter(question, hints) {
|
|
3488
|
+
const filter = { metadata: {}, keywords: [], queryText: question };
|
|
3489
|
+
for (const hint of hints) {
|
|
3490
|
+
if (hint.field) {
|
|
3491
|
+
filter.metadata[hint.field] = hint.value;
|
|
3492
|
+
} else {
|
|
3493
|
+
filter.keywords.push(hint.value);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
3497
|
+
const term = this.normalizeHintValue(match[0]);
|
|
3498
|
+
if (term && !filter.keywords.includes(term)) filter.keywords.push(term);
|
|
3499
|
+
}
|
|
3500
|
+
if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
|
|
3501
|
+
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
3502
|
+
return filter;
|
|
3234
3503
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
}
|
|
3504
|
+
};
|
|
3505
|
+
|
|
3506
|
+
// src/core/Pipeline.ts
|
|
3239
3507
|
var Pipeline = class {
|
|
3240
3508
|
constructor(config) {
|
|
3241
|
-
this.initialised = false;
|
|
3242
|
-
var _a, _b, _c, _d;
|
|
3243
3509
|
this.config = config;
|
|
3510
|
+
this.embeddingCache = /* @__PURE__ */ new Map();
|
|
3511
|
+
this.initialised = false;
|
|
3512
|
+
var _a, _b, _c, _d, _e;
|
|
3244
3513
|
this.chunker = new DocumentChunker(
|
|
3245
3514
|
(_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
|
|
3246
3515
|
(_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
|
|
3247
3516
|
);
|
|
3517
|
+
if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
|
|
3518
|
+
this.llamaIngestor = new LlamaIndexIngestor();
|
|
3519
|
+
}
|
|
3520
|
+
this.reranker = new Reranker();
|
|
3248
3521
|
}
|
|
3249
3522
|
async initialize() {
|
|
3523
|
+
var _a;
|
|
3250
3524
|
if (this.initialised) return;
|
|
3251
3525
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
3252
3526
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -3261,6 +3535,10 @@ var Pipeline = class {
|
|
|
3261
3535
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
3262
3536
|
}
|
|
3263
3537
|
await this.vectorDB.initialize();
|
|
3538
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
|
|
3539
|
+
this.agent = new LangChainAgent(this, this.config);
|
|
3540
|
+
await this.agent.initialize(this.llmProvider);
|
|
3541
|
+
}
|
|
3264
3542
|
this.initialised = true;
|
|
3265
3543
|
}
|
|
3266
3544
|
/**
|
|
@@ -3271,71 +3549,23 @@ var Pipeline = class {
|
|
|
3271
3549
|
await this.initialize();
|
|
3272
3550
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
3273
3551
|
const results = [];
|
|
3274
|
-
for (const doc of documents) {
|
|
3275
|
-
try {
|
|
3276
|
-
const chunks = this.
|
|
3277
|
-
|
|
3278
|
-
metadata: doc.metadata
|
|
3279
|
-
});
|
|
3280
|
-
const embedBatchOptions = {
|
|
3281
|
-
batchSize: 50,
|
|
3282
|
-
maxRetries: 3,
|
|
3283
|
-
initialDelayMs: 100
|
|
3284
|
-
};
|
|
3285
|
-
const vectors = await BatchProcessor.mapWithRetry(
|
|
3286
|
-
chunks.map((c) => c.content),
|
|
3287
|
-
(text) => this.embeddingProvider.embed(text, { taskType: "document" }),
|
|
3288
|
-
embedBatchOptions
|
|
3289
|
-
);
|
|
3290
|
-
if (vectors.length !== chunks.length) {
|
|
3291
|
-
throw new Error(`Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks`);
|
|
3292
|
-
}
|
|
3552
|
+
for (const doc of documents) {
|
|
3553
|
+
try {
|
|
3554
|
+
const chunks = await this.prepareChunks(doc);
|
|
3555
|
+
const vectors = await this.processEmbeddings(chunks);
|
|
3293
3556
|
const upsertDocs = chunks.map((chunk, i) => ({
|
|
3294
3557
|
id: chunk.id,
|
|
3295
3558
|
vector: vectors[i],
|
|
3296
3559
|
content: chunk.content,
|
|
3297
3560
|
metadata: chunk.metadata
|
|
3298
3561
|
}));
|
|
3299
|
-
const
|
|
3300
|
-
batchSize: 100,
|
|
3301
|
-
maxRetries: 3,
|
|
3302
|
-
initialDelayMs: 100
|
|
3303
|
-
};
|
|
3304
|
-
const upsertResult = await BatchProcessor.processBatch(
|
|
3305
|
-
upsertDocs,
|
|
3306
|
-
(batch) => this.vectorDB.batchUpsert(batch, ns),
|
|
3307
|
-
upsertBatchOptions
|
|
3308
|
-
);
|
|
3309
|
-
if (upsertResult.errors.length > 0) {
|
|
3310
|
-
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed for doc ${doc.docId}`);
|
|
3311
|
-
}
|
|
3562
|
+
const totalProcessed = await this.processUpserts(upsertDocs, ns);
|
|
3312
3563
|
results.push({
|
|
3313
3564
|
docId: doc.docId,
|
|
3314
|
-
chunksIngested:
|
|
3565
|
+
chunksIngested: totalProcessed
|
|
3315
3566
|
});
|
|
3316
3567
|
if (this.graphDB && this.entityExtractor) {
|
|
3317
|
-
|
|
3318
|
-
const extractionOptions = {
|
|
3319
|
-
batchSize: 2,
|
|
3320
|
-
// Low concurrency for LLM extraction
|
|
3321
|
-
maxRetries: 1,
|
|
3322
|
-
initialDelayMs: 500
|
|
3323
|
-
};
|
|
3324
|
-
await BatchProcessor.processBatch(
|
|
3325
|
-
chunks,
|
|
3326
|
-
async (batch) => {
|
|
3327
|
-
for (const chunk of batch) {
|
|
3328
|
-
try {
|
|
3329
|
-
const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
|
|
3330
|
-
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
3331
|
-
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
3332
|
-
} catch (err) {
|
|
3333
|
-
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
},
|
|
3337
|
-
extractionOptions
|
|
3338
|
-
);
|
|
3568
|
+
await this.processGraphIngestion(doc.docId, chunks);
|
|
3339
3569
|
}
|
|
3340
3570
|
} catch (error) {
|
|
3341
3571
|
console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
|
|
@@ -3344,45 +3574,210 @@ var Pipeline = class {
|
|
|
3344
3574
|
}
|
|
3345
3575
|
return results;
|
|
3346
3576
|
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Step 1: Chunk the document content.
|
|
3579
|
+
*/
|
|
3580
|
+
async prepareChunks(doc) {
|
|
3581
|
+
var _a, _b;
|
|
3582
|
+
if (this.llamaIngestor) {
|
|
3583
|
+
return await this.llamaIngestor.chunk(doc.content, {
|
|
3584
|
+
docId: doc.docId,
|
|
3585
|
+
metadata: doc.metadata,
|
|
3586
|
+
chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
|
|
3587
|
+
chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
return this.chunker.chunk(doc.content, {
|
|
3591
|
+
docId: doc.docId,
|
|
3592
|
+
metadata: doc.metadata
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
/**
|
|
3596
|
+
* Step 2: Generate embeddings for chunks with retry logic.
|
|
3597
|
+
*/
|
|
3598
|
+
async processEmbeddings(chunks) {
|
|
3599
|
+
const embedBatchOptions = {
|
|
3600
|
+
batchSize: 50,
|
|
3601
|
+
maxRetries: 3,
|
|
3602
|
+
initialDelayMs: 100
|
|
3603
|
+
};
|
|
3604
|
+
const vectors = await BatchProcessor.mapWithRetry(
|
|
3605
|
+
chunks.map((c) => c.content),
|
|
3606
|
+
(text) => this.embeddingProvider.embed(text, { taskType: "document" }),
|
|
3607
|
+
embedBatchOptions
|
|
3608
|
+
);
|
|
3609
|
+
if (vectors.length !== chunks.length) {
|
|
3610
|
+
throw new Error(`Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks`);
|
|
3611
|
+
}
|
|
3612
|
+
return vectors;
|
|
3613
|
+
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Step 3: Upsert chunks to vector database with retry logic.
|
|
3616
|
+
*/
|
|
3617
|
+
async processUpserts(upsertDocs, namespace) {
|
|
3618
|
+
const upsertBatchOptions = {
|
|
3619
|
+
batchSize: 100,
|
|
3620
|
+
maxRetries: 3,
|
|
3621
|
+
initialDelayMs: 100
|
|
3622
|
+
};
|
|
3623
|
+
const upsertResult = await BatchProcessor.processBatch(
|
|
3624
|
+
upsertDocs,
|
|
3625
|
+
(batch) => this.vectorDB.batchUpsert(batch, namespace),
|
|
3626
|
+
upsertBatchOptions
|
|
3627
|
+
);
|
|
3628
|
+
if (upsertResult.errors.length > 0) {
|
|
3629
|
+
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
|
|
3630
|
+
}
|
|
3631
|
+
return upsertResult.totalProcessed;
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Step 4: Optional graph-based entity extraction and ingestion.
|
|
3635
|
+
*/
|
|
3636
|
+
async processGraphIngestion(docId, chunks) {
|
|
3637
|
+
console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
|
|
3638
|
+
const extractionOptions = {
|
|
3639
|
+
batchSize: 2,
|
|
3640
|
+
// Low concurrency for LLM extraction
|
|
3641
|
+
maxRetries: 1,
|
|
3642
|
+
initialDelayMs: 500
|
|
3643
|
+
};
|
|
3644
|
+
await BatchProcessor.processBatch(
|
|
3645
|
+
chunks,
|
|
3646
|
+
async (batch) => {
|
|
3647
|
+
for (const chunk of batch) {
|
|
3648
|
+
try {
|
|
3649
|
+
const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
|
|
3650
|
+
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
3651
|
+
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
3652
|
+
} catch (err) {
|
|
3653
|
+
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
},
|
|
3657
|
+
extractionOptions
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3347
3660
|
async ask(question, history = [], namespace) {
|
|
3348
|
-
var _a
|
|
3661
|
+
var _a;
|
|
3349
3662
|
await this.initialize();
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3663
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
|
|
3664
|
+
console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
|
|
3665
|
+
const agentReply = await this.agent.run(question, history);
|
|
3666
|
+
return { reply: agentReply, sources: [] };
|
|
3667
|
+
}
|
|
3668
|
+
const stream = this.askStream(question, history, namespace);
|
|
3669
|
+
let reply = "";
|
|
3670
|
+
let sources = [];
|
|
3671
|
+
let graphData;
|
|
3353
3672
|
try {
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3673
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
3674
|
+
const chunk = temp.value;
|
|
3675
|
+
if (typeof chunk === "string") {
|
|
3676
|
+
reply += chunk;
|
|
3677
|
+
} else if ("sources" in chunk) {
|
|
3678
|
+
sources = chunk.sources;
|
|
3679
|
+
graphData = chunk.graphData;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
} catch (temp) {
|
|
3683
|
+
error = [temp];
|
|
3684
|
+
} finally {
|
|
3685
|
+
try {
|
|
3686
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
3687
|
+
} finally {
|
|
3688
|
+
if (error)
|
|
3689
|
+
throw error[0];
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
return { reply, sources, graphData };
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* High-performance streaming RAG flow.
|
|
3696
|
+
* Yields text chunks first, then the retrieval metadata at the end.
|
|
3697
|
+
*/
|
|
3698
|
+
askStream(_0) {
|
|
3699
|
+
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
3700
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3701
|
+
yield new __await(this.initialize());
|
|
3702
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
3703
|
+
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
3704
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
3705
|
+
try {
|
|
3706
|
+
let searchQuery = question;
|
|
3707
|
+
if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
|
|
3708
|
+
searchQuery = yield new __await(this.rewriteQuery(question, history));
|
|
3709
|
+
}
|
|
3710
|
+
const hints = QueryProcessor.extractQueryFieldHints(question);
|
|
3711
|
+
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
3712
|
+
const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
|
|
3713
|
+
namespace: ns,
|
|
3714
|
+
topK: topK * 2,
|
|
3715
|
+
filter
|
|
3716
|
+
}));
|
|
3717
|
+
let sources = rawSources.filter((m) => m.score >= scoreThreshold);
|
|
3718
|
+
if ((_f = this.config.rag) == null ? void 0 : _f.useReranking) {
|
|
3719
|
+
sources = yield new __await(this.reranker.rerank(sources, question, topK));
|
|
3720
|
+
} else {
|
|
3721
|
+
sources = sources.slice(0, topK);
|
|
3722
|
+
}
|
|
3723
|
+
let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
3369
3724
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3725
|
+
if (graphData && graphData.nodes.length > 0) {
|
|
3726
|
+
const graphContext = graphData.nodes.map(
|
|
3727
|
+
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
3728
|
+
).join("\n");
|
|
3729
|
+
context = `GRAPH KNOWLEDGE:
|
|
3375
3730
|
${graphContext}
|
|
3376
3731
|
|
|
3377
3732
|
VECTOR CONTEXT:
|
|
3378
3733
|
${context}`;
|
|
3734
|
+
}
|
|
3735
|
+
const messages = [...history, { role: "user", content: question }];
|
|
3736
|
+
if (this.llmProvider.chatStream) {
|
|
3737
|
+
try {
|
|
3738
|
+
for (var iter = __forAwait(this.llmProvider.chatStream(messages, context)), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
3739
|
+
const chunk = temp.value;
|
|
3740
|
+
yield chunk;
|
|
3741
|
+
}
|
|
3742
|
+
} catch (temp) {
|
|
3743
|
+
error = [temp];
|
|
3744
|
+
} finally {
|
|
3745
|
+
try {
|
|
3746
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
3747
|
+
} finally {
|
|
3748
|
+
if (error)
|
|
3749
|
+
throw error[0];
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
} else {
|
|
3753
|
+
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
3754
|
+
yield reply;
|
|
3755
|
+
}
|
|
3756
|
+
yield { reply: "", sources, graphData };
|
|
3757
|
+
} catch (error2) {
|
|
3758
|
+
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3379
3759
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Universal retrieval method combining all enabled providers.
|
|
3764
|
+
*/
|
|
3765
|
+
async retrieve(query, options) {
|
|
3766
|
+
var _a, _b, _c;
|
|
3767
|
+
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
3768
|
+
const topK = (_b = options.topK) != null ? _b : 5;
|
|
3769
|
+
const cacheKey = `${ns}::${query}`;
|
|
3770
|
+
let queryVector = this.embeddingCache.get(cacheKey);
|
|
3771
|
+
const [retrievedVector, graphData] = await Promise.all([
|
|
3772
|
+
queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
|
|
3773
|
+
this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
3774
|
+
]);
|
|
3775
|
+
if (!queryVector) {
|
|
3776
|
+
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
3777
|
+
queryVector = retrievedVector;
|
|
3385
3778
|
}
|
|
3779
|
+
const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
|
|
3780
|
+
return { sources, graphData };
|
|
3386
3781
|
}
|
|
3387
3782
|
/**
|
|
3388
3783
|
* Rewrite the user query for better retrieval performance.
|
|
@@ -3413,11 +3808,11 @@ var ProviderHealthCheck = class {
|
|
|
3413
3808
|
*/
|
|
3414
3809
|
static async checkVectorProvider(config) {
|
|
3415
3810
|
const timestamp = Date.now();
|
|
3416
|
-
const configErrors = ConfigValidator.validate({
|
|
3811
|
+
const configErrors = await ConfigValidator.validate({
|
|
3417
3812
|
projectId: "health-check",
|
|
3418
3813
|
vectorDb: config,
|
|
3419
|
-
llm: { provider: "openai", model: "gpt-4o" },
|
|
3420
|
-
embedding: { provider: "openai", model: "text-embedding-3-small" }
|
|
3814
|
+
llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
|
|
3815
|
+
embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
|
|
3421
3816
|
});
|
|
3422
3817
|
const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
|
|
3423
3818
|
if (vectorDbErrors.length > 0) {
|
|
@@ -3429,35 +3824,11 @@ var ProviderHealthCheck = class {
|
|
|
3429
3824
|
};
|
|
3430
3825
|
}
|
|
3431
3826
|
try {
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
case "pgvector":
|
|
3436
|
-
case "postgresql":
|
|
3437
|
-
return await this.checkPostgres(config, timestamp);
|
|
3438
|
-
case "mongodb":
|
|
3439
|
-
return await this.checkMongoDB(config, timestamp);
|
|
3440
|
-
case "milvus":
|
|
3441
|
-
return await this.checkMilvus(config, timestamp);
|
|
3442
|
-
case "qdrant":
|
|
3443
|
-
return await this.checkQdrant(config, timestamp);
|
|
3444
|
-
case "chromadb":
|
|
3445
|
-
return await this.checkChromaDB(config, timestamp);
|
|
3446
|
-
case "redis":
|
|
3447
|
-
return await this.checkRedis(config, timestamp);
|
|
3448
|
-
case "weaviate":
|
|
3449
|
-
return await this.checkWeaviate(config, timestamp);
|
|
3450
|
-
case "rest":
|
|
3451
|
-
case "universal_rest":
|
|
3452
|
-
return await this.checkRestAPI(config, timestamp);
|
|
3453
|
-
default:
|
|
3454
|
-
return {
|
|
3455
|
-
healthy: false,
|
|
3456
|
-
provider: config.provider,
|
|
3457
|
-
error: `Unsupported provider: ${config.provider}`,
|
|
3458
|
-
timestamp
|
|
3459
|
-
};
|
|
3827
|
+
const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
|
|
3828
|
+
if (checker) {
|
|
3829
|
+
return await checker.check(config);
|
|
3460
3830
|
}
|
|
3831
|
+
return await this.fallbackVectorHealthCheck(config, timestamp);
|
|
3461
3832
|
} catch (error) {
|
|
3462
3833
|
return {
|
|
3463
3834
|
healthy: false,
|
|
@@ -3467,363 +3838,50 @@ var ProviderHealthCheck = class {
|
|
|
3467
3838
|
};
|
|
3468
3839
|
}
|
|
3469
3840
|
}
|
|
3470
|
-
static async
|
|
3471
|
-
|
|
3472
|
-
const
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
const indexes = await client.listIndexes();
|
|
3477
|
-
const indexNames = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
|
|
3478
|
-
if (!indexNames.includes(config.indexName)) {
|
|
3841
|
+
static async fallbackVectorHealthCheck(config, timestamp) {
|
|
3842
|
+
const opts = config.options || {};
|
|
3843
|
+
const baseUrl = opts.baseUrl || opts.uri;
|
|
3844
|
+
if (baseUrl && baseUrl.startsWith("http")) {
|
|
3845
|
+
try {
|
|
3846
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
|
|
3479
3847
|
return {
|
|
3480
|
-
healthy:
|
|
3481
|
-
provider:
|
|
3482
|
-
error:
|
|
3848
|
+
healthy: response.ok,
|
|
3849
|
+
provider: config.provider,
|
|
3850
|
+
error: response.ok ? void 0 : `Health check failed: ${response.status}`,
|
|
3483
3851
|
timestamp
|
|
3484
3852
|
};
|
|
3485
|
-
}
|
|
3486
|
-
return {
|
|
3487
|
-
healthy: true,
|
|
3488
|
-
provider: "pinecone",
|
|
3489
|
-
capabilities: { indexes: indexNames.length, targetIndex: config.indexName },
|
|
3490
|
-
timestamp
|
|
3491
|
-
};
|
|
3492
|
-
} catch (error) {
|
|
3493
|
-
return {
|
|
3494
|
-
healthy: false,
|
|
3495
|
-
provider: "pinecone",
|
|
3496
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3497
|
-
timestamp
|
|
3498
|
-
};
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3501
|
-
static async checkPostgres(config, timestamp) {
|
|
3502
|
-
const opts = config.options;
|
|
3503
|
-
try {
|
|
3504
|
-
const { Client } = await import("pg");
|
|
3505
|
-
const client = new Client({ connectionString: opts.connectionString });
|
|
3506
|
-
await client.connect();
|
|
3507
|
-
const result = await client.query(`
|
|
3508
|
-
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
3509
|
-
`);
|
|
3510
|
-
const hasVector = result.rows[0].exists;
|
|
3511
|
-
await client.end();
|
|
3512
|
-
return {
|
|
3513
|
-
healthy: true,
|
|
3514
|
-
provider: "postgresql",
|
|
3515
|
-
capabilities: { pgvectorInstalled: hasVector },
|
|
3516
|
-
timestamp
|
|
3517
|
-
};
|
|
3518
|
-
} catch (error) {
|
|
3519
|
-
return {
|
|
3520
|
-
healthy: false,
|
|
3521
|
-
provider: "postgresql",
|
|
3522
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3523
|
-
timestamp
|
|
3524
|
-
};
|
|
3525
|
-
}
|
|
3526
|
-
}
|
|
3527
|
-
static async checkMongoDB(config, timestamp) {
|
|
3528
|
-
const opts = config.options;
|
|
3529
|
-
try {
|
|
3530
|
-
const { MongoClient: MongoClient2 } = await import("mongodb");
|
|
3531
|
-
const client = new MongoClient2(opts.uri);
|
|
3532
|
-
await client.connect();
|
|
3533
|
-
const db = client.db(opts.database);
|
|
3534
|
-
const collections = await db.listCollections().toArray();
|
|
3535
|
-
const collectionNames = collections.map((c) => c.name);
|
|
3536
|
-
const hasCollection = collectionNames.includes(opts.collection);
|
|
3537
|
-
await client.close();
|
|
3538
|
-
return {
|
|
3539
|
-
healthy: true,
|
|
3540
|
-
provider: "mongodb",
|
|
3541
|
-
capabilities: {
|
|
3542
|
-
collections: collectionNames.length,
|
|
3543
|
-
targetCollection: hasCollection ? opts.collection : "NOT FOUND (will be created on first insert)"
|
|
3544
|
-
},
|
|
3545
|
-
timestamp
|
|
3546
|
-
};
|
|
3547
|
-
} catch (error) {
|
|
3548
|
-
return {
|
|
3549
|
-
healthy: false,
|
|
3550
|
-
provider: "mongodb",
|
|
3551
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3552
|
-
timestamp
|
|
3553
|
-
};
|
|
3554
|
-
}
|
|
3555
|
-
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Milvus health check — uses baseUrl/uri (matching MilvusProvider constructor).
|
|
3558
|
-
*/
|
|
3559
|
-
static async checkMilvus(config, timestamp) {
|
|
3560
|
-
const opts = config.options;
|
|
3561
|
-
const baseUrl = opts.baseUrl || opts.uri || (opts.host ? `http://${opts.host}:${opts.port || 19530}` : "http://localhost:19530");
|
|
3562
|
-
try {
|
|
3563
|
-
const controller = new AbortController();
|
|
3564
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
3565
|
-
const response = await fetch(`${baseUrl}/v1/health`, { signal: controller.signal });
|
|
3566
|
-
clearTimeout(timeoutId);
|
|
3567
|
-
if (!response.ok) {
|
|
3568
|
-
throw new Error(`Health check returned ${response.status}`);
|
|
3569
|
-
}
|
|
3570
|
-
return {
|
|
3571
|
-
healthy: true,
|
|
3572
|
-
provider: "milvus",
|
|
3573
|
-
capabilities: { endpoint: baseUrl },
|
|
3574
|
-
timestamp
|
|
3575
|
-
};
|
|
3576
|
-
} catch (error) {
|
|
3577
|
-
return {
|
|
3578
|
-
healthy: false,
|
|
3579
|
-
provider: "milvus",
|
|
3580
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3581
|
-
timestamp
|
|
3582
|
-
};
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
/**
|
|
3586
|
-
* Qdrant health check — uses baseUrl (matching QdrantProvider constructor).
|
|
3587
|
-
*/
|
|
3588
|
-
static async checkQdrant(config, timestamp) {
|
|
3589
|
-
const opts = config.options;
|
|
3590
|
-
const baseUrl = (opts.baseUrl || opts.url || "http://localhost:6333").replace(/\/$/, "");
|
|
3591
|
-
try {
|
|
3592
|
-
const apiKey = opts.apiKey;
|
|
3593
|
-
const response = await fetch(`${baseUrl}/`, {
|
|
3594
|
-
headers: apiKey ? { "api-key": apiKey } : {}
|
|
3595
|
-
});
|
|
3596
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3597
|
-
const health = await response.json();
|
|
3598
|
-
return {
|
|
3599
|
-
healthy: true,
|
|
3600
|
-
provider: "qdrant",
|
|
3601
|
-
capabilities: health,
|
|
3602
|
-
timestamp
|
|
3603
|
-
};
|
|
3604
|
-
} catch (error) {
|
|
3605
|
-
return {
|
|
3606
|
-
healthy: false,
|
|
3607
|
-
provider: "qdrant",
|
|
3608
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3609
|
-
timestamp
|
|
3610
|
-
};
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
/**
|
|
3614
|
-
* ChromaDB health check — uses baseUrl/host (matching ChromaDBProvider constructor).
|
|
3615
|
-
*/
|
|
3616
|
-
static async checkChromaDB(config, timestamp) {
|
|
3617
|
-
const opts = config.options;
|
|
3618
|
-
const baseUrl = opts.baseUrl || (opts.host ? `http://${opts.host}:${opts.port || 8e3}` : "http://localhost:8000");
|
|
3619
|
-
try {
|
|
3620
|
-
const response = await fetch(`${baseUrl}/api/v1/heartbeat`);
|
|
3621
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3622
|
-
return {
|
|
3623
|
-
healthy: true,
|
|
3624
|
-
provider: "chromadb",
|
|
3625
|
-
capabilities: { endpoint: baseUrl },
|
|
3626
|
-
timestamp
|
|
3627
|
-
};
|
|
3628
|
-
} catch (error) {
|
|
3629
|
-
return {
|
|
3630
|
-
healthy: false,
|
|
3631
|
-
provider: "chromadb",
|
|
3632
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3633
|
-
timestamp
|
|
3634
|
-
};
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
/**
|
|
3638
|
-
* Redis health check — Redis is TCP-only (no HTTP endpoint).
|
|
3639
|
-
* We report healthy=true with a note; actual connectivity is validated at first operation.
|
|
3640
|
-
*/
|
|
3641
|
-
static async checkRedis(config, timestamp) {
|
|
3642
|
-
const opts = config.options;
|
|
3643
|
-
const url = opts.baseUrl || opts.url || (opts.host && opts.port ? `redis://${opts.host}:${opts.port}` : "redis://localhost:6379");
|
|
3644
|
-
if (url.startsWith("http")) {
|
|
3645
|
-
try {
|
|
3646
|
-
await fetch(url);
|
|
3647
|
-
return { healthy: true, provider: "redis", capabilities: { endpoint: url }, timestamp };
|
|
3648
3853
|
} catch (e) {
|
|
3854
|
+
return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
|
|
3649
3855
|
}
|
|
3650
3856
|
}
|
|
3651
3857
|
return {
|
|
3652
3858
|
healthy: true,
|
|
3653
|
-
provider:
|
|
3654
|
-
|
|
3655
|
-
endpoint: url,
|
|
3656
|
-
note: "Redis uses TCP; connectivity is validated on first operation."
|
|
3657
|
-
},
|
|
3859
|
+
provider: config.provider,
|
|
3860
|
+
error: "Pluggable health check not implemented; basic reachability assumed.",
|
|
3658
3861
|
timestamp
|
|
3659
3862
|
};
|
|
3660
3863
|
}
|
|
3661
|
-
/**
|
|
3662
|
-
* Weaviate health check — uses baseUrl/url (matching WeaviateProvider constructor).
|
|
3663
|
-
*/
|
|
3664
|
-
static async checkWeaviate(config, timestamp) {
|
|
3665
|
-
const opts = config.options;
|
|
3666
|
-
const baseUrl = (opts.baseUrl || opts.url || "http://localhost:8080").replace(/\/$/, "");
|
|
3667
|
-
try {
|
|
3668
|
-
const response = await fetch(`${baseUrl}/v1/.well-known/ready`);
|
|
3669
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3670
|
-
return {
|
|
3671
|
-
healthy: true,
|
|
3672
|
-
provider: "weaviate",
|
|
3673
|
-
capabilities: { endpoint: baseUrl },
|
|
3674
|
-
timestamp
|
|
3675
|
-
};
|
|
3676
|
-
} catch (error) {
|
|
3677
|
-
return {
|
|
3678
|
-
healthy: false,
|
|
3679
|
-
provider: "weaviate",
|
|
3680
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3681
|
-
timestamp
|
|
3682
|
-
};
|
|
3683
|
-
}
|
|
3684
|
-
}
|
|
3685
|
-
static async checkRestAPI(config, timestamp) {
|
|
3686
|
-
const opts = config.options;
|
|
3687
|
-
const baseUrl = (opts.baseUrl || "").replace(/\/$/, "");
|
|
3688
|
-
if (!baseUrl) {
|
|
3689
|
-
return { healthy: false, provider: "rest", error: "baseUrl is required", timestamp };
|
|
3690
|
-
}
|
|
3691
|
-
try {
|
|
3692
|
-
const response = await fetch(`${baseUrl}/health`, {
|
|
3693
|
-
headers: opts.headers ? JSON.parse(opts.headers) : {}
|
|
3694
|
-
});
|
|
3695
|
-
return {
|
|
3696
|
-
healthy: response.ok,
|
|
3697
|
-
provider: "rest",
|
|
3698
|
-
error: response.ok ? void 0 : `Health check returned ${response.status}`,
|
|
3699
|
-
timestamp
|
|
3700
|
-
};
|
|
3701
|
-
} catch (error) {
|
|
3702
|
-
return {
|
|
3703
|
-
healthy: false,
|
|
3704
|
-
provider: "rest",
|
|
3705
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3706
|
-
timestamp
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
3864
|
/**
|
|
3711
3865
|
* Validates LLM provider configuration.
|
|
3712
3866
|
*/
|
|
3713
3867
|
static async checkLLMProvider(config) {
|
|
3714
3868
|
const timestamp = Date.now();
|
|
3715
3869
|
try {
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
case "anthropic":
|
|
3720
|
-
return await this.checkAnthropic(config, timestamp);
|
|
3721
|
-
case "ollama":
|
|
3722
|
-
return await this.checkOllama(config, timestamp);
|
|
3723
|
-
case "rest":
|
|
3724
|
-
case "universal_rest":
|
|
3725
|
-
return await this.checkLLMRestAPI(config, timestamp);
|
|
3726
|
-
default:
|
|
3727
|
-
return {
|
|
3728
|
-
healthy: false,
|
|
3729
|
-
provider: config.provider,
|
|
3730
|
-
error: `Unsupported provider: ${config.provider}`,
|
|
3731
|
-
timestamp
|
|
3732
|
-
};
|
|
3870
|
+
const checker = LLMFactory.getHealthChecker(config.provider);
|
|
3871
|
+
if (checker) {
|
|
3872
|
+
return await checker.check(config);
|
|
3733
3873
|
}
|
|
3734
|
-
} catch (error) {
|
|
3735
|
-
return {
|
|
3736
|
-
healthy: false,
|
|
3737
|
-
provider: config.provider,
|
|
3738
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3739
|
-
timestamp
|
|
3740
|
-
};
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
static async checkOpenAI(config, timestamp) {
|
|
3744
|
-
try {
|
|
3745
|
-
const OpenAI2 = await import("openai");
|
|
3746
|
-
const client = new OpenAI2.default({ apiKey: config.apiKey });
|
|
3747
|
-
const models = await client.models.list();
|
|
3748
|
-
const hasModel = models.data.some((m) => m.id === config.model);
|
|
3749
|
-
return {
|
|
3750
|
-
healthy: true,
|
|
3751
|
-
provider: "openai",
|
|
3752
|
-
capabilities: { model: config.model, available: hasModel, totalModels: models.data.length },
|
|
3753
|
-
timestamp
|
|
3754
|
-
};
|
|
3755
|
-
} catch (error) {
|
|
3756
|
-
return {
|
|
3757
|
-
healthy: false,
|
|
3758
|
-
provider: "openai",
|
|
3759
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3760
|
-
timestamp
|
|
3761
|
-
};
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
static async checkAnthropic(config, timestamp) {
|
|
3765
|
-
try {
|
|
3766
|
-
const { default: Anthropic2 } = await import("@anthropic-ai/sdk");
|
|
3767
|
-
const client = new Anthropic2({ apiKey: config.apiKey });
|
|
3768
|
-
await client.messages.create({
|
|
3769
|
-
model: config.model,
|
|
3770
|
-
max_tokens: 10,
|
|
3771
|
-
messages: [{ role: "user", content: "ping" }]
|
|
3772
|
-
});
|
|
3773
|
-
return { healthy: true, provider: "anthropic", capabilities: { model: config.model }, timestamp };
|
|
3774
|
-
} catch (error) {
|
|
3775
|
-
return {
|
|
3776
|
-
healthy: false,
|
|
3777
|
-
provider: "anthropic",
|
|
3778
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3779
|
-
timestamp
|
|
3780
|
-
};
|
|
3781
|
-
}
|
|
3782
|
-
}
|
|
3783
|
-
static async checkOllama(config, timestamp) {
|
|
3784
|
-
const baseUrl = (config.baseUrl || "http://localhost:11434").replace(/\/$/, "");
|
|
3785
|
-
try {
|
|
3786
|
-
const response = await fetch(`${baseUrl}/api/tags`);
|
|
3787
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3788
|
-
const data = await response.json();
|
|
3789
|
-
const models = data.models || [];
|
|
3790
|
-
const hasModel = models.some((m) => m.name === config.model);
|
|
3791
3874
|
return {
|
|
3792
3875
|
healthy: true,
|
|
3793
|
-
provider: "ollama",
|
|
3794
|
-
capabilities: { model: config.model, available: hasModel, totalModels: models.length },
|
|
3795
|
-
timestamp
|
|
3796
|
-
};
|
|
3797
|
-
} catch (error) {
|
|
3798
|
-
return {
|
|
3799
|
-
healthy: false,
|
|
3800
|
-
provider: "ollama",
|
|
3801
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3802
|
-
timestamp
|
|
3803
|
-
};
|
|
3804
|
-
}
|
|
3805
|
-
}
|
|
3806
|
-
static async checkLLMRestAPI(config, timestamp) {
|
|
3807
|
-
var _a, _b;
|
|
3808
|
-
const baseUrlRaw = config.baseUrl || ((_a = config.options) == null ? void 0 : _a.baseUrl);
|
|
3809
|
-
const baseUrl = (typeof baseUrlRaw === "string" ? baseUrlRaw : "").replace(/\/$/, "");
|
|
3810
|
-
if (!baseUrl) {
|
|
3811
|
-
return { healthy: false, provider: config.provider, error: "baseUrl is required", timestamp };
|
|
3812
|
-
}
|
|
3813
|
-
try {
|
|
3814
|
-
const headers = (_b = config.options) == null ? void 0 : _b.headers;
|
|
3815
|
-
const response = await fetch(`${baseUrl}/health`, { headers: headers || void 0 });
|
|
3816
|
-
return {
|
|
3817
|
-
healthy: response.ok,
|
|
3818
3876
|
provider: config.provider,
|
|
3819
|
-
error:
|
|
3877
|
+
error: "Pluggable health check not implemented; basic reachability assumed.",
|
|
3820
3878
|
timestamp
|
|
3821
3879
|
};
|
|
3822
3880
|
} catch (error) {
|
|
3823
3881
|
return {
|
|
3824
3882
|
healthy: false,
|
|
3825
3883
|
provider: config.provider,
|
|
3826
|
-
error:
|
|
3884
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3827
3885
|
timestamp
|
|
3828
3886
|
};
|
|
3829
3887
|
}
|
|
@@ -3898,6 +3956,14 @@ var VectorPlugin = class {
|
|
|
3898
3956
|
async chat(message, history = [], namespace) {
|
|
3899
3957
|
return this.pipeline.ask(message, history, namespace);
|
|
3900
3958
|
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Run a streaming chat query.
|
|
3961
|
+
*/
|
|
3962
|
+
chatStream(_0) {
|
|
3963
|
+
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
3964
|
+
yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3901
3967
|
/**
|
|
3902
3968
|
* Ingest documents into the vector database.
|
|
3903
3969
|
*/
|