@soulcraft/brainy 6.5.0 → 6.6.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/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/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/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
|
@@ -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
|
/**
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Loader
|
|
3
|
+
*
|
|
4
|
+
* Resolves paths to model files (ONNX model, vocabulary) across environments.
|
|
5
|
+
* Handles Node.js, Bun, and bundled scenarios.
|
|
6
|
+
*
|
|
7
|
+
* Asset Resolution Order:
|
|
8
|
+
* 1. Environment variable: BRAINY_MODEL_PATH
|
|
9
|
+
* 2. Package-relative: node_modules/@soulcraft/brainy/assets/models/
|
|
10
|
+
* 3. Project-relative: ./assets/models/
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Asset loader for model files
|
|
14
|
+
*/
|
|
15
|
+
export declare class AssetLoader {
|
|
16
|
+
private modelDir;
|
|
17
|
+
/**
|
|
18
|
+
* Get the model directory path
|
|
19
|
+
*/
|
|
20
|
+
getModelDir(): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the model directory across environments
|
|
23
|
+
*/
|
|
24
|
+
private resolveModelDir;
|
|
25
|
+
/**
|
|
26
|
+
* Get package root path (Node.js/Bun only)
|
|
27
|
+
*/
|
|
28
|
+
private getPackageRootPath;
|
|
29
|
+
/**
|
|
30
|
+
* Check if path exists (works in Node.js/Bun)
|
|
31
|
+
*/
|
|
32
|
+
private pathExists;
|
|
33
|
+
/**
|
|
34
|
+
* Get path to ONNX model file
|
|
35
|
+
*/
|
|
36
|
+
getModelPath(): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Get path to vocabulary file
|
|
39
|
+
*/
|
|
40
|
+
getVocabPath(): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Load vocabulary from JSON file
|
|
43
|
+
*/
|
|
44
|
+
loadVocab(): Promise<Record<string, number>>;
|
|
45
|
+
/**
|
|
46
|
+
* Load model as ArrayBuffer (for ONNX session)
|
|
47
|
+
*/
|
|
48
|
+
loadModel(): Promise<ArrayBuffer>;
|
|
49
|
+
/**
|
|
50
|
+
* Verify all required assets exist
|
|
51
|
+
*/
|
|
52
|
+
verifyAssets(): Promise<{
|
|
53
|
+
valid: boolean;
|
|
54
|
+
modelPath: string;
|
|
55
|
+
vocabPath: string;
|
|
56
|
+
errors: string[];
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Clear cached paths (for testing)
|
|
60
|
+
*/
|
|
61
|
+
clearCache(): void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create asset loader instance
|
|
65
|
+
*/
|
|
66
|
+
export declare function createAssetLoader(): AssetLoader;
|
|
67
|
+
export declare function getAssetLoader(): AssetLoader;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Loader
|
|
3
|
+
*
|
|
4
|
+
* Resolves paths to model files (ONNX model, vocabulary) across environments.
|
|
5
|
+
* Handles Node.js, Bun, and bundled scenarios.
|
|
6
|
+
*
|
|
7
|
+
* Asset Resolution Order:
|
|
8
|
+
* 1. Environment variable: BRAINY_MODEL_PATH
|
|
9
|
+
* 2. Package-relative: node_modules/@soulcraft/brainy/assets/models/
|
|
10
|
+
* 3. Project-relative: ./assets/models/
|
|
11
|
+
*/
|
|
12
|
+
import { MODEL_CONSTANTS } from './types.js';
|
|
13
|
+
// Cache resolved paths
|
|
14
|
+
let cachedModelDir = null;
|
|
15
|
+
let cachedVocab = null;
|
|
16
|
+
/**
|
|
17
|
+
* Asset loader for model files
|
|
18
|
+
*/
|
|
19
|
+
export class AssetLoader {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.modelDir = null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the model directory path
|
|
25
|
+
*/
|
|
26
|
+
async getModelDir() {
|
|
27
|
+
if (this.modelDir) {
|
|
28
|
+
return this.modelDir;
|
|
29
|
+
}
|
|
30
|
+
if (cachedModelDir) {
|
|
31
|
+
this.modelDir = cachedModelDir;
|
|
32
|
+
return cachedModelDir;
|
|
33
|
+
}
|
|
34
|
+
// Try to resolve model directory
|
|
35
|
+
const resolved = await this.resolveModelDir();
|
|
36
|
+
this.modelDir = resolved;
|
|
37
|
+
cachedModelDir = resolved;
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the model directory across environments
|
|
42
|
+
*/
|
|
43
|
+
async resolveModelDir() {
|
|
44
|
+
// 1. Check environment variable
|
|
45
|
+
if (typeof process !== 'undefined' && process.env?.BRAINY_MODEL_PATH) {
|
|
46
|
+
const envPath = process.env.BRAINY_MODEL_PATH;
|
|
47
|
+
if (await this.pathExists(envPath)) {
|
|
48
|
+
return envPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 2. Try common locations
|
|
52
|
+
const modelName = MODEL_CONSTANTS.MODEL_NAME + '-q8';
|
|
53
|
+
const possiblePaths = [
|
|
54
|
+
// Package assets (when installed as dependency)
|
|
55
|
+
`./assets/models/${modelName}`,
|
|
56
|
+
`./node_modules/@soulcraft/brainy/assets/models/${modelName}`,
|
|
57
|
+
// Development paths
|
|
58
|
+
`../assets/models/${modelName}`,
|
|
59
|
+
// Absolute from package root
|
|
60
|
+
this.getPackageRootPath(`assets/models/${modelName}`),
|
|
61
|
+
].filter(Boolean);
|
|
62
|
+
for (const path of possiblePaths) {
|
|
63
|
+
if (await this.pathExists(path)) {
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// If no path found, return default (will error on use)
|
|
68
|
+
return `./assets/models/${modelName}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get package root path (Node.js/Bun only)
|
|
72
|
+
*/
|
|
73
|
+
getPackageRootPath(relativePath) {
|
|
74
|
+
if (typeof process === 'undefined') {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Use __dirname equivalent
|
|
79
|
+
const url = new URL(import.meta.url);
|
|
80
|
+
const currentDir = url.pathname.replace(/\/[^/]*$/, '');
|
|
81
|
+
// Go up from src/embeddings/wasm to package root
|
|
82
|
+
const packageRoot = currentDir.replace(/\/src\/embeddings\/wasm$/, '');
|
|
83
|
+
return `${packageRoot}/${relativePath}`;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if path exists (works in Node.js/Bun)
|
|
91
|
+
*/
|
|
92
|
+
async pathExists(path) {
|
|
93
|
+
if (typeof process === 'undefined') {
|
|
94
|
+
// Browser - check via fetch
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(path, { method: 'HEAD' });
|
|
97
|
+
return response.ok;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Node.js/Bun
|
|
104
|
+
try {
|
|
105
|
+
const fs = await import('node:fs/promises');
|
|
106
|
+
await fs.access(path);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get path to ONNX model file
|
|
115
|
+
*/
|
|
116
|
+
async getModelPath() {
|
|
117
|
+
const dir = await this.getModelDir();
|
|
118
|
+
return `${dir}/model.onnx`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get path to vocabulary file
|
|
122
|
+
*/
|
|
123
|
+
async getVocabPath() {
|
|
124
|
+
const dir = await this.getModelDir();
|
|
125
|
+
return `${dir}/vocab.json`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Load vocabulary from JSON file
|
|
129
|
+
*/
|
|
130
|
+
async loadVocab() {
|
|
131
|
+
if (cachedVocab) {
|
|
132
|
+
return cachedVocab;
|
|
133
|
+
}
|
|
134
|
+
const vocabPath = await this.getVocabPath();
|
|
135
|
+
if (typeof process !== 'undefined') {
|
|
136
|
+
// Node.js/Bun - read from filesystem
|
|
137
|
+
try {
|
|
138
|
+
const fs = await import('node:fs/promises');
|
|
139
|
+
const content = await fs.readFile(vocabPath, 'utf-8');
|
|
140
|
+
cachedVocab = JSON.parse(content);
|
|
141
|
+
return cachedVocab;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new Error(`Failed to load vocabulary from ${vocabPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Browser - fetch
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(vocabPath);
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`HTTP ${response.status}`);
|
|
153
|
+
}
|
|
154
|
+
cachedVocab = await response.json();
|
|
155
|
+
return cachedVocab;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new Error(`Failed to fetch vocabulary from ${vocabPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Load model as ArrayBuffer (for ONNX session)
|
|
164
|
+
*/
|
|
165
|
+
async loadModel() {
|
|
166
|
+
const modelPath = await this.getModelPath();
|
|
167
|
+
if (typeof process !== 'undefined') {
|
|
168
|
+
// Node.js/Bun - read from filesystem
|
|
169
|
+
try {
|
|
170
|
+
const fs = await import('node:fs/promises');
|
|
171
|
+
const buffer = await fs.readFile(modelPath);
|
|
172
|
+
// Convert Node.js Buffer to ArrayBuffer
|
|
173
|
+
return new Uint8Array(buffer).buffer;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new Error(`Failed to load model from ${modelPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Browser - fetch
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetch(modelPath);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new Error(`HTTP ${response.status}`);
|
|
185
|
+
}
|
|
186
|
+
return await response.arrayBuffer();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw new Error(`Failed to fetch model from ${modelPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Verify all required assets exist
|
|
195
|
+
*/
|
|
196
|
+
async verifyAssets() {
|
|
197
|
+
const errors = [];
|
|
198
|
+
const modelPath = await this.getModelPath();
|
|
199
|
+
const vocabPath = await this.getVocabPath();
|
|
200
|
+
if (!(await this.pathExists(modelPath))) {
|
|
201
|
+
errors.push(`Model file not found: ${modelPath}`);
|
|
202
|
+
}
|
|
203
|
+
if (!(await this.pathExists(vocabPath))) {
|
|
204
|
+
errors.push(`Vocabulary file not found: ${vocabPath}`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
valid: errors.length === 0,
|
|
208
|
+
modelPath,
|
|
209
|
+
vocabPath,
|
|
210
|
+
errors,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Clear cached paths (for testing)
|
|
215
|
+
*/
|
|
216
|
+
clearCache() {
|
|
217
|
+
this.modelDir = null;
|
|
218
|
+
cachedModelDir = null;
|
|
219
|
+
cachedVocab = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Create asset loader instance
|
|
224
|
+
*/
|
|
225
|
+
export function createAssetLoader() {
|
|
226
|
+
return new AssetLoader();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Singleton asset loader
|
|
230
|
+
*/
|
|
231
|
+
let singletonLoader = null;
|
|
232
|
+
export function getAssetLoader() {
|
|
233
|
+
if (!singletonLoader) {
|
|
234
|
+
singletonLoader = new AssetLoader();
|
|
235
|
+
}
|
|
236
|
+
return singletonLoader;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=AssetLoader.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedding Post-Processor
|
|
3
|
+
*
|
|
4
|
+
* Converts raw ONNX model output to final embedding vectors.
|
|
5
|
+
* Implements mean pooling and L2 normalization as used by sentence-transformers.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline:
|
|
8
|
+
* 1. Mean Pooling: Average token embeddings (weighted by attention mask)
|
|
9
|
+
* 2. L2 Normalization: Normalize to unit length for cosine similarity
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Post-processor for converting ONNX output to sentence embeddings
|
|
13
|
+
*/
|
|
14
|
+
export declare class EmbeddingPostProcessor {
|
|
15
|
+
private hiddenSize;
|
|
16
|
+
constructor(hiddenSize?: number);
|
|
17
|
+
/**
|
|
18
|
+
* Mean pool token embeddings weighted by attention mask
|
|
19
|
+
*
|
|
20
|
+
* @param hiddenStates - Raw model output [seqLen * hiddenSize] flattened
|
|
21
|
+
* @param attentionMask - Attention mask [seqLen] (1 for real tokens, 0 for padding)
|
|
22
|
+
* @param seqLen - Sequence length
|
|
23
|
+
* @returns Mean-pooled embedding [hiddenSize]
|
|
24
|
+
*/
|
|
25
|
+
meanPool(hiddenStates: Float32Array, attentionMask: number[], seqLen: number): Float32Array;
|
|
26
|
+
/**
|
|
27
|
+
* L2 normalize embedding to unit length
|
|
28
|
+
*
|
|
29
|
+
* @param embedding - Input embedding
|
|
30
|
+
* @returns Normalized embedding with ||x|| = 1
|
|
31
|
+
*/
|
|
32
|
+
normalize(embedding: Float32Array): Float32Array;
|
|
33
|
+
/**
|
|
34
|
+
* Full post-processing pipeline: mean pool then normalize
|
|
35
|
+
*
|
|
36
|
+
* @param hiddenStates - Raw model output [seqLen * hiddenSize]
|
|
37
|
+
* @param attentionMask - Attention mask [seqLen]
|
|
38
|
+
* @param seqLen - Sequence length
|
|
39
|
+
* @returns Final normalized embedding [hiddenSize]
|
|
40
|
+
*/
|
|
41
|
+
process(hiddenStates: Float32Array, attentionMask: number[], seqLen: number): Float32Array;
|
|
42
|
+
/**
|
|
43
|
+
* Process batch of embeddings
|
|
44
|
+
*
|
|
45
|
+
* @param hiddenStates - Raw model output [batchSize * seqLen * hiddenSize]
|
|
46
|
+
* @param attentionMasks - Attention masks [batchSize][seqLen]
|
|
47
|
+
* @param batchSize - Number of sequences in batch
|
|
48
|
+
* @param seqLen - Sequence length (same for all in batch due to padding)
|
|
49
|
+
* @returns Array of normalized embeddings
|
|
50
|
+
*/
|
|
51
|
+
processBatch(hiddenStates: Float32Array, attentionMasks: number[][], batchSize: number, seqLen: number): Float32Array[];
|
|
52
|
+
/**
|
|
53
|
+
* Convert Float32Array to number array
|
|
54
|
+
*/
|
|
55
|
+
toNumberArray(embedding: Float32Array): number[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a post-processor with default configuration
|
|
59
|
+
*/
|
|
60
|
+
export declare function createPostProcessor(): EmbeddingPostProcessor;
|