@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/handlers/index.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 = {}))
|
|
@@ -46,6 +50,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
46
50
|
mod
|
|
47
51
|
));
|
|
48
52
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
53
|
+
var __await = function(promise, isYieldStar) {
|
|
54
|
+
this[0] = promise;
|
|
55
|
+
this[1] = isYieldStar;
|
|
56
|
+
};
|
|
57
|
+
var __asyncGenerator = (__this, __arguments, generator) => {
|
|
58
|
+
var resume = (k, v, yes, no) => {
|
|
59
|
+
try {
|
|
60
|
+
var x = generator[k](v), isAwait = (v = x.value) instanceof __await, done = x.done;
|
|
61
|
+
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));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
no(e);
|
|
64
|
+
}
|
|
65
|
+
}, 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 = {};
|
|
66
|
+
return generator = generator.apply(__this, __arguments), it[__knownSymbol("asyncIterator")] = () => it, method("next"), method("throw"), method("return"), it;
|
|
67
|
+
};
|
|
68
|
+
var __yieldStar = (value) => {
|
|
69
|
+
var obj = value[__knownSymbol("asyncIterator")], isAwait = false, method, it = {};
|
|
70
|
+
if (obj == null) {
|
|
71
|
+
obj = value[__knownSymbol("iterator")]();
|
|
72
|
+
method = (k) => it[k] = (x) => obj[k](x);
|
|
73
|
+
} else {
|
|
74
|
+
obj = obj.call(value);
|
|
75
|
+
method = (k) => it[k] = (v) => {
|
|
76
|
+
if (isAwait) {
|
|
77
|
+
isAwait = false;
|
|
78
|
+
if (k === "throw") throw v;
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
isAwait = true;
|
|
82
|
+
return {
|
|
83
|
+
done: false,
|
|
84
|
+
value: new __await(new Promise((resolve) => {
|
|
85
|
+
var x = obj[k](v);
|
|
86
|
+
if (!(x instanceof Object)) __typeError("Object expected");
|
|
87
|
+
resolve(x);
|
|
88
|
+
}), 1)
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return it[__knownSymbol("iterator")] = () => it, method("next"), "throw" in obj ? method("throw") : it.throw = (x) => {
|
|
93
|
+
throw x;
|
|
94
|
+
}, "return" in obj && method("return"), it;
|
|
95
|
+
};
|
|
96
|
+
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);
|
|
49
97
|
|
|
50
98
|
// src/utils/templateUtils.ts
|
|
51
99
|
function isRecord(value) {
|
|
@@ -143,6 +191,60 @@ var init_PineconeProvider = __esm({
|
|
|
143
191
|
if (!opts.apiKey) throw new Error("[PineconeProvider] options.apiKey is required");
|
|
144
192
|
this.apiKey = opts.apiKey;
|
|
145
193
|
}
|
|
194
|
+
static getValidator() {
|
|
195
|
+
return {
|
|
196
|
+
validate(config) {
|
|
197
|
+
const errors = [];
|
|
198
|
+
const opts = config.options || {};
|
|
199
|
+
if (!opts.apiKey) {
|
|
200
|
+
errors.push({
|
|
201
|
+
field: "vectorDb.options.apiKey",
|
|
202
|
+
message: "Pinecone API key is required",
|
|
203
|
+
suggestion: "Set PINECONE_API_KEY environment variable",
|
|
204
|
+
severity: "error"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return errors;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
static getHealthChecker() {
|
|
212
|
+
return {
|
|
213
|
+
async check(config) {
|
|
214
|
+
var _a, _b;
|
|
215
|
+
const opts = config.options || {};
|
|
216
|
+
const indexName = config.indexName;
|
|
217
|
+
const timestamp = Date.now();
|
|
218
|
+
try {
|
|
219
|
+
const { Pinecone: Pinecone2 } = await import("@pinecone-database/pinecone");
|
|
220
|
+
const client = new Pinecone2({ apiKey: opts.apiKey });
|
|
221
|
+
const indexes = await client.listIndexes();
|
|
222
|
+
const indexNames = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
|
|
223
|
+
if (!indexNames.includes(indexName)) {
|
|
224
|
+
return {
|
|
225
|
+
healthy: false,
|
|
226
|
+
provider: "pinecone",
|
|
227
|
+
error: `Index "${indexName}" not found. Available: ${indexNames.join(", ")}`,
|
|
228
|
+
timestamp
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
healthy: true,
|
|
233
|
+
provider: "pinecone",
|
|
234
|
+
capabilities: { indexes: indexNames.length, targetIndex: indexName },
|
|
235
|
+
timestamp
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return {
|
|
239
|
+
healthy: false,
|
|
240
|
+
provider: "pinecone",
|
|
241
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
242
|
+
timestamp
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
146
248
|
async initialize() {
|
|
147
249
|
var _a, _b;
|
|
148
250
|
this.client = new import_pinecone.Pinecone({ apiKey: this.apiKey });
|
|
@@ -241,6 +343,61 @@ var init_PostgreSQLProvider = __esm({
|
|
|
241
343
|
this.connectionString = opts.connectionString;
|
|
242
344
|
this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
|
|
243
345
|
}
|
|
346
|
+
static getValidator() {
|
|
347
|
+
return {
|
|
348
|
+
validate(config) {
|
|
349
|
+
const errors = [];
|
|
350
|
+
const opts = config.options || {};
|
|
351
|
+
if (!opts.connectionString) {
|
|
352
|
+
errors.push({
|
|
353
|
+
field: "vectorDb.options.connectionString",
|
|
354
|
+
message: "PostgreSQL connection string is required",
|
|
355
|
+
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
356
|
+
severity: "error"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
360
|
+
errors.push({
|
|
361
|
+
field: "vectorDb.options.tables",
|
|
362
|
+
message: "PostgreSQL tables must be a string or a string array",
|
|
363
|
+
severity: "error"
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return errors;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
static getHealthChecker() {
|
|
371
|
+
return {
|
|
372
|
+
async check(config) {
|
|
373
|
+
const opts = config.options || {};
|
|
374
|
+
const timestamp = Date.now();
|
|
375
|
+
try {
|
|
376
|
+
const { Client } = await import("pg");
|
|
377
|
+
const client = new Client({ connectionString: opts.connectionString });
|
|
378
|
+
await client.connect();
|
|
379
|
+
const result = await client.query(`
|
|
380
|
+
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
381
|
+
`);
|
|
382
|
+
const hasVector = result.rows[0].exists;
|
|
383
|
+
await client.end();
|
|
384
|
+
return {
|
|
385
|
+
healthy: true,
|
|
386
|
+
provider: "postgresql",
|
|
387
|
+
capabilities: { pgvectorInstalled: hasVector },
|
|
388
|
+
timestamp
|
|
389
|
+
};
|
|
390
|
+
} catch (error) {
|
|
391
|
+
return {
|
|
392
|
+
healthy: false,
|
|
393
|
+
provider: "postgresql",
|
|
394
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
395
|
+
timestamp
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
244
401
|
async initialize() {
|
|
245
402
|
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
246
403
|
const client = await this.pool.connect();
|
|
@@ -326,20 +483,27 @@ var init_PostgreSQLProvider = __esm({
|
|
|
326
483
|
}).join(" AND ");
|
|
327
484
|
whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
|
|
328
485
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
486
|
+
const client = await this.pool.connect();
|
|
487
|
+
try {
|
|
488
|
+
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
489
|
+
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
490
|
+
const result = await client.query(
|
|
491
|
+
`SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
|
|
492
|
+
FROM ${this.tableName}
|
|
493
|
+
${whereClause}
|
|
494
|
+
ORDER BY embedding <=> $1::vector
|
|
495
|
+
LIMIT $2`,
|
|
496
|
+
params
|
|
497
|
+
);
|
|
498
|
+
return result.rows.map((row) => ({
|
|
499
|
+
id: String(row["id"]),
|
|
500
|
+
score: parseFloat(String(row["score"])),
|
|
501
|
+
content: String(row["content"]),
|
|
502
|
+
metadata: row["metadata"]
|
|
503
|
+
}));
|
|
504
|
+
} finally {
|
|
505
|
+
client.release();
|
|
506
|
+
}
|
|
343
507
|
}
|
|
344
508
|
async delete(id, namespace) {
|
|
345
509
|
const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
|
|
@@ -389,6 +553,71 @@ var init_MongoDBProvider = __esm({
|
|
|
389
553
|
this.contentKey = opts.contentKey || "content";
|
|
390
554
|
this.metadataKey = opts.metadataKey || "metadata";
|
|
391
555
|
}
|
|
556
|
+
static getValidator() {
|
|
557
|
+
return {
|
|
558
|
+
validate(config) {
|
|
559
|
+
const errors = [];
|
|
560
|
+
const opts = config.options || {};
|
|
561
|
+
if (!opts.uri) {
|
|
562
|
+
errors.push({
|
|
563
|
+
field: "vectorDb.options.uri",
|
|
564
|
+
message: "MongoDB connection URI is required",
|
|
565
|
+
suggestion: "Set MONGODB_URI environment variable",
|
|
566
|
+
severity: "error"
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (!opts.database) {
|
|
570
|
+
errors.push({
|
|
571
|
+
field: "vectorDb.options.database",
|
|
572
|
+
message: "MongoDB database name is required",
|
|
573
|
+
severity: "error"
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (!opts.collection) {
|
|
577
|
+
errors.push({
|
|
578
|
+
field: "vectorDb.options.collection",
|
|
579
|
+
message: "MongoDB collection name is required",
|
|
580
|
+
severity: "error"
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return errors;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
static getHealthChecker() {
|
|
588
|
+
return {
|
|
589
|
+
async check(config) {
|
|
590
|
+
const opts = config.options || {};
|
|
591
|
+
const timestamp = Date.now();
|
|
592
|
+
try {
|
|
593
|
+
const { MongoClient: MongoClient2 } = await import("mongodb");
|
|
594
|
+
const client = new MongoClient2(opts.uri);
|
|
595
|
+
await client.connect();
|
|
596
|
+
const db = client.db(opts.database);
|
|
597
|
+
await db.command({ ping: 1 });
|
|
598
|
+
const collections = await db.listCollections({ name: opts.collection }).toArray();
|
|
599
|
+
await client.close();
|
|
600
|
+
return {
|
|
601
|
+
healthy: true,
|
|
602
|
+
provider: "mongodb",
|
|
603
|
+
capabilities: {
|
|
604
|
+
database: opts.database,
|
|
605
|
+
collection: opts.collection,
|
|
606
|
+
exists: collections.length > 0
|
|
607
|
+
},
|
|
608
|
+
timestamp
|
|
609
|
+
};
|
|
610
|
+
} catch (error) {
|
|
611
|
+
return {
|
|
612
|
+
healthy: false,
|
|
613
|
+
provider: "mongodb",
|
|
614
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
615
|
+
timestamp
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
392
621
|
async initialize() {
|
|
393
622
|
await this.client.connect();
|
|
394
623
|
this.db = this.client.db(this.dbName);
|
|
@@ -427,7 +656,7 @@ var init_MongoDBProvider = __esm({
|
|
|
427
656
|
index: this.config.indexName || "vector_index",
|
|
428
657
|
path: this.embeddingKey,
|
|
429
658
|
queryVector: vector,
|
|
430
|
-
numCandidates: Math.max(topK *
|
|
659
|
+
numCandidates: this.config.options.numCandidates || Math.max(topK * 20, 200),
|
|
431
660
|
limit: topK
|
|
432
661
|
}, Object.keys(publicFilter).length > 0 || namespace ? { filter: __spreadValues(__spreadValues({}, publicFilter), namespace ? { namespace } : {}) } : {})
|
|
433
662
|
},
|
|
@@ -455,10 +684,6 @@ var init_MongoDBProvider = __esm({
|
|
|
455
684
|
async deleteNamespace(namespace) {
|
|
456
685
|
await this.collection.deleteMany({ namespace });
|
|
457
686
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Sanitise and flatten filter for MongoDB.
|
|
460
|
-
* Strips internal engine fields and keywords.
|
|
461
|
-
*/
|
|
462
687
|
sanitizeFilter(filter) {
|
|
463
688
|
const sanitized = super.sanitizeFilter(filter);
|
|
464
689
|
const mongoFilter = {};
|
|
@@ -543,7 +768,11 @@ var init_MilvusProvider = __esm({
|
|
|
543
768
|
vector,
|
|
544
769
|
limit: topK,
|
|
545
770
|
filter: namespace ? `namespace == "${namespace}"` : void 0,
|
|
546
|
-
outputFields: ["content", "metadata"]
|
|
771
|
+
outputFields: ["content", "metadata"],
|
|
772
|
+
searchParams: {
|
|
773
|
+
nprobe: this.config.options.nprobe || 16,
|
|
774
|
+
ef: this.config.options.efSearch || Math.max(topK * 10, 64)
|
|
775
|
+
}
|
|
547
776
|
};
|
|
548
777
|
const { data } = await this.http.post("/v1/vector/search", payload);
|
|
549
778
|
return (data.data || []).map((res) => ({
|
|
@@ -683,6 +912,10 @@ var init_QdrantProvider = __esm({
|
|
|
683
912
|
vector,
|
|
684
913
|
limit: topK,
|
|
685
914
|
with_payload: true,
|
|
915
|
+
params: {
|
|
916
|
+
hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
|
|
917
|
+
exact: false
|
|
918
|
+
},
|
|
686
919
|
filter: namespace ? {
|
|
687
920
|
must: [{ key: "namespace", match: { value: namespace } }]
|
|
688
921
|
} : void 0
|
|
@@ -978,17 +1211,17 @@ var init_WeaviateProvider = __esm({
|
|
|
978
1211
|
};
|
|
979
1212
|
await this.http.post("/v1/batch/objects", payload);
|
|
980
1213
|
}
|
|
981
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
982
1214
|
async query(vector, topK, namespace, _filter) {
|
|
983
1215
|
var _a, _b;
|
|
1216
|
+
const sanitizedFilter = this.sanitizeFilter(_filter);
|
|
1217
|
+
const queryText = sanitizedFilter.queryText;
|
|
1218
|
+
const searchParams = queryText ? `hybrid: { query: ${JSON.stringify(queryText)}, alpha: 0.5 }` : `nearVector: { vector: ${JSON.stringify(vector)} }`;
|
|
984
1219
|
const graphqlQuery = {
|
|
985
1220
|
query: `
|
|
986
1221
|
{
|
|
987
1222
|
Get {
|
|
988
1223
|
${this.indexName}(
|
|
989
|
-
|
|
990
|
-
vector: ${JSON.stringify(vector)}
|
|
991
|
-
}
|
|
1224
|
+
${searchParams}
|
|
992
1225
|
limit: ${topK}
|
|
993
1226
|
${namespace ? `where: { path: ["namespace"], operator: Equal, valueString: "${namespace}" }` : ""}
|
|
994
1227
|
) {
|
|
@@ -1274,6 +1507,7 @@ __export(handlers_exports, {
|
|
|
1274
1507
|
createChatHandler: () => createChatHandler,
|
|
1275
1508
|
createHealthHandler: () => createHealthHandler,
|
|
1276
1509
|
createIngestHandler: () => createIngestHandler,
|
|
1510
|
+
createStreamHandler: () => createStreamHandler,
|
|
1277
1511
|
createUploadHandler: () => createUploadHandler
|
|
1278
1512
|
});
|
|
1279
1513
|
module.exports = __toCommonJS(handlers_exports);
|
|
@@ -1321,7 +1555,6 @@ var PROVIDERS_WITH_EMBEDDINGS = [
|
|
|
1321
1555
|
"rest",
|
|
1322
1556
|
"universal_rest"
|
|
1323
1557
|
];
|
|
1324
|
-
var UI_VISUAL_STYLES = ["glass", "solid"];
|
|
1325
1558
|
var UI_BORDER_RADIUS_OPTIONS = ["none", "sm", "md", "lg", "xl", "full"];
|
|
1326
1559
|
|
|
1327
1560
|
// src/config/serverConfig.ts
|
|
@@ -1499,657 +1732,68 @@ var ConfigResolver = class {
|
|
|
1499
1732
|
}
|
|
1500
1733
|
};
|
|
1501
1734
|
|
|
1502
|
-
// src/
|
|
1503
|
-
var
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
if (!config.projectId) {
|
|
1511
|
-
errors.push({
|
|
1512
|
-
field: "projectId",
|
|
1513
|
-
message: "projectId is required",
|
|
1514
|
-
severity: "error"
|
|
1515
|
-
});
|
|
1516
|
-
}
|
|
1517
|
-
errors.push(...this.validateVectorDbConfig(config.vectorDb));
|
|
1518
|
-
errors.push(...this.validateLLMConfig(config.llm));
|
|
1519
|
-
if (config.embedding) {
|
|
1520
|
-
errors.push(...this.validateEmbeddingConfig(config.embedding));
|
|
1521
|
-
} else if (config.llm.provider === "anthropic") {
|
|
1522
|
-
errors.push({
|
|
1523
|
-
field: "embedding",
|
|
1524
|
-
message: "Embedding config is required when using Anthropic LLM",
|
|
1525
|
-
suggestion: 'Provide embedding config with provider (e.g., "openai" or "ollama")',
|
|
1526
|
-
severity: "error"
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
if (config.ui) {
|
|
1530
|
-
errors.push(...this.validateUIConfig(config.ui));
|
|
1531
|
-
}
|
|
1532
|
-
if (config.rag) {
|
|
1533
|
-
errors.push(...this.validateRAGConfig(config.rag));
|
|
1534
|
-
}
|
|
1535
|
-
return errors;
|
|
1536
|
-
}
|
|
1537
|
-
static validateVectorDbConfig(config) {
|
|
1538
|
-
const errors = [];
|
|
1539
|
-
if (!config.provider) {
|
|
1540
|
-
errors.push({
|
|
1541
|
-
field: "vectorDb.provider",
|
|
1542
|
-
message: "Vector database provider is required",
|
|
1543
|
-
severity: "error"
|
|
1544
|
-
});
|
|
1545
|
-
return errors;
|
|
1546
|
-
}
|
|
1547
|
-
if (!config.indexName) {
|
|
1548
|
-
errors.push({
|
|
1549
|
-
field: "vectorDb.indexName",
|
|
1550
|
-
message: "Vector database index name is required",
|
|
1551
|
-
severity: "error"
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
switch (config.provider) {
|
|
1555
|
-
case "pinecone":
|
|
1556
|
-
errors.push(...this.validatePineconeConfig(config));
|
|
1557
|
-
break;
|
|
1558
|
-
case "pgvector":
|
|
1559
|
-
case "postgresql":
|
|
1560
|
-
errors.push(...this.validatePostgresConfig(config));
|
|
1561
|
-
break;
|
|
1562
|
-
case "mongodb":
|
|
1563
|
-
errors.push(...this.validateMongoDBConfig(config));
|
|
1564
|
-
break;
|
|
1565
|
-
case "milvus":
|
|
1566
|
-
errors.push(...this.validateMilvusConfig(config));
|
|
1567
|
-
break;
|
|
1568
|
-
case "qdrant":
|
|
1569
|
-
errors.push(...this.validateQdrantConfig(config));
|
|
1570
|
-
break;
|
|
1571
|
-
case "chromadb":
|
|
1572
|
-
errors.push(...this.validateChromaDBConfig(config));
|
|
1573
|
-
break;
|
|
1574
|
-
case "redis":
|
|
1575
|
-
errors.push(...this.validateRedisConfig(config));
|
|
1576
|
-
break;
|
|
1577
|
-
case "weaviate":
|
|
1578
|
-
errors.push(...this.validateWeaviateConfig(config));
|
|
1579
|
-
break;
|
|
1580
|
-
case "universal_rest":
|
|
1581
|
-
case "rest":
|
|
1582
|
-
errors.push(...this.validateRestConfig(config));
|
|
1583
|
-
break;
|
|
1584
|
-
}
|
|
1585
|
-
return errors;
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Pinecone — only needs apiKey (environment was removed in SDK v7+).
|
|
1589
|
-
* Options key: { apiKey: string }
|
|
1590
|
-
*/
|
|
1591
|
-
static validatePineconeConfig(config) {
|
|
1592
|
-
const errors = [];
|
|
1593
|
-
const opts = config.options;
|
|
1594
|
-
if (!opts.apiKey) {
|
|
1595
|
-
errors.push({
|
|
1596
|
-
field: "vectorDb.options.apiKey",
|
|
1597
|
-
message: "Pinecone API key is required",
|
|
1598
|
-
suggestion: "Set PINECONE_API_KEY environment variable",
|
|
1599
|
-
severity: "error"
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
return errors;
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
|
-
* PostgreSQL / pgvector — needs a connection string.
|
|
1606
|
-
* Options key: { connectionString: string }
|
|
1607
|
-
*/
|
|
1608
|
-
static validatePostgresConfig(config) {
|
|
1609
|
-
const errors = [];
|
|
1610
|
-
const opts = config.options;
|
|
1611
|
-
if (!opts.connectionString) {
|
|
1612
|
-
errors.push({
|
|
1613
|
-
field: "vectorDb.options.connectionString",
|
|
1614
|
-
message: "PostgreSQL connection string is required",
|
|
1615
|
-
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
1616
|
-
severity: "error"
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
1620
|
-
errors.push({
|
|
1621
|
-
field: "vectorDb.options.tables",
|
|
1622
|
-
message: "PostgreSQL tables must be a string or a string array",
|
|
1623
|
-
severity: "error"
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
if (opts.searchFields && typeof opts.searchFields !== "string" && !Array.isArray(opts.searchFields)) {
|
|
1627
|
-
errors.push({
|
|
1628
|
-
field: "vectorDb.options.searchFields",
|
|
1629
|
-
message: "PostgreSQL searchFields must be a string or a string array",
|
|
1630
|
-
severity: "error"
|
|
1631
|
-
});
|
|
1632
|
-
}
|
|
1633
|
-
return errors;
|
|
1634
|
-
}
|
|
1635
|
-
/**
|
|
1636
|
-
* MongoDB — needs uri, database, and collection.
|
|
1637
|
-
* Options keys: { uri: string, database: string, collection: string }
|
|
1638
|
-
*/
|
|
1639
|
-
static validateMongoDBConfig(config) {
|
|
1640
|
-
const errors = [];
|
|
1641
|
-
const opts = config.options;
|
|
1642
|
-
if (!opts.uri) {
|
|
1643
|
-
errors.push({
|
|
1644
|
-
field: "vectorDb.options.uri",
|
|
1645
|
-
message: "MongoDB connection URI is required",
|
|
1646
|
-
suggestion: "Set MONGODB_URI environment variable",
|
|
1647
|
-
severity: "error"
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
if (!opts.database) {
|
|
1651
|
-
errors.push({
|
|
1652
|
-
field: "vectorDb.options.database",
|
|
1653
|
-
message: "MongoDB database name is required",
|
|
1654
|
-
suggestion: "Set MONGODB_DB environment variable",
|
|
1655
|
-
severity: "error"
|
|
1656
|
-
});
|
|
1657
|
-
}
|
|
1658
|
-
if (!opts.collection) {
|
|
1659
|
-
errors.push({
|
|
1660
|
-
field: "vectorDb.options.collection",
|
|
1661
|
-
message: "MongoDB collection name is required",
|
|
1662
|
-
suggestion: "Set MONGODB_COLLECTION environment variable",
|
|
1663
|
-
severity: "error"
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
return errors;
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Milvus — accepts baseUrl OR uri (preferred) OR host+port.
|
|
1670
|
-
* MilvusProvider reads opts.baseUrl ?? opts.uri.
|
|
1671
|
-
* Options keys: { baseUrl?: string, uri?: string, host?: string, port?: number }
|
|
1672
|
-
*/
|
|
1673
|
-
static validateMilvusConfig(config) {
|
|
1674
|
-
const errors = [];
|
|
1675
|
-
const opts = config.options;
|
|
1676
|
-
const hasBaseUrl = Boolean(opts.baseUrl || opts.uri);
|
|
1677
|
-
const hasHostPort = Boolean(opts.host && opts.port);
|
|
1678
|
-
if (!hasBaseUrl && !hasHostPort) {
|
|
1679
|
-
errors.push({
|
|
1680
|
-
field: "vectorDb.options.baseUrl",
|
|
1681
|
-
message: 'Milvus connection is required: provide baseUrl (e.g., "http://localhost:19530") or host + port',
|
|
1682
|
-
suggestion: "Set MILVUS_URL environment variable",
|
|
1683
|
-
severity: "error"
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
return errors;
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Qdrant — needs baseUrl (or url alias).
|
|
1690
|
-
* Options keys: { baseUrl: string, apiKey?: string }
|
|
1691
|
-
*/
|
|
1692
|
-
static validateQdrantConfig(config) {
|
|
1693
|
-
const errors = [];
|
|
1694
|
-
const opts = config.options;
|
|
1695
|
-
if (!opts.baseUrl && !opts.url) {
|
|
1696
|
-
errors.push({
|
|
1697
|
-
field: "vectorDb.options.baseUrl",
|
|
1698
|
-
message: "Qdrant base URL is required",
|
|
1699
|
-
suggestion: 'Set QDRANT_URL environment variable (e.g., "http://localhost:6333")',
|
|
1700
|
-
severity: "error"
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
return errors;
|
|
1704
|
-
}
|
|
1705
|
-
/**
|
|
1706
|
-
* ChromaDB — accepts baseUrl OR host.
|
|
1707
|
-
* ChromaDBProvider reads opts.baseUrl.
|
|
1708
|
-
* Options keys: { baseUrl?: string, host?: string, port?: number }
|
|
1709
|
-
*/
|
|
1710
|
-
static validateChromaDBConfig(config) {
|
|
1711
|
-
const errors = [];
|
|
1712
|
-
const opts = config.options;
|
|
1713
|
-
if (!opts.baseUrl && !opts.host) {
|
|
1714
|
-
errors.push({
|
|
1715
|
-
field: "vectorDb.options.baseUrl",
|
|
1716
|
-
message: 'ChromaDB connection is required: provide baseUrl (e.g., "http://localhost:8000") or host',
|
|
1717
|
-
suggestion: "Set CHROMADB_URL environment variable",
|
|
1718
|
-
severity: "error"
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
return errors;
|
|
1722
|
-
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Redis — accepts baseUrl OR url OR host+port.
|
|
1725
|
-
* RedisProvider reads opts.baseUrl.
|
|
1726
|
-
* Options keys: { baseUrl?: string, url?: string, host?: string, port?: number, apiKey?: string }
|
|
1727
|
-
*/
|
|
1728
|
-
static validateRedisConfig(config) {
|
|
1729
|
-
const errors = [];
|
|
1730
|
-
const opts = config.options;
|
|
1731
|
-
const hasUrl = Boolean(opts.baseUrl || opts.url);
|
|
1732
|
-
const hasHostPort = Boolean(opts.host && opts.port);
|
|
1733
|
-
if (!hasUrl && !hasHostPort) {
|
|
1734
|
-
errors.push({
|
|
1735
|
-
field: "vectorDb.options.baseUrl",
|
|
1736
|
-
message: "Redis connection is required: provide baseUrl/url or host + port",
|
|
1737
|
-
suggestion: "Set REDIS_URL environment variable",
|
|
1738
|
-
severity: "error"
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
return errors;
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
|
-
* Weaviate — accepts baseUrl OR url.
|
|
1745
|
-
* WeaviateProvider reads opts.baseUrl.
|
|
1746
|
-
* Options keys: { baseUrl?: string, url?: string, apiKey?: string }
|
|
1747
|
-
*/
|
|
1748
|
-
static validateWeaviateConfig(config) {
|
|
1749
|
-
const errors = [];
|
|
1750
|
-
const opts = config.options;
|
|
1751
|
-
if (!opts.baseUrl && !opts.url) {
|
|
1752
|
-
errors.push({
|
|
1753
|
-
field: "vectorDb.options.baseUrl",
|
|
1754
|
-
message: "Weaviate instance URL is required",
|
|
1755
|
-
suggestion: "Set WEAVIATE_URL environment variable",
|
|
1756
|
-
severity: "error"
|
|
1757
|
-
});
|
|
1758
|
-
}
|
|
1759
|
-
return errors;
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Universal REST / custom REST adapter.
|
|
1763
|
-
* Options key: { baseUrl: string }
|
|
1764
|
-
*/
|
|
1765
|
-
static validateRestConfig(config) {
|
|
1766
|
-
const errors = [];
|
|
1767
|
-
const opts = config.options;
|
|
1768
|
-
if (!opts.baseUrl) {
|
|
1769
|
-
errors.push({
|
|
1770
|
-
field: "vectorDb.options.baseUrl",
|
|
1771
|
-
message: "REST API base URL is required",
|
|
1772
|
-
suggestion: "Set VECTOR_BASE_URL environment variable",
|
|
1773
|
-
severity: "error"
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
return errors;
|
|
1735
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
1736
|
+
var import_openai = __toESM(require("openai"));
|
|
1737
|
+
var OpenAIProvider = class {
|
|
1738
|
+
constructor(llmConfig, embeddingConfig) {
|
|
1739
|
+
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
1740
|
+
this.client = new import_openai.default({ apiKey: llmConfig.apiKey });
|
|
1741
|
+
this.llmConfig = llmConfig;
|
|
1742
|
+
this.embeddingConfig = embeddingConfig;
|
|
1777
1743
|
}
|
|
1778
|
-
static
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
message: "LLM provider is required",
|
|
1785
|
-
severity: "error"
|
|
1786
|
-
});
|
|
1787
|
-
return errors;
|
|
1788
|
-
}
|
|
1789
|
-
if (!config.model) {
|
|
1790
|
-
errors.push({
|
|
1791
|
-
field: "llm.model",
|
|
1792
|
-
message: "LLM model name is required",
|
|
1793
|
-
suggestion: `e.g., "gpt-4o" for OpenAI, "claude-3-opus" for Anthropic`,
|
|
1794
|
-
severity: "error"
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
switch (config.provider) {
|
|
1798
|
-
case "openai":
|
|
1744
|
+
static getValidator() {
|
|
1745
|
+
return {
|
|
1746
|
+
validate(config) {
|
|
1747
|
+
const errors = [];
|
|
1748
|
+
const isEmbedding = config.provider === "openai" && "dimensions" in config;
|
|
1749
|
+
const prefix = isEmbedding ? "embedding" : "llm";
|
|
1799
1750
|
if (!config.apiKey) {
|
|
1800
1751
|
errors.push({
|
|
1801
|
-
field:
|
|
1752
|
+
field: `${prefix}.apiKey`,
|
|
1802
1753
|
message: "OpenAI API key is required",
|
|
1803
1754
|
suggestion: "Set OPENAI_API_KEY environment variable",
|
|
1804
1755
|
severity: "error"
|
|
1805
1756
|
});
|
|
1806
1757
|
}
|
|
1807
|
-
|
|
1808
|
-
case "anthropic":
|
|
1809
|
-
if (!config.apiKey) {
|
|
1810
|
-
errors.push({
|
|
1811
|
-
field: "llm.apiKey",
|
|
1812
|
-
message: "Anthropic API key is required",
|
|
1813
|
-
suggestion: "Set ANTHROPIC_API_KEY environment variable",
|
|
1814
|
-
severity: "error"
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
break;
|
|
1818
|
-
case "gemini":
|
|
1819
|
-
if (!config.apiKey) {
|
|
1758
|
+
if (!config.model) {
|
|
1820
1759
|
errors.push({
|
|
1821
|
-
field:
|
|
1822
|
-
message: "
|
|
1823
|
-
suggestion: "
|
|
1760
|
+
field: `${prefix}.model`,
|
|
1761
|
+
message: "OpenAI model name is required",
|
|
1762
|
+
suggestion: isEmbedding ? 'e.g., "text-embedding-3-small"' : 'e.g., "gpt-4o"',
|
|
1824
1763
|
severity: "error"
|
|
1825
1764
|
});
|
|
1826
1765
|
}
|
|
1827
|
-
|
|
1828
|
-
case "ollama":
|
|
1829
|
-
if (!config.baseUrl) {
|
|
1830
|
-
errors.push({
|
|
1831
|
-
field: "llm.baseUrl",
|
|
1832
|
-
message: "Ollama base URL is required",
|
|
1833
|
-
suggestion: 'Set LLM_BASE_URL environment variable (e.g., "http://localhost:11434")',
|
|
1834
|
-
severity: "error"
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
break;
|
|
1838
|
-
case "rest":
|
|
1839
|
-
case "universal_rest":
|
|
1840
|
-
if (!config.baseUrl && !((_a = config.options) == null ? void 0 : _a.baseUrl)) {
|
|
1841
|
-
errors.push({
|
|
1842
|
-
field: "llm.baseUrl",
|
|
1843
|
-
message: "REST API base URL is required",
|
|
1844
|
-
suggestion: "Set LLM_BASE_URL environment variable",
|
|
1845
|
-
severity: "error"
|
|
1846
|
-
});
|
|
1847
|
-
}
|
|
1848
|
-
break;
|
|
1849
|
-
}
|
|
1850
|
-
if (config.temperature !== void 0) {
|
|
1851
|
-
if (config.temperature < 0 || config.temperature > 2) {
|
|
1852
|
-
errors.push({
|
|
1853
|
-
field: "llm.temperature",
|
|
1854
|
-
message: "Temperature must be between 0 and 2",
|
|
1855
|
-
severity: "error"
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
if (config.maxTokens !== void 0 && config.maxTokens <= 0) {
|
|
1860
|
-
errors.push({
|
|
1861
|
-
field: "llm.maxTokens",
|
|
1862
|
-
message: "maxTokens must be greater than 0",
|
|
1863
|
-
severity: "error"
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
return errors;
|
|
1867
|
-
}
|
|
1868
|
-
static validateEmbeddingConfig(config) {
|
|
1869
|
-
const errors = [];
|
|
1870
|
-
if (!config.provider) {
|
|
1871
|
-
errors.push({
|
|
1872
|
-
field: "embedding.provider",
|
|
1873
|
-
message: "Embedding provider is required",
|
|
1874
|
-
severity: "error"
|
|
1875
|
-
});
|
|
1876
|
-
return errors;
|
|
1877
|
-
}
|
|
1878
|
-
if (!config.model) {
|
|
1879
|
-
errors.push({
|
|
1880
|
-
field: "embedding.model",
|
|
1881
|
-
message: "Embedding model name is required",
|
|
1882
|
-
suggestion: 'e.g., "text-embedding-3-small" for OpenAI, "nomic-embed-text" for Ollama',
|
|
1883
|
-
severity: "error"
|
|
1884
|
-
});
|
|
1885
|
-
}
|
|
1886
|
-
if (config.provider === "openai" && !config.apiKey) {
|
|
1887
|
-
errors.push({
|
|
1888
|
-
field: "embedding.apiKey",
|
|
1889
|
-
message: "OpenAI API key is required for embedding",
|
|
1890
|
-
suggestion: "Set OPENAI_API_KEY environment variable",
|
|
1891
|
-
severity: "error"
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
if (config.provider === "gemini" && !config.apiKey) {
|
|
1895
|
-
errors.push({
|
|
1896
|
-
field: "embedding.apiKey",
|
|
1897
|
-
message: "Gemini API key is required for embedding",
|
|
1898
|
-
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1899
|
-
severity: "error"
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
if (config.provider === "ollama" && !config.baseUrl) {
|
|
1903
|
-
errors.push({
|
|
1904
|
-
field: "embedding.baseUrl",
|
|
1905
|
-
message: "Ollama base URL is required for embedding",
|
|
1906
|
-
suggestion: "Set EMBEDDING_BASE_URL environment variable",
|
|
1907
|
-
severity: "error"
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
if (config.dimensions !== void 0 && config.dimensions <= 0) {
|
|
1911
|
-
errors.push({
|
|
1912
|
-
field: "embedding.dimensions",
|
|
1913
|
-
message: "Embedding dimensions must be greater than 0",
|
|
1914
|
-
severity: "error"
|
|
1915
|
-
});
|
|
1916
|
-
}
|
|
1917
|
-
return errors;
|
|
1918
|
-
}
|
|
1919
|
-
static validateUIConfig(config) {
|
|
1920
|
-
const errors = [];
|
|
1921
|
-
if (config.primaryColor) {
|
|
1922
|
-
if (!this.isValidCSSColor(config.primaryColor)) {
|
|
1923
|
-
errors.push({
|
|
1924
|
-
field: "ui.primaryColor",
|
|
1925
|
-
message: "Invalid CSS color format",
|
|
1926
|
-
suggestion: "Use valid CSS colors: hex (#FF0000), rgb, hsl, or named colors",
|
|
1927
|
-
severity: "warning"
|
|
1928
|
-
});
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
if (config.borderRadius) {
|
|
1932
|
-
if (!UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius)) {
|
|
1933
|
-
errors.push({
|
|
1934
|
-
field: "ui.borderRadius",
|
|
1935
|
-
message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(", ")}`,
|
|
1936
|
-
severity: "warning"
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
if (config.visualStyle) {
|
|
1941
|
-
if (!UI_VISUAL_STYLES.includes(config.visualStyle)) {
|
|
1942
|
-
errors.push({
|
|
1943
|
-
field: "ui.visualStyle",
|
|
1944
|
-
message: `visualStyle must be one of: ${UI_VISUAL_STYLES.join(", ")}`,
|
|
1945
|
-
severity: "warning"
|
|
1946
|
-
});
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
return errors;
|
|
1950
|
-
}
|
|
1951
|
-
static validateRAGConfig(config) {
|
|
1952
|
-
const errors = [];
|
|
1953
|
-
if (config.topK !== void 0) {
|
|
1954
|
-
if (typeof config.topK !== "number" || config.topK <= 0) {
|
|
1955
|
-
errors.push({
|
|
1956
|
-
field: "rag.topK",
|
|
1957
|
-
message: "topK must be a positive integer",
|
|
1958
|
-
severity: "error"
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
if (config.scoreThreshold !== void 0) {
|
|
1963
|
-
if (typeof config.scoreThreshold !== "number" || config.scoreThreshold < 0 || config.scoreThreshold > 1) {
|
|
1964
|
-
errors.push({
|
|
1965
|
-
field: "rag.scoreThreshold",
|
|
1966
|
-
message: "scoreThreshold must be between 0 and 1",
|
|
1967
|
-
severity: "error"
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
if (config.chunkSize !== void 0) {
|
|
1972
|
-
if (typeof config.chunkSize !== "number" || config.chunkSize <= 0) {
|
|
1973
|
-
errors.push({
|
|
1974
|
-
field: "rag.chunkSize",
|
|
1975
|
-
message: "chunkSize must be a positive integer",
|
|
1976
|
-
severity: "error"
|
|
1977
|
-
});
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
if (config.chunkOverlap !== void 0) {
|
|
1981
|
-
if (typeof config.chunkOverlap !== "number" || config.chunkOverlap < 0) {
|
|
1982
|
-
errors.push({
|
|
1983
|
-
field: "rag.chunkOverlap",
|
|
1984
|
-
message: "chunkOverlap must be a non-negative integer",
|
|
1985
|
-
severity: "error"
|
|
1986
|
-
});
|
|
1766
|
+
return errors;
|
|
1987
1767
|
}
|
|
1988
|
-
}
|
|
1989
|
-
return errors;
|
|
1990
|
-
}
|
|
1991
|
-
static isValidCSSColor(color) {
|
|
1992
|
-
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
1993
|
-
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
1994
|
-
const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
1995
|
-
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
1996
|
-
return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
1997
|
-
}
|
|
1998
|
-
/**
|
|
1999
|
-
* Throws if there are error-level validation issues.
|
|
2000
|
-
* Logs warnings to console.
|
|
2001
|
-
*/
|
|
2002
|
-
static validateAndThrow(config) {
|
|
2003
|
-
const errors = this.validate(config);
|
|
2004
|
-
const errorItems = errors.filter((e) => e.severity === "error");
|
|
2005
|
-
const warnings = errors.filter((e) => e.severity === "warning");
|
|
2006
|
-
if (warnings.length > 0) {
|
|
2007
|
-
console.warn("[ConfigValidator] Configuration warnings:");
|
|
2008
|
-
warnings.forEach((w) => {
|
|
2009
|
-
console.warn(` ${w.field}: ${w.message}`);
|
|
2010
|
-
if (w.suggestion) console.warn(` Suggestion: ${w.suggestion}`);
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
if (errorItems.length > 0) {
|
|
2014
|
-
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
2015
|
-
throw new Error(`[ConfigValidator] Configuration validation failed:
|
|
2016
|
-
${message}`);
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
};
|
|
2020
|
-
|
|
2021
|
-
// src/rag/DocumentChunker.ts
|
|
2022
|
-
var DocumentChunker = class {
|
|
2023
|
-
constructor(chunkSize = 1e3, chunkOverlap = 200, separators = ["\n\n", "\n", " ", ""]) {
|
|
2024
|
-
this.chunkSize = chunkSize;
|
|
2025
|
-
this.chunkOverlap = chunkOverlap;
|
|
2026
|
-
this.separators = separators;
|
|
1768
|
+
};
|
|
2027
1769
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
let overlapLen = 0;
|
|
2053
|
-
for (let i = currentChunk.length - 1; i >= 0; i--) {
|
|
2054
|
-
if (overlapLen + currentChunk[i].length <= chunkOverlap) {
|
|
2055
|
-
overlapItems.unshift(currentChunk[i]);
|
|
2056
|
-
overlapLen += currentChunk[i].length;
|
|
2057
|
-
} else {
|
|
2058
|
-
break;
|
|
2059
|
-
}
|
|
1770
|
+
static getHealthChecker() {
|
|
1771
|
+
return {
|
|
1772
|
+
async check(config) {
|
|
1773
|
+
const timestamp = Date.now();
|
|
1774
|
+
const apiKey = config.apiKey;
|
|
1775
|
+
const modelName = config.model;
|
|
1776
|
+
try {
|
|
1777
|
+
const OpenAI2 = await import("openai");
|
|
1778
|
+
const client = new OpenAI2.default({ apiKey });
|
|
1779
|
+
const models = await client.models.list();
|
|
1780
|
+
const hasModel = models.data.some((m) => m.id === modelName);
|
|
1781
|
+
return {
|
|
1782
|
+
healthy: true,
|
|
1783
|
+
provider: "openai",
|
|
1784
|
+
capabilities: { model: modelName, available: hasModel, totalModels: models.data.length },
|
|
1785
|
+
timestamp
|
|
1786
|
+
};
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
return {
|
|
1789
|
+
healthy: false,
|
|
1790
|
+
provider: "openai",
|
|
1791
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1792
|
+
timestamp
|
|
1793
|
+
};
|
|
2060
1794
|
}
|
|
2061
|
-
currentChunk = overlapItems;
|
|
2062
|
-
currentLength = overlapLen;
|
|
2063
|
-
}
|
|
2064
|
-
currentChunk.push(split);
|
|
2065
|
-
currentLength += split.length;
|
|
2066
|
-
}
|
|
2067
|
-
if (currentChunk.length > 0) {
|
|
2068
|
-
finalChunks.push({
|
|
2069
|
-
id: `${docId}_chunk_${chunkIndex}`,
|
|
2070
|
-
content: currentChunk.join("").trim(),
|
|
2071
|
-
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex })
|
|
2072
|
-
});
|
|
2073
|
-
}
|
|
2074
|
-
return finalChunks;
|
|
2075
|
-
}
|
|
2076
|
-
/**
|
|
2077
|
-
* Recursively split text based on separators.
|
|
2078
|
-
*/
|
|
2079
|
-
recursiveSplit(text, separators, chunkSize) {
|
|
2080
|
-
const finalSplits = [];
|
|
2081
|
-
let separator = separators[separators.length - 1];
|
|
2082
|
-
let nextSeparators = [];
|
|
2083
|
-
for (let i = 0; i < separators.length; i++) {
|
|
2084
|
-
if (text.includes(separators[i])) {
|
|
2085
|
-
separator = separators[i];
|
|
2086
|
-
nextSeparators = separators.slice(i + 1);
|
|
2087
|
-
break;
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
const parts = text.split(separator);
|
|
2091
|
-
for (const part of parts) {
|
|
2092
|
-
if (part.length <= chunkSize) {
|
|
2093
|
-
finalSplits.push(part + separator);
|
|
2094
|
-
} else if (nextSeparators.length > 0) {
|
|
2095
|
-
finalSplits.push(...this.recursiveSplit(part, nextSeparators, chunkSize));
|
|
2096
|
-
} else {
|
|
2097
|
-
finalSplits.push(part);
|
|
2098
1795
|
}
|
|
2099
|
-
}
|
|
2100
|
-
return finalSplits;
|
|
2101
|
-
}
|
|
2102
|
-
/**
|
|
2103
|
-
* Chunk multiple documents at once.
|
|
2104
|
-
*/
|
|
2105
|
-
chunkMany(documents) {
|
|
2106
|
-
return documents.flatMap(
|
|
2107
|
-
(doc) => this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
|
2108
|
-
);
|
|
2109
|
-
}
|
|
2110
|
-
};
|
|
2111
|
-
|
|
2112
|
-
// src/rag/EntityExtractor.ts
|
|
2113
|
-
var EntityExtractor = class {
|
|
2114
|
-
constructor(llm) {
|
|
2115
|
-
this.llm = llm;
|
|
2116
|
-
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Extract nodes and edges from a text chunk.
|
|
2119
|
-
*/
|
|
2120
|
-
async extract(text) {
|
|
2121
|
-
const prompt = `
|
|
2122
|
-
Extract entities and relationships from the following text.
|
|
2123
|
-
Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
|
|
2124
|
-
Use the same ID for the same entity.
|
|
2125
|
-
|
|
2126
|
-
Text:
|
|
2127
|
-
"${text}"
|
|
2128
|
-
|
|
2129
|
-
Output JSON:
|
|
2130
|
-
`;
|
|
2131
|
-
const response = await this.llm.chat([
|
|
2132
|
-
{ role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
|
|
2133
|
-
{ role: "user", content: prompt }
|
|
2134
|
-
], "");
|
|
2135
|
-
try {
|
|
2136
|
-
const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
|
|
2137
|
-
return JSON.parse(cleanJson);
|
|
2138
|
-
} catch (e) {
|
|
2139
|
-
console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
|
|
2140
|
-
return { nodes: [], edges: [] };
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
|
|
2145
|
-
// src/llm/providers/OpenAIProvider.ts
|
|
2146
|
-
var import_openai = __toESM(require("openai"));
|
|
2147
|
-
var OpenAIProvider = class {
|
|
2148
|
-
constructor(llmConfig, embeddingConfig) {
|
|
2149
|
-
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
2150
|
-
this.client = new import_openai.default({ apiKey: llmConfig.apiKey });
|
|
2151
|
-
this.llmConfig = llmConfig;
|
|
2152
|
-
this.embeddingConfig = embeddingConfig;
|
|
1796
|
+
};
|
|
2153
1797
|
}
|
|
2154
1798
|
async chat(messages, context, options) {
|
|
2155
1799
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -2212,19 +1856,69 @@ var AnthropicProvider = class {
|
|
|
2212
1856
|
this.llmConfig = llmConfig;
|
|
2213
1857
|
this.embeddingConfig = embeddingConfig;
|
|
2214
1858
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
1859
|
+
static getValidator() {
|
|
1860
|
+
return {
|
|
1861
|
+
validate(config) {
|
|
1862
|
+
const errors = [];
|
|
1863
|
+
if (!config.apiKey) {
|
|
1864
|
+
errors.push({
|
|
1865
|
+
field: "llm.apiKey",
|
|
1866
|
+
message: "Anthropic API key is required",
|
|
1867
|
+
suggestion: "Set ANTHROPIC_API_KEY environment variable",
|
|
1868
|
+
severity: "error"
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
if (!config.model) {
|
|
1872
|
+
errors.push({
|
|
1873
|
+
field: "llm.model",
|
|
1874
|
+
message: "Anthropic model name is required",
|
|
1875
|
+
suggestion: 'e.g., "claude-3-5-sonnet-20241022"',
|
|
1876
|
+
severity: "error"
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
return errors;
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
static getHealthChecker() {
|
|
1884
|
+
return {
|
|
1885
|
+
async check(config) {
|
|
1886
|
+
const timestamp = Date.now();
|
|
1887
|
+
const apiKey = config.apiKey;
|
|
1888
|
+
const modelName = config.model;
|
|
1889
|
+
try {
|
|
1890
|
+
const { default: Anthropic2 } = await import("@anthropic-ai/sdk");
|
|
1891
|
+
const client = new Anthropic2({ apiKey });
|
|
1892
|
+
await client.messages.create({
|
|
1893
|
+
model: modelName,
|
|
1894
|
+
max_tokens: 10,
|
|
1895
|
+
messages: [{ role: "user", content: "ping" }]
|
|
1896
|
+
});
|
|
1897
|
+
return { healthy: true, provider: "anthropic", capabilities: { model: modelName }, timestamp };
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
return {
|
|
1900
|
+
healthy: false,
|
|
1901
|
+
provider: "anthropic",
|
|
1902
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1903
|
+
timestamp
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
async chat(messages, context, options) {
|
|
1910
|
+
var _a, _b, _c;
|
|
1911
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
1912
|
+
|
|
1913
|
+
Context:
|
|
1914
|
+
${context}`;
|
|
1915
|
+
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
1916
|
+
|
|
1917
|
+
Context:
|
|
1918
|
+
${context}`;
|
|
1919
|
+
const anthropicMessages = messages.map((m) => ({
|
|
1920
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
1921
|
+
content: m.content
|
|
2228
1922
|
}));
|
|
2229
1923
|
const response = await this.client.messages.create({
|
|
2230
1924
|
model: this.llmConfig.model,
|
|
@@ -2235,11 +1929,6 @@ ${context}`;
|
|
|
2235
1929
|
const block = response.content[0];
|
|
2236
1930
|
return block.type === "text" ? block.text : "";
|
|
2237
1931
|
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Anthropic does not offer an embedding API.
|
|
2240
|
-
* This method throws with a clear error so developers know to configure
|
|
2241
|
-
* a separate embedding provider (OpenAI or Ollama).
|
|
2242
|
-
*/
|
|
2243
1932
|
async embed(text, options) {
|
|
2244
1933
|
void text;
|
|
2245
1934
|
void options;
|
|
@@ -2275,22 +1964,66 @@ var OllamaProvider = class {
|
|
|
2275
1964
|
this.llmConfig = llmConfig;
|
|
2276
1965
|
this.embeddingConfig = embeddingConfig;
|
|
2277
1966
|
}
|
|
1967
|
+
static getValidator() {
|
|
1968
|
+
return {
|
|
1969
|
+
validate(config) {
|
|
1970
|
+
const errors = [];
|
|
1971
|
+
if (!config.model) {
|
|
1972
|
+
const isEmbedding = config.provider === "ollama" && "dimensions" in config;
|
|
1973
|
+
const prefix = isEmbedding ? "embedding" : "llm";
|
|
1974
|
+
errors.push({
|
|
1975
|
+
field: `${prefix}.model`,
|
|
1976
|
+
message: "Ollama model name is required",
|
|
1977
|
+
suggestion: 'e.g., "llama3" or "mistral"',
|
|
1978
|
+
severity: "error"
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return errors;
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
static getHealthChecker() {
|
|
1986
|
+
return {
|
|
1987
|
+
async check(config) {
|
|
1988
|
+
const timestamp = Date.now();
|
|
1989
|
+
const baseUrl = config.baseUrl || "http://localhost:11434";
|
|
1990
|
+
const modelName = config.model;
|
|
1991
|
+
try {
|
|
1992
|
+
const axios9 = (await import("axios")).default;
|
|
1993
|
+
const { data } = await axios9.get(`${baseUrl}/api/tags`);
|
|
1994
|
+
const models = data.models || [];
|
|
1995
|
+
const hasModel = models.some((m) => m.name === modelName || m.name.startsWith(`${modelName}:`));
|
|
1996
|
+
return {
|
|
1997
|
+
healthy: true,
|
|
1998
|
+
provider: "ollama",
|
|
1999
|
+
capabilities: {
|
|
2000
|
+
baseUrl,
|
|
2001
|
+
model: modelName,
|
|
2002
|
+
available: hasModel,
|
|
2003
|
+
totalModels: models.length
|
|
2004
|
+
},
|
|
2005
|
+
timestamp
|
|
2006
|
+
};
|
|
2007
|
+
} catch (e) {
|
|
2008
|
+
return {
|
|
2009
|
+
healthy: false,
|
|
2010
|
+
provider: "ollama",
|
|
2011
|
+
error: `Ollama server not reachable at ${baseUrl}. Is it running?`,
|
|
2012
|
+
timestamp
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2278
2018
|
async chat(messages, context, options) {
|
|
2279
|
-
var _a, _b, _c, _d
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
Context:
|
|
2283
|
-
${context}`;
|
|
2284
|
-
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2285
|
-
|
|
2286
|
-
Context:
|
|
2287
|
-
${context}`;
|
|
2019
|
+
var _a, _b, _c, _d;
|
|
2020
|
+
const system = this.buildSystemPrompt(context);
|
|
2288
2021
|
const { data } = await this.http.post("/api/chat", {
|
|
2289
2022
|
model: this.llmConfig.model,
|
|
2290
2023
|
stream: false,
|
|
2291
2024
|
options: {
|
|
2292
|
-
temperature: (
|
|
2293
|
-
num_predict: (
|
|
2025
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
|
|
2026
|
+
num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
|
|
2294
2027
|
},
|
|
2295
2028
|
messages: [
|
|
2296
2029
|
{ role: "system", content: system },
|
|
@@ -2299,6 +2032,60 @@ ${context}`;
|
|
|
2299
2032
|
});
|
|
2300
2033
|
return data.message.content;
|
|
2301
2034
|
}
|
|
2035
|
+
chatStream(messages, context, options) {
|
|
2036
|
+
return __asyncGenerator(this, null, function* () {
|
|
2037
|
+
var _a, _b, _c, _d, _e;
|
|
2038
|
+
const system = this.buildSystemPrompt(context);
|
|
2039
|
+
const response = yield new __await(this.http.post("/api/chat", {
|
|
2040
|
+
model: this.llmConfig.model,
|
|
2041
|
+
stream: true,
|
|
2042
|
+
options: {
|
|
2043
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
|
|
2044
|
+
num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
|
|
2045
|
+
},
|
|
2046
|
+
messages: [
|
|
2047
|
+
{ role: "system", content: system },
|
|
2048
|
+
...messages.map((m) => ({ role: m.role, content: m.content }))
|
|
2049
|
+
]
|
|
2050
|
+
}, { responseType: "stream" }));
|
|
2051
|
+
try {
|
|
2052
|
+
for (var iter = __forAwait(response.data), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2053
|
+
const chunk = temp.value;
|
|
2054
|
+
const lines = chunk.toString().split("\n").filter(Boolean);
|
|
2055
|
+
for (const line of lines) {
|
|
2056
|
+
try {
|
|
2057
|
+
const json = JSON.parse(line);
|
|
2058
|
+
if ((_e = json.message) == null ? void 0 : _e.content) {
|
|
2059
|
+
yield json.message.content;
|
|
2060
|
+
}
|
|
2061
|
+
if (json.done) return;
|
|
2062
|
+
} catch (e) {
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
} catch (temp) {
|
|
2067
|
+
error = [temp];
|
|
2068
|
+
} finally {
|
|
2069
|
+
try {
|
|
2070
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
2071
|
+
} finally {
|
|
2072
|
+
if (error)
|
|
2073
|
+
throw error[0];
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
buildSystemPrompt(context) {
|
|
2079
|
+
var _a;
|
|
2080
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2081
|
+
|
|
2082
|
+
Context:
|
|
2083
|
+
${context}`;
|
|
2084
|
+
return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2085
|
+
|
|
2086
|
+
Context:
|
|
2087
|
+
${context}`;
|
|
2088
|
+
}
|
|
2302
2089
|
async embed(text, options) {
|
|
2303
2090
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2304
2091
|
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";
|
|
@@ -2355,6 +2142,52 @@ var GeminiProvider = class {
|
|
|
2355
2142
|
});
|
|
2356
2143
|
}
|
|
2357
2144
|
}
|
|
2145
|
+
static getValidator() {
|
|
2146
|
+
return {
|
|
2147
|
+
validate(config) {
|
|
2148
|
+
const errors = [];
|
|
2149
|
+
if (!config.apiKey && !process.env.GOOGLE_GENAI_API_KEY) {
|
|
2150
|
+
errors.push({
|
|
2151
|
+
field: "llm.apiKey",
|
|
2152
|
+
message: "Gemini API key is required",
|
|
2153
|
+
suggestion: "Set GOOGLE_GENAI_API_KEY environment variable or provide in config",
|
|
2154
|
+
severity: "error"
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
if (!config.model) {
|
|
2158
|
+
errors.push({ field: "llm.model", message: "Gemini model name is required", severity: "error" });
|
|
2159
|
+
}
|
|
2160
|
+
return errors;
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
static getHealthChecker() {
|
|
2165
|
+
return {
|
|
2166
|
+
async check(config) {
|
|
2167
|
+
const timestamp = Date.now();
|
|
2168
|
+
const apiKey = config.apiKey || process.env.GOOGLE_GENAI_API_KEY || "";
|
|
2169
|
+
const modelName = config.model;
|
|
2170
|
+
try {
|
|
2171
|
+
const { GoogleGenAI: GoogleGenAI2 } = await import("@google/genai");
|
|
2172
|
+
const genAI = new GoogleGenAI2({ apiKey });
|
|
2173
|
+
await genAI.models.get({ model: modelName });
|
|
2174
|
+
return {
|
|
2175
|
+
healthy: true,
|
|
2176
|
+
provider: "gemini",
|
|
2177
|
+
capabilities: { model: modelName },
|
|
2178
|
+
timestamp
|
|
2179
|
+
};
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
return {
|
|
2182
|
+
healthy: false,
|
|
2183
|
+
provider: "gemini",
|
|
2184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2185
|
+
timestamp
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2358
2191
|
sanitizeModel(model) {
|
|
2359
2192
|
if (!model) return model;
|
|
2360
2193
|
return model.split(":")[0];
|
|
@@ -2575,172 +2408,593 @@ ${context != null ? context : "None"}` },
|
|
|
2575
2408
|
input: text
|
|
2576
2409
|
};
|
|
2577
2410
|
}
|
|
2578
|
-
const { data } = await this.http.post(path, payload);
|
|
2579
|
-
const extractPath = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
2580
|
-
const vector = resolvePath(data, extractPath);
|
|
2581
|
-
if (!Array.isArray(vector)) {
|
|
2582
|
-
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
2411
|
+
const { data } = await this.http.post(path, payload);
|
|
2412
|
+
const extractPath = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
2413
|
+
const vector = resolvePath(data, extractPath);
|
|
2414
|
+
if (!Array.isArray(vector)) {
|
|
2415
|
+
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
2416
|
+
}
|
|
2417
|
+
return vector;
|
|
2418
|
+
}
|
|
2419
|
+
async batchEmbed(texts) {
|
|
2420
|
+
const vectors = [];
|
|
2421
|
+
for (const text of texts) {
|
|
2422
|
+
vectors.push(await this.embed(text));
|
|
2423
|
+
}
|
|
2424
|
+
return vectors;
|
|
2425
|
+
}
|
|
2426
|
+
async ping() {
|
|
2427
|
+
try {
|
|
2428
|
+
if (this.opts.pingPath) {
|
|
2429
|
+
await this.http.get(this.opts.pingPath);
|
|
2430
|
+
}
|
|
2431
|
+
return true;
|
|
2432
|
+
} catch (err) {
|
|
2433
|
+
console.error("[UniversalLLMAdapter] Ping failed:", err);
|
|
2434
|
+
return false;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2439
|
+
// src/llm/LLMFactory.ts
|
|
2440
|
+
var LLMFactory = class _LLMFactory {
|
|
2441
|
+
static create(llmConfig, embeddingConfig) {
|
|
2442
|
+
var _a;
|
|
2443
|
+
switch (llmConfig.provider) {
|
|
2444
|
+
case "openai":
|
|
2445
|
+
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
2446
|
+
case "anthropic":
|
|
2447
|
+
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2448
|
+
case "ollama":
|
|
2449
|
+
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2450
|
+
case "gemini":
|
|
2451
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2452
|
+
case "rest":
|
|
2453
|
+
case "universal_rest":
|
|
2454
|
+
case "custom":
|
|
2455
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
2456
|
+
default:
|
|
2457
|
+
if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
|
|
2458
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
2459
|
+
}
|
|
2460
|
+
throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
static getValidator(provider) {
|
|
2464
|
+
const providerClass = this.getProviderClass(provider);
|
|
2465
|
+
return providerClass && providerClass.getValidator ? providerClass.getValidator() : null;
|
|
2466
|
+
}
|
|
2467
|
+
static getHealthChecker(provider) {
|
|
2468
|
+
const providerClass = this.getProviderClass(provider);
|
|
2469
|
+
return providerClass && providerClass.getHealthChecker ? providerClass.getHealthChecker() : null;
|
|
2470
|
+
}
|
|
2471
|
+
static getProviderClass(provider) {
|
|
2472
|
+
switch (provider) {
|
|
2473
|
+
case "openai":
|
|
2474
|
+
return OpenAIProvider;
|
|
2475
|
+
case "anthropic":
|
|
2476
|
+
return AnthropicProvider;
|
|
2477
|
+
case "ollama":
|
|
2478
|
+
return OllamaProvider;
|
|
2479
|
+
case "gemini":
|
|
2480
|
+
return GeminiProvider;
|
|
2481
|
+
case "rest":
|
|
2482
|
+
case "universal_rest":
|
|
2483
|
+
case "custom":
|
|
2484
|
+
return UniversalLLMAdapter;
|
|
2485
|
+
default:
|
|
2486
|
+
return null;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Creates a dedicated embedding-only provider.
|
|
2491
|
+
*/
|
|
2492
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
2493
|
+
const fakeLLMConfig = {
|
|
2494
|
+
provider: embeddingConfig.provider,
|
|
2495
|
+
model: embeddingConfig.model,
|
|
2496
|
+
apiKey: embeddingConfig.apiKey,
|
|
2497
|
+
baseUrl: embeddingConfig.baseUrl,
|
|
2498
|
+
options: embeddingConfig.options
|
|
2499
|
+
};
|
|
2500
|
+
return _LLMFactory.create(fakeLLMConfig, embeddingConfig);
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
// src/core/ProviderRegistry.ts
|
|
2505
|
+
var ProviderRegistry = class {
|
|
2506
|
+
static registerVectorProvider(name, providerClass) {
|
|
2507
|
+
this.vectorProviders[name] = providerClass;
|
|
2508
|
+
if (providerClass.getValidator) {
|
|
2509
|
+
this.vectorValidators[name] = providerClass.getValidator();
|
|
2510
|
+
}
|
|
2511
|
+
if (providerClass.getHealthChecker) {
|
|
2512
|
+
this.vectorHealthCheckers[name] = providerClass.getHealthChecker();
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
static async getVectorValidator(provider) {
|
|
2516
|
+
if (this.vectorValidators[provider]) return this.vectorValidators[provider];
|
|
2517
|
+
try {
|
|
2518
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
2519
|
+
if (providerClass.getValidator) {
|
|
2520
|
+
this.vectorValidators[provider] = providerClass.getValidator();
|
|
2521
|
+
return this.vectorValidators[provider];
|
|
2522
|
+
}
|
|
2523
|
+
} catch (e) {
|
|
2524
|
+
console.warn(`[ProviderRegistry] Failed to load validator for ${provider}:`, e);
|
|
2525
|
+
}
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
static async getVectorHealthChecker(provider) {
|
|
2529
|
+
if (this.vectorHealthCheckers[provider]) return this.vectorHealthCheckers[provider];
|
|
2530
|
+
try {
|
|
2531
|
+
const providerClass = await this.loadVectorProviderClass(provider);
|
|
2532
|
+
if (providerClass.getHealthChecker) {
|
|
2533
|
+
this.vectorHealthCheckers[provider] = providerClass.getHealthChecker();
|
|
2534
|
+
return this.vectorHealthCheckers[provider];
|
|
2535
|
+
}
|
|
2536
|
+
} catch (e) {
|
|
2537
|
+
console.warn(`[ProviderRegistry] Failed to load health checker for ${provider}:`, e);
|
|
2538
|
+
}
|
|
2539
|
+
return null;
|
|
2540
|
+
}
|
|
2541
|
+
static async loadVectorProviderClass(provider) {
|
|
2542
|
+
if (this.vectorProviders[provider]) return this.vectorProviders[provider];
|
|
2543
|
+
switch (provider) {
|
|
2544
|
+
case "pinecone": {
|
|
2545
|
+
const { PineconeProvider: PineconeProvider2 } = await Promise.resolve().then(() => (init_PineconeProvider(), PineconeProvider_exports));
|
|
2546
|
+
return PineconeProvider2;
|
|
2547
|
+
}
|
|
2548
|
+
case "pgvector":
|
|
2549
|
+
case "postgresql": {
|
|
2550
|
+
const { PostgreSQLProvider: PostgreSQLProvider2 } = await Promise.resolve().then(() => (init_PostgreSQLProvider(), PostgreSQLProvider_exports));
|
|
2551
|
+
return PostgreSQLProvider2;
|
|
2552
|
+
}
|
|
2553
|
+
case "mongodb": {
|
|
2554
|
+
const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
|
|
2555
|
+
return MongoDBProvider2;
|
|
2556
|
+
}
|
|
2557
|
+
case "milvus": {
|
|
2558
|
+
const { MilvusProvider: MilvusProvider2 } = await Promise.resolve().then(() => (init_MilvusProvider(), MilvusProvider_exports));
|
|
2559
|
+
return MilvusProvider2;
|
|
2560
|
+
}
|
|
2561
|
+
case "qdrant": {
|
|
2562
|
+
const { QdrantProvider: QdrantProvider2 } = await Promise.resolve().then(() => (init_QdrantProvider(), QdrantProvider_exports));
|
|
2563
|
+
return QdrantProvider2;
|
|
2564
|
+
}
|
|
2565
|
+
case "chromadb": {
|
|
2566
|
+
const { ChromaDBProvider: ChromaDBProvider2 } = await Promise.resolve().then(() => (init_ChromaDBProvider(), ChromaDBProvider_exports));
|
|
2567
|
+
return ChromaDBProvider2;
|
|
2568
|
+
}
|
|
2569
|
+
case "redis": {
|
|
2570
|
+
const { RedisProvider: RedisProvider2 } = await Promise.resolve().then(() => (init_RedisProvider(), RedisProvider_exports));
|
|
2571
|
+
return RedisProvider2;
|
|
2572
|
+
}
|
|
2573
|
+
case "weaviate": {
|
|
2574
|
+
const { WeaviateProvider: WeaviateProvider2 } = await Promise.resolve().then(() => (init_WeaviateProvider(), WeaviateProvider_exports));
|
|
2575
|
+
return WeaviateProvider2;
|
|
2576
|
+
}
|
|
2577
|
+
case "universal_rest":
|
|
2578
|
+
case "rest": {
|
|
2579
|
+
const { UniversalVectorProvider: UniversalVectorProvider2 } = await Promise.resolve().then(() => (init_UniversalVectorProvider(), UniversalVectorProvider_exports));
|
|
2580
|
+
return UniversalVectorProvider2;
|
|
2581
|
+
}
|
|
2582
|
+
default:
|
|
2583
|
+
throw new Error(`Unsupported vector provider: ${provider}`);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
static async createVectorProvider(config) {
|
|
2587
|
+
const providerClass = await this.loadVectorProviderClass(config.provider);
|
|
2588
|
+
return new providerClass(config);
|
|
2589
|
+
}
|
|
2590
|
+
static async createGraphProvider(config) {
|
|
2591
|
+
const { provider } = config;
|
|
2592
|
+
if (this.graphProviders[provider]) {
|
|
2593
|
+
return new this.graphProviders[provider](config);
|
|
2594
|
+
}
|
|
2595
|
+
switch (provider) {
|
|
2596
|
+
case "simple": {
|
|
2597
|
+
const { SimpleGraphProvider: SimpleGraphProvider2 } = await Promise.resolve().then(() => (init_SimpleGraphProvider(), SimpleGraphProvider_exports));
|
|
2598
|
+
return new SimpleGraphProvider2(config);
|
|
2599
|
+
}
|
|
2600
|
+
default:
|
|
2601
|
+
throw new Error(`Unsupported graph provider: ${provider}`);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
static createLLMProvider(llmConfig, embeddingConfig) {
|
|
2605
|
+
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2608
|
+
ProviderRegistry.vectorProviders = {};
|
|
2609
|
+
ProviderRegistry.graphProviders = {};
|
|
2610
|
+
ProviderRegistry.vectorValidators = {};
|
|
2611
|
+
ProviderRegistry.vectorHealthCheckers = {};
|
|
2612
|
+
ProviderRegistry.llmValidators = {};
|
|
2613
|
+
ProviderRegistry.llmHealthCheckers = {};
|
|
2614
|
+
|
|
2615
|
+
// src/core/ConfigValidator.ts
|
|
2616
|
+
var ConfigValidator = class {
|
|
2617
|
+
/**
|
|
2618
|
+
* Validates the entire RagConfig object.
|
|
2619
|
+
*/
|
|
2620
|
+
static async validate(config) {
|
|
2621
|
+
const errors = [];
|
|
2622
|
+
if (!config.projectId) {
|
|
2623
|
+
errors.push({
|
|
2624
|
+
field: "projectId",
|
|
2625
|
+
message: "projectId is required",
|
|
2626
|
+
severity: "error"
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
errors.push(...await this.validateVectorDbConfig(config.vectorDb));
|
|
2630
|
+
errors.push(...await this.validateLLMConfig(config.llm));
|
|
2631
|
+
if (config.embedding) {
|
|
2632
|
+
errors.push(...await this.validateEmbeddingConfig(config.embedding));
|
|
2633
|
+
} else if (config.llm.provider === "anthropic") {
|
|
2634
|
+
errors.push({
|
|
2635
|
+
field: "embedding",
|
|
2636
|
+
message: "Embedding config is required when using Anthropic LLM",
|
|
2637
|
+
suggestion: 'Provide embedding config with provider (e.g., "openai" or "ollama")',
|
|
2638
|
+
severity: "error"
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
if (config.ui) {
|
|
2642
|
+
errors.push(...this.validateUIConfig(config.ui));
|
|
2643
|
+
}
|
|
2644
|
+
if (config.rag) {
|
|
2645
|
+
errors.push(...this.validateRAGConfig(config.rag));
|
|
2646
|
+
}
|
|
2647
|
+
return errors;
|
|
2648
|
+
}
|
|
2649
|
+
static async validateVectorDbConfig(config) {
|
|
2650
|
+
const errors = [];
|
|
2651
|
+
if (!config.provider) {
|
|
2652
|
+
errors.push({ field: "vectorDb.provider", message: "Vector database provider is required", severity: "error" });
|
|
2653
|
+
return errors;
|
|
2654
|
+
}
|
|
2655
|
+
if (!config.indexName) {
|
|
2656
|
+
errors.push({ field: "vectorDb.indexName", message: "Vector database index name is required", severity: "error" });
|
|
2657
|
+
}
|
|
2658
|
+
const validator = await ProviderRegistry.getVectorValidator(config.provider);
|
|
2659
|
+
if (validator) {
|
|
2660
|
+
errors.push(...validator.validate(config));
|
|
2661
|
+
} else {
|
|
2662
|
+
this.fallbackVectorValidation(config, errors);
|
|
2663
|
+
}
|
|
2664
|
+
return errors;
|
|
2665
|
+
}
|
|
2666
|
+
static async validateLLMConfig(config) {
|
|
2667
|
+
const errors = [];
|
|
2668
|
+
if (!config.provider) {
|
|
2669
|
+
errors.push({ field: "llm.provider", message: "LLM provider is required", severity: "error" });
|
|
2670
|
+
return errors;
|
|
2671
|
+
}
|
|
2672
|
+
const validator = LLMFactory.getValidator(config.provider);
|
|
2673
|
+
if (validator) {
|
|
2674
|
+
errors.push(...validator.validate(config));
|
|
2675
|
+
} else {
|
|
2676
|
+
this.fallbackLLMValidation(config, errors);
|
|
2677
|
+
}
|
|
2678
|
+
if (config.temperature !== void 0 && (config.temperature < 0 || config.temperature > 2)) {
|
|
2679
|
+
errors.push({ field: "llm.temperature", message: "Temperature must be between 0 and 2", severity: "error" });
|
|
2680
|
+
}
|
|
2681
|
+
return errors;
|
|
2682
|
+
}
|
|
2683
|
+
static async validateEmbeddingConfig(config) {
|
|
2684
|
+
const errors = [];
|
|
2685
|
+
if (!config.provider) {
|
|
2686
|
+
errors.push({ field: "embedding.provider", message: "Embedding provider is required", severity: "error" });
|
|
2687
|
+
return errors;
|
|
2688
|
+
}
|
|
2689
|
+
const validator = LLMFactory.getValidator(config.provider);
|
|
2690
|
+
if (validator) {
|
|
2691
|
+
errors.push(...validator.validate(config));
|
|
2692
|
+
} else {
|
|
2693
|
+
this.fallbackEmbeddingValidation(config, errors);
|
|
2694
|
+
}
|
|
2695
|
+
return errors;
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Temporary fallbacks for providers not yet migrated to the pluggable architecture.
|
|
2699
|
+
*/
|
|
2700
|
+
static fallbackVectorValidation(config, errors) {
|
|
2701
|
+
const opts = config.options || {};
|
|
2702
|
+
switch (config.provider) {
|
|
2703
|
+
case "milvus":
|
|
2704
|
+
if (!opts.baseUrl && !opts.uri && !(opts.host && opts.port)) {
|
|
2705
|
+
errors.push({ field: "vectorDb.options.baseUrl", message: "Milvus connection info required", severity: "error" });
|
|
2706
|
+
}
|
|
2707
|
+
break;
|
|
2708
|
+
case "qdrant":
|
|
2709
|
+
if (!opts.baseUrl && !opts.url) {
|
|
2710
|
+
errors.push({ field: "vectorDb.options.baseUrl", message: "Qdrant URL required", severity: "error" });
|
|
2711
|
+
}
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
static fallbackLLMValidation(config, errors) {
|
|
2716
|
+
if (!config.model) {
|
|
2717
|
+
errors.push({ field: "llm.model", message: "LLM model name is required", severity: "error" });
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
static fallbackEmbeddingValidation(config, errors) {
|
|
2721
|
+
if (!config.model) {
|
|
2722
|
+
errors.push({ field: "embedding.model", message: "Embedding model name is required", severity: "error" });
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
static validateUIConfig(config) {
|
|
2726
|
+
const errors = [];
|
|
2727
|
+
if (config.primaryColor && !this.isValidCSSColor(config.primaryColor)) {
|
|
2728
|
+
errors.push({ field: "ui.primaryColor", message: "Invalid CSS color format", severity: "warning" });
|
|
2729
|
+
}
|
|
2730
|
+
if (config.borderRadius && !UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius)) {
|
|
2731
|
+
errors.push({ field: "ui.borderRadius", message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(", ")}`, severity: "warning" });
|
|
2732
|
+
}
|
|
2733
|
+
return errors;
|
|
2734
|
+
}
|
|
2735
|
+
static validateRAGConfig(config) {
|
|
2736
|
+
const errors = [];
|
|
2737
|
+
if (config.topK !== void 0 && (typeof config.topK !== "number" || config.topK <= 0)) {
|
|
2738
|
+
errors.push({ field: "rag.topK", message: "topK must be a positive integer", severity: "error" });
|
|
2739
|
+
}
|
|
2740
|
+
return errors;
|
|
2741
|
+
}
|
|
2742
|
+
static isValidCSSColor(color) {
|
|
2743
|
+
const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
|
|
2744
|
+
const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
|
|
2745
|
+
const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
|
|
2746
|
+
return hexRegex.test(color) || rgbRegex.test(color) || namedColors.includes(color.toLowerCase());
|
|
2747
|
+
}
|
|
2748
|
+
static async validateAndThrow(config) {
|
|
2749
|
+
const errors = await this.validate(config);
|
|
2750
|
+
const errorItems = errors.filter((e) => e.severity === "error");
|
|
2751
|
+
if (errorItems.length > 0) {
|
|
2752
|
+
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
2753
|
+
throw new Error(`[ConfigValidator] Configuration validation failed:
|
|
2754
|
+
${message}`);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
|
|
2759
|
+
// src/rag/DocumentChunker.ts
|
|
2760
|
+
var DocumentChunker = class {
|
|
2761
|
+
constructor(chunkSize = 1e3, chunkOverlap = 200, separators = ["\n# ", "\n## ", "\n### ", "\n#### ", "\n\n", "\n", " ", ""]) {
|
|
2762
|
+
this.chunkSize = chunkSize;
|
|
2763
|
+
this.chunkOverlap = chunkOverlap;
|
|
2764
|
+
this.separators = separators;
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Split a single text string into overlapping chunks using a recursive strategy.
|
|
2768
|
+
* Preserves structural boundaries (Markdown headers) where possible.
|
|
2769
|
+
*/
|
|
2770
|
+
chunk(text, options = {}) {
|
|
2771
|
+
const {
|
|
2772
|
+
chunkSize = this.chunkSize,
|
|
2773
|
+
chunkOverlap = this.chunkOverlap,
|
|
2774
|
+
docId = `doc_${Date.now()}`,
|
|
2775
|
+
metadata = {},
|
|
2776
|
+
separators = this.separators
|
|
2777
|
+
} = options;
|
|
2778
|
+
const finalChunks = [];
|
|
2779
|
+
const splits = this.recursiveSplit(text, separators, chunkSize);
|
|
2780
|
+
let currentChunk = [];
|
|
2781
|
+
let currentLength = 0;
|
|
2782
|
+
let chunkIndex = 0;
|
|
2783
|
+
for (const split of splits) {
|
|
2784
|
+
if (currentLength + split.length > chunkSize && currentChunk.length > 0) {
|
|
2785
|
+
finalChunks.push({
|
|
2786
|
+
id: `${docId}_chunk_${chunkIndex++}`,
|
|
2787
|
+
content: currentChunk.join("").trim(),
|
|
2788
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex: chunkIndex - 1 })
|
|
2789
|
+
});
|
|
2790
|
+
const overlapItems = [];
|
|
2791
|
+
let overlapLen = 0;
|
|
2792
|
+
for (let i = currentChunk.length - 1; i >= 0; i--) {
|
|
2793
|
+
if (overlapLen + currentChunk[i].length <= chunkOverlap) {
|
|
2794
|
+
overlapItems.unshift(currentChunk[i]);
|
|
2795
|
+
overlapLen += currentChunk[i].length;
|
|
2796
|
+
} else {
|
|
2797
|
+
break;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
currentChunk = overlapItems;
|
|
2801
|
+
currentLength = overlapLen;
|
|
2802
|
+
}
|
|
2803
|
+
currentChunk.push(split);
|
|
2804
|
+
currentLength += split.length;
|
|
2805
|
+
}
|
|
2806
|
+
if (currentChunk.length > 0) {
|
|
2807
|
+
finalChunks.push({
|
|
2808
|
+
id: `${docId}_chunk_${chunkIndex}`,
|
|
2809
|
+
content: currentChunk.join("").trim(),
|
|
2810
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex })
|
|
2811
|
+
});
|
|
2583
2812
|
}
|
|
2584
|
-
return
|
|
2813
|
+
return finalChunks;
|
|
2585
2814
|
}
|
|
2586
|
-
|
|
2587
|
-
const
|
|
2588
|
-
|
|
2589
|
-
|
|
2815
|
+
recursiveSplit(text, separators, chunkSize) {
|
|
2816
|
+
const finalSplits = [];
|
|
2817
|
+
let separator = separators[separators.length - 1];
|
|
2818
|
+
let nextSeparators = [];
|
|
2819
|
+
for (let i = 0; i < separators.length; i++) {
|
|
2820
|
+
const sep = separators[i];
|
|
2821
|
+
if (text.includes(sep)) {
|
|
2822
|
+
separator = sep;
|
|
2823
|
+
nextSeparators = separators.slice(i + 1);
|
|
2824
|
+
break;
|
|
2825
|
+
}
|
|
2590
2826
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
if (
|
|
2596
|
-
|
|
2827
|
+
const parts = text.split(separator);
|
|
2828
|
+
for (const part of parts) {
|
|
2829
|
+
if (part.length <= chunkSize) {
|
|
2830
|
+
finalSplits.push(part + (part === parts[parts.length - 1] ? "" : separator));
|
|
2831
|
+
} else if (nextSeparators.length > 0) {
|
|
2832
|
+
finalSplits.push(...this.recursiveSplit(part, nextSeparators, chunkSize));
|
|
2833
|
+
} else {
|
|
2834
|
+
finalSplits.push(part);
|
|
2597
2835
|
}
|
|
2598
|
-
return true;
|
|
2599
|
-
} catch (err) {
|
|
2600
|
-
console.error("[UniversalLLMAdapter] Ping failed:", err);
|
|
2601
|
-
return false;
|
|
2602
2836
|
}
|
|
2837
|
+
return finalSplits;
|
|
2838
|
+
}
|
|
2839
|
+
chunkMany(documents) {
|
|
2840
|
+
return documents.flatMap(
|
|
2841
|
+
(doc) => this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
|
2842
|
+
);
|
|
2603
2843
|
}
|
|
2604
2844
|
};
|
|
2605
2845
|
|
|
2606
|
-
// src/
|
|
2607
|
-
var
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
switch (llmConfig.provider) {
|
|
2611
|
-
case "openai":
|
|
2612
|
-
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
2613
|
-
case "anthropic":
|
|
2614
|
-
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2615
|
-
case "ollama":
|
|
2616
|
-
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2617
|
-
case "gemini":
|
|
2618
|
-
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2619
|
-
case "rest":
|
|
2620
|
-
case "universal_rest":
|
|
2621
|
-
case "custom":
|
|
2622
|
-
return new UniversalLLMAdapter(llmConfig);
|
|
2623
|
-
default:
|
|
2624
|
-
if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
|
|
2625
|
-
return new UniversalLLMAdapter(llmConfig);
|
|
2626
|
-
}
|
|
2627
|
-
throw new Error(
|
|
2628
|
-
`[LLMFactory] Unknown provider "${llmConfig.provider}". Supported: openai | anthropic | ollama | rest | custom`
|
|
2629
|
-
);
|
|
2630
|
-
}
|
|
2846
|
+
// src/rag/EntityExtractor.ts
|
|
2847
|
+
var EntityExtractor = class {
|
|
2848
|
+
constructor(llm) {
|
|
2849
|
+
this.llm = llm;
|
|
2631
2850
|
}
|
|
2632
2851
|
/**
|
|
2633
|
-
*
|
|
2634
|
-
* Useful when the LLM provider (e.g. Anthropic) doesn't support embeddings.
|
|
2852
|
+
* Extract nodes and edges from a text chunk.
|
|
2635
2853
|
*/
|
|
2636
|
-
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2854
|
+
async extract(text) {
|
|
2855
|
+
const prompt = `
|
|
2856
|
+
Extract entities and relationships from the following text.
|
|
2857
|
+
Format the output as JSON with two keys: "nodes" (array of {id, label, properties}) and "edges" (array of {source, target, type, properties}).
|
|
2858
|
+
Use the same ID for the same entity.
|
|
2859
|
+
|
|
2860
|
+
IMPORTANT: Ensure all property values are valid JSON types (strings, numbers, booleans, or null).
|
|
2861
|
+
DO NOT include mathematical expressions like "4.5/5" as numbers; use strings instead.
|
|
2862
|
+
|
|
2863
|
+
Text:
|
|
2864
|
+
"${text}"
|
|
2865
|
+
|
|
2866
|
+
Output JSON:
|
|
2867
|
+
`;
|
|
2868
|
+
const response = await this.llm.chat([
|
|
2869
|
+
{ role: "system", content: "You are an expert at knowledge graph extraction. Respond only with valid JSON." },
|
|
2870
|
+
{ role: "user", content: prompt }
|
|
2871
|
+
], "");
|
|
2872
|
+
try {
|
|
2873
|
+
const cleanJson = response.replace(/```json\n?|\n?```/g, "").trim();
|
|
2874
|
+
return JSON.parse(cleanJson);
|
|
2875
|
+
} catch (e) {
|
|
2876
|
+
console.warn("[EntityExtractor] Failed to parse LLM response as JSON:", response);
|
|
2877
|
+
return { nodes: [], edges: [] };
|
|
2878
|
+
}
|
|
2645
2879
|
}
|
|
2646
2880
|
};
|
|
2647
2881
|
|
|
2648
|
-
// src/
|
|
2649
|
-
var
|
|
2650
|
-
static registerVectorProvider(name, providerClass) {
|
|
2651
|
-
this.vectorProviders[name] = providerClass;
|
|
2652
|
-
}
|
|
2882
|
+
// src/rag/Reranker.ts
|
|
2883
|
+
var Reranker = class {
|
|
2653
2884
|
/**
|
|
2654
|
-
*
|
|
2885
|
+
* Re-ranks matches based on a secondary relevance score.
|
|
2886
|
+
* In a production environment, this would call a Cross-Encoder model.
|
|
2887
|
+
* Here we implement a placeholder that filters by score and limits count.
|
|
2655
2888
|
*/
|
|
2656
|
-
|
|
2657
|
-
|
|
2889
|
+
async rerank(matches, query, limit = 5) {
|
|
2890
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
2658
2891
|
}
|
|
2892
|
+
};
|
|
2893
|
+
|
|
2894
|
+
// src/rag/LlamaIndexIngestor.ts
|
|
2895
|
+
var LlamaIndexIngestor = class {
|
|
2659
2896
|
/**
|
|
2660
|
-
*
|
|
2661
|
-
*
|
|
2897
|
+
* Chunks document content using LlamaIndex SentenceSplitter.
|
|
2898
|
+
* This respects sentence and paragraph boundaries much more effectively
|
|
2899
|
+
* than standard character-count splitting.
|
|
2662
2900
|
*/
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
return new MilvusProvider2(config);
|
|
2685
|
-
}
|
|
2686
|
-
case "qdrant": {
|
|
2687
|
-
const { QdrantProvider: QdrantProvider2 } = await Promise.resolve().then(() => (init_QdrantProvider(), QdrantProvider_exports));
|
|
2688
|
-
return new QdrantProvider2(config);
|
|
2689
|
-
}
|
|
2690
|
-
case "chromadb": {
|
|
2691
|
-
const { ChromaDBProvider: ChromaDBProvider2 } = await Promise.resolve().then(() => (init_ChromaDBProvider(), ChromaDBProvider_exports));
|
|
2692
|
-
return new ChromaDBProvider2(config);
|
|
2693
|
-
}
|
|
2694
|
-
case "redis": {
|
|
2695
|
-
const { RedisProvider: RedisProvider2 } = await Promise.resolve().then(() => (init_RedisProvider(), RedisProvider_exports));
|
|
2696
|
-
return new RedisProvider2(config);
|
|
2697
|
-
}
|
|
2698
|
-
case "weaviate": {
|
|
2699
|
-
const { WeaviateProvider: WeaviateProvider2 } = await Promise.resolve().then(() => (init_WeaviateProvider(), WeaviateProvider_exports));
|
|
2700
|
-
return new WeaviateProvider2(config);
|
|
2701
|
-
}
|
|
2702
|
-
case "universal_rest":
|
|
2703
|
-
case "rest": {
|
|
2704
|
-
const { UniversalVectorProvider: UniversalVectorProvider2 } = await Promise.resolve().then(() => (init_UniversalVectorProvider(), UniversalVectorProvider_exports));
|
|
2705
|
-
return new UniversalVectorProvider2(config);
|
|
2706
|
-
}
|
|
2707
|
-
default:
|
|
2708
|
-
throw new Error(
|
|
2709
|
-
`[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).`
|
|
2710
|
-
);
|
|
2901
|
+
async chunk(text, options = {}) {
|
|
2902
|
+
var _a, _b;
|
|
2903
|
+
try {
|
|
2904
|
+
const { Document, SentenceSplitter, MetadataMode } = await import(`${"llamaindex"}`);
|
|
2905
|
+
const splitter = new SentenceSplitter({
|
|
2906
|
+
chunkSize: (_a = options.chunkSize) != null ? _a : 1e3,
|
|
2907
|
+
chunkOverlap: (_b = options.chunkOverlap) != null ? _b : 200
|
|
2908
|
+
});
|
|
2909
|
+
const doc = new Document({ text, metadata: options.metadata || {} });
|
|
2910
|
+
const nodes = splitter.getNodesFromDocuments([doc]);
|
|
2911
|
+
return nodes.map((node, index) => ({
|
|
2912
|
+
id: `${options.docId || "doc"}_node_${index}`,
|
|
2913
|
+
content: node.getContent(MetadataMode.ALL),
|
|
2914
|
+
metadata: __spreadProps(__spreadValues(__spreadValues({}, options.metadata), node.metadata), {
|
|
2915
|
+
nodeId: node.id_,
|
|
2916
|
+
chunkIndex: index
|
|
2917
|
+
})
|
|
2918
|
+
}));
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
console.warn("[LlamaIndexIngestor] LlamaIndex package not found or failed. Falling back to default chunker.");
|
|
2921
|
+
throw error;
|
|
2711
2922
|
}
|
|
2712
2923
|
}
|
|
2713
2924
|
/**
|
|
2714
|
-
*
|
|
2925
|
+
* Batch processing for multiple documents.
|
|
2715
2926
|
*/
|
|
2716
|
-
|
|
2717
|
-
const
|
|
2718
|
-
|
|
2719
|
-
|
|
2927
|
+
async chunkMany(documents, options = {}) {
|
|
2928
|
+
const allChunks = [];
|
|
2929
|
+
for (const doc of documents) {
|
|
2930
|
+
const chunks = await this.chunk(doc.content, __spreadProps(__spreadValues({}, options), { docId: doc.docId, metadata: doc.metadata }));
|
|
2931
|
+
allChunks.push(...chunks);
|
|
2720
2932
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2933
|
+
return allChunks;
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
|
|
2937
|
+
// src/core/LangChainAgent.ts
|
|
2938
|
+
var LangChainAgent = class {
|
|
2939
|
+
constructor(pipeline, config) {
|
|
2940
|
+
this.pipeline = pipeline;
|
|
2941
|
+
this.config = config;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Initializes the agent with the RAG pipeline as a primary tool.
|
|
2945
|
+
* Dynamically imports LangChain dependencies to avoid build errors if missing.
|
|
2946
|
+
*/
|
|
2947
|
+
async initialize(chatModel) {
|
|
2948
|
+
try {
|
|
2949
|
+
const { DynamicTool } = await import(`${"@langchain/core/tools"}`);
|
|
2950
|
+
const { ChatPromptTemplate, MessagesPlaceholder } = await import(`${"@langchain/core/prompts"}`);
|
|
2951
|
+
const { AgentExecutor, createOpenAIFunctionsAgent } = await import(`${"langchain/agents"}`);
|
|
2952
|
+
const searchTool = new DynamicTool({
|
|
2953
|
+
name: "document_search",
|
|
2954
|
+
description: "Use this tool to search through the knowledge base and document repository. Input should be a specific search query.",
|
|
2955
|
+
func: async (query) => {
|
|
2956
|
+
const response = await this.pipeline.ask(query);
|
|
2957
|
+
return `Search Results:
|
|
2958
|
+
${response.reply}
|
|
2959
|
+
|
|
2960
|
+
Sources Used: ${JSON.stringify(response.sources.map((s) => s.id))}`;
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
const tools = [searchTool];
|
|
2964
|
+
const prompt = ChatPromptTemplate.fromMessages([
|
|
2965
|
+
["system", this.config.llm.systemPrompt || "You are a helpful AI assistant with access to a document search tool."],
|
|
2966
|
+
new MessagesPlaceholder("chat_history"),
|
|
2967
|
+
["human", "{input}"],
|
|
2968
|
+
new MessagesPlaceholder("agent_scratchpad")
|
|
2969
|
+
]);
|
|
2970
|
+
const agent = await createOpenAIFunctionsAgent({
|
|
2971
|
+
llm: chatModel,
|
|
2972
|
+
tools,
|
|
2973
|
+
prompt
|
|
2974
|
+
});
|
|
2975
|
+
this.executor = new AgentExecutor({
|
|
2976
|
+
agent,
|
|
2977
|
+
tools
|
|
2978
|
+
});
|
|
2979
|
+
} catch (error) {
|
|
2980
|
+
console.error("[LangChainAgent] Failed to initialize. Ensure 'langchain' and '@langchain/core' are installed.");
|
|
2981
|
+
throw error;
|
|
2733
2982
|
}
|
|
2734
2983
|
}
|
|
2735
2984
|
/**
|
|
2736
|
-
*
|
|
2985
|
+
* Run the agentic flow.
|
|
2737
2986
|
*/
|
|
2738
|
-
|
|
2739
|
-
|
|
2987
|
+
async run(input, chatHistory = []) {
|
|
2988
|
+
if (!this.executor) {
|
|
2989
|
+
throw new Error("[LangChainAgent] Agent not initialized. Call initialize() first.");
|
|
2990
|
+
}
|
|
2991
|
+
const response = await this.executor.invoke({
|
|
2992
|
+
input,
|
|
2993
|
+
chat_history: chatHistory
|
|
2994
|
+
});
|
|
2995
|
+
return response.output;
|
|
2740
2996
|
}
|
|
2741
2997
|
};
|
|
2742
|
-
ProviderRegistry.vectorProviders = {};
|
|
2743
|
-
ProviderRegistry.graphProviders = {};
|
|
2744
2998
|
|
|
2745
2999
|
// src/core/BatchProcessor.ts
|
|
2746
3000
|
function isTransientError(error) {
|
|
@@ -3042,122 +3296,143 @@ var EmbeddingStrategyResolver = class {
|
|
|
3042
3296
|
}
|
|
3043
3297
|
};
|
|
3044
3298
|
|
|
3045
|
-
// src/core/
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3299
|
+
// src/core/QueryProcessor.ts
|
|
3300
|
+
var QueryProcessor = class {
|
|
3301
|
+
/**
|
|
3302
|
+
* Normalizes a string value by collapsing whitespace and trimming.
|
|
3303
|
+
*/
|
|
3304
|
+
static normalizeHintValue(value) {
|
|
3305
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* Checks if a string is likely a question word or common prompt phrase.
|
|
3309
|
+
*/
|
|
3310
|
+
static isLikelyPromptPhrase(value) {
|
|
3311
|
+
return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Scans a natural language question for potential metadata hints and keywords.
|
|
3315
|
+
*/
|
|
3316
|
+
static extractQueryFieldHints(question) {
|
|
3317
|
+
var _a, _b, _c, _d;
|
|
3318
|
+
if (!question.trim()) return [];
|
|
3319
|
+
const hints = /* @__PURE__ */ new Map();
|
|
3320
|
+
const addHint = (value, field) => {
|
|
3321
|
+
const normalizedValue = this.normalizeHintValue(value);
|
|
3322
|
+
if (!normalizedValue) return;
|
|
3323
|
+
const normalizedField = field ? field.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim() : void 0;
|
|
3324
|
+
const key = `${normalizedField != null ? normalizedField : "*"}::${normalizedValue.toLowerCase()}`;
|
|
3325
|
+
if (!hints.has(key)) {
|
|
3326
|
+
hints.set(key, __spreadValues({
|
|
3327
|
+
value: normalizedValue
|
|
3328
|
+
}, normalizedField ? { field: normalizedField } : {}));
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
3332
|
+
addHint(match[1]);
|
|
3065
3333
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
if (field && !isLikelyPromptPhrase(field)) {
|
|
3122
|
-
addHint(value, field);
|
|
3123
|
-
} else {
|
|
3124
|
-
addHint(value);
|
|
3334
|
+
const naturalQuestionPatterns = [
|
|
3335
|
+
/\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3336
|
+
/\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3337
|
+
/\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
|
|
3338
|
+
];
|
|
3339
|
+
const personCompanyPatterns = [
|
|
3340
|
+
/\bcompany(?:\s+name)?\s+(?:of|for)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
3341
|
+
/\b(?:which|what)\s+company\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi,
|
|
3342
|
+
/\bwhere\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi
|
|
3343
|
+
];
|
|
3344
|
+
for (const pattern of personCompanyPatterns) {
|
|
3345
|
+
for (const match of question.matchAll(pattern)) {
|
|
3346
|
+
const name = match[1];
|
|
3347
|
+
if (name) addHint(name, "name");
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
const universalPatterns = [
|
|
3351
|
+
{ regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
|
|
3352
|
+
{ regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
|
|
3353
|
+
{ 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 },
|
|
3354
|
+
{ regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
|
|
3355
|
+
{ regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
|
|
3356
|
+
{ regex: /"([^"]{2,120})"/g, group: 1 },
|
|
3357
|
+
{ regex: /'([^']{2,120})'/g, group: 1 }
|
|
3358
|
+
];
|
|
3359
|
+
for (const p of universalPatterns) {
|
|
3360
|
+
for (const match of question.matchAll(p.regex)) {
|
|
3361
|
+
const val = p.group ? (_a = match[p.group]) != null ? _a : match[0] : match[0];
|
|
3362
|
+
if (!val) continue;
|
|
3363
|
+
if (p.field) addHint(val, p.field);
|
|
3364
|
+
else addHint(val);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
for (const pattern of naturalQuestionPatterns) {
|
|
3368
|
+
for (const match of question.matchAll(pattern)) {
|
|
3369
|
+
const value = (_b = match[2]) != null ? _b : match[1];
|
|
3370
|
+
if (value) addHint(value);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
|
|
3374
|
+
const valuePattern = `([^\\n?.!,]{1,120}?)`;
|
|
3375
|
+
const fieldValuePatterns = [
|
|
3376
|
+
new RegExp(`\\b${fieldPattern}\\s*(?:=|:)\\s*["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
3377
|
+
new RegExp(`\\b${fieldPattern}\\s+(?:is|are|was|were|equals?|equal to|named|called)\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi"),
|
|
3378
|
+
new RegExp(`\\bwith\\s+${fieldPattern}\\s+["']?${valuePattern}["']?(?=[?.!,]|$)`, "gi")
|
|
3379
|
+
];
|
|
3380
|
+
for (const pattern of fieldValuePatterns) {
|
|
3381
|
+
for (const match of question.matchAll(pattern)) {
|
|
3382
|
+
const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
|
|
3383
|
+
const value = (_d = match[2]) != null ? _d : "";
|
|
3384
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
3385
|
+
addHint(value, field);
|
|
3386
|
+
} else {
|
|
3387
|
+
addHint(value);
|
|
3388
|
+
}
|
|
3125
3389
|
}
|
|
3126
3390
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
addHint(match[0]);
|
|
3130
|
-
}
|
|
3131
|
-
return [...hints.values()];
|
|
3132
|
-
}
|
|
3133
|
-
function buildQueryFilter(question, hints) {
|
|
3134
|
-
const filter = { metadata: {}, keywords: [], queryText: question };
|
|
3135
|
-
for (const hint of hints) {
|
|
3136
|
-
if (hint.field) {
|
|
3137
|
-
filter.metadata[hint.field] = hint.value;
|
|
3138
|
-
} else {
|
|
3139
|
-
filter.keywords.push(hint.value);
|
|
3391
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
3392
|
+
addHint(match[0]);
|
|
3140
3393
|
}
|
|
3394
|
+
return [...hints.values()];
|
|
3141
3395
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3396
|
+
/**
|
|
3397
|
+
* Constructs a QueryFilter object from extracted hints.
|
|
3398
|
+
*/
|
|
3399
|
+
static buildQueryFilter(question, hints) {
|
|
3400
|
+
const filter = { metadata: {}, keywords: [], queryText: question };
|
|
3401
|
+
for (const hint of hints) {
|
|
3402
|
+
if (hint.field) {
|
|
3403
|
+
filter.metadata[hint.field] = hint.value;
|
|
3404
|
+
} else {
|
|
3405
|
+
filter.keywords.push(hint.value);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
3409
|
+
const term = this.normalizeHintValue(match[0]);
|
|
3410
|
+
if (term && !filter.keywords.includes(term)) filter.keywords.push(term);
|
|
3411
|
+
}
|
|
3412
|
+
if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
|
|
3413
|
+
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
3414
|
+
return filter;
|
|
3145
3415
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
}
|
|
3416
|
+
};
|
|
3417
|
+
|
|
3418
|
+
// src/core/Pipeline.ts
|
|
3150
3419
|
var Pipeline = class {
|
|
3151
3420
|
constructor(config) {
|
|
3152
|
-
this.initialised = false;
|
|
3153
|
-
var _a, _b, _c, _d;
|
|
3154
3421
|
this.config = config;
|
|
3422
|
+
this.embeddingCache = /* @__PURE__ */ new Map();
|
|
3423
|
+
this.initialised = false;
|
|
3424
|
+
var _a, _b, _c, _d, _e;
|
|
3155
3425
|
this.chunker = new DocumentChunker(
|
|
3156
3426
|
(_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
|
|
3157
3427
|
(_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
|
|
3158
3428
|
);
|
|
3429
|
+
if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
|
|
3430
|
+
this.llamaIngestor = new LlamaIndexIngestor();
|
|
3431
|
+
}
|
|
3432
|
+
this.reranker = new Reranker();
|
|
3159
3433
|
}
|
|
3160
3434
|
async initialize() {
|
|
3435
|
+
var _a;
|
|
3161
3436
|
if (this.initialised) return;
|
|
3162
3437
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
3163
3438
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -3172,6 +3447,10 @@ var Pipeline = class {
|
|
|
3172
3447
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
3173
3448
|
}
|
|
3174
3449
|
await this.vectorDB.initialize();
|
|
3450
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
|
|
3451
|
+
this.agent = new LangChainAgent(this, this.config);
|
|
3452
|
+
await this.agent.initialize(this.llmProvider);
|
|
3453
|
+
}
|
|
3175
3454
|
this.initialised = true;
|
|
3176
3455
|
}
|
|
3177
3456
|
/**
|
|
@@ -3184,69 +3463,21 @@ var Pipeline = class {
|
|
|
3184
3463
|
const results = [];
|
|
3185
3464
|
for (const doc of documents) {
|
|
3186
3465
|
try {
|
|
3187
|
-
const chunks = this.
|
|
3188
|
-
|
|
3189
|
-
metadata: doc.metadata
|
|
3190
|
-
});
|
|
3191
|
-
const embedBatchOptions = {
|
|
3192
|
-
batchSize: 50,
|
|
3193
|
-
maxRetries: 3,
|
|
3194
|
-
initialDelayMs: 100
|
|
3195
|
-
};
|
|
3196
|
-
const vectors = await BatchProcessor.mapWithRetry(
|
|
3197
|
-
chunks.map((c) => c.content),
|
|
3198
|
-
(text) => this.embeddingProvider.embed(text, { taskType: "document" }),
|
|
3199
|
-
embedBatchOptions
|
|
3200
|
-
);
|
|
3201
|
-
if (vectors.length !== chunks.length) {
|
|
3202
|
-
throw new Error(`Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks`);
|
|
3203
|
-
}
|
|
3466
|
+
const chunks = await this.prepareChunks(doc);
|
|
3467
|
+
const vectors = await this.processEmbeddings(chunks);
|
|
3204
3468
|
const upsertDocs = chunks.map((chunk, i) => ({
|
|
3205
3469
|
id: chunk.id,
|
|
3206
3470
|
vector: vectors[i],
|
|
3207
3471
|
content: chunk.content,
|
|
3208
|
-
metadata: chunk.metadata
|
|
3209
|
-
}));
|
|
3210
|
-
const
|
|
3211
|
-
batchSize: 100,
|
|
3212
|
-
maxRetries: 3,
|
|
3213
|
-
initialDelayMs: 100
|
|
3214
|
-
};
|
|
3215
|
-
const upsertResult = await BatchProcessor.processBatch(
|
|
3216
|
-
upsertDocs,
|
|
3217
|
-
(batch) => this.vectorDB.batchUpsert(batch, ns),
|
|
3218
|
-
upsertBatchOptions
|
|
3219
|
-
);
|
|
3220
|
-
if (upsertResult.errors.length > 0) {
|
|
3221
|
-
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed for doc ${doc.docId}`);
|
|
3222
|
-
}
|
|
3472
|
+
metadata: chunk.metadata
|
|
3473
|
+
}));
|
|
3474
|
+
const totalProcessed = await this.processUpserts(upsertDocs, ns);
|
|
3223
3475
|
results.push({
|
|
3224
3476
|
docId: doc.docId,
|
|
3225
|
-
chunksIngested:
|
|
3477
|
+
chunksIngested: totalProcessed
|
|
3226
3478
|
});
|
|
3227
3479
|
if (this.graphDB && this.entityExtractor) {
|
|
3228
|
-
|
|
3229
|
-
const extractionOptions = {
|
|
3230
|
-
batchSize: 2,
|
|
3231
|
-
// Low concurrency for LLM extraction
|
|
3232
|
-
maxRetries: 1,
|
|
3233
|
-
initialDelayMs: 500
|
|
3234
|
-
};
|
|
3235
|
-
await BatchProcessor.processBatch(
|
|
3236
|
-
chunks,
|
|
3237
|
-
async (batch) => {
|
|
3238
|
-
for (const chunk of batch) {
|
|
3239
|
-
try {
|
|
3240
|
-
const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
|
|
3241
|
-
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
3242
|
-
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
3243
|
-
} catch (err) {
|
|
3244
|
-
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
},
|
|
3248
|
-
extractionOptions
|
|
3249
|
-
);
|
|
3480
|
+
await this.processGraphIngestion(doc.docId, chunks);
|
|
3250
3481
|
}
|
|
3251
3482
|
} catch (error) {
|
|
3252
3483
|
console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
|
|
@@ -3255,45 +3486,210 @@ var Pipeline = class {
|
|
|
3255
3486
|
}
|
|
3256
3487
|
return results;
|
|
3257
3488
|
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Step 1: Chunk the document content.
|
|
3491
|
+
*/
|
|
3492
|
+
async prepareChunks(doc) {
|
|
3493
|
+
var _a, _b;
|
|
3494
|
+
if (this.llamaIngestor) {
|
|
3495
|
+
return await this.llamaIngestor.chunk(doc.content, {
|
|
3496
|
+
docId: doc.docId,
|
|
3497
|
+
metadata: doc.metadata,
|
|
3498
|
+
chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
|
|
3499
|
+
chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
return this.chunker.chunk(doc.content, {
|
|
3503
|
+
docId: doc.docId,
|
|
3504
|
+
metadata: doc.metadata
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Step 2: Generate embeddings for chunks with retry logic.
|
|
3509
|
+
*/
|
|
3510
|
+
async processEmbeddings(chunks) {
|
|
3511
|
+
const embedBatchOptions = {
|
|
3512
|
+
batchSize: 50,
|
|
3513
|
+
maxRetries: 3,
|
|
3514
|
+
initialDelayMs: 100
|
|
3515
|
+
};
|
|
3516
|
+
const vectors = await BatchProcessor.mapWithRetry(
|
|
3517
|
+
chunks.map((c) => c.content),
|
|
3518
|
+
(text) => this.embeddingProvider.embed(text, { taskType: "document" }),
|
|
3519
|
+
embedBatchOptions
|
|
3520
|
+
);
|
|
3521
|
+
if (vectors.length !== chunks.length) {
|
|
3522
|
+
throw new Error(`Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks`);
|
|
3523
|
+
}
|
|
3524
|
+
return vectors;
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Step 3: Upsert chunks to vector database with retry logic.
|
|
3528
|
+
*/
|
|
3529
|
+
async processUpserts(upsertDocs, namespace) {
|
|
3530
|
+
const upsertBatchOptions = {
|
|
3531
|
+
batchSize: 100,
|
|
3532
|
+
maxRetries: 3,
|
|
3533
|
+
initialDelayMs: 100
|
|
3534
|
+
};
|
|
3535
|
+
const upsertResult = await BatchProcessor.processBatch(
|
|
3536
|
+
upsertDocs,
|
|
3537
|
+
(batch) => this.vectorDB.batchUpsert(batch, namespace),
|
|
3538
|
+
upsertBatchOptions
|
|
3539
|
+
);
|
|
3540
|
+
if (upsertResult.errors.length > 0) {
|
|
3541
|
+
console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
|
|
3542
|
+
}
|
|
3543
|
+
return upsertResult.totalProcessed;
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Step 4: Optional graph-based entity extraction and ingestion.
|
|
3547
|
+
*/
|
|
3548
|
+
async processGraphIngestion(docId, chunks) {
|
|
3549
|
+
console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
|
|
3550
|
+
const extractionOptions = {
|
|
3551
|
+
batchSize: 2,
|
|
3552
|
+
// Low concurrency for LLM extraction
|
|
3553
|
+
maxRetries: 1,
|
|
3554
|
+
initialDelayMs: 500
|
|
3555
|
+
};
|
|
3556
|
+
await BatchProcessor.processBatch(
|
|
3557
|
+
chunks,
|
|
3558
|
+
async (batch) => {
|
|
3559
|
+
for (const chunk of batch) {
|
|
3560
|
+
try {
|
|
3561
|
+
const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
|
|
3562
|
+
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
3563
|
+
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
3564
|
+
} catch (err) {
|
|
3565
|
+
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
},
|
|
3569
|
+
extractionOptions
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3258
3572
|
async ask(question, history = [], namespace) {
|
|
3259
|
-
var _a
|
|
3573
|
+
var _a;
|
|
3260
3574
|
await this.initialize();
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3575
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
|
|
3576
|
+
console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
|
|
3577
|
+
const agentReply = await this.agent.run(question, history);
|
|
3578
|
+
return { reply: agentReply, sources: [] };
|
|
3579
|
+
}
|
|
3580
|
+
const stream = this.askStream(question, history, namespace);
|
|
3581
|
+
let reply = "";
|
|
3582
|
+
let sources = [];
|
|
3583
|
+
let graphData;
|
|
3264
3584
|
try {
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3585
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
3586
|
+
const chunk = temp.value;
|
|
3587
|
+
if (typeof chunk === "string") {
|
|
3588
|
+
reply += chunk;
|
|
3589
|
+
} else if ("sources" in chunk) {
|
|
3590
|
+
sources = chunk.sources;
|
|
3591
|
+
graphData = chunk.graphData;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
} catch (temp) {
|
|
3595
|
+
error = [temp];
|
|
3596
|
+
} finally {
|
|
3597
|
+
try {
|
|
3598
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
3599
|
+
} finally {
|
|
3600
|
+
if (error)
|
|
3601
|
+
throw error[0];
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return { reply, sources, graphData };
|
|
3605
|
+
}
|
|
3606
|
+
/**
|
|
3607
|
+
* High-performance streaming RAG flow.
|
|
3608
|
+
* Yields text chunks first, then the retrieval metadata at the end.
|
|
3609
|
+
*/
|
|
3610
|
+
askStream(_0) {
|
|
3611
|
+
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
3612
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3613
|
+
yield new __await(this.initialize());
|
|
3614
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
3615
|
+
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
3616
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
3617
|
+
try {
|
|
3618
|
+
let searchQuery = question;
|
|
3619
|
+
if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
|
|
3620
|
+
searchQuery = yield new __await(this.rewriteQuery(question, history));
|
|
3621
|
+
}
|
|
3622
|
+
const hints = QueryProcessor.extractQueryFieldHints(question);
|
|
3623
|
+
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
3624
|
+
const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
|
|
3625
|
+
namespace: ns,
|
|
3626
|
+
topK: topK * 2,
|
|
3627
|
+
filter
|
|
3628
|
+
}));
|
|
3629
|
+
let sources = rawSources.filter((m) => m.score >= scoreThreshold);
|
|
3630
|
+
if ((_f = this.config.rag) == null ? void 0 : _f.useReranking) {
|
|
3631
|
+
sources = yield new __await(this.reranker.rerank(sources, question, topK));
|
|
3632
|
+
} else {
|
|
3633
|
+
sources = sources.slice(0, topK);
|
|
3634
|
+
}
|
|
3635
|
+
let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
3280
3636
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3637
|
+
if (graphData && graphData.nodes.length > 0) {
|
|
3638
|
+
const graphContext = graphData.nodes.map(
|
|
3639
|
+
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
3640
|
+
).join("\n");
|
|
3641
|
+
context = `GRAPH KNOWLEDGE:
|
|
3286
3642
|
${graphContext}
|
|
3287
3643
|
|
|
3288
3644
|
VECTOR CONTEXT:
|
|
3289
3645
|
${context}`;
|
|
3646
|
+
}
|
|
3647
|
+
const messages = [...history, { role: "user", content: question }];
|
|
3648
|
+
if (this.llmProvider.chatStream) {
|
|
3649
|
+
try {
|
|
3650
|
+
for (var iter = __forAwait(this.llmProvider.chatStream(messages, context)), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
3651
|
+
const chunk = temp.value;
|
|
3652
|
+
yield chunk;
|
|
3653
|
+
}
|
|
3654
|
+
} catch (temp) {
|
|
3655
|
+
error = [temp];
|
|
3656
|
+
} finally {
|
|
3657
|
+
try {
|
|
3658
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
3659
|
+
} finally {
|
|
3660
|
+
if (error)
|
|
3661
|
+
throw error[0];
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
} else {
|
|
3665
|
+
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
3666
|
+
yield reply;
|
|
3667
|
+
}
|
|
3668
|
+
yield { reply: "", sources, graphData };
|
|
3669
|
+
} catch (error2) {
|
|
3670
|
+
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3290
3671
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Universal retrieval method combining all enabled providers.
|
|
3676
|
+
*/
|
|
3677
|
+
async retrieve(query, options) {
|
|
3678
|
+
var _a, _b, _c;
|
|
3679
|
+
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
3680
|
+
const topK = (_b = options.topK) != null ? _b : 5;
|
|
3681
|
+
const cacheKey = `${ns}::${query}`;
|
|
3682
|
+
let queryVector = this.embeddingCache.get(cacheKey);
|
|
3683
|
+
const [retrievedVector, graphData] = await Promise.all([
|
|
3684
|
+
queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
|
|
3685
|
+
this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
3686
|
+
]);
|
|
3687
|
+
if (!queryVector) {
|
|
3688
|
+
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
3689
|
+
queryVector = retrievedVector;
|
|
3296
3690
|
}
|
|
3691
|
+
const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
|
|
3692
|
+
return { sources, graphData };
|
|
3297
3693
|
}
|
|
3298
3694
|
/**
|
|
3299
3695
|
* Rewrite the user query for better retrieval performance.
|
|
@@ -3324,11 +3720,11 @@ var ProviderHealthCheck = class {
|
|
|
3324
3720
|
*/
|
|
3325
3721
|
static async checkVectorProvider(config) {
|
|
3326
3722
|
const timestamp = Date.now();
|
|
3327
|
-
const configErrors = ConfigValidator.validate({
|
|
3723
|
+
const configErrors = await ConfigValidator.validate({
|
|
3328
3724
|
projectId: "health-check",
|
|
3329
3725
|
vectorDb: config,
|
|
3330
|
-
llm: { provider: "openai", model: "gpt-4o" },
|
|
3331
|
-
embedding: { provider: "openai", model: "text-embedding-3-small" }
|
|
3726
|
+
llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
|
|
3727
|
+
embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
|
|
3332
3728
|
});
|
|
3333
3729
|
const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
|
|
3334
3730
|
if (vectorDbErrors.length > 0) {
|
|
@@ -3340,35 +3736,11 @@ var ProviderHealthCheck = class {
|
|
|
3340
3736
|
};
|
|
3341
3737
|
}
|
|
3342
3738
|
try {
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
case "pgvector":
|
|
3347
|
-
case "postgresql":
|
|
3348
|
-
return await this.checkPostgres(config, timestamp);
|
|
3349
|
-
case "mongodb":
|
|
3350
|
-
return await this.checkMongoDB(config, timestamp);
|
|
3351
|
-
case "milvus":
|
|
3352
|
-
return await this.checkMilvus(config, timestamp);
|
|
3353
|
-
case "qdrant":
|
|
3354
|
-
return await this.checkQdrant(config, timestamp);
|
|
3355
|
-
case "chromadb":
|
|
3356
|
-
return await this.checkChromaDB(config, timestamp);
|
|
3357
|
-
case "redis":
|
|
3358
|
-
return await this.checkRedis(config, timestamp);
|
|
3359
|
-
case "weaviate":
|
|
3360
|
-
return await this.checkWeaviate(config, timestamp);
|
|
3361
|
-
case "rest":
|
|
3362
|
-
case "universal_rest":
|
|
3363
|
-
return await this.checkRestAPI(config, timestamp);
|
|
3364
|
-
default:
|
|
3365
|
-
return {
|
|
3366
|
-
healthy: false,
|
|
3367
|
-
provider: config.provider,
|
|
3368
|
-
error: `Unsupported provider: ${config.provider}`,
|
|
3369
|
-
timestamp
|
|
3370
|
-
};
|
|
3739
|
+
const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
|
|
3740
|
+
if (checker) {
|
|
3741
|
+
return await checker.check(config);
|
|
3371
3742
|
}
|
|
3743
|
+
return await this.fallbackVectorHealthCheck(config, timestamp);
|
|
3372
3744
|
} catch (error) {
|
|
3373
3745
|
return {
|
|
3374
3746
|
healthy: false,
|
|
@@ -3378,363 +3750,50 @@ var ProviderHealthCheck = class {
|
|
|
3378
3750
|
};
|
|
3379
3751
|
}
|
|
3380
3752
|
}
|
|
3381
|
-
static async
|
|
3382
|
-
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
const indexes = await client.listIndexes();
|
|
3388
|
-
const indexNames = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
|
|
3389
|
-
if (!indexNames.includes(config.indexName)) {
|
|
3753
|
+
static async fallbackVectorHealthCheck(config, timestamp) {
|
|
3754
|
+
const opts = config.options || {};
|
|
3755
|
+
const baseUrl = opts.baseUrl || opts.uri;
|
|
3756
|
+
if (baseUrl && baseUrl.startsWith("http")) {
|
|
3757
|
+
try {
|
|
3758
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
|
|
3390
3759
|
return {
|
|
3391
|
-
healthy:
|
|
3392
|
-
provider:
|
|
3393
|
-
error:
|
|
3760
|
+
healthy: response.ok,
|
|
3761
|
+
provider: config.provider,
|
|
3762
|
+
error: response.ok ? void 0 : `Health check failed: ${response.status}`,
|
|
3394
3763
|
timestamp
|
|
3395
3764
|
};
|
|
3396
|
-
}
|
|
3397
|
-
return {
|
|
3398
|
-
healthy: true,
|
|
3399
|
-
provider: "pinecone",
|
|
3400
|
-
capabilities: { indexes: indexNames.length, targetIndex: config.indexName },
|
|
3401
|
-
timestamp
|
|
3402
|
-
};
|
|
3403
|
-
} catch (error) {
|
|
3404
|
-
return {
|
|
3405
|
-
healthy: false,
|
|
3406
|
-
provider: "pinecone",
|
|
3407
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3408
|
-
timestamp
|
|
3409
|
-
};
|
|
3410
|
-
}
|
|
3411
|
-
}
|
|
3412
|
-
static async checkPostgres(config, timestamp) {
|
|
3413
|
-
const opts = config.options;
|
|
3414
|
-
try {
|
|
3415
|
-
const { Client } = await import("pg");
|
|
3416
|
-
const client = new Client({ connectionString: opts.connectionString });
|
|
3417
|
-
await client.connect();
|
|
3418
|
-
const result = await client.query(`
|
|
3419
|
-
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
3420
|
-
`);
|
|
3421
|
-
const hasVector = result.rows[0].exists;
|
|
3422
|
-
await client.end();
|
|
3423
|
-
return {
|
|
3424
|
-
healthy: true,
|
|
3425
|
-
provider: "postgresql",
|
|
3426
|
-
capabilities: { pgvectorInstalled: hasVector },
|
|
3427
|
-
timestamp
|
|
3428
|
-
};
|
|
3429
|
-
} catch (error) {
|
|
3430
|
-
return {
|
|
3431
|
-
healthy: false,
|
|
3432
|
-
provider: "postgresql",
|
|
3433
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3434
|
-
timestamp
|
|
3435
|
-
};
|
|
3436
|
-
}
|
|
3437
|
-
}
|
|
3438
|
-
static async checkMongoDB(config, timestamp) {
|
|
3439
|
-
const opts = config.options;
|
|
3440
|
-
try {
|
|
3441
|
-
const { MongoClient: MongoClient2 } = await import("mongodb");
|
|
3442
|
-
const client = new MongoClient2(opts.uri);
|
|
3443
|
-
await client.connect();
|
|
3444
|
-
const db = client.db(opts.database);
|
|
3445
|
-
const collections = await db.listCollections().toArray();
|
|
3446
|
-
const collectionNames = collections.map((c) => c.name);
|
|
3447
|
-
const hasCollection = collectionNames.includes(opts.collection);
|
|
3448
|
-
await client.close();
|
|
3449
|
-
return {
|
|
3450
|
-
healthy: true,
|
|
3451
|
-
provider: "mongodb",
|
|
3452
|
-
capabilities: {
|
|
3453
|
-
collections: collectionNames.length,
|
|
3454
|
-
targetCollection: hasCollection ? opts.collection : "NOT FOUND (will be created on first insert)"
|
|
3455
|
-
},
|
|
3456
|
-
timestamp
|
|
3457
|
-
};
|
|
3458
|
-
} catch (error) {
|
|
3459
|
-
return {
|
|
3460
|
-
healthy: false,
|
|
3461
|
-
provider: "mongodb",
|
|
3462
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3463
|
-
timestamp
|
|
3464
|
-
};
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
/**
|
|
3468
|
-
* Milvus health check — uses baseUrl/uri (matching MilvusProvider constructor).
|
|
3469
|
-
*/
|
|
3470
|
-
static async checkMilvus(config, timestamp) {
|
|
3471
|
-
const opts = config.options;
|
|
3472
|
-
const baseUrl = opts.baseUrl || opts.uri || (opts.host ? `http://${opts.host}:${opts.port || 19530}` : "http://localhost:19530");
|
|
3473
|
-
try {
|
|
3474
|
-
const controller = new AbortController();
|
|
3475
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
3476
|
-
const response = await fetch(`${baseUrl}/v1/health`, { signal: controller.signal });
|
|
3477
|
-
clearTimeout(timeoutId);
|
|
3478
|
-
if (!response.ok) {
|
|
3479
|
-
throw new Error(`Health check returned ${response.status}`);
|
|
3480
|
-
}
|
|
3481
|
-
return {
|
|
3482
|
-
healthy: true,
|
|
3483
|
-
provider: "milvus",
|
|
3484
|
-
capabilities: { endpoint: baseUrl },
|
|
3485
|
-
timestamp
|
|
3486
|
-
};
|
|
3487
|
-
} catch (error) {
|
|
3488
|
-
return {
|
|
3489
|
-
healthy: false,
|
|
3490
|
-
provider: "milvus",
|
|
3491
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3492
|
-
timestamp
|
|
3493
|
-
};
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
/**
|
|
3497
|
-
* Qdrant health check — uses baseUrl (matching QdrantProvider constructor).
|
|
3498
|
-
*/
|
|
3499
|
-
static async checkQdrant(config, timestamp) {
|
|
3500
|
-
const opts = config.options;
|
|
3501
|
-
const baseUrl = (opts.baseUrl || opts.url || "http://localhost:6333").replace(/\/$/, "");
|
|
3502
|
-
try {
|
|
3503
|
-
const apiKey = opts.apiKey;
|
|
3504
|
-
const response = await fetch(`${baseUrl}/`, {
|
|
3505
|
-
headers: apiKey ? { "api-key": apiKey } : {}
|
|
3506
|
-
});
|
|
3507
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3508
|
-
const health = await response.json();
|
|
3509
|
-
return {
|
|
3510
|
-
healthy: true,
|
|
3511
|
-
provider: "qdrant",
|
|
3512
|
-
capabilities: health,
|
|
3513
|
-
timestamp
|
|
3514
|
-
};
|
|
3515
|
-
} catch (error) {
|
|
3516
|
-
return {
|
|
3517
|
-
healthy: false,
|
|
3518
|
-
provider: "qdrant",
|
|
3519
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3520
|
-
timestamp
|
|
3521
|
-
};
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
/**
|
|
3525
|
-
* ChromaDB health check — uses baseUrl/host (matching ChromaDBProvider constructor).
|
|
3526
|
-
*/
|
|
3527
|
-
static async checkChromaDB(config, timestamp) {
|
|
3528
|
-
const opts = config.options;
|
|
3529
|
-
const baseUrl = opts.baseUrl || (opts.host ? `http://${opts.host}:${opts.port || 8e3}` : "http://localhost:8000");
|
|
3530
|
-
try {
|
|
3531
|
-
const response = await fetch(`${baseUrl}/api/v1/heartbeat`);
|
|
3532
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3533
|
-
return {
|
|
3534
|
-
healthy: true,
|
|
3535
|
-
provider: "chromadb",
|
|
3536
|
-
capabilities: { endpoint: baseUrl },
|
|
3537
|
-
timestamp
|
|
3538
|
-
};
|
|
3539
|
-
} catch (error) {
|
|
3540
|
-
return {
|
|
3541
|
-
healthy: false,
|
|
3542
|
-
provider: "chromadb",
|
|
3543
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3544
|
-
timestamp
|
|
3545
|
-
};
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
/**
|
|
3549
|
-
* Redis health check — Redis is TCP-only (no HTTP endpoint).
|
|
3550
|
-
* We report healthy=true with a note; actual connectivity is validated at first operation.
|
|
3551
|
-
*/
|
|
3552
|
-
static async checkRedis(config, timestamp) {
|
|
3553
|
-
const opts = config.options;
|
|
3554
|
-
const url = opts.baseUrl || opts.url || (opts.host && opts.port ? `redis://${opts.host}:${opts.port}` : "redis://localhost:6379");
|
|
3555
|
-
if (url.startsWith("http")) {
|
|
3556
|
-
try {
|
|
3557
|
-
await fetch(url);
|
|
3558
|
-
return { healthy: true, provider: "redis", capabilities: { endpoint: url }, timestamp };
|
|
3559
3765
|
} catch (e) {
|
|
3766
|
+
return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
|
|
3560
3767
|
}
|
|
3561
3768
|
}
|
|
3562
3769
|
return {
|
|
3563
3770
|
healthy: true,
|
|
3564
|
-
provider:
|
|
3565
|
-
|
|
3566
|
-
endpoint: url,
|
|
3567
|
-
note: "Redis uses TCP; connectivity is validated on first operation."
|
|
3568
|
-
},
|
|
3771
|
+
provider: config.provider,
|
|
3772
|
+
error: "Pluggable health check not implemented; basic reachability assumed.",
|
|
3569
3773
|
timestamp
|
|
3570
3774
|
};
|
|
3571
3775
|
}
|
|
3572
|
-
/**
|
|
3573
|
-
* Weaviate health check — uses baseUrl/url (matching WeaviateProvider constructor).
|
|
3574
|
-
*/
|
|
3575
|
-
static async checkWeaviate(config, timestamp) {
|
|
3576
|
-
const opts = config.options;
|
|
3577
|
-
const baseUrl = (opts.baseUrl || opts.url || "http://localhost:8080").replace(/\/$/, "");
|
|
3578
|
-
try {
|
|
3579
|
-
const response = await fetch(`${baseUrl}/v1/.well-known/ready`);
|
|
3580
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3581
|
-
return {
|
|
3582
|
-
healthy: true,
|
|
3583
|
-
provider: "weaviate",
|
|
3584
|
-
capabilities: { endpoint: baseUrl },
|
|
3585
|
-
timestamp
|
|
3586
|
-
};
|
|
3587
|
-
} catch (error) {
|
|
3588
|
-
return {
|
|
3589
|
-
healthy: false,
|
|
3590
|
-
provider: "weaviate",
|
|
3591
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3592
|
-
timestamp
|
|
3593
|
-
};
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
static async checkRestAPI(config, timestamp) {
|
|
3597
|
-
const opts = config.options;
|
|
3598
|
-
const baseUrl = (opts.baseUrl || "").replace(/\/$/, "");
|
|
3599
|
-
if (!baseUrl) {
|
|
3600
|
-
return { healthy: false, provider: "rest", error: "baseUrl is required", timestamp };
|
|
3601
|
-
}
|
|
3602
|
-
try {
|
|
3603
|
-
const response = await fetch(`${baseUrl}/health`, {
|
|
3604
|
-
headers: opts.headers ? JSON.parse(opts.headers) : {}
|
|
3605
|
-
});
|
|
3606
|
-
return {
|
|
3607
|
-
healthy: response.ok,
|
|
3608
|
-
provider: "rest",
|
|
3609
|
-
error: response.ok ? void 0 : `Health check returned ${response.status}`,
|
|
3610
|
-
timestamp
|
|
3611
|
-
};
|
|
3612
|
-
} catch (error) {
|
|
3613
|
-
return {
|
|
3614
|
-
healthy: false,
|
|
3615
|
-
provider: "rest",
|
|
3616
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3617
|
-
timestamp
|
|
3618
|
-
};
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
3776
|
/**
|
|
3622
3777
|
* Validates LLM provider configuration.
|
|
3623
3778
|
*/
|
|
3624
3779
|
static async checkLLMProvider(config) {
|
|
3625
3780
|
const timestamp = Date.now();
|
|
3626
3781
|
try {
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
case "anthropic":
|
|
3631
|
-
return await this.checkAnthropic(config, timestamp);
|
|
3632
|
-
case "ollama":
|
|
3633
|
-
return await this.checkOllama(config, timestamp);
|
|
3634
|
-
case "rest":
|
|
3635
|
-
case "universal_rest":
|
|
3636
|
-
return await this.checkLLMRestAPI(config, timestamp);
|
|
3637
|
-
default:
|
|
3638
|
-
return {
|
|
3639
|
-
healthy: false,
|
|
3640
|
-
provider: config.provider,
|
|
3641
|
-
error: `Unsupported provider: ${config.provider}`,
|
|
3642
|
-
timestamp
|
|
3643
|
-
};
|
|
3782
|
+
const checker = LLMFactory.getHealthChecker(config.provider);
|
|
3783
|
+
if (checker) {
|
|
3784
|
+
return await checker.check(config);
|
|
3644
3785
|
}
|
|
3645
|
-
} catch (error) {
|
|
3646
|
-
return {
|
|
3647
|
-
healthy: false,
|
|
3648
|
-
provider: config.provider,
|
|
3649
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3650
|
-
timestamp
|
|
3651
|
-
};
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
static async checkOpenAI(config, timestamp) {
|
|
3655
|
-
try {
|
|
3656
|
-
const OpenAI2 = await import("openai");
|
|
3657
|
-
const client = new OpenAI2.default({ apiKey: config.apiKey });
|
|
3658
|
-
const models = await client.models.list();
|
|
3659
|
-
const hasModel = models.data.some((m) => m.id === config.model);
|
|
3660
|
-
return {
|
|
3661
|
-
healthy: true,
|
|
3662
|
-
provider: "openai",
|
|
3663
|
-
capabilities: { model: config.model, available: hasModel, totalModels: models.data.length },
|
|
3664
|
-
timestamp
|
|
3665
|
-
};
|
|
3666
|
-
} catch (error) {
|
|
3667
|
-
return {
|
|
3668
|
-
healthy: false,
|
|
3669
|
-
provider: "openai",
|
|
3670
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3671
|
-
timestamp
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
3674
|
-
}
|
|
3675
|
-
static async checkAnthropic(config, timestamp) {
|
|
3676
|
-
try {
|
|
3677
|
-
const { default: Anthropic2 } = await import("@anthropic-ai/sdk");
|
|
3678
|
-
const client = new Anthropic2({ apiKey: config.apiKey });
|
|
3679
|
-
await client.messages.create({
|
|
3680
|
-
model: config.model,
|
|
3681
|
-
max_tokens: 10,
|
|
3682
|
-
messages: [{ role: "user", content: "ping" }]
|
|
3683
|
-
});
|
|
3684
|
-
return { healthy: true, provider: "anthropic", capabilities: { model: config.model }, timestamp };
|
|
3685
|
-
} catch (error) {
|
|
3686
|
-
return {
|
|
3687
|
-
healthy: false,
|
|
3688
|
-
provider: "anthropic",
|
|
3689
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3690
|
-
timestamp
|
|
3691
|
-
};
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
static async checkOllama(config, timestamp) {
|
|
3695
|
-
const baseUrl = (config.baseUrl || "http://localhost:11434").replace(/\/$/, "");
|
|
3696
|
-
try {
|
|
3697
|
-
const response = await fetch(`${baseUrl}/api/tags`);
|
|
3698
|
-
if (!response.ok) throw new Error(`Health check returned ${response.status}`);
|
|
3699
|
-
const data = await response.json();
|
|
3700
|
-
const models = data.models || [];
|
|
3701
|
-
const hasModel = models.some((m) => m.name === config.model);
|
|
3702
3786
|
return {
|
|
3703
3787
|
healthy: true,
|
|
3704
|
-
provider: "ollama",
|
|
3705
|
-
capabilities: { model: config.model, available: hasModel, totalModels: models.length },
|
|
3706
|
-
timestamp
|
|
3707
|
-
};
|
|
3708
|
-
} catch (error) {
|
|
3709
|
-
return {
|
|
3710
|
-
healthy: false,
|
|
3711
|
-
provider: "ollama",
|
|
3712
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3713
|
-
timestamp
|
|
3714
|
-
};
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
static async checkLLMRestAPI(config, timestamp) {
|
|
3718
|
-
var _a, _b;
|
|
3719
|
-
const baseUrlRaw = config.baseUrl || ((_a = config.options) == null ? void 0 : _a.baseUrl);
|
|
3720
|
-
const baseUrl = (typeof baseUrlRaw === "string" ? baseUrlRaw : "").replace(/\/$/, "");
|
|
3721
|
-
if (!baseUrl) {
|
|
3722
|
-
return { healthy: false, provider: config.provider, error: "baseUrl is required", timestamp };
|
|
3723
|
-
}
|
|
3724
|
-
try {
|
|
3725
|
-
const headers = (_b = config.options) == null ? void 0 : _b.headers;
|
|
3726
|
-
const response = await fetch(`${baseUrl}/health`, { headers: headers || void 0 });
|
|
3727
|
-
return {
|
|
3728
|
-
healthy: response.ok,
|
|
3729
3788
|
provider: config.provider,
|
|
3730
|
-
error:
|
|
3789
|
+
error: "Pluggable health check not implemented; basic reachability assumed.",
|
|
3731
3790
|
timestamp
|
|
3732
3791
|
};
|
|
3733
3792
|
} catch (error) {
|
|
3734
3793
|
return {
|
|
3735
3794
|
healthy: false,
|
|
3736
3795
|
provider: config.provider,
|
|
3737
|
-
error:
|
|
3796
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3738
3797
|
timestamp
|
|
3739
3798
|
};
|
|
3740
3799
|
}
|
|
@@ -3809,6 +3868,14 @@ var VectorPlugin = class {
|
|
|
3809
3868
|
async chat(message, history = [], namespace) {
|
|
3810
3869
|
return this.pipeline.ask(message, history, namespace);
|
|
3811
3870
|
}
|
|
3871
|
+
/**
|
|
3872
|
+
* Run a streaming chat query.
|
|
3873
|
+
*/
|
|
3874
|
+
chatStream(_0) {
|
|
3875
|
+
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
3876
|
+
yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3812
3879
|
/**
|
|
3813
3880
|
* Ingest documents into the vector database.
|
|
3814
3881
|
*/
|
|
@@ -3894,6 +3961,55 @@ function createChatHandler(config) {
|
|
|
3894
3961
|
}
|
|
3895
3962
|
};
|
|
3896
3963
|
}
|
|
3964
|
+
function createStreamHandler(config) {
|
|
3965
|
+
const plugin = new VectorPlugin(config);
|
|
3966
|
+
return async function POST(req) {
|
|
3967
|
+
try {
|
|
3968
|
+
const { message, history = [], namespace } = await req.json();
|
|
3969
|
+
const encoder = new TextEncoder();
|
|
3970
|
+
const stream = new ReadableStream({
|
|
3971
|
+
async start(controller) {
|
|
3972
|
+
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
3973
|
+
try {
|
|
3974
|
+
for (var iter = __forAwait(pipelineStream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
3975
|
+
const chunk = temp.value;
|
|
3976
|
+
if (typeof chunk === "string") {
|
|
3977
|
+
controller.enqueue(encoder.encode(chunk));
|
|
3978
|
+
} else {
|
|
3979
|
+
controller.enqueue(encoder.encode(`
|
|
3980
|
+
|
|
3981
|
+
__METADATA__${JSON.stringify(chunk)}`));
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
} catch (temp) {
|
|
3985
|
+
error = [temp];
|
|
3986
|
+
} finally {
|
|
3987
|
+
try {
|
|
3988
|
+
more && (temp = iter.return) && await temp.call(iter);
|
|
3989
|
+
} finally {
|
|
3990
|
+
if (error)
|
|
3991
|
+
throw error[0];
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
controller.close();
|
|
3995
|
+
}
|
|
3996
|
+
});
|
|
3997
|
+
return new Response(stream, {
|
|
3998
|
+
headers: {
|
|
3999
|
+
"Content-Type": "text/event-stream",
|
|
4000
|
+
"Cache-Control": "no-cache",
|
|
4001
|
+
"Connection": "keep-alive"
|
|
4002
|
+
}
|
|
4003
|
+
});
|
|
4004
|
+
} catch (err) {
|
|
4005
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
4006
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
4007
|
+
status: 500,
|
|
4008
|
+
headers: { "Content-Type": "application/json" }
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
3897
4013
|
function createIngestHandler(config) {
|
|
3898
4014
|
const plugin = new VectorPlugin(config);
|
|
3899
4015
|
return async function POST(req) {
|
|
@@ -3966,5 +4082,6 @@ function createUploadHandler(config) {
|
|
|
3966
4082
|
createChatHandler,
|
|
3967
4083
|
createHealthHandler,
|
|
3968
4084
|
createIngestHandler,
|
|
4085
|
+
createStreamHandler,
|
|
3969
4086
|
createUploadHandler
|
|
3970
4087
|
});
|