UNPKG

ultimate-mcp-server

Version:

The definitive all-in-one Model Context Protocol server for AI-assisted coding across 30+ platforms

364 lines (363 loc) 17.8 kB
import { Logger } from "../utils/logger.js"; import { OrchestrationStrategy } from "../types/index.js"; import { OpenRouterProvider } from "./openrouter.js"; // import { AnthropicProvider } from "./anthropic.js"; // Disabled - using Claude directly // import { OpenAIProvider } from "./openai.js"; // import { GoogleProvider } from "./google.js"; import PQueue from "p-queue"; import { MODELS } from "../config/models.js"; export class AIOrchestrator { providers = new Map(); logger; queue; constructor() { this.logger = new Logger("AIOrchestrator"); this.queue = new PQueue({ concurrency: 5 }); this.initializeProviders(); } initializeProviders() { // Initialize providers based on environment variables if (process.env.OPENROUTER_API_KEY) { this.providers.set("openrouter", new OpenRouterProvider(process.env.OPENROUTER_API_KEY)); } // Anthropic/Claude disabled - you already have Claude! // if (process.env.ANTHROPIC_API_KEY) { // this.providers.set("anthropic", new AnthropicProvider(process.env.ANTHROPIC_API_KEY)); // } // Only use OpenRouter - it provides access to all models // if (process.env.OPENAI_API_KEY) { // this.providers.set("openai", new OpenAIProvider(process.env.OPENAI_API_KEY)); // } // if (process.env.GOOGLE_API_KEY) { // this.providers.set("google", new GoogleProvider(process.env.GOOGLE_API_KEY)); // } this.logger.info(`Initialized ${this.providers.size} AI providers`); } async orchestrate(request) { const startTime = Date.now(); const models = request.models || this.getDefaultModels(request.strategy); this.logger.info(`Starting orchestration with strategy: ${request.strategy}`); let result; switch (request.strategy) { case OrchestrationStrategy.Sequential: result = await this.sequentialProcessing(request, models); break; case OrchestrationStrategy.Parallel: result = await this.parallelProcessing(request, models); break; case OrchestrationStrategy.Debate: result = await this.debateMode(request, models); break; case OrchestrationStrategy.Consensus: result = await this.consensusMode(request, models); break; case OrchestrationStrategy.Specialist: result = await this.specialistMode(request, models); break; case OrchestrationStrategy.Hierarchical: result = await this.hierarchicalMode(request, models); break; case OrchestrationStrategy.Mixture: result = await this.mixtureOfExperts(request, models); break; default: throw new Error(`Unknown orchestration strategy: ${request.strategy}`); } result.metadata.totalDuration = Date.now() - startTime; return result; } async sequentialProcessing(request, models) { const responses = []; let currentPrompt = request.prompt; let previousResponse = ""; for (const model of models) { const enhancedPrompt = previousResponse ? `Previous analysis:\n${previousResponse}\n\nPlease refine or expand on this:\n${currentPrompt}` : currentPrompt; const response = await this.callModel(model, enhancedPrompt, request.options); responses.push(response); previousResponse = response.response; } return { strategy: OrchestrationStrategy.Sequential, responses, metadata: { totalDuration: 0, modelsUsed: models, }, }; } async parallelProcessing(request, models) { // Execute all models in parallel const promises = models.map(model => this.queue.add(() => this.callModel(model, request.prompt, request.options))); const responses = (await Promise.all(promises)).filter((r) => r !== undefined); // Synthesize responses if requested let synthesis; if (request.options?.includeReasoning) { synthesis = await this.synthesizeResponses(responses.filter((r) => r !== undefined), request.prompt); } return { strategy: OrchestrationStrategy.Parallel, responses, synthesis, metadata: { totalDuration: 0, modelsUsed: models, }, }; } async debateMode(request, models) { const rounds = []; const maxRounds = request.options?.maxRounds || 3; let currentTopic = request.prompt; for (let round = 0; round < maxRounds; round++) { const roundResponses = []; for (let i = 0; i < models.length; i++) { const model = models[i]; let prompt = currentTopic; // Add context from previous responses in this round if (roundResponses.length > 0) { const previousPoints = roundResponses.map(r => `${r.model}: ${r.response.substring(0, 200)}...`).join("\n\n"); prompt = `Topic: ${currentTopic}\n\nPrevious arguments:\n${previousPoints}\n\nProvide your perspective in 3-4 sentences, addressing the key points raised:`; } const response = await this.callModel(model, prompt, request.options); roundResponses.push(response); } rounds.push({ round: round + 1, responses: roundResponses }); // Prepare topic for next round if (round < maxRounds - 1) { currentTopic = `Based on the following perspectives, identify key disagreements and areas for further discussion:\n\n${roundResponses.map(r => `${r.model}: ${r.response}`).join("\n\n")}`; } } // Generate conclusion const conclusion = await this.generateDebateConclusion(rounds, request.prompt); return { strategy: OrchestrationStrategy.Debate, responses: rounds.flatMap(r => r.responses), rounds, conclusion, metadata: { totalDuration: 0, modelsUsed: models, }, }; } async consensusMode(request, models) { // First, get initial responses const initialResponses = (await Promise.all(models.map(model => this.queue.add(() => this.callModel(model, request.prompt, request.options))))).filter((r) => r !== undefined); // Identify areas of agreement and disagreement const analysisPrompt = `Analyze these responses and identify: 1. Points of agreement 2. Points of disagreement 3. Unique insights from each model Responses: ${initialResponses.map(r => `${r?.model}:\n${r?.response}`).join("\n\n---\n\n")}`; const analysisModel = models[0]; // Use first model for analysis const analysis = await this.callModel(analysisModel, analysisPrompt, request.options); // Generate consensus const consensusPrompt = `Based on this analysis, provide a consensus view that incorporates the strongest points from all perspectives:\n\n${analysis.response}`; const consensus = await this.callModel(analysisModel, consensusPrompt, request.options); return { strategy: OrchestrationStrategy.Consensus, responses: initialResponses, consensus: consensus.response, metadata: { totalDuration: 0, modelsUsed: models, }, }; } async specialistMode(request, models) { // Analyze the task to determine the best model const taskType = await this.analyzeTaskType(request.prompt); const bestModel = this.selectBestModel(taskType, models); this.logger.info(`Selected specialist model: ${bestModel} for task type: ${taskType}`); const response = await this.callModel(bestModel, request.prompt, request.options); return { strategy: OrchestrationStrategy.Specialist, responses: [response], metadata: { totalDuration: 0, modelsUsed: [bestModel], }, }; } async hierarchicalMode(request, models) { // Break down the problem into sub-problems const decompositionPrompt = `Break down this problem into 3-5 sub-problems that can be solved independently:\n\n${request.prompt}`; const decomposition = await this.callModel(models[0], decompositionPrompt, request.options); // Parse sub-problems (simplified for now) const subProblems = decomposition.response.split("\n").filter(line => line.trim().match(/^\d+\./)); // Solve each sub-problem const subSolutions = (await Promise.all(subProblems.map((subProblem, index) => this.callModel(models[index % models.length], subProblem, request.options)))).filter((r) => r !== undefined); // Combine solutions const combinationPrompt = `Combine these sub-solutions into a comprehensive answer:\n\n${subSolutions.map((sol, i) => `Sub-problem ${i + 1}: ${subProblems[i]}\nSolution: ${sol.response}`).join("\n\n")}`; const finalSolution = await this.callModel(models[0], combinationPrompt, request.options); return { strategy: OrchestrationStrategy.Hierarchical, responses: [...subSolutions, finalSolution], synthesis: finalSolution.response, metadata: { totalDuration: 0, modelsUsed: models, }, }; } async mixtureOfExperts(request, models) { // Get responses from all experts const expertResponses = (await Promise.all(models.map(model => this.queue.add(() => this.callModel(model, request.prompt, request.options))))).filter((r) => r !== undefined); // Score each response const scoredResponses = await Promise.all(expertResponses.filter((r) => r !== undefined).map(async (response) => { const score = await this.scoreResponse(response.response, request.prompt); return { ...response, score }; })); // Weight responses by score const topResponses = scoredResponses .sort((a, b) => b.score - a.score) .slice(0, Math.ceil(models.length / 2)); // Combine top responses const mixturePrompt = `Combine these high-quality responses into a single comprehensive answer:\n\n${topResponses.map(r => `[Score: ${r.score}] ${r.model}:\n${r.response}`).join("\n\n---\n\n")}`; const mixture = await this.callModel(models[0], mixturePrompt, request.options); return { strategy: OrchestrationStrategy.Mixture, responses: expertResponses, synthesis: mixture.response, metadata: { totalDuration: 0, modelsUsed: models, }, }; } parseModelId(model) { // Since we only have OpenRouter configured, route ALL models through it // OpenRouter handles the actual provider routing return ["openrouter", model]; } getDefaultModels(strategy) { // Return appropriate default models based on strategy switch (strategy) { case OrchestrationStrategy.Debate: case OrchestrationStrategy.Consensus: return [MODELS.GPT_4O, MODELS.GEMINI_PRO, MODELS.LLAMA_3_3_70B]; case OrchestrationStrategy.Specialist: return [MODELS.GPT_4O]; default: // Return some default models that are generally available return [MODELS.GPT_4O_MINI, MODELS.GEMINI_PRO, MODELS.LLAMA_3_3_70B]; } } async synthesizeResponses(responses, originalPrompt) { const synthesisPrompt = `Original question: ${originalPrompt}\n\nSynthesize these responses into a comprehensive answer:\n\n${responses.map(r => `${r.model}:\n${r.response}`).join("\n\n---\n\n")}`; const synthesis = await this.callModel(MODELS.SYNTHESIS_MODEL, synthesisPrompt, { temperature: 0.3 }); return synthesis.response; } async generateDebateConclusion(rounds, originalPrompt) { const conclusionPrompt = `Original topic: ${originalPrompt}\n\nBased on this debate with ${rounds.length} rounds, provide a concise 3-4 sentence conclusion that acknowledges different perspectives and identifies the key insight.`; const conclusion = await this.callModel(MODELS.SYNTHESIS_MODEL, conclusionPrompt, { temperature: 0.5 }); return conclusion.response; } async analyzeTaskType(prompt) { // Enhanced task classification with better coding detection const lower = prompt.toLowerCase(); // Enhanced coding detection for Kimi K2's strengths if (lower.includes("code") || lower.includes("debug") || lower.includes("function") || lower.includes("implement") || lower.includes("refactor") || lower.includes("optimize") || lower.includes("algorithm") || lower.includes("class") || lower.includes("method") || lower.includes("api") || lower.includes("typescript") || lower.includes("javascript") || lower.includes("python") || lower.includes("programming") || lower.includes("fix") || lower.includes("error") || lower.includes("bug") || lower.includes("component")) { return "coding"; } else if (lower.includes("analyze") || lower.includes("research")) { return "analysis"; } else if (lower.includes("creative") || lower.includes("story") || lower.includes("write")) { return "creative"; } else if (lower.includes("math") || lower.includes("calculate") || lower.includes("solve")) { return "mathematical"; } return "general"; } selectBestModel(taskType, availableModels) { // Model selection logic based on task type const preferences = { coding: [MODELS.KIMI_K2, MODELS.QWEN_2_5_CODER_32B, MODELS.GPT_4O, MODELS.DEEPSEEK_V3], analysis: [MODELS.ANALYSIS_MODEL, MODELS.GPT_4O, MODELS.MISTRAL_LARGE], creative: [MODELS.GPT_4O, MODELS.GEMINI_PRO, MODELS.LLAMA_3_3_70B], mathematical: [MODELS.GPT_4O, MODELS.GEMINI_PRO, MODELS.QWEN_2_5_CODER_32B], general: [MODELS.GEMINI_PRO, MODELS.GPT_4O, MODELS.LLAMA_3_3_70B], }; const preferred = preferences[taskType] || preferences.general; for (const model of preferred) { if (availableModels.includes(model)) { return model; } } return availableModels[0]; } async scoreResponse(response, prompt) { // Simple scoring based on response quality indicators let score = 50; // Base score // Length bonus (but not too long) const wordCount = response.split(/\s+/).length; if (wordCount > 100 && wordCount < 1000) { score += 10; } // Structure bonus (has sections/paragraphs) if (response.includes("\n\n")) { score += 10; } // Relevance bonus (mentions key terms from prompt) const promptTerms = prompt.toLowerCase().split(/\s+/); const responseTerms = response.toLowerCase(); const relevanceCount = promptTerms.filter(term => term.length > 3 && responseTerms.includes(term)).length; score += Math.min(relevanceCount * 5, 20); // Completeness bonus (has conclusion or summary) if (responseTerms.includes("in conclusion") || responseTerms.includes("summary") || responseTerms.includes("therefore")) { score += 10; } return Math.min(score, 100); } async callModel(model, prompt, options) { const [providerName] = this.parseModelId(model); const provider = this.providers.get(providerName); if (!provider) { throw new Error(`Provider ${providerName} not available`); } const startTime = Date.now(); try { // Handle thinking mode let actualModel = model; let actualPrompt = prompt; if (options?.useThinking) { // Add thinking tokens to prompt for deep reasoning // Note: |thinking model variants don't exist yet, so we use prompt engineering const thinkingPrefix = options.thinkingTokens?.join(" ") || "Let me think through this step by step:\n\n"; actualPrompt = thinkingPrefix + prompt; // Optional: Log that we're using thinking mode this.logger.debug(`Using thinking mode for model: ${model}`); } const response = options?.context ? await provider.completeWithContext(options.context.concat([{ role: "user", content: actualPrompt }]), { model: actualModel, temperature: options?.temperature, }) : await provider.complete(actualPrompt, { model: actualModel, temperature: options?.temperature, }); return { model: actualModel, response, duration: Date.now() - startTime, }; } catch (error) { this.logger.error(`Error calling model ${model}:`, error); throw error; } } } //# sourceMappingURL=orchestrator.js.map