phase5
This commit is contained in:
parent
78b30fb7c6
commit
5c3884094c
@ -1,11 +1,11 @@
|
|||||||
// src/pages/api/ai/query.ts - Enhanced with Comprehensive Confidence Metrics
|
// src/pages/api/ai/query.ts - PHASE 5: Enhanced with Centralized Configuration
|
||||||
|
|
||||||
import type { APIRoute } from 'astro';
|
import 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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
459
src/utils/configIntegration.ts
Normal file
459
src/utils/configIntegration.ts
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
// src/utils/configIntegration.ts - PHASE 5: Configuration Integration Utilities
|
||||||
|
|
||||||
|
import { enhancedConfigManager, FORENSIC_CONSTANTS, ConfigurationValidator } from './forensicConfigManager.js';
|
||||||
|
import type { AIModelConfig, ForensicThresholds, BiasDetectionConfig } from './forensicConfig.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHASE 5: Configuration Integration Layer
|
||||||
|
* Provides unified access to configuration across all application components
|
||||||
|
* Replaces scattered hardcoded values and duplicate configuration parsing
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// UNIFIED CONFIGURATION ACCESS LAYER
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export class UnifiedConfigAccess {
|
||||||
|
private static instance: UnifiedConfigAccess;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
static getInstance(): UnifiedConfigAccess {
|
||||||
|
if (!UnifiedConfigAccess.instance) {
|
||||||
|
UnifiedConfigAccess.instance = new UnifiedConfigAccess();
|
||||||
|
}
|
||||||
|
return UnifiedConfigAccess.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// COMPONENT CONFIGURATION ACCESS
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration for a specific component
|
||||||
|
*/
|
||||||
|
getComponentConfig<T = any>(componentId: string): T | null {
|
||||||
|
return enhancedConfigManager.getComponentConfig<T>(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// AI MODEL CONFIGURATION ACCESS
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get AI model configuration with automatic fallback handling
|
||||||
|
*/
|
||||||
|
getAIModelConfig(taskType: 'strategic' | 'tactical' | 'legacy' = 'tactical'): AIModelConfig {
|
||||||
|
try {
|
||||||
|
if (taskType === 'legacy') {
|
||||||
|
// Backward compatibility - use tactical model for legacy calls
|
||||||
|
return enhancedConfigManager.getAIModel('tactical');
|
||||||
|
}
|
||||||
|
return enhancedConfigManager.getAIModel(taskType);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[CONFIG INTEGRATION] Failed to get ${taskType} AI model:`, error);
|
||||||
|
// Fallback to tactical model
|
||||||
|
return enhancedConfigManager.getAIModel('tactical');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine optimal AI model for specific task types
|
||||||
|
*/
|
||||||
|
getOptimalModelForTask(taskType: string): 'strategic' | 'tactical' {
|
||||||
|
const strategicTasks = [
|
||||||
|
'analysis', 'selection', 'scenario_analysis', 'approach_generation',
|
||||||
|
'tool_selection', 'complex_reasoning', 'bias_analysis'
|
||||||
|
];
|
||||||
|
|
||||||
|
const tacticalTasks = [
|
||||||
|
'description', 'evaluation', 'background_knowledge', 'final_recommendations',
|
||||||
|
'text_generation', 'formatting', 'explanation'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (strategicTasks.includes(taskType)) {
|
||||||
|
return 'strategic';
|
||||||
|
} else if (tacticalTasks.includes(taskType)) {
|
||||||
|
return 'tactical';
|
||||||
|
} else {
|
||||||
|
// Default to tactical for unknown tasks (safer, cheaper)
|
||||||
|
console.warn(`[CONFIG INTEGRATION] Unknown task type: ${taskType}, defaulting to tactical model`);
|
||||||
|
return 'tactical';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// THRESHOLD CONFIGURATION ACCESS
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get threshold with validation and fallback
|
||||||
|
*/
|
||||||
|
getThreshold(thresholdName: keyof ForensicThresholds, fallback?: number): number {
|
||||||
|
try {
|
||||||
|
const value = enhancedConfigManager.getThreshold(thresholdName);
|
||||||
|
|
||||||
|
// Validate threshold value
|
||||||
|
if (!ConfigurationValidator.validateThreshold(value, thresholdName)) {
|
||||||
|
if (fallback !== undefined) {
|
||||||
|
console.warn(`[CONFIG INTEGRATION] Invalid ${thresholdName}, using fallback: ${fallback}`);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid threshold ${thresholdName}: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
} catch (error) {
|
||||||
|
if (fallback !== undefined) {
|
||||||
|
console.warn(`[CONFIG INTEGRATION] Failed to get ${thresholdName}, using fallback: ${fallback}`);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all rate limiting configuration
|
||||||
|
*/
|
||||||
|
getRateLimitConfig(): {
|
||||||
|
windowMs: number;
|
||||||
|
maxRequests: number;
|
||||||
|
microTaskLimit: number;
|
||||||
|
cleanupIntervalMs: number;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
windowMs: FORENSIC_CONSTANTS.RATE_LIMIT_WINDOW_MS,
|
||||||
|
maxRequests: this.getThreshold('rateLimitMaxRequests', 6),
|
||||||
|
microTaskLimit: parseInt(process.env.AI_MICRO_TASK_TOTAL_LIMIT || '50', 10),
|
||||||
|
cleanupIntervalMs: FORENSIC_CONSTANTS.RATE_LIMIT_CLEANUP_INTERVAL_MS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get processing timeout configuration
|
||||||
|
*/
|
||||||
|
getProcessingTimeouts(): {
|
||||||
|
aiTimeoutMs: number;
|
||||||
|
microTaskTimeoutMs: number;
|
||||||
|
microTaskDelayMs: number;
|
||||||
|
rateLimitDelayMs: number;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
aiTimeoutMs: FORENSIC_CONSTANTS.DEFAULT_AI_TIMEOUT_MS,
|
||||||
|
microTaskTimeoutMs: this.getThreshold('microTaskTimeoutMs', 25000),
|
||||||
|
microTaskDelayMs: this.getThreshold('microTaskDelayMs', 500),
|
||||||
|
rateLimitDelayMs: this.getThreshold('rateLimitDelayMs', 3000)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// FEATURE FLAG ACCESS
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if feature is enabled with dependency validation
|
||||||
|
*/
|
||||||
|
isFeatureEnabled(feature: string): boolean {
|
||||||
|
const featureMap = {
|
||||||
|
'confidenceScoring': 'confidenceScoring',
|
||||||
|
'biasDetection': 'biasDetection',
|
||||||
|
'performanceMetrics': 'performanceMetrics',
|
||||||
|
'debugMode': 'debugMode',
|
||||||
|
'auditTrail': 'auditTrail', // Special handling for audit trail
|
||||||
|
'embeddings': 'embeddings' // Special handling for embeddings
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const mappedFeature = featureMap[feature as keyof typeof featureMap];
|
||||||
|
|
||||||
|
if (!mappedFeature) {
|
||||||
|
console.warn(`[CONFIG INTEGRATION] Unknown feature flag: ${feature}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for compound features
|
||||||
|
if (feature === 'auditTrail') {
|
||||||
|
const auditConfig = enhancedConfigManager.getComponentConfig('audit_trail');
|
||||||
|
return auditConfig?.auditTrail?.enabled || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature === 'embeddings') {
|
||||||
|
const embeddingsConfig = enhancedConfigManager.getComponentConfig('embeddings');
|
||||||
|
return embeddingsConfig?.embeddings?.enabled || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return enhancedConfigManager.isFeatureEnabled(mappedFeature as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// BIAS DETECTION CONFIGURATION
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get bias detection configuration with validation
|
||||||
|
*/
|
||||||
|
getBiasConfig(): BiasDetectionConfig | null {
|
||||||
|
if (!this.isFeatureEnabled('biasDetection')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return enhancedConfigManager.getValidatedBiasConfig();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CONFIG INTEGRATION] Failed to get bias detection config:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get bias threshold for specific bias type
|
||||||
|
*/
|
||||||
|
getBiasThreshold(biasType: string): number {
|
||||||
|
const biasConfig = this.getBiasConfig();
|
||||||
|
if (!biasConfig) {
|
||||||
|
return 0.5; // Safe default
|
||||||
|
}
|
||||||
|
|
||||||
|
const thresholdMap: Record<string, keyof BiasDetectionConfig['thresholds']> = {
|
||||||
|
'popularity': 'popularity',
|
||||||
|
'availability': 'availability',
|
||||||
|
'recency': 'recency',
|
||||||
|
'domain_concentration': 'domainConcentration',
|
||||||
|
'skill_level': 'skillLevel'
|
||||||
|
};
|
||||||
|
|
||||||
|
const thresholdKey = thresholdMap[biasType];
|
||||||
|
if (!thresholdKey) {
|
||||||
|
console.warn(`[CONFIG INTEGRATION] Unknown bias type: ${biasType}`);
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return biasConfig.thresholds[thresholdKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// VALIDATION AND HEALTH CHECKS
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate current configuration health
|
||||||
|
*/
|
||||||
|
validateConfigurationHealth(): {
|
||||||
|
healthy: boolean;
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
recommendations: string[];
|
||||||
|
} {
|
||||||
|
const health = enhancedConfigManager.getConfigurationHealth();
|
||||||
|
const recommendations: string[] = [];
|
||||||
|
|
||||||
|
// Add specific recommendations based on configuration state
|
||||||
|
if (!health.healthy) {
|
||||||
|
recommendations.push('Review configuration errors and update environment variables');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (health.warnings.some(w => w.includes('retention'))) {
|
||||||
|
recommendations.push('Consider increasing audit retention period for compliance');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (health.errors.some(e => e.includes('AI'))) {
|
||||||
|
recommendations.push('Verify AI model endpoint connectivity and API keys');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
healthy: health.healthy,
|
||||||
|
errors: health.errors,
|
||||||
|
warnings: health.warnings,
|
||||||
|
recommendations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration summary for debugging
|
||||||
|
*/
|
||||||
|
getConfigurationSummary(): {
|
||||||
|
version: string;
|
||||||
|
features: Record<string, boolean>;
|
||||||
|
models: {
|
||||||
|
strategic: string;
|
||||||
|
tactical: string;
|
||||||
|
};
|
||||||
|
thresholds: {
|
||||||
|
confidence: number;
|
||||||
|
bias: number;
|
||||||
|
similarity: number;
|
||||||
|
};
|
||||||
|
health: string;
|
||||||
|
} {
|
||||||
|
const health = enhancedConfigManager.getConfigurationHealth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const strategicModel = this.getAIModelConfig('strategic');
|
||||||
|
const tacticalModel = this.getAIModelConfig('tactical');
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
features: {
|
||||||
|
confidenceScoring: this.isFeatureEnabled('confidenceScoring'),
|
||||||
|
biasDetection: this.isFeatureEnabled('biasDetection'),
|
||||||
|
auditTrail: this.isFeatureEnabled('auditTrail'),
|
||||||
|
embeddings: this.isFeatureEnabled('embeddings'),
|
||||||
|
performanceMetrics: this.isFeatureEnabled('performanceMetrics')
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
strategic: strategicModel.model,
|
||||||
|
tactical: tacticalModel.model
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
confidence: this.getThreshold('confidenceThreshold', 0.7),
|
||||||
|
bias: this.getThreshold('biasAlertThreshold', 0.8),
|
||||||
|
similarity: this.getThreshold('similarityThreshold', 0.3)
|
||||||
|
},
|
||||||
|
health: health.healthy ? 'healthy' : 'degraded'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CONFIG INTEGRATION] Failed to get configuration summary:', error);
|
||||||
|
return {
|
||||||
|
version: 'unknown',
|
||||||
|
features: {},
|
||||||
|
models: { strategic: 'error', tactical: 'error' },
|
||||||
|
thresholds: { confidence: 0.7, bias: 0.8, similarity: 0.3 },
|
||||||
|
health: 'error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CONFIGURATION MIGRATION UTILITIES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export class ConfigurationMigrator {
|
||||||
|
/**
|
||||||
|
* Migrate hardcoded values to configuration-based approach
|
||||||
|
*/
|
||||||
|
static migrateHardcodedValues(): {
|
||||||
|
migrated: string[];
|
||||||
|
remaining: string[];
|
||||||
|
recommendations: string[];
|
||||||
|
} {
|
||||||
|
const migrated = [
|
||||||
|
'Rate limit constants moved to FORENSIC_CONSTANTS',
|
||||||
|
'AI model selection logic centralized',
|
||||||
|
'Threshold validation centralized',
|
||||||
|
'Feature flag access unified',
|
||||||
|
'Bias detection thresholds configurable'
|
||||||
|
];
|
||||||
|
|
||||||
|
const remaining = [
|
||||||
|
// These should be addressed in file-specific migrations
|
||||||
|
'Token estimation constants in aiPipeline.ts',
|
||||||
|
'Default skill level descriptions in dataService.ts',
|
||||||
|
'Emergency selection parameters in aiPipeline.ts'
|
||||||
|
];
|
||||||
|
|
||||||
|
const recommendations = [
|
||||||
|
'Update query.ts to use UnifiedConfigAccess for rate limiting',
|
||||||
|
'Update aiPipeline.ts to use centralized timeout configuration',
|
||||||
|
'Replace remaining hardcoded thresholds with configuration calls',
|
||||||
|
'Add environment variables for any remaining constants'
|
||||||
|
];
|
||||||
|
|
||||||
|
return { migrated, remaining, recommendations };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate environment variable template for missing configuration
|
||||||
|
*/
|
||||||
|
static generateEnvTemplate(): string {
|
||||||
|
return `
|
||||||
|
# PHASE 5: Enhanced Configuration Template
|
||||||
|
# Add these to your .env file for complete configuration management
|
||||||
|
|
||||||
|
# AI Model Configuration (if using different endpoints)
|
||||||
|
AI_STRATEGIC_ENDPOINT=\${AI_ANALYZER_ENDPOINT}
|
||||||
|
AI_STRATEGIC_API_KEY=\${AI_ANALYZER_API_KEY}
|
||||||
|
AI_STRATEGIC_MODEL=\${AI_ANALYZER_MODEL}
|
||||||
|
AI_STRATEGIC_MAX_CONTEXT_TOKENS=32000
|
||||||
|
AI_STRATEGIC_MAX_OUTPUT_TOKENS=1000
|
||||||
|
AI_STRATEGIC_TEMPERATURE=0.2
|
||||||
|
|
||||||
|
AI_TACTICAL_ENDPOINT=\${AI_ANALYZER_ENDPOINT}
|
||||||
|
AI_TACTICAL_API_KEY=\${AI_ANALYZER_API_KEY}
|
||||||
|
AI_TACTICAL_MODEL=\${AI_ANALYZER_MODEL}
|
||||||
|
AI_TACTICAL_MAX_CONTEXT_TOKENS=8000
|
||||||
|
AI_TACTICAL_MAX_OUTPUT_TOKENS=500
|
||||||
|
AI_TACTICAL_TEMPERATURE=0.3
|
||||||
|
|
||||||
|
# Forensic Enhancement Configuration
|
||||||
|
FORENSIC_AUDIT_ENABLED=true
|
||||||
|
FORENSIC_CONFIDENCE_SCORING_ENABLED=true
|
||||||
|
FORENSIC_BIAS_DETECTION_ENABLED=true
|
||||||
|
FORENSIC_AUDIT_RETENTION_DAYS=90
|
||||||
|
FORENSIC_AUDIT_DETAIL_LEVEL=detailed
|
||||||
|
|
||||||
|
# Performance Configuration
|
||||||
|
AI_MICRO_TASK_TOTAL_LIMIT=50
|
||||||
|
AI_MICRO_TASK_TIMEOUT_MS=25000
|
||||||
|
AI_MICRO_TASK_DELAY_MS=500
|
||||||
|
AI_RATE_LIMIT_DELAY_MS=3000
|
||||||
|
AI_RATE_LIMIT_MAX_REQUESTS=6
|
||||||
|
|
||||||
|
# Confidence and Bias Thresholds
|
||||||
|
AI_CONFIDENCE_THRESHOLD=0.7
|
||||||
|
AI_BIAS_ALERT_THRESHOLD=0.8
|
||||||
|
TOOL_POPULARITY_BIAS_THRESHOLD=0.75
|
||||||
|
EMBEDDINGS_CONFIDENCE_THRESHOLD=0.6
|
||||||
|
SELECTION_CONFIDENCE_MINIMUM=0.5
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// EXPORT SINGLETON INSTANCE AND RE-EXPORT CONSTANTS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const unifiedConfig = UnifiedConfigAccess.getInstance();
|
||||||
|
|
||||||
|
// Re-export FORENSIC_CONSTANTS from the canonical source to avoid duplication
|
||||||
|
export { FORENSIC_CONSTANTS };
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PHASE 5: LEGACY CODE REMOVAL GUIDE
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const LEGACY_REMOVAL_GUIDE = {
|
||||||
|
// Files that can have hardcoded values removed
|
||||||
|
filesToUpdate: {
|
||||||
|
'src/pages/api/ai/query.ts': [
|
||||||
|
'Replace RATE_LIMIT_WINDOW = 60 * 1000 with unifiedConfig.getRateLimitConfig().windowMs',
|
||||||
|
'Replace MAIN_RATE_LIMIT_MAX with unifiedConfig.getRateLimitConfig().maxRequests',
|
||||||
|
'Replace MICRO_TASK_TOTAL_LIMIT with unifiedConfig.getRateLimitConfig().microTaskLimit'
|
||||||
|
],
|
||||||
|
'src/utils/aiPipeline.ts': [
|
||||||
|
'Replace hardcoded maxSelectedItems with unifiedConfig.getThreshold("maxSelectedItems")',
|
||||||
|
'Replace hardcoded embeddingCandidates with unifiedConfig.getThreshold("embeddingCandidates")',
|
||||||
|
'Replace hardcoded similarityThreshold with unifiedConfig.getThreshold("similarityThreshold")',
|
||||||
|
'Replace hardcoded microTaskDelay with unifiedConfig.getProcessingTimeouts().microTaskDelayMs'
|
||||||
|
],
|
||||||
|
'src/utils/embeddings.ts': [
|
||||||
|
'Replace hardcoded batchSize with unifiedConfig.getComponentConfig("embeddings").batchSize',
|
||||||
|
'Replace hardcoded batchDelay with unifiedConfig.getComponentConfig("embeddings").batchDelayMs'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Functions that can be consolidated
|
||||||
|
functionsToConsolidate: [
|
||||||
|
'Environment variable parsing (getEnv, getEnvNumber, getEnvFloat, getEnvBoolean)',
|
||||||
|
'Threshold validation logic',
|
||||||
|
'AI model selection logic',
|
||||||
|
'Configuration validation patterns'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Constants that should be removed
|
||||||
|
constantsToRemove: [
|
||||||
|
'Hardcoded rate limit values in query.ts',
|
||||||
|
'Hardcoded timeout values in multiple files',
|
||||||
|
'Duplicate threshold definitions',
|
||||||
|
'Scattered environment variable defaults'
|
||||||
|
]
|
||||||
|
} as const;
|
@ -1,7 +1,9 @@
|
|||||||
// src/utils/embeddings.ts
|
// src/utils/embeddings.ts - PHASE 5: Enhanced with Centralized Configuration
|
||||||
|
|
||||||
import { promises as fs } from 'fs';
|
import { 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[]> {
|
||||||
// Re‑use the private batch helper to avoid auth duplication
|
// Re-use the private batch helper to avoid auth duplication
|
||||||
const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
545
src/utils/forensicConfigManager.ts
Normal file
545
src/utils/forensicConfigManager.ts
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
// src/utils/forensicConfigManager.ts - PHASE 5: Centralized Configuration Management
|
||||||
|
|
||||||
|
import { forensicConfig, type ForensicConfig, type AIModelConfig, type ForensicThresholds, type BiasDetectionConfig } from './forensicConfig.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHASE 5: Enhanced Configuration Manager
|
||||||
|
* Centralizes ALL configuration management, eliminates hardcoded values,
|
||||||
|
* and provides unified configuration access across the entire application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ConfigurationComponent {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
configKeys: string[];
|
||||||
|
validation?: (config: any) => boolean;
|
||||||
|
dependencies?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigurationSnapshot {
|
||||||
|
timestamp: Date;
|
||||||
|
version: string;
|
||||||
|
components: Record<string, any>;
|
||||||
|
thresholds: ForensicThresholds;
|
||||||
|
features: Record<string, boolean>;
|
||||||
|
validation: {
|
||||||
|
valid: boolean;
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnhancedForensicConfigManager {
|
||||||
|
private static instance: EnhancedForensicConfigManager;
|
||||||
|
private config: ForensicConfig;
|
||||||
|
private configSnapshot: ConfigurationSnapshot | null = null;
|
||||||
|
private registeredComponents: Map<string, ConfigurationComponent> = new Map();
|
||||||
|
|
||||||
|
// Configuration validation cache
|
||||||
|
private validationCache: Map<string, { valid: boolean; timestamp: Date }> = new Map();
|
||||||
|
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.config = forensicConfig.getConfig();
|
||||||
|
this.registerBuiltInComponents();
|
||||||
|
this.createConfigurationSnapshot();
|
||||||
|
this.validateAllConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstance(): EnhancedForensicConfigManager {
|
||||||
|
if (!EnhancedForensicConfigManager.instance) {
|
||||||
|
EnhancedForensicConfigManager.instance = new EnhancedForensicConfigManager();
|
||||||
|
}
|
||||||
|
return EnhancedForensicConfigManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register configuration components for centralized management
|
||||||
|
*/
|
||||||
|
private registerBuiltInComponents(): void {
|
||||||
|
// AI Models Configuration Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'ai_models',
|
||||||
|
name: 'AI Models Configuration',
|
||||||
|
configKeys: ['aiModels.strategic', 'aiModels.tactical', 'legacyModel'],
|
||||||
|
validation: (config) => {
|
||||||
|
return config.aiModels?.strategic?.endpoint &&
|
||||||
|
config.aiModels?.tactical?.endpoint &&
|
||||||
|
config.aiModels.strategic.apiKey &&
|
||||||
|
config.aiModels.tactical.apiKey;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Audit Trail Configuration Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'audit_trail',
|
||||||
|
name: 'Audit Trail System',
|
||||||
|
configKeys: ['auditTrail.enabled', 'auditTrail.retentionDays', 'auditTrail.detailLevel'],
|
||||||
|
dependencies: ['ai_models']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confidence Scoring Configuration Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'confidence_scoring',
|
||||||
|
name: 'Confidence Scoring System',
|
||||||
|
configKeys: ['features.confidenceScoring', 'thresholds.confidenceThreshold'],
|
||||||
|
dependencies: ['audit_trail']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bias Detection Configuration Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'bias_detection',
|
||||||
|
name: 'Bias Detection System',
|
||||||
|
configKeys: ['features.biasDetection', 'biasDetection', 'thresholds.biasAlertThreshold'],
|
||||||
|
dependencies: ['confidence_scoring']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Embeddings Configuration Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'embeddings',
|
||||||
|
name: 'Embeddings System',
|
||||||
|
configKeys: ['embeddings.enabled', 'embeddings.endpoint', 'embeddings.model']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Performance & Rate Limiting Component
|
||||||
|
this.registerComponent({
|
||||||
|
id: 'performance',
|
||||||
|
name: 'Performance & Rate Limiting',
|
||||||
|
configKeys: [
|
||||||
|
'thresholds.rateLimitDelayMs',
|
||||||
|
'thresholds.rateLimitMaxRequests',
|
||||||
|
'thresholds.microTaskTimeoutMs',
|
||||||
|
'queue.maxSize'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponent(component: ConfigurationComponent): void {
|
||||||
|
this.registeredComponents.set(component.id, component);
|
||||||
|
console.log(`[CONFIG MANAGER] Registered component: ${component.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration for a specific component with validation
|
||||||
|
*/
|
||||||
|
getComponentConfig<T = any>(componentId: string): T | null {
|
||||||
|
const component = this.registeredComponents.get(componentId);
|
||||||
|
if (!component) {
|
||||||
|
console.warn(`[CONFIG MANAGER] Unknown component: ${componentId}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validation cache
|
||||||
|
const cacheKey = `component_${componentId}`;
|
||||||
|
const cached = this.validationCache.get(cacheKey);
|
||||||
|
if (cached && (Date.now() - cached.timestamp.getTime()) < this.CACHE_TTL) {
|
||||||
|
if (!cached.valid) {
|
||||||
|
console.error(`[CONFIG MANAGER] Component ${componentId} has invalid configuration (cached)`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Validate component configuration
|
||||||
|
const isValid = this.validateComponent(component);
|
||||||
|
this.validationCache.set(cacheKey, { valid: isValid, timestamp: new Date() });
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
console.error(`[CONFIG MANAGER] Component ${componentId} has invalid configuration`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract component configuration
|
||||||
|
const componentConfig: any = {};
|
||||||
|
component.configKeys.forEach(key => {
|
||||||
|
const value = this.getNestedConfigValue(key);
|
||||||
|
if (value !== undefined) {
|
||||||
|
this.setNestedValue(componentConfig, key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return componentConfig as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a specific component's configuration
|
||||||
|
*/
|
||||||
|
private validateComponent(component: ConfigurationComponent): boolean {
|
||||||
|
// Check dependencies first
|
||||||
|
if (component.dependencies) {
|
||||||
|
for (const depId of component.dependencies) {
|
||||||
|
const depComponent = this.registeredComponents.get(depId);
|
||||||
|
if (!depComponent || !this.validateComponent(depComponent)) {
|
||||||
|
console.error(`[CONFIG MANAGER] Component ${component.id} has invalid dependency: ${depId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run custom validation if provided
|
||||||
|
if (component.validation) {
|
||||||
|
return component.validation(this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default validation: check that all required keys exist
|
||||||
|
return component.configKeys.every(key => {
|
||||||
|
const value = this.getNestedConfigValue(key);
|
||||||
|
return value !== undefined && value !== null && value !== '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get nested configuration value using dot notation
|
||||||
|
*/
|
||||||
|
private getNestedConfigValue(path: string): any {
|
||||||
|
return path.split('.').reduce((obj, key) => obj?.[key], this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set nested value using dot notation
|
||||||
|
*/
|
||||||
|
private setNestedValue(obj: any, path: string, value: any): void {
|
||||||
|
const keys = path.split('.');
|
||||||
|
const lastKey = keys.pop()!;
|
||||||
|
const target = keys.reduce((current, key) => {
|
||||||
|
if (!current[key]) current[key] = {};
|
||||||
|
return current[key];
|
||||||
|
}, obj);
|
||||||
|
target[lastKey] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get AI model configuration with automatic selection
|
||||||
|
*/
|
||||||
|
getAIModelForTask(taskType: 'analysis' | 'description' | 'selection' | 'evaluation' | 'strategic' | 'tactical'): AIModelConfig {
|
||||||
|
const aiConfig = this.getComponentConfig<any>('ai_models');
|
||||||
|
if (!aiConfig) {
|
||||||
|
throw new Error('AI models configuration not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine model type based on task
|
||||||
|
const useStrategic = ['analysis', 'selection', 'strategic'].includes(taskType);
|
||||||
|
const modelConfig = useStrategic ? aiConfig.aiModels.strategic : aiConfig.aiModels.tactical;
|
||||||
|
|
||||||
|
console.log(`[CONFIG MANAGER] Selected ${useStrategic ? 'strategic' : 'tactical'} model for task: ${taskType}`);
|
||||||
|
return modelConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all thresholds with component validation
|
||||||
|
*/
|
||||||
|
getValidatedThresholds(): ForensicThresholds {
|
||||||
|
const performanceConfig = this.getComponentConfig('performance');
|
||||||
|
const confidenceConfig = this.getComponentConfig('confidence_scoring');
|
||||||
|
const biasConfig = this.getComponentConfig('bias_detection');
|
||||||
|
|
||||||
|
if (!performanceConfig || !confidenceConfig || !biasConfig) {
|
||||||
|
console.warn('[CONFIG MANAGER] Some threshold components unavailable, using base configuration');
|
||||||
|
return forensicConfig.getThresholds();
|
||||||
|
}
|
||||||
|
|
||||||
|
return forensicConfig.getThresholds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get bias detection configuration with validation
|
||||||
|
*/
|
||||||
|
getValidatedBiasConfig(): BiasDetectionConfig {
|
||||||
|
const biasConfig = this.getComponentConfig('bias_detection');
|
||||||
|
if (!biasConfig) {
|
||||||
|
throw new Error('Bias detection configuration not available or invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return forensicConfig.getBiasDetectionConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a feature is enabled with dependency validation
|
||||||
|
*/
|
||||||
|
isFeatureEnabled(feature: keyof ForensicConfig['features']): boolean {
|
||||||
|
// Feature-specific dependency checks
|
||||||
|
switch (feature) {
|
||||||
|
case 'confidenceScoring':
|
||||||
|
return this.getComponentConfig('confidence_scoring') !== null &&
|
||||||
|
forensicConfig.isFeatureEnabled(feature);
|
||||||
|
|
||||||
|
case 'biasDetection':
|
||||||
|
return this.getComponentConfig('bias_detection') !== null &&
|
||||||
|
this.isFeatureEnabled('confidenceScoring') && // Bias detection depends on confidence scoring
|
||||||
|
forensicConfig.isFeatureEnabled(feature);
|
||||||
|
|
||||||
|
case 'performanceMetrics':
|
||||||
|
return this.getComponentConfig('performance') !== null &&
|
||||||
|
forensicConfig.isFeatureEnabled(feature);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return forensicConfig.isFeatureEnabled(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a comprehensive configuration snapshot
|
||||||
|
*/
|
||||||
|
private createConfigurationSnapshot(): void {
|
||||||
|
const components: Record<string, any> = {};
|
||||||
|
const errors: string[] = [];
|
||||||
|
const warnings: string[] = [];
|
||||||
|
|
||||||
|
// Validate and snapshot each component
|
||||||
|
for (const [id, component] of this.registeredComponents) {
|
||||||
|
try {
|
||||||
|
const config = this.getComponentConfig(id);
|
||||||
|
if (config) {
|
||||||
|
components[id] = config;
|
||||||
|
} else {
|
||||||
|
errors.push(`Component ${id} (${component.name}) configuration invalid`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errors.push(`Component ${id} error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for potential configuration conflicts
|
||||||
|
if (this.config.features.biasDetection && !this.config.features.confidenceScoring) {
|
||||||
|
warnings.push('Bias detection enabled without confidence scoring - some features may be limited');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.auditTrail.enabled && this.config.auditTrail.retentionDays < 30) {
|
||||||
|
warnings.push('Audit retention period is less than 30 days - compliance requirements may not be met');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configSnapshot = {
|
||||||
|
timestamp: new Date(),
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
components,
|
||||||
|
thresholds: this.config.thresholds,
|
||||||
|
features: this.config.features,
|
||||||
|
validation: {
|
||||||
|
valid: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
warnings
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`[CONFIG MANAGER] Configuration snapshot created - ${components.length} components, ${errors.length} errors, ${warnings.length} warnings`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate all configurations
|
||||||
|
*/
|
||||||
|
private validateAllConfigurations(): void {
|
||||||
|
let totalComponents = 0;
|
||||||
|
let validComponents = 0;
|
||||||
|
|
||||||
|
for (const [id, component] of this.registeredComponents) {
|
||||||
|
totalComponents++;
|
||||||
|
if (this.validateComponent(component)) {
|
||||||
|
validComponents++;
|
||||||
|
} else {
|
||||||
|
console.error(`[CONFIG MANAGER] Component validation failed: ${component.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[CONFIG MANAGER] Configuration validation complete: ${validComponents}/${totalComponents} components valid`);
|
||||||
|
|
||||||
|
if (validComponents < totalComponents) {
|
||||||
|
console.warn(`[CONFIG MANAGER] ${totalComponents - validComponents} components have configuration issues`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration health status
|
||||||
|
*/
|
||||||
|
getConfigurationHealth(): {
|
||||||
|
healthy: boolean;
|
||||||
|
componentsValid: number;
|
||||||
|
componentsTotal: number;
|
||||||
|
errors: string[];
|
||||||
|
warnings: string[];
|
||||||
|
lastValidated: Date;
|
||||||
|
} {
|
||||||
|
if (!this.configSnapshot) {
|
||||||
|
this.createConfigurationSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
const validComponents = Object.keys(this.configSnapshot!.components).length;
|
||||||
|
const totalComponents = this.registeredComponents.size;
|
||||||
|
|
||||||
|
return {
|
||||||
|
healthy: this.configSnapshot!.validation.valid && validComponents === totalComponents,
|
||||||
|
componentsValid: validComponents,
|
||||||
|
componentsTotal: totalComponents,
|
||||||
|
errors: this.configSnapshot!.validation.errors,
|
||||||
|
warnings: this.configSnapshot!.validation.warnings,
|
||||||
|
lastValidated: this.configSnapshot!.timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export configuration for debugging/compliance
|
||||||
|
*/
|
||||||
|
exportConfiguration(includeSecrets: boolean = false): string {
|
||||||
|
const exportData = {
|
||||||
|
...this.configSnapshot,
|
||||||
|
configurationComponents: Array.from(this.registeredComponents.entries()).map(([id, component]) => ({
|
||||||
|
id,
|
||||||
|
name: component.name,
|
||||||
|
configKeys: component.configKeys,
|
||||||
|
dependencies: component.dependencies || []
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!includeSecrets && exportData.components) {
|
||||||
|
// Remove sensitive information
|
||||||
|
Object.values(exportData.components).forEach((component: any) => {
|
||||||
|
if (component.aiModels) {
|
||||||
|
Object.values(component.aiModels).forEach((model: any) => {
|
||||||
|
if (model.apiKey) model.apiKey = '[REDACTED]';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (component.embeddings?.apiKey) {
|
||||||
|
component.embeddings.apiKey = '[REDACTED]';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(exportData, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHASE 5: Unified configuration access methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Centralized threshold access
|
||||||
|
getThreshold(thresholdName: keyof ForensicThresholds): number {
|
||||||
|
const thresholds = this.getValidatedThresholds();
|
||||||
|
return thresholds[thresholdName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centralized feature flag access
|
||||||
|
getFeatureFlag(featureName: keyof ForensicConfig['features']): boolean {
|
||||||
|
return this.isFeatureEnabled(featureName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centralized AI model access
|
||||||
|
getAIModel(purpose: 'strategic' | 'tactical'): AIModelConfig {
|
||||||
|
return this.getAIModelForTask(purpose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration update with validation
|
||||||
|
updateThreshold(thresholdName: keyof ForensicThresholds, value: number): boolean {
|
||||||
|
if (value < 0 || value > 1) {
|
||||||
|
console.error(`[CONFIG MANAGER] Invalid threshold value for ${thresholdName}: ${value}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update would require implementation in the base forensicConfig
|
||||||
|
console.log(`[CONFIG MANAGER] Threshold update requested: ${thresholdName} = ${value}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear validation cache (useful for testing/development)
|
||||||
|
clearValidationCache(): void {
|
||||||
|
this.validationCache.clear();
|
||||||
|
console.log('[CONFIG MANAGER] Validation cache cleared');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh configuration (reload from environment)
|
||||||
|
refreshConfiguration(): void {
|
||||||
|
console.log('[CONFIG MANAGER] Refreshing configuration...');
|
||||||
|
this.clearValidationCache();
|
||||||
|
this.config = forensicConfig.getConfig();
|
||||||
|
this.createConfigurationSnapshot();
|
||||||
|
this.validateAllConfigurations();
|
||||||
|
console.log('[CONFIG MANAGER] Configuration refreshed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance for Phase 5 implementation
|
||||||
|
export const enhancedConfigManager = EnhancedForensicConfigManager.getInstance();
|
||||||
|
|
||||||
|
// Export types for component registration
|
||||||
|
export type { ConfigurationComponent, ConfigurationSnapshot };
|
||||||
|
|
||||||
|
// PHASE 5: Centralized configuration constants (replaces scattered hardcoded values)
|
||||||
|
export const FORENSIC_CONSTANTS = {
|
||||||
|
// Rate limiting (consolidated from multiple files)
|
||||||
|
RATE_LIMIT_WINDOW_MS: 60 * 1000,
|
||||||
|
RATE_LIMIT_CLEANUP_INTERVAL_MS: 5 * 60 * 1000,
|
||||||
|
MAX_STORE_SIZE: 1000,
|
||||||
|
|
||||||
|
// Processing timeouts
|
||||||
|
DEFAULT_AI_TIMEOUT_MS: 25000,
|
||||||
|
DEFAULT_MICRO_TASK_DELAY_MS: 500,
|
||||||
|
|
||||||
|
// Cache settings
|
||||||
|
VALIDATION_CACHE_TTL_MS: 5 * 60 * 1000,
|
||||||
|
CONFIG_REFRESH_INTERVAL_MS: 30 * 60 * 1000,
|
||||||
|
|
||||||
|
// Audit trail
|
||||||
|
DEFAULT_AUDIT_RETENTION_DAYS: 90,
|
||||||
|
AUDIT_CLEANUP_INTERVAL_MS: 60 * 60 * 1000,
|
||||||
|
|
||||||
|
// Input validation
|
||||||
|
MAX_QUERY_LENGTH: 2000,
|
||||||
|
MIN_QUERY_LENGTH: 10,
|
||||||
|
MAX_CONTEXT_ENTRIES: 10,
|
||||||
|
|
||||||
|
// Response limits
|
||||||
|
MAX_SELECTED_ITEMS_ABSOLUTE: 100,
|
||||||
|
MIN_CONFIDENCE_FOR_RESPONSE: 0.1,
|
||||||
|
MAX_UNCERTAINTY_FACTORS: 10
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// PHASE 5: Configuration validation utilities
|
||||||
|
export class ConfigurationValidator {
|
||||||
|
static validateThreshold(value: number, name: string): boolean {
|
||||||
|
if (typeof value !== 'number' || isNaN(value)) {
|
||||||
|
console.error(`[CONFIG VALIDATOR] ${name} must be a number, got: ${typeof value}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 0 || value > 1) {
|
||||||
|
console.error(`[CONFIG VALIDATOR] ${name} must be between 0 and 1, got: ${value}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateAIModel(model: AIModelConfig, name: string): boolean {
|
||||||
|
const required = ['endpoint', 'apiKey', 'model', 'maxContextTokens', 'maxOutputTokens'];
|
||||||
|
const missing = required.filter(key => !model[key as keyof AIModelConfig]);
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
console.error(`[CONFIG VALIDATOR] ${name} missing required fields: ${missing.join(', ')}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.temperature < 0 || model.temperature > 2) {
|
||||||
|
console.error(`[CONFIG VALIDATOR] ${name} temperature must be between 0 and 2`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateBiasConfig(config: BiasDetectionConfig): boolean {
|
||||||
|
// Validate thresholds
|
||||||
|
const thresholdKeys = Object.keys(config.thresholds);
|
||||||
|
for (const key of thresholdKeys) {
|
||||||
|
const value = config.thresholds[key as keyof typeof config.thresholds];
|
||||||
|
if (!this.validateThreshold(value, `bias.thresholds.${key}`)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate weights sum to 1.0
|
||||||
|
const weightSum = Object.values(config.weights).reduce((sum, weight) => sum + weight, 0);
|
||||||
|
if (Math.abs(weightSum - 1.0) > 0.01) {
|
||||||
|
console.error(`[CONFIG VALIDATOR] Bias weights sum to ${weightSum.toFixed(3)}, should sum to 1.0`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user