UNPKG

adpa-enterprise-framework-automation

Version:

Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe

350 lines 14.5 kB
/** * Enhanced AI Client Manager Module for Requirements Gathering Agent * * Advanced client management system with improved connection pooling, error handling, * and multi-provider authentication for reliable AI service integration. * * @version 2.1.3 * @author Requirements Gathering Agent Team * @created 2024 * @updated June 2025 * * Key Features: * - Multi-provider client management (Azure, Google, GitHub, Ollama) * - Advanced connection pooling and resource management * - Robust authentication handling (API keys, Entra ID, tokens) * - Enhanced error handling and recovery mechanisms * - Performance optimization and monitoring * * @filepath c:\Users\menno\Source\Repos\requirements-gathering-agent\src\modules\ai\AIClientManager.ts */ import ModelClient from "@azure-rest/ai-inference"; import { AzureKeyCredential } from "@azure/core-auth"; import { AzureOpenAI } from "openai"; import { ClientSecretCredential } from "@azure/identity"; import { GoogleGenerativeAI } from "@google/generative-ai"; import { ConfigurationManager } from "./ConfigurationManager.js"; class AIClientManager { static instance; clients = new Map(); initialized = false; skipAI = false; config; MAX_CONNECTION_ATTEMPTS = 3; CONNECTION_TIMEOUT_MS = 10000; constructor() { this.config = ConfigurationManager.getInstance(); } static getInstance() { if (!AIClientManager.instance) { AIClientManager.instance = new AIClientManager(); } return AIClientManager.instance; } async initializeClients() { if (this.initialized) return; const provider = this.config.getAIProvider(); console.log(`🔍 Initializing AI Provider: ${provider}`); try { await this.initializeProvider(provider); this.initialized = true; console.log(`🚀 ${provider} initialized successfully`); } catch (error) { console.error(`❌ Failed to initialize ${provider}:`, error.message); this.skipAI = true; throw error; } } async initializeSpecificProvider(provider) { if (this.clients.has(provider)) { return; // Already initialized } console.log(`🔧 Initializing specific provider: ${provider}`); try { await this.initializeProvider(provider); console.log(`✅ ${provider} initialized successfully`); } catch (error) { console.error(`❌ Failed to initialize ${provider}:`, error.message); throw error; } } async initializeProvider(provider) { const initMethods = { 'google-ai': () => this.initializeGoogleAI(), 'azure-openai': () => this.initializeAzureOpenAIWithEntra(), 'azure-openai-key': () => this.initializeAzureOpenAIWithKey(), 'azure-openai-entra': () => this.initializeAzureOpenAIWithEntra(), 'azure-ai-studio': () => this.initializeAzureAIStudio(), 'github-ai': () => this.initializeGitHubAI(), 'ollama': () => this.initializeOllama() }; const initMethod = initMethods[provider]; if (!initMethod) { throw new Error(`Unsupported AI provider: ${provider}`); } await initMethod(); } async initializeGoogleAI() { const apiKey = this.config.get('GOOGLE_AI_API_KEY'); if (!apiKey) { throw new Error('GOOGLE_AI_API_KEY is required for Google AI Studio'); } const client = new GoogleGenerativeAI(apiKey); this.setClient('google-ai', client); } async initializeAzureOpenAIWithEntra() { const validation = this.config.validateAzureOpenAIConfig(); if (!validation.isValid) { throw new Error(`Azure OpenAI configuration error: Missing ${validation.missingVars.join(', ')}`); } const endpoint = this.config.get('AZURE_OPENAI_ENDPOINT'); const deployment = this.config.get('AZURE_OPENAI_DEPLOYMENT_NAME'); const apiVersion = this.config.get('AZURE_OPENAI_API_VERSION'); // Get Entra ID credentials const clientId = this.config.get('AZURE_CLIENT_ID'); const tenantId = this.config.get('AZURE_TENANT_ID'); const clientSecret = this.config.get('AZURE_CLIENT_SECRET'); if (!clientId || !tenantId || !clientSecret) { throw new Error('AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_CLIENT_SECRET are required for Entra ID authentication'); } const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); const client = new AzureOpenAI({ endpoint: endpoint, apiKey: await credential.getToken("https://cognitiveservices.azure.com/.default").then(token => token.token), apiVersion, deployment }); this.setClient('azure-openai', client); await this.validateConnection('azure-openai'); } async initializeAzureOpenAIWithKey() { const validation = this.config.validateAzureOpenAIConfig(); if (!validation.isValid) { throw new Error(`Azure OpenAI configuration error: Missing ${validation.missingVars.join(', ')}`); } const endpoint = this.config.get('AZURE_OPENAI_ENDPOINT'); const apiKey = this.config.get('AZURE_OPENAI_API_KEY'); const deployment = this.config.get('AZURE_OPENAI_DEPLOYMENT_NAME'); const apiVersion = this.config.get('AZURE_OPENAI_API_VERSION'); if (!apiKey) { throw new Error('AZURE_OPENAI_API_KEY is required for azure-openai-key provider'); } const client = new AzureOpenAI({ endpoint: endpoint, apiKey, apiVersion, deployment }); this.setClient('azure-openai-key', client); await this.validateConnection('azure-openai-key'); } async initializeAzureAIStudio() { const validation = this.config.validateAzureOpenAIConfig(); if (!validation.isValid) { throw new Error(`Azure AI Studio configuration error: Missing ${validation.missingVars.join(', ')}`); } // Try both AZURE_AI_ENDPOINT and AZURE_OPENAI_ENDPOINT const endpoint = this.config.get('AZURE_AI_ENDPOINT') || this.config.get('AZURE_OPENAI_ENDPOINT'); const apiKey = this.config.get('AZURE_AI_API_KEY') || this.config.get('AZURE_OPENAI_API_KEY'); const client = ModelClient(endpoint, new AzureKeyCredential(apiKey)); this.setClient('azure-ai-studio', client); await this.validateConnection('azure-ai-studio'); } async initializeGitHubAI() { const token = this.config.get('GITHUB_TOKEN'); if (!token) { throw new Error('GITHUB_TOKEN is required for GitHub AI'); } const endpoint = this.config.get('GITHUB_ENDPOINT'); if (!endpoint) { throw new Error('GITHUB_ENDPOINT is required for GitHub AI'); } const client = ModelClient(endpoint, new AzureKeyCredential(token)); this.setClient('github-ai', client); await this.validateConnection('github-ai'); } async initializeOllama() { try { const endpoint = this.config.get('OLLAMA_ENDPOINT') || 'http://127.0.0.1:11434'; const response = await fetch(`${endpoint}/api/tags`); if (!response.ok) { throw new Error('Failed to connect to Ollama'); } const models = await response.json(); this.setClient('ollama', { endpoint, available: true, models }); console.log('✅ Ollama initialized successfully'); } catch (error) { console.error('❌ Failed to initialize Ollama:', error); throw error; } } setClient(provider, client) { this.clients.set(provider, { client, lastUsed: Date.now(), isHealthy: true, connectionAttempts: 0 }); } async validateConnection(provider) { const clientInfo = this.clients.get(provider); if (!clientInfo) { throw new Error(`Client not initialized for provider: ${provider}`); } const { client } = clientInfo; try { await Promise.race([ this.performHealthCheck(provider, client), this.createTimeoutPromise(this.CONNECTION_TIMEOUT_MS) ]); clientInfo.isHealthy = true; clientInfo.connectionAttempts = 0; } catch (error) { clientInfo.isHealthy = false; clientInfo.connectionAttempts++; throw this.enhanceConnectionError(error, provider); } } async performHealthCheck(provider, client) { const healthCheckCalls = { 'azure-openai': () => this.healthCheckAzureOpenAI(client), 'azure-openai-key': () => this.healthCheckAzureOpenAI(client), 'azure-openai-entra': () => this.healthCheckAzureOpenAI(client), 'azure-ai-studio': () => this.healthCheckAzureAIStudio(client), 'github-ai': () => this.healthCheckGitHubAI(client), 'google-ai': () => Promise.resolve(), // Google AI doesn't need health check 'ollama': () => Promise.resolve() // Ollama doesn't need health check }; const healthCheck = healthCheckCalls[provider]; if (healthCheck) { await healthCheck(); } } async healthCheckAzureOpenAI(client) { const deployment = this.config.get('deployment_name'); await client.chat.completions.create({ model: deployment, messages: [{ role: 'system', content: 'ping' }, { role: 'user', content: 'ping' }], max_tokens: 1 }); } async healthCheckAzureAIStudio(client) { const deployment = this.config.get('deployment_name'); const apiVersion = this.config.get('api_version'); const apiPath = `/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`; await client.path(apiPath).post({ body: { messages: [ { role: 'system', content: 'ping' }, { role: 'user', content: 'ping' } ], model: deployment, max_tokens: 1 } }); } async healthCheckGitHubAI(client) { const modelName = this.config.get('REQUIREMENTS_AGENT_MODEL') || "gpt-4o-mini"; try { const result = await client.path("/chat/completions").post({ body: { messages: [ { role: 'system', content: 'ping' }, { role: 'user', content: 'ping' } ], model: modelName, max_tokens: 1 } }); if (result.status !== "200") { throw new Error(`GitHub AI health check failed: ${result.status}`); } } catch (error) { if (error.status === 401) { throw new Error('GitHub AI authentication failed. Please check your token.'); } else if (error.status === 404) { throw new Error(`GitHub AI model ${modelName} not found. Please check your configuration.`); } throw error; } } createTimeoutPromise(timeoutMs) { return new Promise((_, reject) => { setTimeout(() => reject(new Error('Connection timeout')), timeoutMs); }); } enhanceConnectionError(error, provider) { const errorMappings = [ { condition: (e) => e.status === 401 || /auth/i.test(e.message), message: `${provider} authentication failed. Please check your credentials.` }, { condition: (e) => e.status === 404 || /not found/i.test(e.message), message: `${provider} deployment/model not found. Please check your configuration.` }, { condition: (e) => e.status === 429 || /rate limit/i.test(e.message), message: `${provider} rate limit exceeded. Please wait or check your quota.` }, { condition: (e) => /ENOTFOUND|ECONNREFUSED|network|timeout/i.test(e.message), message: `Network error connecting to ${provider}. Please check connectivity.` } ]; const mapping = errorMappings.find(m => m.condition(error)); const message = mapping?.message || `${provider} connection failed: ${error.message}`; return new Error(message); } getClient(provider) { const targetProvider = provider || this.config.getAIProvider(); const clientInfo = this.clients.get(targetProvider); if (!clientInfo) { return null; } // Update last used timestamp clientInfo.lastUsed = Date.now(); return clientInfo.client; } getCurrentProvider() { return this.config.getAIProvider(); } isInitialized() { return this.initialized; } shouldSkipAI() { return this.skipAI; } // Health monitoring getClientHealth() { const health = {}; for (const [provider, clientInfo] of this.clients) { health[provider] = { isHealthy: clientInfo.isHealthy, lastUsed: new Date(clientInfo.lastUsed).toISOString(), connectionAttempts: clientInfo.connectionAttempts, status: clientInfo.connectionAttempts >= this.MAX_CONNECTION_ATTEMPTS ? 'degraded' : 'healthy' }; } return health; } async refreshConnection(provider) { const targetProvider = provider || this.config.getAIProvider(); // Remove existing client this.clients.delete(targetProvider); // Reinitialize await this.initializeProvider(targetProvider); console.log(`🔄 Refreshed connection for ${targetProvider}`); } // Cleanup method cleanup() { this.clients.clear(); this.initialized = false; this.skipAI = false; } } // Export for module usage export { AIClientManager }; //# sourceMappingURL=AIClientManager.js.map