@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
999 lines (955 loc) • 106 kB
JavaScript
/**
* Consensus Engine - SOURCE_OF_TRUTH Implementation
*
* Core 4-stage consensus pipeline per exact design specifications.
* Each complete cycle (4 stages) counts as one conversation against user's daily limit.
*/
import { v4 as uuidv4 } from 'uuid';
import { callOpenRouter } from './openrouter-client.js';
import { OpenRouterStreamingClient } from './openrouter-streaming-client.js';
import { initializeUnifiedDatabase, addMessage, createConversation, getDefaultPipelineProfile, getPipelineProfile, getCurrentModelId, getOpenRouterApiKey, validateOpenRouterApiKey, getCurrentUserId, addToPendingSync, markSyncComplete, recordSyncAttempt } from '../storage/unified-database.js';
import { conversationGateway, ConversationGatewayError, UsageLimitExceededError } from '../auth/conversation-gateway.js';
import { getLicenseKey } from '../subscription/validator.js';
import { globalErrorHandler } from './error-handling.js';
import { globalHealthMonitor } from './health-monitor.js';
import { structuredLogger } from './structured-logger.js';
/**
* Run a single consensus stage per SOURCE_OF_TRUTH design
* Function: runConsensusStage(stageName, question, previousAnswer, model, conversationId)
*/
export async function runConsensusStage(stageName, question, previousAnswer, model, conversationId, enableStreaming = false, callbacks, modelInternalId) {
// Initialize database
await initializeUnifiedDatabase();
// Get OpenRouter API key
const apiKey = await getOpenRouterApiKey();
if (!apiKey) {
throw new Error('No OpenRouter API key configured. Please run: hive-ai configure');
}
if (!validateOpenRouterApiKey(apiKey)) {
throw new Error('Invalid OpenRouter API key format. Please reconfigure.');
}
// Build messages based on stage and context
const messages = await buildStageMessages(stageName, question, previousAnswer);
try {
// Call OpenRouter API with bulletproof error handling
const [provider, modelName] = model.includes('/') ? model.split('/') : ['openrouter', model];
const requestId = `${conversationId}-${stageName}-${Date.now()}`;
// Enhanced tracking variables
let errorCount = 0;
let fallbackUsed = false;
let rateLimitHit = false;
let retryCount = 0;
const stageStartTime = new Date();
let qualityScore = 8.5; // Default quality score
// Use provided model internal ID for cost calculation (passed from profile system)
structuredLogger.stageStart(stageName, {
provider,
model: modelName,
conversationId,
requestId
});
callbacks?.onStageStart?.(stageName, modelName);
const startTime = Date.now();
let response;
if (enableStreaming) {
// Use streaming client
const streamingClient = new OpenRouterStreamingClient(apiKey);
response = await globalErrorHandler.executeWithFallback(async (fallbackProvider, fallbackModel) => {
const fullModel = fallbackProvider === 'openrouter' ? fallbackModel : `${fallbackProvider}/${fallbackModel}`;
// Track if fallback was used
if (fallbackProvider !== provider || fallbackModel !== modelName) {
fallbackUsed = true;
retryCount++;
}
const streamingCallbacks = {
onStart: () => {
structuredLogger.info(`Starting streaming for ${stageName} stage`, { model: fullModel });
},
onChunk: (chunk, totalContent) => {
callbacks?.onStageChunk?.(stageName, chunk, totalContent);
},
onProgress: (progress) => {
callbacks?.onStageProgress?.(stageName, {
tokens: progress.tokens || 0,
percentage: progress.percentage || 0
});
},
onError: (error) => {
errorCount++;
if (error.message.includes('rate limit')) {
rateLimitHit = true;
}
callbacks?.onError?.(stageName, error);
}
};
return await streamingClient.streamChatCompletion(fullModel, messages, { temperature: 0.7, max_tokens: 4000 }, streamingCallbacks);
}, provider, modelName, 'streaming', requestId).then(result => result.result);
}
else {
// Use regular non-streaming client
response = await globalErrorHandler.executeWithFallback(async (fallbackProvider, fallbackModel) => {
const fullModel = fallbackProvider === 'openrouter' ? fallbackModel : `${fallbackProvider}/${fallbackModel}`;
// Track if fallback was used
if (fallbackProvider !== provider || fallbackModel !== modelName) {
fallbackUsed = true;
retryCount++;
}
structuredLogger.apiRequestStart(fallbackProvider, fallbackModel, { requestId });
const apiStartTime = Date.now();
try {
const result = await callOpenRouter(fullModel, messages, apiKey);
structuredLogger.apiRequestComplete(fallbackProvider, fallbackModel, Date.now() - apiStartTime, { requestId });
return result;
}
catch (error) {
errorCount++;
if (error.message.includes('rate limit')) {
rateLimitHit = true;
}
structuredLogger.apiRequestError(fallbackProvider, fallbackModel, error, { requestId });
throw error;
}
}, provider, modelName, 'consensus', requestId).then(result => result.result);
}
const duration = Date.now() - startTime;
const stageEndTime = new Date();
// Calculate cost for this stage
let stageCost = 0;
if (response.usage && modelInternalId) {
try {
const { calculateModelCost } = await import('../storage/unified-database.js');
structuredLogger.info(`💰 Calculating cost for model internal ID: ${modelInternalId}`, {
modelInternalId,
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens
});
stageCost = await calculateModelCost(modelInternalId, response.usage.prompt_tokens, response.usage.completion_tokens);
structuredLogger.info(`💰 Stage cost calculated: $${stageCost.toFixed(6)}`, {
stageCost,
modelInternalId
});
}
catch (error) {
console.error('Failed to calculate model cost:', error);
}
}
else {
structuredLogger.debug('💰 Cannot calculate cost', {
hasUsage: !!response.usage,
modelInternalId
});
}
// Generate stage ID and prepare result
const stageId = uuidv4();
const result = {
stageId,
stageName,
question,
answer: response.content,
model: response.model || model,
conversationId,
timestamp: new Date().toISOString(),
usage: response.usage,
analytics: {
duration,
cost: stageCost,
provider,
modelInternalId: modelInternalId?.toString() || model,
qualityScore,
errorCount,
fallbackUsed,
rateLimitHit,
retryCount,
startTime: stageStartTime.toISOString(),
endTime: stageEndTime.toISOString(),
features: {
streaming: enableStreaming,
routing_variant: 'intelligent' // Indicates this uses the intelligent routing
}
}
};
// Save stage result to SQLite
await saveStageResult(result);
structuredLogger.stageComplete(stageName, duration, {
provider,
model: modelName,
conversationId,
requestId,
usage: response.usage,
cost: stageCost,
qualityScore,
errorCount,
fallbackUsed
});
callbacks?.onStageComplete?.(stageName, result);
return result;
}
catch (error) {
const [provider, modelName] = model.includes('/') ? model.split('/') : ['openrouter', model];
const requestId = `${conversationId}-${stageName}-${Date.now()}`;
structuredLogger.stageError(stageName, error, {
provider,
model: modelName,
conversationId,
requestId
});
console.error(`❌ ${stageName} stage failed after all retries and fallbacks:`, error.message);
throw new Error(`${stageName} stage failed: ${error.message}`);
}
}
/**
* Build stage-specific messages per SOURCE_OF_TRUTH design
* CRITICAL: "Original user question + Stage X answer (as supplemental knowledge)"
*/
export async function buildStageMessages(stageName, question, previousAnswer) {
const messages = [];
// Get curator knowledge as learning material for all stages
const curatorKnowledge = await getCuratorKnowledgeBase(question);
// ALL stages get the same question + curator knowledge as context
if (curatorKnowledge) {
messages.push({
role: 'system',
content: `IMPORTANT: The following are AUTHORITATIVE ARTICLES from previous curator-verified answers. These represent established knowledge and should be treated as factual sources, NOT as content to be improved or critiqued.
${curatorKnowledge}
Instructions:
1. Use these articles to supplement your knowledge when answering the user's question
2. Do NOT attempt to improve, critique, or rewrite these articles
3. Reference specific information from these articles when relevant
4. If the user's question is a follow-up to any of these articles, ensure continuity
5. These articles represent the collective knowledge built over time - respect their authority
Now, please answer the user's question, incorporating relevant knowledge from the articles above where applicable.`
});
}
// Topic focus will be added AFTER all other instructions to ensure it takes priority
if (stageName.toLowerCase() === 'generator') {
// Generator gets scope-aware system instruction
const questionComplexity = detectQuestionComplexity(question);
const generatorInstruction = getGeneratorInstruction(questionComplexity);
messages.push({
role: 'system',
content: generatorInstruction
});
// Generator gets fresh question
messages.push({
role: 'user',
content: question
});
}
switch (stageName.toLowerCase()) {
case 'generator':
messages.push({
role: 'system',
content: `You are the Generator in a 4-stage AI consensus pipeline. Your role is to provide the initial analysis and creative problem decomposition. Focus on breadth and exploring multiple angles.`
});
messages.push({
role: 'user',
content: question
});
break;
case 'refiner':
messages.push({
role: 'system',
content: `You are the Refiner in a 4-stage AI consensus pipeline. Your role is to enhance and specify the technical details from the initial analysis. Focus on depth, accuracy, and implementation details.`
});
messages.push({
role: 'user',
content: `Original question: ${question}\n\nInitial analysis:\n${previousAnswer}\n\nPlease refine and enhance this analysis with technical depth and specific details.`
});
break;
case 'validator':
messages.push({
role: 'system',
content: `You are the Validator in a 4-stage AI consensus pipeline. Your role is to fact-check, verify, and provide a different perspective. Focus on accuracy, potential issues, and alternative viewpoints.`
});
messages.push({
role: 'user',
content: `Original question: ${question}\n\nRefined analysis:\n${previousAnswer}\n\nPlease validate this analysis, check for accuracy, and provide alternative perspectives.`
});
break;
case 'curator':
messages.push({
role: 'system',
content: `You are the Curator in a 4-stage AI consensus pipeline. Your role is to synthesize all previous analyses into a polished, comprehensive final answer. Focus on clarity, completeness, and actionable insights.`
});
messages.push({
role: 'user',
content: `Original question: ${question}\n\nValidated analysis:\n${previousAnswer}\n\nPlease synthesize this into a final, polished response that addresses the original question comprehensively.`
});
break;
}
return messages;
}
/**
* Generate scope-aware generator instruction
*/
function getGeneratorInstruction(complexity) {
if (!complexity.hasCode) {
return `You are a knowledgeable assistant. Provide a clear, direct answer to the question. Focus on being helpful and accurate without over-complicating the response.`;
}
switch (complexity.scope) {
case 'minimal':
return `You are a helpful coding assistant.
**SCOPE**: The user asked for something SIMPLE/MINIMAL. Keep it that way!
**CRITICAL RULES**:
- Provide the SIMPLEST working code that answers their question
- NO enterprise features unless explicitly requested
- NO over-engineering (no Docker, monitoring, advanced security for simple demos)
- Focus on clarity and minimalism
- If they say "simple", give them simple
Provide working code, but match the simplicity level they requested.`;
case 'basic':
return `You are a software developer creating practical solutions.
**SCOPE**: Provide a solid, working implementation with basic best practices.
**GUIDELINES**:
- Include essential error handling and validation
- Add basic configuration (package.json, simple setup)
- Focus on functionality over enterprise features
- Keep it practical and usable
- Don't add features they didn't ask for
Provide complete, working code that's appropriately scoped.`;
case 'production':
return `You are a senior software engineer building production-ready systems.
**SCOPE**: The user explicitly requested production/enterprise-level features.
**REQUIREMENTS**:
- Include comprehensive error handling, logging, monitoring
- Add security middleware, input validation, rate limiting
- Provide Docker configuration, environment management
- Include health checks, metrics, proper project structure
- Focus on scalability, maintainability, deployment readiness
Provide complete, production-ready code with enterprise-grade features.`;
default:
return `You are a software developer. Provide working code that matches the complexity level requested.`;
}
}
/**
* Generate scope-aware improvement instructions that are context-aware
*/
function getImprovementInstruction(stageName, complexity) {
// If this is not a code question, use general improvement instructions
if (!complexity.hasCode) {
const generalInstructions = {
refiner: `Review and improve the previous response for accuracy, clarity, and completeness.
- Verify all facts and information are correct
- Improve clarity and organization of the response
- Add any important details that were missed
- Ensure the answer fully addresses the question
Provide an improved, more complete answer.`,
validator: `Validate the accuracy and quality of the response.
- Check that all information provided is correct
- Verify the answer fully addresses the original question
- Ensure the response is well-organized and clear
- Confirm no important aspects were overlooked
Deliver a validated, accurate response.`,
curator: `You are the final curator. Your job is to deliver the definitive answer to the user.
**CRITICAL**: Do NOT provide commentary on previous stages. Do NOT say "the previous response" or "my previous response".
**YOUR ROLE**: Take the best content from previous stages and deliver it as THE FINAL ANSWER.
- Present the information clearly and professionally
- Format for maximum readability
- Include all important points
- This is what the user will see as their final result
Deliver the final answer directly - no meta-commentary.`
};
return generalInstructions[stageName] || generalInstructions.refiner;
}
const baseInstructions = {
minimal: {
refiner: `Review the previous response for accuracy and clarity.
**CRITICAL**: The user asked for something SIMPLE. Do NOT add complexity!
- Fix any bugs or syntax errors
- Improve code clarity if needed
- DO NOT add enterprise features (Docker, monitoring, advanced security)
- Keep it minimal and focused on their actual request
Provide the improved simple solution.`,
validator: `Validate that the solution works and is appropriate for the request.
**SCOPE CHECK**: Ensure this matches the SIMPLE/MINIMAL request.
- Check that the code would actually run
- Verify it answers their specific question
- REMOVE any over-engineering or unnecessary complexity
- Ensure it's still simple and focused
Deliver a clean, simple, working solution.`,
curator: `Finalize the simple solution.
**FINAL CHECK**: Keep it simple as requested!
- Ensure the code is clean and minimal
- Verify it directly answers their question
- Remove any enterprise bloat
- Polish for clarity and simplicity
Deliver the final simple, working solution.`
},
basic: {
refiner: `Review and improve the implementation with practical enhancements.
- Add basic error handling where missing
- Improve code structure and readability
- Include essential configuration (package.json, basic setup)
- Focus on making it more robust but not over-engineered
Provide a practical, improved implementation.`,
validator: `Validate the implementation for practical use.
- Check for bugs and ensure it would work
- Verify basic best practices are followed
- Add missing essential features (basic validation, error handling)
- Ensure it's complete but appropriately scoped
Deliver a solid, working implementation.`,
curator: `Finalize the practical solution.
- Polish the code for clarity and usability
- Ensure all components work together
- Add basic documentation/setup instructions
- Verify it's complete and ready to use
**CURATOR ROLE**: You deliver the final working solution to the user.
**CRITICAL**: Do NOT say "the previous response" or provide commentary.
Simply deliver the complete, working solution directly.
Deliver the final practical, working solution.`
},
production: {
refiner: `Conduct a production readiness review.
- Identify security vulnerabilities and implementation gaps
- Add comprehensive error handling and logging
- Include proper input validation and sanitization
- Review for scalability and performance issues
Provide a production-enhanced implementation.`,
validator: `Perform enterprise-grade validation.
- Validate security implementations (auth, middleware, input validation)
- Check deployment configurations (Docker, environment variables)
- Verify monitoring, logging, and health check implementations
- Ensure all enterprise requirements are met
Deliver a production-validated implementation.`,
curator: `You are the final curator delivering the enterprise solution to the user.
**CRITICAL**: Do NOT provide commentary. Deliver the final solution directly.
**YOUR ROLE**: Present the complete enterprise-ready solution with:
- Full implementation with all production features
- Clear deployment instructions
- Professional documentation
- Security and monitoring included
Deliver the final enterprise solution - no meta-commentary.`
}
};
const scopeKey = complexity.scope;
const stageKey = stageName.toLowerCase();
return baseInstructions[scopeKey]?.[stageKey] ||
`Improve this response while maintaining appropriate scope. Provide working code, not descriptions.`;
}
async function getCuratorKnowledgeBase(question) {
try {
const { getDatabase } = await import('../storage/unified-database.js');
const database = await getDatabase();
// Hierarchical retrieval approach
console.log('📚 Retrieving curator knowledge base...');
// Step 1: Get recent curator answers (last 24 hours)
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const recentAnswers = await getRecentCuratorAnswers(database, twentyFourHoursAgo, 3);
// Step 2: Detect follow-up patterns and adjust retrieval strategy
const followUpPatterns = [
/give me (\d+|\w+) examples?/i,
/tell me more/i,
/can you explain/i,
/show me/i,
/what about/i,
/how does? it work/i,
/examples? from/i,
/from.*(earlier|previous|before)/i,
/can you.*guess/i,
/try.*list/i,
/based on.*previous/i,
/following up/i,
/as (i|we) (mentioned|discussed|said)/i,
/earlier.*question/i,
/\b(it|that|this|those|these)\b/i // Implicit references
];
const isFollowUp = followUpPatterns.some(pattern => pattern.test(question));
// For follow-up queries OR when we have recent conversations, provide context
if ((isFollowUp || recentAnswers.length > 0) && recentAnswers.length > 0) {
structuredLogger.consensusDebug(`[CONSENSUS] ${isFollowUp ? 'Detected follow-up query' : 'Found recent conversations'}, providing context`);
structuredLogger.consensusDebug(`[CONSENSUS] Recent answers found: ${recentAnswers.length}`);
// Return recent conversations formatted as authoritative articles
const formattedKnowledge = recentAnswers.map((answer, index) => `AUTHORITATIVE ARTICLE ${index + 1} (from ${new Date(answer.created_at).toLocaleDateString()}):\nOriginal Question: ${answer.question}\n\nVerified Answer:\n${answer.curator_output}\n\n---`).join('\n\n');
structuredLogger.consensusDebug(`[CONSENSUS] Returning ${recentAnswers.length} recent conversations as context`);
return formattedKnowledge;
}
// Step 3: Get thematically relevant answers for non-follow-up queries
const keyTerms = extractKeyTerms(question);
let thematicAnswers = [];
if (keyTerms.length > 0) {
thematicAnswers = await database.all(`
SELECT DISTINCT ct.curator_output, kc.question, kc.created_at
FROM curator_truths ct
JOIN knowledge_conversations kc ON ct.conversation_id = kc.conversation_id
JOIN conversation_keywords ck ON kc.conversation_id = ck.conversation_id
WHERE ck.keyword IN (${keyTerms.map(() => '?').join(',')})
AND ct.confidence_score > 0.7
AND kc.conversation_id NOT IN (SELECT conversation_id FROM knowledge_conversations WHERE created_at >= ?)
ORDER BY ct.confidence_score DESC, kc.created_at DESC
LIMIT 2
`, [...keyTerms, twentyFourHoursAgo]);
}
// Combine recent and thematic answers
const allAnswers = [...recentAnswers, ...thematicAnswers];
if (allAnswers.length === 0)
return null;
// Format as authoritative articles
const formattedKnowledge = allAnswers.map((answer, index) => `AUTHORITATIVE ARTICLE ${index + 1} (from ${new Date(answer.created_at).toLocaleDateString()}):\nOriginal Question: ${answer.question}\n\nVerified Answer:\n${answer.curator_output}\n\n---`).join('\n\n');
return formattedKnowledge;
}
catch (error) {
console.error('Error getting curator knowledge:', error);
return null;
}
}
/**
* Get recent curator answers from the last N hours
*/
async function getRecentCuratorAnswers(database, since, limit = 5) {
return database.all(`
SELECT ct.curator_output, kc.question, kc.created_at
FROM curator_truths ct
JOIN knowledge_conversations kc ON ct.conversation_id = kc.conversation_id
WHERE kc.created_at >= ?
ORDER BY kc.created_at DESC
LIMIT ?
`, [since, limit]);
}
/**
* Save stage result to SQLite per SOURCE_OF_TRUTH design
* FORMAT: "question + [stage]_full_answer + conversation_id"
*/
async function saveStageResult(result) {
try {
// Per SOURCE_OF_TRUTH: Save as "question + [stage]_full_answer + conversation_id"
const messageContent = `QUESTION: ${result.question}\n\n${result.stageName.toUpperCase()}_FULL_ANSWER: ${result.answer}`;
await addMessage(result.stageId, result.conversationId, 'assistant', messageContent);
}
catch (error) {
console.error('Error saving stage result:', error);
throw error;
}
}
/**
* Read previous stage result from SQLite per SOURCE_OF_TRUTH design
*/
export async function getPreviousStageResult(conversationId, stageName) {
try {
const { getConversationHistory } = await import('../storage/database.js');
// Get recent messages for this conversation
const messages = await getConversationHistory(conversationId, 10);
// Find the most recent message for the specified stage
const stagePattern = new RegExp(`${stageName.toUpperCase()}_FULL_ANSWER:\\s*(.+)`, 'is');
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
if (message.role === 'assistant') {
const match = message.content.match(stagePattern);
if (match) {
return match[1].trim();
}
}
}
return null;
}
catch (error) {
console.error('Error getting previous stage result:', error);
return null;
}
}
export async function runConsensusPipeline(question, conversationId, modeOverride, outputOptions = {}, enableStreaming = false, callbacks) {
// Set default output format based on environment
const { outputFormat = detectEnvironmentOutputFormat(), enableProgress = true, showTokenProgress = false } = outputOptions;
// Create output writer based on format
const outputWriter = createOutputWriter(outputFormat, enableProgress, showTokenProgress);
await initializeUnifiedDatabase();
// Ensure flagship profiles exist
const { ensureFlagshipProfiles } = await import('./create-flagship-profiles.js');
await ensureFlagshipProfiles();
// PHASE 1: Pre-conversation authorization (SERVER-SIDE VALIDATION)
console.log('🔐 Requesting server authorization...');
let authorization;
try {
authorization = await conversationGateway.requestConversationAuthorization(question);
}
catch (error) {
if (error instanceof UsageLimitExceededError) {
// Attempt automatic license refresh before showing error
const result = await UsageLimitExceededError.handleWithAutoRefresh(error);
if (result === 'retry') {
// Plan upgraded, retry the authorization
try {
authorization = await conversationGateway.requestConversationAuthorization(question);
}
catch (retryError) {
// If retry fails, show the original error
throw new Error(error.getFormattedMessage());
}
}
else {
// No upgrade detected, show normal error
throw new Error(error.getFormattedMessage());
}
}
else if (error instanceof ConversationGatewayError) {
throw new Error(`Authorization failed: ${error.message}`);
}
else {
throw new Error(`Server communication failed: ${error.message}`);
}
}
// Get current user ID for pending sync tracking
const userId = await getCurrentUserId();
if (!userId) {
throw new Error('Unable to identify current user. Please reconfigure your license.');
}
// Add to pending sync queue (will be verified after completion)
await addToPendingSync(conversationId, userId, authorization.questionHash, authorization.conversationToken);
// IMMEDIATELY record conversation to D1 (simple approach - right after authorization)
console.log(`📊 Recording conversation to D1 immediately after authorization...`);
try {
await recordConversationToD1(conversationId, authorization.questionHash, {});
console.log(`✅ D1 conversation recorded successfully`);
}
catch (error) {
console.warn('⚠️ D1 recording failed (will use local count):', error.message);
}
// PHASE 1.5: Health checks and intelligence updates
console.log('🏥 Running system health checks...');
const systemHealth = globalHealthMonitor.getSystemHealth();
if (systemHealth.overall === 'unhealthy') {
console.warn('⚠️ System health is degraded. Proceeding with extra caution.');
console.warn(`- OpenRouter: ${systemHealth.openrouter.status}`);
console.warn(`- Database: ${systemHealth.database.status}`);
}
else {
console.log('✅ System health checks passed');
}
console.log('🔍 Checking OpenRouter intelligence updates...');
try {
const { checkIntelligenceRefresh, syncOpenRouterWithIntelligence } = await import('../storage/unified-database.js');
const needsUpdate = await checkIntelligenceRefresh();
if (needsUpdate) {
console.log('📊 Updating model rankings and intelligence data...');
const syncResult = await syncOpenRouterWithIntelligence();
if (syncResult.success) {
console.log(`✅ Intelligence updated: ${syncResult.rankingStats.rankingsCollected} rankings collected`);
}
}
else {
console.log('📊 Intelligence data is current');
}
}
catch (error) {
console.warn('⚠️ Intelligence update failed, continuing with cached data:', error.message);
}
// PHASE 2: DYNAMIC MODEL SELECTION & COST INTELLIGENCE
console.log('🧠 Analyzing question and selecting optimal models...');
// Analyze question complexity and type
const questionComplexity = detectQuestionComplexity(question);
const questionCategory = extractQuestionType(question);
// Get consensus mode and profile settings
const { getConfig } = await import('../storage/unified-database.js');
const consensusMode = modeOverride || await getConfig('consensus_mode') || 'auto';
let modelLineup;
let modelInternalIds;
let selectionReasoning = '';
let estimatedCost = 0;
if (consensusMode === 'manual') {
// Manual mode: Use user's default profile
console.log('🎯 Using manual profile mode');
const activeProfile = await getDefaultPipelineProfile();
if (!activeProfile) {
throw new Error('No pipeline profile configured. Please run: hive-ai setup');
}
// Get current model IDs using stable internal references
const generatorModel = await getCurrentModelId(activeProfile.generator_model_internal_id);
const refinerModel = await getCurrentModelId(activeProfile.refiner_model_internal_id);
const validatorModel = await getCurrentModelId(activeProfile.validator_model_internal_id);
const curatorModel = await getCurrentModelId(activeProfile.curator_model_internal_id);
if (!generatorModel || !refinerModel || !validatorModel || !curatorModel) {
throw new Error('Profile contains invalid model references. Please recreate your profile with: hive-ai setup');
}
modelLineup = {
generator: generatorModel,
refiner: refinerModel,
validator: validatorModel,
curator: curatorModel
};
modelInternalIds = {
generator: activeProfile.generator_model_internal_id,
refiner: activeProfile.refiner_model_internal_id,
validator: activeProfile.validator_model_internal_id,
curator: activeProfile.curator_model_internal_id
};
selectionReasoning = `Using manual profile: ${activeProfile.name}`;
}
else {
// Dynamic mode: Use intelligent profile selection + model optimization
console.log('🚀 Using dynamic intelligent model selection');
try {
// STEP 1: Try intelligent profile selection first
console.log('🎯 Analyzing question for optimal profile selection...');
let optimalProfile = await selectOptimalProfile(question);
if (optimalProfile) {
console.log(`🎯 Found optimal profile: ${optimalProfile.name}`);
// Get models from the optimal profile
const generatorModel = await getCurrentModelId(optimalProfile.generator_model_internal_id);
const refinerModel = await getCurrentModelId(optimalProfile.refiner_model_internal_id);
const validatorModel = await getCurrentModelId(optimalProfile.validator_model_internal_id);
const curatorModel = await getCurrentModelId(optimalProfile.curator_model_internal_id);
if (generatorModel && refinerModel && validatorModel && curatorModel) {
modelLineup = {
generator: generatorModel,
refiner: refinerModel,
validator: validatorModel,
curator: curatorModel
};
modelInternalIds = {
generator: optimalProfile.generator_model_internal_id,
refiner: optimalProfile.refiner_model_internal_id,
validator: optimalProfile.validator_model_internal_id,
curator: optimalProfile.curator_model_internal_id
};
selectionReasoning = `Intelligent profile selection: ${optimalProfile.name} (complexity: ${questionComplexity.complexity}, category: ${questionCategory})`;
// Estimate cost for the selected profile
try {
const { CostIntelligence } = await import('./cost-intelligence.js');
const costIntel = new CostIntelligence();
// Simplified cost estimation - just use a basic calculation for now
estimatedCost = 0.015; // Default reasonable estimate
}
catch (error) {
estimatedCost = 0.015; // Fallback cost estimate
}
console.log(`✅ Using intelligent profile-based selection`);
}
else {
console.log('⚠️ Optimal profile has invalid models, falling back to dynamic selection');
optimalProfile = null; // Force fallback
}
}
// STEP 2: Fallback to dynamic model selector if no optimal profile found
if (!optimalProfile) {
console.log('🔍 No optimal profile found, using per-model dynamic selection...');
const { DynamicModelSelector } = await import('./dynamic-model-selector.js');
const { CostIntelligence } = await import('./cost-intelligence.js');
const selector = new DynamicModelSelector();
const costIntel = new CostIntelligence();
// Get budget constraints from user settings
const budgetLimit = await getConfig('consensus_budget_limit');
const budgetConstraints = budgetLimit ? {
maxTotalCost: parseFloat(budgetLimit),
prioritizeCost: false
} : undefined;
// Get performance preferences
const performanceMode = await getConfig('consensus_performance_mode') || 'balanced';
const performanceTargets = {
prioritizeSpeed: performanceMode === 'speed',
prioritizeQuality: performanceMode === 'quality',
maxLatency: performanceMode === 'speed' ? 2000 : undefined
};
// Select complete model lineup
const selection = await selector.selectCompleteLineup(questionComplexity.scope, questionCategory, {
budgetConstraints,
performanceTargets
}, conversationId);
modelLineup = {
generator: selection.generator.openrouterId,
refiner: selection.refiner.openrouterId,
validator: selection.validator.openrouterId,
curator: selection.curator.openrouterId
};
modelInternalIds = {
generator: selection.generator.internalId,
refiner: selection.refiner.internalId,
validator: selection.validator.internalId,
curator: selection.curator.internalId
};
selectionReasoning = selection.selectionReasoning;
estimatedCost = selection.totalEstimatedCost;
console.log(`🎯 Dynamic selection complete - Estimated cost: $${estimatedCost.toFixed(4)}`);
console.log(`📋 Selected models: ${selection.generator.openrouterId} → ${selection.refiner.openrouterId} → ${selection.validator.openrouterId} → ${selection.curator.openrouterId}`);
}
}
catch (error) {
console.warn('⚠️ Dynamic selection failed, falling back to flagship profile:', error.message);
// Fallback to flagship profile
const flagshipProfile = await getPipelineProfile('Consensus_Elite') || await getDefaultPipelineProfile();
if (!flagshipProfile) {
throw new Error('No fallback profile available. Please run: hive-ai setup');
}
const generatorModel = await getCurrentModelId(flagshipProfile.generator_model_internal_id);
const refinerModel = await getCurrentModelId(flagshipProfile.refiner_model_internal_id);
const validatorModel = await getCurrentModelId(flagshipProfile.validator_model_internal_id);
const curatorModel = await getCurrentModelId(flagshipProfile.curator_model_internal_id);
if (!generatorModel || !refinerModel || !validatorModel || !curatorModel) {
throw new Error('Fallback profile contains invalid model references. Please recreate profiles with: hive-ai setup');
}
modelLineup = {
generator: generatorModel,
refiner: refinerModel,
validator: validatorModel,
curator: curatorModel
};
modelInternalIds = {
generator: flagshipProfile.generator_model_internal_id,
refiner: flagshipProfile.refiner_model_internal_id,
validator: flagshipProfile.validator_model_internal_id,
curator: flagshipProfile.curator_model_internal_id
};
selectionReasoning = `Fallback to flagship profile: ${flagshipProfile.name}`;
}
}
// Ensure modelLineup was assigned
if (!modelLineup) {
throw new Error('Failed to select models for consensus pipeline');
}
const { generator: generatorModel, refiner: refinerModel, validator: validatorModel, curator: curatorModel } = modelLineup;
if (!generatorModel || !refinerModel || !validatorModel || !curatorModel) {
throw new Error('Selected models are invalid. Please check your configuration.');
}
console.log(`🎯 Model selection: ${consensusMode === 'manual' ? 'Manual Profile' : 'Dynamic Intelligence'}`);
console.log(`📋 Models: ${generatorModel} → ${refinerModel} → ${validatorModel} → ${curatorModel}`);
console.log(`🧠 Selection reasoning: ${selectionReasoning}`);
if (estimatedCost > 0) {
console.log(`💰 Estimated cost: $${estimatedCost.toFixed(4)}`);
}
// Create conversation if it doesn't exist
await createConversation(conversationId);
try {
// Track overall pipeline performance
const pipelineStartTime = Date.now();
const stagePerformance = {};
// Get relevant context from previous conversations with intelligent follow-up detection
outputWriter.stageStart('🔍 Generator stage is now thinking deeply about your question...please stand by...');
const relevantContext = await getIntelligentRelevantContext(question);
// Stage 1 - Generator: Original user question + relevant context from previous conversations
outputWriter.stageStart('🧠 Running Generator stage with error handling...');
const generatorStartTime = Date.now();
const generatorResult = await runConsensusStage('generator', question, relevantContext, generatorModel, conversationId, enableStreaming, callbacks, modelInternalIds?.generator);
const generatorDuration = Date.now() - generatorStartTime;
stagePerformance.generator = {
duration: generatorDuration,
tokensPerSecond: 0, // Will be calculated from actual response
cost: 0, // Will be filled from cost data
retries: 0,
modelUsed: generatorModel,
latency: generatorDuration,
efficiency: 0
};
outputWriter.stageComplete('✅ Generator stage completed...now starting the Refiner stage');
// Stage 2 - Refiner: Original question + Stage 1 answer (as supplemental knowledge)
outputWriter.stageStart('⚡ Running Refiner stage with error handling...');
const refinerStartTime = Date.now();
const refinerResult = await runConsensusStage('refiner', question, generatorResult.answer, refinerModel, conversationId, enableStreaming, callbacks, modelInternalIds?.refiner);
const refinerDuration = Date.now() - refinerStartTime;
stagePerformance.refiner = {
duration: refinerDuration,
tokensPerSecond: 0,
cost: 0,
retries: 0,
modelUsed: refinerModel,
latency: refinerDuration,
efficiency: 0
};
outputWriter.stageComplete('✅ Refiner stage completed...now starting the Validator stage');
// Stage 3 - Validator: Original question + Stage 2 answer (as supplemental knowledge)
outputWriter.stageStart('🔍 Running Validator stage with error handling...');
const validatorStartTime = Date.now();
const validatorResult = await runConsensusStage('validator', question, refinerResult.answer, validatorModel, conversationId, enableStreaming, callbacks, modelInternalIds?.validator);
const validatorDuration = Date.now() - validatorStartTime;
stagePerformance.validator = {
duration: validatorDuration,
tokensPerSecond: 0,
cost: 0,
retries: 0,
modelUsed: validatorModel,
latency: validatorDuration,
efficiency: 0
};
outputWriter.stageComplete('✅ Validator stage completed...now starting the Curator stage');
// Stage 4 - Curator: Original question + Stage 3 answer (as supplemental knowledge)
outputWriter.stageStart('✨ Running Curator stage with error handling...');
const curatorStartTime = Date.now();
const curatorResult = await runConsensusStage('curator', question, validatorResult.answer, curatorModel, conversationId, enableStreaming, callbacks, modelInternalIds?.curator);
const curatorDuration = Date.now() - curatorStartTime;
stagePerformance.curator = {
duration: curatorDuration,
tokensPerSecond: 0,
cost: 0,
retries: 0,
modelUsed: curatorModel,
latency: curatorDuration,
efficiency: 0
};
outputWriter.stageComplete('✅ Curator stage completed');
outputWriter.pipelineComplete('🎉 Generator → Refiner → Validator → Curator pipeline now completed');
// PHASE 2.5: Record cost analytics and performance metrics
if (consensusMode !== 'manual' && estimatedCost > 0) {
try {
const { CostIntelligence } = await import('./cost-intelligence.js');
const costIntel = new CostIntelligence();
// Record cost analytics for learning and optimization
const costBreakdown = {
stageBreakdown: {
generator: estimatedCost * 0.28,
refiner: estimatedCost * 0.24,
validator: estimatedCost * 0.24,
curator: estimatedCost * 0.24
},
tokenBreakdown: {
generator: { input: 500, output: 800 },
refiner: { input: 750, output: 960 },
validator: { input: 900, output: 480 },
curator: { input: 1000, output: 880 }
}
};
await costIntel.recordCostAnalytics(conversationId, costBreakdown, [], // No optimizations applied yet
estimatedCost);
console.log(`💰 Cost analytics recorded for conversation ${conversationId.substring(0, 8)}...`);
}
catch (error) {
console.warn('⚠️ Failed to record cost analytics:', error.message);
}
}
// Store curator knowledge for future multi-conversation context
console.log(`🔄 [PIPELINE] About to store conversation knowledge for ${conversationId.substring(0, 8)}`);
console.log(`🔄 [PIPELINE] Question: "${question.substring(0, 50)}..."`);
console.log(`🔄 [PIPELINE] Curator answer length: ${curatorResult.answer.length} characters`);
try {
await storeConversationKnowledge(conversationId, question, curatorResult.answer);
console.log(`✅ [PIPELINE] Storage function completed for ${conversationId.substring(0, 8)}`);
}
catch (error) {
console.error(`❌ [PIPELINE] Storage function failed for ${conversationId.substring(0, 8)}:`, error);
// Don't re-throw to avoid breaking the pipeline, but log the error
}
// CRITICAL: Record actual costs (replaces estimates with real data)
console.log(`💰 [PIPELINE] Recording actual conversation costs for ${conversationId.substring(0, 8)}`);
console.log(`💰 [PIPELINE] DEBUG - About to enter cost recording section`);
try {
// Calculate actual total cost from all stage results
const generatorCost = Number(generatorResult.analytics?.cost || 0);
const refinerCost = Number(refinerResult.analytics?.cost || 0);
const validatorCost = Number(validatorResult.analytics?.cost || 0);
const curatorCost = Number(curatorResult.analytics?.cost || 0);
console.log(`💰 [AGGR] Generator cost: ${generatorCost} (type: ${typeof generatorResult.analytics?.cost})`);
console.log(`💰 [AGGR] Refiner cost: ${refinerCost} (type: ${typeof refinerResult.analytics?.cost})`);
console.log(`💰 [AGGR] Validator cost: ${validatorCost} (type: ${typeof validatorResult.analytics?.cost})`);
console.log(`💰 [AGGR] Curator cost: ${curatorCost} (type: ${typeof curatorResult.analytics?.cost})`);
const actualTotalCost = generatorCost + refinerCost + validatorCost + curatorCost;
console.log(`💰 [AGGR] Total actual cost: ${actualTotalCost} (result type: ${typeof actualTotalCost})`);
// Calculate stage breakdown with actual costs
const actualStageBreakdown = {
generator: generatorCost,
refiner: refinerCost,
validator: validatorCost,
curator: curatorCost
};
// Calculate token breakdown from actual usage
const actualTokenBreakdown = {
generator: {
input: generatorResult.usage?.prompt_tokens || 0,
output: generatorResult.usage?.completion_tokens || 0
},
refiner: {
input: refinerResult.usage?.prompt_tokens || 0,
output: refinerResult.usage?.completion_tokens || 0
},
validator: {
input: validatorResult.usage?.prompt_tokens || 0,
output: validatorResult.usage?.completion_tokens || 0
},
curator: {
input: curatorResult.usage?.prompt_tokens || 0,
output: curatorResult.usage?.comple