mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	feat(llm): transition from initializing LLM providers, to creating them on demand
This commit is contained in:
		
							parent
							
								
									c1b10d70b8
								
							
						
					
					
						commit
						bb8a374ab8
					
				| @ -43,11 +43,7 @@ interface NoteContext { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class AIServiceManager implements IAIServiceManager { | export class AIServiceManager implements IAIServiceManager { | ||||||
|     private services: Record<ServiceProviders, AIService> = { |     private services: Partial<Record<ServiceProviders, AIService>> = {}; | ||||||
|         openai: new OpenAIService(), |  | ||||||
|         anthropic: new AnthropicService(), |  | ||||||
|         ollama: new OllamaService() |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     private providerOrder: ServiceProviders[] = []; // Will be populated from configuration
 |     private providerOrder: ServiceProviders[] = []; // Will be populated from configuration
 | ||||||
|     private initialized = false; |     private initialized = false; | ||||||
| @ -183,9 +179,42 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|      */ |      */ | ||||||
|     getAvailableProviders(): ServiceProviders[] { |     getAvailableProviders(): ServiceProviders[] { | ||||||
|         this.ensureInitialized(); |         this.ensureInitialized(); | ||||||
|         return Object.entries(this.services) |          | ||||||
|             .filter(([_, service]) => service.isAvailable()) |         const allProviders: ServiceProviders[] = ['openai', 'anthropic', 'ollama']; | ||||||
|             .map(([key, _]) => key as ServiceProviders); |         const availableProviders: ServiceProviders[] = []; | ||||||
|  |          | ||||||
|  |         for (const providerName of allProviders) { | ||||||
|  |             // Use a sync approach - check if we can create the provider
 | ||||||
|  |             const service = this.services[providerName]; | ||||||
|  |             if (service && service.isAvailable()) { | ||||||
|  |                 availableProviders.push(providerName); | ||||||
|  |             } else { | ||||||
|  |                 // For providers not yet created, check configuration to see if they would be available
 | ||||||
|  |                 try { | ||||||
|  |                     switch (providerName) { | ||||||
|  |                         case 'openai': | ||||||
|  |                             if (options.getOption('openaiApiKey')) { | ||||||
|  |                                 availableProviders.push(providerName); | ||||||
|  |                             } | ||||||
|  |                             break; | ||||||
|  |                         case 'anthropic': | ||||||
|  |                             if (options.getOption('anthropicApiKey')) { | ||||||
|  |                                 availableProviders.push(providerName); | ||||||
|  |                             } | ||||||
|  |                             break; | ||||||
|  |                         case 'ollama': | ||||||
|  |                             if (options.getOption('ollamaBaseUrl')) { | ||||||
|  |                                 availableProviders.push(providerName); | ||||||
|  |                             } | ||||||
|  |                             break; | ||||||
|  |                     } | ||||||
|  |                 } catch (error) { | ||||||
|  |                     // Ignore configuration errors, provider just won't be available
 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return availableProviders; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -224,9 +253,12 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
| 
 | 
 | ||||||
|             if (modelIdentifier.provider && availableProviders.includes(modelIdentifier.provider as ServiceProviders)) { |             if (modelIdentifier.provider && availableProviders.includes(modelIdentifier.provider as ServiceProviders)) { | ||||||
|                 try { |                 try { | ||||||
|  |                     const service = this.services[modelIdentifier.provider as ServiceProviders]; | ||||||
|  |                     if (service) { | ||||||
|                         const modifiedOptions = { ...options, model: modelIdentifier.modelId }; |                         const modifiedOptions = { ...options, model: modelIdentifier.modelId }; | ||||||
|                         log.info(`[AIServiceManager] Using provider ${modelIdentifier.provider} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`); |                         log.info(`[AIServiceManager] Using provider ${modelIdentifier.provider} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`); | ||||||
|                     return await this.services[modelIdentifier.provider as ServiceProviders].generateChatCompletion(messages, modifiedOptions); |                         return await service.generateChatCompletion(messages, modifiedOptions); | ||||||
|  |                     } | ||||||
|                 } catch (error) { |                 } catch (error) { | ||||||
|                     log.error(`Error with specified provider ${modelIdentifier.provider}: ${error}`); |                     log.error(`Error with specified provider ${modelIdentifier.provider}: ${error}`); | ||||||
|                     // If the specified provider fails, continue with the fallback providers
 |                     // If the specified provider fails, continue with the fallback providers
 | ||||||
| @ -240,8 +272,11 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
| 
 | 
 | ||||||
|         for (const provider of sortedProviders) { |         for (const provider of sortedProviders) { | ||||||
|             try { |             try { | ||||||
|  |                 const service = this.services[provider]; | ||||||
|  |                 if (service) { | ||||||
|                     log.info(`[AIServiceManager] Trying provider ${provider} with options.stream: ${options.stream}`); |                     log.info(`[AIServiceManager] Trying provider ${provider} with options.stream: ${options.stream}`); | ||||||
|                 return await this.services[provider].generateChatCompletion(messages, options); |                     return await service.generateChatCompletion(messages, options); | ||||||
|  |                 } | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 log.error(`Error with provider ${provider}: ${error}`); |                 log.error(`Error with provider ${provider}: ${error}`); | ||||||
|                 lastError = error as Error; |                 lastError = error as Error; | ||||||
| @ -348,30 +383,49 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Set up embeddings provider using the new configuration system |      * Get or create a chat provider on-demand | ||||||
|      */ |      */ | ||||||
|     async setupEmbeddingsProvider(): Promise<void> { |     private async getOrCreateChatProvider(providerName: ServiceProviders): Promise<AIService | null> { | ||||||
|  |         // Return existing provider if already created
 | ||||||
|  |         if (this.services[providerName]) { | ||||||
|  |             return this.services[providerName]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Create provider on-demand based on configuration
 | ||||||
|         try { |         try { | ||||||
|             const aiEnabled = await isAIEnabled(); |             switch (providerName) { | ||||||
|             if (!aiEnabled) { |                 case 'openai': | ||||||
|                 log.info('AI features are disabled'); |                     const openaiApiKey = await options.getOption('openaiApiKey'); | ||||||
|                 return; |                     if (openaiApiKey) { | ||||||
|  |                         this.services.openai = new OpenAIService(); | ||||||
|  |                         log.info('Created OpenAI chat provider on-demand'); | ||||||
|  |                         return this.services.openai; | ||||||
|                     } |                     } | ||||||
|  |                     break; | ||||||
|                  |                  | ||||||
|             // Use the new configuration system - no string parsing!
 |                 case 'anthropic': | ||||||
|             const enabledProviders = await getEnabledEmbeddingProviders(); |                     const anthropicApiKey = await options.getOption('anthropicApiKey'); | ||||||
| 
 |                     if (anthropicApiKey) { | ||||||
|             if (enabledProviders.length === 0) { |                         this.services.anthropic = new AnthropicService(); | ||||||
|                 log.info('No embedding providers are enabled'); |                         log.info('Created Anthropic chat provider on-demand'); | ||||||
|                 return; |                         return this.services.anthropic; | ||||||
|                     } |                     } | ||||||
|  |                     break; | ||||||
|                  |                  | ||||||
|             // Initialize embedding providers
 |                 case 'ollama': | ||||||
|             log.info('Embedding providers initialized successfully'); |                     const ollamaBaseUrl = await options.getOption('ollamaBaseUrl'); | ||||||
|  |                     if (ollamaBaseUrl) { | ||||||
|  |                         this.services.ollama = new OllamaService(); | ||||||
|  |                         log.info('Created Ollama chat provider on-demand'); | ||||||
|  |                         return this.services.ollama; | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|         } catch (error: any) { |         } catch (error: any) { | ||||||
|             log.error(`Error setting up embedding providers: ${error.message}`); |             log.error(`Error creating ${providerName} provider on-demand: ${error.message || 'Unknown error'}`); | ||||||
|             throw error; |  | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -392,9 +446,6 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|             // Update provider order from configuration
 |             // Update provider order from configuration
 | ||||||
|             await this.updateProviderOrderAsync(); |             await this.updateProviderOrderAsync(); | ||||||
| 
 | 
 | ||||||
|             // Set up embeddings provider if AI is enabled
 |  | ||||||
|             await this.setupEmbeddingsProvider(); |  | ||||||
| 
 |  | ||||||
|             // Initialize index service
 |             // Initialize index service
 | ||||||
|             await this.getIndexService().initialize(); |             await this.getIndexService().initialize(); | ||||||
| 
 | 
 | ||||||
| @ -462,7 +513,7 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|                 try { |                 try { | ||||||
|                     // Get the default LLM service for context enhancement
 |                     // Get the default LLM service for context enhancement
 | ||||||
|                     const provider = this.getPreferredProvider(); |                     const provider = this.getPreferredProvider(); | ||||||
|                     const llmService = this.getService(provider); |                     const llmService = await this.getService(provider); | ||||||
| 
 | 
 | ||||||
|                     // Find relevant notes
 |                     // Find relevant notes
 | ||||||
|                     contextNotes = await contextService.findRelevantNotes( |                     contextNotes = await contextService.findRelevantNotes( | ||||||
| @ -503,25 +554,27 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|     /** |     /** | ||||||
|      * Get AI service for the given provider |      * Get AI service for the given provider | ||||||
|      */ |      */ | ||||||
|     getService(provider?: string): AIService { |     async getService(provider?: string): Promise<AIService> { | ||||||
|         this.ensureInitialized(); |         this.ensureInitialized(); | ||||||
| 
 | 
 | ||||||
|         // If provider is specified, try to use it
 |         // If provider is specified, try to get or create it
 | ||||||
|         if (provider && this.services[provider as ServiceProviders]?.isAvailable()) { |         if (provider) { | ||||||
|             return this.services[provider as ServiceProviders]; |             const service = await this.getOrCreateChatProvider(provider as ServiceProviders); | ||||||
|         } |             if (service && service.isAvailable()) { | ||||||
| 
 |  | ||||||
|         // Otherwise, use the first available provider in the configured order
 |  | ||||||
|         for (const providerName of this.providerOrder) { |  | ||||||
|             const service = this.services[providerName]; |  | ||||||
|             if (service.isAvailable()) { |  | ||||||
|                 return service; |                 return service; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // If no provider is available, use first one anyway (it will throw an error)
 |         // Otherwise, try providers in the configured order
 | ||||||
|         // This allows us to show a proper error message rather than "provider not found"
 |         for (const providerName of this.providerOrder) { | ||||||
|         return this.services[this.providerOrder[0]]; |             const service = await this.getOrCreateChatProvider(providerName); | ||||||
|  |             if (service && service.isAvailable()) { | ||||||
|  |                 return service; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // If no provider is available, throw a clear error
 | ||||||
|  |         throw new Error('No AI chat providers are available. Please check your AI settings.'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -550,7 +603,8 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
| 
 | 
 | ||||||
|         // Return the first available provider in the order
 |         // Return the first available provider in the order
 | ||||||
|         for (const providerName of this.providerOrder) { |         for (const providerName of this.providerOrder) { | ||||||
|             if (this.services[providerName].isAvailable()) { |             const service = this.services[providerName]; | ||||||
|  |             if (service && service.isAvailable()) { | ||||||
|                 return providerName; |                 return providerName; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -634,13 +688,15 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|                         // Initialize embeddings through index service
 |                         // Initialize embeddings through index service
 | ||||||
|                         await indexService.startEmbeddingGeneration(); |                         await indexService.startEmbeddingGeneration(); | ||||||
|                     } else { |                     } else { | ||||||
|                         log.info('AI features disabled, stopping embeddings'); |                         log.info('AI features disabled, stopping embeddings and clearing providers'); | ||||||
|                         // Stop embeddings through index service
 |                         // Stop embeddings through index service
 | ||||||
|                         await indexService.stopEmbeddingGeneration(); |                         await indexService.stopEmbeddingGeneration(); | ||||||
|  |                         // Clear chat providers
 | ||||||
|  |                         this.services = {}; | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     // For other AI-related options, just recreate services
 |                     // For other AI-related options, recreate services on-demand
 | ||||||
|                     this.recreateServices(); |                     await this.recreateServices(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @ -656,8 +712,12 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|             // Clear configuration cache first
 |             // Clear configuration cache first
 | ||||||
|             clearConfigurationCache(); |             clearConfigurationCache(); | ||||||
| 
 | 
 | ||||||
|             // Recreate all service instances to pick up new configuration
 |             // Clear existing chat providers (they will be recreated on-demand)
 | ||||||
|             this.recreateServiceInstances(); |             this.services = {}; | ||||||
|  | 
 | ||||||
|  |             // Clear embedding providers (they will be recreated on-demand when needed)
 | ||||||
|  |             const providerManager = await import('./providers/providers.js'); | ||||||
|  |             providerManager.clearAllEmbeddingProviders(); | ||||||
| 
 | 
 | ||||||
|             // Update provider order with new configuration
 |             // Update provider order with new configuration
 | ||||||
|             await this.updateProviderOrderAsync(); |             await this.updateProviderOrderAsync(); | ||||||
| @ -668,25 +728,6 @@ export class AIServiceManager implements IAIServiceManager { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Recreate service instances to pick up new configuration |  | ||||||
|      */ |  | ||||||
|     private recreateServiceInstances(): void { |  | ||||||
|         try { |  | ||||||
|             log.info('Recreating service instances'); |  | ||||||
| 
 |  | ||||||
|             // Recreate service instances
 |  | ||||||
|             this.services = { |  | ||||||
|                 openai: new OpenAIService(), |  | ||||||
|                 anthropic: new AnthropicService(), |  | ||||||
|                 ollama: new OllamaService() |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             log.info('Service instances recreated successfully'); |  | ||||||
|         } catch (error) { |  | ||||||
|             log.error(`Error recreating service instances: ${this.handleError(error)}`); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Don't create singleton immediately, use a lazy-loading pattern
 | // Don't create singleton immediately, use a lazy-loading pattern
 | ||||||
| @ -759,7 +800,7 @@ export default { | |||||||
|         ); |         ); | ||||||
|     }, |     }, | ||||||
|     // New methods
 |     // New methods
 | ||||||
|     getService(provider?: string): AIService { |     async getService(provider?: string): Promise<AIService> { | ||||||
|         return getInstance().getService(provider); |         return getInstance().getService(provider); | ||||||
|     }, |     }, | ||||||
|     getPreferredProvider(): string { |     getPreferredProvider(): string { | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ async function getSemanticContext( | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Get an LLM service
 |         // Get an LLM service
 | ||||||
|         const llmService = aiServiceManager.getInstance().getService(); |         const llmService = await aiServiceManager.getInstance().getService(); | ||||||
| 
 | 
 | ||||||
|         const result = await contextService.processQuery("", llmService, { |         const result = await contextService.processQuery("", llmService, { | ||||||
|             maxResults: options.maxSimilarNotes || 5, |             maxResults: options.maxSimilarNotes || 5, | ||||||
| @ -543,7 +543,7 @@ export class ContextExtractor { | |||||||
|         try { |         try { | ||||||
|             const { default: aiServiceManager } = await import('../ai_service_manager.js'); |             const { default: aiServiceManager } = await import('../ai_service_manager.js'); | ||||||
|             const contextService = aiServiceManager.getInstance().getContextService(); |             const contextService = aiServiceManager.getInstance().getContextService(); | ||||||
|             const llmService = aiServiceManager.getInstance().getService(); |             const llmService = await aiServiceManager.getInstance().getService(); | ||||||
| 
 | 
 | ||||||
|             if (!contextService) { |             if (!contextService) { | ||||||
|                 return "Context service not available."; |                 return "Context service not available."; | ||||||
|  | |||||||
| @ -45,8 +45,7 @@ export async function initializeEmbeddings() { | |||||||
| 
 | 
 | ||||||
|         // Start the embedding system if AI is enabled
 |         // Start the embedding system if AI is enabled
 | ||||||
|         if (await options.getOptionBool('aiEnabled')) { |         if (await options.getOptionBool('aiEnabled')) { | ||||||
|             // Initialize default embedding providers when AI is enabled
 |             // Embedding providers will be created on-demand when needed
 | ||||||
|             await providerManager.initializeDefaultProviders(); |  | ||||||
|             await initEmbeddings(); |             await initEmbeddings(); | ||||||
|             log.info("Embedding system initialized successfully."); |             log.info("Embedding system initialized successfully."); | ||||||
|         } else { |         } else { | ||||||
|  | |||||||
| @ -851,10 +851,6 @@ export class IndexService { | |||||||
|                 throw new Error("AI features must be enabled first"); |                 throw new Error("AI features must be enabled first"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Re-initialize providers first in case they weren't available when server started
 |  | ||||||
|             log.info("Re-initializing embedding providers"); |  | ||||||
|             await providerManager.initializeDefaultProviders(); |  | ||||||
| 
 |  | ||||||
|             // Re-initialize if needed
 |             // Re-initialize if needed
 | ||||||
|             if (!this.initialized) { |             if (!this.initialized) { | ||||||
|                 await this.initialize(); |                 await this.initialize(); | ||||||
| @ -870,6 +866,13 @@ export class IndexService { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             // Verify providers are available (this will create them on-demand if needed)
 | ||||||
|  |             const providers = await providerManager.getEnabledEmbeddingProviders(); | ||||||
|  |             if (providers.length === 0) { | ||||||
|  |                 throw new Error("No embedding providers available"); | ||||||
|  |             } | ||||||
|  |             log.info(`Found ${providers.length} embedding providers: ${providers.map(p => p.name).join(', ')}`); | ||||||
|  | 
 | ||||||
|             // Setup automatic indexing if enabled
 |             // Setup automatic indexing if enabled
 | ||||||
|             if (await options.getOptionBool('embeddingAutoUpdateEnabled')) { |             if (await options.getOptionBool('embeddingAutoUpdateEnabled')) { | ||||||
|                 this.setupAutomaticIndexing(); |                 this.setupAutomaticIndexing(); | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ export interface AIServiceManagerConfig { | |||||||
|  * Interface for managing AI service providers |  * Interface for managing AI service providers | ||||||
|  */ |  */ | ||||||
| export interface IAIServiceManager { | export interface IAIServiceManager { | ||||||
|   getService(provider?: string): AIService; |   getService(provider?: string): Promise<AIService>; | ||||||
|   getAvailableProviders(): string[]; |   getAvailableProviders(): string[]; | ||||||
|   getPreferredProvider(): string; |   getPreferredProvider(): string; | ||||||
|   isProviderAvailable(provider: string): boolean; |   isProviderAvailable(provider: string): boolean; | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ export class ContextExtractionStage { | |||||||
| 
 | 
 | ||||||
|             // Get enhanced context from the context service
 |             // Get enhanced context from the context service
 | ||||||
|             const contextService = aiServiceManager.getContextService(); |             const contextService = aiServiceManager.getContextService(); | ||||||
|             const llmService = aiServiceManager.getService(); |             const llmService = await aiServiceManager.getService(); | ||||||
| 
 | 
 | ||||||
|             if (contextService) { |             if (contextService) { | ||||||
|                 // Use unified context service to get smart context
 |                 // Use unified context service to get smart context
 | ||||||
|  | |||||||
| @ -104,7 +104,7 @@ export class LLMCompletionStage extends BasePipelineStage<LLMCompletionInput, { | |||||||
| 
 | 
 | ||||||
|         // Use specific provider if available
 |         // Use specific provider if available
 | ||||||
|         if (selectedProvider && aiServiceManager.isProviderAvailable(selectedProvider)) { |         if (selectedProvider && aiServiceManager.isProviderAvailable(selectedProvider)) { | ||||||
|             const service = aiServiceManager.getService(selectedProvider); |             const service = await aiServiceManager.getService(selectedProvider); | ||||||
|             log.info(`[LLMCompletionStage] Using specific service for ${selectedProvider}`); |             log.info(`[LLMCompletionStage] Using specific service for ${selectedProvider}`); | ||||||
| 
 | 
 | ||||||
|             // Generate completion and wrap with enhanced stream handling
 |             // Generate completion and wrap with enhanced stream handling
 | ||||||
|  | |||||||
| @ -292,7 +292,7 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput, | |||||||
|             log.info(`Getting default model for provider ${provider} using AI service manager`); |             log.info(`Getting default model for provider ${provider} using AI service manager`); | ||||||
|              |              | ||||||
|             // Use the existing AI service manager instead of duplicating API calls
 |             // Use the existing AI service manager instead of duplicating API calls
 | ||||||
|             const service = aiServiceManager.getInstance().getService(provider); |             const service = await aiServiceManager.getInstance().getService(provider); | ||||||
|              |              | ||||||
|             if (!service || !service.isAvailable()) { |             if (!service || !service.isAvailable()) { | ||||||
|                 log.info(`Provider ${provider} service is not available`); |                 log.info(`Provider ${provider} service is not available`); | ||||||
|  | |||||||
| @ -123,6 +123,94 @@ export function getEmbeddingProvider(name: string): EmbeddingProvider | undefine | |||||||
|     return providers.get(name); |     return providers.get(name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Create providers on-demand based on current options | ||||||
|  |  */ | ||||||
|  | export async function createProvidersFromCurrentOptions(): Promise<EmbeddingProvider[]> { | ||||||
|  |     const result: EmbeddingProvider[] = []; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |         // Create Ollama provider if embedding base URL is configured
 | ||||||
|  |         const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl'); | ||||||
|  |         if (ollamaEmbeddingBaseUrl) { | ||||||
|  |             const embeddingModel = await options.getOption('ollamaEmbeddingModel'); | ||||||
|  |              | ||||||
|  |             try { | ||||||
|  |                 const ollamaProvider = new OllamaEmbeddingProvider({ | ||||||
|  |                     model: embeddingModel, | ||||||
|  |                     dimension: 768, // Initial value, will be updated during initialization
 | ||||||
|  |                     type: 'float32', | ||||||
|  |                     baseUrl: ollamaEmbeddingBaseUrl | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |                 await ollamaProvider.initialize(); | ||||||
|  |                 registerEmbeddingProvider(ollamaProvider); | ||||||
|  |                 result.push(ollamaProvider); | ||||||
|  |                 log.info(`Created Ollama provider on-demand: ${embeddingModel} at ${ollamaEmbeddingBaseUrl}`); | ||||||
|  |             } catch (error: any) { | ||||||
|  |                 log.error(`Error creating Ollama embedding provider on-demand: ${error.message || 'Unknown error'}`); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Create OpenAI provider if API key is configured
 | ||||||
|  |         const openaiApiKey = await options.getOption('openaiApiKey'); | ||||||
|  |         if (openaiApiKey) { | ||||||
|  |             const openaiModel = await options.getOption('openaiEmbeddingModel') || 'text-embedding-3-small'; | ||||||
|  |             const openaiBaseUrl = await options.getOption('openaiBaseUrl') || 'https://api.openai.com/v1'; | ||||||
|  | 
 | ||||||
|  |             const openaiProvider = new OpenAIEmbeddingProvider({ | ||||||
|  |                 model: openaiModel, | ||||||
|  |                 dimension: 1536, | ||||||
|  |                 type: 'float32', | ||||||
|  |                 apiKey: openaiApiKey, | ||||||
|  |                 baseUrl: openaiBaseUrl | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             registerEmbeddingProvider(openaiProvider); | ||||||
|  |             result.push(openaiProvider); | ||||||
|  |             log.info(`Created OpenAI provider on-demand: ${openaiModel}`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Create Voyage provider if API key is configured
 | ||||||
|  |         const voyageApiKey = await options.getOption('voyageApiKey' as any); | ||||||
|  |         if (voyageApiKey) { | ||||||
|  |             const voyageModel = await options.getOption('voyageEmbeddingModel') || 'voyage-2'; | ||||||
|  |             const voyageBaseUrl = 'https://api.voyageai.com/v1'; | ||||||
|  | 
 | ||||||
|  |             const voyageProvider = new VoyageEmbeddingProvider({ | ||||||
|  |                 model: voyageModel, | ||||||
|  |                 dimension: 1024, | ||||||
|  |                 type: 'float32', | ||||||
|  |                 apiKey: voyageApiKey, | ||||||
|  |                 baseUrl: voyageBaseUrl | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             registerEmbeddingProvider(voyageProvider); | ||||||
|  |             result.push(voyageProvider); | ||||||
|  |             log.info(`Created Voyage provider on-demand: ${voyageModel}`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Always include local provider as fallback
 | ||||||
|  |         if (!providers.has('local')) { | ||||||
|  |             const localProvider = new SimpleLocalEmbeddingProvider({ | ||||||
|  |                 model: 'local', | ||||||
|  |                 dimension: 384, | ||||||
|  |                 type: 'float32' | ||||||
|  |             }); | ||||||
|  |             registerEmbeddingProvider(localProvider); | ||||||
|  |             result.push(localProvider); | ||||||
|  |             log.info(`Created local provider on-demand as fallback`); | ||||||
|  |         } else { | ||||||
|  |             result.push(providers.get('local')!); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } catch (error: any) { | ||||||
|  |         log.error(`Error creating providers from current options: ${error.message || 'Unknown error'}`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Get all enabled embedding providers |  * Get all enabled embedding providers | ||||||
|  */ |  */ | ||||||
| @ -131,31 +219,16 @@ export async function getEnabledEmbeddingProviders(): Promise<EmbeddingProvider[ | |||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Get providers from database ordered by priority
 |     // First try to get existing registered providers
 | ||||||
|     const dbProviders = await sql.getRows(` |     const existingProviders = Array.from(providers.values()); | ||||||
|         SELECT providerId, name, config |  | ||||||
|         FROM embedding_providers |  | ||||||
|         ORDER BY priority DESC` |  | ||||||
|     ); |  | ||||||
|      |      | ||||||
|     const result: EmbeddingProvider[] = []; |     // If no providers are registered, create them on-demand from current options
 | ||||||
| 
 |     if (existingProviders.length === 0) { | ||||||
|     for (const row of dbProviders) { |         log.info('No providers registered, creating from current options'); | ||||||
|         const rowData = row as any; |         return await createProvidersFromCurrentOptions(); | ||||||
|         const provider = providers.get(rowData.name); |  | ||||||
| 
 |  | ||||||
|         if (provider) { |  | ||||||
|             result.push(provider); |  | ||||||
|         } else { |  | ||||||
|             // Only log error if we haven't logged it before for this provider
 |  | ||||||
|             if (!loggedProviderErrors.has(rowData.name)) { |  | ||||||
|                 log.error(`Enabled embedding provider ${rowData.name} not found in registered providers`); |  | ||||||
|                 loggedProviderErrors.add(rowData.name); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return result; |     return existingProviders; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -257,130 +330,13 @@ export async function getEmbeddingProviderConfigs() { | |||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Initialize the default embedding providers |  * Initialize the default embedding providers | ||||||
|  |  * @deprecated - Use on-demand provider creation instead | ||||||
|  */ |  */ | ||||||
| export async function initializeDefaultProviders() { | export async function initializeDefaultProviders() { | ||||||
|     // Register built-in providers
 |     // This function is now deprecated in favor of on-demand provider creation
 | ||||||
|     try { |     // The createProvidersFromCurrentOptions() function should be used instead
 | ||||||
|         // Register OpenAI provider if API key is configured
 |     log.info('initializeDefaultProviders called - using on-demand provider creation instead'); | ||||||
|         const openaiApiKey = await options.getOption('openaiApiKey'); |     return await createProvidersFromCurrentOptions(); | ||||||
|         if (openaiApiKey) { |  | ||||||
|             const openaiModel = await options.getOption('openaiEmbeddingModel') || 'text-embedding-3-small'; |  | ||||||
|             const openaiBaseUrl = await options.getOption('openaiBaseUrl') || 'https://api.openai.com/v1'; |  | ||||||
| 
 |  | ||||||
|             registerEmbeddingProvider(new OpenAIEmbeddingProvider({ |  | ||||||
|                 model: openaiModel, |  | ||||||
|                 dimension: 1536, // OpenAI's typical dimension
 |  | ||||||
|                 type: 'float32', |  | ||||||
|                 apiKey: openaiApiKey, |  | ||||||
|                 baseUrl: openaiBaseUrl |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Create OpenAI provider config if it doesn't exist
 |  | ||||||
|             const existingOpenAI = await sql.getRow( |  | ||||||
|                 "SELECT * FROM embedding_providers WHERE name = ?", |  | ||||||
|                 ['openai'] |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             if (!existingOpenAI) { |  | ||||||
|                 await createEmbeddingProviderConfig('openai', { |  | ||||||
|                     model: openaiModel, |  | ||||||
|                     dimension: 1536, |  | ||||||
|                     type: 'float32' |  | ||||||
|                 }, 100); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Register Voyage provider if API key is configured
 |  | ||||||
|         const voyageApiKey = await options.getOption('voyageApiKey' as any); |  | ||||||
|         if (voyageApiKey) { |  | ||||||
|             const voyageModel = await options.getOption('voyageEmbeddingModel') || 'voyage-2'; |  | ||||||
|             const voyageBaseUrl = 'https://api.voyageai.com/v1'; |  | ||||||
| 
 |  | ||||||
|             registerEmbeddingProvider(new VoyageEmbeddingProvider({ |  | ||||||
|                 model: voyageModel, |  | ||||||
|                 dimension: 1024, // Voyage's embedding dimension
 |  | ||||||
|                 type: 'float32', |  | ||||||
|                 apiKey: voyageApiKey, |  | ||||||
|                 baseUrl: voyageBaseUrl |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Create Voyage provider config if it doesn't exist
 |  | ||||||
|             const existingVoyage = await sql.getRow( |  | ||||||
|                 "SELECT * FROM embedding_providers WHERE name = ?", |  | ||||||
|                 ['voyage'] |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             if (!existingVoyage) { |  | ||||||
|                 await createEmbeddingProviderConfig('voyage', { |  | ||||||
|                     model: voyageModel, |  | ||||||
|                     dimension: 1024, |  | ||||||
|                     type: 'float32' |  | ||||||
|                 }, 75); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Register Ollama embedding provider if embedding base URL is configured
 |  | ||||||
|         const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl'); |  | ||||||
|         if (ollamaEmbeddingBaseUrl) { |  | ||||||
|             // Use specific embedding models if available
 |  | ||||||
|             const embeddingModel = await options.getOption('ollamaEmbeddingModel'); |  | ||||||
| 
 |  | ||||||
|             try { |  | ||||||
|                 // Create provider with initial dimension to be updated during initialization
 |  | ||||||
|                 const ollamaProvider = new OllamaEmbeddingProvider({ |  | ||||||
|                     model: embeddingModel, |  | ||||||
|                     dimension: 768, // Initial value, will be updated during initialization
 |  | ||||||
|                     type: 'float32', |  | ||||||
|                     baseUrl: ollamaEmbeddingBaseUrl |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 // Register the provider
 |  | ||||||
|                 registerEmbeddingProvider(ollamaProvider); |  | ||||||
| 
 |  | ||||||
|                 // Initialize the provider to detect model capabilities
 |  | ||||||
|                 await ollamaProvider.initialize(); |  | ||||||
| 
 |  | ||||||
|                 // Create Ollama provider config if it doesn't exist
 |  | ||||||
|                 const existingOllama = await sql.getRow( |  | ||||||
|                     "SELECT * FROM embedding_providers WHERE name = ?", |  | ||||||
|                     ['ollama'] |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 if (!existingOllama) { |  | ||||||
|                     await createEmbeddingProviderConfig('ollama', { |  | ||||||
|                         model: embeddingModel, |  | ||||||
|                         dimension: ollamaProvider.getDimension(), |  | ||||||
|                         type: 'float32' |  | ||||||
|                     }, 50); |  | ||||||
|                 } |  | ||||||
|             } catch (error: any) { |  | ||||||
|                 log.error(`Error initializing Ollama embedding provider: ${error.message || 'Unknown error'}`); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Always register local provider as fallback
 |  | ||||||
|         registerEmbeddingProvider(new SimpleLocalEmbeddingProvider({ |  | ||||||
|             model: 'local', |  | ||||||
|             dimension: 384, |  | ||||||
|             type: 'float32' |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         // Create local provider config if it doesn't exist
 |  | ||||||
|         const existingLocal = await sql.getRow( |  | ||||||
|             "SELECT * FROM embedding_providers WHERE name = ?", |  | ||||||
|             ['local'] |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         if (!existingLocal) { |  | ||||||
|             await createEmbeddingProviderConfig('local', { |  | ||||||
|                 model: 'local', |  | ||||||
|                 dimension: 384, |  | ||||||
|                 type: 'float32' |  | ||||||
|             }, 10); |  | ||||||
|         } |  | ||||||
|     } catch (error: any) { |  | ||||||
|         log.error(`Error initializing default embedding providers: ${error.message || 'Unknown error'}`); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
| @ -390,6 +346,7 @@ export default { | |||||||
|     getEmbeddingProviders, |     getEmbeddingProviders, | ||||||
|     getEmbeddingProvider, |     getEmbeddingProvider, | ||||||
|     getEnabledEmbeddingProviders, |     getEnabledEmbeddingProviders, | ||||||
|  |     createProvidersFromCurrentOptions, | ||||||
|     createEmbeddingProviderConfig, |     createEmbeddingProviderConfig, | ||||||
|     updateEmbeddingProviderConfig, |     updateEmbeddingProviderConfig, | ||||||
|     deleteEmbeddingProviderConfig, |     deleteEmbeddingProviderConfig, | ||||||
|  | |||||||
| @ -102,12 +102,7 @@ export class NoteSummarizationTool implements ToolHandler { | |||||||
|             const cleanContent = this.cleanHtml(content); |             const cleanContent = this.cleanHtml(content); | ||||||
| 
 | 
 | ||||||
|             // Generate the summary using the AI service
 |             // Generate the summary using the AI service
 | ||||||
|             const aiService = aiServiceManager.getService(); |             const aiService = await aiServiceManager.getService(); | ||||||
| 
 |  | ||||||
|             if (!aiService) { |  | ||||||
|                 log.error('No AI service available for summarization'); |  | ||||||
|                 return `Error: No AI service is available for summarization`; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             log.info(`Using ${aiService.getName()} to generate summary`); |             log.info(`Using ${aiService.getName()} to generate summary`); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -312,16 +312,7 @@ export class RelationshipTool implements ToolHandler { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Get the AI service for relationship suggestion
 |             // Get the AI service for relationship suggestion
 | ||||||
|             const aiService = aiServiceManager.getService(); |             const aiService = await aiServiceManager.getService(); | ||||||
| 
 |  | ||||||
|             if (!aiService) { |  | ||||||
|                 log.error('No AI service available for relationship suggestions'); |  | ||||||
|                 return { |  | ||||||
|                     success: false, |  | ||||||
|                     message: 'AI service not available for relationship suggestions', |  | ||||||
|                     relatedNotes: relatedResult.relatedNotes |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             log.info(`Using ${aiService.getName()} to suggest relationships for ${relatedResult.relatedNotes.length} related notes`); |             log.info(`Using ${aiService.getName()} to suggest relationships for ${relatedResult.relatedNotes.length} related notes`); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -122,9 +122,9 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|             // If summarization is requested
 |             // If summarization is requested
 | ||||||
|             if (summarize) { |             if (summarize) { | ||||||
|                 // Try to get an LLM service for summarization
 |                 // Try to get an LLM service for summarization
 | ||||||
|                 const llmService = aiServiceManager.getService(); |  | ||||||
|                 if (llmService) { |  | ||||||
|                 try { |                 try { | ||||||
|  |                     const llmService = await aiServiceManager.getService(); | ||||||
|  |                      | ||||||
|                     const messages = [ |                     const messages = [ | ||||||
|                             { |                             { | ||||||
|                                 role: "system" as const, |                                 role: "system" as const, | ||||||
| @ -155,7 +155,6 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|                     // Fall through to smart truncation if summarization fails
 |                     // Fall through to smart truncation if summarization fails
 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|                 // Fall back to smart truncation if summarization fails or isn't requested
 |                 // Fall back to smart truncation if summarization fails or isn't requested
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 perf3ct
						perf3ct