UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

267 lines (266 loc) 10.6 kB
import { performFormatAwareLlmCall } from '../../../utils/llmHelper.js'; import { getPromptService } from '../services/prompt-service.js'; import { OpenRouterConfigManager } from '../../../utils/openrouter-config-manager.js'; import logger from '../../../logger.js'; export class LLMFallbackSystem { static instance; config; openRouterConfig = null; cache = new Map(); promptService = getPromptService(); constructor(config = {}) { this.config = { minPatternConfidence: config.minPatternConfidence ?? 0.7, maxProcessingTime: config.maxProcessingTime ?? 5000, temperature: config.temperature ?? 0.1, enableCaching: config.enableCaching ?? true, cacheTTL: config.cacheTTL ?? 300, maxRetries: config.maxRetries ?? 2 }; this.initializeConfig(); } static getInstance(config) { if (!LLMFallbackSystem.instance) { LLMFallbackSystem.instance = new LLMFallbackSystem(config); } return LLMFallbackSystem.instance; } async initializeConfig() { try { const configManager = OpenRouterConfigManager.getInstance(); await configManager.initialize(); this.openRouterConfig = await configManager.getOpenRouterConfig(); logger.info('LLM Fallback System initialized with OpenRouter config'); } catch (error) { logger.error({ err: error }, 'Failed to initialize LLM Fallback System config'); } } async recognizeIntent(text, patternConfidence = 0, context) { const startTime = Date.now(); try { if (!this.openRouterConfig) { await this.initializeConfig(); if (!this.openRouterConfig) { throw new Error('OpenRouter configuration not available'); } } if (patternConfidence >= this.config.minPatternConfidence) { logger.debug({ patternConfidence }, 'Pattern confidence sufficient, skipping LLM fallback'); return null; } if (this.config.enableCaching) { const cached = this.getCachedResult(text); if (cached) { logger.debug('Returning cached LLM intent recognition result'); return cached; } } const systemPrompt = await this.promptService.getPrompt('intent_recognition'); const userPrompt = this.buildUserPrompt(text, context); const llmResponse = await this.callLLMWithRetry(userPrompt, systemPrompt); const parsedResponse = this.parseLLMResponse(llmResponse); const recognizedIntent = this.convertToRecognizedIntent(text, parsedResponse, startTime); if (this.config.enableCaching) { this.cacheResult(text, recognizedIntent); } logger.info({ intent: recognizedIntent.intent, confidence: recognizedIntent.confidence, processingTime: recognizedIntent.metadata.processingTime }, 'LLM intent recognition completed'); return recognizedIntent; } catch (error) { logger.error({ err: error, text: text.substring(0, 100) }, 'LLM intent recognition failed'); return null; } } buildUserPrompt(text, context) { let prompt = `Please analyze the following user input and identify the intent:\n\n"${text}"\n\n`; if (context) { prompt += `Additional context:\n`; for (const [key, value] of Object.entries(context)) { prompt += `- ${key}: ${value}\n`; } prompt += '\n'; } prompt += `Respond with valid JSON matching the specified format. Focus on accuracy and provide confidence scores based on how clear the intent is.`; return prompt; } async callLLMWithRetry(userPrompt, systemPrompt) { let lastError = null; for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) { try { const response = await performFormatAwareLlmCall(userPrompt, systemPrompt, this.openRouterConfig, 'intent_recognition', 'json', undefined, this.config.temperature); return response; } catch (error) { lastError = error; logger.warn({ attempt, maxRetries: this.config.maxRetries, error: error }, 'LLM call failed, retrying'); if (attempt < this.config.maxRetries) { await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } } throw lastError || new Error('LLM call failed after all retries'); } parseLLMResponse(response) { try { const cleanedResponse = response .replace(/```json\s*/g, '') .replace(/```\s*/g, '') .trim(); const parsed = JSON.parse(cleanedResponse); if (!parsed.intent || typeof parsed.confidence !== 'number') { throw new Error('Invalid LLM response structure'); } parsed.confidence = Math.max(0, Math.min(1, parsed.confidence)); return parsed; } catch (error) { logger.error({ err: error, response: response.substring(0, 200) }, 'Failed to parse LLM response'); throw new Error('Invalid JSON response from LLM'); } } convertToRecognizedIntent(originalInput, llmResponse, startTime) { const processingTime = Date.now() - startTime; let intent = llmResponse.intent; let confidence = llmResponse.confidence; if (intent === 'unrecognized_intent' || intent === 'clarification_needed') { intent = 'unknown'; confidence = Math.min(confidence, 0.3); logger.debug({ originalIntent: llmResponse.intent, originalConfidence: llmResponse.confidence, adjustedConfidence: confidence }, 'Adjusted confidence for unrecognized intent'); } else if (!this.isValidIntent(intent)) { intent = 'unknown'; logger.debug({ originalIntent: llmResponse.intent, originalConfidence: llmResponse.confidence, adjustedIntent: intent }, 'Converted invalid intent to unknown'); } return { intent: intent, confidence, confidenceLevel: this.getConfidenceLevel(confidence), entities: this.convertParametersToEntities(llmResponse.parameters || {}), originalInput, processedInput: originalInput.toLowerCase().trim(), alternatives: llmResponse.alternatives?.map(alt => ({ intent: this.isValidIntent(alt.intent) ? alt.intent : 'unknown', confidence: alt.confidence })) || [], metadata: { processingTime, method: 'llm', modelUsed: this.openRouterConfig?.llm_mapping?.intent_recognition || 'unknown', timestamp: new Date() } }; } convertParametersToEntities(parameters) { const entityArray = []; for (const [type, value] of Object.entries(parameters)) { if (value !== undefined && value !== null) { entityArray.push({ type, value: String(value), confidence: 0.8 }); } } return entityArray; } getConfidenceLevel(confidence) { if (confidence >= 0.9) return 'very_high'; if (confidence >= 0.7) return 'high'; if (confidence >= 0.5) return 'medium'; if (confidence >= 0.3) return 'low'; return 'very_low'; } isValidIntent(intent) { const validIntents = [ 'create_project', 'list_projects', 'open_project', 'update_project', 'archive_project', 'create_task', 'list_tasks', 'run_task', 'check_status', 'decompose_task', 'decompose_epic', 'decompose_project', 'search_files', 'search_content', 'refine_task', 'assign_task', 'get_help', 'parse_prd', 'parse_tasks', 'import_artifact', 'unrecognized_intent', 'clarification_needed', 'unknown' ]; return validIntents.includes(intent); } getCachedResult(text) { const cacheKey = this.getCacheKey(text); const entry = this.cache.get(cacheKey); if (entry && entry.expiresAt > new Date()) { return entry.response; } if (entry) { this.cache.delete(cacheKey); } return null; } cacheResult(text, result) { const cacheKey = this.getCacheKey(text); const expiresAt = new Date(Date.now() + this.config.cacheTTL * 1000); this.cache.set(cacheKey, { response: result, timestamp: new Date(), expiresAt }); if (this.cache.size > 100) { this.cleanupExpiredCache(); } } getCacheKey(text) { return text.toLowerCase().trim().replace(/\s+/g, ' '); } cleanupExpiredCache() { const now = new Date(); let removedCount = 0; for (const [key, entry] of this.cache.entries()) { if (entry.expiresAt <= now) { this.cache.delete(key); removedCount++; } } logger.debug({ removedCount, remainingCount: this.cache.size }, 'Cleaned up expired cache entries'); } updateConfig(config) { this.config = { ...this.config, ...config }; logger.info({ config: this.config }, 'LLM Fallback configuration updated'); } getConfig() { return { ...this.config }; } clearCache() { this.cache.clear(); logger.info('LLM Fallback cache cleared'); } getCacheStats() { const now = new Date(); let expiredCount = 0; for (const entry of this.cache.values()) { if (entry.expiresAt <= now) { expiredCount++; } } return { size: this.cache.size, hitRate: 0, expiredEntries: expiredCount }; } }