22 * Embedding Provider
33 *
44 * Unified interface for generating embeddings using local models or AI APIs.
5- * Supports @xenova/ transformers for local inference and @happyvertical/ai for cloud.
5+ * Supports transformers.js packages for local inference and @happyvertical/ai for cloud.
66 */
77
88import type { EmbeddingProviderType , ProjectEmbeddingConfig } from './types' ;
@@ -17,6 +17,55 @@ async function importOptional(moduleName: string): Promise<any> {
1717 return import ( /* @vite -ignore */ name ) ;
1818}
1919
20+ export type OptionalModuleImporter = ( moduleName : string ) => Promise < any > ;
21+
22+ export const LOCAL_TRANSFORMERS_PACKAGES = [
23+ '@huggingface/transformers' ,
24+ '@xenova/transformers' ,
25+ ] as const ;
26+
27+ export type LocalTransformersPackage =
28+ ( typeof LOCAL_TRANSFORMERS_PACKAGES ) [ number ] ;
29+
30+ export interface TransformersModuleResolution {
31+ module : any ;
32+ packageName : LocalTransformersPackage ;
33+ }
34+
35+ function isModuleNotFoundError ( error : unknown , moduleName : string ) : boolean {
36+ return (
37+ error instanceof Error &&
38+ ( error . message . includes ( `Cannot find module '${ moduleName } '` ) ||
39+ error . message . includes ( `Cannot find package '${ moduleName } '` ) )
40+ ) ;
41+ }
42+
43+ function formatTransformersResolutionError (
44+ attemptedPackages : readonly string [ ] ,
45+ ) : Error {
46+ return new Error (
47+ `Local embeddings require one of: ${ attemptedPackages . join ( ', ' ) } . ` +
48+ `Install one of them to use provider: "local", or switch to provider: "ai".` ,
49+ ) ;
50+ }
51+
52+ export async function resolveLocalTransformersModule (
53+ importModule : OptionalModuleImporter = importOptional ,
54+ ) : Promise < TransformersModuleResolution > {
55+ for ( const packageName of LOCAL_TRANSFORMERS_PACKAGES ) {
56+ try {
57+ const module = await importModule ( packageName ) ;
58+ return { module, packageName } ;
59+ } catch ( error ) {
60+ if ( ! isModuleNotFoundError ( error , packageName ) ) {
61+ throw error ;
62+ }
63+ }
64+ }
65+
66+ throw formatTransformersResolutionError ( LOCAL_TRANSFORMERS_PACKAGES ) ;
67+ }
68+
2069/**
2170 * Interface for AI client that can generate embeddings
2271 */
@@ -28,7 +77,7 @@ interface EmbeddingCapableAI {
2877}
2978
3079/**
31- * Pipeline type for @xenova/ transformers
80+ * Pipeline type for local transformers packages
3281 */
3382type FeatureExtractionPipeline = (
3483 texts : string [ ] ,
@@ -98,7 +147,7 @@ export class EmbeddingProvider {
98147 }
99148
100149 /**
101- * Generate embeddings using local model (@xenova/ transformers)
150+ * Generate embeddings using a local transformers model
102151 */
103152 private async embedLocal ( texts : string [ ] ) : Promise < number [ ] [ ] > {
104153 const pipeline = await this . getLocalPipeline ( ) ;
@@ -133,30 +182,15 @@ export class EmbeddingProvider {
133182 * Initialize the local embedding pipeline
134183 */
135184 private async initLocalPipeline ( ) : Promise < FeatureExtractionPipeline > {
136- try {
137- // Dynamic import for optional dependency
138- const transformers = await importOptional ( '@xenova/transformers' ) ;
139- const { pipeline } = transformers ;
185+ const { module : transformers } = await resolveLocalTransformersModule ( ) ;
186+ const { pipeline } = transformers ;
140187
141- const model = this . config . localModel || 'Xenova/bge-base-en-v1.5' ;
188+ const model = this . config . localModel || 'Xenova/bge-base-en-v1.5' ;
142189
143- // Initialize the feature extraction pipeline
144- const pipe = await pipeline ( 'feature-extraction' , model ) ;
190+ // Initialize the feature extraction pipeline
191+ const pipe = await pipeline ( 'feature-extraction' , model ) ;
145192
146- return pipe as unknown as FeatureExtractionPipeline ;
147- } catch ( error ) {
148- if (
149- error instanceof Error &&
150- error . message . includes ( "Cannot find module '@xenova/transformers'" )
151- ) {
152- throw new Error (
153- 'Local embeddings require @xenova/transformers. ' +
154- 'Install it with: pnpm add @xenova/transformers\n' +
155- 'Or use provider: "ai" in your embedding configuration.' ,
156- ) ;
157- }
158- throw error ;
159- }
193+ return pipe as unknown as FeatureExtractionPipeline ;
160194 }
161195
162196 /**
@@ -201,7 +235,7 @@ export class EmbeddingProvider {
201235 */
202236 async isLocalAvailable ( ) : Promise < boolean > {
203237 try {
204- await importOptional ( '@xenova/transformers' ) ;
238+ await resolveLocalTransformersModule ( ) ;
205239 return true ;
206240 } catch {
207241 return false ;
0 commit comments