@denizsvnc/ai-rag-engine 2.2.1 → 2.2.3

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.
@@ -168,7 +168,19 @@ var CircuitBreakerOpenError = class extends Error {
168
168
  this.name = "CircuitBreakerOpenError";
169
169
  }
170
170
  };
171
- var SMLEngine = class {
171
+ var SMLEngine = class _SMLEngine {
172
+ static initLockChain = Promise.resolve();
173
+ static async acquireInitLock() {
174
+ let releaseLock = () => {
175
+ };
176
+ const nextLock = new Promise((resolve) => {
177
+ releaseLock = resolve;
178
+ });
179
+ const currentLock = _SMLEngine.initLockChain;
180
+ _SMLEngine.initLockChain = currentLock.then(() => nextLock);
181
+ await currentLock;
182
+ return releaseLock;
183
+ }
172
184
  modelName;
173
185
  cacheDir;
174
186
  localFilesOnly;
@@ -228,48 +240,62 @@ var SMLEngine = class {
228
240
  console.log(`[SML CircuitBreaker] Cooldown expired. Transitioned to HALF_OPEN for probe.`);
229
241
  }
230
242
  if (!this.extractorPromise) {
231
- console.log(`[SML] Loading ${this.modelName} (dtype: ${this.dtype})...`);
232
- this.extractorPromise = import("@huggingface/transformers").then(async ({ pipeline: pipeline2, env }) => {
233
- const previousCacheDir = env.cacheDir;
234
- const previousAllowRemote = env.allowRemoteModels;
243
+ this.extractorPromise = (async () => {
244
+ const releaseLock = await _SMLEngine.acquireInitLock();
235
245
  try {
236
- if (this.cacheDir) {
237
- env.cacheDir = this.cacheDir;
246
+ console.log(`[SML] Loading ${this.modelName} (dtype: ${this.dtype})...`);
247
+ const { pipeline: pipeline2, env } = await import("@huggingface/transformers");
248
+ const previousCacheDir = env.cacheDir;
249
+ const previousAllowRemote = env.allowRemoteModels;
250
+ const previousLocalModelPath = env.localModelPath;
251
+ try {
252
+ if (this.cacheDir) {
253
+ env.cacheDir = this.cacheDir;
254
+ }
255
+ if (this.localFilesOnly !== void 0) {
256
+ env.allowRemoteModels = !this.localFilesOnly;
257
+ }
258
+ const pipelineOptions = {
259
+ dtype: this.dtype
260
+ };
261
+ if (this.cacheDir) {
262
+ pipelineOptions.cache_dir = this.cacheDir;
263
+ }
264
+ if (this.localFilesOnly !== void 0) {
265
+ pipelineOptions.local_files_only = this.localFilesOnly;
266
+ }
267
+ const pipelineInstance = await pipeline2("feature-extraction", this.modelName, pipelineOptions);
268
+ this.circuitState = "CLOSED";
269
+ this.failureCount = 0;
270
+ const extract = async (text, options) => {
271
+ const output = await pipelineInstance(text, options);
272
+ const values = output.data;
273
+ const data = output.data instanceof Float32Array ? output.data : Float32Array.from({ length: values.length }, (_, index) => Number(values[index]));
274
+ return { data, dims: output.dims };
275
+ };
276
+ return extract;
277
+ } finally {
278
+ env.cacheDir = previousCacheDir;
279
+ env.allowRemoteModels = previousAllowRemote;
280
+ env.localModelPath = previousLocalModelPath;
238
281
  }
239
- if (this.localFilesOnly) {
240
- env.allowRemoteModels = false;
282
+ } catch (err) {
283
+ this.failureCount++;
284
+ this.lastFailureTime = Date.now();
285
+ this.extractorPromise = null;
286
+ if (this.failureCount >= this.failureThreshold) {
287
+ this.circuitState = "OPEN";
288
+ console.error(
289
+ `[SML CircuitBreaker] \u26A0\uFE0F Tripped to OPEN state! Model failed ${this.failureCount} times. Pausing requests for ${this.cooldownMs / 1e3}s.`
290
+ );
291
+ } else {
292
+ console.error(`[SML] Model loading error (attempt ${this.failureCount}/${this.failureThreshold}):`, err);
241
293
  }
242
- return await pipeline2("feature-extraction", this.modelName, {
243
- dtype: this.dtype
244
- });
294
+ throw err;
245
295
  } finally {
246
- env.cacheDir = previousCacheDir;
247
- env.allowRemoteModels = previousAllowRemote;
296
+ releaseLock();
248
297
  }
249
- }).then((pipelineInstance) => {
250
- this.circuitState = "CLOSED";
251
- this.failureCount = 0;
252
- const extract = async (text, options) => {
253
- const output = await pipelineInstance(text, options);
254
- const values = output.data;
255
- const data = output.data instanceof Float32Array ? output.data : Float32Array.from({ length: values.length }, (_, index) => Number(values[index]));
256
- return { data, dims: output.dims };
257
- };
258
- return extract;
259
- }).catch((err) => {
260
- this.failureCount++;
261
- this.lastFailureTime = Date.now();
262
- this.extractorPromise = null;
263
- if (this.failureCount >= this.failureThreshold) {
264
- this.circuitState = "OPEN";
265
- console.error(
266
- `[SML CircuitBreaker] \u26A0\uFE0F Tripped to OPEN state! Model failed ${this.failureCount} times. Pausing requests for ${this.cooldownMs / 1e3}s.`
267
- );
268
- } else {
269
- console.error(`[SML] Model loading error (attempt ${this.failureCount}/${this.failureThreshold}):`, err);
270
- }
271
- throw err;
272
- });
298
+ })();
273
299
  }
274
300
  return this.extractorPromise;
275
301
  }
@@ -535,11 +561,11 @@ var InjectionDetector = class {
535
561
  // 2. Jailbreaks, DAN modes, role hijacking
536
562
  {
537
563
  id: "JAILBREAK_MODE_EN",
538
- regex: /\b(you\s+are\s+now|act\s+as|switch\s+to|enter)\s+(an?\s+)?(developer|dan|jailbreak|unrestricted|unfiltered|god)\s*(mode|persona|ai)?\b/i
564
+ regex: /\b(you\s+are\s+now|act\s+as|switch\s+to|enter)\s+(in\s+(an?\s+)?|an?\s+)?(developer|dan|jailbreak|unrestricted|unfiltered|god)\s*(mode|persona|ai)?\b/i
539
565
  },
540
566
  {
541
567
  id: "JAILBREAK_MODE_TR",
542
- regex: /\bartık\s+(geliştirici|dan|kısıtlamasız|admin|jailbreak)\s+(modundasın|moduna\s+geç|olarak\s+davran)\b/i
568
+ regex: /(?:^|\s|[.,!?])(artık|şimdi)\s+(bir\s+)?(geliştirici|dan|kısıtlamasız|admin|jailbreak)\s+(modundasın|moduna\s+geç|olarak\s+davran)(?:$|\s|[.,!?])/i
543
569
  },
544
570
  {
545
571
  id: "DISABLE_SAFETY_EN",
@@ -1211,6 +1237,8 @@ import fs3 from "node:fs";
1211
1237
  import fsp from "node:fs/promises";
1212
1238
  import path3 from "node:path";
1213
1239
  import os from "node:os";
1240
+ import zlib from "node:zlib";
1241
+ import { StringDecoder } from "node:string_decoder";
1214
1242
 
1215
1243
  // src/core/queue/in-memory-queue.ts
1216
1244
  import { randomUUID } from "node:crypto";
@@ -1462,17 +1490,71 @@ var DocumentIngestionService = class _DocumentIngestionService {
1462
1490
  * - Performs binary artifact suppression and multi-lingual UTF-8 normalization (Turkish, European, and CJK diacritics).
1463
1491
  */
1464
1492
  static extractText(filename, buffer) {
1465
- const rawString = buffer.toString("utf-8");
1466
- if (buffer.length >= 4 && buffer.slice(0, 4).toString() === "%PDF") {
1467
- return _DocumentIngestionService.extractPdfText(rawString);
1493
+ if (buffer.length >= 4 && buffer.subarray(0, 4).toString() === "%PDF") {
1494
+ return _DocumentIngestionService.extractPdfText(buffer);
1468
1495
  }
1469
- return rawString.trim();
1496
+ return buffer.toString("utf-8").trim();
1470
1497
  }
1471
1498
  /**
1472
- * Enterprise High-Efficiency PDF Lexical Extractor.
1473
- * Parses PDF content streams without heavy external C++/WASM dependencies.
1499
+ * Enterprise High-Efficiency PDF Lexical Extractor with native zlib.inflate decompression.
1500
+ * Decodes and parses PDF content streams without heavy external C++/WASM dependencies.
1501
+ *
1502
+ * Supports:
1503
+ * - Decompression of compressed stream objects (/Filter /FlateDecode) via Node.js native zlib.
1504
+ * - BT (Begin Text) ... ET (End Text) matrices.
1505
+ * - TJ array operators with kerning spacing preservation.
1506
+ * - Literal (Tj) and hexadecimal (<hex> Tj) text operators with escape and octal decoding.
1474
1507
  */
1475
1508
  static extractPdfText(rawContent) {
1509
+ const buffer = Buffer.isBuffer(rawContent) ? rawContent : Buffer.from(rawContent, "latin1");
1510
+ const decompressedStreams = [];
1511
+ const streamMarker = Buffer.from("stream");
1512
+ const endStreamMarker = Buffer.from("endstream");
1513
+ let pos = 0;
1514
+ while (pos < buffer.length) {
1515
+ const streamIdx = buffer.indexOf(streamMarker, pos);
1516
+ if (streamIdx === -1) break;
1517
+ const prevChar = streamIdx > 0 ? buffer[streamIdx - 1] : 32;
1518
+ if (prevChar !== 10 && prevChar !== 13 && prevChar !== 32 && prevChar !== 62) {
1519
+ pos = streamIdx + 6;
1520
+ continue;
1521
+ }
1522
+ let dataStart = streamIdx + 6;
1523
+ if (buffer[dataStart] === 13 && buffer[dataStart + 1] === 10) {
1524
+ dataStart += 2;
1525
+ } else if (buffer[dataStart] === 10 || buffer[dataStart] === 13) {
1526
+ dataStart += 1;
1527
+ }
1528
+ const endIdx = buffer.indexOf(endStreamMarker, dataStart);
1529
+ if (endIdx === -1) break;
1530
+ let dataEnd = endIdx;
1531
+ while (dataEnd > dataStart && (buffer[dataEnd - 1] === 10 || buffer[dataEnd - 1] === 13 || buffer[dataEnd - 1] === 32)) {
1532
+ dataEnd--;
1533
+ }
1534
+ const streamBytes = buffer.subarray(dataStart, dataEnd);
1535
+ const dictStart = Math.max(0, streamIdx - 500);
1536
+ const dictSnippet = buffer.subarray(dictStart, streamIdx).toString("latin1");
1537
+ const isFlate = /\/Filter\s*(?:\[\s*)?\/(?:FlateDecode|Fl)\b/.test(dictSnippet) || !/\/Filter\b/.test(dictSnippet);
1538
+ let decoded = "";
1539
+ if (isFlate && streamBytes.length > 0) {
1540
+ try {
1541
+ decoded = zlib.inflateSync(streamBytes).toString("utf-8");
1542
+ } catch {
1543
+ try {
1544
+ decoded = zlib.inflateRawSync(streamBytes).toString("utf-8");
1545
+ } catch {
1546
+ decoded = streamBytes.toString("utf-8");
1547
+ }
1548
+ }
1549
+ } else {
1550
+ decoded = streamBytes.toString("utf-8");
1551
+ }
1552
+ if (decoded && decoded.trim().length > 0) {
1553
+ decompressedStreams.push(decoded);
1554
+ }
1555
+ pos = endIdx + 9;
1556
+ }
1557
+ const contentToScan = [buffer.toString("latin1"), ...decompressedStreams].join("\n");
1476
1558
  const extractedSegments = [];
1477
1559
  const decodePdfLiteral = (encoded) => {
1478
1560
  return encoded.replace(/\\([0-7]{1,3})/g, (_, oct) => String.fromCharCode(parseInt(oct, 8))).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\b/g, "\b").replace(/\\f/g, "\f").replace(/\\\(/g, "(").replace(/\\\)/g, ")").replace(/\\\\/g, "\\");
@@ -1487,7 +1569,7 @@ var DocumentIngestionService = class _DocumentIngestionService {
1487
1569
  };
1488
1570
  const btEtRegex = /BT[\s\S]*?ET/g;
1489
1571
  let btMatch;
1490
- while ((btMatch = btEtRegex.exec(rawContent)) !== null) {
1572
+ while ((btMatch = btEtRegex.exec(contentToScan)) !== null) {
1491
1573
  const blockContent = btMatch[0];
1492
1574
  const tjArrayRegex = /\[([\s\S]*?)\]\s*TJ/g;
1493
1575
  let tjArrMatch;
@@ -1532,7 +1614,7 @@ var DocumentIngestionService = class _DocumentIngestionService {
1532
1614
  if (extractedSegments.length > 0) {
1533
1615
  return extractedSegments.join(" ").replace(/\s+/g, " ").trim();
1534
1616
  }
1535
- const cleaned = rawContent.replace(/%PDF-[^\n]+/g, "").replace(/stream[\s\S]*?endstream/g, (streamObj) => {
1617
+ const cleaned = contentToScan.replace(/%PDF-[^\n]+/g, "").replace(/stream[\s\S]*?endstream/g, (streamObj) => {
1536
1618
  const innerMatch = streamObj.match(/\((.*?)\)/g);
1537
1619
  return innerMatch ? innerMatch.map((s) => decodePdfLiteral(s.slice(1, -1))).join(" ") : "";
1538
1620
  }).replace(/obj[\s\S]*?endobj/g, (obj) => {
@@ -1542,8 +1624,17 @@ var DocumentIngestionService = class _DocumentIngestionService {
1542
1624
  return cleaned || "PDF document with no extractable plain text stream.";
1543
1625
  }
1544
1626
  static chunkText(text, chunkSize = 500, overlap = 50) {
1545
- if (!text || text.length <= chunkSize) {
1546
- return text ? [text] : [];
1627
+ if (!Number.isFinite(chunkSize) || chunkSize <= 0) {
1628
+ throw new RangeError(`[DocumentIngestionService] chunkSize must be a positive integer greater than 0. Received: ${chunkSize}`);
1629
+ }
1630
+ if (!Number.isFinite(overlap) || overlap < 0 || overlap >= chunkSize) {
1631
+ throw new RangeError(`[DocumentIngestionService] overlap must be non-negative and strictly less than chunkSize (${chunkSize}). Received: ${overlap}`);
1632
+ }
1633
+ if (!text || text.trim().length === 0) {
1634
+ return [];
1635
+ }
1636
+ if (text.length <= chunkSize) {
1637
+ return [text.trim()];
1547
1638
  }
1548
1639
  const chunks = [];
1549
1640
  let startIndex = 0;
@@ -1565,6 +1656,9 @@ var DocumentIngestionService = class _DocumentIngestionService {
1565
1656
  if (chunk.length > 0) {
1566
1657
  chunks.push(chunk);
1567
1658
  }
1659
+ if (endIndex >= text.length) {
1660
+ break;
1661
+ }
1568
1662
  startIndex = Math.max(startIndex + 1, endIndex - overlap);
1569
1663
  }
1570
1664
  return chunks;
@@ -1573,11 +1667,19 @@ var DocumentIngestionService = class _DocumentIngestionService {
1573
1667
  * Streaming chunk generator.
1574
1668
  * Ingests chunks of text or buffers incrementally with natural backpressure,
1575
1669
  * maintaining a constant O(1) memory footprint regardless of file size.
1670
+ * Preserves UTF-8 multibyte character boundaries across stream buffer splits.
1576
1671
  */
1577
1672
  static async *chunkStream(textStream, chunkSize = 500, overlap = 50) {
1673
+ if (!Number.isFinite(chunkSize) || chunkSize <= 0) {
1674
+ throw new RangeError(`[DocumentIngestionService] chunkSize must be a positive integer greater than 0. Received: ${chunkSize}`);
1675
+ }
1676
+ if (!Number.isFinite(overlap) || overlap < 0 || overlap >= chunkSize) {
1677
+ throw new RangeError(`[DocumentIngestionService] overlap must be non-negative and strictly less than chunkSize (${chunkSize}). Received: ${overlap}`);
1678
+ }
1679
+ const decoder = new StringDecoder("utf-8");
1578
1680
  let window = "";
1579
1681
  for await (const rawChunk of textStream) {
1580
- const chunkStr = Buffer.isBuffer(rawChunk) ? rawChunk.toString("utf-8") : String(rawChunk);
1682
+ const chunkStr = Buffer.isBuffer(rawChunk) ? decoder.write(rawChunk) : String(rawChunk);
1581
1683
  window += chunkStr;
1582
1684
  while (window.length >= chunkSize) {
1583
1685
  let endIndex = chunkSize;
@@ -1595,9 +1697,13 @@ var DocumentIngestionService = class _DocumentIngestionService {
1595
1697
  if (emitted.length > 0) {
1596
1698
  yield emitted;
1597
1699
  }
1598
- window = window.slice(Math.max(1, endIndex - overlap));
1700
+ window = window.slice(Math.max(0, endIndex - overlap));
1599
1701
  }
1600
1702
  }
1703
+ const flushed = decoder.end();
1704
+ if (flushed) {
1705
+ window += flushed;
1706
+ }
1601
1707
  const remaining = window.trim();
1602
1708
  if (remaining.length > 0) {
1603
1709
  yield remaining;
@@ -1945,6 +2051,12 @@ var MemoryVectorAdapter = class {
1945
2051
  smlEngine;
1946
2052
  documents = [];
1947
2053
  bm25Index = new BM25Index();
2054
+ setSMLEngine(engine) {
2055
+ this.smlEngine = engine;
2056
+ }
2057
+ getSMLEngine() {
2058
+ return this.smlEngine;
2059
+ }
1948
2060
  /**
1949
2061
  * Adds documents to memory, computes their embeddings, and indexes them in BM25.
1950
2062
  */
@@ -2029,6 +2141,11 @@ var MemoryVectorAdapter = class {
2029
2141
 
2030
2142
  // src/core/engine.ts
2031
2143
  var RAGEngine = class {
2144
+ /**
2145
+ * Initializes a new instance of `RAGEngine` with the provided configuration.
2146
+ *
2147
+ * @param config - The engine configuration options (`EngineConfig`).
2148
+ */
2032
2149
  constructor(config) {
2033
2150
  this.config = config;
2034
2151
  if (config.tools?.items.some((tool) => tool.isMutating || tool.requiresConfirmation)) {
@@ -2044,8 +2161,22 @@ var RAGEngine = class {
2044
2161
  config.cache?.ttlMs || 1e3 * 60 * 60 * 24
2045
2162
  );
2046
2163
  }
2047
- this.smlEngine = new SMLEngine({ modelName: config.smlConfig?.modelName });
2164
+ this.smlEngine = new SMLEngine({
2165
+ modelName: config.smlConfig?.modelName,
2166
+ dtype: config.smlConfig?.dtype,
2167
+ cacheDir: config.smlConfig?.cacheDir,
2168
+ localFilesOnly: config.smlConfig?.localFilesOnly,
2169
+ cacheSize: config.smlConfig?.cacheSize,
2170
+ circuitBreakerThreshold: config.smlConfig?.circuitBreakerThreshold,
2171
+ circuitBreakerCooldownMs: config.smlConfig?.circuitBreakerCooldownMs
2172
+ });
2048
2173
  const vectorStore = config.inputs?.vectorStore || new MemoryVectorAdapter(this.smlEngine);
2174
+ if (typeof vectorStore.setSMLEngine === "function") {
2175
+ vectorStore.setSMLEngine(this.smlEngine);
2176
+ }
2177
+ if (config.inputs?.faq && typeof config.inputs.faq.setSMLEngine === "function") {
2178
+ config.inputs.faq.setSMLEngine(this.smlEngine);
2179
+ }
2049
2180
  this.config = {
2050
2181
  ...config,
2051
2182
  inputs: {
@@ -2143,8 +2274,27 @@ var RAGEngine = class {
2143
2274
  await this.toolRegistry.registerTool(tool);
2144
2275
  }
2145
2276
  /**
2146
- * Executes the query lifecycle:
2147
- * Security Guardrail -> Cache -> SML Routing -> Retrieval -> Untrusted Boundary Framing -> LLM Generation.
2277
+ * Executes the complete RAG query lifecycle:
2278
+ * 1. Security Inspection (Prompt Injection Defense, PII Masking)
2279
+ * 2. Cache Lookup (Role & Tenant isolated caching)
2280
+ * 3. SML Semantic Routing (FAQ matching and Tool execution)
2281
+ * 4. Hybrid Dense/Sparse Retrieval (Vector search + BM25 keyword search)
2282
+ * 5. Untrusted Boundary Framing (Anti-indirect injection prompt wrapping)
2283
+ * 6. LLM Answer Synthesis & Citation Tagging
2284
+ *
2285
+ * @param userPrompt - The natural language query or question from the user.
2286
+ * @param context - Optional multi-tenant security context including `tenantId`, `userId`, and `roles`.
2287
+ * @returns Promise resolving to `RAGResponse` containing synthesized answer, citations, intent, and metrics.
2288
+ *
2289
+ * @example
2290
+ * ```typescript
2291
+ * const response = await engine.query('What is our return policy?', {
2292
+ * tenantId: 'tenant_123',
2293
+ * userId: 'usr_456',
2294
+ * roles: ['customer']
2295
+ * });
2296
+ * console.log(response.answer);
2297
+ * ```
2148
2298
  */
2149
2299
  async query(userPrompt, context) {
2150
2300
  const startTime = Date.now();
@@ -2538,13 +2688,29 @@ var OpenAIAdapter = class _OpenAIAdapter {
2538
2688
  apiKey;
2539
2689
  model;
2540
2690
  endpoint;
2541
- constructor(apiKey, model, endpoint) {
2542
- this.apiKey = apiKey || process.env.OPENAI_API_KEY || "";
2691
+ temperature;
2692
+ constructor(configOrApiKey, model, endpoint) {
2693
+ let key = "";
2694
+ let resolvedModel = "";
2695
+ let resolvedEndpoint = "";
2696
+ let temp = 0.1;
2697
+ if (typeof configOrApiKey === "object" && configOrApiKey !== null) {
2698
+ key = configOrApiKey.apiKey || process.env.OPENAI_API_KEY || "";
2699
+ resolvedModel = configOrApiKey.model || process.env.OPENAI_MODEL || process.env.OPENAI_MODEL_FAST || "gpt-4o-mini";
2700
+ resolvedEndpoint = configOrApiKey.endpoint || process.env.OPENAI_ENDPOINT || "https://api.openai.com/v1/chat/completions";
2701
+ if (configOrApiKey.temperature !== void 0) temp = configOrApiKey.temperature;
2702
+ } else {
2703
+ key = configOrApiKey || process.env.OPENAI_API_KEY || "";
2704
+ resolvedModel = model || process.env.OPENAI_MODEL || process.env.OPENAI_MODEL_FAST || "gpt-4o-mini";
2705
+ resolvedEndpoint = endpoint || process.env.OPENAI_ENDPOINT || "https://api.openai.com/v1/chat/completions";
2706
+ }
2707
+ this.apiKey = key;
2543
2708
  if (!this.apiKey) {
2544
2709
  throw new Error("[OpenAIAdapter] API Key not found. Provide it in constructor or set OPENAI_API_KEY in .env");
2545
2710
  }
2546
- this.model = model || process.env.OPENAI_MODEL || process.env.OPENAI_MODEL_FAST || "gpt-4o-mini";
2547
- this.endpoint = endpoint || process.env.OPENAI_ENDPOINT || "https://api.openai.com/v1/chat/completions";
2711
+ this.model = resolvedModel;
2712
+ this.endpoint = resolvedEndpoint;
2713
+ this.temperature = temp;
2548
2714
  }
2549
2715
  static createFast(apiKey) {
2550
2716
  return new _OpenAIAdapter(apiKey, process.env.OPENAI_MODEL_FAST || "gpt-4o-mini");
@@ -2560,7 +2726,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
2560
2726
  const body = {
2561
2727
  model: this.model,
2562
2728
  messages,
2563
- temperature: 0.1
2729
+ temperature: this.temperature
2564
2730
  // Low temperature to reduce hallucination in RAG pipelines
2565
2731
  };
2566
2732
  if (request.tools && request.tools.length > 0) {
@@ -2610,15 +2776,28 @@ var OpenAIAdapter = class _OpenAIAdapter {
2610
2776
  var GeminiAdapter = class _GeminiAdapter {
2611
2777
  apiKey;
2612
2778
  model;
2613
- constructor(apiKey, model) {
2614
- this.apiKey = apiKey || process.env.GEMINI_API_KEY || "";
2779
+ temperature;
2780
+ constructor(configOrApiKey, model) {
2781
+ let key = "";
2782
+ let resolvedModel = "";
2783
+ let temp = 0.1;
2784
+ if (typeof configOrApiKey === "object" && configOrApiKey !== null) {
2785
+ key = configOrApiKey.apiKey || process.env.GEMINI_API_KEY || "";
2786
+ resolvedModel = configOrApiKey.model || process.env.GEMINI_MODEL || process.env.GEMINI_MODEL_FAST || "gemini-2.0-flash";
2787
+ if (configOrApiKey.temperature !== void 0) temp = configOrApiKey.temperature;
2788
+ } else {
2789
+ key = configOrApiKey || process.env.GEMINI_API_KEY || "";
2790
+ resolvedModel = model || process.env.GEMINI_MODEL || process.env.GEMINI_MODEL_FAST || "gemini-2.0-flash";
2791
+ }
2792
+ this.apiKey = key;
2615
2793
  if (!this.apiKey) {
2616
2794
  throw new Error("[GeminiAdapter] API Key not found. Provide it in constructor or set GEMINI_API_KEY in .env");
2617
2795
  }
2618
- this.model = model || process.env.GEMINI_MODEL || process.env.GEMINI_MODEL_FAST || "gemini-1.5-flash";
2796
+ this.model = resolvedModel;
2797
+ this.temperature = temp;
2619
2798
  }
2620
2799
  static createFast(apiKey) {
2621
- return new _GeminiAdapter(apiKey, process.env.GEMINI_MODEL_FAST || "gemini-1.5-flash");
2800
+ return new _GeminiAdapter(apiKey, process.env.GEMINI_MODEL_FAST || "gemini-2.0-flash");
2622
2801
  }
2623
2802
  static createSmart(apiKey) {
2624
2803
  return new _GeminiAdapter(apiKey, process.env.GEMINI_MODEL_SMART || "gemini-1.5-pro");
@@ -2633,7 +2812,7 @@ var GeminiAdapter = class _GeminiAdapter {
2633
2812
  }
2634
2813
  ],
2635
2814
  generationConfig: {
2636
- temperature: 0.1
2815
+ temperature: this.temperature
2637
2816
  // Low temperature to reduce hallucination in RAG pipelines
2638
2817
  }
2639
2818
  };
@@ -2697,18 +2876,34 @@ var GeminiAdapter = class _GeminiAdapter {
2697
2876
 
2698
2877
  // src/adapters/llm/openrouter.adapter.ts
2699
2878
  var OpenRouterAdapter = class _OpenRouterAdapter extends OpenAIAdapter {
2700
- constructor(apiKey, model, endpoint = "https://openrouter.ai/api/v1/chat/completions") {
2879
+ constructor(configOrApiKey, model, endpoint = "https://openrouter.ai/api/v1/chat/completions") {
2701
2880
  if (process.env.ALLOW_PUBLIC_OPENSOURCE_LLM === "false") {
2702
2881
  throw new Error(
2703
2882
  "[OpenRouterAdapter] Blocked by security policy: ALLOW_PUBLIC_OPENSOURCE_LLM is set to false in .env. Corporate prompts cannot be routed to public external open-source models."
2704
2883
  );
2705
2884
  }
2706
- const key = apiKey || process.env.OPENROUTER_API_KEY || "";
2885
+ let key = "";
2886
+ let resolvedModel = "";
2887
+ let resolvedEndpoint = endpoint;
2888
+ let temp = 0.1;
2889
+ if (typeof configOrApiKey === "object" && configOrApiKey !== null) {
2890
+ key = configOrApiKey.apiKey || process.env.OPENROUTER_API_KEY || "";
2891
+ resolvedModel = configOrApiKey.model || process.env.OPENROUTER_MODEL || process.env.OPENROUTER_MODEL_FAST || "meta-llama/llama-3.2-3b-instruct:free";
2892
+ if (configOrApiKey.endpoint) resolvedEndpoint = configOrApiKey.endpoint;
2893
+ if (configOrApiKey.temperature !== void 0) temp = configOrApiKey.temperature;
2894
+ } else {
2895
+ key = configOrApiKey || process.env.OPENROUTER_API_KEY || "";
2896
+ resolvedModel = model || process.env.OPENROUTER_MODEL || process.env.OPENROUTER_MODEL_FAST || "meta-llama/llama-3.2-3b-instruct:free";
2897
+ }
2707
2898
  if (!key) {
2708
2899
  throw new Error("[OpenRouterAdapter] API Key not found. Provide it in constructor or set OPENROUTER_API_KEY in .env");
2709
2900
  }
2710
- const resolvedModel = model || process.env.OPENROUTER_MODEL || process.env.OPENROUTER_MODEL_FAST || "meta-llama/llama-3.2-3b-instruct:free";
2711
- super(key, resolvedModel, endpoint);
2901
+ super({
2902
+ apiKey: key,
2903
+ model: resolvedModel,
2904
+ endpoint: resolvedEndpoint,
2905
+ temperature: temp
2906
+ });
2712
2907
  }
2713
2908
  static createFast(apiKey) {
2714
2909
  return new _OpenRouterAdapter(apiKey, process.env.OPENROUTER_MODEL_FAST || "meta-llama/llama-3.2-3b-instruct:free");
@@ -2865,11 +3060,24 @@ var FallbackLLMProvider = class {
2865
3060
  this.providers = providers;
2866
3061
  this.logFailover = options?.logFailover ?? true;
2867
3062
  }
3063
+ getProviderName(provider, index) {
3064
+ if (provider.name && typeof provider.name === "string") {
3065
+ return provider.name;
3066
+ }
3067
+ if (provider.model && typeof provider.model === "string") {
3068
+ const ctor = provider.constructor?.name && provider.constructor.name !== "Object" ? provider.constructor.name : "LLMProvider";
3069
+ return `${ctor}(${provider.model})`;
3070
+ }
3071
+ if (provider.constructor?.name && provider.constructor.name !== "Object") {
3072
+ return provider.constructor.name;
3073
+ }
3074
+ return `Provider#${index + 1}`;
3075
+ }
2868
3076
  async generate(request) {
2869
3077
  const failureErrors = [];
2870
3078
  for (let i = 0; i < this.providers.length; i++) {
2871
3079
  const provider = this.providers[i];
2872
- const providerName = provider.constructor.name || `Provider#${i + 1}`;
3080
+ const providerName = this.getProviderName(provider, i);
2873
3081
  try {
2874
3082
  return await provider.generate(request);
2875
3083
  } catch (error) {
@@ -2877,7 +3085,7 @@ var FallbackLLMProvider = class {
2877
3085
  failureErrors.push({ providerName, error: errorMessage });
2878
3086
  if (this.logFailover) {
2879
3087
  const nextProvider = this.providers[i + 1];
2880
- const nextName = nextProvider ? nextProvider.constructor.name || `Provider#${i + 2}` : "None";
3088
+ const nextName = nextProvider ? this.getProviderName(nextProvider, i + 1) : "None";
2881
3089
  console.warn(
2882
3090
  `[FallbackLLMProvider] ${providerName} failed: "${errorMessage}". Failing over to next provider: ${nextName}`
2883
3091
  );
@@ -3006,8 +3214,12 @@ var TieredLLMProvider = class _TieredLLMProvider {
3006
3214
  // src/adapters/rules/static-rules.adapter.ts
3007
3215
  var StaticRulesAdapter = class {
3008
3216
  rules;
3009
- constructor(rules) {
3010
- this.rules = rules;
3217
+ constructor(rules = []) {
3218
+ this.rules = rules.map((r) => {
3219
+ if (typeof r === "string") return r;
3220
+ if (r && typeof r === "object" && typeof r.rule === "string") return r.rule;
3221
+ return String(r);
3222
+ });
3011
3223
  }
3012
3224
  // Optional tenantId can be used for tenant-specific rules
3013
3225
  async getActiveRules(tenantId) {
@@ -3029,6 +3241,12 @@ var LocalFaqAdapter = class {
3029
3241
  this.smlEngine = options?.smlEngine || new SMLEngine();
3030
3242
  this.defaultThreshold = options?.defaultMatchThreshold ?? 0.6;
3031
3243
  }
3244
+ setSMLEngine(engine) {
3245
+ this.smlEngine = engine;
3246
+ }
3247
+ getSMLEngine() {
3248
+ return this.smlEngine;
3249
+ }
3032
3250
  /**
3033
3251
  * Initializes the adapter by reading the JSON file and caching embeddings in RAM.
3034
3252
  */
@@ -5,6 +5,34 @@ import type { Job } from './queue/job-queue.interface.js';
5
5
  import type { QueryContext } from '../interfaces/query-context.interface.js';
6
6
  import type { ToolAuditLog } from '../interfaces/tool.interface.js';
7
7
  import type { ICacheAdapter } from '../interfaces/cache.interface.js';
8
+ /**
9
+ * Enterprise Production-Grade Retrieval-Augmented Generation (RAG) Engine.
10
+ *
11
+ * Features:
12
+ * - Multi-tenant isolation & zero-trust security guardrails (prompt injection detection, PII redaction, boundary framing).
13
+ * - Pluggable LLM providers (Gemini, OpenAI, Ollama, Tiered, Fallback).
14
+ * - In-process Small Machine Learning (SML) engine for sub-millisecond semantic routing and multilingual embeddings.
15
+ * - Hybrid dense/sparse vector retrieval with Reciprocal Rank Fusion (RRF).
16
+ * - Governed tool execution with role-based access control (RBAC) and cryptographic mutation tickets.
17
+ * - High-throughput document ingestion supporting PDF (/FlateDecode decompressed), Markdown, and text streams.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const engine = new RAGEngine({
22
+ * llmProvider: new GeminiAdapter({ apiKey: process.env.GEMINI_API_KEY }),
23
+ * inputs: {
24
+ * vectorStore: new PostgresVectorAdapter({ connectionString: process.env.DATABASE_URL })
25
+ * }
26
+ * });
27
+ *
28
+ * const response = await engine.query('Summarize our Q3 compliance audit report.', {
29
+ * tenantId: 'enterprise-client-42',
30
+ * userId: 'usr_8472',
31
+ * roles: ['compliance_officer']
32
+ * });
33
+ * console.log(response.answer, response.sources);
34
+ * ```
35
+ */
8
36
  export declare class RAGEngine {
9
37
  private config;
10
38
  private confirmationService;
@@ -17,6 +45,11 @@ export declare class RAGEngine {
17
45
  private initPromise;
18
46
  private auditLogs;
19
47
  private readonly maxAuditLogs;
48
+ /**
49
+ * Initializes a new instance of `RAGEngine` with the provided configuration.
50
+ *
51
+ * @param config - The engine configuration options (`EngineConfig`).
52
+ */
20
53
  constructor(config: EngineConfig);
21
54
  /**
22
55
  * Records a tool audit log entry into a bounded ring buffer (max 1000 items)
@@ -31,8 +64,27 @@ export declare class RAGEngine {
31
64
  init(): Promise<void>;
32
65
  registerTool(tool: any): Promise<void>;
33
66
  /**
34
- * Executes the query lifecycle:
35
- * Security Guardrail -> Cache -> SML Routing -> Retrieval -> Untrusted Boundary Framing -> LLM Generation.
67
+ * Executes the complete RAG query lifecycle:
68
+ * 1. Security Inspection (Prompt Injection Defense, PII Masking)
69
+ * 2. Cache Lookup (Role & Tenant isolated caching)
70
+ * 3. SML Semantic Routing (FAQ matching and Tool execution)
71
+ * 4. Hybrid Dense/Sparse Retrieval (Vector search + BM25 keyword search)
72
+ * 5. Untrusted Boundary Framing (Anti-indirect injection prompt wrapping)
73
+ * 6. LLM Answer Synthesis & Citation Tagging
74
+ *
75
+ * @param userPrompt - The natural language query or question from the user.
76
+ * @param context - Optional multi-tenant security context including `tenantId`, `userId`, and `roles`.
77
+ * @returns Promise resolving to `RAGResponse` containing synthesized answer, citations, intent, and metrics.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const response = await engine.query('What is our return policy?', {
82
+ * tenantId: 'tenant_123',
83
+ * userId: 'usr_456',
84
+ * roles: ['customer']
85
+ * });
86
+ * console.log(response.answer);
87
+ * ```
36
88
  */
37
89
  query(userPrompt: string, context?: QueryContext): Promise<RAGResponse>;
38
90
  /**
@@ -49,6 +49,11 @@ export declare class RAGEvaluator {
49
49
  grounded: boolean;
50
50
  groundingScore: number;
51
51
  };
52
+ /**
53
+ * Evaluates Answer Faithfulness / Groundedness using deterministic heuristics.
54
+ * Note: This measures lexical consistency, numerical overlap, and negation polarity.
55
+ * It serves as a fast in-process heuristic filter and is not an external semantic LLM judge.
56
+ */
52
57
  static evaluateFaithfulness(answer: string, contextTexts: string[]): FaithfulnessEvalResult;
53
58
  /**
54
59
  * Conducts a full RAG evaluation given a query, generated answer, retrieved docs, and ground truth IDs.
@@ -1,4 +1,11 @@
1
1
  export type JobStatus = 'pending' | 'queued' | 'processing' | 'completed' | 'failed';
2
+ export declare const JobStatusValues: {
3
+ readonly PENDING: 'pending';
4
+ readonly QUEUED: 'queued';
5
+ readonly PROCESSING: 'processing';
6
+ readonly COMPLETED: 'completed';
7
+ readonly FAILED: 'failed';
8
+ };
2
9
  export interface Job<TData = any, TResult = any> {
3
10
  attemptsMade?: number | undefined;
4
11
  maxAttempts?: number | undefined;