@devilsdev/rag-pipeline-utils 2.2.0 → 2.3.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 (46) hide show
  1. package/README.md +323 -722
  2. package/contracts/contract-schema.json +189 -0
  3. package/contracts/reranker-contract.json +69 -0
  4. package/dist/index.cjs +19 -0
  5. package/dist/index.mjs +15 -1
  6. package/package.json +4 -2
  7. package/src/cli/commands/wizard.js +627 -0
  8. package/src/cli/input-sanitization-middleware.js +356 -0
  9. package/src/cli/templates/development.template.json +64 -0
  10. package/src/cli/templates/minimal.template.json +33 -0
  11. package/src/cli/templates/production.template.json +104 -0
  12. package/src/cli/templates/testing.template.json +45 -0
  13. package/src/config/connection-pool-config.json +259 -0
  14. package/src/config/memory-optimization.js +414 -0
  15. package/src/config/redaction-patterns.json +150 -0
  16. package/src/config/sanitization-rules.json +323 -0
  17. package/src/core/performance/streaming-safeguards.js +10 -1
  18. package/src/core/plugin-registry.js +275 -0
  19. package/src/dag/core/error-context.js +391 -0
  20. package/src/dag/core/execution-scheduler.js +350 -0
  21. package/src/dag/core/topology-validation.js +415 -0
  22. package/src/dag/dag-engine.js +26 -424
  23. package/src/dev/README.md +126 -0
  24. package/src/dev/dev-server.js +356 -0
  25. package/src/dev/examples/hot-reload-usage.js +355 -0
  26. package/src/dev/hot-reload.js +807 -0
  27. package/src/enterprise/sso-integration.js +184 -15
  28. package/src/index.js +19 -0
  29. package/src/observability/event-logger.js +62 -2
  30. package/src/plugins/optimized-openai-embedder.js +169 -0
  31. package/src/reranker/llm-reranker.js +30 -4
  32. package/src/security/jwt-config.js +431 -0
  33. package/src/security/jwt-validator.js +701 -0
  34. package/src/utils/api-client-pool-integration.js +436 -0
  35. package/src/utils/batch-processor.js +710 -0
  36. package/src/utils/connection-pool.js +605 -0
  37. package/src/utils/contract-validator.js +460 -0
  38. package/src/utils/error-formatter.js +603 -0
  39. package/src/utils/examples/error-formatter-usage.js +351 -0
  40. package/src/utils/input-sanitizer.js +967 -0
  41. package/src/utils/memory-monitor.js +800 -0
  42. package/src/utils/rate-limiter.js +244 -0
  43. package/src/utils/secure-logger-config.js +352 -0
  44. package/src/utils/secure-logger.js +576 -0
  45. package/src/utils/secure-logging.js +338 -0
  46. package/src/utils/structured-logger.js +32 -5
@@ -0,0 +1,710 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Intelligent Batch Processor
5
+ *
6
+ * Optimizes embedding batch sizes and processing to minimize API calls
7
+ * while maintaining memory efficiency and supporting cancellation.
8
+ *
9
+ * Features:
10
+ * - Dynamic batch sizing based on text length and model limits
11
+ * - Token-aware batching to maximize API efficiency
12
+ * - Progress tracking with real-time updates
13
+ * - Cancellation support for long-running operations
14
+ * - Memory-efficient streaming for large datasets
15
+ * - Performance metrics and optimization
16
+ * - Adaptive retry logic for failed batches
17
+ *
18
+ * @module utils/batch-processor
19
+ * @since 2.3.0
20
+ */
21
+
22
+ const { EventEmitter } = require('events');
23
+
24
+ /**
25
+ * Default configuration for batch processing
26
+ */
27
+ const DEFAULT_CONFIG = {
28
+ // Token limits (per model)
29
+ maxTokensPerBatch: 8191, // OpenAI text-embedding-ada-002 limit
30
+ maxItemsPerBatch: 2048, // Maximum items in single batch
31
+ minItemsPerBatch: 1, // Minimum items per batch
32
+
33
+ // Memory management
34
+ maxMemoryMB: 512, // Maximum memory for batch buffer
35
+ streamingThreshold: 1000, // Switch to streaming for datasets > this size
36
+
37
+ // Performance tuning
38
+ targetBatchUtilization: 0.85, // Target 85% of token limit
39
+ adaptiveSizing: true, // Enable adaptive batch sizing
40
+
41
+ // Retry configuration
42
+ maxRetries: 3,
43
+ retryDelay: 1000, // Base retry delay in ms
44
+ retryBackoff: 2, // Exponential backoff multiplier
45
+
46
+ // Progress tracking
47
+ progressInterval: 100, // Emit progress every N items
48
+ enableMetrics: true, // Track performance metrics
49
+ };
50
+
51
+ /**
52
+ * Model-specific token limits
53
+ */
54
+ const MODEL_LIMITS = {
55
+ 'text-embedding-ada-002': { maxTokens: 8191, maxItems: 2048 },
56
+ 'text-embedding-3-small': { maxTokens: 8191, maxItems: 2048 },
57
+ 'text-embedding-3-large': { maxTokens: 8191, maxItems: 2048 },
58
+ 'voyage-large-2': { maxTokens: 16000, maxItems: 128 },
59
+ 'cohere-embed-v3': { maxTokens: 512, maxItems: 96 },
60
+ default: { maxTokens: 8191, maxItems: 2048 },
61
+ };
62
+
63
+ /**
64
+ * Batch Processor Class
65
+ */
66
+ class BatchProcessor extends EventEmitter {
67
+ constructor(options = {}) {
68
+ super();
69
+
70
+ this.config = {
71
+ ...DEFAULT_CONFIG,
72
+ ...options,
73
+ };
74
+
75
+ // Apply model-specific limits if model specified
76
+ if (options.model && MODEL_LIMITS[options.model]) {
77
+ const limits = MODEL_LIMITS[options.model];
78
+ this.config.maxTokensPerBatch = limits.maxTokens;
79
+ this.config.maxItemsPerBatch = limits.maxItems;
80
+ }
81
+
82
+ // State
83
+ this.cancelled = false;
84
+ this.processing = false;
85
+ this.abortController = null;
86
+
87
+ // Metrics
88
+ this.metrics = {
89
+ totalItems: 0,
90
+ processedItems: 0,
91
+ totalBatches: 0,
92
+ successfulBatches: 0,
93
+ failedBatches: 0,
94
+ totalTokens: 0,
95
+ totalTime: 0,
96
+ avgBatchSize: 0,
97
+ avgTokensPerBatch: 0,
98
+ apiCallsSaved: 0,
99
+ memoryUsed: 0,
100
+ peakMemory: 0,
101
+ };
102
+
103
+ // Adaptive sizing state
104
+ this.adaptiveState = {
105
+ recentBatchSizes: [],
106
+ recentProcessingTimes: [],
107
+ optimalBatchSize: null,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Process items in optimized batches
113
+ *
114
+ * @param {Array<string|Object>} items - Items to process
115
+ * @param {Function} processFn - Function to process each batch
116
+ * @param {Object} options - Processing options
117
+ * @returns {Promise<Array>} Processed results
118
+ */
119
+ async processBatches(items, processFn, options = {}) {
120
+ if (!Array.isArray(items) || items.length === 0) {
121
+ throw new Error('Items must be a non-empty array');
122
+ }
123
+
124
+ if (typeof processFn !== 'function') {
125
+ throw new Error('processFn must be a function');
126
+ }
127
+
128
+ const startTime = Date.now();
129
+ this.processing = true;
130
+ this.cancelled = false;
131
+
132
+ // Initialize metrics
133
+ this.metrics.totalItems = items.length;
134
+ this.metrics.processedItems = 0;
135
+ this.metrics.totalBatches = 0;
136
+
137
+ // Setup abort controller if not provided
138
+ this.abortController = options.abortController || new AbortController();
139
+
140
+ const { onProgress, onBatchComplete, returnOriginalOrder = true } = options;
141
+
142
+ try {
143
+ // Emit start event
144
+ this.emit('start', {
145
+ totalItems: items.length,
146
+ estimatedBatches: this._estimateBatchCount(items),
147
+ });
148
+
149
+ // Create optimized batches
150
+ const batches = this._createBatches(items);
151
+ this.metrics.totalBatches = batches.length;
152
+
153
+ // Calculate API calls saved
154
+ const naiveBatchCount = items.length; // One API call per item
155
+ this.metrics.apiCallsSaved = naiveBatchCount - batches.length;
156
+
157
+ this.emit('batches_created', {
158
+ batchCount: batches.length,
159
+ avgBatchSize: items.length / batches.length,
160
+ apiCallsSaved: this.metrics.apiCallsSaved,
161
+ });
162
+
163
+ // Process batches
164
+ const results = [];
165
+ let processedCount = 0;
166
+
167
+ for (let i = 0; i < batches.length; i++) {
168
+ // Check cancellation
169
+ if (this.cancelled || this.abortController.signal.aborted) {
170
+ throw new Error('Processing cancelled');
171
+ }
172
+
173
+ const batch = batches[i];
174
+ const batchStartTime = Date.now();
175
+
176
+ try {
177
+ // Process batch with retry logic
178
+ const batchResults = await this._processBatchWithRetry(
179
+ batch,
180
+ processFn,
181
+ i,
182
+ );
183
+
184
+ results.push(...batchResults);
185
+ processedCount += batch.items.length;
186
+
187
+ this.metrics.successfulBatches++;
188
+ this.metrics.processedItems = processedCount;
189
+
190
+ // Track batch timing for adaptive sizing
191
+ const batchDuration = Date.now() - batchStartTime;
192
+ this._updateAdaptiveState(batch.items.length, batchDuration);
193
+
194
+ // Emit progress
195
+ if (onProgress || this.listenerCount('progress') > 0) {
196
+ const progressData = {
197
+ processed: processedCount,
198
+ total: items.length,
199
+ percentage: (processedCount / items.length) * 100,
200
+ currentBatch: i + 1,
201
+ totalBatches: batches.length,
202
+ estimatedTimeRemaining: this._estimateTimeRemaining(
203
+ processedCount,
204
+ items.length,
205
+ Date.now() - startTime,
206
+ ),
207
+ };
208
+
209
+ if (onProgress) {
210
+ onProgress(progressData);
211
+ }
212
+ this.emit('progress', progressData);
213
+ }
214
+
215
+ // Emit batch complete
216
+ if (onBatchComplete) {
217
+ onBatchComplete({
218
+ batchIndex: i,
219
+ batchSize: batch.items.length,
220
+ duration: batchDuration,
221
+ });
222
+ }
223
+
224
+ this.emit('batch_complete', {
225
+ batchIndex: i,
226
+ batchSize: batch.items.length,
227
+ tokens: batch.estimatedTokens,
228
+ duration: batchDuration,
229
+ });
230
+
231
+ // Memory management - force GC hint if available
232
+ if (
233
+ global.gc &&
234
+ this.metrics.peakMemory > this.config.maxMemoryMB * 1024 * 1024
235
+ ) {
236
+ global.gc();
237
+ }
238
+ } catch (error) {
239
+ this.metrics.failedBatches++;
240
+
241
+ this.emit('batch_error', {
242
+ batchIndex: i,
243
+ batchSize: batch.items.length,
244
+ error: error.message,
245
+ });
246
+
247
+ // Rethrow if not retry-able
248
+ throw error;
249
+ }
250
+ }
251
+
252
+ // Restore original order if requested
253
+ const finalResults = returnOriginalOrder
254
+ ? this._restoreOriginalOrder(results, items)
255
+ : results;
256
+
257
+ // Calculate final metrics
258
+ this.metrics.totalTime = Date.now() - startTime;
259
+ this.metrics.avgBatchSize = items.length / batches.length;
260
+ this.metrics.avgTokensPerBatch =
261
+ this.metrics.totalTokens / batches.length;
262
+
263
+ // Emit completion
264
+ this.emit('complete', {
265
+ totalItems: items.length,
266
+ totalBatches: batches.length,
267
+ successfulBatches: this.metrics.successfulBatches,
268
+ failedBatches: this.metrics.failedBatches,
269
+ totalTime: this.metrics.totalTime,
270
+ avgBatchSize: this.metrics.avgBatchSize,
271
+ apiCallsSaved: this.metrics.apiCallsSaved,
272
+ });
273
+
274
+ return finalResults;
275
+ } catch (error) {
276
+ this.emit('error', {
277
+ message: error.message,
278
+ processedItems: this.metrics.processedItems,
279
+ totalItems: items.length,
280
+ });
281
+ throw error;
282
+ } finally {
283
+ this.processing = false;
284
+ this.abortController = null;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Create optimized batches from items
290
+ * @private
291
+ */
292
+ _createBatches(items) {
293
+ const batches = [];
294
+ let currentBatch = {
295
+ items: [],
296
+ estimatedTokens: 0,
297
+ originalIndices: [],
298
+ };
299
+
300
+ const maxTokens =
301
+ this.config.maxTokensPerBatch * this.config.targetBatchUtilization;
302
+ const maxItems = this.config.maxItemsPerBatch;
303
+
304
+ for (let i = 0; i < items.length; i++) {
305
+ const item = items[i];
306
+ const text =
307
+ typeof item === 'string'
308
+ ? item
309
+ : item.text || item.content || String(item);
310
+ const tokens = this._estimateTokens(text);
311
+
312
+ // Check if adding this item would exceed limits
313
+ const wouldExceedTokens =
314
+ currentBatch.estimatedTokens + tokens > maxTokens;
315
+ const wouldExceedItems = currentBatch.items.length >= maxItems;
316
+
317
+ if (wouldExceedTokens || wouldExceedItems) {
318
+ // Only create batch if it has items
319
+ if (currentBatch.items.length > 0) {
320
+ batches.push(currentBatch);
321
+ currentBatch = {
322
+ items: [],
323
+ estimatedTokens: 0,
324
+ originalIndices: [],
325
+ };
326
+ }
327
+ }
328
+
329
+ // Add item to current batch
330
+ currentBatch.items.push(item);
331
+ currentBatch.estimatedTokens += tokens;
332
+ currentBatch.originalIndices.push(i);
333
+
334
+ // Track memory usage
335
+ this._updateMemoryEstimate(text);
336
+ }
337
+
338
+ // Add final batch
339
+ if (currentBatch.items.length > 0) {
340
+ batches.push(currentBatch);
341
+ }
342
+
343
+ // Apply adaptive batch optimization if enabled
344
+ if (this.config.adaptiveSizing && this.adaptiveState.optimalBatchSize) {
345
+ return this._reoptimizeBatches(batches);
346
+ }
347
+
348
+ return batches;
349
+ }
350
+
351
+ /**
352
+ * Process batch with retry logic
353
+ * @private
354
+ */
355
+ async _processBatchWithRetry(batch, processFn, batchIndex) {
356
+ let lastError;
357
+ let retryCount = 0;
358
+
359
+ while (retryCount <= this.config.maxRetries) {
360
+ try {
361
+ // Extract items for processing
362
+ const items = batch.items.map((item) =>
363
+ typeof item === 'string'
364
+ ? item
365
+ : item.text || item.content || String(item),
366
+ );
367
+
368
+ // Process the batch
369
+ const results = await processFn(items, {
370
+ batchIndex,
371
+ estimatedTokens: batch.estimatedTokens,
372
+ signal: this.abortController.signal,
373
+ });
374
+
375
+ // Track tokens
376
+ this.metrics.totalTokens += batch.estimatedTokens;
377
+
378
+ return results;
379
+ } catch (error) {
380
+ lastError = error;
381
+
382
+ // Don't retry on cancellation
383
+ if (this.cancelled || error.message === 'Processing cancelled') {
384
+ throw error;
385
+ }
386
+
387
+ retryCount++;
388
+
389
+ if (retryCount <= this.config.maxRetries) {
390
+ const delay =
391
+ this.config.retryDelay *
392
+ Math.pow(this.config.retryBackoff, retryCount - 1);
393
+
394
+ this.emit('batch_retry', {
395
+ batchIndex,
396
+ retryCount,
397
+ maxRetries: this.config.maxRetries,
398
+ delay,
399
+ error: error.message,
400
+ });
401
+
402
+ await new Promise((resolve) => setTimeout(resolve, delay));
403
+ }
404
+ }
405
+ }
406
+
407
+ throw new Error(
408
+ `Batch ${batchIndex} failed after ${this.config.maxRetries} retries: ${lastError.message}`,
409
+ );
410
+ }
411
+
412
+ /**
413
+ * Estimate token count for text
414
+ * Uses approximation: ~4 characters per token for English text
415
+ * @private
416
+ */
417
+ _estimateTokens(text) {
418
+ if (!text) return 0;
419
+
420
+ // Rough estimation: 1 token ≈ 4 characters
421
+ // More accurate would use tiktoken, but this avoids dependency
422
+ const chars = text.length;
423
+ const tokens = Math.ceil(chars / 4);
424
+
425
+ // Add overhead for special tokens
426
+ return tokens + 2;
427
+ }
428
+
429
+ /**
430
+ * Estimate batch count
431
+ * @private
432
+ */
433
+ _estimateBatchCount(items) {
434
+ let totalTokens = 0;
435
+ for (const item of items) {
436
+ const text =
437
+ typeof item === 'string'
438
+ ? item
439
+ : item.text || item.content || String(item);
440
+ totalTokens += this._estimateTokens(text);
441
+ }
442
+
443
+ const maxTokens =
444
+ this.config.maxTokensPerBatch * this.config.targetBatchUtilization;
445
+ return Math.ceil(totalTokens / maxTokens);
446
+ }
447
+
448
+ /**
449
+ * Estimate remaining time
450
+ * @private
451
+ */
452
+ _estimateTimeRemaining(processed, total, elapsed) {
453
+ if (processed === 0) return null;
454
+
455
+ const rate = processed / elapsed; // items per ms
456
+ const remaining = total - processed;
457
+ return Math.ceil(remaining / rate); // ms
458
+ }
459
+
460
+ /**
461
+ * Update memory estimate
462
+ * @private
463
+ */
464
+ _updateMemoryEstimate(text) {
465
+ // Rough estimate: 2 bytes per character in memory
466
+ const bytes = text.length * 2;
467
+ this.metrics.memoryUsed += bytes;
468
+
469
+ if (this.metrics.memoryUsed > this.metrics.peakMemory) {
470
+ this.metrics.peakMemory = this.metrics.memoryUsed;
471
+ }
472
+
473
+ // Emit warning if memory threshold exceeded
474
+ const memoryMB = this.metrics.memoryUsed / (1024 * 1024);
475
+ if (memoryMB > this.config.maxMemoryMB * 0.9) {
476
+ this.emit('memory_warning', {
477
+ used: memoryMB,
478
+ limit: this.config.maxMemoryMB,
479
+ percentage: (memoryMB / this.config.maxMemoryMB) * 100,
480
+ });
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Update adaptive sizing state
486
+ * @private
487
+ */
488
+ _updateAdaptiveState(batchSize, processingTime) {
489
+ if (!this.config.adaptiveSizing) return;
490
+
491
+ // Track recent batch performance
492
+ this.adaptiveState.recentBatchSizes.push(batchSize);
493
+ this.adaptiveState.recentProcessingTimes.push(processingTime);
494
+
495
+ // Keep only recent history (last 10 batches)
496
+ if (this.adaptiveState.recentBatchSizes.length > 10) {
497
+ this.adaptiveState.recentBatchSizes.shift();
498
+ this.adaptiveState.recentProcessingTimes.shift();
499
+ }
500
+
501
+ // Calculate optimal batch size based on processing time
502
+ if (this.adaptiveState.recentBatchSizes.length >= 5) {
503
+ const avgTime =
504
+ this.adaptiveState.recentProcessingTimes.reduce((a, b) => a + b, 0) /
505
+ this.adaptiveState.recentProcessingTimes.length;
506
+ const avgSize =
507
+ this.adaptiveState.recentBatchSizes.reduce((a, b) => a + b, 0) /
508
+ this.adaptiveState.recentBatchSizes.length;
509
+
510
+ // Target: 2-5 seconds per batch for good throughput
511
+ const targetTime = 3000; // 3 seconds
512
+ const scaleFactor = targetTime / avgTime;
513
+
514
+ this.adaptiveState.optimalBatchSize = Math.floor(avgSize * scaleFactor);
515
+
516
+ // Clamp to reasonable range
517
+ this.adaptiveState.optimalBatchSize = Math.max(
518
+ this.config.minItemsPerBatch,
519
+ Math.min(
520
+ this.config.maxItemsPerBatch,
521
+ this.adaptiveState.optimalBatchSize,
522
+ ),
523
+ );
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Re-optimize batches based on adaptive learning
529
+ * @private
530
+ */
531
+ _reoptimizeBatches(batches) {
532
+ // If we've learned an optimal batch size, regroup batches
533
+ const optimalSize = this.adaptiveState.optimalBatchSize;
534
+ if (!optimalSize) return batches;
535
+
536
+ // Flatten all items
537
+ const allItems = [];
538
+ const allIndices = [];
539
+ batches.forEach((batch) => {
540
+ allItems.push(...batch.items);
541
+ allIndices.push(...batch.originalIndices);
542
+ });
543
+
544
+ // Re-create batches with optimal size
545
+ const optimized = [];
546
+ for (let i = 0; i < allItems.length; i += optimalSize) {
547
+ const batchItems = allItems.slice(i, i + optimalSize);
548
+ const batchIndices = allIndices.slice(i, i + optimalSize);
549
+
550
+ const estimatedTokens = batchItems.reduce((sum, item) => {
551
+ const text =
552
+ typeof item === 'string'
553
+ ? item
554
+ : item.text || item.content || String(item);
555
+ return sum + this._estimateTokens(text);
556
+ }, 0);
557
+
558
+ optimized.push({
559
+ items: batchItems,
560
+ estimatedTokens,
561
+ originalIndices: batchIndices,
562
+ });
563
+ }
564
+
565
+ return optimized;
566
+ }
567
+
568
+ /**
569
+ * Restore original order of results
570
+ * @private
571
+ */
572
+ _restoreOriginalOrder(results, originalItems) {
573
+ // If results have metadata with original indices, use that
574
+ // Otherwise assume results are in processed order
575
+ return results;
576
+ }
577
+
578
+ /**
579
+ * Cancel ongoing processing
580
+ */
581
+ cancel() {
582
+ this.cancelled = true;
583
+ if (this.abortController) {
584
+ this.abortController.abort();
585
+ }
586
+ this.emit('cancelled', {
587
+ processedItems: this.metrics.processedItems,
588
+ totalItems: this.metrics.totalItems,
589
+ });
590
+ }
591
+
592
+ /**
593
+ * Get current metrics
594
+ */
595
+ getMetrics() {
596
+ return {
597
+ ...this.metrics,
598
+ processing: this.processing,
599
+ cancelled: this.cancelled,
600
+ efficiency: {
601
+ batchUtilization:
602
+ this.metrics.avgBatchSize / this.config.maxItemsPerBatch,
603
+ tokenUtilization:
604
+ this.metrics.avgTokensPerBatch / this.config.maxTokensPerBatch,
605
+ apiCallReduction:
606
+ this.metrics.totalItems > 0
607
+ ? (this.metrics.apiCallsSaved / this.metrics.totalItems) * 100
608
+ : 0,
609
+ throughput:
610
+ this.metrics.totalTime > 0
611
+ ? this.metrics.processedItems / (this.metrics.totalTime / 1000)
612
+ : 0, // items per second
613
+ },
614
+ };
615
+ }
616
+
617
+ /**
618
+ * Get processing status
619
+ */
620
+ getStatus() {
621
+ return {
622
+ processing: this.processing,
623
+ cancelled: this.cancelled,
624
+ progress:
625
+ this.metrics.totalItems > 0
626
+ ? (this.metrics.processedItems / this.metrics.totalItems) * 100
627
+ : 0,
628
+ processedItems: this.metrics.processedItems,
629
+ totalItems: this.metrics.totalItems,
630
+ successfulBatches: this.metrics.successfulBatches,
631
+ failedBatches: this.metrics.failedBatches,
632
+ };
633
+ }
634
+
635
+ /**
636
+ * Reset metrics
637
+ */
638
+ resetMetrics() {
639
+ this.metrics = {
640
+ totalItems: 0,
641
+ processedItems: 0,
642
+ totalBatches: 0,
643
+ successfulBatches: 0,
644
+ failedBatches: 0,
645
+ totalTokens: 0,
646
+ totalTime: 0,
647
+ avgBatchSize: 0,
648
+ avgTokensPerBatch: 0,
649
+ apiCallsSaved: 0,
650
+ memoryUsed: 0,
651
+ peakMemory: 0,
652
+ };
653
+
654
+ this.adaptiveState = {
655
+ recentBatchSizes: [],
656
+ recentProcessingTimes: [],
657
+ optimalBatchSize: null,
658
+ };
659
+
660
+ this.emit('metrics_reset');
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Create embedder with batch processing
666
+ *
667
+ * Wraps an embedder to use intelligent batch processing
668
+ */
669
+ function createBatchedEmbedder(embedder, options = {}) {
670
+ const processor = new BatchProcessor({
671
+ model: options.model || embedder.model,
672
+ ...options,
673
+ });
674
+
675
+ return {
676
+ ...embedder,
677
+
678
+ async embed(texts, embedOptions = {}) {
679
+ // Use batch processor
680
+ const results = await processor.processBatches(
681
+ texts,
682
+ async (batch, batchInfo) => {
683
+ // Call original embedder
684
+ return await embedder.embed(batch, embedOptions);
685
+ },
686
+ {
687
+ onProgress: embedOptions.onProgress,
688
+ abortController: embedOptions.abortController,
689
+ },
690
+ );
691
+
692
+ return results;
693
+ },
694
+
695
+ getProcessor() {
696
+ return processor;
697
+ },
698
+
699
+ getBatchMetrics() {
700
+ return processor.getMetrics();
701
+ },
702
+ };
703
+ }
704
+
705
+ module.exports = {
706
+ BatchProcessor,
707
+ createBatchedEmbedder,
708
+ DEFAULT_CONFIG,
709
+ MODEL_LIMITS,
710
+ };