@soulcraft/brainy 2.11.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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Singleton Model Manager - THE ONLY SOURCE OF EMBEDDING MODELS
3
+ *
4
+ * This is the SINGLE, UNIFIED model initialization system that ensures:
5
+ * - Only ONE model instance exists across the entire system
6
+ * - Precision is configured once and locked
7
+ * - All components share the same model
8
+ * - No possibility of mixed precisions
9
+ *
10
+ * CRITICAL: This manager is used by EVERYTHING:
11
+ * - Storage operations (add, update)
12
+ * - Search operations (search, find)
13
+ * - Public API (embed, cluster)
14
+ * - Neural API (all neural.* methods)
15
+ * - Internal operations (deduplication, indexing)
16
+ */
17
+ import { TransformerEmbedding } from '../utils/embedding.js';
18
+ import { getModelPrecision, lockModelPrecision } from '../config/modelPrecisionManager.js';
19
+ // Global state - ensures true singleton across entire process
20
+ let globalModelInstance = null;
21
+ let globalInitPromise = null;
22
+ let globalInitialized = false;
23
+ /**
24
+ * The ONE TRUE model manager
25
+ */
26
+ export class SingletonModelManager {
27
+ constructor() {
28
+ this.stats = {
29
+ initialized: false,
30
+ precision: 'unknown',
31
+ initCount: 0,
32
+ embedCount: 0,
33
+ lastUsed: null
34
+ };
35
+ // Private constructor enforces singleton
36
+ this.stats.precision = getModelPrecision();
37
+ console.log(`🔐 SingletonModelManager initialized with ${this.stats.precision.toUpperCase()} precision`);
38
+ }
39
+ /**
40
+ * Get the singleton instance
41
+ */
42
+ static getInstance() {
43
+ if (!SingletonModelManager.instance) {
44
+ SingletonModelManager.instance = new SingletonModelManager();
45
+ }
46
+ return SingletonModelManager.instance;
47
+ }
48
+ /**
49
+ * Get the model instance - creates if needed, reuses if exists
50
+ * This is THE ONLY way to get a model in the entire system
51
+ */
52
+ async getModel() {
53
+ // If already initialized, return immediately
54
+ if (globalModelInstance && globalInitialized) {
55
+ this.stats.lastUsed = new Date();
56
+ return globalModelInstance;
57
+ }
58
+ // If initialization is in progress, wait for it
59
+ if (globalInitPromise) {
60
+ console.log('âŗ Model initialization already in progress, waiting...');
61
+ return await globalInitPromise;
62
+ }
63
+ // Start initialization (only happens once ever)
64
+ globalInitPromise = this.initializeModel();
65
+ try {
66
+ const model = await globalInitPromise;
67
+ globalInitialized = true;
68
+ return model;
69
+ }
70
+ catch (error) {
71
+ // Reset on error to allow retry
72
+ globalInitPromise = null;
73
+ throw error;
74
+ }
75
+ }
76
+ /**
77
+ * Initialize the model - happens exactly once
78
+ */
79
+ async initializeModel() {
80
+ console.log('🚀 Initializing singleton model instance...');
81
+ // Get precision from central manager
82
+ const precision = getModelPrecision();
83
+ console.log(`📊 Using ${precision.toUpperCase()} precision (${precision === 'q8' ? '23MB, 99% accuracy' : '90MB, 100% accuracy'})`);
84
+ // Detect environment for optimal settings
85
+ const isNode = typeof process !== 'undefined' && process.versions?.node;
86
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
87
+ const isServerless = typeof process !== 'undefined' && (process.env.VERCEL ||
88
+ process.env.NETLIFY ||
89
+ process.env.AWS_LAMBDA_FUNCTION_NAME ||
90
+ process.env.FUNCTIONS_WORKER_RUNTIME);
91
+ const isTest = globalThis.__BRAINY_TEST_ENV__ || process.env.NODE_ENV === 'test';
92
+ // Create optimized options based on environment
93
+ const options = {
94
+ precision: precision,
95
+ verbose: !isTest && !isServerless && !isBrowser,
96
+ device: 'cpu', // CPU is most compatible
97
+ localFilesOnly: process.env.BRAINY_ALLOW_REMOTE_MODELS === 'false',
98
+ model: 'Xenova/all-MiniLM-L6-v2'
99
+ };
100
+ try {
101
+ // Create the ONE model instance
102
+ globalModelInstance = new TransformerEmbedding(options);
103
+ // Initialize it
104
+ await globalModelInstance.init();
105
+ // CRITICAL: Lock the precision after successful initialization
106
+ // This prevents any future changes to precision
107
+ lockModelPrecision();
108
+ console.log('🔒 Model precision locked at:', precision.toUpperCase());
109
+ // Update stats
110
+ this.stats.initialized = true;
111
+ this.stats.initCount++;
112
+ this.stats.lastUsed = new Date();
113
+ // Log memory usage if available
114
+ if (isNode && process.memoryUsage) {
115
+ const usage = process.memoryUsage();
116
+ this.stats.memoryFootprint = Math.round(usage.heapUsed / 1024 / 1024);
117
+ console.log(`💾 Model loaded, memory usage: ${this.stats.memoryFootprint}MB`);
118
+ }
119
+ console.log('✅ Singleton model initialized successfully');
120
+ return globalModelInstance;
121
+ }
122
+ catch (error) {
123
+ console.error('❌ Failed to initialize singleton model:', error);
124
+ globalModelInstance = null;
125
+ throw new Error(`Singleton model initialization failed: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+ /**
129
+ * Get embedding function that uses the singleton model
130
+ */
131
+ async getEmbeddingFunction() {
132
+ const model = await this.getModel();
133
+ return async (data) => {
134
+ this.stats.embedCount++;
135
+ this.stats.lastUsed = new Date();
136
+ return await model.embed(data);
137
+ };
138
+ }
139
+ /**
140
+ * Direct embed method for convenience
141
+ */
142
+ async embed(data) {
143
+ const model = await this.getModel();
144
+ this.stats.embedCount++;
145
+ this.stats.lastUsed = new Date();
146
+ return await model.embed(data);
147
+ }
148
+ /**
149
+ * Check if model is initialized
150
+ */
151
+ isInitialized() {
152
+ return globalInitialized && globalModelInstance !== null;
153
+ }
154
+ /**
155
+ * Get current statistics
156
+ */
157
+ getStats() {
158
+ return {
159
+ ...this.stats,
160
+ precision: getModelPrecision()
161
+ };
162
+ }
163
+ /**
164
+ * Validate precision consistency
165
+ * Throws error if attempting to use different precision
166
+ */
167
+ validatePrecision(requestedPrecision) {
168
+ const currentPrecision = getModelPrecision();
169
+ if (requestedPrecision && requestedPrecision !== currentPrecision) {
170
+ throw new Error(`❌ Precision mismatch! System is using ${currentPrecision.toUpperCase()} ` +
171
+ `but ${requestedPrecision.toUpperCase()} was requested. ` +
172
+ `All operations must use the same precision.`);
173
+ }
174
+ }
175
+ /**
176
+ * Force cleanup (for testing only)
177
+ * WARNING: This will break consistency - use only in tests
178
+ */
179
+ async _testOnlyCleanup() {
180
+ if (process.env.NODE_ENV !== 'test') {
181
+ throw new Error('Cleanup only allowed in test environment');
182
+ }
183
+ if (globalModelInstance && 'dispose' in globalModelInstance) {
184
+ await globalModelInstance.dispose();
185
+ }
186
+ globalModelInstance = null;
187
+ globalInitPromise = null;
188
+ globalInitialized = false;
189
+ this.stats.initialized = false;
190
+ console.log('🧹 Singleton model cleaned up (test only)');
191
+ }
192
+ }
193
+ // Export the singleton instance getter
194
+ export const singletonModelManager = SingletonModelManager.getInstance();
195
+ /**
196
+ * THE ONLY embedding function that should be used anywhere
197
+ * This ensures all operations use the same model instance
198
+ */
199
+ export async function getUnifiedEmbeddingFunction() {
200
+ return await singletonModelManager.getEmbeddingFunction();
201
+ }
202
+ /**
203
+ * Direct embed function for convenience
204
+ */
205
+ export async function unifiedEmbed(data) {
206
+ return await singletonModelManager.embed(data);
207
+ }
208
+ /**
209
+ * Check if model is ready
210
+ */
211
+ export function isModelReady() {
212
+ return singletonModelManager.isInitialized();
213
+ }
214
+ /**
215
+ * Get model statistics
216
+ */
217
+ export function getModelStats() {
218
+ return singletonModelManager.getStats();
219
+ }
220
+ //# sourceMappingURL=SingletonModelManager.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Embeddings Module - Clean, Unified Architecture
3
+ *
4
+ * This module provides all embedding functionality for Brainy.
5
+ *
6
+ * Main Components:
7
+ * - EmbeddingManager: Core embedding generation with Q8/FP32 support
8
+ * - CachedEmbeddings: Performance optimization layer with pre-computed embeddings
9
+ */
10
+ export { EmbeddingManager, embeddingManager, embed, getEmbeddingFunction, getEmbeddingStats, type ModelPrecision } from './EmbeddingManager.js';
11
+ export { CachedEmbeddings, cachedEmbeddings } from './CachedEmbeddings.js';
12
+ export { embeddingManager as default } from './EmbeddingManager.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Embeddings Module - Clean, Unified Architecture
3
+ *
4
+ * This module provides all embedding functionality for Brainy.
5
+ *
6
+ * Main Components:
7
+ * - EmbeddingManager: Core embedding generation with Q8/FP32 support
8
+ * - CachedEmbeddings: Performance optimization layer with pre-computed embeddings
9
+ */
10
+ // Core embedding functionality
11
+ export { EmbeddingManager, embeddingManager, embed, getEmbeddingFunction, getEmbeddingStats } from './EmbeddingManager.js';
12
+ // Cached embeddings for performance
13
+ export { CachedEmbeddings, cachedEmbeddings } from './CachedEmbeddings.js';
14
+ // Default export is the singleton manager
15
+ export { embeddingManager as default } from './EmbeddingManager.js';
16
+ //# sourceMappingURL=index.js.map
@@ -8,7 +8,6 @@
8
8
  */
9
9
  import { Vector } from '../coreTypes.js';
10
10
  export declare class LightweightEmbedder {
11
- private onnxEmbedder;
12
11
  private stats;
13
12
  embed(text: string | string[]): Promise<Vector | Vector[]>;
14
13
  private embedSingle;
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * This reduces memory usage by 90% for typical queries
8
8
  */
9
+ import { singletonModelManager } from './SingletonModelManager.js';
9
10
  // Pre-computed embeddings for top 10,000 common terms
10
11
  // In production, this would be loaded from a file
11
12
  const PRECOMPUTED_EMBEDDINGS = {
@@ -59,7 +60,6 @@ function computeSimpleEmbedding(text) {
59
60
  }
60
61
  export class LightweightEmbedder {
61
62
  constructor() {
62
- this.onnxEmbedder = null;
63
63
  this.stats = {
64
64
  precomputedHits: 0,
65
65
  simpleComputes: 0,
@@ -92,18 +92,10 @@ export class LightweightEmbedder {
92
92
  this.stats.simpleComputes++;
93
93
  return computeSimpleEmbedding(normalized);
94
94
  }
95
- // 4. Last resort: Load ONNX model (only if really needed)
96
- if (!this.onnxEmbedder) {
97
- console.log('âš ī¸ Loading ONNX model for complex text...');
98
- const { TransformerEmbedding } = await import('../utils/embedding.js');
99
- this.onnxEmbedder = new TransformerEmbedding({
100
- precision: 'fp32',
101
- verbose: false
102
- });
103
- await this.onnxEmbedder.init();
104
- }
95
+ // 4. Last resort: Use SingletonModelManager for complex text
96
+ console.log('âš ī¸ Using singleton model for complex text...');
105
97
  this.stats.onnxComputes++;
106
- return await this.onnxEmbedder.embed(text);
98
+ return await singletonModelManager.embed(text);
107
99
  }
108
100
  getStats() {
109
101
  return {
@@ -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: 'fp32',
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
- try {
123
- // Strategy-specific cleanup
124
- switch (this.strategy) {
125
- case 'node-worker':
126
- if (this.embeddingFunction?.forceRestart) {
127
- await this.embeddingFunction.forceRestart();
128
- }
129
- break;
130
- case 'serverless-restart':
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
- if (this.embeddingFunction) {
186
- if (this.embeddingFunction.dispose) {
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: 'fp32',
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
- if (model) {
41
- // This doesn't fully free memory (known issue), but try anyway
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':