@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.1
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 +178 -550
- package/bin/cli.js +8 -14
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +18 -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 +620 -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 +294 -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 +1030 -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 +111 -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/rate-limiter.js +244 -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/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +388 -0
- package/src/utils/validate-plugin-contract.js +42 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming memory safeguards and backpressure management
|
|
3
|
+
* Prevents memory overload during large document processing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { logger } = require('../../utils/logger.js');
|
|
7
|
+
|
|
8
|
+
// Use performance.now() for monotonic timing, fallback to perf_hooks for older Node
|
|
9
|
+
const { performance } = globalThis.performance
|
|
10
|
+
? globalThis
|
|
11
|
+
: require('node:perf_hooks');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Memory monitor for tracking heap usage
|
|
15
|
+
*/
|
|
16
|
+
class MemoryMonitor {
|
|
17
|
+
constructor(maxMemoryMB = 512) {
|
|
18
|
+
this.maxMemoryBytes = maxMemoryMB * 1024 * 1024;
|
|
19
|
+
this.warningThreshold = 0.8; // 80% of max memory
|
|
20
|
+
this.criticalThreshold = 0.9; // 90% of max memory
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getCurrentUsage() {
|
|
24
|
+
const usage =
|
|
25
|
+
typeof process.memoryUsage === 'function' ? process.memoryUsage() : null;
|
|
26
|
+
const heapUsed = usage?.heapUsed ?? 0;
|
|
27
|
+
const heapTotal = usage?.heapTotal ?? Math.max(1, heapUsed);
|
|
28
|
+
return {
|
|
29
|
+
heapUsed,
|
|
30
|
+
heapTotal,
|
|
31
|
+
external: usage?.external ?? 0,
|
|
32
|
+
rss: usage?.rss ?? 0,
|
|
33
|
+
heapUsedMB: Math.round(heapUsed / 1024 / 1024),
|
|
34
|
+
heapTotalMB: Math.round(heapTotal / 1024 / 1024),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getUsageRatio() {
|
|
39
|
+
const usage = this.getCurrentUsage();
|
|
40
|
+
return usage.heapUsed / this.maxMemoryBytes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
isWarningLevel() {
|
|
44
|
+
return this.getUsageRatio() > this.warningThreshold;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
isCriticalLevel() {
|
|
48
|
+
return this.getUsageRatio() > this.criticalThreshold;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getMemoryReport() {
|
|
52
|
+
const usage = this.getCurrentUsage();
|
|
53
|
+
const ratio = this.getUsageRatio();
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
heapUsedMB: usage.heapUsedMB,
|
|
57
|
+
heapTotalMB: usage.heapTotalMB,
|
|
58
|
+
maxMemoryMB: Math.round(this.maxMemoryBytes / 1024 / 1024),
|
|
59
|
+
usagePercentage: Math.round(ratio * 100),
|
|
60
|
+
status:
|
|
61
|
+
ratio > this.criticalThreshold
|
|
62
|
+
? 'critical'
|
|
63
|
+
: ratio > this.warningThreshold
|
|
64
|
+
? 'warning'
|
|
65
|
+
: 'normal',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Backpressure controller for streaming operations
|
|
72
|
+
*/
|
|
73
|
+
class BackpressureController {
|
|
74
|
+
constructor(_options = {}) {
|
|
75
|
+
this.maxBufferSize = _options.maxBufferSize || 100;
|
|
76
|
+
this.memoryMonitor = new MemoryMonitor(_options.maxMemoryMB);
|
|
77
|
+
this.pauseThreshold = _options.pauseThreshold || 0.85;
|
|
78
|
+
this.resumeThreshold = _options.resumeThreshold || 0.7;
|
|
79
|
+
this.checkInterval = _options.checkInterval || 1000; // ms
|
|
80
|
+
|
|
81
|
+
this.isPaused = false;
|
|
82
|
+
this.buffer = [];
|
|
83
|
+
this.waitingResolvers = [];
|
|
84
|
+
this.reliefCheckInterval = null; // Initialize to prevent race conditions
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if backpressure should be applied
|
|
89
|
+
* @returns {boolean} True if processing should be paused
|
|
90
|
+
*/
|
|
91
|
+
shouldApplyBackpressure() {
|
|
92
|
+
const memoryRatio = this.memoryMonitor.getUsageRatio();
|
|
93
|
+
const bufferFull = this.buffer.length >= this.maxBufferSize;
|
|
94
|
+
|
|
95
|
+
return memoryRatio > this.pauseThreshold || bufferFull;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Wait for backpressure to be relieved
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
*/
|
|
102
|
+
async waitForRelief() {
|
|
103
|
+
if (!this.shouldApplyBackpressure()) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.isPaused = true;
|
|
108
|
+
const memoryReport = this.memoryMonitor.getMemoryReport();
|
|
109
|
+
logger.warn('Applying backpressure', {
|
|
110
|
+
memoryPercentage: memoryReport.usagePercentage,
|
|
111
|
+
bufferSize: this.buffer.length,
|
|
112
|
+
maxBufferSize: this.maxBufferSize,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
this.waitingResolvers.push(resolve);
|
|
117
|
+
this.startReliefCheck();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Start checking for relief conditions
|
|
123
|
+
* Race condition fix: prevents multiple intervals from being created
|
|
124
|
+
*/
|
|
125
|
+
startReliefCheck() {
|
|
126
|
+
// Critical section: check and set in a way that prevents race conditions
|
|
127
|
+
if (this.reliefCheckInterval !== null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Immediately set to a sentinel value to prevent concurrent calls from proceeding
|
|
132
|
+
this.reliefCheckInterval = true;
|
|
133
|
+
|
|
134
|
+
// Create the actual interval
|
|
135
|
+
this.reliefCheckInterval = setInterval(() => {
|
|
136
|
+
const memoryRatio = this.memoryMonitor.getUsageRatio();
|
|
137
|
+
const bufferOk = this.buffer.length < this.maxBufferSize * 0.5;
|
|
138
|
+
|
|
139
|
+
if (memoryRatio < this.resumeThreshold && bufferOk) {
|
|
140
|
+
this.relieveBackpressure();
|
|
141
|
+
}
|
|
142
|
+
}, this.checkInterval);
|
|
143
|
+
|
|
144
|
+
// Don't keep process alive for relief checks
|
|
145
|
+
if (typeof this.reliefCheckInterval.unref === 'function') {
|
|
146
|
+
this.reliefCheckInterval.unref();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Relieve backpressure and resume processing
|
|
152
|
+
*/
|
|
153
|
+
relieveBackpressure() {
|
|
154
|
+
if (!this.isPaused) return;
|
|
155
|
+
|
|
156
|
+
this.isPaused = false;
|
|
157
|
+
logger.info('Backpressure relieved - resuming processing');
|
|
158
|
+
|
|
159
|
+
// Clear interval
|
|
160
|
+
if (this.reliefCheckInterval) {
|
|
161
|
+
clearInterval(this.reliefCheckInterval);
|
|
162
|
+
this.reliefCheckInterval = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resolve all waiting promises
|
|
166
|
+
const resolvers = this.waitingResolvers.splice(0);
|
|
167
|
+
resolvers.forEach((resolve) => resolve());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add item to buffer with backpressure check
|
|
172
|
+
* @param {any} item - Item to buffer
|
|
173
|
+
*/
|
|
174
|
+
async addToBuffer(item) {
|
|
175
|
+
await this.waitForRelief();
|
|
176
|
+
this.buffer.push(item);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Remove items from buffer
|
|
181
|
+
* @param {number} count - Number of items to remove
|
|
182
|
+
* @returns {any[]} Removed items
|
|
183
|
+
*/
|
|
184
|
+
removeFromBuffer(count = 1) {
|
|
185
|
+
return this.buffer.splice(0, count);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get current status
|
|
190
|
+
* @returns {object} Status information
|
|
191
|
+
*/
|
|
192
|
+
getStatus() {
|
|
193
|
+
const memoryReport = this.memoryMonitor.getMemoryReport();
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
isPaused: this.isPaused,
|
|
197
|
+
bufferSize: this.buffer.length,
|
|
198
|
+
maxBufferSize: this.maxBufferSize,
|
|
199
|
+
memory: memoryReport,
|
|
200
|
+
shouldApplyBackpressure: this.shouldApplyBackpressure(),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Streaming processor with memory safeguards
|
|
207
|
+
*/
|
|
208
|
+
class StreamingProcessor {
|
|
209
|
+
constructor(_options = {}) {
|
|
210
|
+
this.chunkSize = _options.chunkSize || 1000;
|
|
211
|
+
this.backpressureController = new BackpressureController(_options);
|
|
212
|
+
this.tokenLimit = _options.tokenLimit || 100000; // Token limit per stream
|
|
213
|
+
this.tokenWarningThreshold = _options.tokenWarningThreshold || 0.8;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Process document stream with memory safeguards
|
|
218
|
+
* @param {string} docPath - Path to document
|
|
219
|
+
* @param {object} pipeline - Pipeline _instance
|
|
220
|
+
* @returns {AsyncGenerator} Stream of processed chunks
|
|
221
|
+
*/
|
|
222
|
+
async *processDocumentStream(docPath, pipeline) {
|
|
223
|
+
let totalTokens = 0;
|
|
224
|
+
let chunkCount = 0;
|
|
225
|
+
let processedCount = 0;
|
|
226
|
+
let failedCount = 0;
|
|
227
|
+
let totalChunks = 0;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// First, count total chunks for progress tracking
|
|
231
|
+
const allChunks = [];
|
|
232
|
+
for await (const documentChunk of this.loadInChunks(
|
|
233
|
+
docPath,
|
|
234
|
+
pipeline.loaderInstance,
|
|
235
|
+
)) {
|
|
236
|
+
allChunks.push(documentChunk);
|
|
237
|
+
}
|
|
238
|
+
totalChunks = allChunks.length;
|
|
239
|
+
|
|
240
|
+
for (const documentChunk of allChunks) {
|
|
241
|
+
// Check memory and apply backpressure if needed
|
|
242
|
+
await this.backpressureController.waitForRelief();
|
|
243
|
+
|
|
244
|
+
// Estimate tokens (rough approximation: 1 token ≈ 4 characters)
|
|
245
|
+
const estimatedTokens = documentChunk.length / 4;
|
|
246
|
+
totalTokens += estimatedTokens;
|
|
247
|
+
|
|
248
|
+
// Check token limits
|
|
249
|
+
if (totalTokens > this.tokenLimit) {
|
|
250
|
+
const error = new Error(
|
|
251
|
+
`Token limit exceeded: ${totalTokens} > ${this.tokenLimit}`,
|
|
252
|
+
);
|
|
253
|
+
error.code = 'TOKEN_LIMIT_EXCEEDED';
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Process chunk
|
|
258
|
+
const processed = await this.processChunk(documentChunk, pipeline);
|
|
259
|
+
chunkCount++;
|
|
260
|
+
|
|
261
|
+
// Warn if approaching token limit (check after incrementing chunkCount)
|
|
262
|
+
if (
|
|
263
|
+
totalTokens > this.tokenLimit * this.tokenWarningThreshold &&
|
|
264
|
+
chunkCount % 10 === 0
|
|
265
|
+
) {
|
|
266
|
+
logger.warn('Approaching token limit', {
|
|
267
|
+
currentTokens: Math.round(totalTokens),
|
|
268
|
+
tokenLimit: this.tokenLimit,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (processed.processed) {
|
|
273
|
+
processedCount++;
|
|
274
|
+
} else {
|
|
275
|
+
failedCount++;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Add to buffer and yield
|
|
279
|
+
await this.backpressureController.addToBuffer(processed);
|
|
280
|
+
|
|
281
|
+
// Yield processed chunks from buffer with progress information
|
|
282
|
+
const bufferedItems = this.backpressureController.removeFromBuffer(1);
|
|
283
|
+
for (const item of bufferedItems) {
|
|
284
|
+
yield {
|
|
285
|
+
...item,
|
|
286
|
+
progress: {
|
|
287
|
+
processed: processedCount,
|
|
288
|
+
failed: failedCount,
|
|
289
|
+
total: totalChunks,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Periodic garbage collection hint
|
|
295
|
+
if (chunkCount % 50 === 0 && global.gc) {
|
|
296
|
+
global.gc();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Yield any remaining buffered items
|
|
301
|
+
const remainingItems = this.backpressureController.removeFromBuffer(
|
|
302
|
+
this.backpressureController.buffer.length,
|
|
303
|
+
);
|
|
304
|
+
for (const item of remainingItems) {
|
|
305
|
+
yield item;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
logger.info('Streaming processing complete', {
|
|
309
|
+
chunkCount,
|
|
310
|
+
totalTokens: Math.round(totalTokens),
|
|
311
|
+
});
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const status = this.backpressureController.getStatus();
|
|
314
|
+
logger.error('Streaming processing failed', {
|
|
315
|
+
error: error.message,
|
|
316
|
+
totalTokens: Math.round(totalTokens),
|
|
317
|
+
chunkCount,
|
|
318
|
+
memoryStatus: status.memory,
|
|
319
|
+
bufferSize: status.bufferSize,
|
|
320
|
+
});
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Load document in chunks
|
|
327
|
+
* @param {string} docPath - Document path
|
|
328
|
+
* @param {object} loader - Loader _instance
|
|
329
|
+
* @returns {AsyncGenerator} Stream of document chunks
|
|
330
|
+
*/
|
|
331
|
+
async *loadInChunks(docPath, loader) {
|
|
332
|
+
const documents = await loader.load(docPath);
|
|
333
|
+
|
|
334
|
+
for (const doc of documents) {
|
|
335
|
+
const chunks = doc.chunk();
|
|
336
|
+
|
|
337
|
+
// Yield chunks in batches to control memory
|
|
338
|
+
for (let i = 0; i < chunks.length; i += this.chunkSize) {
|
|
339
|
+
const batch = chunks.slice(i, i + this.chunkSize);
|
|
340
|
+
for (const chunk of batch) {
|
|
341
|
+
yield chunk;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Process a single chunk
|
|
349
|
+
* @param {string} chunk - Text chunk
|
|
350
|
+
* @param {object} pipeline - Pipeline _instance
|
|
351
|
+
* @returns {Promise<object>} Processed chunk
|
|
352
|
+
*/
|
|
353
|
+
async processChunk(chunk, pipeline) {
|
|
354
|
+
const start = performance.now();
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
// Embed the chunk
|
|
358
|
+
const vector = await pipeline.embedderInstance.embed([chunk]);
|
|
359
|
+
|
|
360
|
+
// Store in retriever
|
|
361
|
+
await pipeline.retrieverInstance.store(vector);
|
|
362
|
+
|
|
363
|
+
const end = performance.now();
|
|
364
|
+
const duration = Math.max(1, Math.round(end - start)); // clamp to minimum 1ms
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
chunk,
|
|
368
|
+
vector: vector[0],
|
|
369
|
+
processed: true,
|
|
370
|
+
duration,
|
|
371
|
+
timestamp: new Date().toISOString(),
|
|
372
|
+
};
|
|
373
|
+
} catch (error) {
|
|
374
|
+
const end = performance.now();
|
|
375
|
+
const duration = Math.max(1, Math.round(end - start)); // clamp to minimum 1ms
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
chunk,
|
|
379
|
+
processed: false,
|
|
380
|
+
error: error.message,
|
|
381
|
+
duration,
|
|
382
|
+
timestamp: new Date().toISOString(),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get streaming statistics
|
|
389
|
+
* @returns {object} Statistics
|
|
390
|
+
*/
|
|
391
|
+
getStats() {
|
|
392
|
+
const status = this.backpressureController.getStatus();
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
backpressure: status,
|
|
396
|
+
tokenLimit: this.tokenLimit,
|
|
397
|
+
chunkSize: this.chunkSize,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Helper utilities for streaming operations
|
|
404
|
+
*/
|
|
405
|
+
class StreamingHelpers {
|
|
406
|
+
/**
|
|
407
|
+
* Create a streaming processor with default configuration
|
|
408
|
+
* @param {object} options - Configuration options
|
|
409
|
+
* @returns {StreamingProcessor} Configured streaming processor
|
|
410
|
+
*/
|
|
411
|
+
static createProcessor(options = {}) {
|
|
412
|
+
return new StreamingProcessor({
|
|
413
|
+
chunkSize: options.chunkSize || 1000,
|
|
414
|
+
maxMemoryMB: options.maxMemoryMB || 512,
|
|
415
|
+
tokenLimit: options.tokenLimit || 100000,
|
|
416
|
+
tokenWarningThreshold: options.tokenWarningThreshold || 0.8,
|
|
417
|
+
pauseThreshold: options.pauseThreshold || 0.85,
|
|
418
|
+
resumeThreshold: options.resumeThreshold || 0.7,
|
|
419
|
+
checkInterval: options.checkInterval || 1000,
|
|
420
|
+
...options,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Create a memory monitor with default configuration
|
|
426
|
+
* @param {number} maxMemoryMB - Maximum memory in MB
|
|
427
|
+
* @returns {MemoryMonitor} Configured memory monitor
|
|
428
|
+
*/
|
|
429
|
+
static createMemoryMonitor(maxMemoryMB = 512) {
|
|
430
|
+
return new MemoryMonitor(maxMemoryMB);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Create a backpressure controller with default configuration
|
|
435
|
+
* @param {object} options - Configuration options
|
|
436
|
+
* @returns {BackpressureController} Configured backpressure controller
|
|
437
|
+
*/
|
|
438
|
+
static createBackpressureController(options = {}) {
|
|
439
|
+
return new BackpressureController({
|
|
440
|
+
maxBufferSize: options.maxBufferSize || 100,
|
|
441
|
+
maxMemoryMB: options.maxMemoryMB || 512,
|
|
442
|
+
pauseThreshold: options.pauseThreshold || 0.85,
|
|
443
|
+
resumeThreshold: options.resumeThreshold || 0.7,
|
|
444
|
+
checkInterval: options.checkInterval || 1000,
|
|
445
|
+
...options,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Estimate token count from text
|
|
451
|
+
* @param {string} text - Input text
|
|
452
|
+
* @returns {number} Estimated token count
|
|
453
|
+
*/
|
|
454
|
+
static estimateTokens(text) {
|
|
455
|
+
if (typeof text !== 'string') return 0;
|
|
456
|
+
// Rough approximation: 1 token ≈ 4 characters
|
|
457
|
+
return Math.ceil(text.length / 4);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Check if memory usage is within safe limits
|
|
462
|
+
* @param {number} maxMemoryMB - Maximum allowed memory in MB
|
|
463
|
+
* @returns {object} Memory status report
|
|
464
|
+
*/
|
|
465
|
+
static checkMemoryStatus(maxMemoryMB = 512) {
|
|
466
|
+
const monitor = new MemoryMonitor(maxMemoryMB);
|
|
467
|
+
const usage = monitor.getCurrentUsage();
|
|
468
|
+
const ratio = monitor.getUsageRatio();
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
...usage,
|
|
472
|
+
ratio,
|
|
473
|
+
isWarning: monitor.isWarningLevel(),
|
|
474
|
+
isCritical: monitor.isCriticalLevel(),
|
|
475
|
+
status: ratio > 0.9 ? 'critical' : ratio > 0.8 ? 'warning' : 'normal',
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Create a streaming configuration for different use cases
|
|
481
|
+
* @param {string} profile - Configuration profile ('light', 'standard', 'heavy')
|
|
482
|
+
* @returns {object} Configuration object
|
|
483
|
+
*/
|
|
484
|
+
static getStreamingProfile(profile = 'standard') {
|
|
485
|
+
const profiles = {
|
|
486
|
+
light: {
|
|
487
|
+
chunkSize: 500,
|
|
488
|
+
maxMemoryMB: 256,
|
|
489
|
+
tokenLimit: 50000,
|
|
490
|
+
maxBufferSize: 50,
|
|
491
|
+
pauseThreshold: 0.8,
|
|
492
|
+
resumeThreshold: 0.6,
|
|
493
|
+
},
|
|
494
|
+
standard: {
|
|
495
|
+
chunkSize: 1000,
|
|
496
|
+
maxMemoryMB: 512,
|
|
497
|
+
tokenLimit: 100000,
|
|
498
|
+
maxBufferSize: 100,
|
|
499
|
+
pauseThreshold: 0.85,
|
|
500
|
+
resumeThreshold: 0.7,
|
|
501
|
+
},
|
|
502
|
+
heavy: {
|
|
503
|
+
chunkSize: 2000,
|
|
504
|
+
maxMemoryMB: 1024,
|
|
505
|
+
tokenLimit: 200000,
|
|
506
|
+
maxBufferSize: 200,
|
|
507
|
+
pauseThreshold: 0.9,
|
|
508
|
+
resumeThreshold: 0.75,
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
return profiles[profile] || profiles.standard;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Wrap an async iterator with memory monitoring
|
|
517
|
+
* @param {AsyncIterator} iterator - Source async iterator
|
|
518
|
+
* @param {object} options - Monitoring options
|
|
519
|
+
* @returns {AsyncGenerator} Monitored async generator
|
|
520
|
+
*/
|
|
521
|
+
static async *withMemoryMonitoring(iterator, options = {}) {
|
|
522
|
+
const monitor = new MemoryMonitor(options.maxMemoryMB || 512);
|
|
523
|
+
let itemCount = 0;
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
for await (const item of iterator) {
|
|
527
|
+
itemCount++;
|
|
528
|
+
|
|
529
|
+
// Check memory every N items
|
|
530
|
+
if (itemCount % (options.checkInterval || 10) === 0) {
|
|
531
|
+
const report = monitor.getMemoryReport();
|
|
532
|
+
|
|
533
|
+
if (monitor.isCriticalLevel()) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`Critical memory usage: ${report.usagePercentage}% (${report.heapUsedMB}MB)`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (monitor.isWarningLevel() && options.onWarning) {
|
|
540
|
+
options.onWarning(report);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
yield {
|
|
545
|
+
...item,
|
|
546
|
+
memoryStatus:
|
|
547
|
+
itemCount % (options.reportInterval || 100) === 0
|
|
548
|
+
? monitor.getMemoryReport()
|
|
549
|
+
: undefined,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
} catch (error) {
|
|
553
|
+
const finalReport = monitor.getMemoryReport();
|
|
554
|
+
logger.error('Memory monitoring error:', {
|
|
555
|
+
error: error.message,
|
|
556
|
+
itemsProcessed: itemCount,
|
|
557
|
+
finalMemoryStatus: finalReport,
|
|
558
|
+
});
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Create a throttled async iterator
|
|
565
|
+
* @param {AsyncIterator} iterator - Source async iterator
|
|
566
|
+
* @param {number} delayMs - Delay between items in milliseconds
|
|
567
|
+
* @returns {AsyncGenerator} Throttled async generator
|
|
568
|
+
*/
|
|
569
|
+
static async *throttle(iterator, delayMs = 100) {
|
|
570
|
+
for await (const item of iterator) {
|
|
571
|
+
yield item;
|
|
572
|
+
|
|
573
|
+
// Add delay between items
|
|
574
|
+
if (delayMs > 0) {
|
|
575
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Batch items from an async iterator
|
|
582
|
+
* @param {AsyncIterator} iterator - Source async iterator
|
|
583
|
+
* @param {number} batchSize - Number of items per batch
|
|
584
|
+
* @returns {AsyncGenerator} Batched async generator
|
|
585
|
+
*/
|
|
586
|
+
static async *batch(iterator, batchSize = 10) {
|
|
587
|
+
let batch = [];
|
|
588
|
+
|
|
589
|
+
for await (const item of iterator) {
|
|
590
|
+
batch.push(item);
|
|
591
|
+
|
|
592
|
+
if (batch.length >= batchSize) {
|
|
593
|
+
yield batch;
|
|
594
|
+
batch = [];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Yield remaining items
|
|
599
|
+
if (batch.length > 0) {
|
|
600
|
+
yield batch;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
module.exports = {
|
|
606
|
+
BackpressureController,
|
|
607
|
+
StreamingProcessor,
|
|
608
|
+
MemoryMonitor,
|
|
609
|
+
StreamingHelpers,
|
|
610
|
+
// Convenience exports
|
|
611
|
+
createProcessor: StreamingHelpers.createProcessor,
|
|
612
|
+
createMemoryMonitor: StreamingHelpers.createMemoryMonitor,
|
|
613
|
+
createBackpressureController: StreamingHelpers.createBackpressureController,
|
|
614
|
+
estimateTokens: StreamingHelpers.estimateTokens,
|
|
615
|
+
checkMemoryStatus: StreamingHelpers.checkMemoryStatus,
|
|
616
|
+
getStreamingProfile: StreamingHelpers.getStreamingProfile,
|
|
617
|
+
withMemoryMonitoring: StreamingHelpers.withMemoryMonitoring,
|
|
618
|
+
throttle: StreamingHelpers.throttle,
|
|
619
|
+
batch: StreamingHelpers.batch,
|
|
620
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
async function createPipeline({
|
|
4
|
+
loader,
|
|
5
|
+
embedder,
|
|
6
|
+
retriever,
|
|
7
|
+
reranker,
|
|
8
|
+
llm,
|
|
9
|
+
evaluator,
|
|
10
|
+
} = {}) {
|
|
11
|
+
// Required components check
|
|
12
|
+
const required = ['loader', 'embedder', 'retriever', 'llm'];
|
|
13
|
+
const missing = required.filter(
|
|
14
|
+
(name) => !arguments[0] || !arguments[0][name],
|
|
15
|
+
);
|
|
16
|
+
if (missing.length > 0) {
|
|
17
|
+
throw new Error(`Required components missing: ${missing.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
async run(input, { signal } = {}) {
|
|
22
|
+
// Check if signal is already aborted
|
|
23
|
+
if (signal && signal.aborted) {
|
|
24
|
+
throw new Error('Aborted');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Execute pipeline in order
|
|
28
|
+
const docs = await loader.load(input, { signal });
|
|
29
|
+
const embeddings = await embedder.embed(docs, { signal });
|
|
30
|
+
const candidates = await retriever.retrieve(embeddings, { signal });
|
|
31
|
+
|
|
32
|
+
let ranked = candidates;
|
|
33
|
+
if (reranker && typeof reranker.rerank === 'function') {
|
|
34
|
+
ranked = await reranker.rerank(input, candidates, { signal });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const answer = await llm.generate(input, ranked, { signal });
|
|
38
|
+
|
|
39
|
+
let evaluation = null;
|
|
40
|
+
if (evaluator && typeof evaluator.evaluate === 'function') {
|
|
41
|
+
evaluation = await evaluator.evaluate(input, answer, ranked);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
docs,
|
|
46
|
+
embeddings,
|
|
47
|
+
candidates,
|
|
48
|
+
ranked,
|
|
49
|
+
answer,
|
|
50
|
+
evaluation,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// CJS+ESM interop pattern
|
|
57
|
+
module.exports = createPipeline;
|
|
58
|
+
module.exports.createPipeline = createPipeline;
|
|
59
|
+
module.exports.createRagPipeline = createPipeline; // Alias for backward compatibility
|
|
60
|
+
module.exports.default = module.exports;
|