@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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 (110) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +292 -32
  3. package/bin/cli.js +8 -14
  4. package/dist/index.d.ts +237 -0
  5. package/dist/index.mjs +22 -7
  6. package/package.json +17 -13
  7. package/src/ai/adaptive-retrieval.js +191 -0
  8. package/src/ai/federated-learning.js +214 -0
  9. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  10. package/src/ai/federation.js +76 -0
  11. package/src/ai/index-fixed.js +509 -0
  12. package/src/ai/index.js +33 -0
  13. package/src/ai/model-trainer.js +217 -0
  14. package/src/ai/model-training.js +205 -0
  15. package/src/ai/module-template.js +37 -0
  16. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  17. package/src/ai/multimodal-processing.js +268 -0
  18. package/src/ai/multimodal.js +71 -0
  19. package/src/ai/orchestrator.js +70 -0
  20. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  21. package/src/ai/retrieval-engine.js +69 -0
  22. package/src/ai/training/model-training-orchestrator.js +228 -0
  23. package/src/ci/workflow-analyzer.js +71 -0
  24. package/src/cli/commands/ai-ml.js +967 -0
  25. package/src/cli/commands/docs.js +228 -0
  26. package/src/cli/commands/dx.js +538 -0
  27. package/src/cli/commands/plugin-hub.js +875 -0
  28. package/src/cli/doctor-command.js +639 -0
  29. package/src/cli/enhanced-cli-commands.js +945 -0
  30. package/src/cli/interactive-wizard.js +997 -0
  31. package/src/cli/plugin-marketplace-commands.js +869 -0
  32. package/src/cli.js +17 -0
  33. package/src/config/enhanced-ragrc-schema.js +632 -0
  34. package/src/config/load-config.js +54 -0
  35. package/src/config/load-plugin-config.js +69 -0
  36. package/src/config/normalize-config.js +105 -0
  37. package/src/config/schema-validator.js +315 -0
  38. package/src/config/validate-plugin-schema.js +35 -0
  39. package/src/config/validate-schema.js +113 -0
  40. package/src/contracts/plugin-contracts.js +33 -0
  41. package/src/core/create-pipeline.js +99 -0
  42. package/src/core/dependency-injection.js +242 -0
  43. package/src/core/observability/event-logger.js +241 -0
  44. package/src/core/observability/instrumented-pipeline.js +609 -0
  45. package/src/core/observability/metrics.js +497 -0
  46. package/src/core/observability/tracing.js +781 -0
  47. package/src/core/performance/benchmark.js +516 -0
  48. package/src/core/performance/parallel-processor.js +240 -0
  49. package/src/core/performance/streaming-safeguards.js +611 -0
  50. package/src/core/pipeline-factory.js +60 -0
  51. package/src/core/plugin-contracts.js +89 -0
  52. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  53. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  54. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  55. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  56. package/src/core/plugin-registry.js +265 -0
  57. package/src/dag/dag-engine.js +954 -0
  58. package/src/dag/dag-node.js +76 -0
  59. package/src/dag/enhanced-dag-engine.js +822 -0
  60. package/src/dx/index.js +40 -0
  61. package/src/dx/integration-templates.js +665 -0
  62. package/src/dx/performance-profiler.js +968 -0
  63. package/src/dx/realtime-debugger.js +755 -0
  64. package/src/dx/visual-pipeline-builder.js +578 -0
  65. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  66. package/src/ecosystem/plugin-certification.js +580 -0
  67. package/src/ecosystem/plugin-hub.js +821 -0
  68. package/src/enterprise/audit-logging.js +967 -0
  69. package/src/enterprise/data-governance.js +408 -0
  70. package/src/enterprise/multi-tenancy.js +496 -0
  71. package/src/enterprise/sso-integration.js +903 -0
  72. package/src/evaluate/evaluator.js +52 -0
  73. package/src/evaluate/scoring.js +91 -0
  74. package/src/index.js +67 -0
  75. package/src/ingest.js +59 -0
  76. package/src/loader/csv-loader.js +38 -0
  77. package/src/loader/directory-loader.js +48 -0
  78. package/src/loader/html-loader.js +62 -0
  79. package/src/loader/markdown-loader.js +67 -0
  80. package/src/mocks/openai-embedder.js +119 -0
  81. package/src/mocks/openai-llm.js +77 -0
  82. package/src/mocks/pdf-loader.js +21 -0
  83. package/src/mocks/pinecone-retriever.js +106 -0
  84. package/src/observability/audit-logger.js +758 -0
  85. package/src/observability/dashboard-config.json +675 -0
  86. package/src/observability/event-logger.js +310 -0
  87. package/src/observability/metrics.js +691 -0
  88. package/src/observability/security-monitor.js +830 -0
  89. package/src/observability/slo-monitor.js +319 -0
  90. package/src/observability/telemetry.js +686 -0
  91. package/src/pipeline/pipeline.js +18 -0
  92. package/src/plugins/sample-embedder.js +53 -0
  93. package/src/plugins/sample-llm.js +30 -0
  94. package/src/plugins/sample-loader.js +24 -0
  95. package/src/plugins/sample-retriever.js +84 -0
  96. package/src/query.js +117 -0
  97. package/src/reranker/llm-reranker.js +85 -0
  98. package/src/scripts/script-utilities.js +36 -0
  99. package/src/security/plugin-sandbox.js +586 -0
  100. package/src/security/plugin-signature-verifier.js +414 -0
  101. package/src/security/validator.js +36 -0
  102. package/src/utils/ci/diagnostic-reporter.js +57 -0
  103. package/src/utils/logger.js +211 -0
  104. package/src/utils/performance-monitor.js +561 -0
  105. package/src/utils/plugin-scaffolder.js +447 -0
  106. package/src/utils/retry-policy.js +443 -0
  107. package/src/utils/retry.js +107 -0
  108. package/src/utils/safe-expression-evaluator.js +579 -0
  109. package/src/utils/structured-logger.js +361 -0
  110. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Evaluation runner with scoring metrics for batch RAG QA
3
+ */
4
+
5
+ const { createRagPipeline } = require('../core/create-pipeline.js');
6
+ const { logger } = require('../utils/logger.js');
7
+ const fs = require('fs/promises');
8
+ const path = require('path');
9
+ const { scoreAnswer } = require('./scoring.js');
10
+
11
+ /**
12
+ * Evaluate a list of prompt/answer pairs using the RAG pipeline and compute scores
13
+ * @param {string} datasetPath - Path to JSON file with { prompt, expected }[]
14
+ * @param {object} _config - RAG plugin _config (loader optional)
15
+ * @returns {Promise<Array<{ prompt: string, expected: string, actual: string, success: boolean, scores: object }>>}
16
+ */
17
+ async function evaluateRagDataset(_datasetPath, _config) {
18
+ const file = await fs.readFile(path.resolve(_datasetPath), 'utf-8');
19
+ const cases = JSON.parse(file);
20
+ const pipeline = createRagPipeline(_config);
21
+
22
+ const results = [];
23
+ for (const { prompt, expected } of cases) {
24
+ try {
25
+ logger.info({ prompt }, 'Evaluating case');
26
+ const actual = await pipeline.query(prompt);
27
+ const success = normalizeText(actual) === normalizeText(expected);
28
+ const scores = scoreAnswer(actual, expected);
29
+ results.push({ prompt, expected, actual, success, scores });
30
+ } catch (err) {
31
+ logger.error({ prompt, error: err.message }, 'Evaluation failed');
32
+ results.push({
33
+ prompt,
34
+ expected,
35
+ actual: '',
36
+ success: false,
37
+ scores: { bleu: 0, rouge: 0 },
38
+ });
39
+ }
40
+ }
41
+ return results;
42
+ }
43
+
44
+ function normalizeText(txt) {
45
+ return txt
46
+ .trim()
47
+ .toLowerCase()
48
+ .replace(/[^a-z0-9 ]/g, '')
49
+ .replace(/\s+/g, ' ');
50
+ }
51
+
52
+ module.exports = { evaluateRagDataset, normalizeText };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/evaluate/scoring.js
4
+ * Description: Basic scoring metrics (BLEU, ROUGE-L) for RAG output evaluation
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ /**
9
+ * Tokenize a string by words
10
+ * @param {string} text
11
+ * @returns {string[]}
12
+ */
13
+ function tokenize(text) {
14
+ return text
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9 ]/g, '')
17
+ .split(/\s+/)
18
+ .filter(Boolean);
19
+ }
20
+
21
+ /**
22
+ * Compute BLEU-1 score: unigram precision
23
+ * @param {string[]} candidate - Generated tokens
24
+ * @param {string[]} reference - Reference tokens
25
+ * @returns {number}
26
+ */
27
+ function computeBLEU(_candidate, _reference) {
28
+ const candCounts = _candidate.reduce(
29
+ (acc, t) => ((acc[t] = (acc[t] || 0) + 1), acc),
30
+ {},
31
+ );
32
+ const refCounts = _reference.reduce(
33
+ (acc, t) => ((acc[t] = (acc[t] || 0) + 1), acc),
34
+ {},
35
+ );
36
+
37
+ let match = 0;
38
+ for (const token of Object.keys(candCounts)) {
39
+ match += Math.min(candCounts[token], refCounts[token] || 0);
40
+ }
41
+ return _candidate.length ? match / _candidate.length : 0;
42
+ }
43
+
44
+ /**
45
+ * Compute ROUGE-L (Longest Common Subsequence) recall
46
+ * @param {string[]} candidate
47
+ * @param {string[]} reference
48
+ * @returns {number}
49
+ */
50
+ function computeROUGE(_candidate, _reference) {
51
+ const m = _candidate.length;
52
+ const n = _reference.length;
53
+ const dp = Array(m + 1)
54
+ .fill(null)
55
+ .map(() => Array(n + 1).fill(0));
56
+
57
+ for (let i = 1; i <= m; i++) {
58
+ for (let j = 1; j <= n; j++) {
59
+ if (_candidate[i - 1] === _reference[j - 1]) {
60
+ dp[i][j] = dp[i - 1][j - 1] + 1;
61
+ } else {
62
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
63
+ }
64
+ }
65
+ }
66
+
67
+ const lcs = dp[m][n];
68
+ return _reference.length ? lcs / _reference.length : 0;
69
+ }
70
+
71
+ /**
72
+ * Score a prompt-answer pair using multiple metrics
73
+ * @param {string} actual - LLM response
74
+ * @param {string} expected - Reference answer
75
+ * @returns {{ bleu: number, rouge: number }}
76
+ */
77
+ function scoreAnswer(_actual, _expected) {
78
+ const candTokens = tokenize(_actual);
79
+ const refTokens = tokenize(_expected);
80
+ return {
81
+ bleu: computeBLEU(candTokens, refTokens),
82
+ rouge: computeROUGE(candTokens, refTokens),
83
+ };
84
+ }
85
+
86
+ module.exports = {
87
+ scoreAnswer,
88
+ tokenize,
89
+ computeBLEU,
90
+ computeROUGE,
91
+ };
package/src/index.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * RAG Pipeline Utils - Public API
3
+ * Main entry point for consumers of the library
4
+ */
5
+
6
+ // Core pipeline functionality
7
+ const { createRagPipeline } = require('./core/create-pipeline');
8
+
9
+ // Configuration utilities
10
+ const { loadConfig } = require('./config/load-config');
11
+ const { validateRagrc } = require('./config/enhanced-ragrc-schema');
12
+ const { normalizeConfig } = require('./config/normalize-config');
13
+
14
+ // Plugin system
15
+ const pluginRegistry = require('./core/plugin-registry');
16
+
17
+ // Utilities
18
+ const logger = require('./utils/logger');
19
+
20
+ // AI/ML capabilities
21
+ const MultiModalProcessor = require('./ai/multimodal');
22
+ const AdaptiveRetrievalEngine = require('./ai/retrieval-engine');
23
+
24
+ // DAG engine for complex workflows
25
+ const { DAGEngine } = require('./dag/dag-engine');
26
+
27
+ // Performance and observability
28
+ const ParallelProcessor = require('./core/performance/parallel-processor');
29
+ const eventLogger = require('./core/observability/event-logger');
30
+ const metrics = require('./core/observability/metrics');
31
+
32
+ // Enterprise features
33
+ const { AuditLogger } = require('./enterprise/audit-logging');
34
+ const { DataGovernance } = require('./enterprise/data-governance');
35
+
36
+ module.exports = {
37
+ // Core API
38
+ createRagPipeline,
39
+ createPipeline: createRagPipeline, // Alias for backward compatibility
40
+
41
+ // Configuration
42
+ loadConfig,
43
+ validateRagrc,
44
+ normalizeConfig,
45
+
46
+ // Plugin system
47
+ pluginRegistry,
48
+
49
+ // Utilities
50
+ logger,
51
+
52
+ // AI/ML
53
+ MultiModalProcessor,
54
+ AdaptiveRetrievalEngine,
55
+
56
+ // Workflow engine
57
+ DAGEngine,
58
+
59
+ // Performance & Observability
60
+ ParallelProcessor,
61
+ eventLogger,
62
+ metrics,
63
+
64
+ // Enterprise
65
+ AuditLogger,
66
+ DataGovernance,
67
+ };
package/src/ingest.js ADDED
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+ /**
3
+ * Minimal ingest surface; wire real backend later.
4
+ */
5
+ const path = require('path');
6
+ const { logger } = require('./utils/logger.js');
7
+
8
+ /**
9
+ * Ingest a single document by path.
10
+ * @param {string} filepath - Document path
11
+ * @param {object} config - Configuration object
12
+ * @returns {boolean} - Result (synchronous for now, can be made async later)
13
+ */
14
+ function ingestDocument(filepath, config) {
15
+ // Validate document path - always throw synchronously for validation errors
16
+ if (!filepath || typeof filepath !== 'string' || filepath.trim() === '') {
17
+ throw new Error('Document path is required');
18
+ }
19
+
20
+ // Validate configuration - always throw synchronously for validation errors
21
+ if (!config || typeof config !== 'object') {
22
+ throw new Error('Configuration is required');
23
+ }
24
+
25
+ // Process document with safer error handling
26
+ try {
27
+ const abs = path.resolve(filepath);
28
+ logger.info({ file: abs }, 'ingest:start');
29
+
30
+ // TODO: real ingestion (mime sniff, loader selection, vector upsert)
31
+ // For now, return success
32
+ logger.info({ file: abs }, 'ingest:complete');
33
+ return true;
34
+ } catch (error) {
35
+ // Log processing errors but don't crash
36
+ logger.error({ file: filepath, error: error.message }, 'ingest:error');
37
+
38
+ // Re-throw with more context for better debugging
39
+ const enhancedError = new Error(
40
+ `Failed to ingest document '${filepath}': ${error.message}`,
41
+ );
42
+ enhancedError.originalError = error;
43
+ enhancedError.filepath = filepath;
44
+ throw enhancedError;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Async version of ingestDocument for future use
50
+ * @param {string} filepath - Document path
51
+ * @param {object} config - Configuration object
52
+ * @returns {Promise<boolean>} - Result promise
53
+ */
54
+ async function ingestDocumentAsync(filepath, config) {
55
+ // Reuse sync validation and processing
56
+ return ingestDocument(filepath, config);
57
+ }
58
+
59
+ module.exports = { ingestDocument, ingestDocumentAsync };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * CSV document loader with row-wise chunking
3
+ */
4
+
5
+ const fs = require('fs/promises');
6
+ const path = require('path');
7
+ const { parse } = require('csv-parse/sync');
8
+
9
+ class CSVLoader {
10
+ /**
11
+ * Load and parse a CSV file into chunks
12
+ * @param {string} filePath
13
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
14
+ */
15
+ async load(filePath) {
16
+ const absPath = path.resolve(filePath);
17
+
18
+ try {
19
+ const raw = await fs.readFile(absPath, 'utf8');
20
+ const src = raw.replace(/^\uFEFF/, ''); // Remove BOM
21
+ const normalizedSrc = src.replace(/\r\n|\r/g, '\n'); // Normalize line endings
22
+
23
+ const records = parse(normalizedSrc, { columns: true });
24
+
25
+ return [
26
+ {
27
+ chunk: () => records.map((r) => Object.values(r).join(' ')),
28
+ },
29
+ ];
30
+ } catch (error) {
31
+ throw new Error(`CSV parsing failed for ${filePath}: ${error.message}`);
32
+ }
33
+ }
34
+ }
35
+
36
+ module.exports = {
37
+ CSVLoader,
38
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/loader/directory-loader.js
4
+ * Description: Recursive directory loader with file _type routing
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ const fg = require('fast-glob');
9
+ // eslint-disable-line global-require
10
+ const { MarkdownLoader } = require('./markdown-loader.js');
11
+ // eslint-disable-line global-require
12
+ const { HTMLLoader } = require('./html-loader.js');
13
+ // eslint-disable-line global-require
14
+ const { CSVLoader } = require('./csv-loader.js');
15
+ // eslint-disable-line global-require
16
+
17
+ const loaders = {
18
+ '.md': new MarkdownLoader(),
19
+ '.html': new HTMLLoader(),
20
+ '.csv': new CSVLoader(),
21
+ };
22
+
23
+ class DirectoryLoader {
24
+ /**
25
+ * Load and route supported files from a directory
26
+ * @param {string} dirPath
27
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
28
+ */
29
+ async load(dirPath) {
30
+ const matches = await fg([`${dirPath}/**/*.{md,html,csv}`]);
31
+ const all = [];
32
+ for (const file of matches) {
33
+ const ext = file.slice(file.lastIndexOf('.'));
34
+ const loader = loaders[ext];
35
+ if (loader) {
36
+ const docs = await loader.load(file);
37
+ all.push(...docs);
38
+ }
39
+ }
40
+ return all;
41
+ }
42
+ }
43
+
44
+ // Default export
45
+
46
+ module.exports = {
47
+ DirectoryLoader,
48
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Loader for HTML (.html) documents
3
+ */
4
+
5
+ const fs = require('fs/promises');
6
+ const path = require('path');
7
+ const { JSDOM } = require('jsdom');
8
+
9
+ /**
10
+ * HTMLLoader reads HTML files and extracts visible text.
11
+ * Implements the Loader interface.
12
+ */
13
+ class HTMLLoader {
14
+ /**
15
+ * Load and extract text from an HTML file
16
+ * @param {string} filePath - Path to .html file
17
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
18
+ */
19
+ async load(filePath) {
20
+ const absPath = path.resolve(filePath);
21
+ const raw = await fs.readFile(absPath, 'utf-8');
22
+ const dom = new JSDOM(raw);
23
+ let text = dom.window.document.body.textContent || '';
24
+
25
+ const MAX_LEN = 1_000_000;
26
+ text = String(text).replace(/\s+/g, ' ').trim();
27
+ if (text.length > MAX_LEN) text = text.slice(0, MAX_LEN);
28
+
29
+ return [
30
+ {
31
+ chunk: () => this._chunkText(text),
32
+ },
33
+ ];
34
+ }
35
+
36
+ /**
37
+ * Chunk HTML text content by sentence
38
+ * @param {string} input
39
+ * @param {number} maxLen
40
+ * @returns {string[]}
41
+ */
42
+ _chunkText(input, maxLen = 500) {
43
+ const sentences = input.split(/(?<=[.!?])\s+/);
44
+ const chunks = [];
45
+ let buffer = '';
46
+
47
+ for (const sentence of sentences) {
48
+ if ((buffer + sentence).length <= maxLen) {
49
+ buffer += sentence + ' ';
50
+ } else {
51
+ chunks.push(buffer.trim());
52
+ buffer = sentence + ' ';
53
+ }
54
+ }
55
+ if (buffer.trim()) chunks.push(buffer.trim());
56
+ return chunks;
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ HTMLLoader,
62
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ const fs = require('fs');
3
+ * Version: 0.1.0
4
+ * Path: /src/loader/markdown-loader.js
5
+ * Description: Loader for Markdown (.md) documents
6
+ * Author: Ali Kahwaji
7
+ */
8
+
9
+ const fs = require('fs/promises');
10
+ // eslint-disable-line global-require
11
+ const path = require('path');
12
+ // eslint-disable-line global-require
13
+ const { marked } = require('marked');
14
+ // eslint-disable-line global-require
15
+
16
+ /**
17
+ * MarkdownLoader reads and parses .md files into plain text chunks.
18
+ * Implements the Loader interface.
19
+ */
20
+ class MarkdownLoader {
21
+ /**
22
+ * Load and parse Markdown file
23
+ * @param {string} _filePath - Path to .md file
24
+ * @returns {Promise<Array<{ chunk(): string[] }>>}
25
+ */
26
+ async load(_filePath) {
27
+ const absPath = path.resolve(_filePath);
28
+ const raw = await fs.readFile(absPath, 'utf-8');
29
+ const html = marked(raw);
30
+ const text = html.replace(/<[^>]+>/g, ''); // strip tags
31
+
32
+ return [
33
+ {
34
+ chunk: () => this._chunkText(text),
35
+ },
36
+ ];
37
+ }
38
+
39
+ /**
40
+ * Simple sentence-based chunking strategy
41
+ * @param {string} input
42
+ * @param {number} maxLen
43
+ * @returns {string[]}
44
+ */
45
+ _chunkText(input, maxLen = 500) {
46
+ const sentences = input.split(/(?<=[.!?])\s+/);
47
+ const chunks = [];
48
+ let buffer = '';
49
+
50
+ for (const sentence of sentences) {
51
+ if ((buffer + sentence).length <= maxLen) {
52
+ buffer += sentence + ' ';
53
+ } else {
54
+ chunks.push(buffer.trim());
55
+ buffer = sentence + ' ';
56
+ }
57
+ }
58
+ if (buffer.trim()) chunks.push(buffer.trim());
59
+ return chunks;
60
+ }
61
+ }
62
+
63
+ // Default export
64
+
65
+ module.exports = {
66
+ MarkdownLoader,
67
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Version: 2.0.0
3
+ * Description: Mock implementation of OpenAI embedder for testing and development
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ class OpenAIEmbedder {
8
+ constructor(_options = {}) {
9
+ this.apiKey = _options.apiKey || "mock-api-key";
10
+ this.model = _options.model || "text-embedding-ada-002";
11
+ this.dimensions = _options.dimensions || 1536;
12
+ this.options = _options;
13
+
14
+ // Configurable batch size from environment or options
15
+ this.batchSize =
16
+ parseInt(process.env.RAG_EMBEDDER_BATCH_SIZE) ||
17
+ _options.batchSize ||
18
+ 100; // Default batch size for OpenAI API
19
+ }
20
+
21
+ /**
22
+ * Generate embeddings for an array of text chunks
23
+ * @param {string[]} chunks - Array of text chunks to embed
24
+ * @returns {Promise<number[][]>} Array of embedding vectors
25
+ */
26
+ async embed(chunks) {
27
+ if (!Array.isArray(chunks)) {
28
+ throw new Error("embed() expects an array of strings");
29
+ }
30
+
31
+ // Process in batches for better memory management and API rate limiting
32
+ const results = [];
33
+ for (let i = 0; i < chunks.length; i += this.batchSize) {
34
+ const batch = chunks.slice(i, i + this.batchSize);
35
+ const batchResults = await this._processBatch(batch);
36
+ results.push(...batchResults);
37
+
38
+ // Optional progress callback
39
+ if (this.options.onProgress) {
40
+ this.options.onProgress({
41
+ processed: Math.min(i + this.batchSize, chunks.length),
42
+ total: chunks.length,
43
+ stage: "embedding",
44
+ });
45
+ }
46
+
47
+ // Simulate API rate limiting delay
48
+ if (chunks.length > this.batchSize) {
49
+ await new Promise((resolve) => setTimeout(resolve, 50));
50
+ }
51
+ }
52
+
53
+ return results;
54
+ }
55
+
56
+ /**
57
+ * Process a batch of chunks
58
+ * @private
59
+ * @param {string[]} batch - Batch of text chunks
60
+ * @returns {Promise<number[][]>} Array of embedding vectors
61
+ */
62
+ async _processBatch(batch) {
63
+ // Return mock embeddings - deterministic based on content for testing
64
+ return batch.map((chunk) => this.#generateMockEmbedding(chunk));
65
+ }
66
+
67
+ /**
68
+ * Generate embedding for a single query string
69
+ * @param {string} query - Query string to embed
70
+ * @returns {Promise<number[]>} Embedding vector
71
+ */
72
+ async embedQuery(query) {
73
+ if (typeof query !== "string") {
74
+ throw new Error("embedQuery() expects a string");
75
+ }
76
+
77
+ return this.#generateMockEmbedding(query);
78
+ }
79
+
80
+ /**
81
+ * Generate a deterministic mock embedding based on input text
82
+ * @private
83
+ * @param {string} text - Input text
84
+ * @returns {number[]} Mock embedding vector
85
+ */
86
+ #generateMockEmbedding(text) {
87
+ // Create deterministic "embedding" based on text content
88
+ const hash = this.#simpleHash(text);
89
+ const embedding = [];
90
+
91
+ for (let i = 0; i < this.dimensions; i++) {
92
+ // Generate pseudo-random values based on hash and index
93
+ const value = Math.sin(hash + i) * 0.5;
94
+ embedding.push(value);
95
+ }
96
+
97
+ return embedding;
98
+ }
99
+
100
+ /**
101
+ * Simple hash function for deterministic mock data
102
+ * @private
103
+ * @param {string} str - Input string
104
+ * @returns {number} Hash value
105
+ */
106
+ #simpleHash(str) {
107
+ let hash = 0;
108
+ for (let i = 0; i < str.length; i++) {
109
+ const char = str.charCodeAt(i);
110
+ hash = (hash << 5) - hash + char;
111
+ hash = hash & hash; // Convert to 32-bit integer
112
+ }
113
+ return hash;
114
+ }
115
+ }
116
+
117
+ module.exports = {
118
+ OpenAIEmbedder,
119
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Version: 2.0.0
3
+ * File: /src/mocks/openai-llm.js
4
+ * Description: Mock implementation of an OpenAI LLM with streaming support
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ class OpenAILLM {
9
+ constructor(_options = {}) {
10
+ this.streamDelay = _options.streamDelay || 50; // ms between tokens
11
+ this.model = _options.model || "gpt-3.5-turbo";
12
+ }
13
+
14
+ /**
15
+ * Generates a complete response from input prompt and context
16
+ * @param {string} prompt
17
+ * @param {Array<{ text: string }>} context
18
+ * @returns {Promise<string>}
19
+ */
20
+ async generate(prompt, context) {
21
+ const contextText = Array.isArray(context)
22
+ ? context.map((d) => d.text || d).join(", ")
23
+ : "";
24
+ return `Generated answer using context: ${contextText}. Query: "${prompt}". This is a comprehensive response that addresses the user's question based on the retrieved information.`;
25
+ }
26
+
27
+ /**
28
+ * Generates a streaming response token-by-token
29
+ * @param {string} prompt
30
+ * @param {Array<{ text: string }>} context
31
+ * @returns {AsyncIterable<string>} Stream of tokens
32
+ */
33
+ async *generateStream(prompt, context) {
34
+ const fullResponse = await this.generate(prompt, context);
35
+ const tokens = this.#tokenize(fullResponse);
36
+
37
+ for (const token of tokens) {
38
+ // Simulate network delay between tokens
39
+ await this.#delay(this.streamDelay);
40
+ yield token;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Simple tokenization for mock streaming
46
+ * @private
47
+ * @param {string} text - Text to tokenize
48
+ * @returns {string[]} Array of tokens
49
+ */
50
+ #tokenize(text) {
51
+ // Simple word-based tokenization for demo purposes
52
+ // In real implementation, this would use proper tokenization
53
+ return text.split(/\s+/).map((word) => word + " ");
54
+ }
55
+
56
+ /**
57
+ * Utility delay function for simulating streaming
58
+ * @private
59
+ * @param {number} ms - Milliseconds to delay
60
+ * @returns {Promise<void>}
61
+ */
62
+ #delay(ms) {
63
+ return new Promise((resolve) => setTimeout(resolve, ms));
64
+ }
65
+
66
+ /**
67
+ * Check if this LLM _instance supports streaming
68
+ * @returns {boolean}
69
+ */
70
+ supportsStreaming() {
71
+ return true;
72
+ }
73
+ }
74
+
75
+ // Default export
76
+
77
+ module.exports = OpenAILLM;