@soulcraft/brainy 6.5.0 → 6.6.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/assets/models/all-MiniLM-L6-v2-q8/config.json +25 -0
- package/assets/models/all-MiniLM-L6-v2-q8/model.onnx +0 -0
- package/assets/models/all-MiniLM-L6-v2-q8/tokenizer.json +30686 -0
- package/assets/models/all-MiniLM-L6-v2-q8/vocab.json +1 -0
- package/dist/brainy.js +0 -6
- package/dist/config/index.d.ts +1 -3
- package/dist/config/index.js +2 -4
- package/dist/config/modelAutoConfig.d.ts +10 -17
- package/dist/config/modelAutoConfig.js +15 -88
- package/dist/config/sharedConfigManager.d.ts +1 -2
- package/dist/config/zeroConfig.d.ts +2 -13
- package/dist/config/zeroConfig.js +7 -15
- package/dist/critical/model-guardian.d.ts +5 -22
- package/dist/critical/model-guardian.js +38 -210
- package/dist/embeddings/EmbeddingManager.d.ts +7 -17
- package/dist/embeddings/EmbeddingManager.js +28 -136
- package/dist/embeddings/wasm/AssetLoader.d.ts +67 -0
- package/dist/embeddings/wasm/AssetLoader.js +238 -0
- package/dist/embeddings/wasm/EmbeddingPostProcessor.d.ts +60 -0
- package/dist/embeddings/wasm/EmbeddingPostProcessor.js +123 -0
- package/dist/embeddings/wasm/ONNXInferenceEngine.d.ts +55 -0
- package/dist/embeddings/wasm/ONNXInferenceEngine.js +154 -0
- package/dist/embeddings/wasm/WASMEmbeddingEngine.d.ts +82 -0
- package/dist/embeddings/wasm/WASMEmbeddingEngine.js +231 -0
- package/dist/embeddings/wasm/WordPieceTokenizer.d.ts +71 -0
- package/dist/embeddings/wasm/WordPieceTokenizer.js +264 -0
- package/dist/embeddings/wasm/index.d.ts +13 -0
- package/dist/embeddings/wasm/index.js +15 -0
- package/dist/embeddings/wasm/types.d.ts +114 -0
- package/dist/embeddings/wasm/types.js +25 -0
- package/dist/setup.d.ts +11 -11
- package/dist/setup.js +17 -31
- package/dist/types/brainy.types.d.ts +0 -5
- package/dist/utils/embedding.d.ts +45 -62
- package/dist/utils/embedding.js +61 -440
- package/package.json +10 -3
- package/scripts/download-model.cjs +175 -0
|
@@ -5,51 +5,24 @@
|
|
|
5
5
|
* Without the exact model, users CANNOT access their data
|
|
6
6
|
*
|
|
7
7
|
* Requirements:
|
|
8
|
-
* 1. Model MUST be
|
|
9
|
-
* 2. Model MUST be available at runtime
|
|
8
|
+
* 1. Model MUST be all-MiniLM-L6-v2-q8 (bundled in package)
|
|
9
|
+
* 2. Model MUST be available at runtime (embedded in npm package)
|
|
10
10
|
* 3. Model MUST produce consistent 384-dim embeddings
|
|
11
11
|
* 4. System MUST fail fast if model unavailable in production
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { WASMEmbeddingEngine } from '../embeddings/wasm/index.js';
|
|
14
14
|
// CRITICAL: These values MUST NEVER CHANGE
|
|
15
15
|
const CRITICAL_MODEL_CONFIG = {
|
|
16
|
-
modelName: '
|
|
17
|
-
modelHash: {
|
|
18
|
-
// SHA256 of model.onnx - computed from actual model
|
|
19
|
-
'onnx/model.onnx': 'add_actual_hash_here',
|
|
20
|
-
'tokenizer.json': 'add_actual_hash_here'
|
|
21
|
-
},
|
|
22
|
-
modelSize: {
|
|
23
|
-
'onnx/model.onnx': 90387606, // Exact size in bytes (updated to match actual file)
|
|
24
|
-
'tokenizer.json': 711661
|
|
25
|
-
},
|
|
16
|
+
modelName: 'all-MiniLM-L6-v2-q8',
|
|
26
17
|
embeddingDimensions: 384,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
name: 'Soulcraft CDN (Primary)',
|
|
31
|
-
url: 'https://models.soulcraft.com/models/all-MiniLM-L6-v2.tar.gz',
|
|
32
|
-
type: 'tarball'
|
|
33
|
-
},
|
|
34
|
-
// Secondary: GitHub releases backup
|
|
35
|
-
{
|
|
36
|
-
name: 'GitHub Backup',
|
|
37
|
-
url: 'https://github.com/soulcraftlabs/brainy-models/releases/download/v1.0.0/all-MiniLM-L6-v2.tar.gz',
|
|
38
|
-
type: 'tarball'
|
|
39
|
-
},
|
|
40
|
-
// Tertiary: Hugging Face (original source)
|
|
41
|
-
{
|
|
42
|
-
name: 'Hugging Face',
|
|
43
|
-
url: 'huggingface',
|
|
44
|
-
type: 'transformers'
|
|
45
|
-
}
|
|
46
|
-
]
|
|
18
|
+
// Model is bundled in package - no external downloads needed
|
|
19
|
+
bundled: true
|
|
47
20
|
};
|
|
48
21
|
export class ModelGuardian {
|
|
49
22
|
constructor() {
|
|
50
23
|
this.isVerified = false;
|
|
51
24
|
this.lastVerification = null;
|
|
52
|
-
|
|
25
|
+
// Model is bundled - no path detection needed
|
|
53
26
|
}
|
|
54
27
|
static getInstance() {
|
|
55
28
|
if (!ModelGuardian.instance) {
|
|
@@ -62,200 +35,55 @@ export class ModelGuardian {
|
|
|
62
35
|
* This MUST be called before any embedding operations
|
|
63
36
|
*/
|
|
64
37
|
async ensureCriticalModel() {
|
|
65
|
-
console.log('DEBUG: ensureCriticalModel called');
|
|
66
|
-
console.log('🛡️ MODEL GUARDIAN: Verifying critical model availability...');
|
|
67
|
-
console.log(`🚀 Debug: Model path: ${this.modelPath}`);
|
|
68
|
-
console.log(`🚀 Debug: Already verified: ${this.isVerified}`);
|
|
69
38
|
// Check if already verified in this session
|
|
70
39
|
if (this.isVerified && this.lastVerification) {
|
|
71
40
|
const hoursSinceVerification = (Date.now() - this.lastVerification.getTime()) / (1000 * 60 * 60);
|
|
72
41
|
if (hoursSinceVerification < 24) {
|
|
73
|
-
console.log('✅ Model previously verified in this session');
|
|
74
42
|
return;
|
|
75
43
|
}
|
|
76
44
|
}
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (modelExists) {
|
|
81
|
-
console.log('✅ Critical model verified locally');
|
|
45
|
+
// Verify the bundled WASM model works
|
|
46
|
+
const modelWorks = await this.verifyBundledModel();
|
|
47
|
+
if (modelWorks) {
|
|
82
48
|
this.isVerified = true;
|
|
83
49
|
this.lastVerification = new Date();
|
|
84
|
-
this.configureTransformers();
|
|
85
50
|
return;
|
|
86
51
|
}
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'Solution: Run "npm run download-models" during build stage.');
|
|
93
|
-
}
|
|
94
|
-
// Step 3: Attempt to download from fallback sources
|
|
95
|
-
console.warn('⚠️ Model not found locally, attempting download...');
|
|
96
|
-
for (const source of CRITICAL_MODEL_CONFIG.fallbackSources) {
|
|
97
|
-
try {
|
|
98
|
-
console.log(`📥 Trying ${source.name}...`);
|
|
99
|
-
await this.downloadFromSource(source);
|
|
100
|
-
// Verify the download
|
|
101
|
-
if (await this.verifyLocalModel()) {
|
|
102
|
-
console.log(`✅ Successfully downloaded from ${source.name}`);
|
|
103
|
-
this.isVerified = true;
|
|
104
|
-
this.lastVerification = new Date();
|
|
105
|
-
this.configureTransformers();
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
console.warn(`❌ ${source.name} failed:`, error.message);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// Step 4: CRITICAL FAILURE
|
|
114
|
-
throw new Error('🚨 CRITICAL FAILURE: Cannot obtain transformer model!\n' +
|
|
115
|
-
'Tried all fallback sources.\n' +
|
|
116
|
-
'Brainy CANNOT function without the model.\n' +
|
|
117
|
-
'Users CANNOT access their data.\n' +
|
|
118
|
-
'Please check network connectivity or pre-download models.');
|
|
52
|
+
// CRITICAL FAILURE
|
|
53
|
+
throw new Error('🚨 CRITICAL FAILURE: Bundled transformer model not working!\n' +
|
|
54
|
+
'The model is REQUIRED for Brainy to function.\n' +
|
|
55
|
+
'Users CANNOT access their data without it.\n' +
|
|
56
|
+
'This indicates a package installation issue.');
|
|
119
57
|
}
|
|
120
58
|
/**
|
|
121
|
-
* Verify the
|
|
59
|
+
* Verify the bundled WASM model works correctly
|
|
122
60
|
*/
|
|
123
|
-
async
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.log(`🔍 Debug: Model path components: ${this.modelPath} + ${CRITICAL_MODEL_CONFIG.modelName.split('/')}`);
|
|
136
|
-
// Check critical files
|
|
137
|
-
const criticalFiles = [
|
|
138
|
-
'onnx/model.onnx',
|
|
139
|
-
'tokenizer.json',
|
|
140
|
-
'config.json'
|
|
141
|
-
];
|
|
142
|
-
for (const file of criticalFiles) {
|
|
143
|
-
const filePath = path.join(modelBasePath, file);
|
|
144
|
-
console.log(`🔍 Debug: Checking file: ${filePath}`);
|
|
145
|
-
if (!fs.existsSync(filePath)) {
|
|
146
|
-
console.log(`❌ Missing critical file: ${file} at ${filePath}`);
|
|
61
|
+
async verifyBundledModel() {
|
|
62
|
+
try {
|
|
63
|
+
const engine = WASMEmbeddingEngine.getInstance();
|
|
64
|
+
// Initialize the engine (loads bundled model)
|
|
65
|
+
await engine.initialize();
|
|
66
|
+
// Test embedding generation
|
|
67
|
+
const testEmbedding = await engine.embed('test verification');
|
|
68
|
+
// Verify dimensions
|
|
69
|
+
if (testEmbedding.length !== CRITICAL_MODEL_CONFIG.embeddingDimensions) {
|
|
70
|
+
console.error(`❌ CRITICAL: Model dimension mismatch!\n` +
|
|
71
|
+
`Expected: ${CRITICAL_MODEL_CONFIG.embeddingDimensions}\n` +
|
|
72
|
+
`Got: ${testEmbedding.length}`);
|
|
147
73
|
return false;
|
|
148
74
|
}
|
|
149
|
-
// Verify
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.error(`❌ CRITICAL: Model file size mismatch!\n` +
|
|
155
|
-
`File: ${file}\n` +
|
|
156
|
-
`Expected: ${expectedSize} bytes\n` +
|
|
157
|
-
`Actual: ${stats.size} bytes\n` +
|
|
158
|
-
`This indicates model corruption or version mismatch!`);
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// SHA256 verification for ultimate security
|
|
163
|
-
if (CRITICAL_MODEL_CONFIG.modelHash && CRITICAL_MODEL_CONFIG.modelHash[file]) {
|
|
164
|
-
const hash = await this.computeFileHash(filePath);
|
|
165
|
-
if (hash !== CRITICAL_MODEL_CONFIG.modelHash[file]) {
|
|
166
|
-
console.error(`❌ CRITICAL: Model hash mismatch for ${file}!\n` +
|
|
167
|
-
`Expected: ${CRITICAL_MODEL_CONFIG.modelHash[file]}\n` +
|
|
168
|
-
`Got: ${hash}\n` +
|
|
169
|
-
`This indicates model tampering or corruption!`);
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
75
|
+
// Verify normalization (should be unit length)
|
|
76
|
+
const norm = Math.sqrt(testEmbedding.reduce((sum, v) => sum + v * v, 0));
|
|
77
|
+
if (Math.abs(norm - 1.0) > 0.01) {
|
|
78
|
+
console.error(`❌ CRITICAL: Embeddings not normalized! Norm: ${norm}`);
|
|
79
|
+
return false;
|
|
172
80
|
}
|
|
173
|
-
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Compute SHA256 hash of a file
|
|
178
|
-
*/
|
|
179
|
-
async computeFileHash(filePath) {
|
|
180
|
-
try {
|
|
181
|
-
const { readFile } = await import('node:fs/promises');
|
|
182
|
-
const { createHash } = await import('node:crypto');
|
|
183
|
-
const fileBuffer = await readFile(filePath);
|
|
184
|
-
const hash = createHash('sha256').update(fileBuffer).digest('hex');
|
|
185
|
-
return hash;
|
|
81
|
+
return true;
|
|
186
82
|
}
|
|
187
83
|
catch (error) {
|
|
188
|
-
console.error(
|
|
189
|
-
return
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Download model from a fallback source
|
|
194
|
-
*/
|
|
195
|
-
async downloadFromSource(source) {
|
|
196
|
-
if (source.type === 'transformers') {
|
|
197
|
-
// Use transformers.js native download
|
|
198
|
-
const { pipeline } = await import('@huggingface/transformers');
|
|
199
|
-
env.cacheDir = this.modelPath;
|
|
200
|
-
env.allowRemoteModels = true;
|
|
201
|
-
const extractor = await pipeline('feature-extraction', CRITICAL_MODEL_CONFIG.modelName);
|
|
202
|
-
// Test the model
|
|
203
|
-
const test = await extractor('test', { pooling: 'mean', normalize: true });
|
|
204
|
-
if (test.data.length !== CRITICAL_MODEL_CONFIG.embeddingDimensions) {
|
|
205
|
-
throw new Error(`CRITICAL: Model dimension mismatch! ` +
|
|
206
|
-
`Expected ${CRITICAL_MODEL_CONFIG.embeddingDimensions}, ` +
|
|
207
|
-
`got ${test.data.length}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else if (source.type === 'tarball') {
|
|
211
|
-
// Tarball extraction would require additional dependencies
|
|
212
|
-
// Skip this source and try next fallback
|
|
213
|
-
console.warn(`⚠️ Tarball extraction not available for ${source.name}. Trying next source...`);
|
|
214
|
-
return; // Will continue to next source in the loop
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Configure transformers.js to use verified local model
|
|
219
|
-
*/
|
|
220
|
-
configureTransformers() {
|
|
221
|
-
env.localModelPath = this.modelPath;
|
|
222
|
-
env.allowRemoteModels = false; // Force local only after verification
|
|
223
|
-
console.log('🔒 Transformers configured to use verified local model');
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Detect where models should be stored
|
|
227
|
-
*/
|
|
228
|
-
detectModelPath() {
|
|
229
|
-
// Browser always uses default path
|
|
230
|
-
if (typeof window !== 'undefined') {
|
|
231
|
-
return './models';
|
|
232
|
-
}
|
|
233
|
-
// Use require for synchronous access in Node.js
|
|
234
|
-
try {
|
|
235
|
-
const fs = require('node:fs');
|
|
236
|
-
const path = require('node:path');
|
|
237
|
-
const candidates = [
|
|
238
|
-
process.env.BRAINY_MODELS_PATH,
|
|
239
|
-
'./models',
|
|
240
|
-
path.join(process.cwd(), 'models'),
|
|
241
|
-
path.join(process.env.HOME || '', '.brainy', 'models'),
|
|
242
|
-
'/opt/models', // Lambda/container path
|
|
243
|
-
env.cacheDir
|
|
244
|
-
];
|
|
245
|
-
for (const candidatePath of candidates) {
|
|
246
|
-
if (candidatePath && fs.existsSync(candidatePath)) {
|
|
247
|
-
const modelPath = path.join(candidatePath, ...CRITICAL_MODEL_CONFIG.modelName.split('/'));
|
|
248
|
-
if (fs.existsSync(path.join(modelPath, 'onnx', 'model.onnx'))) {
|
|
249
|
-
return candidatePath; // Return the models directory, not its parent
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch (e) {
|
|
255
|
-
// If Node.js modules not available, return default
|
|
84
|
+
console.error('❌ Model verification failed:', error);
|
|
85
|
+
return false;
|
|
256
86
|
}
|
|
257
|
-
// Default
|
|
258
|
-
return './models';
|
|
259
87
|
}
|
|
260
88
|
/**
|
|
261
89
|
* Get model status for diagnostics
|
|
@@ -263,10 +91,10 @@ export class ModelGuardian {
|
|
|
263
91
|
async getStatus() {
|
|
264
92
|
return {
|
|
265
93
|
verified: this.isVerified,
|
|
266
|
-
path: this.modelPath,
|
|
267
94
|
lastVerification: this.lastVerification,
|
|
268
95
|
modelName: CRITICAL_MODEL_CONFIG.modelName,
|
|
269
|
-
dimensions: CRITICAL_MODEL_CONFIG.embeddingDimensions
|
|
96
|
+
dimensions: CRITICAL_MODEL_CONFIG.embeddingDimensions,
|
|
97
|
+
bundled: CRITICAL_MODEL_CONFIG.bundled
|
|
270
98
|
};
|
|
271
99
|
}
|
|
272
100
|
/**
|
|
@@ -2,18 +2,14 @@
|
|
|
2
2
|
* Unified Embedding Manager
|
|
3
3
|
*
|
|
4
4
|
* THE single source of truth for all embedding operations in Brainy.
|
|
5
|
-
*
|
|
6
|
-
* into one clean, maintainable class.
|
|
5
|
+
* Uses direct ONNX WASM inference for universal compatibility.
|
|
7
6
|
*
|
|
8
7
|
* Features:
|
|
9
8
|
* - Singleton pattern ensures ONE model instance
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
9
|
+
* - Direct ONNX WASM (no transformers.js dependency)
|
|
10
|
+
* - Bundled model (no runtime downloads)
|
|
11
|
+
* - Works everywhere: Node.js, Bun, Bun --compile, browsers
|
|
13
12
|
* - Memory monitoring
|
|
14
|
-
*
|
|
15
|
-
* This replaces: SingletonModelManager, TransformerEmbedding, ModelPrecisionManager,
|
|
16
|
-
* hybridModelManager, universalMemoryManager, and more.
|
|
17
13
|
*/
|
|
18
14
|
import { Vector, EmbeddingFunction } from '../coreTypes.js';
|
|
19
15
|
export type ModelPrecision = 'q8' | 'fp32';
|
|
@@ -27,9 +23,11 @@ interface EmbeddingStats {
|
|
|
27
23
|
}
|
|
28
24
|
/**
|
|
29
25
|
* Unified Embedding Manager - Clean, simple, reliable
|
|
26
|
+
*
|
|
27
|
+
* Now powered by direct ONNX WASM for universal compatibility.
|
|
30
28
|
*/
|
|
31
29
|
export declare class EmbeddingManager {
|
|
32
|
-
private
|
|
30
|
+
private engine;
|
|
33
31
|
private precision;
|
|
34
32
|
private modelName;
|
|
35
33
|
private initialized;
|
|
@@ -61,14 +59,6 @@ export declare class EmbeddingManager {
|
|
|
61
59
|
* Get embedding function for compatibility
|
|
62
60
|
*/
|
|
63
61
|
getEmbeddingFunction(): EmbeddingFunction;
|
|
64
|
-
/**
|
|
65
|
-
* Get models directory path
|
|
66
|
-
* Note: In browser environments, returns a simple default path
|
|
67
|
-
* In Node.js, checks multiple locations for the models directory
|
|
68
|
-
*/
|
|
69
|
-
private getModelsPath;
|
|
70
|
-
private modelsPathCache;
|
|
71
|
-
private resolveModelsPathSync;
|
|
72
62
|
/**
|
|
73
63
|
* Get memory usage in MB
|
|
74
64
|
*/
|
|
@@ -2,39 +2,34 @@
|
|
|
2
2
|
* Unified Embedding Manager
|
|
3
3
|
*
|
|
4
4
|
* THE single source of truth for all embedding operations in Brainy.
|
|
5
|
-
*
|
|
6
|
-
* into one clean, maintainable class.
|
|
5
|
+
* Uses direct ONNX WASM inference for universal compatibility.
|
|
7
6
|
*
|
|
8
7
|
* Features:
|
|
9
8
|
* - Singleton pattern ensures ONE model instance
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
9
|
+
* - Direct ONNX WASM (no transformers.js dependency)
|
|
10
|
+
* - Bundled model (no runtime downloads)
|
|
11
|
+
* - Works everywhere: Node.js, Bun, Bun --compile, browsers
|
|
13
12
|
* - Memory monitoring
|
|
14
|
-
*
|
|
15
|
-
* This replaces: SingletonModelManager, TransformerEmbedding, ModelPrecisionManager,
|
|
16
|
-
* hybridModelManager, universalMemoryManager, and more.
|
|
17
13
|
*/
|
|
18
|
-
import {
|
|
19
|
-
import { isNode } from '../utils/environment.js';
|
|
14
|
+
import { WASMEmbeddingEngine } from './wasm/index.js';
|
|
20
15
|
// Global state for true singleton across entire process
|
|
21
16
|
let globalInstance = null;
|
|
22
17
|
let globalInitPromise = null;
|
|
23
18
|
/**
|
|
24
19
|
* Unified Embedding Manager - Clean, simple, reliable
|
|
20
|
+
*
|
|
21
|
+
* Now powered by direct ONNX WASM for universal compatibility.
|
|
25
22
|
*/
|
|
26
23
|
export class EmbeddingManager {
|
|
27
24
|
constructor() {
|
|
28
|
-
this.
|
|
29
|
-
this.modelName = '
|
|
25
|
+
this.precision = 'q8';
|
|
26
|
+
this.modelName = 'all-MiniLM-L6-v2';
|
|
30
27
|
this.initialized = false;
|
|
31
28
|
this.initTime = null;
|
|
32
29
|
this.embedCount = 0;
|
|
33
30
|
this.locked = false;
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
this.precision = 'q8';
|
|
37
|
-
console.log(`🎯 EmbeddingManager: Using Q8 precision`);
|
|
31
|
+
this.engine = WASMEmbeddingEngine.getInstance();
|
|
32
|
+
console.log('🎯 EmbeddingManager: Using Q8 precision (WASM)');
|
|
38
33
|
}
|
|
39
34
|
/**
|
|
40
35
|
* Get the singleton instance
|
|
@@ -50,9 +45,10 @@ export class EmbeddingManager {
|
|
|
50
45
|
*/
|
|
51
46
|
async init() {
|
|
52
47
|
// In unit test mode, skip real model initialization
|
|
53
|
-
const isTestMode = process.env.BRAINY_UNIT_TEST === 'true' ||
|
|
48
|
+
const isTestMode = process.env.BRAINY_UNIT_TEST === 'true' ||
|
|
49
|
+
globalThis.__BRAINY_UNIT_TEST__;
|
|
54
50
|
if (isTestMode) {
|
|
55
|
-
// Production safeguard
|
|
51
|
+
// Production safeguard
|
|
56
52
|
if (process.env.NODE_ENV === 'production') {
|
|
57
53
|
throw new Error('CRITICAL: Mock embeddings detected in production environment! ' +
|
|
58
54
|
'BRAINY_UNIT_TEST or __BRAINY_UNIT_TEST__ is set while NODE_ENV=production. ' +
|
|
@@ -66,7 +62,7 @@ export class EmbeddingManager {
|
|
|
66
62
|
return;
|
|
67
63
|
}
|
|
68
64
|
// Already initialized
|
|
69
|
-
if (this.initialized && this.
|
|
65
|
+
if (this.initialized && this.engine.isInitialized()) {
|
|
70
66
|
return;
|
|
71
67
|
}
|
|
72
68
|
// Initialization in progress
|
|
@@ -88,62 +84,20 @@ export class EmbeddingManager {
|
|
|
88
84
|
*/
|
|
89
85
|
async performInit() {
|
|
90
86
|
const startTime = Date.now();
|
|
91
|
-
console.log(`🚀 Initializing embedding model (${this.precision.toUpperCase()})...`);
|
|
92
87
|
try {
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
env.cacheDir = modelsPath;
|
|
96
|
-
env.allowLocalModels = true;
|
|
97
|
-
env.useFSCache = true;
|
|
98
|
-
// Check if models exist locally (only in Node.js)
|
|
99
|
-
if (isNode()) {
|
|
100
|
-
try {
|
|
101
|
-
const nodeRequire = typeof require !== 'undefined' ? require : null;
|
|
102
|
-
if (nodeRequire) {
|
|
103
|
-
const path = nodeRequire('node:path');
|
|
104
|
-
const fs = nodeRequire('node:fs');
|
|
105
|
-
const modelPath = path.join(modelsPath, ...this.modelName.split('/'));
|
|
106
|
-
const hasLocalModels = fs.existsSync(modelPath);
|
|
107
|
-
if (hasLocalModels) {
|
|
108
|
-
console.log('✅ Using cached models from:', modelPath);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Silently continue if require fails
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Configure pipeline options for the selected precision
|
|
117
|
-
const pipelineOptions = {
|
|
118
|
-
cache_dir: modelsPath,
|
|
119
|
-
local_files_only: false,
|
|
120
|
-
// Always use Q8 precision
|
|
121
|
-
dtype: 'q8',
|
|
122
|
-
quantized: true,
|
|
123
|
-
// Memory optimizations
|
|
124
|
-
session_options: {
|
|
125
|
-
enableCpuMemArena: false,
|
|
126
|
-
enableMemPattern: false,
|
|
127
|
-
interOpNumThreads: 1,
|
|
128
|
-
intraOpNumThreads: 1,
|
|
129
|
-
graphOptimizationLevel: 'disabled'
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
// Load the model
|
|
133
|
-
this.model = await pipeline('feature-extraction', this.modelName, pipelineOptions);
|
|
88
|
+
// Initialize WASM engine (handles all model loading)
|
|
89
|
+
await this.engine.initialize();
|
|
134
90
|
// Lock precision after successful initialization
|
|
135
91
|
this.locked = true;
|
|
136
92
|
this.initialized = true;
|
|
137
93
|
this.initTime = Date.now() - startTime;
|
|
138
94
|
// Log success
|
|
139
95
|
const memoryMB = this.getMemoryUsage();
|
|
140
|
-
console.log(`✅ Model loaded in ${this.initTime}ms`);
|
|
141
96
|
console.log(`📊 Precision: Q8 | Memory: ${memoryMB}MB`);
|
|
142
|
-
console.log(
|
|
97
|
+
console.log('🔒 Configuration locked');
|
|
143
98
|
}
|
|
144
99
|
catch (error) {
|
|
145
100
|
this.initialized = false;
|
|
146
|
-
this.model = null;
|
|
147
101
|
throw new Error(`Failed to initialize embedding model: ${error instanceof Error ? error.message : String(error)}`);
|
|
148
102
|
}
|
|
149
103
|
}
|
|
@@ -151,10 +105,10 @@ export class EmbeddingManager {
|
|
|
151
105
|
* Generate embeddings
|
|
152
106
|
*/
|
|
153
107
|
async embed(text) {
|
|
154
|
-
// Check for unit test environment
|
|
155
|
-
const isTestMode = process.env.BRAINY_UNIT_TEST === 'true' ||
|
|
108
|
+
// Check for unit test environment
|
|
109
|
+
const isTestMode = process.env.BRAINY_UNIT_TEST === 'true' ||
|
|
110
|
+
globalThis.__BRAINY_UNIT_TEST__;
|
|
156
111
|
if (isTestMode) {
|
|
157
|
-
// Production safeguard
|
|
158
112
|
if (process.env.NODE_ENV === 'production') {
|
|
159
113
|
throw new Error('CRITICAL: Mock embeddings in production!');
|
|
160
114
|
}
|
|
@@ -162,38 +116,26 @@ export class EmbeddingManager {
|
|
|
162
116
|
}
|
|
163
117
|
// Ensure initialized
|
|
164
118
|
await this.init();
|
|
165
|
-
|
|
166
|
-
throw new Error('Model not initialized');
|
|
167
|
-
}
|
|
168
|
-
// CRITICAL FIX: Ensure input is always a string
|
|
119
|
+
// Normalize input to string
|
|
169
120
|
let input;
|
|
170
121
|
if (Array.isArray(text)) {
|
|
171
|
-
|
|
172
|
-
input = text.map(t => typeof t === 'string' ? t : String(t)).join(' ');
|
|
122
|
+
input = text.map((t) => (typeof t === 'string' ? t : String(t))).join(' ');
|
|
173
123
|
}
|
|
174
124
|
else if (typeof text === 'string') {
|
|
175
125
|
input = text;
|
|
176
126
|
}
|
|
177
127
|
else if (typeof text === 'object') {
|
|
178
|
-
// Convert object to string representation
|
|
179
128
|
input = JSON.stringify(text);
|
|
180
129
|
}
|
|
181
130
|
else {
|
|
182
|
-
// This shouldn't happen but let's be defensive
|
|
183
131
|
console.warn('EmbeddingManager.embed received unexpected input type:', typeof text);
|
|
184
132
|
input = String(text);
|
|
185
133
|
}
|
|
186
|
-
// Generate embedding
|
|
187
|
-
const
|
|
188
|
-
pooling: 'mean',
|
|
189
|
-
normalize: true
|
|
190
|
-
});
|
|
191
|
-
// Extract embedding vector
|
|
192
|
-
const embedding = Array.from(output.data);
|
|
134
|
+
// Generate embedding using WASM engine
|
|
135
|
+
const embedding = await this.engine.embed(input);
|
|
193
136
|
// Validate dimensions
|
|
194
137
|
if (embedding.length !== 384) {
|
|
195
138
|
console.warn(`Unexpected embedding dimension: ${embedding.length}`);
|
|
196
|
-
// Pad or truncate
|
|
197
139
|
if (embedding.length < 384) {
|
|
198
140
|
return [...embedding, ...new Array(384 - embedding.length).fill(0)];
|
|
199
141
|
}
|
|
@@ -208,7 +150,6 @@ export class EmbeddingManager {
|
|
|
208
150
|
* Generate mock embeddings for unit tests
|
|
209
151
|
*/
|
|
210
152
|
getMockEmbedding(text) {
|
|
211
|
-
// Use the same mock logic as setup-unit.ts for consistency
|
|
212
153
|
const input = Array.isArray(text) ? text.join(' ') : text;
|
|
213
154
|
const str = typeof input === 'string' ? input : JSON.stringify(input);
|
|
214
155
|
const vector = new Array(384).fill(0);
|
|
@@ -220,7 +161,6 @@ export class EmbeddingManager {
|
|
|
220
161
|
for (let i = 0; i < 384; i++) {
|
|
221
162
|
vector[i] += Math.sin(i * 0.1 + str.length) * 0.1;
|
|
222
163
|
}
|
|
223
|
-
// Track mock embedding count
|
|
224
164
|
this.embedCount++;
|
|
225
165
|
return vector;
|
|
226
166
|
}
|
|
@@ -232,55 +172,6 @@ export class EmbeddingManager {
|
|
|
232
172
|
return await this.embed(data);
|
|
233
173
|
};
|
|
234
174
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Get models directory path
|
|
237
|
-
* Note: In browser environments, returns a simple default path
|
|
238
|
-
* In Node.js, checks multiple locations for the models directory
|
|
239
|
-
*/
|
|
240
|
-
getModelsPath() {
|
|
241
|
-
// In browser environments, use a default path
|
|
242
|
-
if (!isNode()) {
|
|
243
|
-
return './models';
|
|
244
|
-
}
|
|
245
|
-
// Node.js-specific model path resolution
|
|
246
|
-
// Cache the result for performance
|
|
247
|
-
if (!this.modelsPathCache) {
|
|
248
|
-
this.modelsPathCache = this.resolveModelsPathSync();
|
|
249
|
-
}
|
|
250
|
-
return this.modelsPathCache;
|
|
251
|
-
}
|
|
252
|
-
resolveModelsPathSync() {
|
|
253
|
-
// For Node.js environments, we can safely assume these modules exist
|
|
254
|
-
// TypeScript will handle the imports at build time
|
|
255
|
-
// At runtime, these will only be called if isNode() is true
|
|
256
|
-
// Default fallback path
|
|
257
|
-
const defaultPath = './models';
|
|
258
|
-
try {
|
|
259
|
-
// Create a conditional require function that only works in Node
|
|
260
|
-
const nodeRequire = typeof require !== 'undefined' ? require : null;
|
|
261
|
-
if (!nodeRequire)
|
|
262
|
-
return defaultPath;
|
|
263
|
-
const fs = nodeRequire('node:fs');
|
|
264
|
-
const path = nodeRequire('node:path');
|
|
265
|
-
const paths = [
|
|
266
|
-
process.env.BRAINY_MODELS_PATH,
|
|
267
|
-
'./models',
|
|
268
|
-
path.join(process.cwd(), 'models'),
|
|
269
|
-
path.join(process.env.HOME || '', '.brainy', 'models')
|
|
270
|
-
];
|
|
271
|
-
for (const p of paths) {
|
|
272
|
-
if (p && fs.existsSync(p)) {
|
|
273
|
-
return p;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// Default Node.js path
|
|
277
|
-
return path.join(process.cwd(), 'models');
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
// Fallback if require fails
|
|
281
|
-
return defaultPath;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
175
|
/**
|
|
285
176
|
* Get memory usage in MB
|
|
286
177
|
*/
|
|
@@ -295,13 +186,14 @@ export class EmbeddingManager {
|
|
|
295
186
|
* Get current statistics
|
|
296
187
|
*/
|
|
297
188
|
getStats() {
|
|
189
|
+
const engineStats = this.engine.getStats();
|
|
298
190
|
return {
|
|
299
191
|
initialized: this.initialized,
|
|
300
192
|
precision: this.precision,
|
|
301
193
|
modelName: this.modelName,
|
|
302
|
-
embedCount: this.embedCount,
|
|
194
|
+
embedCount: this.embedCount + engineStats.embedCount,
|
|
303
195
|
initTime: this.initTime,
|
|
304
|
-
memoryMB: this.getMemoryUsage()
|
|
196
|
+
memoryMB: this.getMemoryUsage(),
|
|
305
197
|
};
|
|
306
198
|
}
|
|
307
199
|
/**
|