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