@sparkleideas/embeddings 3.0.0-alpha.17 → 3.0.0-alpha.27
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/README.md +308 -17
- package/package.json +19 -7
- package/src/chunking.ts +351 -0
- package/src/embedding-service.ts +477 -5
- package/src/hyperbolic.ts +458 -0
- package/src/index.ts +77 -0
- package/src/neural-integration.ts +295 -0
- package/src/normalization.ts +267 -0
- package/src/persistent-cache.ts +410 -0
- package/src/types.ts +61 -2
- package/dist/__tests__/embedding-service.test.d.ts +0 -2
- package/dist/__tests__/embedding-service.test.d.ts.map +0 -1
- package/dist/__tests__/embedding-service.test.js +0 -98
- package/dist/__tests__/embedding-service.test.js.map +0 -1
- package/dist/embedding-service.d.ts +0 -113
- package/dist/embedding-service.d.ts.map +0 -1
- package/dist/embedding-service.js +0 -543
- package/dist/embedding-service.js.map +0 -1
- package/dist/index.d.ts +0 -15
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -15
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts +0 -178
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -15
- package/dist/types.js.map +0 -1
package/src/embedding-service.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
OpenAIEmbeddingConfig,
|
|
20
20
|
TransformersEmbeddingConfig,
|
|
21
21
|
MockEmbeddingConfig,
|
|
22
|
+
AgenticFlowEmbeddingConfig,
|
|
22
23
|
EmbeddingResult,
|
|
23
24
|
BatchEmbeddingResult,
|
|
24
25
|
IEmbeddingService,
|
|
@@ -26,7 +27,11 @@ import type {
|
|
|
26
27
|
EmbeddingEventListener,
|
|
27
28
|
SimilarityMetric,
|
|
28
29
|
SimilarityResult,
|
|
30
|
+
NormalizationType,
|
|
31
|
+
PersistentCacheConfig,
|
|
29
32
|
} from './types.js';
|
|
33
|
+
import { normalize } from './normalization.js';
|
|
34
|
+
import { PersistentEmbeddingCache } from './persistent-cache.js';
|
|
30
35
|
|
|
31
36
|
// ============================================================================
|
|
32
37
|
// LRU Cache Implementation
|
|
@@ -98,16 +103,55 @@ class LRUCache<K, V> {
|
|
|
98
103
|
abstract class BaseEmbeddingService extends EventEmitter implements IEmbeddingService {
|
|
99
104
|
abstract readonly provider: EmbeddingProvider;
|
|
100
105
|
protected cache: LRUCache<string, Float32Array>;
|
|
106
|
+
protected persistentCache: PersistentEmbeddingCache | null = null;
|
|
101
107
|
protected embeddingListeners: Set<EmbeddingEventListener> = new Set();
|
|
108
|
+
protected normalizationType: NormalizationType;
|
|
102
109
|
|
|
103
110
|
constructor(protected readonly config: EmbeddingConfig) {
|
|
104
111
|
super();
|
|
105
112
|
this.cache = new LRUCache(config.cacheSize ?? 1000);
|
|
113
|
+
this.normalizationType = config.normalization ?? 'none';
|
|
114
|
+
|
|
115
|
+
// Initialize persistent cache if configured
|
|
116
|
+
if (config.persistentCache?.enabled) {
|
|
117
|
+
const pcConfig: PersistentCacheConfig = config.persistentCache;
|
|
118
|
+
this.persistentCache = new PersistentEmbeddingCache({
|
|
119
|
+
dbPath: pcConfig.dbPath ?? '.cache/embeddings.db',
|
|
120
|
+
maxSize: pcConfig.maxSize ?? 10000,
|
|
121
|
+
ttlMs: pcConfig.ttlMs,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
abstract embed(text: string): Promise<EmbeddingResult>;
|
|
109
127
|
abstract embedBatch(texts: string[]): Promise<BatchEmbeddingResult>;
|
|
110
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Apply normalization to embedding if configured
|
|
131
|
+
*/
|
|
132
|
+
protected applyNormalization(embedding: Float32Array): Float32Array {
|
|
133
|
+
if (this.normalizationType === 'none') {
|
|
134
|
+
return embedding;
|
|
135
|
+
}
|
|
136
|
+
return normalize(embedding, { type: this.normalizationType });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check persistent cache for embedding
|
|
141
|
+
*/
|
|
142
|
+
protected async checkPersistentCache(text: string): Promise<Float32Array | null> {
|
|
143
|
+
if (!this.persistentCache) return null;
|
|
144
|
+
return this.persistentCache.get(text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Store embedding in persistent cache
|
|
149
|
+
*/
|
|
150
|
+
protected async storePersistentCache(text: string, embedding: Float32Array): Promise<void> {
|
|
151
|
+
if (!this.persistentCache) return;
|
|
152
|
+
await this.persistentCache.set(text, embedding);
|
|
153
|
+
}
|
|
154
|
+
|
|
111
155
|
protected emitEvent(event: EmbeddingEvent): void {
|
|
112
156
|
for (const listener of this.embeddingListeners) {
|
|
113
157
|
try {
|
|
@@ -431,10 +475,17 @@ export class MockEmbeddingService extends BaseEmbeddingService {
|
|
|
431
475
|
private readonly dimensions: number;
|
|
432
476
|
private readonly simulatedLatency: number;
|
|
433
477
|
|
|
434
|
-
constructor(config: MockEmbeddingConfig) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
478
|
+
constructor(config: Partial<MockEmbeddingConfig> = {}) {
|
|
479
|
+
const fullConfig: MockEmbeddingConfig = {
|
|
480
|
+
provider: 'mock',
|
|
481
|
+
dimensions: config.dimensions ?? 384,
|
|
482
|
+
cacheSize: config.cacheSize ?? 1000,
|
|
483
|
+
simulatedLatency: config.simulatedLatency ?? 0,
|
|
484
|
+
enableCache: config.enableCache ?? true,
|
|
485
|
+
};
|
|
486
|
+
super(fullConfig);
|
|
487
|
+
this.dimensions = fullConfig.dimensions!;
|
|
488
|
+
this.simulatedLatency = fullConfig.simulatedLatency!;
|
|
438
489
|
}
|
|
439
490
|
|
|
440
491
|
async embed(text: string): Promise<EmbeddingResult> {
|
|
@@ -532,12 +583,286 @@ export class MockEmbeddingService extends BaseEmbeddingService {
|
|
|
532
583
|
}
|
|
533
584
|
}
|
|
534
585
|
|
|
586
|
+
// ============================================================================
|
|
587
|
+
// Agentic-Flow Embedding Service
|
|
588
|
+
// ============================================================================
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Agentic-Flow embedding service using OptimizedEmbedder
|
|
592
|
+
*
|
|
593
|
+
* Features:
|
|
594
|
+
* - ONNX-based embeddings with SIMD acceleration
|
|
595
|
+
* - 256-entry LRU cache with FNV-1a hash
|
|
596
|
+
* - 8x loop unrolling for cosine similarity
|
|
597
|
+
* - Pre-allocated buffers (no GC pressure)
|
|
598
|
+
* - 3-4x faster batch processing
|
|
599
|
+
*/
|
|
600
|
+
export class AgenticFlowEmbeddingService extends BaseEmbeddingService {
|
|
601
|
+
readonly provider: EmbeddingProvider = 'agentic-flow';
|
|
602
|
+
private embedder: any = null;
|
|
603
|
+
private initialized = false;
|
|
604
|
+
private readonly modelId: string;
|
|
605
|
+
private readonly dimensions: number;
|
|
606
|
+
private readonly embedderCacheSize: number;
|
|
607
|
+
private readonly modelDir: string | undefined;
|
|
608
|
+
private readonly autoDownload: boolean;
|
|
609
|
+
|
|
610
|
+
constructor(config: AgenticFlowEmbeddingConfig) {
|
|
611
|
+
super(config);
|
|
612
|
+
this.modelId = config.modelId ?? 'all-MiniLM-L6-v2';
|
|
613
|
+
this.dimensions = config.dimensions ?? 384;
|
|
614
|
+
this.embedderCacheSize = config.embedderCacheSize ?? 256;
|
|
615
|
+
this.modelDir = config.modelDir;
|
|
616
|
+
this.autoDownload = config.autoDownload ?? false;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private async initialize(): Promise<void> {
|
|
620
|
+
if (this.initialized) return;
|
|
621
|
+
|
|
622
|
+
let lastError: Error | undefined;
|
|
623
|
+
|
|
624
|
+
const createEmbedder = async (modulePath: string): Promise<boolean> => {
|
|
625
|
+
try {
|
|
626
|
+
// Use file:// protocol for absolute paths
|
|
627
|
+
const importPath = modulePath.startsWith('/') ? `file://${modulePath}` : modulePath;
|
|
628
|
+
const module = await import(/* webpackIgnore: true */ importPath);
|
|
629
|
+
const getOptimizedEmbedder = module.getOptimizedEmbedder || module.default?.getOptimizedEmbedder;
|
|
630
|
+
if (!getOptimizedEmbedder) {
|
|
631
|
+
lastError = new Error(`Module loaded but getOptimizedEmbedder not found`);
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Only include defined values to not override defaults
|
|
636
|
+
const embedderConfig: Record<string, unknown> = {
|
|
637
|
+
modelId: this.modelId,
|
|
638
|
+
dimension: this.dimensions,
|
|
639
|
+
cacheSize: this.embedderCacheSize,
|
|
640
|
+
autoDownload: this.autoDownload,
|
|
641
|
+
};
|
|
642
|
+
if (this.modelDir !== undefined) {
|
|
643
|
+
embedderConfig.modelDir = this.modelDir;
|
|
644
|
+
}
|
|
645
|
+
this.embedder = getOptimizedEmbedder(embedderConfig);
|
|
646
|
+
await this.embedder.init();
|
|
647
|
+
this.initialized = true;
|
|
648
|
+
return true;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Build list of possible module paths to try
|
|
656
|
+
const possiblePaths: string[] = [];
|
|
657
|
+
|
|
658
|
+
// Try proper package exports first (preferred)
|
|
659
|
+
possiblePaths.push('agentic-flow/embeddings');
|
|
660
|
+
|
|
661
|
+
// Try node_modules resolution from different locations (for file:// imports)
|
|
662
|
+
try {
|
|
663
|
+
const path = await import('path');
|
|
664
|
+
const { existsSync } = await import('fs');
|
|
665
|
+
const cwd = process.cwd();
|
|
666
|
+
|
|
667
|
+
// Prioritize absolute paths that exist (for file:// import fallback)
|
|
668
|
+
const absolutePaths = [
|
|
669
|
+
path.join(cwd, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js'),
|
|
670
|
+
path.join(cwd, '../node_modules/agentic-flow/dist/embeddings/optimized-embedder.js'),
|
|
671
|
+
'/workspaces/claude-flow/node_modules/agentic-flow/dist/embeddings/optimized-embedder.js',
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
for (const p of absolutePaths) {
|
|
675
|
+
if (existsSync(p)) {
|
|
676
|
+
possiblePaths.push(p);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
// fs/path module not available
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Try each path
|
|
684
|
+
for (const modulePath of possiblePaths) {
|
|
685
|
+
if (await createEmbedder(modulePath)) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const errorDetail = lastError?.message ? ` Last error: ${lastError.message}` : '';
|
|
691
|
+
throw new Error(
|
|
692
|
+
`Failed to initialize agentic-flow embeddings.${errorDetail} ` +
|
|
693
|
+
`Ensure agentic-flow is installed and ONNX model is downloaded: ` +
|
|
694
|
+
`npx agentic-flow@alpha embeddings init`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async embed(text: string): Promise<EmbeddingResult> {
|
|
699
|
+
await this.initialize();
|
|
700
|
+
|
|
701
|
+
// Check our LRU cache first
|
|
702
|
+
const cached = this.cache.get(text);
|
|
703
|
+
if (cached) {
|
|
704
|
+
this.emitEvent({ type: 'cache_hit', text });
|
|
705
|
+
return {
|
|
706
|
+
embedding: cached,
|
|
707
|
+
latencyMs: 0,
|
|
708
|
+
cached: true,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.emitEvent({ type: 'embed_start', text });
|
|
713
|
+
const startTime = performance.now();
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
// Use agentic-flow's optimized embedder (has its own internal cache)
|
|
717
|
+
const embedding = await this.embedder.embed(text);
|
|
718
|
+
|
|
719
|
+
// Store in our cache as well
|
|
720
|
+
this.cache.set(text, embedding);
|
|
721
|
+
|
|
722
|
+
const latencyMs = performance.now() - startTime;
|
|
723
|
+
this.emitEvent({ type: 'embed_complete', text, latencyMs });
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
embedding,
|
|
727
|
+
latencyMs,
|
|
728
|
+
};
|
|
729
|
+
} catch (error) {
|
|
730
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
731
|
+
this.emitEvent({ type: 'embed_error', text, error: message });
|
|
732
|
+
throw new Error(`Agentic-flow embedding failed: ${message}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async embedBatch(texts: string[]): Promise<BatchEmbeddingResult> {
|
|
737
|
+
await this.initialize();
|
|
738
|
+
|
|
739
|
+
this.emitEvent({ type: 'batch_start', count: texts.length });
|
|
740
|
+
const startTime = performance.now();
|
|
741
|
+
|
|
742
|
+
// Check cache for each text
|
|
743
|
+
const cached: Array<{ index: number; embedding: Float32Array }> = [];
|
|
744
|
+
const uncached: Array<{ index: number; text: string }> = [];
|
|
745
|
+
|
|
746
|
+
texts.forEach((text, index) => {
|
|
747
|
+
const cachedEmbedding = this.cache.get(text);
|
|
748
|
+
if (cachedEmbedding) {
|
|
749
|
+
cached.push({ index, embedding: cachedEmbedding });
|
|
750
|
+
this.emitEvent({ type: 'cache_hit', text });
|
|
751
|
+
} else {
|
|
752
|
+
uncached.push({ index, text });
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Use optimized batch embedding for uncached texts
|
|
757
|
+
let batchEmbeddings: Float32Array[] = [];
|
|
758
|
+
if (uncached.length > 0) {
|
|
759
|
+
const uncachedTexts = uncached.map(u => u.text);
|
|
760
|
+
batchEmbeddings = await this.embedder.embedBatch(uncachedTexts);
|
|
761
|
+
|
|
762
|
+
// Cache results
|
|
763
|
+
uncached.forEach((item, i) => {
|
|
764
|
+
this.cache.set(item.text, batchEmbeddings[i]);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Reconstruct result array in original order
|
|
769
|
+
const embeddings: Float32Array[] = new Array(texts.length);
|
|
770
|
+
cached.forEach(c => {
|
|
771
|
+
embeddings[c.index] = c.embedding;
|
|
772
|
+
});
|
|
773
|
+
uncached.forEach((u, i) => {
|
|
774
|
+
embeddings[u.index] = batchEmbeddings[i];
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
const totalLatencyMs = performance.now() - startTime;
|
|
778
|
+
this.emitEvent({ type: 'batch_complete', count: texts.length, latencyMs: totalLatencyMs });
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
embeddings,
|
|
782
|
+
totalLatencyMs,
|
|
783
|
+
avgLatencyMs: totalLatencyMs / texts.length,
|
|
784
|
+
cacheStats: {
|
|
785
|
+
hits: cached.length,
|
|
786
|
+
misses: uncached.length,
|
|
787
|
+
},
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Get combined cache statistics from both our LRU cache and embedder's internal cache
|
|
793
|
+
*/
|
|
794
|
+
override getCacheStats() {
|
|
795
|
+
const baseStats = super.getCacheStats();
|
|
796
|
+
|
|
797
|
+
if (this.embedder && this.embedder.getCacheStats) {
|
|
798
|
+
const embedderStats = this.embedder.getCacheStats();
|
|
799
|
+
return {
|
|
800
|
+
size: baseStats.size + embedderStats.size,
|
|
801
|
+
maxSize: baseStats.maxSize + embedderStats.maxSize,
|
|
802
|
+
hitRate: baseStats.hitRate,
|
|
803
|
+
embedderCache: embedderStats,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return baseStats;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
override async shutdown(): Promise<void> {
|
|
811
|
+
if (this.embedder && this.embedder.clearCache) {
|
|
812
|
+
this.embedder.clearCache();
|
|
813
|
+
}
|
|
814
|
+
await super.shutdown();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
535
818
|
// ============================================================================
|
|
536
819
|
// Factory Functions
|
|
537
820
|
// ============================================================================
|
|
538
821
|
|
|
539
822
|
/**
|
|
540
|
-
*
|
|
823
|
+
* Check if agentic-flow is available
|
|
824
|
+
*/
|
|
825
|
+
async function isAgenticFlowAvailable(): Promise<boolean> {
|
|
826
|
+
try {
|
|
827
|
+
await import('agentic-flow/embeddings');
|
|
828
|
+
return true;
|
|
829
|
+
} catch {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Auto-install agentic-flow and initialize model
|
|
836
|
+
*/
|
|
837
|
+
async function autoInstallAgenticFlow(): Promise<boolean> {
|
|
838
|
+
const { exec } = await import('child_process');
|
|
839
|
+
const { promisify } = await import('util');
|
|
840
|
+
const execAsync = promisify(exec);
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
// Check if already available
|
|
844
|
+
if (await isAgenticFlowAvailable()) {
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
console.log('[embeddings] Installing agentic-flow@alpha...');
|
|
849
|
+
await execAsync('npm install agentic-flow@alpha --save', { timeout: 120000 });
|
|
850
|
+
|
|
851
|
+
// Initialize the model
|
|
852
|
+
console.log('[embeddings] Downloading embedding model...');
|
|
853
|
+
await execAsync('npx agentic-flow@alpha embeddings init', { timeout: 300000 });
|
|
854
|
+
|
|
855
|
+
// Verify installation
|
|
856
|
+
return await isAgenticFlowAvailable();
|
|
857
|
+
} catch (error) {
|
|
858
|
+
console.warn('[embeddings] Auto-install failed:', error instanceof Error ? error.message : error);
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Create embedding service based on configuration (sync version)
|
|
865
|
+
* Note: For 'auto' provider or smart fallback, use createEmbeddingServiceAsync
|
|
541
866
|
*/
|
|
542
867
|
export function createEmbeddingService(config: EmbeddingConfig): IEmbeddingService {
|
|
543
868
|
switch (config.provider) {
|
|
@@ -547,12 +872,159 @@ export function createEmbeddingService(config: EmbeddingConfig): IEmbeddingServi
|
|
|
547
872
|
return new TransformersEmbeddingService(config as TransformersEmbeddingConfig);
|
|
548
873
|
case 'mock':
|
|
549
874
|
return new MockEmbeddingService(config as MockEmbeddingConfig);
|
|
875
|
+
case 'agentic-flow':
|
|
876
|
+
return new AgenticFlowEmbeddingService(config as AgenticFlowEmbeddingConfig);
|
|
550
877
|
default:
|
|
551
878
|
console.warn(`Unknown provider, using mock`);
|
|
552
879
|
return new MockEmbeddingService({ provider: 'mock', dimensions: 384 });
|
|
553
880
|
}
|
|
554
881
|
}
|
|
555
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Extended config with auto provider option
|
|
885
|
+
*/
|
|
886
|
+
export interface AutoEmbeddingConfig {
|
|
887
|
+
/** Provider: 'auto' will pick best available (agentic-flow > transformers > mock) */
|
|
888
|
+
provider: EmbeddingProvider | 'auto';
|
|
889
|
+
/** Fallback provider if primary fails */
|
|
890
|
+
fallback?: EmbeddingProvider;
|
|
891
|
+
/** Auto-install agentic-flow if not available (default: true for 'auto' provider) */
|
|
892
|
+
autoInstall?: boolean;
|
|
893
|
+
/** Model ID for agentic-flow */
|
|
894
|
+
modelId?: string;
|
|
895
|
+
/** Model name for transformers */
|
|
896
|
+
model?: string;
|
|
897
|
+
/** Dimensions */
|
|
898
|
+
dimensions?: number;
|
|
899
|
+
/** Cache size */
|
|
900
|
+
cacheSize?: number;
|
|
901
|
+
/** OpenAI API key (required for openai provider) */
|
|
902
|
+
apiKey?: string;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Create embedding service with automatic provider detection and fallback
|
|
907
|
+
*
|
|
908
|
+
* Features:
|
|
909
|
+
* - 'auto' provider picks best available: agentic-flow > transformers > mock
|
|
910
|
+
* - Automatic fallback if primary provider fails to initialize
|
|
911
|
+
* - Pre-validates provider availability before returning
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* // Auto-select best provider
|
|
915
|
+
* const service = await createEmbeddingServiceAsync({ provider: 'auto' });
|
|
916
|
+
*
|
|
917
|
+
* // Try agentic-flow, fallback to transformers
|
|
918
|
+
* const service = await createEmbeddingServiceAsync({
|
|
919
|
+
* provider: 'agentic-flow',
|
|
920
|
+
* fallback: 'transformers'
|
|
921
|
+
* });
|
|
922
|
+
*/
|
|
923
|
+
export async function createEmbeddingServiceAsync(
|
|
924
|
+
config: AutoEmbeddingConfig
|
|
925
|
+
): Promise<IEmbeddingService> {
|
|
926
|
+
const { provider, fallback, autoInstall = true, ...rest } = config;
|
|
927
|
+
|
|
928
|
+
// Auto provider selection
|
|
929
|
+
if (provider === 'auto') {
|
|
930
|
+
// Try agentic-flow first (fastest, ONNX-based)
|
|
931
|
+
let agenticFlowAvailable = await isAgenticFlowAvailable();
|
|
932
|
+
|
|
933
|
+
// Auto-install if not available and autoInstall is enabled
|
|
934
|
+
if (!agenticFlowAvailable && autoInstall) {
|
|
935
|
+
agenticFlowAvailable = await autoInstallAgenticFlow();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (agenticFlowAvailable) {
|
|
939
|
+
try {
|
|
940
|
+
const service = new AgenticFlowEmbeddingService({
|
|
941
|
+
provider: 'agentic-flow',
|
|
942
|
+
modelId: rest.modelId ?? 'all-MiniLM-L6-v2',
|
|
943
|
+
dimensions: rest.dimensions ?? 384,
|
|
944
|
+
cacheSize: rest.cacheSize,
|
|
945
|
+
});
|
|
946
|
+
// Validate it can initialize
|
|
947
|
+
await service.embed('test');
|
|
948
|
+
return service;
|
|
949
|
+
} catch {
|
|
950
|
+
// Fall through to next option
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Try transformers (good quality, built-in)
|
|
955
|
+
try {
|
|
956
|
+
const service = new TransformersEmbeddingService({
|
|
957
|
+
provider: 'transformers',
|
|
958
|
+
model: rest.model ?? 'Xenova/all-MiniLM-L6-v2',
|
|
959
|
+
cacheSize: rest.cacheSize,
|
|
960
|
+
});
|
|
961
|
+
// Validate it can initialize
|
|
962
|
+
await service.embed('test');
|
|
963
|
+
return service;
|
|
964
|
+
} catch {
|
|
965
|
+
// Fall through to mock
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Fallback to mock (always works)
|
|
969
|
+
console.warn('[embeddings] Using mock provider - install agentic-flow or @xenova/transformers for real embeddings');
|
|
970
|
+
return new MockEmbeddingService({
|
|
971
|
+
dimensions: rest.dimensions ?? 384,
|
|
972
|
+
cacheSize: rest.cacheSize,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Specific provider with optional fallback
|
|
977
|
+
const createPrimary = (): IEmbeddingService => {
|
|
978
|
+
switch (provider) {
|
|
979
|
+
case 'agentic-flow':
|
|
980
|
+
return new AgenticFlowEmbeddingService({
|
|
981
|
+
provider: 'agentic-flow',
|
|
982
|
+
modelId: rest.modelId ?? 'all-MiniLM-L6-v2',
|
|
983
|
+
dimensions: rest.dimensions ?? 384,
|
|
984
|
+
cacheSize: rest.cacheSize,
|
|
985
|
+
});
|
|
986
|
+
case 'transformers':
|
|
987
|
+
return new TransformersEmbeddingService({
|
|
988
|
+
provider: 'transformers',
|
|
989
|
+
model: rest.model ?? 'Xenova/all-MiniLM-L6-v2',
|
|
990
|
+
cacheSize: rest.cacheSize,
|
|
991
|
+
});
|
|
992
|
+
case 'openai':
|
|
993
|
+
if (!rest.apiKey) throw new Error('OpenAI provider requires apiKey');
|
|
994
|
+
return new OpenAIEmbeddingService({
|
|
995
|
+
provider: 'openai',
|
|
996
|
+
apiKey: rest.apiKey,
|
|
997
|
+
dimensions: rest.dimensions,
|
|
998
|
+
cacheSize: rest.cacheSize,
|
|
999
|
+
});
|
|
1000
|
+
case 'mock':
|
|
1001
|
+
return new MockEmbeddingService({
|
|
1002
|
+
dimensions: rest.dimensions ?? 384,
|
|
1003
|
+
cacheSize: rest.cacheSize,
|
|
1004
|
+
});
|
|
1005
|
+
default:
|
|
1006
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const primary = createPrimary();
|
|
1011
|
+
|
|
1012
|
+
// Try to validate primary provider
|
|
1013
|
+
try {
|
|
1014
|
+
await primary.embed('test');
|
|
1015
|
+
return primary;
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
if (!fallback) {
|
|
1018
|
+
throw error;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Try fallback
|
|
1022
|
+
console.warn(`[embeddings] Primary provider '${provider}' failed, using fallback '${fallback}'`);
|
|
1023
|
+
const fallbackConfig: AutoEmbeddingConfig = { ...rest, provider: fallback };
|
|
1024
|
+
return createEmbeddingServiceAsync(fallbackConfig);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
556
1028
|
/**
|
|
557
1029
|
* Convenience function for quick embeddings
|
|
558
1030
|
*/
|