@soulcraft/brainy 2.12.0 → 2.14.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/CHANGELOG.md +15 -0
- package/dist/brainyData.js +4 -17
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/modelAutoConfig.d.ts +1 -0
- package/dist/config/modelAutoConfig.js +27 -22
- package/dist/config/modelPrecisionManager.d.ts +42 -0
- package/dist/config/modelPrecisionManager.js +98 -0
- package/dist/config/zeroConfig.js +1 -1
- package/dist/embeddings/CachedEmbeddings.d.ts +40 -0
- package/dist/embeddings/CachedEmbeddings.js +146 -0
- package/dist/embeddings/EmbeddingManager.d.ts +106 -0
- package/dist/embeddings/EmbeddingManager.js +296 -0
- package/dist/embeddings/SingletonModelManager.d.ts +95 -0
- package/dist/embeddings/SingletonModelManager.js +220 -0
- package/dist/embeddings/index.d.ts +12 -0
- package/dist/embeddings/index.js +16 -0
- package/dist/embeddings/lightweight-embedder.d.ts +0 -1
- package/dist/embeddings/lightweight-embedder.js +4 -12
- package/dist/embeddings/universal-memory-manager.js +13 -50
- package/dist/embeddings/worker-embedding.js +4 -8
- package/dist/utils/embedding.d.ts +7 -2
- package/dist/utils/embedding.js +51 -33
- package/dist/utils/hybridModelManager.d.ts +19 -28
- package/dist/utils/hybridModelManager.js +36 -200
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Works in ALL environments: Node.js, browsers, serverless, workers
|
|
5
5
|
* Solves transformers.js memory leak with environment-specific strategies
|
|
6
6
|
*/
|
|
7
|
+
import { getModelPrecision } from '../config/modelPrecisionManager.js';
|
|
7
8
|
// Environment detection
|
|
8
9
|
const isNode = typeof process !== 'undefined' && process.versions?.node;
|
|
9
10
|
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
@@ -107,7 +108,7 @@ export class UniversalMemoryManager {
|
|
|
107
108
|
const { TransformerEmbedding } = await import('../utils/embedding.js');
|
|
108
109
|
this.embeddingFunction = new TransformerEmbedding({
|
|
109
110
|
verbose: false,
|
|
110
|
-
precision:
|
|
111
|
+
precision: getModelPrecision(), // Use centrally managed precision
|
|
111
112
|
localFilesOnly: process.env.BRAINY_ALLOW_REMOTE_MODELS !== 'true'
|
|
112
113
|
});
|
|
113
114
|
await this.embeddingFunction.init();
|
|
@@ -119,49 +120,15 @@ export class UniversalMemoryManager {
|
|
|
119
120
|
}
|
|
120
121
|
async cleanup() {
|
|
121
122
|
const startTime = Date.now();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// In serverless, create new instance
|
|
132
|
-
if (this.embeddingFunction?.dispose) {
|
|
133
|
-
this.embeddingFunction.dispose();
|
|
134
|
-
}
|
|
135
|
-
this.embeddingFunction = null;
|
|
136
|
-
break;
|
|
137
|
-
case 'browser-dispose':
|
|
138
|
-
// In browser, try disposal
|
|
139
|
-
if (this.embeddingFunction?.dispose) {
|
|
140
|
-
this.embeddingFunction.dispose();
|
|
141
|
-
}
|
|
142
|
-
// Force garbage collection if available
|
|
143
|
-
if (typeof window !== 'undefined' && window.gc) {
|
|
144
|
-
window.gc();
|
|
145
|
-
}
|
|
146
|
-
break;
|
|
147
|
-
default:
|
|
148
|
-
// Fallback: dispose and recreate
|
|
149
|
-
if (this.embeddingFunction?.dispose) {
|
|
150
|
-
this.embeddingFunction.dispose();
|
|
151
|
-
}
|
|
152
|
-
this.embeddingFunction = null;
|
|
153
|
-
}
|
|
154
|
-
this.embedCount = 0;
|
|
155
|
-
this.restartCount++;
|
|
156
|
-
this.lastRestart = Date.now();
|
|
157
|
-
const cleanupTime = Date.now() - startTime;
|
|
158
|
-
console.log(`🧹 Memory cleanup completed in ${cleanupTime}ms (strategy: ${this.strategy})`);
|
|
159
|
-
}
|
|
160
|
-
catch (error) {
|
|
161
|
-
console.warn('⚠️ Cleanup failed:', error instanceof Error ? error.message : String(error));
|
|
162
|
-
// Force null assignment as last resort
|
|
163
|
-
this.embeddingFunction = null;
|
|
164
|
-
}
|
|
123
|
+
// SingletonModelManager persists - we just reset our counters
|
|
124
|
+
// The singleton model stays alive for consistency across all operations
|
|
125
|
+
// Reset counters
|
|
126
|
+
this.embedCount = 0;
|
|
127
|
+
this.restartCount++;
|
|
128
|
+
this.lastRestart = Date.now();
|
|
129
|
+
const cleanupTime = Date.now() - startTime;
|
|
130
|
+
console.log(`🧹 Memory counters reset in ${cleanupTime}ms (strategy: ${this.strategy})`);
|
|
131
|
+
console.log('ℹ️ Singleton model persists for consistency across all operations');
|
|
165
132
|
}
|
|
166
133
|
getMemoryStats() {
|
|
167
134
|
let memoryUsage = 'unknown';
|
|
@@ -182,12 +149,8 @@ export class UniversalMemoryManager {
|
|
|
182
149
|
};
|
|
183
150
|
}
|
|
184
151
|
async dispose() {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
await this.embeddingFunction.dispose();
|
|
188
|
-
}
|
|
189
|
-
this.embeddingFunction = null;
|
|
190
|
-
}
|
|
152
|
+
// SingletonModelManager persists - nothing to dispose
|
|
153
|
+
console.log('ℹ️ Universal Memory Manager: Singleton model persists');
|
|
191
154
|
}
|
|
192
155
|
}
|
|
193
156
|
// Export singleton instance
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { TransformerEmbedding } from '../utils/embedding.js';
|
|
8
8
|
import { parentPort } from 'worker_threads';
|
|
9
|
+
import { getModelPrecision } from '../config/modelPrecisionManager.js';
|
|
9
10
|
let model = null;
|
|
10
11
|
let requestCount = 0;
|
|
11
12
|
const MAX_REQUESTS = 100; // Restart worker after 100 requests to prevent memory leak
|
|
@@ -13,7 +14,7 @@ async function initModel() {
|
|
|
13
14
|
if (!model) {
|
|
14
15
|
model = new TransformerEmbedding({
|
|
15
16
|
verbose: false,
|
|
16
|
-
precision:
|
|
17
|
+
precision: getModelPrecision(), // Use centrally managed precision
|
|
17
18
|
localFilesOnly: process.env.BRAINY_ALLOW_REMOTE_MODELS !== 'true'
|
|
18
19
|
});
|
|
19
20
|
await model.init();
|
|
@@ -37,13 +38,8 @@ if (parentPort) {
|
|
|
37
38
|
}
|
|
38
39
|
break;
|
|
39
40
|
case 'dispose':
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if ('dispose' in model && typeof model.dispose === 'function') {
|
|
43
|
-
model.dispose();
|
|
44
|
-
}
|
|
45
|
-
model = null;
|
|
46
|
-
}
|
|
41
|
+
// SingletonModelManager persists - just acknowledge
|
|
42
|
+
console.log('ℹ️ Worker: Singleton model persists');
|
|
47
43
|
parentPort.postMessage({ id, success: true });
|
|
48
44
|
break;
|
|
49
45
|
case 'restart':
|
|
@@ -51,6 +51,10 @@ export declare class TransformerEmbedding implements EmbeddingModel {
|
|
|
51
51
|
* Log message only if verbose mode is enabled
|
|
52
52
|
*/
|
|
53
53
|
private logger;
|
|
54
|
+
/**
|
|
55
|
+
* Generate mock embeddings for unit tests
|
|
56
|
+
*/
|
|
57
|
+
private getMockEmbedding;
|
|
54
58
|
/**
|
|
55
59
|
* Initialize the embedding model
|
|
56
60
|
*/
|
|
@@ -78,12 +82,13 @@ export declare const UniversalSentenceEncoder: typeof TransformerEmbedding;
|
|
|
78
82
|
*/
|
|
79
83
|
export declare function createEmbeddingModel(options?: TransformerEmbeddingOptions): EmbeddingModel;
|
|
80
84
|
/**
|
|
81
|
-
* Default embedding function using the
|
|
82
|
-
*
|
|
85
|
+
* Default embedding function using the unified EmbeddingManager
|
|
86
|
+
* Simple, clean, reliable - no more layers of indirection
|
|
83
87
|
*/
|
|
84
88
|
export declare const defaultEmbeddingFunction: EmbeddingFunction;
|
|
85
89
|
/**
|
|
86
90
|
* Create an embedding function with custom options
|
|
91
|
+
* NOTE: Options are validated but the singleton EmbeddingManager is always used
|
|
87
92
|
*/
|
|
88
93
|
export declare function createEmbeddingFunction(options?: TransformerEmbeddingOptions): EmbeddingFunction;
|
|
89
94
|
/**
|
package/dist/utils/embedding.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Complete rewrite to eliminate TensorFlow.js and use ONNX-based models
|
|
4
4
|
*/
|
|
5
5
|
import { isBrowser } from './environment.js';
|
|
6
|
-
import { ModelManager } from '../embeddings/model-manager.js';
|
|
7
6
|
import { join } from 'path';
|
|
8
7
|
import { existsSync } from 'fs';
|
|
9
8
|
// @ts-ignore - Transformers.js is now the primary embedding library
|
|
@@ -208,6 +207,24 @@ export class TransformerEmbedding {
|
|
|
208
207
|
console[level](`[TransformerEmbedding] ${message}`, ...args);
|
|
209
208
|
}
|
|
210
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Generate mock embeddings for unit tests
|
|
212
|
+
*/
|
|
213
|
+
getMockEmbedding(data) {
|
|
214
|
+
// Use the same mock logic as setup-unit.ts for consistency
|
|
215
|
+
const input = Array.isArray(data) ? data.join(' ') : data;
|
|
216
|
+
const str = typeof input === 'string' ? input : JSON.stringify(input);
|
|
217
|
+
const vector = new Array(384).fill(0);
|
|
218
|
+
// Create semi-realistic embeddings based on text content
|
|
219
|
+
for (let i = 0; i < Math.min(str.length, 384); i++) {
|
|
220
|
+
vector[i] = (str.charCodeAt(i % str.length) % 256) / 256;
|
|
221
|
+
}
|
|
222
|
+
// Add position-based variation
|
|
223
|
+
for (let i = 0; i < 384; i++) {
|
|
224
|
+
vector[i] += Math.sin(i * 0.1 + str.length) * 0.1;
|
|
225
|
+
}
|
|
226
|
+
return vector;
|
|
227
|
+
}
|
|
211
228
|
/**
|
|
212
229
|
* Initialize the embedding model
|
|
213
230
|
*/
|
|
@@ -215,11 +232,13 @@ export class TransformerEmbedding {
|
|
|
215
232
|
if (this.initialized) {
|
|
216
233
|
return;
|
|
217
234
|
}
|
|
218
|
-
//
|
|
235
|
+
// In unit test mode, skip real model initialization to prevent ONNX conflicts
|
|
236
|
+
if (process.env.BRAINY_UNIT_TEST === 'true' || globalThis.__BRAINY_UNIT_TEST__) {
|
|
237
|
+
this.initialized = true;
|
|
238
|
+
this.logger('log', '🧪 Using mocked embeddings for unit tests');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
219
241
|
try {
|
|
220
|
-
// Ensure models are available (downloads if needed)
|
|
221
|
-
const modelManager = ModelManager.getInstance();
|
|
222
|
-
await modelManager.ensureModels(this.options.model);
|
|
223
242
|
// Resolve device configuration and cache directory
|
|
224
243
|
const device = await resolveDevice(this.options.device);
|
|
225
244
|
const cacheDir = this.options.cacheDir === './models'
|
|
@@ -227,35 +246,26 @@ export class TransformerEmbedding {
|
|
|
227
246
|
: this.options.cacheDir;
|
|
228
247
|
this.logger('log', `Loading Transformer model: ${this.options.model} on device: ${device}`);
|
|
229
248
|
const startTime = Date.now();
|
|
230
|
-
//
|
|
231
|
-
const
|
|
232
|
-
let actualType =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (actualType !== this.options.precision) {
|
|
237
|
-
this.logger('log', `Using ${actualType} model (${this.options.precision} not available)`);
|
|
238
|
-
}
|
|
239
|
-
// CRITICAL FIX: Control which model file transformers.js loads
|
|
240
|
-
// When both model.onnx and model_quantized.onnx exist, transformers.js defaults to model.onnx
|
|
241
|
-
// We need to explicitly control this based on the precision setting
|
|
242
|
-
// Set environment to control model selection BEFORE creating pipeline
|
|
249
|
+
// Use the configured precision from EmbeddingManager
|
|
250
|
+
const { embeddingManager } = await import('../embeddings/EmbeddingManager.js');
|
|
251
|
+
let actualType = embeddingManager.getPrecision();
|
|
252
|
+
// CRITICAL: Control which model precision transformers.js uses
|
|
253
|
+
// Q8 models use quantized int8 weights for 75% size reduction
|
|
254
|
+
// FP32 models use full precision floating point
|
|
243
255
|
if (actualType === 'q8') {
|
|
244
|
-
|
|
245
|
-
// transformers.js v3 doesn't have a direct flag, so we need to work around this
|
|
246
|
-
// HACK: Temporarily modify the model file preference
|
|
247
|
-
// This forces transformers.js to look for model_quantized.onnx first
|
|
248
|
-
const originalModelFileName = env.onnxModelFileName(env).onnxModelFileName = 'model_quantized';
|
|
249
|
-
this.logger('log', '🎯 Selecting Q8 quantized model (75% smaller)');
|
|
256
|
+
this.logger('log', '🎯 Selecting Q8 quantized model (75% smaller, 99% accuracy)');
|
|
250
257
|
}
|
|
251
258
|
else {
|
|
252
|
-
this.logger('log', '📦 Using FP32 model (full precision)');
|
|
259
|
+
this.logger('log', '📦 Using FP32 model (full precision, larger size)');
|
|
253
260
|
}
|
|
254
261
|
// Load the feature extraction pipeline with memory optimizations
|
|
255
262
|
const pipelineOptions = {
|
|
256
263
|
cache_dir: cacheDir,
|
|
257
264
|
local_files_only: isBrowser() ? false : this.options.localFilesOnly,
|
|
258
|
-
//
|
|
265
|
+
// CRITICAL: Specify dtype for model precision
|
|
266
|
+
dtype: actualType === 'q8' ? 'q8' : 'fp32',
|
|
267
|
+
// CRITICAL: For Q8, explicitly use quantized model
|
|
268
|
+
quantized: actualType === 'q8',
|
|
259
269
|
// CRITICAL: ONNX memory optimizations
|
|
260
270
|
session_options: {
|
|
261
271
|
enableCpuMemArena: false, // Disable pre-allocated memory arena
|
|
@@ -336,6 +346,10 @@ export class TransformerEmbedding {
|
|
|
336
346
|
* Generate embeddings for text data
|
|
337
347
|
*/
|
|
338
348
|
async embed(data) {
|
|
349
|
+
// In unit test mode, return mock embeddings
|
|
350
|
+
if (process.env.BRAINY_UNIT_TEST === 'true' || globalThis.__BRAINY_UNIT_TEST__) {
|
|
351
|
+
return this.getMockEmbedding(data);
|
|
352
|
+
}
|
|
339
353
|
if (!this.initialized) {
|
|
340
354
|
await this.init();
|
|
341
355
|
}
|
|
@@ -433,21 +447,25 @@ export function createEmbeddingModel(options) {
|
|
|
433
447
|
return new TransformerEmbedding(options);
|
|
434
448
|
}
|
|
435
449
|
/**
|
|
436
|
-
* Default embedding function using the
|
|
437
|
-
*
|
|
450
|
+
* Default embedding function using the unified EmbeddingManager
|
|
451
|
+
* Simple, clean, reliable - no more layers of indirection
|
|
438
452
|
*/
|
|
439
453
|
export const defaultEmbeddingFunction = async (data) => {
|
|
440
|
-
const {
|
|
441
|
-
|
|
442
|
-
return await embeddingFn(data);
|
|
454
|
+
const { embed } = await import('../embeddings/EmbeddingManager.js');
|
|
455
|
+
return await embed(data);
|
|
443
456
|
};
|
|
444
457
|
/**
|
|
445
458
|
* Create an embedding function with custom options
|
|
459
|
+
* NOTE: Options are validated but the singleton EmbeddingManager is always used
|
|
446
460
|
*/
|
|
447
461
|
export function createEmbeddingFunction(options = {}) {
|
|
448
|
-
const embedder = new TransformerEmbedding(options);
|
|
449
462
|
return async (data) => {
|
|
450
|
-
|
|
463
|
+
const { embeddingManager } = await import('../embeddings/EmbeddingManager.js');
|
|
464
|
+
// Validate precision if specified
|
|
465
|
+
if (options.precision) {
|
|
466
|
+
embeddingManager.validatePrecision(options.precision);
|
|
467
|
+
}
|
|
468
|
+
return await embeddingManager.embed(data);
|
|
451
469
|
};
|
|
452
470
|
}
|
|
453
471
|
/**
|
|
@@ -1,55 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hybrid Model Manager - BEST OF BOTH WORLDS
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* NOW A WRAPPER AROUND SingletonModelManager
|
|
5
|
+
* Maintained for backward compatibility
|
|
6
|
+
*
|
|
7
|
+
* Previously combined:
|
|
5
8
|
* 1. Multi-source downloading strategy (GitHub → CDN → Hugging Face)
|
|
6
9
|
* 2. Singleton pattern preventing multiple ONNX model loads
|
|
7
10
|
* 3. Environment-specific optimizations
|
|
8
11
|
* 4. Graceful fallbacks and error handling
|
|
12
|
+
*
|
|
13
|
+
* Now delegates all operations to SingletonModelManager for true unification
|
|
9
14
|
*/
|
|
10
|
-
import { TransformerEmbedding } from './embedding.js';
|
|
11
15
|
import { EmbeddingFunction } from '../coreTypes.js';
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
17
|
+
* HybridModelManager - Now a wrapper around SingletonModelManager
|
|
18
|
+
* Maintained for backward compatibility
|
|
14
19
|
*/
|
|
15
20
|
declare class HybridModelManager {
|
|
16
21
|
private static instance;
|
|
17
|
-
private primaryModel;
|
|
18
|
-
private modelPromise;
|
|
19
|
-
private isInitialized;
|
|
20
|
-
private modelsPath;
|
|
21
22
|
private constructor();
|
|
22
23
|
static getInstance(): HybridModelManager;
|
|
23
24
|
/**
|
|
24
|
-
* Get the primary embedding model -
|
|
25
|
-
*/
|
|
26
|
-
getPrimaryModel(): Promise<TransformerEmbedding>;
|
|
27
|
-
/**
|
|
28
|
-
* Smart model path detection
|
|
29
|
-
*/
|
|
30
|
-
private getModelsPath;
|
|
31
|
-
/**
|
|
32
|
-
* Initialize with BEST OF BOTH: Multi-source + Singleton
|
|
33
|
-
*/
|
|
34
|
-
private initializePrimaryModel;
|
|
35
|
-
/**
|
|
36
|
-
* Create model with multi-source fallback strategy
|
|
25
|
+
* Get the primary embedding model - delegates to SingletonModelManager
|
|
37
26
|
*/
|
|
38
|
-
|
|
27
|
+
getPrimaryModel(): Promise<any>;
|
|
39
28
|
/**
|
|
40
|
-
* Get embedding function
|
|
29
|
+
* Get embedding function - delegates to SingletonModelManager
|
|
41
30
|
*/
|
|
42
31
|
getEmbeddingFunction(): Promise<EmbeddingFunction>;
|
|
43
32
|
/**
|
|
44
|
-
* Check if model is ready
|
|
33
|
+
* Check if model is ready - delegates to SingletonModelManager
|
|
45
34
|
*/
|
|
46
35
|
isModelReady(): boolean;
|
|
47
36
|
/**
|
|
48
|
-
* Force model reload
|
|
37
|
+
* Force model reload - not supported with SingletonModelManager
|
|
49
38
|
*/
|
|
50
39
|
reloadModel(): Promise<void>;
|
|
51
40
|
/**
|
|
52
|
-
* Get model status
|
|
41
|
+
* Get model status - delegates to SingletonModelManager
|
|
53
42
|
*/
|
|
54
43
|
getModelStatus(): {
|
|
55
44
|
loaded: boolean;
|
|
@@ -59,15 +48,17 @@ declare class HybridModelManager {
|
|
|
59
48
|
}
|
|
60
49
|
export declare const hybridModelManager: HybridModelManager;
|
|
61
50
|
/**
|
|
62
|
-
* Get the hybrid singleton embedding function -
|
|
51
|
+
* Get the hybrid singleton embedding function - Now delegates to SingletonModelManager
|
|
52
|
+
* Maintained for backward compatibility
|
|
63
53
|
*/
|
|
64
54
|
export declare function getHybridEmbeddingFunction(): Promise<EmbeddingFunction>;
|
|
65
55
|
/**
|
|
66
|
-
*
|
|
56
|
+
* Hybrid embedding function - Now delegates to SingletonModelManager
|
|
57
|
+
* Maintained for backward compatibility
|
|
67
58
|
*/
|
|
68
59
|
export declare const hybridEmbeddingFunction: EmbeddingFunction;
|
|
69
60
|
/**
|
|
70
|
-
* Preload model for tests or production -
|
|
61
|
+
* Preload model for tests or production - Now delegates to SingletonModelManager
|
|
71
62
|
*/
|
|
72
63
|
export declare function preloadHybridModel(): Promise<void>;
|
|
73
64
|
export {};
|