airefactor #19

Merged
mstoeck3 merged 25 commits from airefactor into main 2025-08-17 22:59:31 +00:00
8 changed files with 1392 additions and 231 deletions
Showing only changes of commit 77f09ed399 - Show all commits

View File

@ -217,7 +217,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
<script type="module" define:vars={{ tools, phases, domainAgnosticSoftware }}>
// Utility functions (inline to avoid import issues)
function showElement(element) {
if (element) {
element.style.display = 'block';
@ -908,40 +907,537 @@ class AIQueryInterface {
}
renderAuditTrail(rawAuditTrail) {
// Simplified audit trail rendering - remove complex processor
const container = document.getElementById('audit-trail-container');
if (!container || !rawAuditTrail || !Array.isArray(rawAuditTrail)) return;
if (!container || !rawAuditTrail || !Array.isArray(rawAuditTrail)) {
console.warn('[AI Interface] Cannot render audit trail: invalid container or data');
return;
}
const totalTime = rawAuditTrail.reduce((sum, entry) => sum + (entry.processingTimeMs || 0), 0);
const avgConfidence = rawAuditTrail.length > 0
? Math.round(rawAuditTrail.reduce((sum, entry) => sum + (entry.confidence || 0), 0) / rawAuditTrail.length)
: 0;
console.log('[AI Interface] Rendering comprehensive audit trail with', rawAuditTrail.length, 'entries');
container.innerHTML = `
<div class="audit-trail-container">
<div class="audit-trail-header">
<div class="audit-trail-title">
<div class="audit-icon">
<div class="audit-icon-gradient">✓</div>
<h4>KI-Entscheidungspfad</h4>
try {
// Calculate audit statistics
const stats = this.calculateAuditStats(rawAuditTrail);
container.innerHTML = `
<div class="audit-trail-container">
<div class="audit-trail-header clickable" onclick="this.parentElement.querySelector('.audit-trail-details').classList.toggle('collapsed'); this.querySelector('.toggle-icon').style.transform = this.parentElement.querySelector('.audit-trail-details').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(90deg)';">
<div class="audit-trail-title">
<div class="audit-icon">
<div class="audit-icon-gradient">🔍</div>
<h4>KI-Entscheidungspfad</h4>
</div>
<div class="audit-stats">
<div class="stat-item">
<div class="stat-dot stat-time"></div>
<span>${formatDuration(stats.totalTime)}</span>
</div>
<div class="stat-item">
<div class="stat-dot" style="background-color: ${getConfidenceColor(stats.avgConfidence)};"></div>
<span>${stats.avgConfidence}% Vertrauen</span>
</div>
<div class="stat-item">
<span>${rawAuditTrail.length} Schritte</span>
</div>
</div>
</div>
<div class="audit-stats">
<div class="stat-item">
<span>${formatDuration(totalTime)}</span>
<svg class="toggle-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="transform: rotate(90deg);">
<polyline points="9 18 15 12 9 6"/>
</svg>
</div>
<div class="audit-trail-details">
<!-- Audit Summary -->
<div class="audit-summary">
<div class="summary-header">📊 Analyse-Zusammenfassung</div>
<div class="summary-grid">
<div class="summary-stat">
<div class="summary-value success">${stats.aiDecisionCount}</div>
<div class="summary-label">KI-Entscheidungen</div>
</div>
<div class="summary-stat">
<div class="summary-value ${stats.embeddingsUsageCount > 0 ? 'success' : 'warning'}">${stats.embeddingsUsageCount}</div>
<div class="summary-label">Semantische Suchen</div>
</div>
<div class="summary-stat">
<div class="summary-value success">${stats.toolSelectionCount}</div>
<div class="summary-label">Tool-Auswahlen</div>
</div>
</div>
<div class="stat-item">
<span>${avgConfidence}% Vertrauen</span>
</div>
<div class="stat-item">
<span>${rawAuditTrail.length} Schritte</span>
<div class="insights-section">
${stats.qualityMetrics.aiTransparency >= 80 ? `
<div class="insights-header success">✅ Hohe Transparenz</div>
<ul class="insights-list">
<li>${stats.qualityMetrics.aiTransparency}% der Entscheidungen mit nachvollziehbarer Begründung</li>
<li>Durchschnittliche Verarbeitungszeit: ${formatDuration(stats.qualityMetrics.avgProcessingTime)}</li>
</ul>
` : `
<div class="insights-header warning">⚠️ Transparenz-Hinweise</div>
<ul class="insights-list">
<li>Nur ${stats.qualityMetrics.aiTransparency}% der Entscheidungen vollständig dokumentiert</li>
<li>Einige KI-Entscheidungen ohne detaillierte Begründung</li>
</ul>
`}
</div>
</div>
<!-- Process Flow -->
<div class="audit-process-flow">
${this.renderPhaseGroups(rawAuditTrail, stats)}
</div>
<!-- Download/Upload Controls -->
<div class="ai-results-controls" style="margin-top: 1.5rem; text-align: center; border-top: 1px solid var(--color-border); padding-top: 1rem;">
<button id="download-results-btn" class="btn btn-secondary" style="margin-right: 0.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
Analyse herunterladen
</button>
<label for="upload-results" class="btn btn-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
Analyse hochladen
<input type="file" id="upload-results" accept=".json" style="display: none;" />
</label>
</div>
<!-- Technical Details Toggle -->
<div class="technical-toggle">
<button class="technical-toggle-btn" onclick="this.parentElement.parentElement.querySelector('.technical-details').classList.toggle('collapsed'); this.textContent = this.parentElement.parentElement.querySelector('.technical-details').classList.contains('collapsed') ? 'Technische Details anzeigen' : 'Technische Details verbergen';">
Technische Details anzeigen
</button>
</div>
<!-- Technical Details -->
<div class="technical-details collapsed">
${this.renderTechnicalDetails(rawAuditTrail)}
</div>
</div>
</div>
`;
// Setup download functionality
this.setupDownloadUpload();
} catch (error) {
console.error('[AI Interface] Error rendering audit trail:', error);
container.innerHTML = `
<div class="audit-trail-container">
<div class="audit-trail-header">
<div class="audit-trail-title">
<div class="audit-icon">
<div class="audit-icon-gradient">⚠️</div>
<h4>Audit Trail Fehler</h4>
</div>
</div>
</div>
<div class="audit-trail-details">
<p style="color: var(--color-error); padding: 1rem;">
Fehler beim Rendern des Audit Trails. Details in der Konsole.
</p>
</div>
</div>
`;
}
}
calculateAuditStats(auditTrail) {
if (!auditTrail || auditTrail.length === 0) {
return {
totalTime: 0,
avgConfidence: 0,
stepCount: 0,
aiDecisionCount: 0,
embeddingsUsageCount: 0,
toolSelectionCount: 0,
qualityMetrics: {
avgProcessingTime: 0,
aiTransparency: 0
}
};
}
const totalTime = auditTrail.reduce((sum, entry) => sum + (entry.processingTimeMs || 0), 0);
const validConfidenceEntries = auditTrail.filter(entry => typeof entry.confidence === 'number');
const avgConfidence = validConfidenceEntries.length > 0
? Math.round(validConfidenceEntries.reduce((sum, entry) => sum + entry.confidence, 0) / validConfidenceEntries.length)
: 0;
const aiDecisionCount = auditTrail.filter(entry => entry.action === 'ai-decision').length;
const embeddingsUsageCount = auditTrail.filter(entry => entry.metadata?.embeddingsUsed).length;
const toolSelectionCount = auditTrail.filter(entry => entry.action === 'selection-decision').length;
const avgProcessingTime = auditTrail.length > 0 ? totalTime / auditTrail.length : 0;
const aiTransparency = auditTrail.length > 0 ?
(auditTrail.filter(entry => entry.metadata?.aiPrompt || entry.metadata?.reasoning).length / auditTrail.length) * 100 : 0;
return {
totalTime,
avgConfidence,
stepCount: auditTrail.length,
aiDecisionCount,
embeddingsUsageCount,
toolSelectionCount,
qualityMetrics: {
avgProcessingTime,
aiTransparency: Math.round(aiTransparency)
}
};
}
renderPhaseGroups(auditTrail, stats) {
// Group audit entries by phase
const phaseGroups = new Map();
auditTrail.forEach(entry => {
const phase = entry.phase || 'unknown';
if (!phaseGroups.has(phase)) {
phaseGroups.set(phase, []);
}
// FIXED: Remove non-null assertion, use proper null checking
const phaseEntries = phaseGroups.get(phase);
if (phaseEntries) {
phaseEntries.push(entry);
}
});
let html = '';
let groupIndex = 0;
for (const [phaseName, entries] of phaseGroups) {
const isLastPhase = groupIndex === phaseGroups.size - 1;
const phaseAvgConfidence = entries.reduce((sum, e) => sum + (e.confidence || 0), 0) / entries.length;
html += `
<div class="phase-group ${isLastPhase ? 'last-phase' : ''}">
<div class="phase-header" style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<div class="phase-icon">${this.getPhaseIcon(phaseName)}</div>
<div class="phase-name">${this.getPhaseDisplayName(phaseName)}</div>
</div>
<div class="phase-divider"></div>
<div class="phase-stats">
<div class="confidence-bar">
<div class="confidence-fill" style="width: ${phaseAvgConfidence}%; background-color: ${getConfidenceColor(phaseAvgConfidence)};"></div>
</div>
<span class="confidence-text">${Math.round(phaseAvgConfidence)}%</span>
</div>
</div>
<div class="phase-entries">
${entries.map(entry => this.renderAuditEntry(entry)).join('')}
</div>
</div>
`;
groupIndex++;
}
return html;
}
renderAuditEntry(entry) {
const confidenceColor = getConfidenceColor(entry.confidence || 0);
const processingTime = formatDuration(entry.processingTimeMs || 0);
return `
<div class="audit-entry">
<div class="entry-main">
<div class="entry-action">${this.getActionDisplayName(entry.action)}</div>
<div class="entry-meta">
<div class="confidence-indicator" style="background-color: ${confidenceColor};"></div>
<span class="confidence-value">${entry.confidence || 0}%</span>
<span class="processing-time">${processingTime}</span>
</div>
</div>
${this.renderEntryDetails(entry)}
</div>
`;
}
renderEntryDetails(entry) {
const details = [];
// AI-specific details
if (entry.action === 'ai-decision' && entry.metadata?.reasoning) {
details.push(`<div class="detail-item"><strong>Begründung:</strong> ${truncateText(entry.metadata.reasoning, 200)}</div>`);
}
// Tool selection details
if (entry.action === 'selection-decision') {
if (entry.metadata?.selectedToolsCount && entry.metadata?.availableToolsCount) {
details.push(`<div class="detail-item"><strong>Auswahl:</strong> ${entry.metadata.selectedToolsCount} von ${entry.metadata.availableToolsCount} Tools</div>`);
}
if (entry.metadata?.selectionMethod) {
details.push(`<div class="detail-item"><strong>Methode:</strong> ${entry.metadata.selectionMethod}</div>`);
}
}
// Embeddings details
if (entry.metadata?.embeddingsUsed && entry.metadata?.totalMatches) {
details.push(`<div class="detail-item"><strong>Semantische Treffer:</strong> ${entry.metadata.totalMatches}</div>`);
}
// Confidence factors
if (entry.metadata?.confidenceFactors?.length > 0) {
const factors = entry.metadata.confidenceFactors.slice(0, 2).join(', ');
details.push(`<div class="detail-item"><strong>Faktoren:</strong> ${factors}</div>`);
}
if (details.length === 0) return '';
return `
<div class="entry-details">
${details.join('')}
</div>
`;
}
renderTechnicalDetails(auditTrail) {
return auditTrail.map(entry => `
<div class="technical-entry">
<div class="technical-header">
<div class="technical-phase">${entry.phase}/${entry.action}</div>
<div class="technical-time">${new Date(entry.timestamp).toLocaleTimeString('de-DE')}</div>
</div>
<div class="technical-content">
${entry.metadata?.aiModel ? `<div class="technical-row">Model: ${entry.metadata.aiModel}</div>` : ''}
${entry.metadata?.promptTokens ? `<div class="technical-row">Tokens: ${entry.metadata.promptTokens}/${entry.metadata.completionTokens}</div>` : ''}
${entry.metadata?.microTaskType ? `<div class="technical-row">Task: ${entry.metadata.microTaskType}</div>` : ''}
${entry.metadata?.selectionMethod ? `<div class="technical-row">Method: ${entry.metadata.selectionMethod}</div>` : ''}
<div class="technical-row">Confidence: ${entry.confidence}% (${entry.processingTimeMs}ms)</div>
</div>
</div>
`).join('');
}
getPhaseIcon(phase) {
const icons = {
'initialization': '🚀',
'tool-selection': '🔧',
'contextual-analysis': '🧠',
'workflow-phase': '⚡',
'tool-reasoning': '💭',
'knowledge-synthesis': '📚',
'confidence-scoring': '📊',
'phase-completion': '✅',
'completion': '🎯',
'embeddings': '🔍',
'unknown': '❓'
};
return icons[phase] || icons['unknown'];
}
getPhaseDisplayName(phase) {
const names = {
'initialization': 'Initialisierung',
'tool-selection': 'Tool-Auswahl',
'contextual-analysis': 'Kontext-Analyse',
'workflow-phase': 'Workflow-Phase',
'tool-reasoning': 'Tool-Bewertung',
'knowledge-synthesis': 'Wissens-Synthese',
'confidence-scoring': 'Vertrauenswertung',
'phase-completion': 'Phasen-Vervollständigung',
'completion': 'Abschluss',
'embeddings': 'Semantische Suche',
'unknown': 'Unbekannt'
};
return names[phase] || phase;
}
getActionDisplayName(action) {
const actions = {
'pipeline-start': 'Analyse gestartet',
'selection-decision': 'Tools ausgewählt',
'ai-decision': 'KI-Entscheidung',
'phase-tool-selection': 'Phasen-Tools evaluiert',
'tool-added-to-phase': 'Tool zur Phase hinzugefügt',
'concept-selection': 'Konzepte ausgewählt',
'tool-confidence': 'Vertrauen berechnet',
'phase-enhancement': 'Phase vervollständigt',
'similarity-search': 'Ähnlichkeitssuche',
'pipeline-end': 'Analyse abgeschlossen'
};
return actions[action] || action;
}
setupDownloadUpload() {
const downloadBtn = document.getElementById('download-results-btn');
const uploadInput = document.getElementById('upload-results');
if (downloadBtn) {
downloadBtn.addEventListener('click', () => this.downloadResults());
}
if (uploadInput) {
uploadInput.addEventListener('change', (event) => {
// FIXED: Remove TypeScript casting
const files = event.target.files;
const file = files ? files[0] : null;
if (file) {
this.handleFileUpload(file);
}
});
}
}
downloadResults() {
if (!this.currentRecommendation) {
alert('Keine Analyse zum Herunterladen verfügbar.');
return;
}
// FIXED: Use safer property access
const inputValue = this.elements.input ? this.elements.input.value : '';
const exportData = {
metadata: {
timestamp: new Date().toISOString(),
version: "1.0",
toolsDataHash: "current", // This would come from dataService
aiModel: "claude-sonnet-4", // This would come from aiService
aiParameters: {},
userQuery: inputValue,
mode: this.currentMode,
processingStats: {},
exportedBy: 'ForensicPathways'
},
recommendation: this.currentRecommendation,
auditTrail: this.currentRecommendation.auditTrail || [],
rawContext: {
selectedTools: [],
backgroundKnowledge: [],
contextHistory: [],
embeddingsSimilarities: {}
}
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `forensic-ai-analysis-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
console.log('[AI Interface] Analysis downloaded');
}
async handleFileUpload(file) {
try {
// Security validation
if (file.size > 10 * 1024 * 1024) {
throw new Error('Datei zu groß (max 10MB)');
}
if (!file.name.endsWith('.json')) {
throw new Error('Ungültiger Dateityp - nur JSON-Dateien werden unterstützt');
}
const text = await file.text();
let data;
try {
data = JSON.parse(text);
} catch (error) {
throw new Error('Ungültiges JSON-Format');
}
if (!this.validateUploadStructure(data)) {
throw new Error('Ungültiges Analyse-Dateiformat');
}
console.log('[AI Interface] Valid analysis file uploaded');
this.displayUploadedResults(data);
} catch (error) {
console.error('[AI Interface] Upload failed:', error);
alert(`Upload fehlgeschlagen: ${error.message}`);
}
}
validateUploadStructure(data) {
try {
return !!(
data &&
typeof data === 'object' &&
data.metadata &&
typeof data.metadata === 'object' &&
typeof data.metadata.timestamp === 'string' &&
data.recommendation &&
typeof data.recommendation === 'object' &&
Array.isArray(data.auditTrail)
);
} catch (error) {
console.error('[AI Interface] Structure validation error:', error);
return false;
}
}
displayUploadedResults(data) {
try {
// Show upload banner
this.showUploadedBanner(data.metadata);
// Restore interface state
this.currentRecommendation = data.recommendation;
// Display the uploaded analysis
this.showResults();
// Update the recommendation display
if (data.metadata.mode === 'workflow') {
this.displayWorkflowResults(data.recommendation, data.metadata.userQuery);
} else {
this.displayToolResults(data.recommendation, data.metadata.userQuery);
}
// Render the uploaded audit trail
setTimeout(() => {
this.renderAuditTrail(data.auditTrail);
}, 100);
console.log('[AI Interface] Uploaded analysis displayed successfully');
} catch (error) {
console.error('[AI Interface] Error displaying uploaded results:', error);
alert('Fehler beim Anzeigen der hochgeladenen Analyse');
}
}
showUploadedBanner(metadata) {
const banner = `
<div class="uploaded-results-banner" style="background: var(--color-bg-secondary); border: 1px solid var(--color-warning); border-radius: 0.5rem; padding: 1rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem;">
<div style="font-size: 1.5rem;">📁</div>
<div style="flex: 1;">
<strong style="color: var(--color-warning);">Hochgeladene Analyse</strong>
<div style="font-size: 0.875rem; color: var(--color-text-secondary); margin-top: 0.25rem;">
Erstellt: ${new Date(metadata.timestamp).toLocaleString('de-DE')} |
Modus: ${metadata.mode} |
${metadata.toolsDataHash ? `Hash: ${metadata.toolsDataHash.slice(0, 8)}...` : ''}
</div>
</div>
<button onclick="this.parentElement.remove()" style="background: none; border: none; color: var(--color-text-secondary); cursor: pointer; font-size: 1.25rem;">&times;</button>
</div>
`;
// FIXED: Add null checking
if (this.elements && this.elements.results) {
this.elements.results.insertAdjacentHTML('afterbegin', banner);
}
}
renderHeader(title, query) {
return `
<div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-primary) 0%, #525252 100%); color: white; border-radius: 0.75rem;">

View File

@ -1,5 +1,5 @@
---
import { createToolSlug } from '../utils/toolHelpers.js';
import { createToolSlug } from '../utils/clientUtils.js';
export interface Props {
toolName: string;

View File

@ -1,10 +1,11 @@
// src/utils/aiPipeline.ts - Refactored
import { getCompressedToolsDataForAI } from './dataService.js';
// src/utils/aiPipeline.ts - Enhanced with comprehensive audit logging
import { getCompressedToolsDataForAI, getDataVersion } from './dataService.js';
import { aiService } from './aiService.js';
import { toolSelector, type SelectionContext } from './toolSelector.js';
import { confidenceScoring, type AnalysisContext } from './confidenceScoring.js';
import { embeddingsService } from './embeddings.js';
import { auditService, type AuditEntry } from './auditService.js';
import { JSONParser } from './jsonUtils.js'; // FIXED: Use centralized JSON parsing
import { getPrompt } from '../config/prompts.js';
import 'dotenv/config';
@ -20,6 +21,11 @@ interface MicroTaskResult {
processingTimeMs: number;
success: boolean;
error?: string;
aiUsage?: {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
};
}
interface AnalysisResult {
@ -32,6 +38,8 @@ interface AnalysisResult {
microTasksCompleted: number;
microTasksFailed: number;
contextContinuityUsed: boolean;
totalAITokensUsed: number;
auditEntriesGenerated: number;
};
}
@ -64,6 +72,7 @@ interface PipelineContext {
class AIPipeline {
private config: PipelineConfig;
private totalTokensUsed: number = 0;
constructor() {
this.config = {
@ -79,6 +88,7 @@ class AIPipeline {
const startTime = Date.now();
let completedTasks = 0;
let failedTasks = 0;
this.totalTokensUsed = 0;
console.log('[AI-PIPELINE] Starting', mode, 'analysis pipeline');
@ -87,6 +97,8 @@ class AIPipeline {
try {
const toolsData = await getCompressedToolsDataForAI();
const aiConfig = aiService.getConfig();
const toolsDataHash = getDataVersion?.() || 'unknown';
const context: PipelineContext = {
userQuery,
@ -99,67 +111,119 @@ class AIPipeline {
embeddingsSimilarities: new Map<string, number>()
};
// Phase 1: Get intelligent tool candidates
// AUDIT: Pipeline initialization
auditService.addEntry(
'initialization',
'pipeline-start',
{
userQuery: this.truncateForAudit(userQuery),
mode,
toolsDataLoaded: !!toolsData,
aiConfig: { model: aiConfig.model }
},
{
totalAvailableTools: toolsData.tools.length,
totalAvailableConcepts: toolsData.concepts.length,
embeddingsEnabled: embeddingsService.isEnabled()
},
90,
startTime,
{
toolsDataHash,
aiModel: aiConfig.model,
embeddingsUsed: embeddingsService.isEnabled(),
pipelineVersion: '2.0-enhanced'
}
);
// Phase 1: Get intelligent tool candidates with enhanced logging
console.log('[AI-PIPELINE] Phase 1: Tool candidate selection');
const candidateSelectionStart = Date.now();
const candidateData = await toolSelector.getIntelligentCandidates(userQuery, toolsData, mode, context);
// AUDIT: Tool candidate selection
auditService.addToolSelection(
candidateData.tools.map(t => t.name),
toolsData.tools.map(t => t.name),
candidateData.selectionMethod,
85,
candidateSelectionStart,
{
embeddingsUsed: embeddingsService.isEnabled(),
totalCandidatesFound: candidateData.tools.length + candidateData.concepts.length,
selectionMethod: candidateData.selectionMethod,
reductionRatio: candidateData.tools.length / toolsData.tools.length
}
);
context.filteredData = candidateData;
this.addAuditEntry(
'initialization',
'pipeline-start',
{ userQuery, mode, toolsDataLoaded: !!toolsData },
{ candidateTools: candidateData.tools.length, candidateConcepts: candidateData.concepts.length },
90,
startTime,
{ selectionMethod: candidateData.selectionMethod }
);
// Phase 2: Contextual analysis micro-tasks
// Phase 2: Contextual analysis micro-tasks with enhanced logging
console.log('[AI-PIPELINE] Phase 2: Contextual analysis');
const analysisResult = await this.analyzeScenario(context);
const analysisResult = await this.analyzeScenario(context, startTime);
if (analysisResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(analysisResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const approachResult = await this.generateApproach(context);
const approachResult = await this.generateApproach(context, startTime);
if (approachResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(approachResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const considerationsResult = await this.generateCriticalConsiderations(context);
const considerationsResult = await this.generateCriticalConsiderations(context, startTime);
if (considerationsResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(considerationsResult.aiUsage);
await this.delay(this.config.microTaskDelay);
// Phase 3: Tool-specific analysis
// Phase 3: Tool-specific analysis with enhanced logging
console.log('[AI-PIPELINE] Phase 3: Tool-specific analysis');
if (mode === 'workflow') {
await this.processWorkflowMode(context, toolsData, completedTasks, failedTasks);
const workflowResults = await this.processWorkflowMode(context, toolsData, completedTasks, failedTasks, startTime);
completedTasks = workflowResults.completed;
failedTasks = workflowResults.failed;
} else {
await this.processToolMode(context, completedTasks, failedTasks);
const toolResults = await this.processToolMode(context, completedTasks, failedTasks, startTime);
completedTasks = toolResults.completed;
failedTasks = toolResults.failed;
}
// Phase 4: Knowledge and finalization
// Phase 4: Knowledge and finalization with enhanced logging
console.log('[AI-PIPELINE] Phase 4: Knowledge synthesis');
const knowledgeResult = await this.selectBackgroundKnowledge(context);
const knowledgeResult = await this.selectBackgroundKnowledge(context, startTime);
if (knowledgeResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(knowledgeResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const finalResult = await this.generateFinalRecommendations(context);
const finalResult = await this.generateFinalRecommendations(context, startTime);
if (finalResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(finalResult.aiUsage);
// Build final recommendation
const recommendation = this.buildRecommendation(context, mode, finalResult.content);
this.addAuditEntry(
// AUDIT: Pipeline completion
auditService.addEntry(
'completion',
'pipeline-end',
{ completedTasks, failedTasks },
{ finalRecommendation: !!recommendation, auditEntriesGenerated: auditService.getCurrentAuditTrail().length },
{ completedTasks, failedTasks, totalTokensUsed: this.totalTokensUsed },
{
finalRecommendation: !!recommendation,
auditEntriesGenerated: auditService.getCurrentAuditTrail().length,
selectedToolsCount: context.selectedTools?.length || 0,
backgroundKnowledgeCount: context.backgroundKnowledge?.length || 0
},
completedTasks > failedTasks ? 85 : 60,
startTime,
{ totalProcessingTimeMs: Date.now() - startTime }
{
totalProcessingTimeMs: Date.now() - startTime,
aiModel: aiConfig.model,
finalTokenUsage: this.totalTokensUsed,
pipelineEfficiency: completedTasks / (completedTasks + failedTasks)
}
);
const processingStats = {
@ -169,7 +233,9 @@ class AIPipeline {
processingTimeMs: Date.now() - startTime,
microTasksCompleted: completedTasks,
microTasksFailed: failedTasks,
contextContinuityUsed: true
contextContinuityUsed: true,
totalAITokensUsed: this.totalTokensUsed,
auditEntriesGenerated: auditService.getCurrentAuditTrail().length
};
console.log('[AI-PIPELINE] Pipeline completed successfully:', {
@ -177,7 +243,9 @@ class AIPipeline {
processingTimeMs: processingStats.processingTimeMs,
completedTasks,
failedTasks,
finalItems: processingStats.finalSelectedItems
finalItems: processingStats.finalSelectedItems,
totalTokensUsed: this.totalTokensUsed,
auditEntries: processingStats.auditEntriesGenerated
});
// Finalize audit trail
@ -193,6 +261,18 @@ class AIPipeline {
} catch (error) {
console.error('[AI-PIPELINE] Pipeline failed:', error);
// AUDIT: Pipeline failure
auditService.addEntry(
'error',
'pipeline-failure',
{ userQuery: this.truncateForAudit(userQuery), mode },
{ error: error.message, completedTasks, failedTasks },
0,
startTime,
{ errorType: error.constructor.name, totalTokensUsed: this.totalTokensUsed }
);
throw error;
}
}
@ -201,23 +281,63 @@ class AIPipeline {
context: PipelineContext,
toolsData: any,
completedTasks: number,
failedTasks: number
): Promise<void> {
failedTasks: number,
pipelineStart: number
): Promise<{ completed: number; failed: number }> {
const phases = toolsData.phases || [];
// Select tools for each phase
// Select tools for each phase with enhanced logging
for (const phase of phases) {
const phaseStart = Date.now();
const phaseTools = context.filteredData.tools.filter((tool: any) =>
tool && tool.phases && Array.isArray(tool.phases) && tool.phases.includes(phase.id)
);
const selections = await toolSelector.selectToolsForPhase(context.userQuery, phase, phaseTools, context);
// AUDIT: Phase tool selection
auditService.addEntry(
'workflow-phase',
'phase-tool-selection',
{
phaseId: phase.id,
phaseName: phase.name,
availableTools: phaseTools.map(t => t.name)
},
{
selectedTools: selections.map(s => s.toolName),
selectionCount: selections.length
},
selections.length > 0 ? 80 : 50,
phaseStart,
{
phaseId: phase.id,
availableToolsCount: phaseTools.length,
selectedToolsCount: selections.length,
microTaskType: 'phase-tool-selection'
}
);
selections.forEach((sel: any) => {
const tool = phaseTools.find((t: any) => t && t.name === sel.toolName);
if (tool) {
const priority = this.derivePriorityFromScore(sel.taskRelevance);
this.addToolToSelection(context, tool, phase.id, priority, sel.justification, sel.taskRelevance, sel.limitations);
// AUDIT: Individual tool selection reasoning
auditService.addEntry(
'tool-reasoning',
'tool-added-to-phase',
{ toolName: tool.name, phaseId: phase.id, taskRelevance: sel.taskRelevance },
{ justification: sel.justification, limitations: sel.limitations },
sel.taskRelevance || 70,
phaseStart,
{
toolType: tool.type,
priority,
selectionReasoning: sel.justification
}
);
}
});
@ -225,26 +345,37 @@ class AIPipeline {
await this.delay(this.config.microTaskDelay);
}
// Complete underrepresented phases
await this.completeUnderrepresentedPhases(context, toolsData);
// Complete underrepresented phases with enhanced logging
const completionResult = await this.completeUnderrepresentedPhases(context, toolsData, pipelineStart);
return { completed: completedTasks, failed: failedTasks };
}
private async processToolMode(context: PipelineContext, completedTasks: number, failedTasks: number): Promise<void> {
private async processToolMode(
context: PipelineContext,
completedTasks: number,
failedTasks: number,
pipelineStart: number
): Promise<{ completed: number; failed: number }> {
const topTools = context.filteredData.tools.slice(0, 3);
for (let i = 0; i < topTools.length; i++) {
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1);
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1, pipelineStart);
if (evaluationResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(evaluationResult.aiUsage);
await this.delay(this.config.microTaskDelay);
}
return { completed: completedTasks, failed: failedTasks };
}
private async analyzeScenario(context: PipelineContext): Promise<MicroTaskResult> {
private async analyzeScenario(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Scenario analysis');
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 400);
const result = await this.callMicroTaskAI(prompt, context, 400, 'scenario-analysis');
if (result.success) {
if (isWorkflow) {
@ -254,52 +385,109 @@ class AIPipeline {
}
this.addToContextHistory(context, `${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
// AUDIT: Scenario analysis
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
80,
`Generated ${isWorkflow ? 'scenario' : 'problem'} analysis for user query`,
taskStart,
{
microTaskType: 'scenario-analysis',
analysisType: isWorkflow ? 'scenario' : 'problem',
contentLength: result.content.length,
...result.aiUsage
}
);
}
return result;
}
private async generateApproach(context: PipelineContext): Promise<MicroTaskResult> {
private async generateApproach(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Investigation approach');
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('investigationApproach', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 400);
const result = await this.callMicroTaskAI(prompt, context, 400, 'investigation-approach');
if (result.success) {
context.investigationApproach = result.content;
this.addToContextHistory(context, `${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz: ${result.content.slice(0, 200)}...`);
// AUDIT: Investigation approach
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
75,
`Generated ${isWorkflow ? 'investigation' : 'solution'} approach`,
taskStart,
{
microTaskType: 'investigation-approach',
approachType: isWorkflow ? 'investigation' : 'solution',
contentLength: result.content.length,
contextHistoryLength: context.contextHistory.length,
...result.aiUsage
}
);
}
return result;
}
private async generateCriticalConsiderations(context: PipelineContext): Promise<MicroTaskResult> {
private async generateCriticalConsiderations(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Critical considerations');
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('criticalConsiderations', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 350);
const result = await this.callMicroTaskAI(prompt, context, 350, 'critical-considerations');
if (result.success) {
context.criticalConsiderations = result.content;
this.addToContextHistory(context, `Kritische Überlegungen: ${result.content.slice(0, 200)}...`);
// AUDIT: Critical considerations
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
70,
'Generated critical considerations and constraints',
taskStart,
{
microTaskType: 'critical-considerations',
contentLength: result.content.length,
...result.aiUsage
}
);
}
return result;
}
private async evaluateSpecificTool(context: PipelineContext, tool: any, rank: number): Promise<MicroTaskResult> {
private async evaluateSpecificTool(
context: PipelineContext,
tool: any,
rank: number,
pipelineStart: number
): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Tool evaluation for:', tool.name);
const taskStart = Date.now();
const existingSelection = context.selectedTools?.find((st: any) => st.tool && st.tool.name === tool.name);
const taskRelevance = existingSelection?.taskRelevance || 70;
const priority = this.derivePriorityFromScore(taskRelevance);
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank, taskRelevance);
const result = await this.callMicroTaskAI(prompt, context, 1000);
const result = await this.callMicroTaskAI(prompt, context, 1000, 'tool-evaluation');
if (result.success) {
const evaluation = this.safeParseJSON(result.content, {
// FIXED: Use centralized JSON parsing
const evaluation = JSONParser.safeParseJSON(result.content, {
detailed_explanation: 'Evaluation failed',
implementation_approach: '',
pros: [],
@ -315,13 +503,35 @@ class AIPipeline {
task_relevance: taskRelevance
}
}, 'evaluation', priority, evaluation.detailed_explanation, taskRelevance, evaluation.limitations);
// AUDIT: Tool evaluation
auditService.addAIDecision(
'tool-evaluation',
prompt,
result.content,
taskRelevance,
`Evaluated tool ${tool.name} for ${context.mode} mode`,
taskStart,
{
microTaskType: 'tool-evaluation',
toolName: tool.name,
toolType: tool.type,
rank,
taskRelevance,
evaluationParsed: !!evaluation.detailed_explanation,
prosCount: evaluation.pros?.length || 0,
limitationsCount: evaluation.limitations?.length || 0,
...result.aiUsage
}
);
}
return result;
}
private async selectBackgroundKnowledge(context: PipelineContext): Promise<MicroTaskResult> {
private async selectBackgroundKnowledge(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Background knowledge selection');
const taskStart = Date.now();
const availableConcepts = context.filteredData.concepts;
if (availableConcepts.length === 0) {
@ -335,10 +545,11 @@ class AIPipeline {
const selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
const prompt = getPrompt('backgroundKnowledgeSelection', context.userQuery, context.mode, selectedToolNames, availableConcepts);
const result = await this.callMicroTaskAI(prompt, context, 700);
const result = await this.callMicroTaskAI(prompt, context, 700, 'background-knowledge');
if (result.success) {
const selections = this.safeParseJSON(result.content, []);
// FIXED: Use centralized JSON parsing
const selections = JSONParser.safeParseJSON(result.content, []);
if (Array.isArray(selections)) {
context.backgroundKnowledge = selections.filter((sel: any) =>
@ -347,21 +558,66 @@ class AIPipeline {
concept: availableConcepts.find((c: any) => c.name === sel.conceptName),
relevance: sel.relevance
}));
// AUDIT: Background knowledge selection
auditService.addEntry(
'knowledge-synthesis',
'concept-selection',
{
availableConcepts: availableConcepts.map(c => c.name),
selectedToolsContext: selectedToolNames
},
{
selectedConcepts: context.backgroundKnowledge.map(bk => bk.concept.name),
selectionReasonings: context.backgroundKnowledge.map(bk => bk.relevance)
},
context.backgroundKnowledge.length > 0 ? 75 : 50,
taskStart,
{
microTaskType: 'background-knowledge',
availableConceptsCount: availableConcepts.length,
selectedConceptsCount: context.backgroundKnowledge.length,
selectionRatio: context.backgroundKnowledge.length / availableConcepts.length,
...result.aiUsage
}
);
}
}
return result;
}
private async generateFinalRecommendations(context: PipelineContext): Promise<MicroTaskResult> {
private async generateFinalRecommendations(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
console.log('[AI-PIPELINE] Micro-task: Final recommendations');
const taskStart = Date.now();
const selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
const prompt = getPrompt('finalRecommendations', context.mode === 'workflow', context.userQuery, selectedToolNames);
return this.callMicroTaskAI(prompt, context, 350);
const result = await this.callMicroTaskAI(prompt, context, 350, 'final-recommendations');
if (result.success) {
// AUDIT: Final recommendations
auditService.addAIDecision(
'synthesis',
prompt,
result.content,
85,
`Generated final ${context.mode} recommendations`,
taskStart,
{
microTaskType: 'final-recommendations',
mode: context.mode,
selectedToolsCount: selectedToolNames.length,
contentLength: result.content.length,
...result.aiUsage
}
);
}
return result;
}
private async completeUnderrepresentedPhases(context: PipelineContext, toolsData: any): Promise<void> {
private async completeUnderrepresentedPhases(context: PipelineContext, toolsData: any, pipelineStart: number): Promise<void> {
const phases = toolsData.phases || [];
const selectedPhases = new Map<string, number>();
@ -382,13 +638,34 @@ class AIPipeline {
console.log('[AI-PIPELINE] Completing underrepresented phases:', underrepresentedPhases.map((p: any) => p.id).join(', '));
// AUDIT: Phase completion start
auditService.addEntry(
'phase-completion',
'underrepresented-phases-detected',
{
underrepresentedPhases: underrepresentedPhases.map(p => p.id),
currentPhaseDistribution: Array.from(selectedPhases.entries())
},
{
phasesToComplete: underrepresentedPhases.length,
completionStrategy: 'semantic-search'
},
70,
pipelineStart,
{
totalPhases: phases.length,
adequatelyRepresented: phases.length - underrepresentedPhases.length,
completionMethod: 'semantic-phase-search'
}
);
for (const phase of underrepresentedPhases) {
await this.completePhaseWithSemanticSearch(context, phase, toolsData);
await this.completePhaseWithSemanticSearch(context, phase, toolsData, pipelineStart);
await this.delay(this.config.microTaskDelay);
}
}
private async completePhaseWithSemanticSearch(context: PipelineContext, phase: any, toolsData: any): Promise<void> {
private async completePhaseWithSemanticSearch(context: PipelineContext, phase: any, toolsData: any, pipelineStart: number): Promise<void> {
const phaseStart = Date.now();
const phaseQuery = `forensic ${phase.name.toLowerCase()} tools methods`;
@ -397,6 +674,19 @@ class AIPipeline {
try {
const phaseResults = await embeddingsService.findSimilar(phaseQuery, 20, 0.2);
// AUDIT: Embeddings search for phase completion
auditService.addEmbeddingsSearch(
phaseQuery,
phaseResults,
0.2,
phaseStart,
{
phaseId: phase.id,
phaseName: phase.name,
completionPurpose: 'underrepresented-phase-enhancement'
}
);
if (phaseResults.length === 0) {
console.log('[AI-PIPELINE] No semantic results for phase:', phase.id);
return;
@ -422,7 +712,7 @@ class AIPipeline {
return;
}
// Add tools with justification
// Add tools with justification and audit each addition
for (const tool of phaseTools) {
const justification = `Nachträglich hinzugefügt zur Vervollständigung der ${phase.name}-Phase. Die ursprüngliche KI-Auswahl war zu spezifisch und hat wichtige Tools für diese Phase übersehen.`;
@ -436,11 +726,40 @@ class AIPipeline {
['Nachträgliche Ergänzung via semantische Phasensuche']
);
// AUDIT: Phase completion tool addition
auditService.addPhaseCompletion(
phase.id,
[tool.name],
justification,
phaseStart,
{
toolName: tool.name,
toolType: tool.type,
semanticSimilarity: phaseResults.find(r => r.name === tool.name)?.similarity,
completionReason: 'underrepresented-phase',
originalSelectionMissed: true
}
);
console.log('[AI-PIPELINE] Added phase completion tool:', tool.name);
}
} catch (error) {
console.error('[AI-PIPELINE] Phase completion failed for:', phase.id, error);
// AUDIT: Phase completion failure
auditService.addEntry(
'phase-completion',
'completion-failed',
{ phaseId: phase.id, error: error.message },
{ success: false },
20,
phaseStart,
{
errorType: error.constructor.name,
phaseId: phase.id
}
);
}
}
@ -476,6 +795,18 @@ class AIPipeline {
st.limitations || []
);
// AUDIT: Confidence calculation for each tool
auditService.addConfidenceCalculation(
st.tool.name,
confidence,
Date.now(),
{
phase: st.phase,
priority: st.priority,
toolType: st.tool.type
}
);
return {
name: st.tool.name,
type: st.tool.type,
@ -508,6 +839,17 @@ class AIPipeline {
st.limitations || []
);
// AUDIT: Confidence calculation for each tool
auditService.addConfidenceCalculation(
st.tool.name,
confidence,
Date.now(),
{
rank: st.tool.evaluation?.rank || 1,
toolType: st.tool.type
}
);
return {
name: st.tool.name,
type: st.tool.type,
@ -532,7 +874,12 @@ class AIPipeline {
}
// Helper methods
private async callMicroTaskAI(prompt: string, context: PipelineContext, maxTokens: number = 500): Promise<MicroTaskResult> {
private async callMicroTaskAI(
prompt: string,
context: PipelineContext,
maxTokens: number = 500,
taskType: string = 'micro-task'
): Promise<MicroTaskResult> {
const startTime = Date.now();
let contextPrompt = prompt;
@ -549,15 +896,16 @@ class AIPipeline {
const response = await aiService.callMicroTaskAI(contextPrompt, maxTokens);
return {
taskType: 'micro-task',
taskType,
content: response.content,
processingTimeMs: Date.now() - startTime,
success: true
success: true,
aiUsage: response.usage
};
} catch (error) {
return {
taskType: 'micro-task',
taskType,
content: '',
processingTimeMs: Date.now() - startTime,
success: false,
@ -608,48 +956,21 @@ class AIPipeline {
return 'low';
}
private safeParseJSON(jsonString: string, fallback: any = null): any {
try {
let cleaned = jsonString.trim();
private truncateForAudit(text: string, maxLength: number = 200): string {
if (typeof text !== 'string') return String(text);
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...[audit-truncated]';
}
// Remove code block markers
const jsonBlockPatterns = [
/```json\s*([\s\S]*?)\s*```/i,
/```\s*([\s\S]*?)\s*```/i,
/\{[\s\S]*\}/,
];
for (const pattern of jsonBlockPatterns) {
const match = cleaned.match(pattern);
if (match) {
cleaned = match[1] || match[0];
break;
}
}
return JSON.parse(cleaned);
} catch (error) {
console.warn('[AI-PIPELINE] JSON parsing failed:', error.message);
return fallback;
private trackTokenUsage(usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number }): void {
if (usage?.totalTokens) {
this.totalTokensUsed += usage.totalTokens;
}
}
private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private addAuditEntry(
phase: string,
action: string,
input: any,
output: any,
confidence: number,
startTime: number,
metadata: Record<string, any> = {}
): void {
auditService.addEntry(phase, action, input, output, confidence, startTime, metadata);
}
}
export const aiPipeline = new AIPipeline();

View File

@ -1,4 +1,4 @@
// src/utils/auditService.ts - Refactored
// src/utils/auditService.ts - Enhanced for forensic-grade transparency
import 'dotenv/config';
function env(key: string, fallback: string | undefined = undefined): string | undefined {
@ -19,7 +19,29 @@ export interface AuditEntry {
output: any;
confidence: number;
processingTimeMs: number;
metadata: Record<string, any>;
metadata: {
aiModel?: string;
aiParameters?: any;
promptTokens?: number;
completionTokens?: number;
toolsDataHash?: string;
embeddingsUsed?: boolean;
selectionMethod?: string;
microTaskType?: string;
confidenceFactors?: string[];
reasoning?: string;
aiPrompt?: string;
aiResponse?: string;
toolSelectionCriteria?: string;
availableToolsCount?: number;
selectedToolsCount?: number;
phaseId?: string;
toolsAdded?: string[];
completionReasoning?: string;
similarityScores?: Record<string, number>;
contextLength?: number;
[key: string]: any;
};
}
interface AuditConfig {
@ -87,6 +109,135 @@ class AuditService {
console.log(`[AUDIT-SERVICE] ${phase}/${action}: ${confidence}% confidence, ${entry.processingTimeMs}ms`);
}
// NEW: Specialized audit methods for forensic transparency
addAIDecision(
phase: string,
aiPrompt: string,
aiResponse: string,
confidence: number,
reasoning: string,
startTime: number,
metadata: Record<string, any> = {}
): void {
this.addEntry(
phase,
'ai-decision',
{ prompt: this.truncateForAudit(aiPrompt) },
{ response: this.truncateForAudit(aiResponse) },
confidence,
startTime,
{
...metadata,
reasoning,
aiPrompt: this.config.detailLevel === 'verbose' ? aiPrompt : this.truncateForAudit(aiPrompt),
aiResponse: this.config.detailLevel === 'verbose' ? aiResponse : this.truncateForAudit(aiResponse)
}
);
}
addToolSelection(
selectedTools: string[],
availableTools: string[],
selectionMethod: string,
confidence: number,
startTime: number,
metadata: Record<string, any> = {}
): void {
this.addEntry(
'tool-selection',
'selection-decision',
{ availableTools: availableTools.length > 10 ? availableTools.slice(0, 10) : availableTools },
{ selectedTools },
confidence,
startTime,
{
...metadata,
selectionMethod,
availableToolsCount: availableTools.length,
selectedToolsCount: selectedTools.length,
toolSelectionCriteria: `${selectionMethod} selection from ${availableTools.length} available tools`
}
);
}
addPhaseCompletion(
phaseId: string,
toolsAdded: string[],
completionReasoning: string,
startTime: number,
metadata: Record<string, any> = {}
): void {
this.addEntry(
'phase-completion',
'phase-enhancement',
{ phaseId, underrepresentedPhase: true },
{ toolsAdded },
75, // Default confidence for phase completion
startTime,
{
...metadata,
phaseId,
toolsAdded,
completionReasoning,
enhancementType: 'semantic-phase-completion'
}
);
}
addEmbeddingsSearch(
query: string,
similarResults: any[],
threshold: number,
startTime: number,
metadata: Record<string, any> = {}
): void {
const similarityScores = similarResults.reduce((acc, result) => {
acc[result.name] = result.similarity;
return acc;
}, {} as Record<string, number>);
this.addEntry(
'embeddings',
'similarity-search',
{ query: this.truncateForAudit(query), threshold },
{ resultsCount: similarResults.length, topResults: similarResults.slice(0, 5).map(r => r.name) },
similarResults.length > 0 ? 85 : 50,
startTime,
{
...metadata,
embeddingsUsed: true,
similarityScores,
searchThreshold: threshold,
totalMatches: similarResults.length
}
);
}
addConfidenceCalculation(
toolName: string,
confidenceBreakdown: any,
startTime: number,
metadata: Record<string, any> = {}
): void {
this.addEntry(
'confidence-scoring',
'tool-confidence',
{ toolName },
{ confidenceBreakdown },
confidenceBreakdown.overall || 50,
startTime,
{
...metadata,
confidenceFactors: [
...(confidenceBreakdown.strengthIndicators || []),
...(confidenceBreakdown.uncertaintyFactors || [])
],
semanticRelevance: confidenceBreakdown.semanticRelevance,
taskSuitability: confidenceBreakdown.taskSuitability
}
);
}
getCurrentAuditTrail(): AuditEntry[] {
return [...this.activeAuditTrail];
}
@ -135,6 +286,12 @@ class AuditService {
return data;
}
private truncateForAudit(text: string, maxLength: number = 300): string {
if (typeof text !== 'string') return String(text);
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...[truncated for audit]';
}
isEnabled(): boolean {
return this.config.enabled;
}
@ -143,7 +300,7 @@ class AuditService {
return { ...this.config };
}
// Statistics and analysis methods
// Statistics and analysis methods (enhanced)
getAuditStatistics(auditTrail: AuditEntry[]): {
totalTime: number;
avgConfidence: number;
@ -151,6 +308,14 @@ class AuditService {
highConfidenceSteps: number;
lowConfidenceSteps: number;
phaseBreakdown: Record<string, { count: number; avgConfidence: number; totalTime: number }>;
aiDecisionCount: number;
embeddingsUsageCount: number;
toolSelectionCount: number;
qualityMetrics: {
avgProcessingTime: number;
confidenceDistribution: { high: number; medium: number; low: number };
aiTransparency: number;
};
} {
if (!auditTrail || auditTrail.length === 0) {
return {
@ -159,7 +324,15 @@ class AuditService {
stepCount: 0,
highConfidenceSteps: 0,
lowConfidenceSteps: 0,
phaseBreakdown: {}
phaseBreakdown: {},
aiDecisionCount: 0,
embeddingsUsageCount: 0,
toolSelectionCount: 0,
qualityMetrics: {
avgProcessingTime: 0,
confidenceDistribution: { high: 0, medium: 0, low: 0 },
aiTransparency: 0
}
};
}
@ -171,6 +344,12 @@ class AuditService {
const highConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) >= 80).length;
const lowConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) < 60).length;
const mediumConfidenceSteps = auditTrail.length - highConfidenceSteps - lowConfidenceSteps;
// Enhanced metrics
const aiDecisionCount = auditTrail.filter(entry => entry.action === 'ai-decision').length;
const embeddingsUsageCount = auditTrail.filter(entry => entry.metadata?.embeddingsUsed).length;
const toolSelectionCount = auditTrail.filter(entry => entry.action === 'selection-decision').length;
// Phase breakdown
const phaseBreakdown: Record<string, { count: number; avgConfidence: number; totalTime: number }> = {};
@ -197,13 +376,29 @@ class AuditService {
}
});
const avgProcessingTime = auditTrail.length > 0 ? totalTime / auditTrail.length : 0;
const aiTransparency = auditTrail.length > 0 ?
(auditTrail.filter(entry => entry.metadata?.aiPrompt || entry.metadata?.reasoning).length / auditTrail.length) * 100 : 0;
return {
totalTime,
avgConfidence,
stepCount: auditTrail.length,
highConfidenceSteps,
lowConfidenceSteps,
phaseBreakdown
phaseBreakdown,
aiDecisionCount,
embeddingsUsageCount,
toolSelectionCount,
qualityMetrics: {
avgProcessingTime,
confidenceDistribution: {
high: highConfidenceSteps,
medium: mediumConfidenceSteps,
low: lowConfidenceSteps
},
aiTransparency: Math.round(aiTransparency)
}
};
}
@ -238,6 +433,15 @@ class AuditService {
}
});
// Enhanced validation for audit quality
if (entry.action === 'ai-decision' && !entry.metadata?.aiPrompt && !entry.metadata?.reasoning) {
warnings.push(`Entry ${index}: AI decision lacks transparency (no prompt or reasoning)`);
}
if (entry.action === 'selection-decision' && !entry.metadata?.selectionMethod) {
warnings.push(`Entry ${index}: Tool selection lacks methodology info`);
}
// Data type validation
if (typeof entry.confidence !== 'number' || entry.confidence < 0 || entry.confidence > 100) {
warnings.push(`Entry ${index} has invalid confidence value: ${entry.confidence}`);

View File

@ -1,5 +1,5 @@
// src/utils/confidenceScoring.ts
import { isToolHosted } from './toolHelpers.js';
import { isToolHosted } from './clientUtils.js';
import 'dotenv/config';
export interface ConfidenceMetrics {

View File

@ -1,4 +1,4 @@
// src/utils/jsonUtils.ts
// src/utils/jsonUtils.ts - Centralized JSON parsing and utilities
export class JSONParser {
static safeParseJSON(jsonString: string, fallback: any = null): any {
try {
@ -129,4 +129,246 @@ export class JSONParser {
return { selectedTools, selectedConcepts };
}
static secureParseJSON(jsonString: string, maxSize: number = 10 * 1024 * 1024): any {
if (typeof jsonString !== 'string') {
throw new Error('Input must be a string');
}
if (jsonString.length > maxSize) {
throw new Error(`JSON string too large (${jsonString.length} bytes, max ${maxSize})`);
}
// Security checks for potentially malicious content
const suspiciousPatterns = [
/<script/i,
/javascript:/i,
/eval\(/i,
/function\s*\(/i,
/__proto__/i,
/constructor/i
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(jsonString)) {
throw new Error('Potentially malicious content detected in JSON');
}
}
try {
const parsed = JSON.parse(jsonString);
// Validate basic structure
if (typeof parsed !== 'object' || parsed === null) {
throw new Error('JSON must be an object');
}
return parsed;
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON syntax: ${error.message}`);
}
throw error;
}
}
static sanitizeForAudit(obj: any, maxDepth: number = 5, currentDepth: number = 0): any {
if (currentDepth >= maxDepth) {
return '[Max depth reached]';
}
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj === 'string') {
// Sanitize strings that might contain sensitive data
if (obj.length > 500) {
return obj.slice(0, 500) + '...[truncated]';
}
// Remove potential code injection patterns
return obj.replace(/<script[\s\S]*?<\/script>/gi, '[script removed]');
}
if (typeof obj === 'number' || typeof obj === 'boolean') {
return obj;
}
if (Array.isArray(obj)) {
if (obj.length > 20) {
return [
...obj.slice(0, 20).map(item => this.sanitizeForAudit(item, maxDepth, currentDepth + 1)),
`...[${obj.length - 20} more items]`
];
}
return obj.map(item => this.sanitizeForAudit(item, maxDepth, currentDepth + 1));
}
if (typeof obj === 'object') {
const keys = Object.keys(obj);
if (keys.length > 50) {
const sanitized: any = {};
keys.slice(0, 50).forEach(key => {
sanitized[key] = this.sanitizeForAudit(obj[key], maxDepth, currentDepth + 1);
});
sanitized['[truncated]'] = `${keys.length - 50} more properties`;
return sanitized;
}
const sanitized: any = {};
keys.forEach(key => {
// Skip potential dangerous properties
if (['__proto__', 'constructor', 'prototype'].includes(key)) {
return;
}
sanitized[key] = this.sanitizeForAudit(obj[key], maxDepth, currentDepth + 1);
});
return sanitized;
}
return String(obj);
}
static validateAuditExportStructure(data: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!data || typeof data !== 'object') {
errors.push('Export data must be an object');
return { isValid: false, errors };
}
// Check required top-level properties
const requiredProps = ['metadata', 'recommendation', 'auditTrail'];
for (const prop of requiredProps) {
if (!(prop in data)) {
errors.push(`Missing required property: ${prop}`);
}
}
// Validate metadata
if (data.metadata && typeof data.metadata === 'object') {
const requiredMetadataProps = ['timestamp', 'version', 'userQuery', 'mode'];
for (const prop of requiredMetadataProps) {
if (!(prop in data.metadata)) {
errors.push(`Missing required metadata property: ${prop}`);
}
}
} else {
errors.push('Invalid metadata structure');
}
// Validate audit trail
if (!Array.isArray(data.auditTrail)) {
errors.push('auditTrail must be an array');
} else {
data.auditTrail.forEach((entry: any, index: number) => {
if (!entry || typeof entry !== 'object') {
errors.push(`Audit entry ${index} is not a valid object`);
return;
}
const requiredEntryProps = ['timestamp', 'phase', 'action', 'confidence', 'processingTimeMs'];
for (const prop of requiredEntryProps) {
if (!(prop in entry)) {
errors.push(`Audit entry ${index} missing required property: ${prop}`);
}
}
});
}
return {
isValid: errors.length === 0,
errors
};
}
static prepareAuditExport(
recommendation: any,
userQuery: string,
mode: string,
auditTrail: any[] = [],
additionalMetadata: any = {}
): any {
return {
metadata: {
timestamp: new Date().toISOString(),
version: "1.0",
userQuery: userQuery.slice(0, 1000), // Truncate for security
mode,
exportedBy: 'ForensicPathways',
toolsDataHash: additionalMetadata.toolsDataHash || 'unknown',
aiModel: additionalMetadata.aiModel || 'unknown',
aiParameters: additionalMetadata.aiParameters || {},
processingStats: additionalMetadata.processingStats || {}
},
recommendation: this.sanitizeForAudit(recommendation, 6),
auditTrail: auditTrail.map(entry => this.sanitizeForAudit(entry, 4)),
rawContext: {
selectedTools: additionalMetadata.selectedTools || [],
backgroundKnowledge: additionalMetadata.backgroundKnowledge || [],
contextHistory: additionalMetadata.contextHistory || [],
embeddingsSimilarities: additionalMetadata.embeddingsSimilarities || {}
}
};
}
static validateUploadedAnalysis(data: any): { isValid: boolean; issues: string[]; warnings: string[] } {
const issues: string[] = [];
const warnings: string[] = [];
// Basic structure validation
const structureValidation = this.validateAuditExportStructure(data);
if (!structureValidation.isValid) {
issues.push(...structureValidation.errors);
return { isValid: false, issues, warnings };
}
// Additional validation for uploaded content
if (data.metadata) {
// Check timestamp validity
const timestamp = new Date(data.metadata.timestamp);
if (isNaN(timestamp.getTime())) {
warnings.push('Invalid timestamp in metadata');
} else {
const age = Date.now() - timestamp.getTime();
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
if (age > maxAge) {
warnings.push(`Analysis is ${Math.floor(age / (24 * 60 * 60 * 1000))} days old`);
}
}
// Validate mode
if (!['workflow', 'tool'].includes(data.metadata.mode)) {
warnings.push(`Unknown analysis mode: ${data.metadata.mode}`);
}
}
// Validate audit trail quality
if (Array.isArray(data.auditTrail)) {
const aiDecisions = data.auditTrail.filter(e => e.action === 'ai-decision').length;
const toolSelections = data.auditTrail.filter(e => e.action === 'selection-decision').length;
if (aiDecisions === 0) {
warnings.push('No AI decisions found in audit trail');
}
if (toolSelections === 0) {
warnings.push('No tool selections found in audit trail');
}
// Check for confidence values
const entriesWithConfidence = data.auditTrail.filter(e => typeof e.confidence === 'number').length;
const confidenceRatio = entriesWithConfidence / data.auditTrail.length;
if (confidenceRatio < 0.8) {
warnings.push(`Only ${Math.round(confidenceRatio * 100)}% of audit entries have confidence scores`);
}
}
return {
isValid: issues.length === 0,
issues,
warnings
};
}
}

View File

@ -1,22 +0,0 @@
// src/utils/toolHelpers.ts
export interface Tool {
name: string;
type?: 'software' | 'method' | 'concept';
projectUrl?: string | null;
license?: string;
knowledgebase?: boolean;
domains?: string[];
phases?: string[];
platforms?: string[];
skillLevel?: string;
description?: string;
tags?: string[];
related_concepts?: string[];
}
export {
createToolSlug,
findToolByIdentifier,
isToolHosted
} from './clientUtils.js';

View File

@ -2,6 +2,7 @@
import { aiService } from './aiService.js';
import { embeddingsService, type SimilarityResult } from './embeddings.js';
import { confidenceScoring } from './confidenceScoring.js';
import { JSONParser } from './jsonUtils.js'; // FIXED: Use centralized JSON parsing
import { getPrompt } from '../config/prompts.js';
import 'dotenv/config';
@ -248,7 +249,8 @@ class ToolSelector {
try {
const response = await aiService.callAI(prompt, { maxTokens: 2500 });
const result = this.safeParseJSON(response.content, null);
// FIXED: Use centralized JSON parsing
const result = JSONParser.safeParseJSON(response.content, null);
if (!result || !Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
console.error('[TOOL-SELECTOR] AI selection returned invalid structure');
@ -313,7 +315,8 @@ class ToolSelector {
try {
const response = await aiService.callMicroTaskAI(prompt, 1000);
const selections = this.safeParseJSON(response.content, []);
// FIXED: Use centralized JSON parsing
const selections = JSONParser.safeParseJSON(response.content, []);
if (Array.isArray(selections)) {
const validSelections = selections.filter((sel: any) => {
@ -365,90 +368,7 @@ class ToolSelector {
related_software: concept.related_software || []
});
private safeParseJSON(jsonString: string, fallback: any = null): any {
try {
let cleaned = jsonString.trim();
// Remove code block markers
const jsonBlockPatterns = [
/```json\s*([\s\S]*?)\s*```/i,
/```\s*([\s\S]*?)\s*```/i,
/\{[\s\S]*\}/,
];
for (const pattern of jsonBlockPatterns) {
const match = cleaned.match(pattern);
if (match) {
cleaned = match[1] || match[0];
break;
}
}
// Handle truncated JSON
if (!cleaned.endsWith('}') && !cleaned.endsWith(']')) {
console.warn('[TOOL-SELECTOR] JSON appears truncated, attempting recovery');
let braceCount = 0;
let bracketCount = 0;
let inString = false;
let escaped = false;
let lastCompleteStructure = '';
for (let i = 0; i < cleaned.length; i++) {
const char = cleaned[i];
if (escaped) {
escaped = false;
continue;
}
if (char === '\\') {
escaped = true;
continue;
}
if (char === '"' && !escaped) {
inString = !inString;
continue;
}
if (!inString) {
if (char === '{') braceCount++;
if (char === '}') braceCount--;
if (char === '[') bracketCount++;
if (char === ']') bracketCount--;
if (braceCount === 0 && bracketCount === 0 && (char === '}' || char === ']')) {
lastCompleteStructure = cleaned.substring(0, i + 1);
}
}
}
if (lastCompleteStructure) {
cleaned = lastCompleteStructure;
} else {
if (braceCount > 0) cleaned += '}';
if (bracketCount > 0) cleaned += ']';
}
}
const parsed = JSON.parse(cleaned);
// Ensure proper structure
if (parsed && typeof parsed === 'object') {
if (!parsed.selectedTools) parsed.selectedTools = [];
if (!parsed.selectedConcepts) parsed.selectedConcepts = [];
if (!Array.isArray(parsed.selectedTools)) parsed.selectedTools = [];
if (!Array.isArray(parsed.selectedConcepts)) parsed.selectedConcepts = [];
}
return parsed;
} catch (error) {
console.warn('[TOOL-SELECTOR] JSON parsing failed:', error.message);
return fallback;
}
}
// REMOVED: safeParseJSON method - now using centralized version from jsonUtils.ts
getConfig(): ToolSelectionConfig {
return { ...this.config };