2025-03-02 19:39:10 -08:00
|
|
|
import options from '../options.js';
|
2025-04-08 21:24:56 +00:00
|
|
|
import type { AIService, ChatCompletionOptions, ChatResponse, Message } from './ai_interface.js';
|
2025-03-11 17:30:50 +00:00
|
|
|
import { AnthropicService } from './providers/anthropic_service.js';
|
2025-03-11 18:39:59 +00:00
|
|
|
import { ContextExtractor } from './context/index.js';
|
2025-04-07 22:34:24 +00:00
|
|
|
import agentTools from './context_extractors/index.js';
|
2025-04-08 21:24:56 +00:00
|
|
|
import contextService from './context/services/context_service.js';
|
|
|
|
import { getEmbeddingProvider, getEnabledEmbeddingProviders } from './providers/providers.js';
|
|
|
|
import indexService from './index_service.js';
|
|
|
|
import log from '../log.js';
|
|
|
|
import { OllamaService } from './providers/ollama_service.js';
|
|
|
|
import { OpenAIService } from './providers/openai_service.js';
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-03-28 21:04:12 +00:00
|
|
|
// Import interfaces
|
|
|
|
import type {
|
|
|
|
ServiceProviders,
|
|
|
|
IAIServiceManager,
|
|
|
|
ProviderMetadata
|
|
|
|
} from './interfaces/ai_service_interfaces.js';
|
|
|
|
import type { NoteSearchResult } from './interfaces/context_interfaces.js';
|
|
|
|
|
2025-04-16 17:29:35 +00:00
|
|
|
/**
|
|
|
|
* Interface representing relevant note context
|
|
|
|
*/
|
|
|
|
interface NoteContext {
|
|
|
|
title: string;
|
|
|
|
content?: string;
|
|
|
|
noteId?: string;
|
|
|
|
summary?: string;
|
|
|
|
score?: number;
|
|
|
|
}
|
|
|
|
|
2025-03-28 21:04:12 +00:00
|
|
|
export class AIServiceManager implements IAIServiceManager {
|
2025-03-02 19:39:10 -08:00
|
|
|
private services: Record<ServiceProviders, AIService> = {
|
|
|
|
openai: new OpenAIService(),
|
|
|
|
anthropic: new AnthropicService(),
|
|
|
|
ollama: new OllamaService()
|
|
|
|
};
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
private currentChatProvider: ServiceProviders | null = null; // No default
|
|
|
|
private currentChatService: AIService | null = null; // Current active service
|
|
|
|
private currentEmbeddingProvider: string | null = null; // No default
|
2025-03-09 02:19:26 +00:00
|
|
|
private initialized = false;
|
2025-03-02 19:39:10 -08:00
|
|
|
|
|
|
|
constructor() {
|
2025-06-04 17:40:29 +00:00
|
|
|
// Initialize provider immediately
|
|
|
|
this.updateCurrentProvider();
|
2025-04-16 17:29:35 +00:00
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
// Initialize tools immediately
|
|
|
|
this.initializeTools().catch(error => {
|
|
|
|
log.error(`Error initializing LLM tools during AIServiceManager construction: ${error.message || String(error)}`);
|
|
|
|
});
|
|
|
|
}
|
2025-04-16 17:29:35 +00:00
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
/**
|
|
|
|
* Initialize all LLM tools in one place
|
|
|
|
*/
|
|
|
|
private async initializeTools(): Promise<void> {
|
|
|
|
try {
|
|
|
|
log.info('Initializing LLM tools during AIServiceManager construction...');
|
2025-04-16 17:29:35 +00:00
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
// Initialize agent tools
|
2025-04-16 17:29:35 +00:00
|
|
|
await this.initializeAgentTools();
|
2025-04-11 22:52:09 +00:00
|
|
|
log.info("Agent tools initialized successfully");
|
2025-04-16 17:29:35 +00:00
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
// Initialize LLM tools
|
|
|
|
const toolInitializer = await import('./tools/tool_initializer.js');
|
|
|
|
await toolInitializer.default.initializeTools();
|
|
|
|
log.info("LLM tools initialized successfully");
|
2025-04-16 17:29:35 +00:00
|
|
|
} catch (error: unknown) {
|
|
|
|
log.error(`Error initializing tools: ${this.handleError(error)}`);
|
2025-04-11 22:52:09 +00:00
|
|
|
// Don't throw, just log the error to prevent breaking construction
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-04 17:40:29 +00:00
|
|
|
* Update the current provider from saved options
|
2025-03-09 02:19:26 +00:00
|
|
|
* Returns true if successful, false if options not available yet
|
2025-03-02 19:39:10 -08:00
|
|
|
*/
|
2025-06-04 17:40:29 +00:00
|
|
|
updateCurrentProvider(): boolean {
|
2025-03-09 02:19:26 +00:00
|
|
|
if (this.initialized) {
|
|
|
|
return true;
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
try {
|
2025-06-04 17:40:29 +00:00
|
|
|
// Always get selected chat provider from options
|
|
|
|
const selectedChatProvider = options.getOption('aiChatProvider');
|
|
|
|
if (!selectedChatProvider) {
|
|
|
|
throw new Error('No chat provider configured. Please set aiChatProvider option.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Object.keys(this.services).includes(selectedChatProvider)) {
|
|
|
|
throw new Error(`Invalid chat provider '${selectedChatProvider}'. Valid providers are: ${Object.keys(this.services).join(', ')}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
2025-06-04 17:40:29 +00:00
|
|
|
|
|
|
|
this.currentChatProvider = selectedChatProvider as ServiceProviders;
|
|
|
|
this.currentChatService = this.services[this.currentChatProvider];
|
|
|
|
|
|
|
|
// Always get selected embedding provider from options
|
|
|
|
const selectedEmbeddingProvider = options.getOption('aiEmbeddingProvider');
|
|
|
|
if (!selectedEmbeddingProvider) {
|
|
|
|
throw new Error('No embedding provider configured. Please set aiEmbeddingProvider option.');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.currentEmbeddingProvider = selectedEmbeddingProvider;
|
2025-03-09 02:19:26 +00:00
|
|
|
|
|
|
|
this.initialized = true;
|
2025-06-04 17:40:29 +00:00
|
|
|
log.info(`AI Service Manager initialized with chat provider: ${this.currentChatProvider}, embedding provider: ${this.currentEmbeddingProvider}`);
|
2025-03-17 16:23:58 +00:00
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
return true;
|
|
|
|
} catch (error) {
|
2025-06-04 17:40:29 +00:00
|
|
|
// If options table doesn't exist yet or providers not configured
|
2025-03-09 02:19:26 +00:00
|
|
|
// This happens during initial database creation
|
2025-06-04 17:40:29 +00:00
|
|
|
log.error(`Failed to initialize AI providers: ${error}`);
|
|
|
|
this.currentChatProvider = null;
|
|
|
|
this.currentChatService = null;
|
|
|
|
this.currentEmbeddingProvider = null;
|
2025-03-09 02:19:26 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-17 16:23:58 +00:00
|
|
|
/**
|
|
|
|
* Validate embedding providers configuration
|
|
|
|
* - Check if embedding default provider is in provider precedence list
|
|
|
|
* - Check if all providers in precedence list and default provider are enabled
|
|
|
|
*
|
|
|
|
* @returns A warning message if there are issues, or null if everything is fine
|
|
|
|
*/
|
|
|
|
async validateEmbeddingProviders(): Promise<string | null> {
|
|
|
|
try {
|
|
|
|
// Check if AI is enabled, if not, skip validation
|
|
|
|
const aiEnabled = await options.getOptionBool('aiEnabled');
|
|
|
|
if (!aiEnabled) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Get selected provider from options
|
|
|
|
const selectedProvider = await options.getOption('aiChatProvider');
|
|
|
|
if (!selectedProvider) {
|
|
|
|
throw new Error('No chat provider configured');
|
2025-03-17 16:23:58 +00:00
|
|
|
}
|
2025-05-29 20:45:27 +00:00
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Check for configuration issues with the selected provider
|
2025-05-29 20:45:27 +00:00
|
|
|
const configIssues: string[] = [];
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Check the selected provider for proper configuration
|
|
|
|
if (selectedProvider === 'openai') {
|
|
|
|
// Check OpenAI configuration
|
|
|
|
const apiKey = await options.getOption('openaiApiKey');
|
|
|
|
if (!apiKey) {
|
|
|
|
configIssues.push(`OpenAI API key is missing`);
|
|
|
|
}
|
|
|
|
} else if (selectedProvider === 'anthropic') {
|
|
|
|
// Check Anthropic configuration
|
|
|
|
const apiKey = await options.getOption('anthropicApiKey');
|
|
|
|
if (!apiKey) {
|
|
|
|
configIssues.push(`Anthropic API key is missing`);
|
|
|
|
}
|
|
|
|
} else if (selectedProvider === 'ollama') {
|
|
|
|
// Check Ollama configuration
|
|
|
|
const baseUrl = await options.getOption('ollamaBaseUrl');
|
|
|
|
if (!baseUrl) {
|
|
|
|
configIssues.push(`Ollama Base URL is missing`);
|
2025-05-29 20:45:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return warning message if there are configuration issues
|
|
|
|
if (configIssues.length > 0) {
|
2025-03-17 16:23:58 +00:00
|
|
|
let message = 'There are issues with your AI provider configuration:';
|
2025-05-29 20:45:27 +00:00
|
|
|
|
|
|
|
for (const issue of configIssues) {
|
|
|
|
message += `\n• ${issue}`;
|
2025-03-17 16:23:58 +00:00
|
|
|
}
|
2025-05-29 20:45:27 +00:00
|
|
|
|
2025-03-17 16:23:58 +00:00
|
|
|
message += '\n\nPlease check your AI settings.';
|
2025-05-29 20:45:27 +00:00
|
|
|
|
2025-03-17 16:23:58 +00:00
|
|
|
// Log warning to console
|
|
|
|
log.error('AI Provider Configuration Warning: ' + message);
|
2025-05-29 20:45:27 +00:00
|
|
|
|
2025-03-17 16:23:58 +00:00
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
} catch (error) {
|
|
|
|
log.error(`Error validating embedding providers: ${error}`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
/**
|
|
|
|
* Ensure manager is initialized before using
|
|
|
|
*/
|
|
|
|
private ensureInitialized() {
|
|
|
|
if (!this.initialized) {
|
2025-06-04 17:40:29 +00:00
|
|
|
this.updateCurrentProvider();
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if any AI service is available
|
|
|
|
*/
|
|
|
|
isAnyServiceAvailable(): boolean {
|
|
|
|
return Object.values(this.services).some(service => service.isAvailable());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get list of available providers
|
|
|
|
*/
|
|
|
|
getAvailableProviders(): ServiceProviders[] {
|
2025-03-09 02:19:26 +00:00
|
|
|
this.ensureInitialized();
|
2025-03-02 19:39:10 -08:00
|
|
|
return Object.entries(this.services)
|
|
|
|
.filter(([_, service]) => service.isAvailable())
|
|
|
|
.map(([key, _]) => key as ServiceProviders);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-06-04 17:40:29 +00:00
|
|
|
* Generate a chat completion response using the current AI service
|
2025-03-02 19:39:10 -08:00
|
|
|
*/
|
|
|
|
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
2025-03-09 02:19:26 +00:00
|
|
|
this.ensureInitialized();
|
|
|
|
|
2025-04-09 19:53:45 +00:00
|
|
|
log.info(`[AIServiceManager] generateChatCompletion called with options: ${JSON.stringify({
|
|
|
|
model: options.model,
|
|
|
|
stream: options.stream,
|
|
|
|
enableTools: options.enableTools
|
|
|
|
})}`);
|
|
|
|
log.info(`[AIServiceManager] Stream option type: ${typeof options.stream}`);
|
|
|
|
|
2025-03-02 19:39:10 -08:00
|
|
|
if (!messages || messages.length === 0) {
|
|
|
|
throw new Error('No messages provided for chat completion');
|
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// If a specific provider is requested via model prefix, use it temporarily
|
2025-03-02 19:39:10 -08:00
|
|
|
if (options.model && options.model.includes(':')) {
|
|
|
|
const [providerName, modelName] = options.model.split(':');
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
if (this.services[providerName as ServiceProviders]?.isAvailable()) {
|
2025-03-02 19:39:10 -08:00
|
|
|
try {
|
|
|
|
const modifiedOptions = { ...options, model: modelName };
|
2025-04-09 19:53:45 +00:00
|
|
|
log.info(`[AIServiceManager] Using provider ${providerName} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
return await this.services[providerName as ServiceProviders].generateChatCompletion(messages, modifiedOptions);
|
|
|
|
} catch (error) {
|
2025-03-09 02:19:26 +00:00
|
|
|
log.error(`Error with specified provider ${providerName}: ${error}`);
|
2025-06-04 17:40:29 +00:00
|
|
|
throw new Error(`Provider ${providerName} failed: ${error}`);
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
2025-06-04 17:40:29 +00:00
|
|
|
} else {
|
|
|
|
throw new Error(`Requested provider ${providerName} is not available`);
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Ensure we have a configured service
|
|
|
|
if (!this.currentChatProvider || !this.currentChatService) {
|
|
|
|
// Try to initialize again in case options were updated
|
|
|
|
this.initialized = false;
|
|
|
|
this.updateCurrentProvider();
|
|
|
|
|
|
|
|
if (!this.currentChatProvider || !this.currentChatService) {
|
|
|
|
throw new Error('No chat provider configured. Please configure aiChatProvider in AI settings.');
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
}
|
2025-06-04 17:40:29 +00:00
|
|
|
|
|
|
|
if (!this.currentChatService.isAvailable()) {
|
|
|
|
throw new Error(`Configured chat provider '${this.currentChatProvider}' is not available. Please check your AI settings.`);
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
try {
|
|
|
|
log.info(`[AIServiceManager] Using current chat service (${this.currentChatProvider}) with options.stream: ${options.stream}`);
|
|
|
|
return await this.currentChatService.generateChatCompletion(messages, options);
|
|
|
|
} catch (error) {
|
|
|
|
log.error(`Error with provider ${this.currentChatProvider}: ${error}`);
|
|
|
|
throw new Error(`Chat provider ${this.currentChatProvider} failed: ${error}`);
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
2025-03-11 18:07:28 +00:00
|
|
|
|
|
|
|
setupEventListeners() {
|
|
|
|
// Setup event listeners for AI services
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the context extractor service
|
|
|
|
* @returns The context extractor instance
|
|
|
|
*/
|
|
|
|
getContextExtractor() {
|
|
|
|
return contextExtractor;
|
|
|
|
}
|
|
|
|
|
2025-03-19 19:28:02 +00:00
|
|
|
/**
|
|
|
|
* Get the context service for advanced context management
|
|
|
|
* @returns The context service instance
|
|
|
|
*/
|
|
|
|
getContextService() {
|
|
|
|
return contextService;
|
2025-03-11 18:07:28 +00:00
|
|
|
}
|
2025-03-17 16:23:58 +00:00
|
|
|
|
2025-03-11 23:26:47 +00:00
|
|
|
/**
|
|
|
|
* Get the index service for managing knowledge base indexing
|
|
|
|
* @returns The index service instance
|
|
|
|
*/
|
|
|
|
getIndexService() {
|
|
|
|
return indexService;
|
|
|
|
}
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
/**
|
2025-04-11 22:52:09 +00:00
|
|
|
* Ensure agent tools are initialized (no-op as they're initialized in constructor)
|
|
|
|
* Kept for backward compatibility with existing API
|
2025-03-19 16:19:48 +00:00
|
|
|
*/
|
|
|
|
async initializeAgentTools(): Promise<void> {
|
2025-04-11 22:52:09 +00:00
|
|
|
// Agent tools are already initialized in the constructor
|
|
|
|
// This method is kept for backward compatibility
|
2025-04-13 21:16:18 +00:00
|
|
|
log.info("initializeAgentTools called, but tools are already initialized in constructor");
|
2025-03-19 16:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the agent tools manager
|
|
|
|
* This provides access to all agent tools
|
|
|
|
*/
|
|
|
|
getAgentTools() {
|
|
|
|
return agentTools;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the vector search tool for semantic similarity search
|
|
|
|
*/
|
|
|
|
getVectorSearchTool() {
|
2025-04-08 21:24:56 +00:00
|
|
|
const tools = agentTools.getTools();
|
|
|
|
return tools.vectorSearch;
|
2025-03-19 16:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the note navigator tool for hierarchical exploration
|
|
|
|
*/
|
|
|
|
getNoteNavigatorTool() {
|
2025-04-08 21:24:56 +00:00
|
|
|
const tools = agentTools.getTools();
|
|
|
|
return tools.noteNavigator;
|
2025-03-19 16:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the query decomposition tool for complex queries
|
|
|
|
*/
|
|
|
|
getQueryDecompositionTool() {
|
2025-04-08 21:24:56 +00:00
|
|
|
const tools = agentTools.getTools();
|
|
|
|
return tools.queryDecomposition;
|
2025-03-19 16:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the contextual thinking tool for transparent reasoning
|
|
|
|
*/
|
|
|
|
getContextualThinkingTool() {
|
2025-04-08 21:24:56 +00:00
|
|
|
const tools = agentTools.getTools();
|
|
|
|
return tools.contextualThinking;
|
2025-03-19 16:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get whether AI features are enabled from options
|
|
|
|
*/
|
|
|
|
getAIEnabled(): boolean {
|
|
|
|
return options.getOptionBool('aiEnabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set up embeddings provider for AI features
|
|
|
|
*/
|
|
|
|
async setupEmbeddingsProvider(): Promise<void> {
|
|
|
|
try {
|
|
|
|
if (!this.getAIEnabled()) {
|
|
|
|
log.info('AI features are disabled');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Get selected embedding provider
|
|
|
|
const selectedProvider = await options.getOption('aiEmbeddingProvider') || 'openai';
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
// Check if we have enabled providers
|
|
|
|
const enabledProviders = await getEnabledEmbeddingProviders();
|
|
|
|
|
|
|
|
if (enabledProviders.length === 0) {
|
|
|
|
log.info('No embedding providers are enabled');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize embedding providers
|
|
|
|
log.info('Embedding providers initialized successfully');
|
|
|
|
} catch (error: any) {
|
|
|
|
log.error(`Error setting up embedding providers: ${error.message}`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the AI Service
|
|
|
|
*/
|
|
|
|
async initialize(): Promise<void> {
|
|
|
|
try {
|
|
|
|
log.info("Initializing AI service...");
|
|
|
|
|
|
|
|
// Check if AI is enabled in options
|
|
|
|
const isAIEnabled = this.getAIEnabled();
|
|
|
|
|
|
|
|
if (!isAIEnabled) {
|
|
|
|
log.info("AI features are disabled in options");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up embeddings provider if AI is enabled
|
|
|
|
await this.setupEmbeddingsProvider();
|
|
|
|
|
|
|
|
// Initialize index service
|
|
|
|
await this.getIndexService().initialize();
|
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
// Tools are already initialized in the constructor
|
|
|
|
// No need to initialize them again
|
2025-03-19 16:19:48 +00:00
|
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
log.info("AI service initialized successfully");
|
|
|
|
} catch (error: any) {
|
|
|
|
log.error(`Error initializing AI service: ${error.message}`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
2025-03-19 19:28:02 +00:00
|
|
|
|
|
|
|
/**
|
2025-04-08 21:24:56 +00:00
|
|
|
* Get description of available agent tools
|
|
|
|
*/
|
|
|
|
async getAgentToolsDescription(): Promise<string> {
|
|
|
|
try {
|
|
|
|
// Get all available tools
|
|
|
|
const tools = agentTools.getAllTools();
|
|
|
|
|
|
|
|
if (!tools || tools.length === 0) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Format tool descriptions
|
|
|
|
const toolDescriptions = tools.map(tool =>
|
|
|
|
`- ${tool.name}: ${tool.description}`
|
|
|
|
).join('\n');
|
|
|
|
|
|
|
|
return `Available tools:\n${toolDescriptions}`;
|
|
|
|
} catch (error) {
|
|
|
|
log.error(`Error getting agent tools description: ${error}`);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-04-16 17:29:35 +00:00
|
|
|
* Get enhanced context with available agent tools
|
|
|
|
* @param noteId - The ID of the note
|
2025-04-08 21:24:56 +00:00
|
|
|
* @param query - The user's query
|
|
|
|
* @param showThinking - Whether to show LLM's thinking process
|
|
|
|
* @param relevantNotes - Optional notes already found to be relevant
|
|
|
|
* @returns Enhanced context with agent tools information
|
2025-03-19 19:28:02 +00:00
|
|
|
*/
|
|
|
|
async getAgentToolsContext(
|
|
|
|
noteId: string,
|
|
|
|
query: string,
|
|
|
|
showThinking: boolean = false,
|
2025-04-16 17:29:35 +00:00
|
|
|
relevantNotes: NoteSearchResult[] = []
|
2025-03-19 19:28:02 +00:00
|
|
|
): Promise<string> {
|
2025-03-19 20:09:18 +00:00
|
|
|
try {
|
2025-04-08 21:24:56 +00:00
|
|
|
// Create agent tools message
|
|
|
|
const toolsMessage = await this.getAgentToolsDescription();
|
2025-04-16 17:29:35 +00:00
|
|
|
|
2025-04-11 22:52:09 +00:00
|
|
|
// Agent tools are already initialized in the constructor
|
|
|
|
// No need to initialize them again
|
2025-04-08 21:24:56 +00:00
|
|
|
|
|
|
|
// If we have notes that were already found to be relevant, use them directly
|
|
|
|
let contextNotes = relevantNotes;
|
|
|
|
|
|
|
|
// If no notes provided, find relevant ones
|
|
|
|
if (!contextNotes || contextNotes.length === 0) {
|
|
|
|
try {
|
|
|
|
// Get the default LLM service for context enhancement
|
|
|
|
const provider = this.getPreferredProvider();
|
|
|
|
const llmService = this.getService(provider);
|
|
|
|
|
|
|
|
// Find relevant notes
|
|
|
|
contextNotes = await contextService.findRelevantNotes(
|
|
|
|
query,
|
|
|
|
noteId,
|
|
|
|
{
|
|
|
|
maxResults: 5,
|
|
|
|
summarize: true,
|
|
|
|
llmService
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
log.info(`Found ${contextNotes.length} relevant notes for context`);
|
|
|
|
} catch (error) {
|
2025-04-16 17:29:35 +00:00
|
|
|
log.error(`Failed to find relevant notes: ${this.handleError(error)}`);
|
2025-04-08 21:24:56 +00:00
|
|
|
// Continue without context notes
|
|
|
|
contextNotes = [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Format notes into context string if we have any
|
|
|
|
let contextStr = "";
|
|
|
|
if (contextNotes && contextNotes.length > 0) {
|
|
|
|
contextStr = "\n\nRelevant context:\n";
|
|
|
|
contextNotes.forEach((note, index) => {
|
|
|
|
contextStr += `[${index + 1}] "${note.title}"\n${note.content || 'No content available'}\n\n`;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Combine tool message with context
|
|
|
|
return toolsMessage + contextStr;
|
2025-03-19 20:09:18 +00:00
|
|
|
} catch (error) {
|
2025-04-16 17:29:35 +00:00
|
|
|
log.error(`Error getting agent tools context: ${this.handleError(error)}`);
|
2025-04-08 21:24:56 +00:00
|
|
|
return "";
|
2025-03-28 21:04:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get AI service for the given provider
|
|
|
|
*/
|
|
|
|
getService(provider?: string): AIService {
|
|
|
|
this.ensureInitialized();
|
|
|
|
|
|
|
|
// If provider is specified, try to use it
|
|
|
|
if (provider && this.services[provider as ServiceProviders]?.isAvailable()) {
|
|
|
|
return this.services[provider as ServiceProviders];
|
2025-03-19 20:09:18 +00:00
|
|
|
}
|
2025-03-28 21:04:12 +00:00
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// Otherwise, use the current chat service
|
|
|
|
if (this.currentChatService && this.currentChatService.isAvailable()) {
|
|
|
|
return this.currentChatService;
|
2025-03-28 21:04:12 +00:00
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
// If current service is not available, throw an error
|
|
|
|
throw new Error(`Configured chat provider '${this.currentChatProvider}' is not available`);
|
2025-03-28 21:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the preferred provider based on configuration
|
|
|
|
*/
|
|
|
|
getPreferredProvider(): string {
|
|
|
|
this.ensureInitialized();
|
2025-06-04 17:40:29 +00:00
|
|
|
if (!this.currentChatProvider) {
|
|
|
|
throw new Error('No chat provider configured');
|
2025-03-28 21:04:12 +00:00
|
|
|
}
|
2025-06-04 17:40:29 +00:00
|
|
|
return this.currentChatProvider;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current chat service
|
|
|
|
*/
|
|
|
|
getCurrentChatService(): AIService | null {
|
|
|
|
this.ensureInitialized();
|
|
|
|
return this.currentChatService;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current chat provider name
|
|
|
|
*/
|
|
|
|
getCurrentChatProvider(): string {
|
|
|
|
this.ensureInitialized();
|
|
|
|
if (!this.currentChatProvider) {
|
|
|
|
throw new Error('No chat provider configured');
|
|
|
|
}
|
|
|
|
return this.currentChatProvider;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current embedding provider name
|
|
|
|
*/
|
|
|
|
getCurrentEmbeddingProvider(): string {
|
|
|
|
this.ensureInitialized();
|
|
|
|
if (!this.currentEmbeddingProvider) {
|
|
|
|
throw new Error('No embedding provider configured');
|
|
|
|
}
|
|
|
|
return this.currentEmbeddingProvider;
|
2025-03-28 21:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a specific provider is available
|
|
|
|
*/
|
|
|
|
isProviderAvailable(provider: string): boolean {
|
|
|
|
return this.services[provider as ServiceProviders]?.isAvailable() ?? false;
|
|
|
|
}
|
|
|
|
|
2025-06-04 17:40:29 +00:00
|
|
|
/**
|
|
|
|
* Reinitialize the service manager when provider settings change
|
|
|
|
* This will update the current provider selection and service objects
|
|
|
|
*/
|
|
|
|
async reinitialize(): Promise<void> {
|
|
|
|
log.info('Reinitializing AI Service Manager due to provider change');
|
|
|
|
|
|
|
|
// Reset initialization flag to force update
|
|
|
|
this.initialized = false;
|
|
|
|
|
|
|
|
// Update current provider and service objects from options
|
|
|
|
this.updateCurrentProvider();
|
|
|
|
|
|
|
|
// Re-validate providers if needed
|
|
|
|
await this.validateEmbeddingProviders();
|
|
|
|
|
|
|
|
log.info(`AI Service Manager reinitialized with chat provider: ${this.currentChatProvider}, embedding provider: ${this.currentEmbeddingProvider}`);
|
|
|
|
}
|
|
|
|
|
2025-03-28 21:04:12 +00:00
|
|
|
/**
|
|
|
|
* Get metadata about a provider
|
|
|
|
*/
|
|
|
|
getProviderMetadata(provider: string): ProviderMetadata | null {
|
|
|
|
const service = this.services[provider as ServiceProviders];
|
|
|
|
if (!service) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
name: provider,
|
|
|
|
capabilities: {
|
|
|
|
chat: true,
|
|
|
|
embeddings: provider !== 'anthropic', // Anthropic doesn't have embeddings
|
|
|
|
streaming: true,
|
|
|
|
functionCalling: provider === 'openai' // Only OpenAI has function calling
|
|
|
|
},
|
|
|
|
models: ['default'], // Placeholder, could be populated from the service
|
|
|
|
defaultModel: 'default'
|
|
|
|
};
|
2025-03-19 19:28:02 +00:00
|
|
|
}
|
2025-04-16 17:29:35 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Error handler that properly types the error object
|
|
|
|
*/
|
|
|
|
private handleError(error: unknown): string {
|
|
|
|
if (error instanceof Error) {
|
|
|
|
return error.message || String(error);
|
|
|
|
}
|
|
|
|
return String(error);
|
|
|
|
}
|
2025-03-02 19:39:10 -08:00
|
|
|
}
|
|
|
|
|
2025-03-09 02:19:26 +00:00
|
|
|
// Don't create singleton immediately, use a lazy-loading pattern
|
|
|
|
let instance: AIServiceManager | null = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the AIServiceManager instance (creates it if not already created)
|
|
|
|
*/
|
|
|
|
function getInstance(): AIServiceManager {
|
|
|
|
if (!instance) {
|
|
|
|
instance = new AIServiceManager();
|
|
|
|
}
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
getInstance,
|
|
|
|
// Also export methods directly for convenience
|
|
|
|
isAnyServiceAvailable(): boolean {
|
|
|
|
return getInstance().isAnyServiceAvailable();
|
|
|
|
},
|
|
|
|
getAvailableProviders() {
|
|
|
|
return getInstance().getAvailableProviders();
|
|
|
|
},
|
|
|
|
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
|
|
|
return getInstance().generateChatCompletion(messages, options);
|
2025-03-11 18:07:28 +00:00
|
|
|
},
|
2025-03-17 16:23:58 +00:00
|
|
|
// Add validateEmbeddingProviders method
|
|
|
|
async validateEmbeddingProviders(): Promise<string | null> {
|
|
|
|
return getInstance().validateEmbeddingProviders();
|
|
|
|
},
|
2025-03-11 23:26:47 +00:00
|
|
|
// Context and index related methods
|
2025-03-11 18:07:28 +00:00
|
|
|
getContextExtractor() {
|
|
|
|
return getInstance().getContextExtractor();
|
|
|
|
},
|
2025-03-19 19:28:02 +00:00
|
|
|
getContextService() {
|
|
|
|
return getInstance().getContextService();
|
|
|
|
},
|
2025-03-11 23:26:47 +00:00
|
|
|
getIndexService() {
|
|
|
|
return getInstance().getIndexService();
|
2025-03-19 16:19:48 +00:00
|
|
|
},
|
|
|
|
// Agent tools related methods
|
2025-04-11 22:52:09 +00:00
|
|
|
// Tools are now initialized in the constructor
|
2025-03-19 16:19:48 +00:00
|
|
|
getAgentTools() {
|
|
|
|
return getInstance().getAgentTools();
|
|
|
|
},
|
|
|
|
getVectorSearchTool() {
|
|
|
|
return getInstance().getVectorSearchTool();
|
|
|
|
},
|
|
|
|
getNoteNavigatorTool() {
|
|
|
|
return getInstance().getNoteNavigatorTool();
|
|
|
|
},
|
|
|
|
getQueryDecompositionTool() {
|
|
|
|
return getInstance().getQueryDecompositionTool();
|
|
|
|
},
|
|
|
|
getContextualThinkingTool() {
|
|
|
|
return getInstance().getContextualThinkingTool();
|
2025-03-19 19:28:02 +00:00
|
|
|
},
|
|
|
|
async getAgentToolsContext(
|
|
|
|
noteId: string,
|
|
|
|
query: string,
|
|
|
|
showThinking: boolean = false,
|
2025-04-16 17:29:35 +00:00
|
|
|
relevantNotes: NoteSearchResult[] = []
|
2025-03-19 19:28:02 +00:00
|
|
|
): Promise<string> {
|
|
|
|
return getInstance().getAgentToolsContext(
|
|
|
|
noteId,
|
|
|
|
query,
|
|
|
|
showThinking,
|
|
|
|
relevantNotes
|
|
|
|
);
|
2025-03-28 21:04:12 +00:00
|
|
|
},
|
|
|
|
// New methods
|
|
|
|
getService(provider?: string): AIService {
|
|
|
|
return getInstance().getService(provider);
|
|
|
|
},
|
|
|
|
getPreferredProvider(): string {
|
|
|
|
return getInstance().getPreferredProvider();
|
|
|
|
},
|
|
|
|
isProviderAvailable(provider: string): boolean {
|
|
|
|
return getInstance().isProviderAvailable(provider);
|
|
|
|
},
|
|
|
|
getProviderMetadata(provider: string): ProviderMetadata | null {
|
|
|
|
return getInstance().getProviderMetadata(provider);
|
2025-06-04 17:40:29 +00:00
|
|
|
},
|
|
|
|
async reinitialize(): Promise<void> {
|
|
|
|
return getInstance().reinitialize();
|
|
|
|
},
|
|
|
|
getCurrentChatService(): AIService | null {
|
|
|
|
return getInstance().getCurrentChatService();
|
|
|
|
},
|
|
|
|
getCurrentChatProvider(): string {
|
|
|
|
return getInstance().getCurrentChatProvider();
|
|
|
|
},
|
|
|
|
getCurrentEmbeddingProvider(): string {
|
|
|
|
return getInstance().getCurrentEmbeddingProvider();
|
2025-03-09 02:19:26 +00:00
|
|
|
}
|
|
|
|
};
|
2025-03-11 18:39:59 +00:00
|
|
|
|
|
|
|
// Create an instance of ContextExtractor for backward compatibility
|
|
|
|
const contextExtractor = new ContextExtractor();
|