This commit is contained in:
overcuriousity 2025-08-02 16:57:22 +02:00
parent 78b30fb7c6
commit 5c3884094c
5 changed files with 1506 additions and 205 deletions

View File

@ -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 type { APIRoute } from 'astro';
import { withAPIAuth } from '../../../utils/auth.js'; import { withAPIAuth } from '../../../utils/auth.js';
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js'; import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js'; import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
import { aiPipeline } from '../../../utils/aiPipeline.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 { confidenceScorer } from '../../../utils/confidenceScoring.js';
import { biasDetector } from '../../../utils/biasDetection.js'; import { biasDetector } from '../../../utils/biasDetection.js';
@ -19,13 +19,9 @@ interface RateLimitData {
const rateLimitStore = new Map<string, RateLimitData>(); const rateLimitStore = new Map<string, RateLimitData>();
// Use configuration instead of hard-coded values // PHASE 5: Use centralized configuration instead of hardcoded values
const config = forensicConfig.getConfig(); const rateLimitConfig = unifiedConfig.getRateLimitConfig();
const thresholds = forensicConfig.getThresholds(); const processingTimeouts = unifiedConfig.getProcessingTimeouts();
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);
function sanitizeInput(input: string): string { function sanitizeInput(input: string): string {
let sanitized = input 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]') .replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
.trim(); .trim();
sanitized = sanitized.slice(0, 2000).replace(/\s+/g, ' '); sanitized = sanitized.slice(0, FORENSIC_CONSTANTS.MAX_QUERY_LENGTH).replace(/\s+/g, ' ');
return sanitized; return sanitized;
} }
@ -46,26 +42,26 @@ function checkRateLimit(userId: string): { allowed: boolean; reason?: string; mi
if (!userLimit || now > userLimit.resetTime) { if (!userLimit || now > userLimit.resetTime) {
rateLimitStore.set(userId, { rateLimitStore.set(userId, {
count: 1, count: 1,
resetTime: now + RATE_LIMIT_WINDOW, resetTime: now + rateLimitConfig.windowMs,
microTaskCount: 0 microTaskCount: 0
}); });
return { return {
allowed: true, allowed: true,
microTasksRemaining: MICRO_TASK_TOTAL_LIMIT microTasksRemaining: rateLimitConfig.microTaskLimit
}; };
} }
if (userLimit.count >= MAIN_RATE_LIMIT_MAX) { if (userLimit.count >= rateLimitConfig.maxRequests) {
return { return {
allowed: false, 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 { return {
allowed: false, 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 { return {
allowed: true, 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); const userLimit = rateLimitStore.get(userId);
if (userLimit) { if (userLimit) {
userLimit.microTaskCount += aiCallsMade; 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() { function cleanupExpiredRateLimits() {
const now = Date.now(); const now = Date.now();
const maxStoreSize = 1000;
for (const [userId, limit] of rateLimitStore.entries()) { for (const [userId, limit] of rateLimitStore.entries()) {
if (now > limit.resetTime) { 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()); const entries = Array.from(rateLimitStore.entries());
entries.sort((a, b) => a[1].resetTime - b[1].resetTime); 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)); toRemove.forEach(([userId]) => rateLimitStore.delete(userId));
console.log(`[RATE LIMIT] Cleanup: removed ${toRemove.length} old entries`); 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 }) => { export const POST: APIRoute = async ({ request }) => {
try { try {
@ -125,9 +121,17 @@ export const POST: APIRoute = async ({ request }) => {
const body = await request.json(); const body = await request.json();
const { query, mode = 'workflow', taskId: clientTaskId } = body; 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] 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] User: ${userId}, Configuration Health: ${configSummary.health}`);
console.log(`[ENHANCED API] Audit Trail: ${config.auditTrail.enabled ? 'Enabled' : 'Disabled'}`); 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}`); console.log(`[ENHANCED API] Micro-task calls remaining: ${rateLimitResult.microTasksRemaining}`);
if (!query || typeof query !== 'string') { if (!query || typeof query !== 'string') {
@ -141,6 +145,11 @@ export const POST: APIRoute = async ({ request }) => {
} }
const sanitizedQuery = sanitizeInput(query); 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]')) { if (sanitizedQuery.includes('[FILTERED]')) {
console.log(`[ENHANCED API] Filtered input detected for task ${clientTaskId}`); console.log(`[ENHANCED API] Filtered input detected for task ${clientTaskId}`);
return apiError.badRequest('Invalid input detected'); return apiError.badRequest('Invalid input detected');
@ -163,12 +172,15 @@ export const POST: APIRoute = async ({ request }) => {
const estimatedAICallsMade = stats.microTasksCompleted + stats.microTasksFailed; const estimatedAICallsMade = stats.microTasksCompleted + stats.microTasksFailed;
incrementMicroTaskCount(userId, estimatedAICallsMade); incrementMicroTaskCount(userId, estimatedAICallsMade);
// Log comprehensive results // PHASE 5: Enhanced logging with configuration context
console.log(`[ENHANCED API] Enhanced pipeline completed for ${taskId}:`); console.log(`[ENHANCED API] Enhanced pipeline completed for ${taskId}:`);
console.log(` - Mode: ${mode}`); console.log(` - Mode: ${mode}`);
console.log(` - User: ${userId}`); console.log(` - User: ${userId}`);
console.log(` - Query length: ${sanitizedQuery.length}`); console.log(` - Query length: ${sanitizedQuery.length}`);
console.log(` - Processing time: ${stats.processingTimeMs}ms`); 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 completed: ${stats.microTasksCompleted}`);
console.log(` - Micro-tasks failed: ${stats.microTasksFailed}`); console.log(` - Micro-tasks failed: ${stats.microTasksFailed}`);
console.log(` - Estimated AI calls: ${estimatedAICallsMade}`); console.log(` - Estimated AI calls: ${estimatedAICallsMade}`);
@ -188,8 +200,8 @@ export const POST: APIRoute = async ({ request }) => {
} }
} }
// NEW: Enhanced confidence metrics // PHASE 5: Enhanced confidence metrics using centralized configuration
if (result.confidenceMetrics && config.features.confidenceScoring) { if (result.confidenceMetrics && unifiedConfig.isFeatureEnabled('confidenceScoring')) {
console.log(` - Confidence Breakdown:`); console.log(` - Confidence Breakdown:`);
console.log(` * Retrieval: ${(result.confidenceMetrics.breakdown.retrieval * 100).toFixed(1)}%`); console.log(` * Retrieval: ${(result.confidenceMetrics.breakdown.retrieval * 100).toFixed(1)}%`);
console.log(` * Selection: ${(result.confidenceMetrics.breakdown.selection * 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 currentLimit = rateLimitStore.get(userId);
const remainingMicroTasks = currentLimit ? 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 ? const confidenceAcceptable = result.auditTrail ?
confidenceScorer.isConfidenceAcceptable(result.auditTrail.qualityMetrics.overallConfidence) : true; 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({ return new Response(JSON.stringify({
success: true, success: true,
mode, mode,
@ -220,13 +238,14 @@ export const POST: APIRoute = async ({ request }) => {
auditCompliant: result.auditTrail?.compliance.auditCompliant || false, auditCompliant: result.auditTrail?.compliance.auditCompliant || false,
biasChecked: result.auditTrail?.compliance.biasChecked || false, biasChecked: result.auditTrail?.compliance.biasChecked || false,
confidenceAssessed: result.auditTrail?.compliance.confidenceAssessed || 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 ? { forensicMetadata: result.auditTrail ? {
auditTrailId: result.auditTrail.auditId, auditTrailId: result.auditTrail.auditId,
auditEnabled: config.auditTrail.enabled, auditEnabled: unifiedConfig.isFeatureEnabled('auditTrail'),
// Core quality metrics // Core quality metrics
overallConfidence: result.auditTrail.qualityMetrics.overallConfidence, overallConfidence: result.auditTrail.qualityMetrics.overallConfidence,
@ -236,17 +255,19 @@ export const POST: APIRoute = async ({ request }) => {
evidenceQuality: result.auditTrail.qualityMetrics.evidenceQuality, evidenceQuality: result.auditTrail.qualityMetrics.evidenceQuality,
methodologicalSoundness: result.auditTrail.qualityMetrics.methodologicalSoundness, methodologicalSoundness: result.auditTrail.qualityMetrics.methodologicalSoundness,
// ENHANCED: Detailed bias analysis // ENHANCED: Detailed bias analysis using centralized thresholds
biasAnalysis: { biasAnalysis: {
overallBiasRisk: result.auditTrail.qualityMetrics.biasRiskScore, 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 => ({ detectedBiases: result.auditTrail.biasAnalysis.filter(bias => bias.detected).map(bias => ({
type: bias.biasType, type: bias.biasType,
severity: bias.severity, severity: bias.severity,
confidence: bias.confidence, confidence: bias.confidence,
description: bias.description, description: bias.description,
affectedTools: bias.evidence.affectedTools, affectedTools: bias.evidence.affectedTools,
recommendation: bias.recommendation recommendation: bias.recommendation,
thresholdUsed: unifiedConfig.getBiasThreshold(bias.biasType)
})), })),
biasFreeSeverity: result.auditTrail.biasAnalysis.filter(bias => !bias.detected).length, biasFreeSeverity: result.auditTrail.biasAnalysis.filter(bias => !bias.detected).length,
mitigationSuggestions: biasDetector.suggestBiasMitigation(result.auditTrail.biasAnalysis), mitigationSuggestions: biasDetector.suggestBiasMitigation(result.auditTrail.biasAnalysis),
@ -259,71 +280,101 @@ export const POST: APIRoute = async ({ request }) => {
} }
}, },
// Detailed confidence breakdown // Detailed confidence breakdown using centralized configuration
confidenceBreakdown: config.features.confidenceScoring ? { confidenceBreakdown: unifiedConfig.isFeatureEnabled('confidenceScoring') ? {
retrieval: result.auditTrail.qualityMetrics.confidenceBreakdown.retrieval, retrieval: result.auditTrail.qualityMetrics.confidenceBreakdown.retrieval,
selection: result.auditTrail.qualityMetrics.confidenceBreakdown.selection, selection: result.auditTrail.qualityMetrics.confidenceBreakdown.selection,
domain: result.auditTrail.qualityMetrics.confidenceBreakdown.domain, domain: result.auditTrail.qualityMetrics.confidenceBreakdown.domain,
meta: result.auditTrail.qualityMetrics.confidenceBreakdown.meta meta: result.auditTrail.qualityMetrics.confidenceBreakdown.meta
} : undefined, } : undefined,
// Confidence assessment details // Confidence assessment details with centralized thresholds
confidenceAssessment: config.features.confidenceScoring ? { confidenceAssessment: unifiedConfig.isFeatureEnabled('confidenceScoring') ? {
qualityLevel: result.auditTrail.qualityMetrics.qualityLevel, qualityLevel: result.auditTrail.qualityMetrics.qualityLevel,
reliability: result.auditTrail.qualityMetrics.confidenceReliability, reliability: result.auditTrail.qualityMetrics.confidenceReliability,
uncertaintyFactors: result.auditTrail.qualityMetrics.uncertaintyFactors, uncertaintyFactors: result.auditTrail.qualityMetrics.uncertaintyFactors,
improvementSuggestions: result.auditTrail.qualityMetrics.improvementSuggestions, improvementSuggestions: result.auditTrail.qualityMetrics.improvementSuggestions,
isAcceptable: confidenceAcceptable, isAcceptable: confidenceAcceptable,
threshold: thresholds.confidenceThreshold threshold: confidenceThreshold,
thresholdSource: 'centralized_configuration'
} : undefined, } : 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 => ({ biasWarnings: result.auditTrail.biasAnalysis.filter(b => b.detected).map(bias => ({
type: bias.biasType, type: bias.biasType,
severity: bias.severity, severity: bias.severity,
message: bias.description, message: bias.description,
actionRequired: bias.severity > 0.7 ? 'immediate_review' : 'consideration', actionRequired: bias.severity > 0.7 ? 'immediate_review' : 'consideration',
mitigation: bias.mitigation mitigation: bias.mitigation,
configuredThreshold: unifiedConfig.getBiasThreshold(bias.biasType)
})), })),
qualityWarnings: [ qualityWarnings: [
...((!confidenceAcceptable) ? [{ ...((!confidenceAcceptable) ? [{
type: 'low_confidence', type: 'low_confidence',
message: 'Overall confidence below acceptable threshold', message: `Overall confidence ${(result.auditTrail.qualityMetrics.overallConfidence * 100).toFixed(1)}% below threshold ${(confidenceThreshold * 100).toFixed(1)}%`,
actionRequired: 'expert_review' actionRequired: 'expert_review',
configuredThreshold: confidenceThreshold
}] : []), }] : []),
...(result.auditTrail.qualityMetrics.biasRiskScore > thresholds.biasAlertThreshold ? [{ ...(highBiasRisk ? [{
type: 'high_bias_risk', type: 'high_bias_risk',
message: 'High bias risk detected in tool selection', message: `High bias risk ${(result.auditTrail.qualityMetrics.biasRiskScore * 100).toFixed(1)}% above threshold ${(biasThreshold * 100).toFixed(1)}%`,
actionRequired: 'bias_review' 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: { systemConfig: {
strategicModel: result.auditTrail.systemConfig.strategicModel, strategicModel: result.auditTrail.systemConfig.strategicModel,
tacticalModel: result.auditTrail.systemConfig.tacticalModel, tacticalModel: result.auditTrail.systemConfig.tacticalModel,
auditLevel: result.auditTrail.systemConfig.auditLevel, auditLevel: result.auditTrail.systemConfig.auditLevel,
confidenceScoringEnabled: config.features.confidenceScoring, configurationHealth: configSummary.health,
biasDetectionEnabled: config.features.biasDetection, configurationVersion: configSummary.version,
biasThresholds: biasDetector.getBiasThresholds() featuresEnabled: configSummary.features,
thresholdsApplied: configSummary.thresholds,
centralized: true, // PHASE 5 indicator
configurationSource: 'enhanced_config_manager'
}, },
// Compliance and traceability // Enhanced compliance with configuration tracking
compliance: result.auditTrail.compliance, compliance: {
qualityLevel: result.auditTrail.qualityMetrics.overallConfidence >= thresholds.confidenceThreshold ? 'high' : ...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', result.auditTrail.qualityMetrics.overallConfidence >= 0.5 ? 'medium' : 'low',
// ENHANCED: Actionable insights with bias considerations // ENHANCED: Actionable insights with configuration-aware recommendations
actionableInsights: { actionableInsights: {
shouldReviewSelection: result.auditTrail.qualityMetrics.biasRiskScore > thresholds.biasAlertThreshold, shouldReviewSelection: highBiasRisk,
shouldImproveQuery: result.auditTrail.qualityMetrics.uncertaintyFactors.length > 2, shouldImproveQuery: result.auditTrail.qualityMetrics.uncertaintyFactors.length > 2,
shouldSeekExpertReview: result.auditTrail.qualityMetrics.overallConfidence < 0.6 || shouldSeekExpertReview: !confidenceAcceptable || highBiasRisk,
biasDetector.isHighBiasRisk(result.auditTrail.qualityMetrics.biasRiskScore), shouldCheckConfiguration: !configHealth.healthy,
confidenceImprovement: result.auditTrail.qualityMetrics.improvementSuggestions.slice(0, 3), confidenceImprovement: result.auditTrail.qualityMetrics.improvementSuggestions.slice(0, 3),
biasReduction: biasDetector.suggestBiasMitigation(result.auditTrail.biasAnalysis).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: { biasInsights: {
hasPopularityBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'popularity' && b.detected), hasPopularityBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'popularity' && b.detected),
hasAvailabilityBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'availability' && 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), hasRecencyBias: result.auditTrail.biasAnalysis.some(b => b.biasType === 'recency' && b.detected),
primaryBiasConcern: result.auditTrail.biasAnalysis primaryBiasConcern: result.auditTrail.biasAnalysis
.filter(b => b.detected) .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, auditTrailId: null,
auditEnabled: false, 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' message: 'Enhanced forensic features disabled - operating in legacy mode'
}, },
// PHASE 5: Enhanced rate limit info using centralized configuration
rateLimitInfo: { rateLimitInfo: {
mainRequestsRemaining: MAIN_RATE_LIMIT_MAX - (currentLimit?.count || 0), mainRequestsRemaining: rateLimitConfig.maxRequests - (currentLimit?.count || 0),
microTaskCallsRemaining: remainingMicroTasks, 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, status: 200,
@ -355,23 +424,29 @@ export const POST: APIRoute = async ({ request }) => {
} catch (error) { } catch (error) {
console.error('[ENHANCED API] Pipeline error:', 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 // Provide detailed error information for forensic purposes
if (error.message.includes('bias')) { 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')) { } 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')) { } 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')) { } 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')) { } 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')) { } 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')) { } 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 { } 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}`);
} }
} }
}; };

View File

@ -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 { getCompressedToolsDataForAI } from './dataService.js';
import { embeddingsService, type EmbeddingData, type EmbeddingSearchResult } from './embeddings.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 { auditTrailService, type ForensicAuditEntry } from './auditTrail.js';
import { confidenceScorer, type ConfidenceMetrics } from './confidenceScoring.js'; import { confidenceScorer, type ConfidenceMetrics } from './confidenceScoring.js';
import { biasDetector, type BiasAnalysisResult } from './biasDetection.js'; import { biasDetector, type BiasAnalysisResult } from './biasDetection.js';
@ -38,7 +38,6 @@ interface AnalysisResult {
biasRiskScore: number; biasRiskScore: number;
transparencyScore: number; transparencyScore: number;
}; };
// NEW: Enhanced confidence metrics
confidenceMetrics?: ConfidenceMetrics; confidenceMetrics?: ConfidenceMetrics;
} }
@ -62,36 +61,88 @@ interface AnalysisContext {
} }
class EnhancedMicroTaskAIPipeline { class EnhancedMicroTaskAIPipeline {
private config = forensicConfig.getConfig(); // PHASE 5: Replace hardcoded values with centralized configuration
private thresholds = forensicConfig.getThresholds();
// Remove hard-coded values - now using configuration
private maxSelectedItems: number; private maxSelectedItems: number;
private embeddingCandidates: number; private embeddingCandidates: number;
private similarityThreshold: number; private similarityThreshold: number;
private microTaskDelay: number; private microTaskDelay: number;
private maxContextTokens: number; private maxContextTokens: number;
private maxPromptTokens: number; private maxPromptTokens: number;
private processingTimeouts: any;
constructor() { constructor() {
// All values now come from configuration - no more hard-coded values // PHASE 5: All values now come from centralized configuration
this.maxSelectedItems = this.thresholds.maxSelectedItems; this.loadConfigurationValues();
this.embeddingCandidates = this.thresholds.embeddingCandidates;
this.similarityThreshold = this.thresholds.similarityThreshold;
this.microTaskDelay = this.thresholds.microTaskDelayMs;
// Dynamic token limits based on model capabilities console.log('[ENHANCED PIPELINE] Initialized with centralized configuration');
this.maxContextTokens = this.config.aiModels.strategic.maxContextTokens; this.logConfigurationSummary();
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'}`);
} }
/**
* 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 { 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 // Update bias detection baseline with recent audit data
const recentAudits = Array.from(auditTrailService['auditStorage'].values()) const recentAudits = Array.from(auditTrailService['auditStorage'].values())
.filter(audit => { .filter(audit => {
@ -99,7 +150,8 @@ class EnhancedMicroTaskAIPipeline {
return daysSinceAudit <= 30; // Last 30 days 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); biasDetector.updateBaseline(recentAudits);
console.log(`[ENHANCED PIPELINE] Updated bias baseline with ${recentAudits.length} recent audits`); 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( private async callAIWithModel(
@ -172,9 +224,10 @@ class EnhancedMicroTaskAIPipeline {
model: string; model: string;
endpoint: string; endpoint: string;
}> { }> {
// PHASE 5: Use centralized configuration for model selection
const modelConfig = modelType === 'legacy' ? const modelConfig = modelType === 'legacy' ?
forensicConfig.getLegacyAIModel() : unifiedConfig.getAIModelConfig('tactical') : // Legacy falls back to tactical
forensicConfig.getAIModel(modelType); unifiedConfig.getAIModelConfig(modelType);
const finalMaxTokens = maxTokens || modelConfig.maxOutputTokens; const finalMaxTokens = maxTokens || modelConfig.maxOutputTokens;
@ -206,7 +259,7 @@ class EnhancedMicroTaskAIPipeline {
throw new Error('No response from AI model'); 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 promptTokens = this.estimateTokens(prompt);
const responseTokens = this.estimateTokens(content); 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) { 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 similarityScores: Array<{ tool: string; score: number; type: string }> = [];
let retrievalConfidence = 0; let retrievalConfidence = 0;
// Log retrieval start // PHASE 5: Check if embeddings are enabled via centralized configuration
if (embeddingsService.isEnabled()) { const embeddingsEnabled = unifiedConfig.isFeatureEnabled('embeddings');
if (embeddingsEnabled && embeddingsService.isEnabled()) {
auditTrailService.logRetrievalStart('embeddings'); auditTrailService.logRetrievalStart('embeddings');
const similarItems = await embeddingsService.findSimilar( const similarItems = await embeddingsService.findSimilar(
@ -263,7 +318,10 @@ class EnhancedMicroTaskAIPipeline {
console.log(`[ENHANCED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`); 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)); candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name));
candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name)); candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name));
selectionMethod = 'embeddings_candidates'; selectionMethod = 'embeddings_candidates';
@ -272,7 +330,7 @@ class EnhancedMicroTaskAIPipeline {
console.log(`[ENHANCED PIPELINE] Using embeddings candidates: ${candidateTools.length} tools`); console.log(`[ENHANCED PIPELINE] Using embeddings candidates: ${candidateTools.length} tools`);
} else { } 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'); auditTrailService.logRetrievalStart('ai_selector');
candidateTools = toolsData.tools; candidateTools = toolsData.tools;
candidateConcepts = toolsData.concepts; candidateConcepts = toolsData.concepts;
@ -321,12 +379,13 @@ class EnhancedMicroTaskAIPipeline {
const startTime = Date.now(); const startTime = Date.now();
const initialCandidates = candidateTools.map(tool => tool.name); const initialCandidates = candidateTools.map(tool => tool.name);
// Log selection start - use strategic model for tool selection // PHASE 5: Use centralized configuration for model selection
auditTrailService.logSelectionStart('strategic', initialCandidates); const modelType = unifiedConfig.getOptimalModelForTask('tool_selection');
auditTrailService.logSelectionStart(modelType, initialCandidates);
const modeInstruction = mode === 'workflow' 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 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 3-8 tools that are most relevant and effective.'; : `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) => ({ const toolsWithFullData = candidateTools.map((tool: any) => ({
name: tool.name, name: tool.name,
@ -357,7 +416,16 @@ class EnhancedMicroTaskAIPipeline {
related_software: concept.related_software || [] 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. 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} SELECTION METHOD: ${selectionMethod}
@ -370,7 +438,7 @@ class EnhancedMicroTaskAIPipeline {
USER QUERY: "${userQuery}" USER QUERY: "${userQuery}"
CRITICAL SELECTION PRINCIPLES: 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**: 2. **SCENARIO-SPECIFIC LOGIC**:
- "Rapid/Quick/Urgent/Triage" scenarios Prioritize METHODS and rapid response approaches over complex software - "Rapid/Quick/Urgent/Triage" scenarios Prioritize METHODS and rapid response approaches over complex software
@ -419,7 +487,7 @@ class EnhancedMicroTaskAIPipeline {
}`; }`;
try { 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); const result = this.safeParseJSON(aiResult.content, null);
@ -434,6 +502,14 @@ class EnhancedMicroTaskAIPipeline {
throw new Error('AI selection returned empty selection'); 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 selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
console.log(`[ENHANCED PIPELINE] AI reasoning: ${result.reasoning}`); console.log(`[ENHANCED PIPELINE] AI reasoning: ${result.reasoning}`);
console.log(`[ENHANCED PIPELINE] AI bias considerations: ${result.biasConsiderations || 'Not specified'}`); console.log(`[ENHANCED PIPELINE] AI bias considerations: ${result.biasConsiderations || 'Not specified'}`);
@ -455,9 +531,11 @@ class EnhancedMicroTaskAIPipeline {
rawResponse: aiResult.content 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...'); console.log('[ENHANCED PIPELINE] Running comprehensive bias analysis...');
auditTrailService.logBiasAnalysis(selectedTools, candidateTools, userQuery, mode); auditTrailService.logBiasAnalysis(selectedTools, candidateTools, userQuery, mode);
}
// Log domain confidence analysis // Log domain confidence analysis
auditTrailService.logDomainAnalysis(selectedTools, selectedConcepts); auditTrailService.logDomainAnalysis(selectedTools, selectedConcepts);
@ -512,7 +590,11 @@ class EnhancedMicroTaskAIPipeline {
}).filter(item => item.score > 0) }).filter(item => item.score > 0)
.sort((a, b) => b.score - a.score); .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); 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(', ')}`); 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> { private async delay(ms: number): Promise<void> {
@ -560,8 +642,8 @@ class EnhancedMicroTaskAIPipeline {
} }
try { try {
// Use tactical model for micro-tasks (faster, cheaper) // PHASE 5: Use centralized configuration for model selection
const modelType = forensicConfig.getModelForTask(taskType as any); const modelType = unifiedConfig.getOptimalModelForTask(taskType);
const aiResult = await this.callAIWithModel(contextPrompt, modelType, taskType, maxTokens); const aiResult = await this.callAIWithModel(contextPrompt, modelType, taskType, maxTokens);
const result: MicroTaskResult = { 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> { private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> {
const isWorkflow = context.mode === 'workflow'; 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> { 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 completedTasks = 0;
let failedTasks = 0; let failedTasks = 0;
// Update bias detection baseline periodically // PHASE 5: Update bias detection baseline using centralized configuration
this.updateBiasBaseline(); 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 // Start audit trail
const auditId = auditTrailService.startAudit(userId, userQuery, mode as 'workflow' | 'tool'); 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 // Log query classification
auditTrailService.logQueryClassification({ 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`); 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 // Task 1: Scenario/Problem Analysis
const analysisResult = await this.analyzeScenario(context); const analysisResult = await this.analyzeScenario(context);
if (analysisResult.success) completedTasks++; else failedTasks++; if (analysisResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay); await this.delay(this.microTaskDelay);
// ... (Additional micro-tasks would be implemented here)
// Build final recommendation (simplified for this example) // Build final recommendation (simplified for this example)
const recommendation = this.buildRecommendation(context, mode, "Workflow-Empfehlung"); 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 auditTrail = auditTrailService.finalizeAudit(finalRecommendationCount);
const processingStats = { const processingStats = {
embeddingsUsed: embeddingsService.isEnabled(), embeddingsUsed: unifiedConfig.isFeatureEnabled('embeddings') && embeddingsService.isEnabled(),
candidatesFromEmbeddings: filteredData.tools.length, candidatesFromEmbeddings: filteredData.tools.length,
finalSelectedItems: finalRecommendationCount, finalSelectedItems: finalRecommendationCount,
processingTimeMs: Date.now() - startTime, 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 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; let confidenceMetrics: ConfidenceMetrics | undefined;
if (auditTrail && this.config.features.confidenceScoring) { if (auditTrail && unifiedConfig.isFeatureEnabled('confidenceScoring')) {
confidenceMetrics = { confidenceMetrics = {
overall: auditTrail.qualityMetrics.overallConfidence, overall: auditTrail.qualityMetrics.overallConfidence,
breakdown: auditTrail.qualityMetrics.confidenceBreakdown, 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] Completed: ${completedTasks} tasks, Failed: ${failedTasks} tasks`);
console.log(`[ENHANCED PIPELINE] Unique tools selected: ${context.seenToolNames.size}`); console.log(`[ENHANCED PIPELINE] Unique tools selected: ${context.seenToolNames.size}`);
console.log(`[ENHANCED PIPELINE] Configuration health: ${configHealth.healthy ? 'Healthy' : 'Degraded'}`);
if (auditTrail) { if (auditTrail) {
console.log(`[ENHANCED PIPELINE] Audit Trail: ${auditTrail.auditId}`); 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, biasRiskScore: auditTrail.qualityMetrics.biasRiskScore,
transparencyScore: auditTrail.qualityMetrics.transparencyScore transparencyScore: auditTrail.qualityMetrics.transparencyScore
} : undefined, } : undefined,
confidenceMetrics // NEW: Return detailed confidence metrics confidenceMetrics
}; };
} catch (error) { } 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]') .replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
.trim(); .trim();
sanitized = sanitized.slice(0, 2000).replace(/\s+/g, ' '); sanitized = sanitized.slice(0, FORENSIC_CONSTANTS.MAX_QUERY_LENGTH).replace(/\s+/g, ' ');
return sanitized; return sanitized;
} }

View 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;

View File

@ -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 { promises as fs } from 'fs';
import path from 'path'; import path from 'path';
import { getCompressedToolsDataForAI } from './dataService.js'; import { getCompressedToolsDataForAI } from './dataService.js';
import { unifiedConfig } from './configIntegration.js';
interface EmbeddingData { interface EmbeddingData {
id: string; id: string;
@ -32,24 +34,60 @@ class EmbeddingsService {
private embeddings: EmbeddingData[] = []; private embeddings: EmbeddingData[] = [];
private isInitialized = false; private isInitialized = false;
private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json'); 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 batchSize: number;
private readonly batchDelay: number; private readonly batchDelay: number;
private readonly enabled: boolean; private readonly endpoint: string;
private readonly apiKey: string;
private readonly model: string;
constructor() { constructor() {
this.enabled = process.env.AI_EMBEDDINGS_ENABLED === 'true'; // PHASE 5: Load all configuration from centralized config manager
this.batchSize = parseInt(process.env.AI_EMBEDDINGS_BATCH_SIZE || '20', 10); const embeddingsConfig = unifiedConfig.getComponentConfig('embeddings');
this.batchDelay = parseInt(process.env.AI_EMBEDDINGS_BATCH_DELAY_MS || '1000', 10);
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> { async initialize(): Promise<void> {
if (!this.enabled) { 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; return;
} }
try { try {
console.log('[EMBEDDINGS] Initializing embeddings system...'); console.log('[EMBEDDINGS] Initializing embeddings system with centralized configuration...');
// Create data directory if it doesn't exist // Create data directory if it doesn't exist
await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true }); await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true });
@ -64,12 +102,12 @@ class EmbeddingsService {
console.log('[EMBEDDINGS] Using cached embeddings'); console.log('[EMBEDDINGS] Using cached embeddings');
this.embeddings = existingEmbeddings.embeddings; this.embeddings = existingEmbeddings.embeddings;
} else { } else {
console.log('[EMBEDDINGS] Generating new embeddings...'); console.log('[EMBEDDINGS] Generating new embeddings with configured parameters...');
await this.generateEmbeddings(toolsData, currentDataHash); await this.generateEmbeddings(toolsData, currentDataHash);
} }
this.isInitialized = true; 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) { } catch (error) {
console.error('[EMBEDDINGS] Failed to initialize:', error); console.error('[EMBEDDINGS] Failed to initialize:', error);
@ -115,22 +153,19 @@ class EmbeddingsService {
} }
private async generateEmbeddingsBatch(contents: string[]): Promise<number[][]> { private async generateEmbeddingsBatch(contents: string[]): Promise<number[][]> {
const endpoint = process.env.AI_EMBEDDINGS_ENDPOINT; // PHASE 5: Use centralized configuration for API parameters
const apiKey = process.env.AI_EMBEDDINGS_API_KEY; if (!this.endpoint || !this.apiKey || !this.model) {
const model = process.env.AI_EMBEDDINGS_MODEL; throw new Error('Missing embeddings API configuration from centralized config');
if (!endpoint || !apiKey || !model) {
throw new Error('Missing embeddings API configuration');
} }
const response = await fetch(endpoint, { const response = await fetch(this.endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}` 'Authorization': `Bearer ${this.apiKey}`
}, },
body: JSON.stringify({ body: JSON.stringify({
model, model: this.model,
input: contents input: contents
}) })
}); });
@ -153,12 +188,17 @@ class EmbeddingsService {
const contents = allItems.map(item => this.createContentString(item)); const contents = allItems.map(item => this.createContentString(item));
this.embeddings = []; 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) { for (let i = 0; i < contents.length; i += this.batchSize) {
const batch = contents.slice(i, i + this.batchSize); const batch = contents.slice(i, i + this.batchSize);
const batchItems = allItems.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 { try {
const embeddings = await this.generateEmbeddingsBatch(batch); 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) { 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)); await new Promise(resolve => setTimeout(resolve, this.batchDelay));
} }
} catch (error) { } 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; throw error;
} }
} }
await this.saveEmbeddings(version); await this.saveEmbeddings(version);
console.log(`[EMBEDDINGS] Generation complete using centralized configuration`);
} }
public async embedText(text: string): Promise<number[]> { public async embedText(text: string): Promise<number[]> {
// Reuse 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()]); const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]);
return embedding; return embedding;
} }
@ -217,6 +259,7 @@ class EmbeddingsService {
async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise<EmbeddingSearchResult[]> { async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise<EmbeddingSearchResult[]> {
if (!this.enabled || !this.isInitialized || this.embeddings.length === 0) { 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 []; return [];
} }
@ -229,11 +272,14 @@ class EmbeddingsService {
similarity: this.cosineSimilarity(queryEmbedding, item.embedding) similarity: this.cosineSimilarity(queryEmbedding, item.embedding)
})); }));
return similarities const results = similarities
.filter(item => item.similarity >= threshold) .filter(item => item.similarity >= threshold)
.sort((a, b) => b.similarity - a.similarity) .sort((a, b) => b.similarity - a.similarity)
.slice(0, maxResults); .slice(0, maxResults);
console.log(`[EMBEDDINGS] Found ${results.length} similar items above threshold ${threshold} (max: ${maxResults})`);
return results;
} catch (error) { } catch (error) {
console.error('[EMBEDDINGS] Failed to find similar items:', error); console.error('[EMBEDDINGS] Failed to find similar items:', error);
return []; return [];
@ -244,23 +290,114 @@ class EmbeddingsService {
return this.enabled && this.isInitialized; 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 { return {
enabled: this.enabled, enabled: this.enabled,
initialized: this.isInitialized, 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(); const embeddingsService = new EmbeddingsService();
export { embeddingsService, type EmbeddingData, type EmbeddingSearchResult }; export { embeddingsService, type EmbeddingData, type EmbeddingSearchResult };
// PHASE 5: Auto-initialization with centralized configuration validation
if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') { 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 => { embeddingsService.initialize().catch(error => {
console.error('[EMBEDDINGS] Auto-initialization failed:', error); console.error('[EMBEDDINGS] Auto-initialization failed:', error);
}); });
} else {
console.warn('[EMBEDDINGS] Skipping auto-initialization due to configuration health issues:', configHealth.errors);
}
} }

View 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;
}
}