mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-26 15:01:32 +08:00
feat(llm): still work on decomplicating provider creation
This commit is contained in:
parent
8f33f37de3
commit
20ec294774
@ -267,12 +267,23 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
// If not a provider prefix, treat the entire string as a model name and continue with normal provider selection
|
||||
}
|
||||
|
||||
// Try each provider in order until one succeeds
|
||||
// If user has a specific provider selected, try only that one and fail fast
|
||||
if (this.providerOrder.length === 1 && sortedProviders.length === 1) {
|
||||
const selectedProvider = sortedProviders[0];
|
||||
const service = await this.getOrCreateChatProvider(selectedProvider);
|
||||
if (!service) {
|
||||
throw new Error(`Failed to create selected chat provider: ${selectedProvider}. Please check your configuration.`);
|
||||
}
|
||||
log.info(`[AIServiceManager] Using selected provider ${selectedProvider} with options.stream: ${options.stream}`);
|
||||
return await service.generateChatCompletion(messages, options);
|
||||
}
|
||||
|
||||
// If no specific provider selected, try each provider in order until one succeeds
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (const provider of sortedProviders) {
|
||||
try {
|
||||
const service = this.services[provider];
|
||||
const service = await this.getOrCreateChatProvider(provider);
|
||||
if (service) {
|
||||
log.info(`[AIServiceManager] Trying provider ${provider} with options.stream: ${options.stream}`);
|
||||
return await service.generateChatCompletion(messages, options);
|
||||
@ -383,7 +394,7 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a chat provider on-demand
|
||||
* Get or create a chat provider on-demand with inline validation
|
||||
*/
|
||||
private async getOrCreateChatProvider(providerName: ServiceProviders): Promise<AIService | null> {
|
||||
// Return existing provider if already created
|
||||
@ -391,38 +402,54 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
return this.services[providerName];
|
||||
}
|
||||
|
||||
// Create provider on-demand based on configuration
|
||||
// Create and validate provider on-demand
|
||||
try {
|
||||
let service: AIService | null = null;
|
||||
|
||||
switch (providerName) {
|
||||
case 'openai':
|
||||
const openaiApiKey = await options.getOption('openaiApiKey');
|
||||
if (openaiApiKey) {
|
||||
this.services.openai = new OpenAIService();
|
||||
log.info('Created OpenAI chat provider on-demand');
|
||||
return this.services.openai;
|
||||
}
|
||||
break;
|
||||
case 'openai': {
|
||||
const apiKey = await options.getOption('openaiApiKey');
|
||||
const baseUrl = await options.getOption('openaiBaseUrl');
|
||||
if (!apiKey && !baseUrl) return null;
|
||||
|
||||
case 'anthropic':
|
||||
const anthropicApiKey = await options.getOption('anthropicApiKey');
|
||||
if (anthropicApiKey) {
|
||||
this.services.anthropic = new AnthropicService();
|
||||
log.info('Created Anthropic chat provider on-demand');
|
||||
return this.services.anthropic;
|
||||
service = new OpenAIService();
|
||||
// Validate by checking if it's available
|
||||
if (!service.isAvailable()) {
|
||||
throw new Error('OpenAI service not available');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ollama':
|
||||
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;
|
||||
case 'anthropic': {
|
||||
const apiKey = await options.getOption('anthropicApiKey');
|
||||
if (!apiKey) return null;
|
||||
|
||||
service = new AnthropicService();
|
||||
if (!service.isAvailable()) {
|
||||
throw new Error('Anthropic service not available');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ollama': {
|
||||
const baseUrl = await options.getOption('ollamaBaseUrl');
|
||||
if (!baseUrl) return null;
|
||||
|
||||
service = new OllamaService();
|
||||
if (!service.isAvailable()) {
|
||||
throw new Error('Ollama service not available');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (service) {
|
||||
this.services[providerName] = service;
|
||||
log.info(`Created and validated ${providerName} chat provider`);
|
||||
return service;
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(`Error creating ${providerName} provider on-demand: ${error.message || 'Unknown error'}`);
|
||||
log.error(`Failed to create ${providerName} chat provider: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -48,53 +48,16 @@ export class IndexService {
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// Check if database is initialized before proceeding
|
||||
if (!sqlInit.isDbInitialized()) {
|
||||
log.info("Index service: Database not initialized yet, skipping initialization");
|
||||
return;
|
||||
}
|
||||
// Setup event listeners for note changes
|
||||
this.setupEventListeners();
|
||||
|
||||
const aiEnabled = options.getOptionOrNull('aiEnabled') === "true";
|
||||
if (!aiEnabled) {
|
||||
log.info("Index service: AI features disabled, skipping initialization");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if embedding system is ready
|
||||
if (!(await hasWorkingEmbeddingProviders())) {
|
||||
log.info("Index service: No working embedding providers available, skipping initialization");
|
||||
return;
|
||||
}
|
||||
|
||||
const providers = await providerManager.getEnabledEmbeddingProviders();
|
||||
if (!providers || providers.length === 0) {
|
||||
log.info("Index service: No enabled embedding providers, skipping initialization");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this instance should process embeddings
|
||||
const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client';
|
||||
const isSyncServer = await this.isSyncServerForEmbeddings();
|
||||
const shouldProcessEmbeddings = embeddingLocation === 'client' || isSyncServer;
|
||||
|
||||
// Setup automatic indexing if enabled and this instance should process embeddings
|
||||
if (await options.getOptionBool('embeddingAutoUpdateEnabled') && shouldProcessEmbeddings) {
|
||||
this.setupAutomaticIndexing();
|
||||
log.info(`Index service: Automatic indexing enabled, processing embeddings ${isSyncServer ? 'as sync server' : 'as client'}`);
|
||||
} else if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
|
||||
log.info("Index service: Automatic indexing enabled, but this instance is not configured to process embeddings");
|
||||
}
|
||||
|
||||
// Listen for note changes to update index
|
||||
this.setupEventListeners();
|
||||
|
||||
this.initialized = true;
|
||||
log.info("Index service initialized successfully");
|
||||
} catch (error: any) {
|
||||
log.error(`Error initializing index service: ${error.message || "Unknown error"}`);
|
||||
throw error;
|
||||
// Setup automatic indexing if enabled
|
||||
if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
|
||||
this.setupAutomaticIndexing();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
log.info("Index service initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,23 +110,7 @@ export class IndexService {
|
||||
this.automaticIndexingInterval = setInterval(async () => {
|
||||
try {
|
||||
if (!this.indexingInProgress) {
|
||||
// Check if this instance should process embeddings
|
||||
const embeddingLocation = await options.getOption('embeddingGenerationLocation') || 'client';
|
||||
const isSyncServer = await this.isSyncServerForEmbeddings();
|
||||
const shouldProcessEmbeddings = embeddingLocation === 'client' || isSyncServer;
|
||||
|
||||
if (!shouldProcessEmbeddings) {
|
||||
// This instance is not configured to process embeddings
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = await vectorStore.getEmbeddingStats();
|
||||
|
||||
// Only run automatic indexing if we're below 95% completion
|
||||
if (stats.percentComplete < 95) {
|
||||
log.info(`Starting automatic indexing (current completion: ${stats.percentComplete}%)`);
|
||||
await this.runBatchIndexing(50); // Process 50 notes at a time
|
||||
}
|
||||
await this.runBatchIndexing(50); // Processing logic handles sync server checks
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(`Error in automatic indexing: ${error.message || "Unknown error"}`);
|
||||
@ -498,35 +445,14 @@ export class IndexService {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all enabled embedding providers
|
||||
const providers = await providerManager.getEnabledEmbeddingProviders();
|
||||
if (!providers || providers.length === 0) {
|
||||
throw new Error("No embedding providers available");
|
||||
}
|
||||
|
||||
// Get the selected embedding provider
|
||||
const options = (await import('../options.js')).default;
|
||||
// Get the selected embedding provider on-demand
|
||||
const selectedEmbeddingProvider = await options.getOption('embeddingSelectedProvider');
|
||||
let provider;
|
||||
|
||||
if (selectedEmbeddingProvider) {
|
||||
// Try to use the selected provider
|
||||
const enabledProviders = await providerManager.getEnabledEmbeddingProviders();
|
||||
provider = enabledProviders.find(p => p.name === selectedEmbeddingProvider);
|
||||
|
||||
if (!provider) {
|
||||
log.info(`Selected embedding provider ${selectedEmbeddingProvider} is not available, using first enabled provider`);
|
||||
// Fall back to first enabled provider
|
||||
provider = providers[0];
|
||||
}
|
||||
} else {
|
||||
// No provider selected, use first available provider
|
||||
log.info('No embedding provider selected, using first available provider');
|
||||
provider = providers[0];
|
||||
}
|
||||
const provider = selectedEmbeddingProvider
|
||||
? await providerManager.getOrCreateEmbeddingProvider(selectedEmbeddingProvider)
|
||||
: (await providerManager.getEnabledEmbeddingProviders())[0];
|
||||
|
||||
if (!provider) {
|
||||
throw new Error("No suitable embedding provider found");
|
||||
throw new Error("No embedding provider available");
|
||||
}
|
||||
|
||||
log.info(`Searching with embedding provider: ${provider.name}, model: ${provider.getConfig().model}`);
|
||||
@ -684,6 +610,12 @@ export class IndexService {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get embedding providers on-demand
|
||||
const providers = await providerManager.getEnabledEmbeddingProviders();
|
||||
if (providers.length === 0) {
|
||||
return "I don't have access to your note embeddings. Please configure an embedding provider in your AI settings.";
|
||||
}
|
||||
|
||||
// Find similar notes to the query
|
||||
const similarNotes = await this.findSimilarNotes(
|
||||
query,
|
||||
@ -819,9 +751,13 @@ export class IndexService {
|
||||
// Get complete note context for indexing
|
||||
const context = await vectorStore.getNoteEmbeddingContext(noteId);
|
||||
|
||||
// Queue note for embedding with all available providers
|
||||
const providers = await providerManager.getEnabledEmbeddingProviders();
|
||||
for (const provider of providers) {
|
||||
// Generate embedding with the selected provider
|
||||
const selectedEmbeddingProvider = await options.getOption('embeddingSelectedProvider');
|
||||
const provider = selectedEmbeddingProvider
|
||||
? await providerManager.getOrCreateEmbeddingProvider(selectedEmbeddingProvider)
|
||||
: (await providerManager.getEnabledEmbeddingProviders())[0];
|
||||
|
||||
if (provider) {
|
||||
try {
|
||||
const embedding = await provider.generateNoteEmbeddings(context);
|
||||
if (embedding) {
|
||||
@ -873,16 +809,13 @@ export class IndexService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify providers are available
|
||||
if (!(await hasWorkingEmbeddingProviders())) {
|
||||
throw new Error("No working embedding providers available");
|
||||
}
|
||||
|
||||
// Get embedding providers (will be created on-demand when needed)
|
||||
const providers = await providerManager.getEnabledEmbeddingProviders();
|
||||
if (providers.length === 0) {
|
||||
throw new Error("No embedding providers available");
|
||||
log.info("No embedding providers configured, but continuing initialization");
|
||||
} else {
|
||||
log.info(`Found ${providers.length} embedding providers: ${providers.map(p => p.name).join(', ')}`);
|
||||
}
|
||||
log.info(`Found ${providers.length} embedding providers: ${providers.map(p => p.name).join(', ')}`);
|
||||
|
||||
// Setup automatic indexing if enabled
|
||||
if (await options.getOptionBool('embeddingAutoUpdateEnabled')) {
|
||||
|
@ -18,7 +18,7 @@ export interface ProviderValidationResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all available providers without throwing errors
|
||||
* Simplified provider validation - just checks configuration without creating providers
|
||||
*/
|
||||
export async function validateProviders(): Promise<ProviderValidationResult> {
|
||||
const result: ProviderValidationResult = {
|
||||
@ -37,14 +37,12 @@ export async function validateProviders(): Promise<ProviderValidationResult> {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Validate embedding providers
|
||||
await validateEmbeddingProviders(result);
|
||||
// Check configuration only - don't create providers
|
||||
await checkEmbeddingProviderConfigs(result);
|
||||
await checkChatProviderConfigs(result);
|
||||
|
||||
// Validate chat providers
|
||||
await validateChatProviders(result);
|
||||
|
||||
// Determine if we have any valid providers
|
||||
result.hasValidProviders = result.validEmbeddingProviders.length > 0 || result.validChatProviders.length > 0;
|
||||
// Determine if we have any valid providers based on configuration
|
||||
result.hasValidProviders = result.validChatProviders.length > 0;
|
||||
|
||||
if (!result.hasValidProviders) {
|
||||
result.errors.push("No valid AI providers are configured");
|
||||
@ -58,241 +56,80 @@ export async function validateProviders(): Promise<ProviderValidationResult> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate embedding providers
|
||||
* Check embedding provider configurations without creating providers
|
||||
*/
|
||||
async function validateEmbeddingProviders(result: ProviderValidationResult): Promise<void> {
|
||||
async function checkEmbeddingProviderConfigs(result: ProviderValidationResult): Promise<void> {
|
||||
try {
|
||||
// Import provider classes and check configurations
|
||||
const { OpenAIEmbeddingProvider } = await import("./embeddings/providers/openai.js");
|
||||
const { OllamaEmbeddingProvider } = await import("./embeddings/providers/ollama.js");
|
||||
const { VoyageEmbeddingProvider } = await import("./embeddings/providers/voyage.js");
|
||||
// Check OpenAI embedding configuration
|
||||
const openaiApiKey = await options.getOption('openaiApiKey');
|
||||
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
||||
if (openaiApiKey || openaiBaseUrl) {
|
||||
if (!openaiApiKey) {
|
||||
result.warnings.push("OpenAI embedding: No API key (may work with compatible endpoints)");
|
||||
}
|
||||
log.info("OpenAI embedding provider configuration available");
|
||||
}
|
||||
|
||||
// Check OpenAI embedding provider
|
||||
await validateOpenAIEmbeddingProvider(result, OpenAIEmbeddingProvider);
|
||||
// Check Ollama embedding configuration
|
||||
const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
||||
if (ollamaEmbeddingBaseUrl) {
|
||||
log.info("Ollama embedding provider configuration available");
|
||||
}
|
||||
|
||||
// Check Ollama embedding provider
|
||||
await validateOllamaEmbeddingProvider(result, OllamaEmbeddingProvider);
|
||||
// Check Voyage embedding configuration
|
||||
const voyageApiKey = await options.getOption('voyageApiKey' as any);
|
||||
if (voyageApiKey) {
|
||||
log.info("Voyage embedding provider configuration available");
|
||||
}
|
||||
|
||||
// Check Voyage embedding provider
|
||||
await validateVoyageEmbeddingProvider(result, VoyageEmbeddingProvider);
|
||||
|
||||
// Local provider is always available as fallback
|
||||
await validateLocalEmbeddingProvider(result);
|
||||
// Local provider is always available
|
||||
log.info("Local embedding provider available as fallback");
|
||||
|
||||
} catch (error: any) {
|
||||
result.errors.push(`Error validating embedding providers: ${error.message || 'Unknown error'}`);
|
||||
result.errors.push(`Error checking embedding provider configs: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate chat providers
|
||||
* Check chat provider configurations without creating providers
|
||||
*/
|
||||
async function validateChatProviders(result: ProviderValidationResult): Promise<void> {
|
||||
async function checkChatProviderConfigs(result: ProviderValidationResult): Promise<void> {
|
||||
try {
|
||||
// Check OpenAI chat provider
|
||||
const openaiApiKey = await options.getOption('openaiApiKey');
|
||||
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
||||
|
||||
if (openaiApiKey || openaiBaseUrl) {
|
||||
if (!openaiApiKey && !openaiBaseUrl) {
|
||||
result.warnings.push("OpenAI chat provider: No API key or base URL configured");
|
||||
} else if (!openaiApiKey) {
|
||||
result.warnings.push("OpenAI chat provider: No API key configured (may work with compatible endpoints)");
|
||||
result.validChatProviders.push('openai');
|
||||
} else {
|
||||
result.validChatProviders.push('openai');
|
||||
if (!openaiApiKey) {
|
||||
result.warnings.push("OpenAI chat: No API key (may work with compatible endpoints)");
|
||||
}
|
||||
result.validChatProviders.push('openai');
|
||||
}
|
||||
|
||||
// Check Anthropic chat provider
|
||||
const anthropicApiKey = await options.getOption('anthropicApiKey');
|
||||
if (anthropicApiKey) {
|
||||
result.validChatProviders.push('anthropic');
|
||||
} else {
|
||||
result.warnings.push("Anthropic chat provider: No API key configured");
|
||||
}
|
||||
|
||||
// Check Ollama chat provider
|
||||
const ollamaBaseUrl = await options.getOption('ollamaBaseUrl');
|
||||
if (ollamaBaseUrl) {
|
||||
result.validChatProviders.push('ollama');
|
||||
} else {
|
||||
result.warnings.push("Ollama chat provider: No base URL configured");
|
||||
}
|
||||
|
||||
if (result.validChatProviders.length === 0) {
|
||||
result.warnings.push("No chat providers configured. Please configure at least one provider.");
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
result.errors.push(`Error validating chat providers: ${error.message || 'Unknown error'}`);
|
||||
result.errors.push(`Error checking chat provider configs: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate OpenAI embedding provider
|
||||
*/
|
||||
async function validateOpenAIEmbeddingProvider(
|
||||
result: ProviderValidationResult,
|
||||
OpenAIEmbeddingProvider: any
|
||||
): Promise<void> {
|
||||
try {
|
||||
const openaiApiKey = await options.getOption('openaiApiKey');
|
||||
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
||||
|
||||
if (openaiApiKey || openaiBaseUrl) {
|
||||
const openaiModel = await options.getOption('openaiEmbeddingModel');
|
||||
const finalBaseUrl = openaiBaseUrl || 'https://api.openai.com/v1';
|
||||
|
||||
if (!openaiApiKey) {
|
||||
result.warnings.push("OpenAI embedding provider: No API key configured (may work with compatible endpoints)");
|
||||
}
|
||||
|
||||
const provider = new OpenAIEmbeddingProvider({
|
||||
model: openaiModel,
|
||||
dimension: 1536,
|
||||
type: 'float32',
|
||||
apiKey: openaiApiKey || '',
|
||||
baseUrl: finalBaseUrl
|
||||
});
|
||||
|
||||
result.validEmbeddingProviders.push(provider);
|
||||
log.info(`Validated OpenAI embedding provider: ${openaiModel} at ${finalBaseUrl}`);
|
||||
} else {
|
||||
result.warnings.push("OpenAI embedding provider: No API key or base URL configured");
|
||||
}
|
||||
} catch (error: any) {
|
||||
result.errors.push(`OpenAI embedding provider validation failed: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Ollama embedding provider
|
||||
*/
|
||||
async function validateOllamaEmbeddingProvider(
|
||||
result: ProviderValidationResult,
|
||||
OllamaEmbeddingProvider: any
|
||||
): Promise<void> {
|
||||
try {
|
||||
const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
||||
|
||||
if (ollamaEmbeddingBaseUrl) {
|
||||
const embeddingModel = await options.getOption('ollamaEmbeddingModel');
|
||||
|
||||
try {
|
||||
const provider = new OllamaEmbeddingProvider({
|
||||
model: embeddingModel,
|
||||
dimension: 768,
|
||||
type: 'float32',
|
||||
baseUrl: ollamaEmbeddingBaseUrl
|
||||
});
|
||||
|
||||
// Try to initialize to validate connection
|
||||
await provider.initialize();
|
||||
result.validEmbeddingProviders.push(provider);
|
||||
log.info(`Validated Ollama embedding provider: ${embeddingModel} at ${ollamaEmbeddingBaseUrl}`);
|
||||
} catch (error: any) {
|
||||
result.warnings.push(`Ollama embedding provider initialization failed: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
} else {
|
||||
result.warnings.push("Ollama embedding provider: No base URL configured");
|
||||
}
|
||||
} catch (error: any) {
|
||||
result.errors.push(`Ollama embedding provider validation failed: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Voyage embedding provider
|
||||
*/
|
||||
async function validateVoyageEmbeddingProvider(
|
||||
result: ProviderValidationResult,
|
||||
VoyageEmbeddingProvider: any
|
||||
): Promise<void> {
|
||||
try {
|
||||
const voyageApiKey = await options.getOption('voyageApiKey' as any);
|
||||
|
||||
if (voyageApiKey) {
|
||||
const voyageModel = await options.getOption('voyageEmbeddingModel') || 'voyage-2';
|
||||
|
||||
const provider = new VoyageEmbeddingProvider({
|
||||
model: voyageModel,
|
||||
dimension: 1024,
|
||||
type: 'float32',
|
||||
apiKey: voyageApiKey,
|
||||
baseUrl: 'https://api.voyageai.com/v1'
|
||||
});
|
||||
|
||||
result.validEmbeddingProviders.push(provider);
|
||||
log.info(`Validated Voyage embedding provider: ${voyageModel}`);
|
||||
} else {
|
||||
result.warnings.push("Voyage embedding provider: No API key configured");
|
||||
}
|
||||
} catch (error: any) {
|
||||
result.errors.push(`Voyage embedding provider validation failed: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate local embedding provider (always available as fallback)
|
||||
*/
|
||||
async function validateLocalEmbeddingProvider(result: ProviderValidationResult): Promise<void> {
|
||||
try {
|
||||
// Simple local embedding provider implementation
|
||||
class SimpleLocalEmbeddingProvider {
|
||||
name = "local";
|
||||
config = {
|
||||
model: 'local',
|
||||
dimension: 384,
|
||||
type: 'float32' as const
|
||||
};
|
||||
|
||||
getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
getNormalizationStatus() {
|
||||
return 0; // NormalizationStatus.NEVER
|
||||
}
|
||||
|
||||
async generateEmbeddings(text: string): Promise<Float32Array> {
|
||||
const result = new Float32Array(this.config.dimension);
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const charSum = Array.from(text).reduce((sum, char, idx) =>
|
||||
sum + char.charCodeAt(0) * Math.sin(idx * 0.1), 0);
|
||||
result[i] = Math.sin(i * 0.1 + charSum * 0.01);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async generateBatchEmbeddings(texts: string[]): Promise<Float32Array[]> {
|
||||
return Promise.all(texts.map(text => this.generateEmbeddings(text)));
|
||||
}
|
||||
|
||||
async generateNoteEmbeddings(context: any): Promise<Float32Array> {
|
||||
const text = (context.title || "") + " " + (context.content || "");
|
||||
return this.generateEmbeddings(text);
|
||||
}
|
||||
|
||||
async generateBatchNoteEmbeddings(contexts: any[]): Promise<Float32Array[]> {
|
||||
return Promise.all(contexts.map(context => this.generateNoteEmbeddings(context)));
|
||||
}
|
||||
}
|
||||
|
||||
const localProvider = new SimpleLocalEmbeddingProvider();
|
||||
result.validEmbeddingProviders.push(localProvider as any);
|
||||
log.info("Validated local embedding provider as fallback");
|
||||
} catch (error: any) {
|
||||
result.errors.push(`Local embedding provider validation failed: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any working providers are available for embeddings
|
||||
*/
|
||||
export async function hasWorkingEmbeddingProviders(): Promise<boolean> {
|
||||
const validation = await validateProviders();
|
||||
return validation.validEmbeddingProviders.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any working providers are available for chat
|
||||
* Check if any chat providers are configured
|
||||
*/
|
||||
export async function hasWorkingChatProviders(): Promise<boolean> {
|
||||
const validation = await validateProviders();
|
||||
@ -300,11 +137,21 @@ export async function hasWorkingChatProviders(): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only the working embedding providers
|
||||
* Check if any embedding providers are configured (simplified)
|
||||
*/
|
||||
export async function getWorkingEmbeddingProviders(): Promise<EmbeddingProvider[]> {
|
||||
const validation = await validateProviders();
|
||||
return validation.validEmbeddingProviders;
|
||||
export async function hasWorkingEmbeddingProviders(): Promise<boolean> {
|
||||
if (!(await options.getOptionBool('aiEnabled'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any embedding provider is configured
|
||||
const openaiKey = await options.getOption('openaiApiKey');
|
||||
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
||||
const ollamaUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
||||
const voyageKey = await options.getOption('voyageApiKey' as any);
|
||||
|
||||
// Local provider is always available as fallback
|
||||
return !!(openaiKey || openaiBaseUrl || ollamaUrl || voyageKey) || true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,118 +124,129 @@ export function getEmbeddingProvider(name: string): EmbeddingProvider | undefine
|
||||
}
|
||||
|
||||
/**
|
||||
* Create providers on-demand based on current options
|
||||
* Get or create a specific embedding provider with inline validation
|
||||
*/
|
||||
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 even without API key (for OpenAI-compatible endpoints)
|
||||
const openaiApiKey = await options.getOption('openaiApiKey');
|
||||
const openaiBaseUrl = await options.getOption('openaiBaseUrl');
|
||||
|
||||
// Only create OpenAI provider if base URL is set or API key is provided
|
||||
if (openaiApiKey || openaiBaseUrl) {
|
||||
const openaiModel = await options.getOption('openaiEmbeddingModel')
|
||||
const finalBaseUrl = openaiBaseUrl || 'https://api.openai.com/v1';
|
||||
|
||||
if (!openaiApiKey) {
|
||||
log.info('Creating OpenAI embedding provider without API key. This may cause issues with official OpenAI endpoints.');
|
||||
}
|
||||
|
||||
const openaiProvider = new OpenAIEmbeddingProvider({
|
||||
model: openaiModel,
|
||||
dimension: 1536,
|
||||
type: 'float32',
|
||||
apiKey: openaiApiKey || '', // Default to empty string
|
||||
baseUrl: finalBaseUrl
|
||||
});
|
||||
|
||||
registerEmbeddingProvider(openaiProvider);
|
||||
result.push(openaiProvider);
|
||||
log.info(`Created OpenAI provider on-demand: ${openaiModel} at ${finalBaseUrl}`);
|
||||
}
|
||||
|
||||
// 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'}`);
|
||||
export async function getOrCreateEmbeddingProvider(providerName: string): Promise<EmbeddingProvider | null> {
|
||||
// Return existing provider if already created and valid
|
||||
const existing = providers.get(providerName);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return result;
|
||||
// Create and validate provider on-demand
|
||||
try {
|
||||
let provider: EmbeddingProvider | null = null;
|
||||
|
||||
switch (providerName) {
|
||||
case 'ollama': {
|
||||
const baseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
||||
if (!baseUrl) return null;
|
||||
|
||||
const model = await options.getOption('ollamaEmbeddingModel');
|
||||
provider = new OllamaEmbeddingProvider({
|
||||
model,
|
||||
dimension: 768,
|
||||
type: 'float32',
|
||||
baseUrl
|
||||
});
|
||||
|
||||
// Validate by initializing (if provider supports it)
|
||||
if ('initialize' in provider && typeof provider.initialize === 'function') {
|
||||
await provider.initialize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'openai': {
|
||||
const apiKey = await options.getOption('openaiApiKey');
|
||||
const baseUrl = await options.getOption('openaiBaseUrl');
|
||||
if (!apiKey && !baseUrl) return null;
|
||||
|
||||
const model = await options.getOption('openaiEmbeddingModel');
|
||||
provider = new OpenAIEmbeddingProvider({
|
||||
model,
|
||||
dimension: 1536,
|
||||
type: 'float32',
|
||||
apiKey: apiKey || '',
|
||||
baseUrl: baseUrl || 'https://api.openai.com/v1'
|
||||
});
|
||||
|
||||
if (!apiKey) {
|
||||
log.info('OpenAI embedding provider created without API key for compatible endpoints');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'voyage': {
|
||||
const apiKey = await options.getOption('voyageApiKey' as any);
|
||||
if (!apiKey) return null;
|
||||
|
||||
const model = await options.getOption('voyageEmbeddingModel') || 'voyage-2';
|
||||
provider = new VoyageEmbeddingProvider({
|
||||
model,
|
||||
dimension: 1024,
|
||||
type: 'float32',
|
||||
apiKey,
|
||||
baseUrl: 'https://api.voyageai.com/v1'
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'local': {
|
||||
provider = new SimpleLocalEmbeddingProvider({
|
||||
model: 'local',
|
||||
dimension: 384,
|
||||
type: 'float32'
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
registerEmbeddingProvider(provider);
|
||||
log.info(`Created and validated ${providerName} embedding provider`);
|
||||
return provider;
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(`Failed to create ${providerName} embedding provider: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled embedding providers
|
||||
* Get all enabled embedding providers for the specified feature
|
||||
*/
|
||||
export async function getEnabledEmbeddingProviders(): Promise<EmbeddingProvider[]> {
|
||||
export async function getEnabledEmbeddingProviders(feature: 'embeddings' | 'chat' = 'embeddings'): Promise<EmbeddingProvider[]> {
|
||||
if (!(await options.getOptionBool('aiEnabled'))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// First try to get existing registered providers
|
||||
const existingProviders = Array.from(providers.values());
|
||||
const result: EmbeddingProvider[] = [];
|
||||
|
||||
// If no providers are registered, create them on-demand from current options
|
||||
if (existingProviders.length === 0) {
|
||||
log.info('No providers registered, creating from current options');
|
||||
return await createProvidersFromCurrentOptions();
|
||||
// Get the selected provider for the feature
|
||||
const selectedProvider = feature === 'embeddings'
|
||||
? await options.getOption('embeddingSelectedProvider')
|
||||
: await options.getOption('aiSelectedProvider');
|
||||
|
||||
// Try to get or create the specific selected provider
|
||||
const provider = await getOrCreateEmbeddingProvider(selectedProvider);
|
||||
if (!provider) {
|
||||
throw new Error(`Failed to create selected embedding provider: ${selectedProvider}. Please check your configuration.`);
|
||||
}
|
||||
result.push(provider);
|
||||
|
||||
|
||||
// Always ensure local provider as fallback
|
||||
const localProvider = await getOrCreateEmbeddingProvider('local');
|
||||
if (localProvider && !result.some(p => p.name === 'local')) {
|
||||
result.push(localProvider);
|
||||
}
|
||||
|
||||
return existingProviders;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -342,7 +353,7 @@ export default {
|
||||
getEmbeddingProviders,
|
||||
getEmbeddingProvider,
|
||||
getEnabledEmbeddingProviders,
|
||||
createProvidersFromCurrentOptions,
|
||||
getOrCreateEmbeddingProvider,
|
||||
createEmbeddingProviderConfig,
|
||||
updateEmbeddingProviderConfig,
|
||||
deleteEmbeddingProviderConfig,
|
||||
|
Loading…
x
Reference in New Issue
Block a user