mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
refactor(llm): enhance configuration handling to avoid default assumptions and improve error handling
This commit is contained in:
parent
45175b6af3
commit
ce7c4a31a1
@ -532,7 +532,13 @@ export class AIServiceManager implements IAIServiceManager {
|
|||||||
*/
|
*/
|
||||||
async getPreferredProviderAsync(): Promise<string> {
|
async getPreferredProviderAsync(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return await getPreferredProvider();
|
const preferredProvider = await getPreferredProvider();
|
||||||
|
if (preferredProvider === null) {
|
||||||
|
// No providers configured, fallback to first available
|
||||||
|
log.info('No providers configured in precedence, using first available provider');
|
||||||
|
return this.providerOrder[0];
|
||||||
|
}
|
||||||
|
return preferredProvider;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error getting preferred provider: ${error}`);
|
log.error(`Error getting preferred provider: ${error}`);
|
||||||
return this.providerOrder[0];
|
return this.providerOrder[0];
|
||||||
|
@ -23,8 +23,11 @@ export async function getProviderPrecedence(): Promise<ProviderType[]> {
|
|||||||
/**
|
/**
|
||||||
* Get the default/preferred AI provider
|
* Get the default/preferred AI provider
|
||||||
*/
|
*/
|
||||||
export async function getPreferredProvider(): Promise<ProviderType> {
|
export async function getPreferredProvider(): Promise<ProviderType | null> {
|
||||||
const config = await configurationManager.getProviderPrecedence();
|
const config = await configurationManager.getProviderPrecedence();
|
||||||
|
if (config.providers.length === 0) {
|
||||||
|
return null; // No providers configured
|
||||||
|
}
|
||||||
return config.defaultProvider || config.providers[0];
|
return config.defaultProvider || config.providers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +42,11 @@ export async function getEmbeddingProviderPrecedence(): Promise<string[]> {
|
|||||||
/**
|
/**
|
||||||
* Get the default embedding provider
|
* Get the default embedding provider
|
||||||
*/
|
*/
|
||||||
export async function getPreferredEmbeddingProvider(): Promise<string> {
|
export async function getPreferredEmbeddingProvider(): Promise<string | null> {
|
||||||
const config = await configurationManager.getEmbeddingProviderPrecedence();
|
const config = await configurationManager.getEmbeddingProviderPrecedence();
|
||||||
|
if (config.providers.length === 0) {
|
||||||
|
return null; // No providers configured
|
||||||
|
}
|
||||||
return config.defaultProvider || config.providers[0];
|
return config.defaultProvider || config.providers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +67,9 @@ export function createModelConfig(modelString: string, defaultProvider?: Provide
|
|||||||
/**
|
/**
|
||||||
* Get the default model for a specific provider
|
* Get the default model for a specific provider
|
||||||
*/
|
*/
|
||||||
export async function getDefaultModelForProvider(provider: ProviderType): Promise<string> {
|
export async function getDefaultModelForProvider(provider: ProviderType): Promise<string | undefined> {
|
||||||
const config = await configurationManager.getAIConfig();
|
const config = await configurationManager.getAIConfig();
|
||||||
return config.defaultModels[provider];
|
return config.defaultModels[provider]; // This can now be undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,13 +112,17 @@ export async function isProviderConfigured(provider: ProviderType): Promise<bool
|
|||||||
export async function getFirstAvailableProvider(): Promise<ProviderType | null> {
|
export async function getFirstAvailableProvider(): Promise<ProviderType | null> {
|
||||||
const providers = await getProviderPrecedence();
|
const providers = await getProviderPrecedence();
|
||||||
|
|
||||||
|
if (providers.length === 0) {
|
||||||
|
return null; // No providers configured
|
||||||
|
}
|
||||||
|
|
||||||
for (const provider of providers) {
|
for (const provider of providers) {
|
||||||
if (await isProviderConfigured(provider)) {
|
if (await isProviderConfigured(provider)) {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null; // No providers are properly configured
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,3 +138,42 @@ export async function validateConfiguration() {
|
|||||||
export function clearConfigurationCache(): void {
|
export function clearConfigurationCache(): void {
|
||||||
configurationManager.clearCache();
|
configurationManager.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a model configuration with validation that no defaults are assumed
|
||||||
|
*/
|
||||||
|
export async function getValidModelConfig(provider: ProviderType): Promise<{ model: string; provider: ProviderType } | null> {
|
||||||
|
const defaultModel = await getDefaultModelForProvider(provider);
|
||||||
|
|
||||||
|
if (!defaultModel) {
|
||||||
|
// No default model configured for this provider
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isConfigured = await isProviderConfigured(provider);
|
||||||
|
if (!isConfigured) {
|
||||||
|
// Provider is not properly configured
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
model: defaultModel,
|
||||||
|
provider
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first valid model configuration from the provider precedence list
|
||||||
|
*/
|
||||||
|
export async function getFirstValidModelConfig(): Promise<{ model: string; provider: ProviderType } | null> {
|
||||||
|
const providers = await getProviderPrecedence();
|
||||||
|
|
||||||
|
for (const provider of providers) {
|
||||||
|
const config = await getValidModelConfig(provider);
|
||||||
|
if (config) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // No valid model configuration found
|
||||||
|
}
|
||||||
|
@ -75,13 +75,14 @@ export class ConfigurationManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
providers: providers as ProviderType[],
|
providers: providers as ProviderType[],
|
||||||
defaultProvider: providers[0] as ProviderType
|
defaultProvider: providers.length > 0 ? providers[0] as ProviderType : undefined
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error parsing provider precedence: ${error}`);
|
log.error(`Error parsing provider precedence: ${error}`);
|
||||||
|
// Only return known providers if they exist, don't assume defaults
|
||||||
return {
|
return {
|
||||||
providers: ['openai', 'anthropic', 'ollama'],
|
providers: [],
|
||||||
defaultProvider: 'openai'
|
defaultProvider: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,13 +97,14 @@ export class ConfigurationManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
providers: providers as EmbeddingProviderType[],
|
providers: providers as EmbeddingProviderType[],
|
||||||
defaultProvider: providers[0] as EmbeddingProviderType
|
defaultProvider: providers.length > 0 ? providers[0] as EmbeddingProviderType : undefined
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error parsing embedding provider precedence: ${error}`);
|
log.error(`Error parsing embedding provider precedence: ${error}`);
|
||||||
|
// Don't assume defaults, return empty configuration
|
||||||
return {
|
return {
|
||||||
providers: ['openai', 'ollama'],
|
providers: [],
|
||||||
defaultProvider: 'openai'
|
defaultProvider: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,9 +169,9 @@ export class ConfigurationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default models for each provider
|
* Get default models for each provider - ONLY from user configuration
|
||||||
*/
|
*/
|
||||||
public async getDefaultModels(): Promise<Record<ProviderType, string>> {
|
public async getDefaultModels(): Promise<Record<ProviderType, string | undefined>> {
|
||||||
try {
|
try {
|
||||||
const [openaiModel, anthropicModel, ollamaModel] = await Promise.all([
|
const [openaiModel, anthropicModel, ollamaModel] = await Promise.all([
|
||||||
options.getOption('openaiDefaultModel'),
|
options.getOption('openaiDefaultModel'),
|
||||||
@ -178,16 +180,17 @@ export class ConfigurationManager {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openai: openaiModel || 'gpt-3.5-turbo',
|
openai: openaiModel || undefined,
|
||||||
anthropic: anthropicModel || 'claude-3-sonnet-20240229',
|
anthropic: anthropicModel || undefined,
|
||||||
ollama: ollamaModel || 'llama2'
|
ollama: ollamaModel || undefined
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error loading default models: ${error}`);
|
log.error(`Error loading default models: ${error}`);
|
||||||
|
// Return undefined for all providers if we can't load config
|
||||||
return {
|
return {
|
||||||
openai: 'gpt-3.5-turbo',
|
openai: undefined,
|
||||||
anthropic: 'claude-3-sonnet-20240229',
|
anthropic: undefined,
|
||||||
ollama: 'llama2'
|
ollama: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,7 +325,8 @@ export class ConfigurationManager {
|
|||||||
|
|
||||||
private parseProviderList(precedenceOption: string | null): string[] {
|
private parseProviderList(precedenceOption: string | null): string[] {
|
||||||
if (!precedenceOption) {
|
if (!precedenceOption) {
|
||||||
return ['openai', 'anthropic', 'ollama'];
|
// Don't assume any defaults - return empty array
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -344,7 +348,8 @@ export class ConfigurationManager {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error parsing provider list "${precedenceOption}": ${error}`);
|
log.error(`Error parsing provider list "${precedenceOption}": ${error}`);
|
||||||
return ['openai', 'anthropic', 'ollama'];
|
// Don't assume defaults on parse error
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,17 +357,17 @@ export class ConfigurationManager {
|
|||||||
return {
|
return {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
providerPrecedence: {
|
providerPrecedence: {
|
||||||
providers: ['openai', 'anthropic', 'ollama'],
|
providers: [],
|
||||||
defaultProvider: 'openai'
|
defaultProvider: undefined
|
||||||
},
|
},
|
||||||
embeddingProviderPrecedence: {
|
embeddingProviderPrecedence: {
|
||||||
providers: ['openai', 'ollama'],
|
providers: [],
|
||||||
defaultProvider: 'openai'
|
defaultProvider: undefined
|
||||||
},
|
},
|
||||||
defaultModels: {
|
defaultModels: {
|
||||||
openai: 'gpt-3.5-turbo',
|
openai: undefined,
|
||||||
anthropic: 'claude-3-sonnet-20240229',
|
anthropic: undefined,
|
||||||
ollama: 'llama2'
|
ollama: undefined
|
||||||
},
|
},
|
||||||
providerSettings: {}
|
providerSettings: {}
|
||||||
};
|
};
|
||||||
|
@ -48,7 +48,7 @@ export interface AIConfig {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
providerPrecedence: ProviderPrecedenceConfig;
|
providerPrecedence: ProviderPrecedenceConfig;
|
||||||
embeddingProviderPrecedence: EmbeddingProviderPrecedenceConfig;
|
embeddingProviderPrecedence: EmbeddingProviderPrecedenceConfig;
|
||||||
defaultModels: Record<ProviderType, string>;
|
defaultModels: Record<ProviderType, string | undefined>;
|
||||||
providerSettings: ProviderSettings;
|
providerSettings: ProviderSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,4 +105,4 @@ export interface ConfigValidationResult {
|
|||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
errors: string[];
|
errors: string[];
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
}
|
}
|
||||||
|
@ -100,61 +100,65 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get default provider and model using the new configuration system
|
// Get default provider and model using the new configuration system
|
||||||
let defaultProvider: ProviderType = 'openai';
|
|
||||||
let defaultModelName = 'gpt-3.5-turbo';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the new configuration helpers - no string parsing!
|
// Use the new configuration helpers - no string parsing!
|
||||||
defaultProvider = await getPreferredProvider();
|
const preferredProvider = await getPreferredProvider();
|
||||||
defaultModelName = await getDefaultModelForProvider(defaultProvider);
|
|
||||||
|
|
||||||
log.info(`Selected provider: ${defaultProvider}, model: ${defaultModelName}`);
|
if (!preferredProvider) {
|
||||||
} catch (error) {
|
throw new Error('No AI providers are configured. Please check your AI settings.');
|
||||||
// If any error occurs, use the fallback default
|
|
||||||
log.error(`Error determining default model: ${error}`);
|
|
||||||
defaultProvider = 'openai';
|
|
||||||
defaultModelName = 'gpt-3.5-turbo';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine query complexity
|
|
||||||
let queryComplexity = 'low';
|
|
||||||
if (query) {
|
|
||||||
// Simple heuristic: longer queries or those with complex terms indicate higher complexity
|
|
||||||
const complexityIndicators = [
|
|
||||||
'explain', 'analyze', 'compare', 'evaluate', 'synthesize',
|
|
||||||
'summarize', 'elaborate', 'investigate', 'research', 'debate'
|
|
||||||
];
|
|
||||||
|
|
||||||
const hasComplexTerms = complexityIndicators.some(term => query.toLowerCase().includes(term));
|
|
||||||
const isLongQuery = query.length > 100;
|
|
||||||
const hasMultipleQuestions = (query.match(/\?/g) || []).length > 1;
|
|
||||||
|
|
||||||
if ((hasComplexTerms && isLongQuery) || hasMultipleQuestions) {
|
|
||||||
queryComplexity = 'high';
|
|
||||||
} else if (hasComplexTerms || isLongQuery) {
|
|
||||||
queryComplexity = 'medium';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modelName = await getDefaultModelForProvider(preferredProvider);
|
||||||
|
|
||||||
|
if (!modelName) {
|
||||||
|
throw new Error(`No default model configured for provider ${preferredProvider}. Please set a default model in your AI settings.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Selected provider: ${preferredProvider}, model: ${modelName}`);
|
||||||
|
|
||||||
|
// Determine query complexity
|
||||||
|
let queryComplexity = 'low';
|
||||||
|
if (query) {
|
||||||
|
// Simple heuristic: longer queries or those with complex terms indicate higher complexity
|
||||||
|
const complexityIndicators = [
|
||||||
|
'explain', 'analyze', 'compare', 'evaluate', 'synthesize',
|
||||||
|
'summarize', 'elaborate', 'investigate', 'research', 'debate'
|
||||||
|
];
|
||||||
|
|
||||||
|
const hasComplexTerms = complexityIndicators.some(term => query.toLowerCase().includes(term));
|
||||||
|
const isLongQuery = query.length > 100;
|
||||||
|
const hasMultipleQuestions = (query.match(/\?/g) || []).length > 1;
|
||||||
|
|
||||||
|
if ((hasComplexTerms && isLongQuery) || hasMultipleQuestions) {
|
||||||
|
queryComplexity = 'high';
|
||||||
|
} else if (hasComplexTerms || isLongQuery) {
|
||||||
|
queryComplexity = 'medium';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check content length if provided
|
||||||
|
if (contentLength && contentLength > SEARCH_CONSTANTS.CONTEXT.CONTENT_LENGTH.MEDIUM_THRESHOLD) {
|
||||||
|
// For large content, favor more powerful models
|
||||||
|
queryComplexity = contentLength > SEARCH_CONSTANTS.CONTEXT.CONTENT_LENGTH.HIGH_THRESHOLD ? 'high' : 'medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the model and add provider metadata
|
||||||
|
updatedOptions.model = modelName;
|
||||||
|
this.addProviderMetadata(updatedOptions, preferredProvider as ServiceProviders, modelName);
|
||||||
|
|
||||||
|
log.info(`Selected model: ${modelName} from provider: ${preferredProvider} for query complexity: ${queryComplexity}`);
|
||||||
|
log.info(`[ModelSelectionStage] Final options: ${JSON.stringify({
|
||||||
|
model: updatedOptions.model,
|
||||||
|
stream: updatedOptions.stream,
|
||||||
|
provider: preferredProvider,
|
||||||
|
enableTools: updatedOptions.enableTools
|
||||||
|
})}`);
|
||||||
|
|
||||||
|
return { options: updatedOptions };
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error determining default model: ${error}`);
|
||||||
|
throw new Error(`Failed to determine AI model configuration: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check content length if provided
|
|
||||||
if (contentLength && contentLength > SEARCH_CONSTANTS.CONTEXT.CONTENT_LENGTH.MEDIUM_THRESHOLD) {
|
|
||||||
// For large content, favor more powerful models
|
|
||||||
queryComplexity = contentLength > SEARCH_CONSTANTS.CONTEXT.CONTENT_LENGTH.HIGH_THRESHOLD ? 'high' : 'medium';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the model and add provider metadata
|
|
||||||
updatedOptions.model = defaultModelName;
|
|
||||||
this.addProviderMetadata(updatedOptions, defaultProvider as ServiceProviders, defaultModelName);
|
|
||||||
|
|
||||||
log.info(`Selected model: ${defaultModelName} from provider: ${defaultProvider} for query complexity: ${queryComplexity}`);
|
|
||||||
log.info(`[ModelSelectionStage] Final options: ${JSON.stringify({
|
|
||||||
model: updatedOptions.model,
|
|
||||||
stream: updatedOptions.stream,
|
|
||||||
provider: defaultProvider,
|
|
||||||
enableTools: updatedOptions.enableTools
|
|
||||||
})}`);
|
|
||||||
|
|
||||||
return { options: updatedOptions };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,6 +229,10 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
|||||||
const defaultProvider = availableProviders[0];
|
const defaultProvider = availableProviders[0];
|
||||||
const defaultModel = await getDefaultModelForProvider(defaultProvider);
|
const defaultModel = await getDefaultModelForProvider(defaultProvider);
|
||||||
|
|
||||||
|
if (!defaultModel) {
|
||||||
|
throw new Error(`No default model configured for provider ${defaultProvider}. Please configure a default model in your AI settings.`);
|
||||||
|
}
|
||||||
|
|
||||||
// Set provider metadata
|
// Set provider metadata
|
||||||
if (!input.options.providerMetadata) {
|
if (!input.options.providerMetadata) {
|
||||||
input.options.providerMetadata = {
|
input.options.providerMetadata = {
|
||||||
@ -237,8 +245,7 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
|||||||
return defaultModel;
|
return defaultModel;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error determining default model: ${error}`);
|
log.error(`Error determining default model: ${error}`);
|
||||||
// Fallback to hardcoded default
|
throw error; // Don't provide fallback defaults, let the error propagate
|
||||||
return 'gpt-3.5-turbo';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user