@sparkleideas/embeddings 3.0.0-alpha.12-patch.17 → 3.0.0-alpha.12-patch.19

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.
Files changed (45) hide show
  1. package/package.json +3 -3
  2. package/dist/__tests__/embedding-service.test.d.ts +0 -2
  3. package/dist/__tests__/embedding-service.test.d.ts.map +0 -1
  4. package/dist/__tests__/embedding-service.test.js +0 -98
  5. package/dist/__tests__/embedding-service.test.js.map +0 -1
  6. package/dist/chunking.d.ts +0 -68
  7. package/dist/chunking.d.ts.map +0 -1
  8. package/dist/chunking.js +0 -251
  9. package/dist/chunking.js.map +0 -1
  10. package/dist/embedding-service.d.ts +0 -207
  11. package/dist/embedding-service.d.ts.map +0 -1
  12. package/dist/embedding-service.js +0 -965
  13. package/dist/embedding-service.js.map +0 -1
  14. package/dist/hyperbolic.d.ts +0 -103
  15. package/dist/hyperbolic.d.ts.map +0 -1
  16. package/dist/hyperbolic.js +0 -343
  17. package/dist/hyperbolic.js.map +0 -1
  18. package/dist/index.d.ts +0 -31
  19. package/dist/index.d.ts.map +0 -1
  20. package/dist/index.js +0 -37
  21. package/dist/index.js.map +0 -1
  22. package/dist/neural-integration.d.ts +0 -203
  23. package/dist/neural-integration.d.ts.map +0 -1
  24. package/dist/neural-integration.js +0 -213
  25. package/dist/neural-integration.js.map +0 -1
  26. package/dist/normalization.d.ts +0 -73
  27. package/dist/normalization.d.ts.map +0 -1
  28. package/dist/normalization.js +0 -192
  29. package/dist/normalization.js.map +0 -1
  30. package/dist/persistent-cache.d.ts +0 -119
  31. package/dist/persistent-cache.d.ts.map +0 -1
  32. package/dist/persistent-cache.js +0 -337
  33. package/dist/persistent-cache.js.map +0 -1
  34. package/dist/rvf-embedding-cache.d.ts +0 -118
  35. package/dist/rvf-embedding-cache.d.ts.map +0 -1
  36. package/dist/rvf-embedding-cache.js +0 -458
  37. package/dist/rvf-embedding-cache.js.map +0 -1
  38. package/dist/rvf-embedding-service.d.ts +0 -79
  39. package/dist/rvf-embedding-service.d.ts.map +0 -1
  40. package/dist/rvf-embedding-service.js +0 -318
  41. package/dist/rvf-embedding-service.js.map +0 -1
  42. package/dist/types.d.ts +0 -237
  43. package/dist/types.d.ts.map +0 -1
  44. package/dist/types.js +0 -15
  45. package/dist/types.js.map +0 -1
@@ -1,965 +0,0 @@
1
- /**
2
- * V3 Embedding Service Implementation
3
- *
4
- * Production embedding service aligned with agentic-flow@alpha:
5
- * - OpenAI provider (text-embedding-3-small/large)
6
- * - Transformers.js provider (local ONNX models)
7
- * - Mock provider (development/testing)
8
- *
9
- * Performance Targets:
10
- * - Single embedding: <100ms (API), <50ms (local)
11
- * - Batch embedding: <500ms for 10 items
12
- * - Cache hit: <1ms
13
- */
14
- import { EventEmitter } from 'events';
15
- import { normalize } from './normalization.js';
16
- import { PersistentEmbeddingCache } from './persistent-cache.js';
17
- import { RvfEmbeddingService } from './rvf-embedding-service.js';
18
- // ============================================================================
19
- // LRU Cache Implementation
20
- // ============================================================================
21
- class LRUCache {
22
- maxSize;
23
- cache = new Map();
24
- hits = 0;
25
- misses = 0;
26
- constructor(maxSize) {
27
- this.maxSize = maxSize;
28
- }
29
- get(key) {
30
- const value = this.cache.get(key);
31
- if (value !== undefined) {
32
- // Move to end (most recently used)
33
- this.cache.delete(key);
34
- this.cache.set(key, value);
35
- this.hits++;
36
- return value;
37
- }
38
- this.misses++;
39
- return undefined;
40
- }
41
- set(key, value) {
42
- if (this.cache.has(key)) {
43
- this.cache.delete(key);
44
- }
45
- else if (this.cache.size >= this.maxSize) {
46
- // Remove oldest (first) entry
47
- const firstKey = this.cache.keys().next().value;
48
- if (firstKey !== undefined) {
49
- this.cache.delete(firstKey);
50
- }
51
- }
52
- this.cache.set(key, value);
53
- }
54
- clear() {
55
- this.cache.clear();
56
- this.hits = 0;
57
- this.misses = 0;
58
- }
59
- get size() {
60
- return this.cache.size;
61
- }
62
- get hitRate() {
63
- const total = this.hits + this.misses;
64
- return total > 0 ? this.hits / total : 0;
65
- }
66
- getStats() {
67
- return {
68
- size: this.cache.size,
69
- maxSize: this.maxSize,
70
- hits: this.hits,
71
- misses: this.misses,
72
- hitRate: this.hitRate,
73
- };
74
- }
75
- }
76
- // ============================================================================
77
- // Base Embedding Service
78
- // ============================================================================
79
- class BaseEmbeddingService extends EventEmitter {
80
- config;
81
- cache;
82
- persistentCache = null;
83
- embeddingListeners = new Set();
84
- normalizationType;
85
- constructor(config) {
86
- super();
87
- this.config = config;
88
- this.cache = new LRUCache(config.cacheSize ?? 1000);
89
- this.normalizationType = config.normalization ?? 'none';
90
- // Initialize persistent cache if configured
91
- if (config.persistentCache?.enabled) {
92
- const pcConfig = config.persistentCache;
93
- this.persistentCache = new PersistentEmbeddingCache({
94
- dbPath: pcConfig.dbPath ?? '.cache/embeddings.db',
95
- maxSize: pcConfig.maxSize ?? 10000,
96
- ttlMs: pcConfig.ttlMs,
97
- });
98
- }
99
- }
100
- /**
101
- * Apply normalization to embedding if configured
102
- */
103
- applyNormalization(embedding) {
104
- if (this.normalizationType === 'none') {
105
- return embedding;
106
- }
107
- return normalize(embedding, { type: this.normalizationType });
108
- }
109
- /**
110
- * Check persistent cache for embedding
111
- */
112
- async checkPersistentCache(text) {
113
- if (!this.persistentCache)
114
- return null;
115
- return this.persistentCache.get(text);
116
- }
117
- /**
118
- * Store embedding in persistent cache
119
- */
120
- async storePersistentCache(text, embedding) {
121
- if (!this.persistentCache)
122
- return;
123
- await this.persistentCache.set(text, embedding);
124
- }
125
- emitEvent(event) {
126
- for (const listener of this.embeddingListeners) {
127
- try {
128
- listener(event);
129
- }
130
- catch (error) {
131
- console.error('Error in embedding event listener:', error);
132
- }
133
- }
134
- this.emit(event.type, event);
135
- }
136
- addEventListener(listener) {
137
- this.embeddingListeners.add(listener);
138
- }
139
- removeEventListener(listener) {
140
- this.embeddingListeners.delete(listener);
141
- }
142
- clearCache() {
143
- const size = this.cache.size;
144
- this.cache.clear();
145
- this.emitEvent({ type: 'cache_eviction', size });
146
- }
147
- getCacheStats() {
148
- const stats = this.cache.getStats();
149
- return {
150
- size: stats.size,
151
- maxSize: stats.maxSize,
152
- hitRate: stats.hitRate,
153
- };
154
- }
155
- async shutdown() {
156
- this.clearCache();
157
- this.embeddingListeners.clear();
158
- }
159
- }
160
- // ============================================================================
161
- // OpenAI Embedding Service
162
- // ============================================================================
163
- export class OpenAIEmbeddingService extends BaseEmbeddingService {
164
- provider = 'openai';
165
- apiKey;
166
- model;
167
- baseURL;
168
- timeout;
169
- maxRetries;
170
- constructor(config) {
171
- super(config);
172
- this.apiKey = config.apiKey;
173
- this.model = config.model ?? 'text-embedding-3-small';
174
- this.baseURL = config.baseURL ?? 'https://api.openai.com/v1/embeddings';
175
- this.timeout = config.timeout ?? 30000;
176
- this.maxRetries = config.maxRetries ?? 3;
177
- }
178
- async embed(text) {
179
- // Check cache
180
- const cached = this.cache.get(text);
181
- if (cached) {
182
- this.emitEvent({ type: 'cache_hit', text });
183
- return {
184
- embedding: cached,
185
- latencyMs: 0,
186
- cached: true,
187
- };
188
- }
189
- this.emitEvent({ type: 'embed_start', text });
190
- const startTime = performance.now();
191
- try {
192
- const response = await this.callOpenAI([text]);
193
- const embedding = new Float32Array(response.data[0].embedding);
194
- // Cache result
195
- this.cache.set(text, embedding);
196
- const latencyMs = performance.now() - startTime;
197
- this.emitEvent({ type: 'embed_complete', text, latencyMs });
198
- return {
199
- embedding,
200
- latencyMs,
201
- usage: {
202
- promptTokens: response.usage?.prompt_tokens ?? 0,
203
- totalTokens: response.usage?.total_tokens ?? 0,
204
- },
205
- };
206
- }
207
- catch (error) {
208
- const message = error instanceof Error ? error.message : 'Unknown error';
209
- this.emitEvent({ type: 'embed_error', text, error: message });
210
- throw new Error(`OpenAI embedding failed: ${message}`);
211
- }
212
- }
213
- async embedBatch(texts) {
214
- this.emitEvent({ type: 'batch_start', count: texts.length });
215
- const startTime = performance.now();
216
- // Check cache for each text
217
- const cached = [];
218
- const uncached = [];
219
- texts.forEach((text, index) => {
220
- const cachedEmbedding = this.cache.get(text);
221
- if (cachedEmbedding) {
222
- cached.push({ index, embedding: cachedEmbedding });
223
- this.emitEvent({ type: 'cache_hit', text });
224
- }
225
- else {
226
- uncached.push({ index, text });
227
- }
228
- });
229
- // Fetch uncached embeddings
230
- let apiEmbeddings = [];
231
- let usage = { promptTokens: 0, totalTokens: 0 };
232
- if (uncached.length > 0) {
233
- const response = await this.callOpenAI(uncached.map(u => u.text));
234
- apiEmbeddings = response.data.map(d => new Float32Array(d.embedding));
235
- // Cache results
236
- uncached.forEach((item, i) => {
237
- this.cache.set(item.text, apiEmbeddings[i]);
238
- });
239
- usage = {
240
- promptTokens: response.usage?.prompt_tokens ?? 0,
241
- totalTokens: response.usage?.total_tokens ?? 0,
242
- };
243
- }
244
- // Reconstruct result array in original order
245
- const embeddings = new Array(texts.length);
246
- cached.forEach(c => {
247
- embeddings[c.index] = c.embedding;
248
- });
249
- uncached.forEach((u, i) => {
250
- embeddings[u.index] = apiEmbeddings[i];
251
- });
252
- const totalLatencyMs = performance.now() - startTime;
253
- this.emitEvent({ type: 'batch_complete', count: texts.length, latencyMs: totalLatencyMs });
254
- return {
255
- embeddings,
256
- totalLatencyMs,
257
- avgLatencyMs: totalLatencyMs / texts.length,
258
- usage,
259
- cacheStats: {
260
- hits: cached.length,
261
- misses: uncached.length,
262
- },
263
- };
264
- }
265
- async callOpenAI(texts) {
266
- const config = this.config;
267
- for (let attempt = 0; attempt < this.maxRetries; attempt++) {
268
- try {
269
- const controller = new AbortController();
270
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
271
- const response = await fetch(this.baseURL, {
272
- method: 'POST',
273
- headers: {
274
- 'Content-Type': 'application/json',
275
- Authorization: `Bearer ${this.apiKey}`,
276
- },
277
- body: JSON.stringify({
278
- model: this.model,
279
- input: texts,
280
- dimensions: config.dimensions,
281
- }),
282
- signal: controller.signal,
283
- });
284
- clearTimeout(timeoutId);
285
- if (!response.ok) {
286
- const error = await response.text();
287
- throw new Error(`OpenAI API error: ${response.status} - ${error}`);
288
- }
289
- return await response.json();
290
- }
291
- catch (error) {
292
- if (attempt === this.maxRetries - 1) {
293
- throw error;
294
- }
295
- // Exponential backoff
296
- await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100));
297
- }
298
- }
299
- throw new Error('Max retries exceeded');
300
- }
301
- }
302
- // ============================================================================
303
- // Transformers.js Embedding Service
304
- // ============================================================================
305
- export class TransformersEmbeddingService extends BaseEmbeddingService {
306
- provider = 'transformers';
307
- pipeline = null;
308
- modelName;
309
- initialized = false;
310
- constructor(config) {
311
- super(config);
312
- this.modelName = config.model ?? 'Xenova/all-MiniLM-L6-v2';
313
- }
314
- async initialize() {
315
- if (this.initialized)
316
- return;
317
- try {
318
- const { pipeline } = await import('@xenova/transformers');
319
- this.pipeline = await pipeline('feature-extraction', this.modelName);
320
- this.initialized = true;
321
- }
322
- catch (error) {
323
- throw new Error(`Failed to initialize transformers.js: ${error}`);
324
- }
325
- }
326
- async embed(text) {
327
- await this.initialize();
328
- // Check cache
329
- const cached = this.cache.get(text);
330
- if (cached) {
331
- this.emitEvent({ type: 'cache_hit', text });
332
- return {
333
- embedding: cached,
334
- latencyMs: 0,
335
- cached: true,
336
- };
337
- }
338
- this.emitEvent({ type: 'embed_start', text });
339
- const startTime = performance.now();
340
- try {
341
- const output = await this.pipeline(text, { pooling: 'mean', normalize: true });
342
- const embedding = new Float32Array(output.data);
343
- // Cache result
344
- this.cache.set(text, embedding);
345
- const latencyMs = performance.now() - startTime;
346
- this.emitEvent({ type: 'embed_complete', text, latencyMs });
347
- return {
348
- embedding,
349
- latencyMs,
350
- };
351
- }
352
- catch (error) {
353
- const message = error instanceof Error ? error.message : 'Unknown error';
354
- this.emitEvent({ type: 'embed_error', text, error: message });
355
- throw new Error(`Transformers.js embedding failed: ${message}`);
356
- }
357
- }
358
- async embedBatch(texts) {
359
- await this.initialize();
360
- this.emitEvent({ type: 'batch_start', count: texts.length });
361
- const startTime = performance.now();
362
- const embeddings = [];
363
- let cacheHits = 0;
364
- for (const text of texts) {
365
- const cached = this.cache.get(text);
366
- if (cached) {
367
- embeddings.push(cached);
368
- cacheHits++;
369
- this.emitEvent({ type: 'cache_hit', text });
370
- }
371
- else {
372
- const output = await this.pipeline(text, { pooling: 'mean', normalize: true });
373
- const embedding = new Float32Array(output.data);
374
- this.cache.set(text, embedding);
375
- embeddings.push(embedding);
376
- }
377
- }
378
- const totalLatencyMs = performance.now() - startTime;
379
- this.emitEvent({ type: 'batch_complete', count: texts.length, latencyMs: totalLatencyMs });
380
- return {
381
- embeddings,
382
- totalLatencyMs,
383
- avgLatencyMs: totalLatencyMs / texts.length,
384
- cacheStats: {
385
- hits: cacheHits,
386
- misses: texts.length - cacheHits,
387
- },
388
- };
389
- }
390
- }
391
- // ============================================================================
392
- // Mock Embedding Service
393
- // ============================================================================
394
- export class MockEmbeddingService extends BaseEmbeddingService {
395
- provider = 'mock';
396
- dimensions;
397
- simulatedLatency;
398
- constructor(config = {}) {
399
- const fullConfig = {
400
- provider: 'mock',
401
- dimensions: config.dimensions ?? 384,
402
- cacheSize: config.cacheSize ?? 1000,
403
- simulatedLatency: config.simulatedLatency ?? 0,
404
- enableCache: config.enableCache ?? true,
405
- };
406
- super(fullConfig);
407
- this.dimensions = fullConfig.dimensions;
408
- this.simulatedLatency = fullConfig.simulatedLatency;
409
- }
410
- async embed(text) {
411
- // Check cache
412
- const cached = this.cache.get(text);
413
- if (cached) {
414
- this.emitEvent({ type: 'cache_hit', text });
415
- return {
416
- embedding: cached,
417
- latencyMs: 0,
418
- cached: true,
419
- };
420
- }
421
- this.emitEvent({ type: 'embed_start', text });
422
- const startTime = performance.now();
423
- // Simulate latency
424
- if (this.simulatedLatency > 0) {
425
- await new Promise(resolve => setTimeout(resolve, this.simulatedLatency));
426
- }
427
- const embedding = this.hashEmbedding(text);
428
- this.cache.set(text, embedding);
429
- const latencyMs = performance.now() - startTime;
430
- this.emitEvent({ type: 'embed_complete', text, latencyMs });
431
- return {
432
- embedding,
433
- latencyMs,
434
- };
435
- }
436
- async embedBatch(texts) {
437
- this.emitEvent({ type: 'batch_start', count: texts.length });
438
- const startTime = performance.now();
439
- const embeddings = [];
440
- let cacheHits = 0;
441
- for (const text of texts) {
442
- const cached = this.cache.get(text);
443
- if (cached) {
444
- embeddings.push(cached);
445
- cacheHits++;
446
- }
447
- else {
448
- const embedding = this.hashEmbedding(text);
449
- this.cache.set(text, embedding);
450
- embeddings.push(embedding);
451
- }
452
- }
453
- const totalLatencyMs = performance.now() - startTime;
454
- this.emitEvent({ type: 'batch_complete', count: texts.length, latencyMs: totalLatencyMs });
455
- return {
456
- embeddings,
457
- totalLatencyMs,
458
- avgLatencyMs: totalLatencyMs / texts.length,
459
- cacheStats: {
460
- hits: cacheHits,
461
- misses: texts.length - cacheHits,
462
- },
463
- };
464
- }
465
- /**
466
- * Generate deterministic hash-based embedding
467
- */
468
- hashEmbedding(text) {
469
- const embedding = new Float32Array(this.dimensions);
470
- // Seed with text hash
471
- let hash = 0;
472
- for (let i = 0; i < text.length; i++) {
473
- hash = (hash << 5) - hash + text.charCodeAt(i);
474
- hash = hash & hash;
475
- }
476
- // Generate pseudo-random embedding
477
- for (let i = 0; i < this.dimensions; i++) {
478
- const seed = hash + i * 2654435761;
479
- const x = Math.sin(seed) * 10000;
480
- embedding[i] = x - Math.floor(x);
481
- }
482
- // Normalize to unit vector
483
- const norm = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0));
484
- for (let i = 0; i < this.dimensions; i++) {
485
- embedding[i] /= norm;
486
- }
487
- return embedding;
488
- }
489
- }
490
- // ============================================================================
491
- // Agentic-Flow Embedding Service
492
- // ============================================================================
493
- /**
494
- * Agentic-Flow embedding service using OptimizedEmbedder
495
- *
496
- * Features:
497
- * - ONNX-based embeddings with SIMD acceleration
498
- * - 256-entry LRU cache with FNV-1a hash
499
- * - 8x loop unrolling for cosine similarity
500
- * - Pre-allocated buffers (no GC pressure)
501
- * - 3-4x faster batch processing
502
- */
503
- export class AgenticFlowEmbeddingService extends BaseEmbeddingService {
504
- provider = 'agentic-flow';
505
- embedder = null;
506
- initialized = false;
507
- modelId;
508
- dimensions;
509
- embedderCacheSize;
510
- modelDir;
511
- autoDownload;
512
- constructor(config) {
513
- super(config);
514
- this.modelId = config.modelId ?? 'all-MiniLM-L6-v2';
515
- this.dimensions = config.dimensions ?? 384;
516
- this.embedderCacheSize = config.embedderCacheSize ?? 256;
517
- this.modelDir = config.modelDir;
518
- this.autoDownload = config.autoDownload ?? false;
519
- }
520
- async initialize() {
521
- if (this.initialized)
522
- return;
523
- let lastError;
524
- const createEmbedder = async (modulePath) => {
525
- try {
526
- // Use file:// protocol for absolute paths
527
- const importPath = modulePath.startsWith('/') ? `file://${modulePath}` : modulePath;
528
- const module = await import(/* webpackIgnore: true */ importPath);
529
- const getOptimizedEmbedder = module.getOptimizedEmbedder || module.default?.getOptimizedEmbedder;
530
- if (!getOptimizedEmbedder) {
531
- lastError = new Error(`Module loaded but getOptimizedEmbedder not found`);
532
- return false;
533
- }
534
- // Only include defined values to not override defaults
535
- const embedderConfig = {
536
- modelId: this.modelId,
537
- dimension: this.dimensions,
538
- cacheSize: this.embedderCacheSize,
539
- autoDownload: this.autoDownload,
540
- };
541
- if (this.modelDir !== undefined) {
542
- embedderConfig.modelDir = this.modelDir;
543
- }
544
- this.embedder = getOptimizedEmbedder(embedderConfig);
545
- await this.embedder.init();
546
- this.initialized = true;
547
- return true;
548
- }
549
- catch (error) {
550
- lastError = error instanceof Error ? error : new Error(String(error));
551
- return false;
552
- }
553
- };
554
- // Build list of possible module paths to try
555
- const possiblePaths = [];
556
- // Try proper package exports first (preferred)
557
- possiblePaths.push('agentic-flow/embeddings');
558
- // Try node_modules resolution from different locations (for file:// imports)
559
- try {
560
- const path = await import('path');
561
- const { existsSync } = await import('fs');
562
- const cwd = process.cwd();
563
- // Prioritize absolute paths that exist (for file:// import fallback)
564
- const absolutePaths = [
565
- path.join(cwd, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js'),
566
- path.join(cwd, '../node_modules/agentic-flow/dist/embeddings/optimized-embedder.js'),
567
- '/workspaces/claude-flow/node_modules/agentic-flow/dist/embeddings/optimized-embedder.js',
568
- ];
569
- for (const p of absolutePaths) {
570
- if (existsSync(p)) {
571
- possiblePaths.push(p);
572
- }
573
- }
574
- }
575
- catch {
576
- // fs/path module not available
577
- }
578
- // Try each path
579
- for (const modulePath of possiblePaths) {
580
- if (await createEmbedder(modulePath)) {
581
- return;
582
- }
583
- }
584
- const errorDetail = lastError?.message ? ` Last error: ${lastError.message}` : '';
585
- throw new Error(`Failed to initialize agentic-flow embeddings.${errorDetail} ` +
586
- `Ensure agentic-flow is installed and ONNX model is downloaded: ` +
587
- `npx agentic-flow@alpha embeddings init`);
588
- }
589
- async embed(text) {
590
- await this.initialize();
591
- // Check our LRU cache first
592
- const cached = this.cache.get(text);
593
- if (cached) {
594
- this.emitEvent({ type: 'cache_hit', text });
595
- return {
596
- embedding: cached,
597
- latencyMs: 0,
598
- cached: true,
599
- };
600
- }
601
- this.emitEvent({ type: 'embed_start', text });
602
- const startTime = performance.now();
603
- try {
604
- // Use agentic-flow's optimized embedder (has its own internal cache)
605
- const embedding = await this.embedder.embed(text);
606
- // Store in our cache as well
607
- this.cache.set(text, embedding);
608
- const latencyMs = performance.now() - startTime;
609
- this.emitEvent({ type: 'embed_complete', text, latencyMs });
610
- return {
611
- embedding,
612
- latencyMs,
613
- };
614
- }
615
- catch (error) {
616
- const message = error instanceof Error ? error.message : 'Unknown error';
617
- this.emitEvent({ type: 'embed_error', text, error: message });
618
- throw new Error(`Agentic-flow embedding failed: ${message}`);
619
- }
620
- }
621
- async embedBatch(texts) {
622
- await this.initialize();
623
- this.emitEvent({ type: 'batch_start', count: texts.length });
624
- const startTime = performance.now();
625
- // Check cache for each text
626
- const cached = [];
627
- const uncached = [];
628
- texts.forEach((text, index) => {
629
- const cachedEmbedding = this.cache.get(text);
630
- if (cachedEmbedding) {
631
- cached.push({ index, embedding: cachedEmbedding });
632
- this.emitEvent({ type: 'cache_hit', text });
633
- }
634
- else {
635
- uncached.push({ index, text });
636
- }
637
- });
638
- // Use optimized batch embedding for uncached texts
639
- let batchEmbeddings = [];
640
- if (uncached.length > 0) {
641
- const uncachedTexts = uncached.map(u => u.text);
642
- batchEmbeddings = await this.embedder.embedBatch(uncachedTexts);
643
- // Cache results
644
- uncached.forEach((item, i) => {
645
- this.cache.set(item.text, batchEmbeddings[i]);
646
- });
647
- }
648
- // Reconstruct result array in original order
649
- const embeddings = new Array(texts.length);
650
- cached.forEach(c => {
651
- embeddings[c.index] = c.embedding;
652
- });
653
- uncached.forEach((u, i) => {
654
- embeddings[u.index] = batchEmbeddings[i];
655
- });
656
- const totalLatencyMs = performance.now() - startTime;
657
- this.emitEvent({ type: 'batch_complete', count: texts.length, latencyMs: totalLatencyMs });
658
- return {
659
- embeddings,
660
- totalLatencyMs,
661
- avgLatencyMs: totalLatencyMs / texts.length,
662
- cacheStats: {
663
- hits: cached.length,
664
- misses: uncached.length,
665
- },
666
- };
667
- }
668
- /**
669
- * Get combined cache statistics from both our LRU cache and embedder's internal cache
670
- */
671
- getCacheStats() {
672
- const baseStats = super.getCacheStats();
673
- if (this.embedder && this.embedder.getCacheStats) {
674
- const embedderStats = this.embedder.getCacheStats();
675
- return {
676
- size: baseStats.size + embedderStats.size,
677
- maxSize: baseStats.maxSize + embedderStats.maxSize,
678
- hitRate: baseStats.hitRate,
679
- embedderCache: embedderStats,
680
- };
681
- }
682
- return baseStats;
683
- }
684
- async shutdown() {
685
- if (this.embedder && this.embedder.clearCache) {
686
- this.embedder.clearCache();
687
- }
688
- await super.shutdown();
689
- }
690
- }
691
- // ============================================================================
692
- // Factory Functions
693
- // ============================================================================
694
- /**
695
- * Check if agentic-flow is available
696
- */
697
- async function isAgenticFlowAvailable() {
698
- try {
699
- await import('agentic-flow/embeddings');
700
- return true;
701
- }
702
- catch {
703
- return false;
704
- }
705
- }
706
- /**
707
- * Auto-install agentic-flow and initialize model
708
- */
709
- async function autoInstallAgenticFlow() {
710
- const { exec } = await import('child_process');
711
- const { promisify } = await import('util');
712
- const execAsync = promisify(exec);
713
- try {
714
- // Check if already available
715
- if (await isAgenticFlowAvailable()) {
716
- return true;
717
- }
718
- console.log('[embeddings] Installing agentic-flow@alpha...');
719
- await execAsync('npm install agentic-flow@alpha --save', { timeout: 120000 });
720
- // Initialize the model
721
- console.log('[embeddings] Downloading embedding model...');
722
- await execAsync('npx agentic-flow@alpha embeddings init', { timeout: 300000 });
723
- // Verify installation
724
- return await isAgenticFlowAvailable();
725
- }
726
- catch (error) {
727
- console.warn('[embeddings] Auto-install failed:', error instanceof Error ? error.message : error);
728
- return false;
729
- }
730
- }
731
- /**
732
- * Create embedding service based on configuration (sync version)
733
- * Note: For 'auto' provider or smart fallback, use createEmbeddingServiceAsync
734
- */
735
- export function createEmbeddingService(config) {
736
- switch (config.provider) {
737
- case 'openai':
738
- return new OpenAIEmbeddingService(config);
739
- case 'transformers':
740
- return new TransformersEmbeddingService(config);
741
- case 'mock':
742
- return new MockEmbeddingService(config);
743
- case 'agentic-flow':
744
- return new AgenticFlowEmbeddingService(config);
745
- case 'rvf':
746
- return new RvfEmbeddingService(config);
747
- default:
748
- console.warn(`Unknown provider, using mock`);
749
- return new MockEmbeddingService({ provider: 'mock', dimensions: 384 });
750
- }
751
- }
752
- /**
753
- * Create embedding service with automatic provider detection and fallback
754
- *
755
- * Features:
756
- * - 'auto' provider picks best available: agentic-flow > transformers > mock
757
- * - Automatic fallback if primary provider fails to initialize
758
- * - Pre-validates provider availability before returning
759
- *
760
- * @example
761
- * // Auto-select best provider
762
- * const service = await createEmbeddingServiceAsync({ provider: 'auto' });
763
- *
764
- * // Try agentic-flow, fallback to transformers
765
- * const service = await createEmbeddingServiceAsync({
766
- * provider: 'agentic-flow',
767
- * fallback: 'transformers'
768
- * });
769
- */
770
- export async function createEmbeddingServiceAsync(config) {
771
- const { provider, fallback, autoInstall = true, ...rest } = config;
772
- // Auto provider selection
773
- if (provider === 'auto') {
774
- // Try RVF first (52KB, always available, fast hash embeddings)
775
- try {
776
- const service = new RvfEmbeddingService({
777
- provider: 'rvf',
778
- dimensions: rest.dimensions ?? 384,
779
- cacheSize: rest.cacheSize,
780
- });
781
- await service.embed('test');
782
- return service;
783
- }
784
- catch { /* fall through */ }
785
- // Try agentic-flow (fastest neural, ONNX-based)
786
- let agenticFlowAvailable = await isAgenticFlowAvailable();
787
- // Auto-install if not available and autoInstall is enabled
788
- if (!agenticFlowAvailable && autoInstall) {
789
- agenticFlowAvailable = await autoInstallAgenticFlow();
790
- }
791
- if (agenticFlowAvailable) {
792
- try {
793
- const service = new AgenticFlowEmbeddingService({
794
- provider: 'agentic-flow',
795
- modelId: rest.modelId ?? 'all-MiniLM-L6-v2',
796
- dimensions: rest.dimensions ?? 384,
797
- cacheSize: rest.cacheSize,
798
- });
799
- // Validate it can initialize
800
- await service.embed('test');
801
- return service;
802
- }
803
- catch {
804
- // Fall through to next option
805
- }
806
- }
807
- // Try transformers (good quality, built-in)
808
- try {
809
- const service = new TransformersEmbeddingService({
810
- provider: 'transformers',
811
- model: rest.model ?? 'Xenova/all-MiniLM-L6-v2',
812
- cacheSize: rest.cacheSize,
813
- });
814
- // Validate it can initialize
815
- await service.embed('test');
816
- return service;
817
- }
818
- catch {
819
- // Fall through to mock
820
- }
821
- // Fallback to mock (always works)
822
- console.warn('[embeddings] Using mock provider - install agentic-flow or @xenova/transformers for real embeddings');
823
- return new MockEmbeddingService({
824
- dimensions: rest.dimensions ?? 384,
825
- cacheSize: rest.cacheSize,
826
- });
827
- }
828
- // Specific provider with optional fallback
829
- const createPrimary = () => {
830
- switch (provider) {
831
- case 'agentic-flow':
832
- return new AgenticFlowEmbeddingService({
833
- provider: 'agentic-flow',
834
- modelId: rest.modelId ?? 'all-MiniLM-L6-v2',
835
- dimensions: rest.dimensions ?? 384,
836
- cacheSize: rest.cacheSize,
837
- });
838
- case 'transformers':
839
- return new TransformersEmbeddingService({
840
- provider: 'transformers',
841
- model: rest.model ?? 'Xenova/all-MiniLM-L6-v2',
842
- cacheSize: rest.cacheSize,
843
- });
844
- case 'openai':
845
- if (!rest.apiKey)
846
- throw new Error('OpenAI provider requires apiKey');
847
- return new OpenAIEmbeddingService({
848
- provider: 'openai',
849
- apiKey: rest.apiKey,
850
- dimensions: rest.dimensions,
851
- cacheSize: rest.cacheSize,
852
- });
853
- case 'rvf':
854
- return new RvfEmbeddingService({
855
- provider: 'rvf',
856
- dimensions: rest.dimensions ?? 384,
857
- cacheSize: rest.cacheSize,
858
- });
859
- case 'mock':
860
- return new MockEmbeddingService({
861
- dimensions: rest.dimensions ?? 384,
862
- cacheSize: rest.cacheSize,
863
- });
864
- default:
865
- throw new Error(`Unknown provider: ${provider}`);
866
- }
867
- };
868
- const primary = createPrimary();
869
- // Try to validate primary provider
870
- try {
871
- await primary.embed('test');
872
- return primary;
873
- }
874
- catch (error) {
875
- if (!fallback) {
876
- throw error;
877
- }
878
- // Try fallback
879
- console.warn(`[embeddings] Primary provider '${provider}' failed, using fallback '${fallback}'`);
880
- const fallbackConfig = { ...rest, provider: fallback };
881
- return createEmbeddingServiceAsync(fallbackConfig);
882
- }
883
- }
884
- /**
885
- * Convenience function for quick embeddings
886
- */
887
- export async function getEmbedding(text, config) {
888
- const service = createEmbeddingService({
889
- provider: 'mock',
890
- dimensions: 384,
891
- ...config,
892
- });
893
- try {
894
- const result = await service.embed(text);
895
- return result.embedding;
896
- }
897
- finally {
898
- await service.shutdown();
899
- }
900
- }
901
- // ============================================================================
902
- // Similarity Functions
903
- // ============================================================================
904
- /**
905
- * Compute cosine similarity between two embeddings
906
- */
907
- export function cosineSimilarity(a, b) {
908
- if (a.length !== b.length) {
909
- throw new Error('Embedding dimensions must match');
910
- }
911
- let dot = 0;
912
- let normA = 0;
913
- let normB = 0;
914
- for (let i = 0; i < a.length; i++) {
915
- dot += a[i] * b[i];
916
- normA += a[i] * a[i];
917
- normB += b[i] * b[i];
918
- }
919
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
920
- return denom > 0 ? dot / denom : 0;
921
- }
922
- /**
923
- * Compute Euclidean distance between two embeddings
924
- */
925
- export function euclideanDistance(a, b) {
926
- if (a.length !== b.length) {
927
- throw new Error('Embedding dimensions must match');
928
- }
929
- let sum = 0;
930
- for (let i = 0; i < a.length; i++) {
931
- const diff = a[i] - b[i];
932
- sum += diff * diff;
933
- }
934
- return Math.sqrt(sum);
935
- }
936
- /**
937
- * Compute dot product between two embeddings
938
- */
939
- export function dotProduct(a, b) {
940
- if (a.length !== b.length) {
941
- throw new Error('Embedding dimensions must match');
942
- }
943
- let dot = 0;
944
- for (let i = 0; i < a.length; i++) {
945
- dot += a[i] * b[i];
946
- }
947
- return dot;
948
- }
949
- /**
950
- * Compute similarity using specified metric
951
- */
952
- export function computeSimilarity(a, b, metric = 'cosine') {
953
- switch (metric) {
954
- case 'cosine':
955
- return { score: cosineSimilarity(a, b), metric };
956
- case 'euclidean':
957
- // Convert distance to similarity (closer = higher score)
958
- return { score: 1 / (1 + euclideanDistance(a, b)), metric };
959
- case 'dot':
960
- return { score: dotProduct(a, b), metric };
961
- default:
962
- return { score: cosineSimilarity(a, b), metric: 'cosine' };
963
- }
964
- }
965
- //# sourceMappingURL=embedding-service.js.map