@devilsdev/rag-pipeline-utils 2.1.11 → 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.
- package/.ragrc.schema.json +218 -0
- package/README.md +651 -393
- package/bin/cli.js +8 -14
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +17 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +611 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +265 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +903 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +85 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/structured-logger.js +361 -0
- 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;
|