refactor(llm): implement new configuration methods for provider order and validation, enhancing error handling and deprecating legacy functions

This commit is contained in:
perf3ct 2025-06-02 21:49:35 +00:00
parent 5a5a69ebb8
commit 3a55735cd5
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
2 changed files with 100 additions and 118 deletions

View File

@ -26,7 +26,8 @@ import {
parseModelIdentifier, parseModelIdentifier,
isAIEnabled, isAIEnabled,
getDefaultModelForProvider, getDefaultModelForProvider,
clearConfigurationCache clearConfigurationCache,
validateConfiguration
} from './config/configuration_helpers.js'; } from './config/configuration_helpers.js';
import type { ProviderType } from './interfaces/configuration_interfaces.js'; import type { ProviderType } from './interfaces/configuration_interfaces.js';
@ -48,7 +49,7 @@ export class AIServiceManager implements IAIServiceManager {
ollama: new OllamaService() ollama: new OllamaService()
}; };
private providerOrder: ServiceProviders[] = ['openai', 'anthropic', 'ollama']; // Default order private providerOrder: ServiceProviders[] = []; // Will be populated from configuration
private initialized = false; private initialized = false;
constructor() { constructor() {
@ -84,6 +85,23 @@ export class AIServiceManager implements IAIServiceManager {
/** /**
* Update the provider precedence order using the new configuration system * Update the provider precedence order using the new configuration system
*/
async updateProviderOrderAsync(): Promise<void> {
try {
const providers = await getProviderPrecedence();
this.providerOrder = providers as ServiceProviders[];
this.initialized = true;
log.info(`Updated provider order: ${providers.join(', ')}`);
} catch (error) {
log.error(`Failed to get provider precedence: ${error}`);
// Keep empty order, will be handled gracefully by other methods
this.providerOrder = [];
this.initialized = true;
}
}
/**
* Update the provider precedence order (legacy sync version)
* Returns true if successful, false if options not available yet * Returns true if successful, false if options not available yet
*/ */
updateProviderOrder(): boolean { updateProviderOrder(): boolean {
@ -91,89 +109,57 @@ export class AIServiceManager implements IAIServiceManager {
return true; return true;
} }
try { // Use async version but don't wait
// Use async helper but handle it synchronously for now this.updateProviderOrderAsync().catch(error => {
// In a real refactor, this method should become async log.error(`Error in async provider order update: ${error}`);
getProviderPrecedence().then(providers => { });
this.providerOrder = providers as ServiceProviders[];
log.info(`Updated provider order: ${providers.join(', ')}`);
}).catch(error => {
log.error(`Failed to get provider precedence: ${error}`);
// Keep default order
});
this.initialized = true; return true;
return true; }
/**
* Validate AI configuration using the new configuration system
*/
async validateConfiguration(): Promise<string | null> {
try {
const result = await validateConfiguration();
if (!result.isValid) {
let message = 'There are issues with your AI configuration:';
for (const error of result.errors) {
message += `\n• ${error}`;
}
if (result.warnings.length > 0) {
message += '\n\nWarnings:';
for (const warning of result.warnings) {
message += `\n• ${warning}`;
}
}
message += '\n\nPlease check your AI settings.';
return message;
}
if (result.warnings.length > 0) {
let message = 'AI configuration warnings:';
for (const warning of result.warnings) {
message += `\n• ${warning}`;
}
log.info(message);
}
return null;
} catch (error) { } catch (error) {
// If options table doesn't exist yet, use defaults log.error(`Error validating AI configuration: ${error}`);
// This happens during initial database creation return `Configuration validation failed: ${error}`;
this.providerOrder = ['openai', 'anthropic', 'ollama'];
return false;
} }
} }
/** /**
* Validate embedding providers configuration using the new configuration system * @deprecated Use validateConfiguration() instead
*/ */
async validateEmbeddingProviders(): Promise<string | null> { async validateEmbeddingProviders(): Promise<string | null> {
try { log.info('validateEmbeddingProviders is deprecated, use validateConfiguration instead');
// Check if AI is enabled using the new helper return this.validateConfiguration();
const aiEnabled = await isAIEnabled();
if (!aiEnabled) {
return null;
}
// Get precedence list using the new helper (no string parsing!)
const precedenceList = await getEmbeddingProviderPrecedence();
// Check for configuration issues with providers in the precedence list
const configIssues: string[] = [];
// Check each provider in the precedence list for proper configuration
for (const provider of precedenceList) {
if (provider === 'openai') {
// Check OpenAI configuration
const apiKey = await options.getOption('openaiApiKey');
if (!apiKey) {
configIssues.push(`OpenAI API key is missing`);
}
} else if (provider === 'anthropic') {
// Check Anthropic configuration
const apiKey = await options.getOption('anthropicApiKey');
if (!apiKey) {
configIssues.push(`Anthropic API key is missing`);
}
} else if (provider === 'ollama') {
// Check Ollama configuration
const baseUrl = await options.getOption('ollamaBaseUrl');
if (!baseUrl) {
configIssues.push(`Ollama Base URL is missing`);
}
}
// Add checks for other providers as needed
}
// Return warning message if there are configuration issues
if (configIssues.length > 0) {
let message = 'There are issues with your AI provider configuration:';
for (const issue of configIssues) {
message += `\n• ${issue}`;
}
message += '\n\nPlease check your AI settings.';
// Log warning to console
log.error('AI Provider Configuration Warning: ' + message);
return message;
}
return null;
} catch (error) {
log.error(`Error validating embedding providers: ${error}`);
return null;
}
} }
/** /**
@ -348,6 +334,13 @@ export class AIServiceManager implements IAIServiceManager {
/** /**
* Get whether AI features are enabled using the new configuration system * Get whether AI features are enabled using the new configuration system
*/ */
async getAIEnabledAsync(): Promise<boolean> {
return isAIEnabled();
}
/**
* Get whether AI features are enabled (sync version for compatibility)
*/
getAIEnabled(): boolean { getAIEnabled(): boolean {
// For synchronous compatibility, use the old method // For synchronous compatibility, use the old method
// In a full refactor, this should be async // In a full refactor, this should be async
@ -355,11 +348,12 @@ export class AIServiceManager implements IAIServiceManager {
} }
/** /**
* Set up embeddings provider for AI features * Set up embeddings provider using the new configuration system
*/ */
async setupEmbeddingsProvider(): Promise<void> { async setupEmbeddingsProvider(): Promise<void> {
try { try {
if (!this.getAIEnabled()) { const aiEnabled = await isAIEnabled();
if (!aiEnabled) {
log.info('AI features are disabled'); log.info('AI features are disabled');
return; return;
} }
@ -381,20 +375,23 @@ export class AIServiceManager implements IAIServiceManager {
} }
/** /**
* Initialize the AI Service * Initialize the AI Service using the new configuration system
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
try { try {
log.info("Initializing AI service..."); log.info("Initializing AI service...");
// Check if AI is enabled using the new helper // Check if AI is enabled using the new helper
const isAIEnabled_value = await isAIEnabled(); const aiEnabled = await isAIEnabled();
if (!isAIEnabled_value) { if (!aiEnabled) {
log.info("AI features are disabled in options"); log.info("AI features are disabled in options");
return; return;
} }
// Update provider order from configuration
await this.updateProviderOrderAsync();
// Set up embeddings provider if AI is enabled // Set up embeddings provider if AI is enabled
await this.setupEmbeddingsProvider(); await this.setupEmbeddingsProvider();

View File

@ -1,4 +1,4 @@
import sql from "../../sql.js"; import sql from '../../sql.js'
import { randomString } from "../../../services/utils.js"; import { randomString } from "../../../services/utils.js";
import dateUtils from "../../../services/date_utils.js"; import dateUtils from "../../../services/date_utils.js";
import log from "../../log.js"; import log from "../../log.js";
@ -11,6 +11,7 @@ import { SEARCH_CONSTANTS } from '../constants/search_constants.js';
import type { NoteEmbeddingContext } from "./embeddings_interface.js"; import type { NoteEmbeddingContext } from "./embeddings_interface.js";
import becca from "../../../becca/becca.js"; import becca from "../../../becca/becca.js";
import { isNoteExcludedFromAIById } from "../utils/ai_exclusion_utils.js"; import { isNoteExcludedFromAIById } from "../utils/ai_exclusion_utils.js";
import { getEmbeddingProviderPrecedence } from '../config/configuration_helpers.js';
interface Similarity { interface Similarity {
noteId: string; noteId: string;
@ -271,44 +272,28 @@ export async function findSimilarNotes(
} }
} }
} else { } else {
// Use dedicated embedding provider precedence from options for other strategies // Try providers using the new configuration system
let preferredProviders: string[] = []; if (useFallback) {
const embeddingPrecedence = await options.getOption('embeddingProviderPrecedence'); log.info('No embeddings found for specified provider, trying fallback providers...');
if (embeddingPrecedence) { // Use the new configuration system - no string parsing!
// For "comma,separated,values" const preferredProviders = await getEmbeddingProviderPrecedence();
if (embeddingPrecedence.includes(',')) {
preferredProviders = embeddingPrecedence.split(',').map(p => p.trim()); log.info(`Using provider precedence: ${preferredProviders.join(', ')}`);
}
// For JSON array ["value1", "value2"] // Try providers in precedence order
else if (embeddingPrecedence.startsWith('[') && embeddingPrecedence.endsWith(']')) { for (const provider of preferredProviders) {
try { const providerEmbeddings = availableEmbeddings.filter(e => e.providerId === provider);
preferredProviders = JSON.parse(embeddingPrecedence);
} catch (e) { if (providerEmbeddings.length > 0) {
log.error(`Error parsing embedding precedence: ${e}`); // Choose the model with the most embeddings
preferredProviders = [embeddingPrecedence]; // Fallback to using as single value const bestModel = providerEmbeddings.sort((a, b) => b.count - a.count)[0];
log.info(`Found fallback provider: ${provider}, model: ${bestModel.modelId}, dimension: ${bestModel.dimension}`);
// The 'regenerate' strategy would go here if needed
// We're no longer supporting the 'adapt' strategy
} }
} }
// For a single value
else {
preferredProviders = [embeddingPrecedence];
}
}
log.info(`Using provider precedence: ${preferredProviders.join(', ')}`);
// Try providers in precedence order
for (const provider of preferredProviders) {
const providerEmbeddings = availableEmbeddings.filter(e => e.providerId === provider);
if (providerEmbeddings.length > 0) {
// Choose the model with the most embeddings
const bestModel = providerEmbeddings.sort((a, b) => b.count - a.count)[0];
log.info(`Found fallback provider: ${provider}, model: ${bestModel.modelId}, dimension: ${bestModel.dimension}`);
// The 'regenerate' strategy would go here if needed
// We're no longer supporting the 'adapt' strategy
}
} }
} }
} }