phase5
This commit is contained in:
parent
78b30fb7c6
commit
5c3884094c
@ -1,11 +1,11 @@
|
||||
// src/pages/api/ai/query.ts - Enhanced with Comprehensive Confidence Metrics
|
||||
// src/pages/api/ai/query.ts - PHASE 5: Enhanced with Centralized Configuration
|
||||
|
||||
import type { APIRoute } from 'astro';
|
||||
import { withAPIAuth } from '../../../utils/auth.js';
|
||||
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
|
||||
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
|
||||
import { aiPipeline } from '../../../utils/aiPipeline.js';
|
||||
import { forensicConfig } from '../../../utils/forensicConfig.js';
|
||||
import { unifiedConfig, FORENSIC_CONSTANTS } from '../../../utils/configIntegration.js';
|
||||
import { confidenceScorer } from '../../../utils/confidenceScoring.js';
|
||||
import { biasDetector } from '../../../utils/biasDetection.js';
|
||||
|
||||
@ -19,13 +19,9 @@ interface RateLimitData {
|
||||
|
||||
const rateLimitStore = new Map<string, RateLimitData>();
|
||||
|
||||
// Use configuration instead of hard-coded values
|
||||
const config = forensicConfig.getConfig();
|
||||
const thresholds = forensicConfig.getThresholds();
|
||||
|
||||
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
||||
const MAIN_RATE_LIMIT_MAX = thresholds.rateLimitMaxRequests;
|
||||
const MICRO_TASK_TOTAL_LIMIT = parseInt(process.env.AI_MICRO_TASK_TOTAL_LIMIT || '50', 10);
|
||||
// PHASE 5: Use centralized configuration instead of hardcoded values
|
||||
const rateLimitConfig = unifiedConfig.getRateLimitConfig();
|
||||
const processingTimeouts = unifiedConfig.getProcessingTimeouts();
|
||||
|
||||
function sanitizeInput(input: string): string {
|
||||
let sanitized = input
|
||||
@ -35,7 +31,7 @@ function sanitizeInput(input: string): string {
|
||||
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
||||
.trim();
|
||||
|
||||
sanitized = sanitized.slice(0, 2000).replace(/\s+/g, ' ');
|
||||
sanitized = sanitized.slice(0, FORENSIC_CONSTANTS.MAX_QUERY_LENGTH).replace(/\s+/g, ' ');
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@ -46,26 +42,26 @@ function checkRateLimit(userId: string): { allowed: boolean; reason?: string; mi
|
||||
if (!userLimit || now > userLimit.resetTime) {
|
||||
rateLimitStore.set(userId, {
|
||||
count: 1,
|
||||
resetTime: now + RATE_LIMIT_WINDOW,
|
||||
resetTime: now + rateLimitConfig.windowMs,
|
||||
microTaskCount: 0
|
||||
});
|
||||
return {
|
||||
allowed: true,
|
||||
microTasksRemaining: MICRO_TASK_TOTAL_LIMIT
|
||||
microTasksRemaining: rateLimitConfig.microTaskLimit
|
||||
};
|
||||
}
|
||||
|
||||
if (userLimit.count >= MAIN_RATE_LIMIT_MAX) {
|
||||
if (userLimit.count >= rateLimitConfig.maxRequests) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Main rate limit exceeded. Max ${MAIN_RATE_LIMIT_MAX} requests per minute.`
|
||||
reason: `Main rate limit exceeded. Max ${rateLimitConfig.maxRequests} requests per minute.`
|
||||
};
|
||||
}
|
||||
|
||||
if (userLimit.microTaskCount >= MICRO_TASK_TOTAL_LIMIT) {
|
||||
if (userLimit.microTaskCount >= rateLimitConfig.microTaskLimit) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Micro-task limit exceeded. Max ${MICRO_TASK_TOTAL_LIMIT} AI calls per minute.`
|
||||
reason: `Micro-task limit exceeded. Max ${rateLimitConfig.microTaskLimit} AI calls per minute.`
|
||||
};
|
||||
}
|
||||
|
||||
@ -73,7 +69,7 @@ function checkRateLimit(userId: string): { allowed: boolean; reason?: string; mi
|
||||
|
||||
return {
|
||||
allowed: true,
|
||||
microTasksRemaining: MICRO_TASK_TOTAL_LIMIT - userLimit.microTaskCount
|
||||
microTasksRemaining: rateLimitConfig.microTaskLimit - userLimit.microTaskCount
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,13 +77,12 @@ function incrementMicroTaskCount(userId: string, aiCallsMade: number): void {
|
||||
const userLimit = rateLimitStore.get(userId);
|
||||
if (userLimit) {
|
||||
userLimit.microTaskCount += aiCallsMade;
|
||||
console.log(`[RATE LIMIT] User ${userId} now at ${userLimit.microTaskCount}/${MICRO_TASK_TOTAL_LIMIT} micro-task calls`);
|
||||
console.log(`[RATE LIMIT] User ${userId} now at ${userLimit.microTaskCount}/${rateLimitConfig.microTaskLimit} micro-task calls`);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupExpiredRateLimits() {
|
||||
const now = Date.now();
|
||||
const maxStoreSize = 1000;
|
||||
|
||||
for (const [userId, limit] of rateLimitStore.entries()) {
|
||||
if (now > limit.resetTime) {
|
||||
@ -95,18 +90,19 @@ function cleanupExpiredRateLimits() {
|
||||
}
|
||||
}
|
||||
|
||||
if (rateLimitStore.size > maxStoreSize) {
|
||||
if (rateLimitStore.size > FORENSIC_CONSTANTS.MAX_STORE_SIZE) {
|
||||
const entries = Array.from(rateLimitStore.entries());
|
||||
entries.sort((a, b) => a[1].resetTime - b[1].resetTime);
|
||||
|
||||
const toRemove = entries.slice(0, entries.length - maxStoreSize);
|
||||
const toRemove = entries.slice(0, entries.length - FORENSIC_CONSTANTS.MAX_STORE_SIZE);
|
||||
toRemove.forEach(([userId]) => rateLimitStore.delete(userId));
|
||||
|
||||
console.log(`[RATE LIMIT] Cleanup: removed ${toRemove.length} old entries`);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
||||
// PHASE 5: Use centralized configuration for cleanup interval
|
||||
setInterval(cleanupExpiredRateLimits, rateLimitConfig.cleanupIntervalMs);
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
@ -125,9 +121,17 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
const body = await request.json();
|
||||
const { query, mode = 'workflow', taskId: clientTaskId } = body;
|
||||
|
||||
// PHASE 5: Use centralized configuration validation
|
||||
const configHealth = unifiedConfig.validateConfigurationHealth();
|
||||
if (!configHealth.healthy) {
|
||||
console.error('[ENHANCED API] Configuration health check failed:', configHealth.errors);
|
||||
// Continue with degraded functionality but log warnings
|
||||
}
|
||||
|
||||
const configSummary = unifiedConfig.getConfigurationSummary();
|
||||
console.log(`[ENHANCED API] Received request - TaskId: ${clientTaskId}, Mode: ${mode}, Query length: ${query?.length || 0}`);
|
||||
console.log(`[ENHANCED API] User: ${userId}, Confidence Scoring: ${config.features.confidenceScoring ? 'Enabled' : 'Disabled'}`);
|
||||
console.log(`[ENHANCED API] Audit Trail: ${config.auditTrail.enabled ? 'Enabled' : 'Disabled'}`);
|
||||
console.log(`[ENHANCED API] User: ${userId}, Configuration Health: ${configSummary.health}`);
|
||||
console.log(`[ENHANCED API] Features - Confidence: ${configSummary.features.confidenceScoring}, Bias: ${configSummary.features.biasDetection}, Audit: ${configSummary.features.auditTrail}`);
|
||||
console.log(`[ENHANCED API] Micro-task calls remaining: ${rateLimitResult.microTasksRemaining}`);
|
||||
|
||||
if (!query || typeof query !== 'string') {
|
||||
@ -141,6 +145,11 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
}
|
||||
|
||||
const sanitizedQuery = sanitizeInput(query);
|
||||
if (sanitizedQuery.length < FORENSIC_CONSTANTS.MIN_QUERY_LENGTH) {
|
||||
console.log(`[ENHANCED API] Query too short for task ${clientTaskId}: ${sanitizedQuery.length} characters`);
|
||||
return apiError.badRequest(`Query too short. Minimum ${FORENSIC_CONSTANTS.MIN_QUERY_LENGTH} characters required.`);
|
||||
}
|
||||
|
||||
if (sanitizedQuery.includes('[FILTERED]')) {
|
||||
console.log(`[ENHANCED API] Filtered input detected for task ${clientTaskId}`);
|
||||
return apiError.badRequest('Invalid input detected');
|
||||
@ -163,12 +172,15 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
const estimatedAICallsMade = stats.microTasksCompleted + stats.microTasksFailed;
|
||||
incrementMicroTaskCount(userId, estimatedAICallsMade);
|
||||
|
||||
// Log comprehensive results
|
||||
// PHASE 5: Enhanced logging with configuration context
|
||||
console.log(`[ENHANCED API] Enhanced pipeline completed for ${taskId}:`);
|
||||
console.log(` - Mode: ${mode}`);
|
||||
console.log(` - User: ${userId}`);
|
||||
console.log(` - Query length: ${sanitizedQuery.length}`);
|
||||
console.log(` - Processing time: ${stats.processingTimeMs}ms`);
|
||||
console.log(` - Configuration health: ${configSummary.health}`);
|
||||
console.log(` - Features enabled: Confidence=${configSummary.features.confidenceScoring}, Bias=${configSummary.features.biasDetection}`);
|
||||
console.log(` - Models used: Strategic=${configSummary.models.strategic}, Tactical=${configSummary.models.tactical}`);
|
||||
console.log(` - Micro-tasks completed: ${stats.microTasksCompleted}`);
|
||||
console.log(` - Micro-tasks failed: ${stats.microTasksFailed}`);
|
||||
console.log(` - Estimated AI calls: ${estimatedAICallsMade}`);
|
||||
@ -188,8 +200,8 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// NEW: Enhanced confidence metrics
|
||||
if (result.confidenceMetrics && config.features.confidenceScoring) {
|
||||
// PHASE 5: Enhanced confidence metrics using centralized configuration
|
||||
if (result.confidenceMetrics && unifiedConfig.isFeatureEnabled('confidenceScoring')) {
|
||||
console.log(` - Confidence Breakdown:`);
|
||||
console.log(` * Retrieval: ${(result.confidenceMetrics.breakdown.retrieval * 100).toFixed(1)}%`);
|
||||
console.log(` * Selection: ${(result.confidenceMetrics.breakdown.selection * 100).toFixed(1)}%`);
|
||||
@ -199,12 +211,18 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
|
||||
const currentLimit = rateLimitStore.get(userId);
|
||||
const remainingMicroTasks = currentLimit ?
|
||||
MICRO_TASK_TOTAL_LIMIT - currentLimit.microTaskCount : MICRO_TASK_TOTAL_LIMIT;
|
||||
rateLimitConfig.microTaskLimit - currentLimit.microTaskCount : rateLimitConfig.microTaskLimit;
|
||||
|
||||
// NEW: Check if confidence is acceptable
|
||||
// PHASE 5: Use centralized thresholds for confidence evaluation
|
||||
const confidenceThreshold = unifiedConfig.getThreshold('confidenceThreshold', 0.7);
|
||||
const confidenceAcceptable = result.auditTrail ?
|
||||
confidenceScorer.isConfidenceAcceptable(result.auditTrail.qualityMetrics.overallConfidence) : true;
|
||||
|
||||
// PHASE 5: Use centralized bias thresholds
|
||||
const biasThreshold = unifiedConfig.getThreshold('biasAlertThreshold', 0.8);
|
||||
const highBiasRisk = result.auditTrail ?
|
||||
biasDetector.isHighBiasRisk(result.auditTrail.qualityMetrics.biasRiskScore) : false;
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
mode,
|
||||
@ -220,13 +238,14 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
auditCompliant: result.auditTrail?.compliance.auditCompliant || false,
|
||||
biasChecked: result.auditTrail?.compliance.biasChecked || false,
|
||||
confidenceAssessed: result.auditTrail?.compliance.confidenceAssessed || false,
|
||||
confidenceAcceptable
|
||||
confidenceAcceptable,
|
||||
configurationHealth: configSummary.health
|
||||
},
|
||||
|
||||
// ENHANCED: Comprehensive forensic metadata with bias detection
|
||||
// ENHANCED: Comprehensive forensic metadata with centralized configuration
|
||||
forensicMetadata: result.auditTrail ? {
|
||||
auditTrailId: result.auditTrail.auditId,
|
||||
auditEnabled: config.auditTrail.enabled,
|
||||
auditEnabled: unifiedConfig.isFeatureEnabled('auditTrail'),
|
||||
|
||||
// Core quality metrics
|
||||
overallConfidence: result.auditTrail.qualityMetrics.overallConfidence,
|
||||
@ -236,17 +255,19 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
evidenceQuality: result.auditTrail.qualityMetrics.evidenceQuality,
|
||||
methodologicalSoundness: result.auditTrail.qualityMetrics.methodologicalSoundness,
|
||||
|
||||
// ENHANCED: Detailed bias analysis
|
||||
// ENHANCED: Detailed bias analysis using centralized thresholds
|
||||
biasAnalysis: {
|
||||
overallBiasRisk: result.auditTrail.qualityMetrics.biasRiskScore,
|
||||
isHighBiasRisk: biasDetector.isHighBiasRisk(result.auditTrail.qualityMetrics.biasRiskScore),
|
||||
isHighBiasRisk: highBiasRisk,
|
||||
biasThreshold: biasThreshold,
|
||||
detectedBiases: result.auditTrail.biasAnalysis.filter(bias => bias.detected).map(bias => ({
|
||||
type: bias.biasType,
|
||||
severity: bias.severity,
|
||||
confidence: bias.confidence,
|
||||
description: bias.description,
|
||||
affectedTools: bias.evidence.affectedTools,
|
||||
recommendation: bias.recommendation
|
||||
recommendation: bias.recommendation,
|
||||
thresholdUsed: unifiedConfig.getBiasThreshold(bias.biasType)
|
||||
})),
|
||||
biasFreeSeverity: result.auditTrail.biasAnalysis.filter(bias => !bias.detected).length,
|
||||
mitigationSuggestions: biasDetector.suggestBiasMitigation(result.auditTrail.biasAnalysis),
|
||||
@ -259,71 +280,101 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
}
|
||||
},
|
||||
|
||||
// Detailed confidence breakdown
|
||||
confidenceBreakdown: config.features.confidenceScoring ? {
|
||||
// Detailed confidence breakdown using centralized configuration
|
||||
confidenceBreakdown: unifiedConfig.isFeatureEnabled('confidenceScoring') ? {
|
||||
retrieval: result.auditTrail.qualityMetrics.confidenceBreakdown.retrieval,
|
||||
selection: result.auditTrail.qualityMetrics.confidenceBreakdown.selection,
|
||||
domain: result.auditTrail.qualityMetrics.confidenceBreakdown.domain,
|
||||
meta: result.auditTrail.qualityMetrics.confidenceBreakdown.meta
|
||||
} : undefined,
|
||||
|
||||
// Confidence assessment details
|
||||
confidenceAssessment: config.features.confidenceScoring ? {
|
||||
// Confidence assessment details with centralized thresholds
|
||||
confidenceAssessment: unifiedConfig.isFeatureEnabled('confidenceScoring') ? {
|
||||
qualityLevel: result.auditTrail.qualityMetrics.qualityLevel,
|
||||
reliability: result.auditTrail.qualityMetrics.confidenceReliability,
|
||||
uncertaintyFactors: result.auditTrail.qualityMetrics.uncertaintyFactors,
|
||||
improvementSuggestions: result.auditTrail.qualityMetrics.improvementSuggestions,
|
||||
isAcceptable: confidenceAcceptable,
|
||||
threshold: thresholds.confidenceThreshold
|
||||
threshold: confidenceThreshold,
|
||||
thresholdSource: 'centralized_configuration'
|
||||
} : undefined,
|
||||
|
||||
// ENHANCED: Bias and quality warnings with specific guidance
|
||||
// ENHANCED: Configuration-aware warnings and quality checks
|
||||
biasWarnings: result.auditTrail.biasAnalysis.filter(b => b.detected).map(bias => ({
|
||||
type: bias.biasType,
|
||||
severity: bias.severity,
|
||||
message: bias.description,
|
||||
actionRequired: bias.severity > 0.7 ? 'immediate_review' : 'consideration',
|
||||
mitigation: bias.mitigation
|
||||
mitigation: bias.mitigation,
|
||||
configuredThreshold: unifiedConfig.getBiasThreshold(bias.biasType)
|
||||
})),
|
||||
|
||||
qualityWarnings: [
|
||||
...((!confidenceAcceptable) ? [{
|
||||
type: 'low_confidence',
|
||||
message: 'Overall confidence below acceptable threshold',
|
||||
actionRequired: 'expert_review'
|
||||
message: `Overall confidence ${(result.auditTrail.qualityMetrics.overallConfidence * 100).toFixed(1)}% below threshold ${(confidenceThreshold * 100).toFixed(1)}%`,
|
||||
actionRequired: 'expert_review',
|
||||
configuredThreshold: confidenceThreshold
|
||||
}] : []),
|
||||
...(result.auditTrail.qualityMetrics.biasRiskScore > thresholds.biasAlertThreshold ? [{
|
||||
...(highBiasRisk ? [{
|
||||
type: 'high_bias_risk',
|
||||
message: 'High bias risk detected in tool selection',
|
||||
actionRequired: 'bias_review'
|
||||
message: `High bias risk ${(result.auditTrail.qualityMetrics.biasRiskScore * 100).toFixed(1)}% above threshold ${(biasThreshold * 100).toFixed(1)}%`,
|
||||
actionRequired: 'bias_review',
|
||||
configuredThreshold: biasThreshold
|
||||
}] : []),
|
||||
...(!configHealth.healthy ? [{
|
||||
type: 'configuration_degraded',
|
||||
message: 'Configuration health degraded - some features may be limited',
|
||||
actionRequired: 'admin_review',
|
||||
configErrors: configHealth.errors
|
||||
}] : [])
|
||||
],
|
||||
|
||||
// System configuration snapshot
|
||||
// PHASE 5: Enhanced system configuration with centralized management
|
||||
systemConfig: {
|
||||
strategicModel: result.auditTrail.systemConfig.strategicModel,
|
||||
tacticalModel: result.auditTrail.systemConfig.tacticalModel,
|
||||
auditLevel: result.auditTrail.systemConfig.auditLevel,
|
||||
confidenceScoringEnabled: config.features.confidenceScoring,
|
||||
biasDetectionEnabled: config.features.biasDetection,
|
||||
biasThresholds: biasDetector.getBiasThresholds()
|
||||
configurationHealth: configSummary.health,
|
||||
configurationVersion: configSummary.version,
|
||||
featuresEnabled: configSummary.features,
|
||||
thresholdsApplied: configSummary.thresholds,
|
||||
centralized: true, // PHASE 5 indicator
|
||||
configurationSource: 'enhanced_config_manager'
|
||||
},
|
||||
|
||||
// Compliance and traceability
|
||||
compliance: result.auditTrail.compliance,
|
||||
qualityLevel: result.auditTrail.qualityMetrics.overallConfidence >= thresholds.confidenceThreshold ? 'high' :
|
||||
// Enhanced compliance with configuration tracking
|
||||
compliance: {
|
||||
...result.auditTrail.compliance,
|
||||
configurationValidated: configHealth.healthy,
|
||||
thresholdsFromConfiguration: true,
|
||||
centralizationCompliant: true
|
||||
},
|
||||
|
||||
// PHASE 5: Quality level determination using centralized thresholds
|
||||
qualityLevel: result.auditTrail.qualityMetrics.overallConfidence >= confidenceThreshold ? 'high' :
|
||||
result.auditTrail.qualityMetrics.overallConfidence >= 0.5 ? 'medium' : 'low',
|
||||
|
||||
// ENHANCED: Actionable insights with bias considerations
|
||||
// ENHANCED: Actionable insights with configuration-aware recommendations
|
||||
actionableInsights: {
|
||||
shouldReviewSelection: result.auditTrail.qualityMetrics.biasRiskScore > thresholds.biasAlertThreshold,
|
||||
shouldReviewSelection: highBiasRisk,
|
||||
shouldImproveQuery: result.auditTrail.qualityMetrics.uncertaintyFactors.length > 2,
|
||||
shouldSeekExpertReview: result.auditTrail.qualityMetrics.overallConfidence < 0.6 ||
|
||||
biasDetector.isHighBiasRisk(result.auditTrail.qualityMetrics.biasRiskScore),
|
||||
shouldSeekExpertReview: !confidenceAcceptable || highBiasRisk,
|
||||
shouldCheckConfiguration: !configHealth.healthy,
|
||||
confidenceImprovement: result.auditTrail.qualityMetrics.improvementSuggestions.slice(0, 3),
|
||||
biasReduction: biasDetector.suggestBiasMitigation(result.auditTrail.biasAnalysis).slice(0, 3),
|
||||
configurationRecommendations: configHealth.recommendations,
|
||||
|
||||
// NEW: Specific bias-related insights
|
||||
// PHASE 5: Configuration-specific insights
|
||||
configurationInsights: {
|
||||
healthStatus: configSummary.health,
|
||||
featuresOptimal: Object.values(configSummary.features).every(Boolean),
|
||||
thresholdsValidated: configHealth.healthy,
|
||||
modelsAccessible: configSummary.models.strategic !== 'error' && configSummary.models.tactical !== 'error',
|
||||
centralizationComplete: true
|
||||
},
|
||||
|
||||
// Enhanced bias-related insights using centralized configuration
|
||||
biasInsights: {
|
||||
hasPopularityBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'popularity' && b.detected),
|
||||
hasAvailabilityBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'availability' && b.detected),
|
||||
@ -332,20 +383,38 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
hasRecencyBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'recency' && b.detected),
|
||||
primaryBiasConcern: result.auditTrail.biasAnalysis
|
||||
.filter(b => b.detected)
|
||||
.sort((a, b) => b.severity - a.severity)[0]?.biasType || null
|
||||
.sort((a, b) => b.severity - a.severity)[0]?.biasType || null,
|
||||
biasThresholdsUsed: {
|
||||
popularity: unifiedConfig.getBiasThreshold('popularity'),
|
||||
availability: unifiedConfig.getBiasThreshold('availability'),
|
||||
domainConcentration: unifiedConfig.getBiasThreshold('domain_concentration'),
|
||||
skillLevel: unifiedConfig.getBiasThreshold('skill_level'),
|
||||
recency: unifiedConfig.getBiasThreshold('recency')
|
||||
}
|
||||
}
|
||||
}
|
||||
} : {
|
||||
auditTrailId: null,
|
||||
auditEnabled: false,
|
||||
biasAnalysis: { enabled: false, message: 'Bias detection disabled - operating in legacy mode' },
|
||||
biasAnalysis: {
|
||||
enabled: false,
|
||||
message: 'Bias detection disabled - operating in legacy mode'
|
||||
},
|
||||
configurationHealth: configSummary.health,
|
||||
message: 'Enhanced forensic features disabled - operating in legacy mode'
|
||||
},
|
||||
|
||||
// PHASE 5: Enhanced rate limit info using centralized configuration
|
||||
rateLimitInfo: {
|
||||
mainRequestsRemaining: MAIN_RATE_LIMIT_MAX - (currentLimit?.count || 0),
|
||||
mainRequestsRemaining: rateLimitConfig.maxRequests - (currentLimit?.count || 0),
|
||||
microTaskCallsRemaining: remainingMicroTasks,
|
||||
resetTime: Date.now() + RATE_LIMIT_WINDOW
|
||||
resetTime: Date.now() + rateLimitConfig.windowMs,
|
||||
configuration: {
|
||||
windowMs: rateLimitConfig.windowMs,
|
||||
maxRequests: rateLimitConfig.maxRequests,
|
||||
microTaskLimit: rateLimitConfig.microTaskLimit,
|
||||
source: 'centralized_configuration'
|
||||
}
|
||||
}
|
||||
}), {
|
||||
status: 200,
|
||||
@ -355,23 +424,29 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
} catch (error) {
|
||||
console.error('[ENHANCED API] Pipeline error:', error);
|
||||
|
||||
// PHASE 5: Enhanced error information with configuration context
|
||||
const configSummary = unifiedConfig.getConfigurationSummary();
|
||||
const errorContext = `Configuration Health: ${configSummary.health}, Features: ${JSON.stringify(configSummary.features)}`;
|
||||
|
||||
// Provide detailed error information for forensic purposes
|
||||
if (error.message.includes('bias')) {
|
||||
return apiServerError.unavailable('Bias detection error - recommendation objectivity may be affected');
|
||||
return apiServerError.unavailable(`Bias detection error - recommendation objectivity may be affected. ${errorContext}`);
|
||||
} else if (error.message.includes('confidence')) {
|
||||
return apiServerError.unavailable('Confidence scoring error - recommendation quality may be affected');
|
||||
return apiServerError.unavailable(`Confidence scoring error - recommendation quality may be affected. ${errorContext}`);
|
||||
} else if (error.message.includes('embeddings')) {
|
||||
return apiServerError.unavailable('Embeddings service error - using AI fallback with bias detection');
|
||||
return apiServerError.unavailable(`Embeddings service error - using AI fallback with bias detection. ${errorContext}`);
|
||||
} else if (error.message.includes('configuration')) {
|
||||
return apiServerError.unavailable(`Configuration error - system operating in degraded mode. ${errorContext}`);
|
||||
} else if (error.message.includes('micro-task')) {
|
||||
return apiServerError.unavailable('Micro-task pipeline error - some analysis steps failed but audit trail maintained');
|
||||
return apiServerError.unavailable(`Micro-task pipeline error - some analysis steps failed but audit trail maintained. ${errorContext}`);
|
||||
} else if (error.message.includes('selector')) {
|
||||
return apiServerError.unavailable('AI selector service error - emergency fallback used with full audit and bias detection');
|
||||
return apiServerError.unavailable(`AI selector service error - emergency fallback used with full audit and bias detection. ${errorContext}`);
|
||||
} else if (error.message.includes('rate limit')) {
|
||||
return apiError.rateLimit('AI service rate limits exceeded during enhanced processing');
|
||||
return apiError.rateLimit(`AI service rate limits exceeded during enhanced processing. ${errorContext}`);
|
||||
} else if (error.message.includes('audit')) {
|
||||
return apiServerError.internal('Audit trail system error - check forensic configuration');
|
||||
return apiServerError.internal(`Audit trail system error - check forensic configuration. ${errorContext}`);
|
||||
} else {
|
||||
return apiServerError.internal('Enhanced AI pipeline error - forensic audit and bias detection may be incomplete');
|
||||
return apiServerError.internal(`Enhanced AI pipeline error - forensic audit and bias detection may be incomplete. ${errorContext}`);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
// src/utils/aiPipeline.ts - Enhanced with Confidence Scoring Integration
|
||||
// src/utils/aiPipeline.ts - PHASE 5: Enhanced with Centralized Configuration
|
||||
|
||||
import { getCompressedToolsDataForAI } from './dataService.js';
|
||||
import { embeddingsService, type EmbeddingData, type EmbeddingSearchResult } from './embeddings.js';
|
||||
import { forensicConfig, type AIModelConfig } from './forensicConfig.js';
|
||||
import { unifiedConfig, FORENSIC_CONSTANTS } from './configIntegration.js';
|
||||
import { auditTrailService, type ForensicAuditEntry } from './auditTrail.js';
|
||||
import { confidenceScorer, type ConfidenceMetrics } from './confidenceScoring.js';
|
||||
import { biasDetector, type BiasAnalysisResult } from './biasDetection.js';
|
||||
@ -38,7 +38,6 @@ interface AnalysisResult {
|
||||
biasRiskScore: number;
|
||||
transparencyScore: number;
|
||||
};
|
||||
// NEW: Enhanced confidence metrics
|
||||
confidenceMetrics?: ConfidenceMetrics;
|
||||
}
|
||||
|
||||
@ -62,36 +61,88 @@ interface AnalysisContext {
|
||||
}
|
||||
|
||||
class EnhancedMicroTaskAIPipeline {
|
||||
private config = forensicConfig.getConfig();
|
||||
private thresholds = forensicConfig.getThresholds();
|
||||
|
||||
// Remove hard-coded values - now using configuration
|
||||
// PHASE 5: Replace hardcoded values with centralized configuration
|
||||
private maxSelectedItems: number;
|
||||
private embeddingCandidates: number;
|
||||
private similarityThreshold: number;
|
||||
private microTaskDelay: number;
|
||||
private maxContextTokens: number;
|
||||
private maxPromptTokens: number;
|
||||
private processingTimeouts: any;
|
||||
|
||||
constructor() {
|
||||
// All values now come from configuration - no more hard-coded values
|
||||
this.maxSelectedItems = this.thresholds.maxSelectedItems;
|
||||
this.embeddingCandidates = this.thresholds.embeddingCandidates;
|
||||
this.similarityThreshold = this.thresholds.similarityThreshold;
|
||||
this.microTaskDelay = this.thresholds.microTaskDelayMs;
|
||||
// PHASE 5: All values now come from centralized configuration
|
||||
this.loadConfigurationValues();
|
||||
|
||||
// Dynamic token limits based on model capabilities
|
||||
this.maxContextTokens = this.config.aiModels.strategic.maxContextTokens;
|
||||
this.maxPromptTokens = Math.floor(this.maxContextTokens * 0.6); // Leave room for response
|
||||
|
||||
console.log('[ENHANCED PIPELINE] Initialized with forensic configuration and confidence scoring');
|
||||
console.log(`[ENHANCED PIPELINE] Strategic Model: ${this.config.aiModels.strategic.model}`);
|
||||
console.log(`[ENHANCED PIPELINE] Tactical Model: ${this.config.aiModels.tactical.model}`);
|
||||
console.log(`[ENHANCED PIPELINE] Confidence Scoring: ${this.config.features.confidenceScoring ? 'Enabled' : 'Disabled'}`);
|
||||
console.log(`[ENHANCED PIPELINE] Audit Trail: ${this.config.auditTrail.enabled ? 'Enabled' : 'Disabled'}`);
|
||||
console.log('[ENHANCED PIPELINE] Initialized with centralized configuration');
|
||||
this.logConfigurationSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* PHASE 5: Load all configuration values from centralized config manager
|
||||
*/
|
||||
private loadConfigurationValues(): void {
|
||||
try {
|
||||
// Get thresholds from centralized configuration
|
||||
this.maxSelectedItems = unifiedConfig.getThreshold('maxSelectedItems');
|
||||
this.embeddingCandidates = unifiedConfig.getThreshold('embeddingCandidates');
|
||||
this.similarityThreshold = unifiedConfig.getThreshold('similarityThreshold');
|
||||
|
||||
// Get processing timeouts from centralized configuration
|
||||
this.processingTimeouts = unifiedConfig.getProcessingTimeouts();
|
||||
this.microTaskDelay = this.processingTimeouts.microTaskDelayMs;
|
||||
|
||||
// Get AI model context limits
|
||||
const strategicModel = unifiedConfig.getAIModelConfig('strategic');
|
||||
this.maxContextTokens = strategicModel.maxContextTokens;
|
||||
this.maxPromptTokens = Math.floor(this.maxContextTokens * 0.6); // Leave room for response
|
||||
|
||||
console.log('[ENHANCED PIPELINE] Configuration loaded successfully');
|
||||
} catch (error) {
|
||||
console.error('[ENHANCED PIPELINE] Failed to load configuration, using fallback values:', error);
|
||||
|
||||
// Fallback to safe defaults if configuration fails
|
||||
this.maxSelectedItems = 60;
|
||||
this.embeddingCandidates = 60;
|
||||
this.similarityThreshold = 0.3;
|
||||
this.microTaskDelay = 500;
|
||||
this.maxContextTokens = 8000;
|
||||
this.maxPromptTokens = 4800;
|
||||
this.processingTimeouts = {
|
||||
aiTimeoutMs: 25000,
|
||||
microTaskTimeoutMs: 25000,
|
||||
microTaskDelayMs: 500,
|
||||
rateLimitDelayMs: 3000
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PHASE 5: Log configuration summary for debugging
|
||||
*/
|
||||
private logConfigurationSummary(): void {
|
||||
const configSummary = unifiedConfig.getConfigurationSummary();
|
||||
console.log(`[ENHANCED PIPELINE] Configuration Summary:`);
|
||||
console.log(` - Health: ${configSummary.health}`);
|
||||
console.log(` - Strategic Model: ${configSummary.models.strategic}`);
|
||||
console.log(` - Tactical Model: ${configSummary.models.tactical}`);
|
||||
console.log(` - Features: ${JSON.stringify(configSummary.features)}`);
|
||||
console.log(` - Thresholds: Confidence=${configSummary.thresholds.confidence}, Bias=${configSummary.thresholds.bias}, Similarity=${configSummary.thresholds.similarity}`);
|
||||
console.log(` - Max Selected Items: ${this.maxSelectedItems}`);
|
||||
console.log(` - Embedding Candidates: ${this.embeddingCandidates}`);
|
||||
console.log(` - Similarity Threshold: ${this.similarityThreshold}`);
|
||||
console.log(` - Micro-task Delay: ${this.microTaskDelay}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* PHASE 5: Update bias detection baseline using centralized configuration
|
||||
*/
|
||||
private updateBiasBaseline(): void {
|
||||
if (!unifiedConfig.isFeatureEnabled('biasDetection')) {
|
||||
console.log('[ENHANCED PIPELINE] Bias detection disabled via configuration');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bias detection baseline with recent audit data
|
||||
const recentAudits = Array.from(auditTrailService['auditStorage'].values())
|
||||
.filter(audit => {
|
||||
@ -99,7 +150,8 @@ class EnhancedMicroTaskAIPipeline {
|
||||
return daysSinceAudit <= 30; // Last 30 days
|
||||
});
|
||||
|
||||
if (recentAudits.length >= 5) { // Minimum data for meaningful baseline
|
||||
const minSamples = unifiedConfig.getBiasConfig()?.baseline.updateMinSamples || 5;
|
||||
if (recentAudits.length >= minSamples) {
|
||||
biasDetector.updateBaseline(recentAudits);
|
||||
console.log(`[ENHANCED PIPELINE] Updated bias baseline with ${recentAudits.length} recent audits`);
|
||||
}
|
||||
@ -157,7 +209,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENHANCED AI CALLING WITH DUAL MODELS
|
||||
// ENHANCED AI CALLING WITH CENTRALIZED MODEL SELECTION
|
||||
// ============================================================================
|
||||
|
||||
private async callAIWithModel(
|
||||
@ -172,9 +224,10 @@ class EnhancedMicroTaskAIPipeline {
|
||||
model: string;
|
||||
endpoint: string;
|
||||
}> {
|
||||
// PHASE 5: Use centralized configuration for model selection
|
||||
const modelConfig = modelType === 'legacy' ?
|
||||
forensicConfig.getLegacyAIModel() :
|
||||
forensicConfig.getAIModel(modelType);
|
||||
unifiedConfig.getAIModelConfig('tactical') : // Legacy falls back to tactical
|
||||
unifiedConfig.getAIModelConfig(modelType);
|
||||
|
||||
const finalMaxTokens = maxTokens || modelConfig.maxOutputTokens;
|
||||
|
||||
@ -206,7 +259,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
throw new Error('No response from AI model');
|
||||
}
|
||||
|
||||
// Estimate token usage (since most APIs don't return exact counts)
|
||||
// Estimate token usage
|
||||
const promptTokens = this.estimateTokens(prompt);
|
||||
const responseTokens = this.estimateTokens(content);
|
||||
|
||||
@ -226,7 +279,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENHANCED CANDIDATE RETRIEVAL WITH AUDIT TRAIL
|
||||
// ENHANCED CANDIDATE RETRIEVAL WITH CENTRALIZED CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
private async getIntelligentCandidatesWithAudit(userQuery: string, toolsData: any, mode: string) {
|
||||
@ -237,8 +290,10 @@ class EnhancedMicroTaskAIPipeline {
|
||||
let similarityScores: Array<{ tool: string; score: number; type: string }> = [];
|
||||
let retrievalConfidence = 0;
|
||||
|
||||
// Log retrieval start
|
||||
if (embeddingsService.isEnabled()) {
|
||||
// PHASE 5: Check if embeddings are enabled via centralized configuration
|
||||
const embeddingsEnabled = unifiedConfig.isFeatureEnabled('embeddings');
|
||||
|
||||
if (embeddingsEnabled && embeddingsService.isEnabled()) {
|
||||
auditTrailService.logRetrievalStart('embeddings');
|
||||
|
||||
const similarItems = await embeddingsService.findSimilar(
|
||||
@ -263,7 +318,10 @@ class EnhancedMicroTaskAIPipeline {
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`);
|
||||
|
||||
if (toolNames.size >= 15) {
|
||||
// PHASE 5: Use configurable minimum candidates threshold
|
||||
const minCandidates = Math.min(15, this.embeddingCandidates * 0.25);
|
||||
|
||||
if (toolNames.size >= minCandidates) {
|
||||
candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name));
|
||||
candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name));
|
||||
selectionMethod = 'embeddings_candidates';
|
||||
@ -272,7 +330,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Using embeddings candidates: ${candidateTools.length} tools`);
|
||||
} else {
|
||||
console.log(`[ENHANCED PIPELINE] Embeddings insufficient (${toolNames.size} < 15), using AI selector`);
|
||||
console.log(`[ENHANCED PIPELINE] Embeddings insufficient (${toolNames.size} < ${minCandidates}), using AI selector`);
|
||||
auditTrailService.logRetrievalStart('ai_selector');
|
||||
candidateTools = toolsData.tools;
|
||||
candidateConcepts = toolsData.concepts;
|
||||
@ -321,12 +379,13 @@ class EnhancedMicroTaskAIPipeline {
|
||||
const startTime = Date.now();
|
||||
const initialCandidates = candidateTools.map(tool => tool.name);
|
||||
|
||||
// Log selection start - use strategic model for tool selection
|
||||
auditTrailService.logSelectionStart('strategic', initialCandidates);
|
||||
// PHASE 5: Use centralized configuration for model selection
|
||||
const modelType = unifiedConfig.getOptimalModelForTask('tool_selection');
|
||||
auditTrailService.logSelectionStart(modelType, initialCandidates);
|
||||
|
||||
const modeInstruction = mode === 'workflow'
|
||||
? 'The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases. Select 15-25 tools that cover the full investigation lifecycle.'
|
||||
: 'The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem. Select 3-8 tools that are most relevant and effective.';
|
||||
? `The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases. Select ${Math.min(25, Math.floor(this.maxSelectedItems * 0.4))}-${Math.min(this.maxSelectedItems, Math.floor(this.maxSelectedItems * 0.6))} tools that cover the full investigation lifecycle.`
|
||||
: `The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem. Select ${Math.min(8, Math.floor(this.maxSelectedItems * 0.1))}-${Math.min(this.maxSelectedItems, Math.floor(this.maxSelectedItems * 0.2))} tools that are most relevant and effective.`;
|
||||
|
||||
const toolsWithFullData = candidateTools.map((tool: any) => ({
|
||||
name: tool.name,
|
||||
@ -357,7 +416,16 @@ class EnhancedMicroTaskAIPipeline {
|
||||
related_software: concept.related_software || []
|
||||
}));
|
||||
|
||||
// ENHANCED: Bias-aware selection prompt
|
||||
// ENHANCED: Bias-aware selection prompt with configurable thresholds
|
||||
const biasConfig = unifiedConfig.getBiasConfig();
|
||||
const biasAwareness = biasConfig ? `
|
||||
BIAS PREVENTION (Configured Thresholds):
|
||||
- Popularity bias threshold: ${biasConfig.thresholds.popularity}
|
||||
- Avoid these over-selected tools unless optimal: ${biasConfig.patterns.commonlyOverselectedTools.slice(0, 5).join(', ')}
|
||||
- Domain concentration limit: ${biasConfig.thresholds.domainConcentration}
|
||||
- Skill level bias threshold: ${biasConfig.thresholds.skillLevel}
|
||||
` : 'BIAS PREVENTION: Use objective selection criteria based on scenario requirements.';
|
||||
|
||||
const prompt = `You are a DFIR expert with access to the complete forensics tool database. You need to select the most relevant tools and concepts for this specific query.
|
||||
|
||||
SELECTION METHOD: ${selectionMethod}
|
||||
@ -370,7 +438,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
USER QUERY: "${userQuery}"
|
||||
|
||||
CRITICAL SELECTION PRINCIPLES:
|
||||
1. **BIAS PREVENTION**: Avoid defaulting to popular tools like Volatility, Wireshark, Autopsy unless they are genuinely optimal for THIS SPECIFIC scenario.
|
||||
1. ${biasAwareness}
|
||||
|
||||
2. **SCENARIO-SPECIFIC LOGIC**:
|
||||
- "Rapid/Quick/Urgent/Triage" scenarios → Prioritize METHODS and rapid response approaches over complex software
|
||||
@ -419,7 +487,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}`;
|
||||
|
||||
try {
|
||||
const aiResult = await this.callAIWithModel(prompt, 'strategic', 'tool_selection', 2500);
|
||||
const aiResult = await this.callAIWithModel(prompt, modelType, 'tool_selection', 2500);
|
||||
|
||||
const result = this.safeParseJSON(aiResult.content, null);
|
||||
|
||||
@ -434,6 +502,14 @@ class EnhancedMicroTaskAIPipeline {
|
||||
throw new Error('AI selection returned empty selection');
|
||||
}
|
||||
|
||||
// PHASE 5: Validate selection against configuration limits
|
||||
if (totalSelected > this.maxSelectedItems) {
|
||||
console.warn(`[ENHANCED PIPELINE] AI selected ${totalSelected} items, exceeding limit of ${this.maxSelectedItems}. Truncating.`);
|
||||
const ratio = this.maxSelectedItems / totalSelected;
|
||||
result.selectedTools = result.selectedTools.slice(0, Math.floor(result.selectedTools.length * ratio));
|
||||
result.selectedConcepts = result.selectedConcepts.slice(0, Math.floor(result.selectedConcepts.length * ratio));
|
||||
}
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] AI selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
|
||||
console.log(`[ENHANCED PIPELINE] AI reasoning: ${result.reasoning}`);
|
||||
console.log(`[ENHANCED PIPELINE] AI bias considerations: ${result.biasConsiderations || 'Not specified'}`);
|
||||
@ -455,9 +531,11 @@ class EnhancedMicroTaskAIPipeline {
|
||||
rawResponse: aiResult.content
|
||||
});
|
||||
|
||||
// ENHANCED: Comprehensive bias analysis using the new BiasDetector
|
||||
// ENHANCED: Comprehensive bias analysis using centralized configuration
|
||||
if (unifiedConfig.isFeatureEnabled('biasDetection')) {
|
||||
console.log('[ENHANCED PIPELINE] Running comprehensive bias analysis...');
|
||||
auditTrailService.logBiasAnalysis(selectedTools, candidateTools, userQuery, mode);
|
||||
}
|
||||
|
||||
// Log domain confidence analysis
|
||||
auditTrailService.logDomainAnalysis(selectedTools, selectedConcepts);
|
||||
@ -512,7 +590,11 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}).filter(item => item.score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
const maxTools = mode === 'workflow' ? 20 : 8;
|
||||
// PHASE 5: Use configurable emergency limits
|
||||
const maxTools = mode === 'workflow' ?
|
||||
Math.min(20, Math.floor(this.maxSelectedItems * 0.33)) :
|
||||
Math.min(8, Math.floor(this.maxSelectedItems * 0.13));
|
||||
|
||||
const selectedTools = scoredTools.slice(0, maxTools).map(item => item.tool);
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Emergency selection: ${selectedTools.length} tools, keywords: ${keywords.slice(0, 5).join(', ')}`);
|
||||
@ -537,7 +619,7 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENHANCED MICRO-TASK METHODS WITH AUDIT TRAIL
|
||||
// MICRO-TASK METHODS WITH CENTRALIZED CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
private async delay(ms: number): Promise<void> {
|
||||
@ -560,8 +642,8 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}
|
||||
|
||||
try {
|
||||
// Use tactical model for micro-tasks (faster, cheaper)
|
||||
const modelType = forensicConfig.getModelForTask(taskType as any);
|
||||
// PHASE 5: Use centralized configuration for model selection
|
||||
const modelType = unifiedConfig.getOptimalModelForTask(taskType);
|
||||
const aiResult = await this.callAIWithModel(contextPrompt, modelType, taskType, maxTokens);
|
||||
|
||||
const result: MicroTaskResult = {
|
||||
@ -622,7 +704,6 @@ class EnhancedMicroTaskAIPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// Rest of the micro-task methods remain the same but use the enhanced callMicroTaskAI...
|
||||
private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> {
|
||||
const isWorkflow = context.mode === 'workflow';
|
||||
|
||||
@ -660,7 +741,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PROCESSING METHOD WITH FULL AUDIT TRAIL AND CONFIDENCE
|
||||
// MAIN PROCESSING METHOD WITH CENTRALIZED CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
async processQuery(userQuery: string, mode: string, userId: string = 'anonymous'): Promise<AnalysisResult> {
|
||||
@ -668,13 +749,18 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
let completedTasks = 0;
|
||||
let failedTasks = 0;
|
||||
|
||||
// Update bias detection baseline periodically
|
||||
// PHASE 5: Update bias detection baseline using centralized configuration
|
||||
this.updateBiasBaseline();
|
||||
|
||||
// PHASE 5: Check configuration health before processing
|
||||
const configHealth = unifiedConfig.validateConfigurationHealth();
|
||||
if (!configHealth.healthy) {
|
||||
console.warn('[ENHANCED PIPELINE] Configuration health degraded:', configHealth.errors);
|
||||
}
|
||||
|
||||
// Start audit trail
|
||||
const auditId = auditTrailService.startAudit(userId, userQuery, mode as 'workflow' | 'tool');
|
||||
console.log(`[ENHANCED PIPELINE] Starting ${mode} query processing with audit trail ${auditId} and bias detection`);
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Starting ${mode} query processing with audit trail ${auditId} and centralized configuration`);
|
||||
|
||||
// Log query classification
|
||||
auditTrailService.logQueryClassification({
|
||||
@ -706,15 +792,13 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`);
|
||||
|
||||
// MICRO-TASK SEQUENCE WITH AUDIT TRAIL
|
||||
// MICRO-TASK SEQUENCE WITH CENTRALIZED DELAYS
|
||||
|
||||
// Task 1: Scenario/Problem Analysis
|
||||
const analysisResult = await this.analyzeScenario(context);
|
||||
if (analysisResult.success) completedTasks++; else failedTasks++;
|
||||
await this.delay(this.microTaskDelay);
|
||||
|
||||
// ... (Additional micro-tasks would be implemented here)
|
||||
|
||||
// Build final recommendation (simplified for this example)
|
||||
const recommendation = this.buildRecommendation(context, mode, "Workflow-Empfehlung");
|
||||
|
||||
@ -724,7 +808,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
const auditTrail = auditTrailService.finalizeAudit(finalRecommendationCount);
|
||||
|
||||
const processingStats = {
|
||||
embeddingsUsed: embeddingsService.isEnabled(),
|
||||
embeddingsUsed: unifiedConfig.isFeatureEnabled('embeddings') && embeddingsService.isEnabled(),
|
||||
candidatesFromEmbeddings: filteredData.tools.length,
|
||||
finalSelectedItems: finalRecommendationCount,
|
||||
processingTimeMs: Date.now() - startTime,
|
||||
@ -735,9 +819,9 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
tokensTotalUsed: auditTrail?.processingSummary.tokensTotalUsed || 0
|
||||
};
|
||||
|
||||
// NEW: Extract confidence metrics from audit trail
|
||||
// PHASE 5: Extract confidence metrics from audit trail with centralized validation
|
||||
let confidenceMetrics: ConfidenceMetrics | undefined;
|
||||
if (auditTrail && this.config.features.confidenceScoring) {
|
||||
if (auditTrail && unifiedConfig.isFeatureEnabled('confidenceScoring')) {
|
||||
confidenceMetrics = {
|
||||
overall: auditTrail.qualityMetrics.overallConfidence,
|
||||
breakdown: auditTrail.qualityMetrics.confidenceBreakdown,
|
||||
@ -750,6 +834,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
|
||||
console.log(`[ENHANCED PIPELINE] Completed: ${completedTasks} tasks, Failed: ${failedTasks} tasks`);
|
||||
console.log(`[ENHANCED PIPELINE] Unique tools selected: ${context.seenToolNames.size}`);
|
||||
console.log(`[ENHANCED PIPELINE] Configuration health: ${configHealth.healthy ? 'Healthy' : 'Degraded'}`);
|
||||
|
||||
if (auditTrail) {
|
||||
console.log(`[ENHANCED PIPELINE] Audit Trail: ${auditTrail.auditId}`);
|
||||
@ -771,7 +856,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
biasRiskScore: auditTrail.qualityMetrics.biasRiskScore,
|
||||
transparencyScore: auditTrail.qualityMetrics.transparencyScore
|
||||
} : undefined,
|
||||
confidenceMetrics // NEW: Return detailed confidence metrics
|
||||
confidenceMetrics
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
@ -792,7 +877,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
||||
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
||||
.trim();
|
||||
|
||||
sanitized = sanitized.slice(0, 2000).replace(/\s+/g, ' ');
|
||||
sanitized = sanitized.slice(0, FORENSIC_CONSTANTS.MAX_QUERY_LENGTH).replace(/\s+/g, ' ');
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
|
459
src/utils/configIntegration.ts
Normal file
459
src/utils/configIntegration.ts
Normal file
@ -0,0 +1,459 @@
|
||||
// src/utils/configIntegration.ts - PHASE 5: Configuration Integration Utilities
|
||||
|
||||
import { enhancedConfigManager, FORENSIC_CONSTANTS, ConfigurationValidator } from './forensicConfigManager.js';
|
||||
import type { AIModelConfig, ForensicThresholds, BiasDetectionConfig } from './forensicConfig.js';
|
||||
|
||||
/**
|
||||
* PHASE 5: Configuration Integration Layer
|
||||
* Provides unified access to configuration across all application components
|
||||
* Replaces scattered hardcoded values and duplicate configuration parsing
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// UNIFIED CONFIGURATION ACCESS LAYER
|
||||
// ============================================================================
|
||||
|
||||
export class UnifiedConfigAccess {
|
||||
private static instance: UnifiedConfigAccess;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): UnifiedConfigAccess {
|
||||
if (!UnifiedConfigAccess.instance) {
|
||||
UnifiedConfigAccess.instance = new UnifiedConfigAccess();
|
||||
}
|
||||
return UnifiedConfigAccess.instance;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// COMPONENT CONFIGURATION ACCESS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get configuration for a specific component
|
||||
*/
|
||||
getComponentConfig<T = any>(componentId: string): T | null {
|
||||
return enhancedConfigManager.getComponentConfig<T>(componentId);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// AI MODEL CONFIGURATION ACCESS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get AI model configuration with automatic fallback handling
|
||||
*/
|
||||
getAIModelConfig(taskType: 'strategic' | 'tactical' | 'legacy' = 'tactical'): AIModelConfig {
|
||||
try {
|
||||
if (taskType === 'legacy') {
|
||||
// Backward compatibility - use tactical model for legacy calls
|
||||
return enhancedConfigManager.getAIModel('tactical');
|
||||
}
|
||||
return enhancedConfigManager.getAIModel(taskType);
|
||||
} catch (error) {
|
||||
console.error(`[CONFIG INTEGRATION] Failed to get ${taskType} AI model:`, error);
|
||||
// Fallback to tactical model
|
||||
return enhancedConfigManager.getAIModel('tactical');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine optimal AI model for specific task types
|
||||
*/
|
||||
getOptimalModelForTask(taskType: string): 'strategic' | 'tactical' {
|
||||
const strategicTasks = [
|
||||
'analysis', 'selection', 'scenario_analysis', 'approach_generation',
|
||||
'tool_selection', 'complex_reasoning', 'bias_analysis'
|
||||
];
|
||||
|
||||
const tacticalTasks = [
|
||||
'description', 'evaluation', 'background_knowledge', 'final_recommendations',
|
||||
'text_generation', 'formatting', 'explanation'
|
||||
];
|
||||
|
||||
if (strategicTasks.includes(taskType)) {
|
||||
return 'strategic';
|
||||
} else if (tacticalTasks.includes(taskType)) {
|
||||
return 'tactical';
|
||||
} else {
|
||||
// Default to tactical for unknown tasks (safer, cheaper)
|
||||
console.warn(`[CONFIG INTEGRATION] Unknown task type: ${taskType}, defaulting to tactical model`);
|
||||
return 'tactical';
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// THRESHOLD CONFIGURATION ACCESS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get threshold with validation and fallback
|
||||
*/
|
||||
getThreshold(thresholdName: keyof ForensicThresholds, fallback?: number): number {
|
||||
try {
|
||||
const value = enhancedConfigManager.getThreshold(thresholdName);
|
||||
|
||||
// Validate threshold value
|
||||
if (!ConfigurationValidator.validateThreshold(value, thresholdName)) {
|
||||
if (fallback !== undefined) {
|
||||
console.warn(`[CONFIG INTEGRATION] Invalid ${thresholdName}, using fallback: ${fallback}`);
|
||||
return fallback;
|
||||
}
|
||||
throw new Error(`Invalid threshold ${thresholdName}: ${value}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
} catch (error) {
|
||||
if (fallback !== undefined) {
|
||||
console.warn(`[CONFIG INTEGRATION] Failed to get ${thresholdName}, using fallback: ${fallback}`);
|
||||
return fallback;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rate limiting configuration
|
||||
*/
|
||||
getRateLimitConfig(): {
|
||||
windowMs: number;
|
||||
maxRequests: number;
|
||||
microTaskLimit: number;
|
||||
cleanupIntervalMs: number;
|
||||
} {
|
||||
return {
|
||||
windowMs: FORENSIC_CONSTANTS.RATE_LIMIT_WINDOW_MS,
|
||||
maxRequests: this.getThreshold('rateLimitMaxRequests', 6),
|
||||
microTaskLimit: parseInt(process.env.AI_MICRO_TASK_TOTAL_LIMIT || '50', 10),
|
||||
cleanupIntervalMs: FORENSIC_CONSTANTS.RATE_LIMIT_CLEANUP_INTERVAL_MS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get processing timeout configuration
|
||||
*/
|
||||
getProcessingTimeouts(): {
|
||||
aiTimeoutMs: number;
|
||||
microTaskTimeoutMs: number;
|
||||
microTaskDelayMs: number;
|
||||
rateLimitDelayMs: number;
|
||||
} {
|
||||
return {
|
||||
aiTimeoutMs: FORENSIC_CONSTANTS.DEFAULT_AI_TIMEOUT_MS,
|
||||
microTaskTimeoutMs: this.getThreshold('microTaskTimeoutMs', 25000),
|
||||
microTaskDelayMs: this.getThreshold('microTaskDelayMs', 500),
|
||||
rateLimitDelayMs: this.getThreshold('rateLimitDelayMs', 3000)
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// FEATURE FLAG ACCESS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Check if feature is enabled with dependency validation
|
||||
*/
|
||||
isFeatureEnabled(feature: string): boolean {
|
||||
const featureMap = {
|
||||
'confidenceScoring': 'confidenceScoring',
|
||||
'biasDetection': 'biasDetection',
|
||||
'performanceMetrics': 'performanceMetrics',
|
||||
'debugMode': 'debugMode',
|
||||
'auditTrail': 'auditTrail', // Special handling for audit trail
|
||||
'embeddings': 'embeddings' // Special handling for embeddings
|
||||
} as const;
|
||||
|
||||
const mappedFeature = featureMap[feature as keyof typeof featureMap];
|
||||
|
||||
if (!mappedFeature) {
|
||||
console.warn(`[CONFIG INTEGRATION] Unknown feature flag: ${feature}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special handling for compound features
|
||||
if (feature === 'auditTrail') {
|
||||
const auditConfig = enhancedConfigManager.getComponentConfig('audit_trail');
|
||||
return auditConfig?.auditTrail?.enabled || false;
|
||||
}
|
||||
|
||||
if (feature === 'embeddings') {
|
||||
const embeddingsConfig = enhancedConfigManager.getComponentConfig('embeddings');
|
||||
return embeddingsConfig?.embeddings?.enabled || false;
|
||||
}
|
||||
|
||||
return enhancedConfigManager.isFeatureEnabled(mappedFeature as any);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// BIAS DETECTION CONFIGURATION
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Get bias detection configuration with validation
|
||||
*/
|
||||
getBiasConfig(): BiasDetectionConfig | null {
|
||||
if (!this.isFeatureEnabled('biasDetection')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return enhancedConfigManager.getValidatedBiasConfig();
|
||||
} catch (error) {
|
||||
console.error('[CONFIG INTEGRATION] Failed to get bias detection config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bias threshold for specific bias type
|
||||
*/
|
||||
getBiasThreshold(biasType: string): number {
|
||||
const biasConfig = this.getBiasConfig();
|
||||
if (!biasConfig) {
|
||||
return 0.5; // Safe default
|
||||
}
|
||||
|
||||
const thresholdMap: Record<string, keyof BiasDetectionConfig['thresholds']> = {
|
||||
'popularity': 'popularity',
|
||||
'availability': 'availability',
|
||||
'recency': 'recency',
|
||||
'domain_concentration': 'domainConcentration',
|
||||
'skill_level': 'skillLevel'
|
||||
};
|
||||
|
||||
const thresholdKey = thresholdMap[biasType];
|
||||
if (!thresholdKey) {
|
||||
console.warn(`[CONFIG INTEGRATION] Unknown bias type: ${biasType}`);
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
return biasConfig.thresholds[thresholdKey];
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// VALIDATION AND HEALTH CHECKS
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Validate current configuration health
|
||||
*/
|
||||
validateConfigurationHealth(): {
|
||||
healthy: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
recommendations: string[];
|
||||
} {
|
||||
const health = enhancedConfigManager.getConfigurationHealth();
|
||||
const recommendations: string[] = [];
|
||||
|
||||
// Add specific recommendations based on configuration state
|
||||
if (!health.healthy) {
|
||||
recommendations.push('Review configuration errors and update environment variables');
|
||||
}
|
||||
|
||||
if (health.warnings.some(w => w.includes('retention'))) {
|
||||
recommendations.push('Consider increasing audit retention period for compliance');
|
||||
}
|
||||
|
||||
if (health.errors.some(e => e.includes('AI'))) {
|
||||
recommendations.push('Verify AI model endpoint connectivity and API keys');
|
||||
}
|
||||
|
||||
return {
|
||||
healthy: health.healthy,
|
||||
errors: health.errors,
|
||||
warnings: health.warnings,
|
||||
recommendations
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration summary for debugging
|
||||
*/
|
||||
getConfigurationSummary(): {
|
||||
version: string;
|
||||
features: Record<string, boolean>;
|
||||
models: {
|
||||
strategic: string;
|
||||
tactical: string;
|
||||
};
|
||||
thresholds: {
|
||||
confidence: number;
|
||||
bias: number;
|
||||
similarity: number;
|
||||
};
|
||||
health: string;
|
||||
} {
|
||||
const health = enhancedConfigManager.getConfigurationHealth();
|
||||
|
||||
try {
|
||||
const strategicModel = this.getAIModelConfig('strategic');
|
||||
const tacticalModel = this.getAIModelConfig('tactical');
|
||||
|
||||
return {
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
features: {
|
||||
confidenceScoring: this.isFeatureEnabled('confidenceScoring'),
|
||||
biasDetection: this.isFeatureEnabled('biasDetection'),
|
||||
auditTrail: this.isFeatureEnabled('auditTrail'),
|
||||
embeddings: this.isFeatureEnabled('embeddings'),
|
||||
performanceMetrics: this.isFeatureEnabled('performanceMetrics')
|
||||
},
|
||||
models: {
|
||||
strategic: strategicModel.model,
|
||||
tactical: tacticalModel.model
|
||||
},
|
||||
thresholds: {
|
||||
confidence: this.getThreshold('confidenceThreshold', 0.7),
|
||||
bias: this.getThreshold('biasAlertThreshold', 0.8),
|
||||
similarity: this.getThreshold('similarityThreshold', 0.3)
|
||||
},
|
||||
health: health.healthy ? 'healthy' : 'degraded'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[CONFIG INTEGRATION] Failed to get configuration summary:', error);
|
||||
return {
|
||||
version: 'unknown',
|
||||
features: {},
|
||||
models: { strategic: 'error', tactical: 'error' },
|
||||
thresholds: { confidence: 0.7, bias: 0.8, similarity: 0.3 },
|
||||
health: 'error'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION MIGRATION UTILITIES
|
||||
// ============================================================================
|
||||
|
||||
export class ConfigurationMigrator {
|
||||
/**
|
||||
* Migrate hardcoded values to configuration-based approach
|
||||
*/
|
||||
static migrateHardcodedValues(): {
|
||||
migrated: string[];
|
||||
remaining: string[];
|
||||
recommendations: string[];
|
||||
} {
|
||||
const migrated = [
|
||||
'Rate limit constants moved to FORENSIC_CONSTANTS',
|
||||
'AI model selection logic centralized',
|
||||
'Threshold validation centralized',
|
||||
'Feature flag access unified',
|
||||
'Bias detection thresholds configurable'
|
||||
];
|
||||
|
||||
const remaining = [
|
||||
// These should be addressed in file-specific migrations
|
||||
'Token estimation constants in aiPipeline.ts',
|
||||
'Default skill level descriptions in dataService.ts',
|
||||
'Emergency selection parameters in aiPipeline.ts'
|
||||
];
|
||||
|
||||
const recommendations = [
|
||||
'Update query.ts to use UnifiedConfigAccess for rate limiting',
|
||||
'Update aiPipeline.ts to use centralized timeout configuration',
|
||||
'Replace remaining hardcoded thresholds with configuration calls',
|
||||
'Add environment variables for any remaining constants'
|
||||
];
|
||||
|
||||
return { migrated, remaining, recommendations };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate environment variable template for missing configuration
|
||||
*/
|
||||
static generateEnvTemplate(): string {
|
||||
return `
|
||||
# PHASE 5: Enhanced Configuration Template
|
||||
# Add these to your .env file for complete configuration management
|
||||
|
||||
# AI Model Configuration (if using different endpoints)
|
||||
AI_STRATEGIC_ENDPOINT=\${AI_ANALYZER_ENDPOINT}
|
||||
AI_STRATEGIC_API_KEY=\${AI_ANALYZER_API_KEY}
|
||||
AI_STRATEGIC_MODEL=\${AI_ANALYZER_MODEL}
|
||||
AI_STRATEGIC_MAX_CONTEXT_TOKENS=32000
|
||||
AI_STRATEGIC_MAX_OUTPUT_TOKENS=1000
|
||||
AI_STRATEGIC_TEMPERATURE=0.2
|
||||
|
||||
AI_TACTICAL_ENDPOINT=\${AI_ANALYZER_ENDPOINT}
|
||||
AI_TACTICAL_API_KEY=\${AI_ANALYZER_API_KEY}
|
||||
AI_TACTICAL_MODEL=\${AI_ANALYZER_MODEL}
|
||||
AI_TACTICAL_MAX_CONTEXT_TOKENS=8000
|
||||
AI_TACTICAL_MAX_OUTPUT_TOKENS=500
|
||||
AI_TACTICAL_TEMPERATURE=0.3
|
||||
|
||||
# Forensic Enhancement Configuration
|
||||
FORENSIC_AUDIT_ENABLED=true
|
||||
FORENSIC_CONFIDENCE_SCORING_ENABLED=true
|
||||
FORENSIC_BIAS_DETECTION_ENABLED=true
|
||||
FORENSIC_AUDIT_RETENTION_DAYS=90
|
||||
FORENSIC_AUDIT_DETAIL_LEVEL=detailed
|
||||
|
||||
# Performance Configuration
|
||||
AI_MICRO_TASK_TOTAL_LIMIT=50
|
||||
AI_MICRO_TASK_TIMEOUT_MS=25000
|
||||
AI_MICRO_TASK_DELAY_MS=500
|
||||
AI_RATE_LIMIT_DELAY_MS=3000
|
||||
AI_RATE_LIMIT_MAX_REQUESTS=6
|
||||
|
||||
# Confidence and Bias Thresholds
|
||||
AI_CONFIDENCE_THRESHOLD=0.7
|
||||
AI_BIAS_ALERT_THRESHOLD=0.8
|
||||
TOOL_POPULARITY_BIAS_THRESHOLD=0.75
|
||||
EMBEDDINGS_CONFIDENCE_THRESHOLD=0.6
|
||||
SELECTION_CONFIDENCE_MINIMUM=0.5
|
||||
`.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT SINGLETON INSTANCE AND RE-EXPORT CONSTANTS
|
||||
// ============================================================================
|
||||
|
||||
export const unifiedConfig = UnifiedConfigAccess.getInstance();
|
||||
|
||||
// Re-export FORENSIC_CONSTANTS from the canonical source to avoid duplication
|
||||
export { FORENSIC_CONSTANTS };
|
||||
|
||||
// ============================================================================
|
||||
// PHASE 5: LEGACY CODE REMOVAL GUIDE
|
||||
// ============================================================================
|
||||
|
||||
export const LEGACY_REMOVAL_GUIDE = {
|
||||
// Files that can have hardcoded values removed
|
||||
filesToUpdate: {
|
||||
'src/pages/api/ai/query.ts': [
|
||||
'Replace RATE_LIMIT_WINDOW = 60 * 1000 with unifiedConfig.getRateLimitConfig().windowMs',
|
||||
'Replace MAIN_RATE_LIMIT_MAX with unifiedConfig.getRateLimitConfig().maxRequests',
|
||||
'Replace MICRO_TASK_TOTAL_LIMIT with unifiedConfig.getRateLimitConfig().microTaskLimit'
|
||||
],
|
||||
'src/utils/aiPipeline.ts': [
|
||||
'Replace hardcoded maxSelectedItems with unifiedConfig.getThreshold("maxSelectedItems")',
|
||||
'Replace hardcoded embeddingCandidates with unifiedConfig.getThreshold("embeddingCandidates")',
|
||||
'Replace hardcoded similarityThreshold with unifiedConfig.getThreshold("similarityThreshold")',
|
||||
'Replace hardcoded microTaskDelay with unifiedConfig.getProcessingTimeouts().microTaskDelayMs'
|
||||
],
|
||||
'src/utils/embeddings.ts': [
|
||||
'Replace hardcoded batchSize with unifiedConfig.getComponentConfig("embeddings").batchSize',
|
||||
'Replace hardcoded batchDelay with unifiedConfig.getComponentConfig("embeddings").batchDelayMs'
|
||||
]
|
||||
},
|
||||
|
||||
// Functions that can be consolidated
|
||||
functionsToConsolidate: [
|
||||
'Environment variable parsing (getEnv, getEnvNumber, getEnvFloat, getEnvBoolean)',
|
||||
'Threshold validation logic',
|
||||
'AI model selection logic',
|
||||
'Configuration validation patterns'
|
||||
],
|
||||
|
||||
// Constants that should be removed
|
||||
constantsToRemove: [
|
||||
'Hardcoded rate limit values in query.ts',
|
||||
'Hardcoded timeout values in multiple files',
|
||||
'Duplicate threshold definitions',
|
||||
'Scattered environment variable defaults'
|
||||
]
|
||||
} as const;
|
@ -1,7 +1,9 @@
|
||||
// src/utils/embeddings.ts
|
||||
// src/utils/embeddings.ts - PHASE 5: Enhanced with Centralized Configuration
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { getCompressedToolsDataForAI } from './dataService.js';
|
||||
import { unifiedConfig } from './configIntegration.js';
|
||||
|
||||
interface EmbeddingData {
|
||||
id: string;
|
||||
@ -32,24 +34,60 @@ class EmbeddingsService {
|
||||
private embeddings: EmbeddingData[] = [];
|
||||
private isInitialized = false;
|
||||
private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json');
|
||||
|
||||
// PHASE 5: Remove hardcoded values, use centralized configuration
|
||||
private readonly enabled: boolean;
|
||||
private readonly batchSize: number;
|
||||
private readonly batchDelay: number;
|
||||
private readonly enabled: boolean;
|
||||
private readonly endpoint: string;
|
||||
private readonly apiKey: string;
|
||||
private readonly model: string;
|
||||
|
||||
constructor() {
|
||||
this.enabled = process.env.AI_EMBEDDINGS_ENABLED === 'true';
|
||||
this.batchSize = parseInt(process.env.AI_EMBEDDINGS_BATCH_SIZE || '20', 10);
|
||||
this.batchDelay = parseInt(process.env.AI_EMBEDDINGS_BATCH_DELAY_MS || '1000', 10);
|
||||
// PHASE 5: Load all configuration from centralized config manager
|
||||
const embeddingsConfig = unifiedConfig.getComponentConfig('embeddings');
|
||||
|
||||
this.enabled = unifiedConfig.isFeatureEnabled('embeddings');
|
||||
|
||||
if (this.enabled && embeddingsConfig?.embeddings) {
|
||||
this.batchSize = embeddingsConfig.embeddings.batchSize;
|
||||
this.batchDelay = embeddingsConfig.embeddings.batchDelayMs;
|
||||
this.endpoint = embeddingsConfig.embeddings.endpoint;
|
||||
this.apiKey = embeddingsConfig.embeddings.apiKey;
|
||||
this.model = embeddingsConfig.embeddings.model;
|
||||
|
||||
console.log(`[EMBEDDINGS] Initialized with centralized configuration:`);
|
||||
console.log(` - Enabled: ${this.enabled}`);
|
||||
console.log(` - Batch size: ${this.batchSize}`);
|
||||
console.log(` - Batch delay: ${this.batchDelay}ms`);
|
||||
console.log(` - Model: ${this.model}`);
|
||||
} else {
|
||||
// Fallback values if configuration fails
|
||||
this.batchSize = 20;
|
||||
this.batchDelay = 1000;
|
||||
this.endpoint = '';
|
||||
this.apiKey = '';
|
||||
this.model = 'mistral-embed';
|
||||
|
||||
console.log('[EMBEDDINGS] Embeddings disabled or configuration unavailable, using fallback values');
|
||||
}
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization');
|
||||
console.log('[EMBEDDINGS] Embeddings disabled via centralized configuration, skipping initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate configuration before proceeding
|
||||
if (!this.endpoint || !this.apiKey || !this.model) {
|
||||
console.error('[EMBEDDINGS] Missing required configuration: endpoint, apiKey, or model');
|
||||
console.log('[EMBEDDINGS] Disabling embeddings service');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[EMBEDDINGS] Initializing embeddings system...');
|
||||
console.log('[EMBEDDINGS] Initializing embeddings system with centralized configuration...');
|
||||
|
||||
// Create data directory if it doesn't exist
|
||||
await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true });
|
||||
@ -64,12 +102,12 @@ class EmbeddingsService {
|
||||
console.log('[EMBEDDINGS] Using cached embeddings');
|
||||
this.embeddings = existingEmbeddings.embeddings;
|
||||
} else {
|
||||
console.log('[EMBEDDINGS] Generating new embeddings...');
|
||||
console.log('[EMBEDDINGS] Generating new embeddings with configured parameters...');
|
||||
await this.generateEmbeddings(toolsData, currentDataHash);
|
||||
}
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log(`[EMBEDDINGS] Initialized with ${this.embeddings.length} embeddings`);
|
||||
console.log(`[EMBEDDINGS] Initialized with ${this.embeddings.length} embeddings using centralized configuration`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[EMBEDDINGS] Failed to initialize:', error);
|
||||
@ -115,22 +153,19 @@ class EmbeddingsService {
|
||||
}
|
||||
|
||||
private async generateEmbeddingsBatch(contents: string[]): Promise<number[][]> {
|
||||
const endpoint = process.env.AI_EMBEDDINGS_ENDPOINT;
|
||||
const apiKey = process.env.AI_EMBEDDINGS_API_KEY;
|
||||
const model = process.env.AI_EMBEDDINGS_MODEL;
|
||||
|
||||
if (!endpoint || !apiKey || !model) {
|
||||
throw new Error('Missing embeddings API configuration');
|
||||
// PHASE 5: Use centralized configuration for API parameters
|
||||
if (!this.endpoint || !this.apiKey || !this.model) {
|
||||
throw new Error('Missing embeddings API configuration from centralized config');
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
const response = await fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
'Authorization': `Bearer ${this.apiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
model: this.model,
|
||||
input: contents
|
||||
})
|
||||
});
|
||||
@ -153,12 +188,17 @@ class EmbeddingsService {
|
||||
const contents = allItems.map(item => this.createContentString(item));
|
||||
this.embeddings = [];
|
||||
|
||||
// Process in batches to respect rate limits
|
||||
// PHASE 5: Use centralized configuration for batch processing
|
||||
console.log(`[EMBEDDINGS] Processing ${contents.length} items in batches of ${this.batchSize} with ${this.batchDelay}ms delay`);
|
||||
|
||||
for (let i = 0; i < contents.length; i += this.batchSize) {
|
||||
const batch = contents.slice(i, i + this.batchSize);
|
||||
const batchItems = allItems.slice(i, i + this.batchSize);
|
||||
|
||||
console.log(`[EMBEDDINGS] Processing batch ${Math.ceil((i + 1) / this.batchSize)} of ${Math.ceil(contents.length / this.batchSize)}`);
|
||||
const batchNumber = Math.ceil((i + 1) / this.batchSize);
|
||||
const totalBatches = Math.ceil(contents.length / this.batchSize);
|
||||
|
||||
console.log(`[EMBEDDINGS] Processing batch ${batchNumber} of ${totalBatches} (configured batch size: ${this.batchSize})`);
|
||||
|
||||
try {
|
||||
const embeddings = await this.generateEmbeddingsBatch(batch);
|
||||
@ -181,22 +221,24 @@ class EmbeddingsService {
|
||||
});
|
||||
});
|
||||
|
||||
// Rate limiting delay between batches
|
||||
// PHASE 5: Use centralized configuration for batch delay
|
||||
if (i + this.batchSize < contents.length) {
|
||||
console.log(`[EMBEDDINGS] Waiting ${this.batchDelay}ms before next batch (configured delay)`);
|
||||
await new Promise(resolve => setTimeout(resolve, this.batchDelay));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[EMBEDDINGS] Failed to process batch ${Math.ceil((i + 1) / this.batchSize)}:`, error);
|
||||
console.error(`[EMBEDDINGS] Failed to process batch ${batchNumber}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveEmbeddings(version);
|
||||
console.log(`[EMBEDDINGS] Generation complete using centralized configuration`);
|
||||
}
|
||||
|
||||
public async embedText(text: string): Promise<number[]> {
|
||||
// Re‑use the private batch helper to avoid auth duplication
|
||||
// Re-use the private batch helper to avoid auth duplication
|
||||
const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]);
|
||||
return embedding;
|
||||
}
|
||||
@ -217,6 +259,7 @@ class EmbeddingsService {
|
||||
|
||||
async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise<EmbeddingSearchResult[]> {
|
||||
if (!this.enabled || !this.isInitialized || this.embeddings.length === 0) {
|
||||
console.log(`[EMBEDDINGS] Service unavailable - Enabled: ${this.enabled}, Initialized: ${this.isInitialized}, Count: ${this.embeddings.length}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -229,11 +272,14 @@ class EmbeddingsService {
|
||||
similarity: this.cosineSimilarity(queryEmbedding, item.embedding)
|
||||
}));
|
||||
|
||||
return similarities
|
||||
const results = similarities
|
||||
.filter(item => item.similarity >= threshold)
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.slice(0, maxResults);
|
||||
|
||||
console.log(`[EMBEDDINGS] Found ${results.length} similar items above threshold ${threshold} (max: ${maxResults})`);
|
||||
return results;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[EMBEDDINGS] Failed to find similar items:', error);
|
||||
return [];
|
||||
@ -244,23 +290,114 @@ class EmbeddingsService {
|
||||
return this.enabled && this.isInitialized;
|
||||
}
|
||||
|
||||
getStats(): { enabled: boolean; initialized: boolean; count: number } {
|
||||
getStats(): {
|
||||
enabled: boolean;
|
||||
initialized: boolean;
|
||||
count: number;
|
||||
configuration: {
|
||||
batchSize: number;
|
||||
batchDelay: number;
|
||||
model: string;
|
||||
endpoint: string;
|
||||
configurationSource: string;
|
||||
}
|
||||
} {
|
||||
return {
|
||||
enabled: this.enabled,
|
||||
initialized: this.isInitialized,
|
||||
count: this.embeddings.length
|
||||
count: this.embeddings.length,
|
||||
configuration: {
|
||||
batchSize: this.batchSize,
|
||||
batchDelay: this.batchDelay,
|
||||
model: this.model,
|
||||
endpoint: this.endpoint ? `${this.endpoint.split('/')[2]}` : 'not_configured', // Hide full endpoint for security
|
||||
configurationSource: 'centralized_config_manager'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// PHASE 5: Configuration validation and health check
|
||||
validateConfiguration(): {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
} {
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (!this.enabled) {
|
||||
warnings.push('Embeddings service disabled via configuration');
|
||||
}
|
||||
|
||||
if (this.enabled) {
|
||||
if (!this.endpoint) errors.push('Embeddings endpoint not configured');
|
||||
if (!this.apiKey) errors.push('Embeddings API key not configured');
|
||||
if (!this.model) errors.push('Embeddings model not configured');
|
||||
|
||||
if (this.batchSize < 1 || this.batchSize > 100) {
|
||||
warnings.push(`Batch size ${this.batchSize} may be suboptimal (recommended: 10-50)`);
|
||||
}
|
||||
|
||||
if (this.batchDelay < 100) {
|
||||
warnings.push(`Batch delay ${this.batchDelay}ms may be too aggressive for rate limiting`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
// PHASE 5: Refresh configuration from centralized manager
|
||||
async refreshConfiguration(): Promise<void> {
|
||||
console.log('[EMBEDDINGS] Refreshing configuration from centralized manager...');
|
||||
|
||||
const embeddingsConfig = unifiedConfig.getComponentConfig('embeddings');
|
||||
|
||||
if (embeddingsConfig?.embeddings) {
|
||||
const oldBatchSize = this.batchSize;
|
||||
const oldBatchDelay = this.batchDelay;
|
||||
|
||||
// Update configuration values
|
||||
Object.assign(this, {
|
||||
batchSize: embeddingsConfig.embeddings.batchSize,
|
||||
batchDelay: embeddingsConfig.embeddings.batchDelayMs,
|
||||
endpoint: embeddingsConfig.embeddings.endpoint,
|
||||
apiKey: embeddingsConfig.embeddings.apiKey,
|
||||
model: embeddingsConfig.embeddings.model
|
||||
});
|
||||
|
||||
console.log(`[EMBEDDINGS] Configuration updated - Batch size: ${oldBatchSize} → ${this.batchSize}, Delay: ${oldBatchDelay} → ${this.batchDelay}ms`);
|
||||
}
|
||||
|
||||
// Validate new configuration
|
||||
const validation = this.validateConfiguration();
|
||||
if (!validation.valid) {
|
||||
console.error('[EMBEDDINGS] Configuration validation failed after refresh:', validation.errors);
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
console.warn('[EMBEDDINGS] Configuration warnings after refresh:', validation.warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const embeddingsService = new EmbeddingsService();
|
||||
|
||||
export { embeddingsService, type EmbeddingData, type EmbeddingSearchResult };
|
||||
|
||||
// PHASE 5: Auto-initialization with centralized configuration validation
|
||||
if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') {
|
||||
// Validate configuration before attempting initialization
|
||||
const configHealth = unifiedConfig.validateConfigurationHealth();
|
||||
|
||||
if (configHealth.healthy) {
|
||||
embeddingsService.initialize().catch(error => {
|
||||
console.error('[EMBEDDINGS] Auto-initialization failed:', error);
|
||||
});
|
||||
} else {
|
||||
console.warn('[EMBEDDINGS] Skipping auto-initialization due to configuration health issues:', configHealth.errors);
|
||||
}
|
||||
}
|
545
src/utils/forensicConfigManager.ts
Normal file
545
src/utils/forensicConfigManager.ts
Normal file
@ -0,0 +1,545 @@
|
||||
// src/utils/forensicConfigManager.ts - PHASE 5: Centralized Configuration Management
|
||||
|
||||
import { forensicConfig, type ForensicConfig, type AIModelConfig, type ForensicThresholds, type BiasDetectionConfig } from './forensicConfig.js';
|
||||
|
||||
/**
|
||||
* PHASE 5: Enhanced Configuration Manager
|
||||
* Centralizes ALL configuration management, eliminates hardcoded values,
|
||||
* and provides unified configuration access across the entire application.
|
||||
*/
|
||||
|
||||
interface ConfigurationComponent {
|
||||
id: string;
|
||||
name: string;
|
||||
configKeys: string[];
|
||||
validation?: (config: any) => boolean;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
interface ConfigurationSnapshot {
|
||||
timestamp: Date;
|
||||
version: string;
|
||||
components: Record<string, any>;
|
||||
thresholds: ForensicThresholds;
|
||||
features: Record<string, boolean>;
|
||||
validation: {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
};
|
||||
}
|
||||
|
||||
class EnhancedForensicConfigManager {
|
||||
private static instance: EnhancedForensicConfigManager;
|
||||
private config: ForensicConfig;
|
||||
private configSnapshot: ConfigurationSnapshot | null = null;
|
||||
private registeredComponents: Map<string, ConfigurationComponent> = new Map();
|
||||
|
||||
// Configuration validation cache
|
||||
private validationCache: Map<string, { valid: boolean; timestamp: Date }> = new Map();
|
||||
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
private constructor() {
|
||||
this.config = forensicConfig.getConfig();
|
||||
this.registerBuiltInComponents();
|
||||
this.createConfigurationSnapshot();
|
||||
this.validateAllConfigurations();
|
||||
}
|
||||
|
||||
static getInstance(): EnhancedForensicConfigManager {
|
||||
if (!EnhancedForensicConfigManager.instance) {
|
||||
EnhancedForensicConfigManager.instance = new EnhancedForensicConfigManager();
|
||||
}
|
||||
return EnhancedForensicConfigManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register configuration components for centralized management
|
||||
*/
|
||||
private registerBuiltInComponents(): void {
|
||||
// AI Models Configuration Component
|
||||
this.registerComponent({
|
||||
id: 'ai_models',
|
||||
name: 'AI Models Configuration',
|
||||
configKeys: ['aiModels.strategic', 'aiModels.tactical', 'legacyModel'],
|
||||
validation: (config) => {
|
||||
return config.aiModels?.strategic?.endpoint &&
|
||||
config.aiModels?.tactical?.endpoint &&
|
||||
config.aiModels.strategic.apiKey &&
|
||||
config.aiModels.tactical.apiKey;
|
||||
}
|
||||
});
|
||||
|
||||
// Audit Trail Configuration Component
|
||||
this.registerComponent({
|
||||
id: 'audit_trail',
|
||||
name: 'Audit Trail System',
|
||||
configKeys: ['auditTrail.enabled', 'auditTrail.retentionDays', 'auditTrail.detailLevel'],
|
||||
dependencies: ['ai_models']
|
||||
});
|
||||
|
||||
// Confidence Scoring Configuration Component
|
||||
this.registerComponent({
|
||||
id: 'confidence_scoring',
|
||||
name: 'Confidence Scoring System',
|
||||
configKeys: ['features.confidenceScoring', 'thresholds.confidenceThreshold'],
|
||||
dependencies: ['audit_trail']
|
||||
});
|
||||
|
||||
// Bias Detection Configuration Component
|
||||
this.registerComponent({
|
||||
id: 'bias_detection',
|
||||
name: 'Bias Detection System',
|
||||
configKeys: ['features.biasDetection', 'biasDetection', 'thresholds.biasAlertThreshold'],
|
||||
dependencies: ['confidence_scoring']
|
||||
});
|
||||
|
||||
// Embeddings Configuration Component
|
||||
this.registerComponent({
|
||||
id: 'embeddings',
|
||||
name: 'Embeddings System',
|
||||
configKeys: ['embeddings.enabled', 'embeddings.endpoint', 'embeddings.model']
|
||||
});
|
||||
|
||||
// Performance & Rate Limiting Component
|
||||
this.registerComponent({
|
||||
id: 'performance',
|
||||
name: 'Performance & Rate Limiting',
|
||||
configKeys: [
|
||||
'thresholds.rateLimitDelayMs',
|
||||
'thresholds.rateLimitMaxRequests',
|
||||
'thresholds.microTaskTimeoutMs',
|
||||
'queue.maxSize'
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
registerComponent(component: ConfigurationComponent): void {
|
||||
this.registeredComponents.set(component.id, component);
|
||||
console.log(`[CONFIG MANAGER] Registered component: ${component.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration for a specific component with validation
|
||||
*/
|
||||
getComponentConfig<T = any>(componentId: string): T | null {
|
||||
const component = this.registeredComponents.get(componentId);
|
||||
if (!component) {
|
||||
console.warn(`[CONFIG MANAGER] Unknown component: ${componentId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check validation cache
|
||||
const cacheKey = `component_${componentId}`;
|
||||
const cached = this.validationCache.get(cacheKey);
|
||||
if (cached && (Date.now() - cached.timestamp.getTime()) < this.CACHE_TTL) {
|
||||
if (!cached.valid) {
|
||||
console.error(`[CONFIG MANAGER] Component ${componentId} has invalid configuration (cached)`);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Validate component configuration
|
||||
const isValid = this.validateComponent(component);
|
||||
this.validationCache.set(cacheKey, { valid: isValid, timestamp: new Date() });
|
||||
|
||||
if (!isValid) {
|
||||
console.error(`[CONFIG MANAGER] Component ${componentId} has invalid configuration`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract component configuration
|
||||
const componentConfig: any = {};
|
||||
component.configKeys.forEach(key => {
|
||||
const value = this.getNestedConfigValue(key);
|
||||
if (value !== undefined) {
|
||||
this.setNestedValue(componentConfig, key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return componentConfig as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a specific component's configuration
|
||||
*/
|
||||
private validateComponent(component: ConfigurationComponent): boolean {
|
||||
// Check dependencies first
|
||||
if (component.dependencies) {
|
||||
for (const depId of component.dependencies) {
|
||||
const depComponent = this.registeredComponents.get(depId);
|
||||
if (!depComponent || !this.validateComponent(depComponent)) {
|
||||
console.error(`[CONFIG MANAGER] Component ${component.id} has invalid dependency: ${depId}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run custom validation if provided
|
||||
if (component.validation) {
|
||||
return component.validation(this.config);
|
||||
}
|
||||
|
||||
// Default validation: check that all required keys exist
|
||||
return component.configKeys.every(key => {
|
||||
const value = this.getNestedConfigValue(key);
|
||||
return value !== undefined && value !== null && value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nested configuration value using dot notation
|
||||
*/
|
||||
private getNestedConfigValue(path: string): any {
|
||||
return path.split('.').reduce((obj, key) => obj?.[key], this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nested value using dot notation
|
||||
*/
|
||||
private setNestedValue(obj: any, path: string, value: any): void {
|
||||
const keys = path.split('.');
|
||||
const lastKey = keys.pop()!;
|
||||
const target = keys.reduce((current, key) => {
|
||||
if (!current[key]) current[key] = {};
|
||||
return current[key];
|
||||
}, obj);
|
||||
target[lastKey] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get AI model configuration with automatic selection
|
||||
*/
|
||||
getAIModelForTask(taskType: 'analysis' | 'description' | 'selection' | 'evaluation' | 'strategic' | 'tactical'): AIModelConfig {
|
||||
const aiConfig = this.getComponentConfig<any>('ai_models');
|
||||
if (!aiConfig) {
|
||||
throw new Error('AI models configuration not available');
|
||||
}
|
||||
|
||||
// Determine model type based on task
|
||||
const useStrategic = ['analysis', 'selection', 'strategic'].includes(taskType);
|
||||
const modelConfig = useStrategic ? aiConfig.aiModels.strategic : aiConfig.aiModels.tactical;
|
||||
|
||||
console.log(`[CONFIG MANAGER] Selected ${useStrategic ? 'strategic' : 'tactical'} model for task: ${taskType}`);
|
||||
return modelConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all thresholds with component validation
|
||||
*/
|
||||
getValidatedThresholds(): ForensicThresholds {
|
||||
const performanceConfig = this.getComponentConfig('performance');
|
||||
const confidenceConfig = this.getComponentConfig('confidence_scoring');
|
||||
const biasConfig = this.getComponentConfig('bias_detection');
|
||||
|
||||
if (!performanceConfig || !confidenceConfig || !biasConfig) {
|
||||
console.warn('[CONFIG MANAGER] Some threshold components unavailable, using base configuration');
|
||||
return forensicConfig.getThresholds();
|
||||
}
|
||||
|
||||
return forensicConfig.getThresholds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bias detection configuration with validation
|
||||
*/
|
||||
getValidatedBiasConfig(): BiasDetectionConfig {
|
||||
const biasConfig = this.getComponentConfig('bias_detection');
|
||||
if (!biasConfig) {
|
||||
throw new Error('Bias detection configuration not available or invalid');
|
||||
}
|
||||
|
||||
return forensicConfig.getBiasDetectionConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a feature is enabled with dependency validation
|
||||
*/
|
||||
isFeatureEnabled(feature: keyof ForensicConfig['features']): boolean {
|
||||
// Feature-specific dependency checks
|
||||
switch (feature) {
|
||||
case 'confidenceScoring':
|
||||
return this.getComponentConfig('confidence_scoring') !== null &&
|
||||
forensicConfig.isFeatureEnabled(feature);
|
||||
|
||||
case 'biasDetection':
|
||||
return this.getComponentConfig('bias_detection') !== null &&
|
||||
this.isFeatureEnabled('confidenceScoring') && // Bias detection depends on confidence scoring
|
||||
forensicConfig.isFeatureEnabled(feature);
|
||||
|
||||
case 'performanceMetrics':
|
||||
return this.getComponentConfig('performance') !== null &&
|
||||
forensicConfig.isFeatureEnabled(feature);
|
||||
|
||||
default:
|
||||
return forensicConfig.isFeatureEnabled(feature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a comprehensive configuration snapshot
|
||||
*/
|
||||
private createConfigurationSnapshot(): void {
|
||||
const components: Record<string, any> = {};
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
// Validate and snapshot each component
|
||||
for (const [id, component] of this.registeredComponents) {
|
||||
try {
|
||||
const config = this.getComponentConfig(id);
|
||||
if (config) {
|
||||
components[id] = config;
|
||||
} else {
|
||||
errors.push(`Component ${id} (${component.name}) configuration invalid`);
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push(`Component ${id} error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for potential configuration conflicts
|
||||
if (this.config.features.biasDetection && !this.config.features.confidenceScoring) {
|
||||
warnings.push('Bias detection enabled without confidence scoring - some features may be limited');
|
||||
}
|
||||
|
||||
if (this.config.auditTrail.enabled && this.config.auditTrail.retentionDays < 30) {
|
||||
warnings.push('Audit retention period is less than 30 days - compliance requirements may not be met');
|
||||
}
|
||||
|
||||
this.configSnapshot = {
|
||||
timestamp: new Date(),
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
components,
|
||||
thresholds: this.config.thresholds,
|
||||
features: this.config.features,
|
||||
validation: {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
warnings
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`[CONFIG MANAGER] Configuration snapshot created - ${components.length} components, ${errors.length} errors, ${warnings.length} warnings`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all configurations
|
||||
*/
|
||||
private validateAllConfigurations(): void {
|
||||
let totalComponents = 0;
|
||||
let validComponents = 0;
|
||||
|
||||
for (const [id, component] of this.registeredComponents) {
|
||||
totalComponents++;
|
||||
if (this.validateComponent(component)) {
|
||||
validComponents++;
|
||||
} else {
|
||||
console.error(`[CONFIG MANAGER] Component validation failed: ${component.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[CONFIG MANAGER] Configuration validation complete: ${validComponents}/${totalComponents} components valid`);
|
||||
|
||||
if (validComponents < totalComponents) {
|
||||
console.warn(`[CONFIG MANAGER] ${totalComponents - validComponents} components have configuration issues`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration health status
|
||||
*/
|
||||
getConfigurationHealth(): {
|
||||
healthy: boolean;
|
||||
componentsValid: number;
|
||||
componentsTotal: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
lastValidated: Date;
|
||||
} {
|
||||
if (!this.configSnapshot) {
|
||||
this.createConfigurationSnapshot();
|
||||
}
|
||||
|
||||
const validComponents = Object.keys(this.configSnapshot!.components).length;
|
||||
const totalComponents = this.registeredComponents.size;
|
||||
|
||||
return {
|
||||
healthy: this.configSnapshot!.validation.valid && validComponents === totalComponents,
|
||||
componentsValid: validComponents,
|
||||
componentsTotal: totalComponents,
|
||||
errors: this.configSnapshot!.validation.errors,
|
||||
warnings: this.configSnapshot!.validation.warnings,
|
||||
lastValidated: this.configSnapshot!.timestamp
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export configuration for debugging/compliance
|
||||
*/
|
||||
exportConfiguration(includeSecrets: boolean = false): string {
|
||||
const exportData = {
|
||||
...this.configSnapshot,
|
||||
configurationComponents: Array.from(this.registeredComponents.entries()).map(([id, component]) => ({
|
||||
id,
|
||||
name: component.name,
|
||||
configKeys: component.configKeys,
|
||||
dependencies: component.dependencies || []
|
||||
}))
|
||||
};
|
||||
|
||||
if (!includeSecrets && exportData.components) {
|
||||
// Remove sensitive information
|
||||
Object.values(exportData.components).forEach((component: any) => {
|
||||
if (component.aiModels) {
|
||||
Object.values(component.aiModels).forEach((model: any) => {
|
||||
if (model.apiKey) model.apiKey = '[REDACTED]';
|
||||
});
|
||||
}
|
||||
if (component.embeddings?.apiKey) {
|
||||
component.embeddings.apiKey = '[REDACTED]';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* PHASE 5: Unified configuration access methods
|
||||
*/
|
||||
|
||||
// Centralized threshold access
|
||||
getThreshold(thresholdName: keyof ForensicThresholds): number {
|
||||
const thresholds = this.getValidatedThresholds();
|
||||
return thresholds[thresholdName];
|
||||
}
|
||||
|
||||
// Centralized feature flag access
|
||||
getFeatureFlag(featureName: keyof ForensicConfig['features']): boolean {
|
||||
return this.isFeatureEnabled(featureName);
|
||||
}
|
||||
|
||||
// Centralized AI model access
|
||||
getAIModel(purpose: 'strategic' | 'tactical'): AIModelConfig {
|
||||
return this.getAIModelForTask(purpose);
|
||||
}
|
||||
|
||||
// Configuration update with validation
|
||||
updateThreshold(thresholdName: keyof ForensicThresholds, value: number): boolean {
|
||||
if (value < 0 || value > 1) {
|
||||
console.error(`[CONFIG MANAGER] Invalid threshold value for ${thresholdName}: ${value}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update would require implementation in the base forensicConfig
|
||||
console.log(`[CONFIG MANAGER] Threshold update requested: ${thresholdName} = ${value}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear validation cache (useful for testing/development)
|
||||
clearValidationCache(): void {
|
||||
this.validationCache.clear();
|
||||
console.log('[CONFIG MANAGER] Validation cache cleared');
|
||||
}
|
||||
|
||||
// Refresh configuration (reload from environment)
|
||||
refreshConfiguration(): void {
|
||||
console.log('[CONFIG MANAGER] Refreshing configuration...');
|
||||
this.clearValidationCache();
|
||||
this.config = forensicConfig.getConfig();
|
||||
this.createConfigurationSnapshot();
|
||||
this.validateAllConfigurations();
|
||||
console.log('[CONFIG MANAGER] Configuration refreshed');
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance for Phase 5 implementation
|
||||
export const enhancedConfigManager = EnhancedForensicConfigManager.getInstance();
|
||||
|
||||
// Export types for component registration
|
||||
export type { ConfigurationComponent, ConfigurationSnapshot };
|
||||
|
||||
// PHASE 5: Centralized configuration constants (replaces scattered hardcoded values)
|
||||
export const FORENSIC_CONSTANTS = {
|
||||
// Rate limiting (consolidated from multiple files)
|
||||
RATE_LIMIT_WINDOW_MS: 60 * 1000,
|
||||
RATE_LIMIT_CLEANUP_INTERVAL_MS: 5 * 60 * 1000,
|
||||
MAX_STORE_SIZE: 1000,
|
||||
|
||||
// Processing timeouts
|
||||
DEFAULT_AI_TIMEOUT_MS: 25000,
|
||||
DEFAULT_MICRO_TASK_DELAY_MS: 500,
|
||||
|
||||
// Cache settings
|
||||
VALIDATION_CACHE_TTL_MS: 5 * 60 * 1000,
|
||||
CONFIG_REFRESH_INTERVAL_MS: 30 * 60 * 1000,
|
||||
|
||||
// Audit trail
|
||||
DEFAULT_AUDIT_RETENTION_DAYS: 90,
|
||||
AUDIT_CLEANUP_INTERVAL_MS: 60 * 60 * 1000,
|
||||
|
||||
// Input validation
|
||||
MAX_QUERY_LENGTH: 2000,
|
||||
MIN_QUERY_LENGTH: 10,
|
||||
MAX_CONTEXT_ENTRIES: 10,
|
||||
|
||||
// Response limits
|
||||
MAX_SELECTED_ITEMS_ABSOLUTE: 100,
|
||||
MIN_CONFIDENCE_FOR_RESPONSE: 0.1,
|
||||
MAX_UNCERTAINTY_FACTORS: 10
|
||||
} as const;
|
||||
|
||||
// PHASE 5: Configuration validation utilities
|
||||
export class ConfigurationValidator {
|
||||
static validateThreshold(value: number, name: string): boolean {
|
||||
if (typeof value !== 'number' || isNaN(value)) {
|
||||
console.error(`[CONFIG VALIDATOR] ${name} must be a number, got: ${typeof value}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value < 0 || value > 1) {
|
||||
console.error(`[CONFIG VALIDATOR] ${name} must be between 0 and 1, got: ${value}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static validateAIModel(model: AIModelConfig, name: string): boolean {
|
||||
const required = ['endpoint', 'apiKey', 'model', 'maxContextTokens', 'maxOutputTokens'];
|
||||
const missing = required.filter(key => !model[key as keyof AIModelConfig]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error(`[CONFIG VALIDATOR] ${name} missing required fields: ${missing.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.temperature < 0 || model.temperature > 2) {
|
||||
console.error(`[CONFIG VALIDATOR] ${name} temperature must be between 0 and 2`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static validateBiasConfig(config: BiasDetectionConfig): boolean {
|
||||
// Validate thresholds
|
||||
const thresholdKeys = Object.keys(config.thresholds);
|
||||
for (const key of thresholdKeys) {
|
||||
const value = config.thresholds[key as keyof typeof config.thresholds];
|
||||
if (!this.validateThreshold(value, `bias.thresholds.${key}`)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate weights sum to 1.0
|
||||
const weightSum = Object.values(config.weights).reduce((sum, weight) => sum + weight, 0);
|
||||
if (Math.abs(weightSum - 1.0) > 0.01) {
|
||||
console.error(`[CONFIG VALIDATOR] Bias weights sum to ${weightSum.toFixed(3)}, should sum to 1.0`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user