@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.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 (160) hide show
  1. package/README.md +293 -369
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +20 -8
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+
5
+ /**
6
+ * Default configuration for the SelfCritiqueChecker.
7
+ * @type {object}
8
+ */
9
+ const DEFAULT_CONFIG = {
10
+ approvalThreshold: 0.5,
11
+ };
12
+
13
+ /**
14
+ * Answer verification against retrieved source documents.
15
+ * Uses CitationTracker (lazy-loaded) to compute groundedness and detect hallucinations.
16
+ *
17
+ * @extends EventEmitter
18
+ */
19
+ class SelfCritiqueChecker extends EventEmitter {
20
+ /**
21
+ * @param {object} [options] - Override default configuration
22
+ * @param {number} [options.approvalThreshold=0.5] - Minimum groundedness score to approve an answer
23
+ */
24
+ constructor(options = {}) {
25
+ super();
26
+ this.config = { ...DEFAULT_CONFIG, ...options };
27
+ }
28
+
29
+ /**
30
+ * Check an answer against retrieved documents for groundedness.
31
+ *
32
+ * @param {string} answer - The LLM-generated answer to verify
33
+ * @param {Array<object>} retrievedDocs - Source documents to check against
34
+ * @returns {{
35
+ * approved: boolean,
36
+ * score: number,
37
+ * issues: Array<{sentence: string, classification: string}>,
38
+ * citationResult: object
39
+ * }}
40
+ * @throws {Error} If answer or retrievedDocs are invalid
41
+ * @fires SelfCritiqueChecker#checked
42
+ */
43
+ check(answer, retrievedDocs) {
44
+ // 1. Validate inputs
45
+ if (!answer || typeof answer !== 'string' || !answer.trim()) {
46
+ throw new Error(
47
+ 'SelfCritiqueChecker.check(): answer must be a non-empty string',
48
+ );
49
+ }
50
+ if (!Array.isArray(retrievedDocs) || retrievedDocs.length === 0) {
51
+ throw new Error(
52
+ 'SelfCritiqueChecker.check(): retrievedDocs must be a non-empty array',
53
+ );
54
+ }
55
+
56
+ // 2. Lazy-require CitationTracker (not loaded at module level)
57
+ const { CitationTracker } = require('../citation/citation-tracker');
58
+
59
+ // 3. Create tracker instance
60
+ const tracker = new CitationTracker();
61
+
62
+ // 4. Track citations
63
+ const citationResult = tracker.track(answer, retrievedDocs);
64
+
65
+ // 5. Extract groundedness and hallucination data
66
+ const { groundednessScore, hallucinationReport } = citationResult;
67
+
68
+ // 6. Determine approval
69
+ const approved = groundednessScore >= this.config.approvalThreshold;
70
+
71
+ // 7. Build issues array from hallucination report
72
+ const issues = [];
73
+ if (hallucinationReport && Array.isArray(hallucinationReport.sentences)) {
74
+ for (const s of hallucinationReport.sentences) {
75
+ if (
76
+ s.classification === 'definite_hallucination' ||
77
+ s.classification === 'likely_hallucination'
78
+ ) {
79
+ issues.push({
80
+ sentence: s.sentence || s.text || '',
81
+ classification: s.classification,
82
+ });
83
+ }
84
+ }
85
+ }
86
+
87
+ // 8. Emit 'checked' event
88
+ const result = {
89
+ approved,
90
+ score: groundednessScore,
91
+ issues,
92
+ citationResult,
93
+ };
94
+
95
+ this.emit('checked', result);
96
+
97
+ // 9. Return
98
+ return result;
99
+ }
100
+ }
101
+
102
+ module.exports = { SelfCritiqueChecker, DEFAULT_CONFIG };
@@ -186,6 +186,3 @@ const adaptiveRetrievalEngine = {
186
186
  };
187
187
 
188
188
  module.exports = adaptiveRetrievalEngine;
189
-
190
- // CJS+ESM interop pattern
191
- module.exports.default = module.exports;
@@ -209,6 +209,3 @@ const federatedLearningCoordinator = {
209
209
 
210
210
  // Export singleton as default
211
211
  module.exports = federatedLearningCoordinator;
212
-
213
- // CJS+ESM interop pattern
214
- module.exports.default = module.exports;
@@ -68,9 +68,4 @@ class FederatedLearningCoordinator {
68
68
  }
69
69
  }
70
70
 
71
- // Create and export singleton instance
72
- const coordinator = new FederatedLearningCoordinator();
73
-
74
- module.exports = coordinator;
75
- module.exports.FederatedLearningCoordinator = FederatedLearningCoordinator;
76
- module.exports.default = module.exports;
71
+ module.exports = { FederatedLearningCoordinator };
package/src/ai/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AI/ML Module Index - Decomposed Architecture
3
- * Provides unified interface to 4 separate AI modules as required by CLAUDE.md
3
+ * Provides unified interface to 4 separate AI modules
4
4
  *
5
5
  * Decomposed modules:
6
6
  * - orchestrator.js - ModelTrainingOrchestrator
@@ -9,25 +9,14 @@
9
9
  * - federation.js - FederatedLearningCoordinator
10
10
  */
11
11
 
12
- const orchestrator = require("./orchestrator");
13
- const retrievalEngine = require("./retrieval-engine");
14
- const multimodal = require("./multimodal");
15
- const federation = require("./federation");
12
+ const { ModelTrainingOrchestrator } = require("./orchestrator");
13
+ const { AdaptiveRetrievalEngine } = require("./retrieval-engine");
14
+ const { MultiModalProcessor } = require("./multimodal");
15
+ const { FederatedLearningCoordinator } = require("./federation");
16
16
 
17
- // Export the decomposed modules
18
17
  module.exports = {
19
- // Primary exports with new decomposed architecture
20
- ModelTrainingOrchestrator: orchestrator,
21
- AdaptiveRetrievalEngine: retrievalEngine,
22
- MultiModalProcessor: multimodal,
23
- FederatedLearningCoordinator: federation,
24
-
25
- // Legacy names for backward compatibility
26
- modelTrainer: orchestrator,
27
- adaptiveRetrieval: retrievalEngine,
28
- multiModalProcessor: multimodal,
29
- federatedLearning: federation,
18
+ ModelTrainingOrchestrator,
19
+ AdaptiveRetrievalEngine,
20
+ MultiModalProcessor,
21
+ FederatedLearningCoordinator,
30
22
  };
31
-
32
- // CJS+ESM interop pattern
33
- module.exports.default = module.exports;
@@ -200,6 +200,3 @@ const modelTrainingOrchestrator = {
200
200
 
201
201
  // Export singleton as default
202
202
  module.exports = modelTrainingOrchestrator;
203
-
204
- // CJS+ESM interop pattern
205
- module.exports.default = module.exports;
@@ -263,6 +263,3 @@ const multiModalProcessor = {
263
263
 
264
264
  // Export singleton as default
265
265
  module.exports = multiModalProcessor;
266
-
267
- // CJS+ESM interop pattern
268
- module.exports.default = module.exports;
@@ -63,9 +63,4 @@ class MultiModalProcessor {
63
63
  }
64
64
  }
65
65
 
66
- // Create and export singleton instance
67
- const processor = new MultiModalProcessor();
68
-
69
- module.exports = processor;
70
- module.exports.MultiModalProcessor = MultiModalProcessor;
71
- module.exports.default = module.exports;
66
+ module.exports = { MultiModalProcessor };
@@ -62,9 +62,4 @@ class ModelTrainingOrchestrator {
62
62
  }
63
63
  }
64
64
 
65
- // Create and export singleton instance
66
- const orchestrator = new ModelTrainingOrchestrator();
67
-
68
- module.exports = orchestrator;
69
- module.exports.ModelTrainingOrchestrator = ModelTrainingOrchestrator;
70
- module.exports.default = module.exports;
65
+ module.exports = { ModelTrainingOrchestrator };
@@ -61,9 +61,4 @@ class AdaptiveRetrievalEngine {
61
61
  }
62
62
  }
63
63
 
64
- // Create and export singleton instance
65
- const engine = new AdaptiveRetrievalEngine();
66
-
67
- module.exports = engine;
68
- module.exports.AdaptiveRetrievalEngine = AdaptiveRetrievalEngine;
69
- module.exports.default = module.exports;
64
+ module.exports = { AdaptiveRetrievalEngine };
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+ const { sentenceChunk } = require('./strategies/sentence');
5
+ const { fixedSizeChunk } = require('./strategies/fixed-size');
6
+ const { recursiveChunk } = require('./strategies/recursive');
7
+ const { semanticChunk } = require('./strategies/semantic');
8
+ const { structureAwareChunk } = require('./strategies/structure-aware');
9
+
10
+ /**
11
+ * Default configuration for the chunking engine.
12
+ * @type {object}
13
+ */
14
+ const DEFAULT_CONFIG = {
15
+ strategy: 'recursive',
16
+ chunkSize: 512,
17
+ chunkOverlap: 50,
18
+ minChunkSize: 50,
19
+ separators: ['\n\n', '\n', '. ', ' '],
20
+ similarityThreshold: 0.3,
21
+ };
22
+
23
+ /**
24
+ * ChunkingEngine orchestrates text chunking using pluggable strategies.
25
+ * Extends EventEmitter to emit 'chunk' events after each chunking operation.
26
+ *
27
+ * @extends EventEmitter
28
+ */
29
+ class ChunkingEngine extends EventEmitter {
30
+ /**
31
+ * Create a ChunkingEngine instance.
32
+ *
33
+ * @param {object} [options={}] - Configuration options merged with DEFAULT_CONFIG.
34
+ * @param {string} [options.strategy='recursive'] - Default strategy name.
35
+ * @param {number} [options.chunkSize=512] - Default chunk size.
36
+ * @param {number} [options.chunkOverlap=50] - Default chunk overlap.
37
+ * @param {number} [options.minChunkSize=50] - Default minimum chunk size.
38
+ * @param {string[]} [options.separators] - Default separators for recursive strategy.
39
+ * @param {number} [options.similarityThreshold=0.3] - Default similarity threshold for semantic strategy.
40
+ */
41
+ constructor(options = {}) {
42
+ super();
43
+ this._config = { ...DEFAULT_CONFIG, ...options };
44
+ this._strategies = new Map();
45
+
46
+ // Register built-in strategies
47
+ this._strategies.set('sentence', sentenceChunk);
48
+ this._strategies.set('fixed-size', fixedSizeChunk);
49
+ this._strategies.set('recursive', recursiveChunk);
50
+ this._strategies.set('semantic', semanticChunk);
51
+ this._strategies.set('structure-aware', structureAwareChunk);
52
+ }
53
+
54
+ /**
55
+ * Chunk text using the configured or specified strategy.
56
+ *
57
+ * @param {string} text - The input text to chunk. Must be a non-empty string.
58
+ * @param {object} [options={}] - Per-call options that override the engine config.
59
+ * @param {string} [options.strategy] - Strategy name to use for this call.
60
+ * @returns {string[]} Array of text chunks.
61
+ * @throws {Error} If text is not a non-empty string.
62
+ * @throws {Error} If the specified strategy is not registered.
63
+ * @fires ChunkingEngine#chunk
64
+ */
65
+ chunk(text, options = {}) {
66
+ if (!text || typeof text !== 'string' || text.trim().length === 0) {
67
+ throw new Error('text must be a non-empty string');
68
+ }
69
+
70
+ const mergedOptions = { ...this._config, ...options };
71
+ const strategyName = mergedOptions.strategy;
72
+
73
+ const strategyFn = this._strategies.get(strategyName);
74
+ if (!strategyFn) {
75
+ throw new Error(
76
+ `Unknown chunking strategy "${strategyName}". Available strategies: ${this.listStrategies().join(', ')}`,
77
+ );
78
+ }
79
+
80
+ const results = strategyFn(text, mergedOptions);
81
+
82
+ // Filter out empty chunks
83
+ const chunks = results.filter((chunk) => chunk && chunk.trim().length > 0);
84
+
85
+ /**
86
+ * Chunk event emitted after a successful chunking operation.
87
+ *
88
+ * @event ChunkingEngine#chunk
89
+ * @type {object}
90
+ * @property {string} strategy - The strategy name used.
91
+ * @property {number} inputLength - Character length of the input text.
92
+ * @property {number} chunkCount - Number of resulting chunks.
93
+ */
94
+ this.emit('chunk', {
95
+ strategy: strategyName,
96
+ inputLength: text.length,
97
+ chunkCount: chunks.length,
98
+ });
99
+
100
+ return chunks;
101
+ }
102
+
103
+ /**
104
+ * Register a custom chunking strategy.
105
+ *
106
+ * @param {string} name - Strategy name (used to select it via options.strategy).
107
+ * @param {function} fn - Strategy function with signature (text, options) => string[].
108
+ * @throws {Error} If name is not a non-empty string.
109
+ * @throws {Error} If fn is not a function.
110
+ */
111
+ registerStrategy(name, fn) {
112
+ if (!name || typeof name !== 'string') {
113
+ throw new Error('Strategy name must be a non-empty string');
114
+ }
115
+ if (typeof fn !== 'function') {
116
+ throw new Error(
117
+ 'Strategy must be a function with signature (text, options) => string[]',
118
+ );
119
+ }
120
+ this._strategies.set(name, fn);
121
+ }
122
+
123
+ /**
124
+ * List all registered strategy names.
125
+ *
126
+ * @returns {string[]} Array of strategy names.
127
+ */
128
+ listStrategies() {
129
+ return Array.from(this._strategies.keys());
130
+ }
131
+
132
+ /**
133
+ * Get the current engine configuration.
134
+ *
135
+ * @returns {object} A copy of the current configuration.
136
+ */
137
+ getConfig() {
138
+ return { ...this._config };
139
+ }
140
+ }
141
+
142
+ module.exports = { ChunkingEngine, DEFAULT_CONFIG };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Fixed-size chunking strategy.
5
+ * Splits text by character count with configurable overlap.
6
+ * Attempts to break at word boundaries by looking back up to 20 characters.
7
+ *
8
+ * @param {string} text - The input text to chunk.
9
+ * @param {object} [options={}] - Chunking options.
10
+ * @param {number} [options.chunkSize=512] - Target chunk size in characters.
11
+ * @param {number} [options.chunkOverlap=50] - Number of overlapping characters between chunks.
12
+ * @param {number} [options.minChunkSize=50] - Minimum chunk size; smaller chunks are filtered out.
13
+ * @returns {string[]} Array of text chunks.
14
+ */
15
+ function fixedSizeChunk(text, options = {}) {
16
+ const { chunkSize = 512, chunkOverlap = 50, minChunkSize = 50 } = options;
17
+
18
+ if (!text || typeof text !== 'string') {
19
+ return [];
20
+ }
21
+
22
+ const step = chunkSize - chunkOverlap;
23
+ if (step <= 0) {
24
+ throw new Error('chunkSize must be greater than chunkOverlap');
25
+ }
26
+
27
+ const chunks = [];
28
+ let pos = 0;
29
+
30
+ while (pos < text.length) {
31
+ let end = Math.min(pos + chunkSize, text.length);
32
+
33
+ // Try to break at a word boundary by looking back up to 20 chars
34
+ if (end < text.length) {
35
+ const lookbackStart = Math.max(end - 20, pos);
36
+ const segment = text.slice(lookbackStart, end);
37
+ const lastSpace = segment.lastIndexOf(' ');
38
+ if (lastSpace !== -1) {
39
+ end = lookbackStart + lastSpace;
40
+ }
41
+ }
42
+
43
+ const chunk = text.slice(pos, end).trim();
44
+ if (chunk.length >= minChunkSize) {
45
+ chunks.push(chunk);
46
+ }
47
+
48
+ pos += step;
49
+ }
50
+
51
+ return chunks;
52
+ }
53
+
54
+ module.exports = { fixedSizeChunk };
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_SEPARATORS = ['\n\n', '\n', '. ', ' '];
4
+
5
+ /**
6
+ * Recursive text chunking strategy.
7
+ * Splits text using a hierarchy of separators, merging consecutive pieces
8
+ * into chunks up to chunkSize. Pieces exceeding chunkSize are recursively
9
+ * split with the next separator in the hierarchy.
10
+ *
11
+ * @param {string} text - The input text to chunk.
12
+ * @param {object} [options={}] - Chunking options.
13
+ * @param {number} [options.chunkSize=512] - Maximum chunk size in characters.
14
+ * @param {number} [options.chunkOverlap=50] - Number of overlapping characters between chunks.
15
+ * @param {string[]} [options.separators] - Separator hierarchy for splitting.
16
+ * @param {number} [options.minChunkSize=50] - Minimum chunk size; smaller chunks are filtered out.
17
+ * @returns {string[]} Array of text chunks.
18
+ */
19
+ function recursiveChunk(text, options = {}) {
20
+ const {
21
+ chunkSize = 512,
22
+ chunkOverlap = 50,
23
+ separators = DEFAULT_SEPARATORS,
24
+ minChunkSize = 50,
25
+ } = options;
26
+
27
+ if (!text || typeof text !== 'string') {
28
+ return [];
29
+ }
30
+
31
+ const results = _splitRecursive(text, separators, 0, chunkSize, chunkOverlap);
32
+
33
+ return results.filter((chunk) => chunk.length >= minChunkSize);
34
+ }
35
+
36
+ /**
37
+ * Internal recursive splitting function.
38
+ *
39
+ * @param {string} text - Text to split.
40
+ * @param {string[]} separators - Separator hierarchy.
41
+ * @param {number} sepIndex - Current separator index.
42
+ * @param {number} chunkSize - Maximum chunk size.
43
+ * @param {number} chunkOverlap - Overlap size.
44
+ * @returns {string[]} Array of text chunks.
45
+ * @private
46
+ */
47
+ function _splitRecursive(text, separators, sepIndex, chunkSize, chunkOverlap) {
48
+ // Base case: no more separators or text fits in one chunk
49
+ if (text.length <= chunkSize) {
50
+ return [text.trim()];
51
+ }
52
+
53
+ if (sepIndex >= separators.length) {
54
+ // No more separators; hard-cut the text
55
+ return [text.slice(0, chunkSize).trim()];
56
+ }
57
+
58
+ const separator = separators[sepIndex];
59
+ const pieces = text.split(separator);
60
+
61
+ // If splitting produced only one piece, try next separator
62
+ if (pieces.length <= 1) {
63
+ return _splitRecursive(
64
+ text,
65
+ separators,
66
+ sepIndex + 1,
67
+ chunkSize,
68
+ chunkOverlap,
69
+ );
70
+ }
71
+
72
+ const chunks = [];
73
+ let buffer = '';
74
+
75
+ for (const piece of pieces) {
76
+ const candidate = buffer ? buffer + separator + piece : piece;
77
+
78
+ if (candidate.length <= chunkSize) {
79
+ buffer = candidate;
80
+ } else {
81
+ // Flush the current buffer
82
+ if (buffer) {
83
+ chunks.push(buffer.trim());
84
+ }
85
+
86
+ // If the piece itself exceeds chunkSize, recurse with next separator
87
+ if (piece.length > chunkSize) {
88
+ const subChunks = _splitRecursive(
89
+ piece,
90
+ separators,
91
+ sepIndex + 1,
92
+ chunkSize,
93
+ chunkOverlap,
94
+ );
95
+ chunks.push(...subChunks);
96
+ buffer = '';
97
+ } else {
98
+ buffer = piece;
99
+ }
100
+ }
101
+ }
102
+
103
+ if (buffer.trim()) {
104
+ chunks.push(buffer.trim());
105
+ }
106
+
107
+ // Apply overlap: prefix each chunk (except the first) with trailing text from previous chunk
108
+ if (chunkOverlap > 0 && chunks.length > 1) {
109
+ const overlapped = [chunks[0]];
110
+ for (let i = 1; i < chunks.length; i++) {
111
+ const prev = chunks[i - 1];
112
+ const overlapText = prev.slice(-chunkOverlap);
113
+ const combined = overlapText + chunks[i];
114
+ // Only add overlap if it doesn't push us way over chunkSize
115
+ if (combined.length <= chunkSize + chunkOverlap) {
116
+ overlapped.push(combined.trim());
117
+ } else {
118
+ overlapped.push(chunks[i]);
119
+ }
120
+ }
121
+ return overlapped;
122
+ }
123
+
124
+ return chunks;
125
+ }
126
+
127
+ module.exports = { recursiveChunk };
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ const { recursiveChunk } = require('./recursive');
4
+
5
+ /**
6
+ * Tokenize text for Jaccard similarity computation.
7
+ * Lowercases, splits on whitespace, filters tokens with length > 2.
8
+ *
9
+ * @param {string} text - Text to tokenize.
10
+ * @returns {Set<string>} Set of tokens.
11
+ * @private
12
+ */
13
+ function _tokenize(text) {
14
+ const tokens = text
15
+ .toLowerCase()
16
+ .split(/\s+/)
17
+ .filter((t) => t.length > 2);
18
+ return new Set(tokens);
19
+ }
20
+
21
+ /**
22
+ * Compute Jaccard similarity between two token sets.
23
+ *
24
+ * @param {Set<string>} setA - First token set.
25
+ * @param {Set<string>} setB - Second token set.
26
+ * @returns {number} Jaccard similarity coefficient (0-1).
27
+ * @private
28
+ */
29
+ function _jaccardSimilarity(setA, setB) {
30
+ if (setA.size === 0 && setB.size === 0) {
31
+ return 1.0;
32
+ }
33
+
34
+ let intersection = 0;
35
+ for (const token of setA) {
36
+ if (setB.has(token)) {
37
+ intersection++;
38
+ }
39
+ }
40
+
41
+ const union = setA.size + setB.size - intersection;
42
+ return union === 0 ? 0 : intersection / union;
43
+ }
44
+
45
+ /**
46
+ * Semantic chunking strategy using topic-boundary detection.
47
+ * Splits text into sentences, computes Jaccard token overlap between
48
+ * consecutive sentence pairs, and inserts chunk boundaries where
49
+ * similarity drops below the threshold. Oversized segments fall back
50
+ * to recursive splitting.
51
+ *
52
+ * @param {string} text - The input text to chunk.
53
+ * @param {object} [options={}] - Chunking options.
54
+ * @param {number} [options.chunkSize=512] - Maximum chunk size in characters.
55
+ * @param {number} [options.similarityThreshold=0.3] - Minimum similarity to keep sentences in the same chunk.
56
+ * @param {number} [options.minChunkSize=50] - Minimum chunk size; smaller chunks are filtered out.
57
+ * @returns {string[]} Array of text chunks.
58
+ */
59
+ function semanticChunk(text, options = {}) {
60
+ const {
61
+ chunkSize = 512,
62
+ similarityThreshold = 0.3,
63
+ minChunkSize = 50,
64
+ } = options;
65
+
66
+ if (!text || typeof text !== 'string') {
67
+ return [];
68
+ }
69
+
70
+ const sentences = text
71
+ .split(/(?<=[.!?])\s+/)
72
+ .filter((s) => s.trim().length > 0);
73
+
74
+ if (sentences.length === 0) {
75
+ return [];
76
+ }
77
+
78
+ if (sentences.length === 1) {
79
+ const trimmed = sentences[0].trim();
80
+ if (trimmed.length > chunkSize) {
81
+ return recursiveChunk(trimmed, { chunkSize, minChunkSize });
82
+ }
83
+ return trimmed.length >= minChunkSize ? [trimmed] : [];
84
+ }
85
+
86
+ // Compute token sets for each sentence
87
+ const tokenSets = sentences.map(_tokenize);
88
+
89
+ // Group sentences into segments based on similarity boundaries
90
+ const segments = [];
91
+ let currentSegment = [sentences[0]];
92
+
93
+ for (let i = 1; i < sentences.length; i++) {
94
+ const similarity = _jaccardSimilarity(tokenSets[i - 1], tokenSets[i]);
95
+
96
+ if (similarity < similarityThreshold) {
97
+ // Topic boundary detected; flush current segment
98
+ segments.push(currentSegment.join(' '));
99
+ currentSegment = [sentences[i]];
100
+ } else {
101
+ currentSegment.push(sentences[i]);
102
+ }
103
+ }
104
+
105
+ // Flush the last segment
106
+ if (currentSegment.length > 0) {
107
+ segments.push(currentSegment.join(' '));
108
+ }
109
+
110
+ // Apply chunkSize limit: oversized segments fall back to recursive splitting
111
+ const chunks = [];
112
+ for (const segment of segments) {
113
+ const trimmed = segment.trim();
114
+ if (trimmed.length === 0) {
115
+ continue;
116
+ }
117
+ if (trimmed.length > chunkSize) {
118
+ const subChunks = recursiveChunk(trimmed, { chunkSize, minChunkSize });
119
+ chunks.push(...subChunks);
120
+ } else if (trimmed.length >= minChunkSize) {
121
+ chunks.push(trimmed);
122
+ }
123
+ }
124
+
125
+ return chunks;
126
+ }
127
+
128
+ module.exports = { semanticChunk };