@retrivora-ai/rag-engine 0.4.4 → 1.0.0

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