@ruvector/edge-net 0.1.7 → 0.2.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/onnx-worker.js ADDED
@@ -0,0 +1,482 @@
1
+ /**
2
+ * @ruvector/edge-net ONNX Worker Module
3
+ *
4
+ * Real semantic embeddings and LLM inference for workers
5
+ * Uses @xenova/transformers for actual AI inference
6
+ *
7
+ * @module @ruvector/edge-net/onnx-worker
8
+ */
9
+
10
+ import { EventEmitter } from 'events';
11
+ import { randomBytes } from 'crypto';
12
+
13
+ // ============================================
14
+ // ONNX EMBEDDER (REAL SEMANTIC EMBEDDINGS)
15
+ // ============================================
16
+
17
+ let transformers = null;
18
+ let embeddingPipeline = null;
19
+ let textGenPipeline = null;
20
+ let loadedEmbedModel = null;
21
+ let loadedGenModel = null;
22
+
23
+ /**
24
+ * Available embedding models (smallest first)
25
+ */
26
+ export const EMBEDDING_MODELS = {
27
+ 'minilm-l6': {
28
+ id: 'Xenova/all-MiniLM-L6-v2',
29
+ dimensions: 384,
30
+ size: '~22MB',
31
+ description: 'Fast, good quality embeddings',
32
+ },
33
+ 'minilm-l12': {
34
+ id: 'Xenova/all-MiniLM-L12-v2',
35
+ dimensions: 384,
36
+ size: '~33MB',
37
+ description: 'Better quality, slightly slower',
38
+ },
39
+ 'gte-small': {
40
+ id: 'Xenova/gte-small',
41
+ dimensions: 384,
42
+ size: '~67MB',
43
+ description: 'High quality embeddings',
44
+ },
45
+ 'bge-small': {
46
+ id: 'Xenova/bge-small-en-v1.5',
47
+ dimensions: 384,
48
+ size: '~33MB',
49
+ description: 'Best for retrieval tasks',
50
+ },
51
+ };
52
+
53
+ /**
54
+ * Available text generation models
55
+ */
56
+ export const GENERATION_MODELS = {
57
+ 'distilgpt2': {
58
+ id: 'Xenova/distilgpt2',
59
+ size: '~82MB',
60
+ description: 'Fast text generation',
61
+ },
62
+ 'gpt2': {
63
+ id: 'Xenova/gpt2',
64
+ size: '~250MB',
65
+ description: 'Classic GPT-2',
66
+ },
67
+ 'tinystories': {
68
+ id: 'Xenova/TinyStories-33M',
69
+ size: '~65MB',
70
+ description: 'Ultra-small for stories',
71
+ },
72
+ };
73
+
74
+ /**
75
+ * Initialize transformers.js
76
+ */
77
+ async function initTransformers() {
78
+ if (transformers) return transformers;
79
+
80
+ try {
81
+ transformers = await import('@xenova/transformers');
82
+
83
+ // Configure cache
84
+ const { env } = transformers;
85
+ env.cacheDir = process.env.ONNX_CACHE_DIR ||
86
+ (process.env.HOME ? `${process.env.HOME}/.ruvector/models/onnx` : '/tmp/.ruvector/models/onnx');
87
+ env.allowRemoteModels = true;
88
+ env.allowLocalModels = true;
89
+
90
+ return transformers;
91
+ } catch (error) {
92
+ console.error('[ONNX Worker] transformers.js not available:', error.message);
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Initialize embedding model
99
+ */
100
+ export async function initEmbedding(modelKey = 'minilm-l6') {
101
+ const tf = await initTransformers();
102
+ if (!tf) return false;
103
+
104
+ const model = EMBEDDING_MODELS[modelKey] || EMBEDDING_MODELS['minilm-l6'];
105
+
106
+ if (embeddingPipeline && loadedEmbedModel === model.id) {
107
+ return true;
108
+ }
109
+
110
+ try {
111
+ console.error(`[ONNX] Loading embedding model: ${model.id}...`);
112
+ const { pipeline } = tf;
113
+ embeddingPipeline = await pipeline('feature-extraction', model.id, {
114
+ quantized: true,
115
+ });
116
+ loadedEmbedModel = model.id;
117
+ console.error(`[ONNX] Embedding model ready: ${model.id}`);
118
+ return true;
119
+ } catch (error) {
120
+ console.error('[ONNX] Failed to load embedding model:', error.message);
121
+ return false;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Initialize text generation model
127
+ */
128
+ export async function initGeneration(modelKey = 'distilgpt2') {
129
+ const tf = await initTransformers();
130
+ if (!tf) return false;
131
+
132
+ const model = GENERATION_MODELS[modelKey] || GENERATION_MODELS['distilgpt2'];
133
+
134
+ if (textGenPipeline && loadedGenModel === model.id) {
135
+ return true;
136
+ }
137
+
138
+ try {
139
+ console.error(`[ONNX] Loading generation model: ${model.id}...`);
140
+ const { pipeline } = tf;
141
+ textGenPipeline = await pipeline('text-generation', model.id, {
142
+ quantized: true,
143
+ });
144
+ loadedGenModel = model.id;
145
+ console.error(`[ONNX] Generation model ready: ${model.id}`);
146
+ return true;
147
+ } catch (error) {
148
+ console.error('[ONNX] Failed to load generation model:', error.message);
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Generate real semantic embeddings
155
+ */
156
+ export async function embed(texts, options = {}) {
157
+ const initialized = await initEmbedding(options.model);
158
+
159
+ if (!initialized || !embeddingPipeline) {
160
+ // Fallback to hash-based embeddings
161
+ return fallbackEmbed(texts);
162
+ }
163
+
164
+ const inputTexts = Array.isArray(texts) ? texts : [texts];
165
+ const startTime = performance.now();
166
+
167
+ try {
168
+ const results = [];
169
+
170
+ for (const text of inputTexts) {
171
+ const output = await embeddingPipeline(text, {
172
+ pooling: 'mean',
173
+ normalize: true,
174
+ });
175
+
176
+ // Convert tensor to array
177
+ const embedding = Array.from(output.data);
178
+
179
+ results.push({
180
+ text: text.slice(0, 100),
181
+ embedding,
182
+ dimensions: embedding.length,
183
+ semantic: true,
184
+ });
185
+ }
186
+
187
+ const timeMs = performance.now() - startTime;
188
+
189
+ return {
190
+ embeddings: results,
191
+ model: loadedEmbedModel,
192
+ timeMs,
193
+ count: results.length,
194
+ semantic: true,
195
+ };
196
+ } catch (error) {
197
+ console.error('[ONNX] Embedding error:', error.message);
198
+ return fallbackEmbed(texts);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Fallback hash-based embeddings
204
+ */
205
+ function fallbackEmbed(texts) {
206
+ const inputTexts = Array.isArray(texts) ? texts : [texts];
207
+
208
+ const results = inputTexts.map(text => {
209
+ const hash = createHash('sha256').update(String(text)).digest();
210
+ const embedding = new Float32Array(384);
211
+ for (let i = 0; i < 384; i++) {
212
+ embedding[i] = (hash[i % 32] - 128) / 128;
213
+ }
214
+ return {
215
+ text: String(text).slice(0, 100),
216
+ embedding: Array.from(embedding),
217
+ dimensions: 384,
218
+ semantic: false,
219
+ };
220
+ });
221
+
222
+ return {
223
+ embeddings: results,
224
+ model: 'hash-fallback',
225
+ count: results.length,
226
+ semantic: false,
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Generate text using ONNX LLM
232
+ */
233
+ export async function generate(prompt, options = {}) {
234
+ const initialized = await initGeneration(options.model);
235
+
236
+ if (!initialized || !textGenPipeline) {
237
+ return {
238
+ text: `[Fallback] Processing: ${prompt.slice(0, 50)}...`,
239
+ model: 'fallback',
240
+ semantic: false,
241
+ };
242
+ }
243
+
244
+ const startTime = performance.now();
245
+
246
+ try {
247
+ const outputs = await textGenPipeline(prompt, {
248
+ max_new_tokens: options.maxTokens || 64,
249
+ temperature: options.temperature || 0.7,
250
+ top_p: options.topP || 0.9,
251
+ do_sample: true,
252
+ return_full_text: false,
253
+ });
254
+
255
+ const timeMs = performance.now() - startTime;
256
+ const generatedText = outputs[0]?.generated_text || '';
257
+
258
+ return {
259
+ text: generatedText.trim(),
260
+ model: loadedGenModel,
261
+ timeMs,
262
+ tokensPerSecond: Math.round((generatedText.split(/\s+/).length * 1.3) / (timeMs / 1000)),
263
+ semantic: true,
264
+ };
265
+ } catch (error) {
266
+ console.error('[ONNX] Generation error:', error.message);
267
+ return {
268
+ text: `[Error] ${error.message}`,
269
+ model: 'error',
270
+ semantic: false,
271
+ };
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Compute similarity between two texts
277
+ */
278
+ export async function similarity(text1, text2, options = {}) {
279
+ const result = await embed([text1, text2], options);
280
+
281
+ if (result.embeddings.length < 2) {
282
+ return { similarity: 0, semantic: false };
283
+ }
284
+
285
+ const e1 = result.embeddings[0].embedding;
286
+ const e2 = result.embeddings[1].embedding;
287
+
288
+ // Cosine similarity
289
+ let dotProduct = 0;
290
+ let norm1 = 0;
291
+ let norm2 = 0;
292
+
293
+ for (let i = 0; i < e1.length; i++) {
294
+ dotProduct += e1[i] * e2[i];
295
+ norm1 += e1[i] * e1[i];
296
+ norm2 += e2[i] * e2[i];
297
+ }
298
+
299
+ const cosineSim = dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
300
+
301
+ return {
302
+ similarity: cosineSim,
303
+ text1: text1.slice(0, 50),
304
+ text2: text2.slice(0, 50),
305
+ model: result.model,
306
+ semantic: result.semantic,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Semantic search - find most similar texts
312
+ */
313
+ export async function semanticSearch(query, documents, options = {}) {
314
+ const topK = options.topK || 5;
315
+
316
+ // Embed query and documents together
317
+ const allTexts = [query, ...documents];
318
+ const result = await embed(allTexts, options);
319
+
320
+ if (result.embeddings.length < 2) {
321
+ return { results: [], semantic: false };
322
+ }
323
+
324
+ const queryEmbed = result.embeddings[0].embedding;
325
+ const docEmbeds = result.embeddings.slice(1);
326
+
327
+ // Calculate similarities
328
+ const scores = docEmbeds.map((doc, index) => {
329
+ let dotProduct = 0;
330
+ let norm1 = 0;
331
+ let norm2 = 0;
332
+
333
+ for (let i = 0; i < queryEmbed.length; i++) {
334
+ dotProduct += queryEmbed[i] * doc.embedding[i];
335
+ norm1 += queryEmbed[i] * queryEmbed[i];
336
+ norm2 += doc.embedding[i] * doc.embedding[i];
337
+ }
338
+
339
+ return {
340
+ index,
341
+ text: documents[index],
342
+ score: dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)),
343
+ };
344
+ });
345
+
346
+ // Sort by score and return top K
347
+ scores.sort((a, b) => b.score - a.score);
348
+
349
+ return {
350
+ query,
351
+ results: scores.slice(0, topK),
352
+ model: result.model,
353
+ semantic: result.semantic,
354
+ };
355
+ }
356
+
357
+ // ============================================
358
+ // ONNX WORKER POOL
359
+ // ============================================
360
+
361
+ /**
362
+ * Enhanced worker pool with ONNX capabilities
363
+ */
364
+ export class OnnxWorkerPool extends EventEmitter {
365
+ constructor(options = {}) {
366
+ super();
367
+ this.id = `onnx-pool-${randomBytes(6).toString('hex')}`;
368
+ this.embedModel = options.embedModel || 'minilm-l6';
369
+ this.genModel = options.genModel || 'distilgpt2';
370
+ this.initialized = false;
371
+
372
+ this.stats = {
373
+ embeddings: 0,
374
+ generations: 0,
375
+ searches: 0,
376
+ totalTimeMs: 0,
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Initialize ONNX models
382
+ */
383
+ async initialize() {
384
+ this.emit('status', 'Initializing ONNX models...');
385
+
386
+ // Initialize embedding model
387
+ const embedReady = await initEmbedding(this.embedModel);
388
+
389
+ // Initialize generation model (optional)
390
+ const genReady = await initGeneration(this.genModel);
391
+
392
+ this.initialized = embedReady;
393
+
394
+ this.emit('ready', {
395
+ poolId: this.id,
396
+ embedding: embedReady,
397
+ generation: genReady,
398
+ });
399
+
400
+ return this;
401
+ }
402
+
403
+ /**
404
+ * Execute an ONNX task
405
+ */
406
+ async execute(type, data, options = {}) {
407
+ const startTime = performance.now();
408
+ let result;
409
+
410
+ switch (type) {
411
+ case 'embed':
412
+ result = await embed(data, options);
413
+ this.stats.embeddings++;
414
+ break;
415
+
416
+ case 'generate':
417
+ result = await generate(data, options);
418
+ this.stats.generations++;
419
+ break;
420
+
421
+ case 'similarity':
422
+ result = await similarity(data.text1, data.text2, options);
423
+ break;
424
+
425
+ case 'search':
426
+ result = await semanticSearch(data.query, data.documents, options);
427
+ this.stats.searches++;
428
+ break;
429
+
430
+ default:
431
+ throw new Error(`Unknown task type: ${type}`);
432
+ }
433
+
434
+ this.stats.totalTimeMs += performance.now() - startTime;
435
+
436
+ return result;
437
+ }
438
+
439
+ /**
440
+ * Batch embed documents
441
+ */
442
+ async embedBatch(texts, options = {}) {
443
+ return this.execute('embed', texts, options);
444
+ }
445
+
446
+ /**
447
+ * Semantic search
448
+ */
449
+ async search(query, documents, options = {}) {
450
+ return this.execute('search', { query, documents }, options);
451
+ }
452
+
453
+ /**
454
+ * Get pool status
455
+ */
456
+ getStatus() {
457
+ return {
458
+ poolId: this.id,
459
+ initialized: this.initialized,
460
+ embedModel: loadedEmbedModel,
461
+ genModel: loadedGenModel,
462
+ stats: this.stats,
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Shutdown pool
468
+ */
469
+ async shutdown() {
470
+ embeddingPipeline = null;
471
+ textGenPipeline = null;
472
+ loadedEmbedModel = null;
473
+ loadedGenModel = null;
474
+ this.initialized = false;
475
+ }
476
+ }
477
+
478
+ // ============================================
479
+ // EXPORTS
480
+ // ============================================
481
+
482
+ export default OnnxWorkerPool;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ruvector/edge-net",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "description": "Distributed compute intelligence network with AI agents and workers - contribute browser compute, spawn distributed AI agents, earn credits. Features Time Crystal coordination, Neural DAG attention, P2P swarm intelligence, and multi-agent workflows.",
5
+ "description": "Distributed compute intelligence network with AI agents and workers - contribute browser compute, spawn distributed AI agents, earn credits. Features Time Crystal coordination, Neural DAG attention, P2P swarm intelligence, ONNX inference, WebRTC signaling, CRDT ledger, and multi-agent workflows.",
6
6
  "main": "ruvector_edge_net.js",
7
7
  "module": "ruvector_edge_net.js",
8
8
  "types": "ruvector_edge_net.d.ts",
@@ -35,7 +35,15 @@
35
35
  "worker-pools",
36
36
  "multi-agent",
37
37
  "webrtc",
38
- "task-orchestration"
38
+ "task-orchestration",
39
+ "onnx",
40
+ "semantic-search",
41
+ "crdt",
42
+ "ledger",
43
+ "signaling",
44
+ "scheduler",
45
+ "monitoring",
46
+ "qdag"
39
47
  ],
40
48
  "author": "RuVector Team <team@ruvector.dev>",
41
49
  "license": "MIT",
@@ -65,6 +73,12 @@
65
73
  "real-workers.js",
66
74
  "real-workflows.js",
67
75
  "sync.js",
76
+ "onnx-worker.js",
77
+ "signaling.js",
78
+ "qdag.js",
79
+ "ledger.js",
80
+ "scheduler.js",
81
+ "monitor.js",
68
82
  "README.md",
69
83
  "LICENSE"
70
84
  ],
@@ -96,6 +110,24 @@
96
110
  },
97
111
  "./webrtc": {
98
112
  "import": "./webrtc.js"
113
+ },
114
+ "./onnx-worker": {
115
+ "import": "./onnx-worker.js"
116
+ },
117
+ "./signaling": {
118
+ "import": "./signaling.js"
119
+ },
120
+ "./qdag": {
121
+ "import": "./qdag.js"
122
+ },
123
+ "./ledger": {
124
+ "import": "./ledger.js"
125
+ },
126
+ "./scheduler": {
127
+ "import": "./scheduler.js"
128
+ },
129
+ "./monitor": {
130
+ "import": "./monitor.js"
99
131
  }
100
132
  },
101
133
  "sideEffects": [
@@ -113,10 +145,13 @@
113
145
  "join:multi": "node join.js --generate",
114
146
  "network": "node network.js stats",
115
147
  "peers": "node join.js --peers",
116
- "history": "node join.js --history"
148
+ "history": "node join.js --history",
149
+ "signaling": "node -e \"import('./signaling.js').then(m => new m.SignalingServer().start())\"",
150
+ "monitor": "node -e \"import('./monitor.js').then(m => { const mon = new m.Monitor(); mon.start(); setInterval(() => console.log(JSON.stringify(mon.generateReport(), null, 2)), 5000); })\""
117
151
  },
118
152
  "dependencies": {
119
153
  "@ruvector/ruvllm": "^0.2.3",
120
- "@xenova/transformers": "^2.17.2"
154
+ "@xenova/transformers": "^2.17.2",
155
+ "ws": "^8.18.3"
121
156
  }
122
157
  }