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