|
|
|
|
@ -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);
|
|
|
|
|
|
|
|
|
|
// Validate chat providers
|
|
|
|
|
await validateChatProviders(result);
|
|
|
|
|
// Check configuration only - don't create providers
|
|
|
|
|
await checkEmbeddingProviderConfigs(result);
|
|
|
|
|
await checkChatProviderConfigs(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 provider
|
|
|
|
|
await validateOllamaEmbeddingProvider(result, OllamaEmbeddingProvider);
|
|
|
|
|
|
|
|
|
|
// Check Voyage embedding provider
|
|
|
|
|
await validateVoyageEmbeddingProvider(result, VoyageEmbeddingProvider);
|
|
|
|
|
// Check Ollama embedding configuration
|
|
|
|
|
const ollamaEmbeddingBaseUrl = await options.getOption('ollamaEmbeddingBaseUrl');
|
|
|
|
|
if (ollamaEmbeddingBaseUrl) {
|
|
|
|
|
log.info("Ollama embedding provider configuration available");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check Voyage embedding configuration
|
|
|
|
|
const voyageApiKey = await options.getOption('voyageApiKey' as any);
|
|
|
|
|
if (voyageApiKey) {
|
|
|
|
|
log.info("Voyage embedding provider configuration available");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
result.errors.push(`Error validating chat providers: ${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");
|
|
|
|
|
if (result.validChatProviders.length === 0) {
|
|
|
|
|
result.warnings.push("No chat providers configured. Please configure at least one provider.");
|
|
|
|
|
}
|
|
|
|
|
} 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'}`);
|
|
|
|
|
result.errors.push(`Error checking chat provider configs: ${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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|