airefactor #19
@ -217,7 +217,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
|||||||
|
|
||||||
<script type="module" define:vars={{ tools, phases, domainAgnosticSoftware }}>
|
<script type="module" define:vars={{ tools, phases, domainAgnosticSoftware }}>
|
||||||
|
|
||||||
// Utility functions (inline to avoid import issues)
|
|
||||||
function showElement(element) {
|
function showElement(element) {
|
||||||
if (element) {
|
if (element) {
|
||||||
element.style.display = 'block';
|
element.style.display = 'block';
|
||||||
@ -908,40 +907,537 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAuditTrail(rawAuditTrail) {
|
renderAuditTrail(rawAuditTrail) {
|
||||||
// Simplified audit trail rendering - remove complex processor
|
|
||||||
const container = document.getElementById('audit-trail-container');
|
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);
|
console.log('[AI Interface] Rendering comprehensive audit trail with', rawAuditTrail.length, 'entries');
|
||||||
const avgConfidence = rawAuditTrail.length > 0
|
|
||||||
? Math.round(rawAuditTrail.reduce((sum, entry) => sum + (entry.confidence || 0), 0) / rawAuditTrail.length)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
container.innerHTML = `
|
try {
|
||||||
<div class="audit-trail-container">
|
// Calculate audit statistics
|
||||||
<div class="audit-trail-header">
|
const stats = this.calculateAuditStats(rawAuditTrail);
|
||||||
<div class="audit-trail-title">
|
|
||||||
<div class="audit-icon">
|
container.innerHTML = `
|
||||||
<div class="audit-icon-gradient">✓</div>
|
<div class="audit-trail-container">
|
||||||
<h4>KI-Entscheidungspfad</h4>
|
<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>
|
||||||
<div class="audit-stats">
|
<svg class="toggle-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="transform: rotate(90deg);">
|
||||||
<div class="stat-item">
|
<polyline points="9 18 15 12 9 6"/>
|
||||||
<span>${formatDuration(totalTime)}</span>
|
</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>
|
||||||
<div class="stat-item">
|
|
||||||
<span>${avgConfidence}% Vertrauen</span>
|
<div class="insights-section">
|
||||||
</div>
|
${stats.qualityMetrics.aiTransparency >= 80 ? `
|
||||||
<div class="stat-item">
|
<div class="insights-header success">✅ Hohe Transparenz</div>
|
||||||
<span>${rawAuditTrail.length} Schritte</span>
|
<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>
|
||||||
</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>
|
||||||
</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>
|
</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;">×</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// FIXED: Add null checking
|
||||||
|
if (this.elements && this.elements.results) {
|
||||||
|
this.elements.results.insertAdjacentHTML('afterbegin', banner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderHeader(title, query) {
|
renderHeader(title, query) {
|
||||||
return `
|
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;">
|
<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;">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { createToolSlug } from '../utils/toolHelpers.js';
|
import { createToolSlug } from '../utils/clientUtils.js';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
// src/utils/aiPipeline.ts - Refactored
|
// src/utils/aiPipeline.ts - Enhanced with comprehensive audit logging
|
||||||
import { getCompressedToolsDataForAI } from './dataService.js';
|
import { getCompressedToolsDataForAI, getDataVersion } from './dataService.js';
|
||||||
import { aiService } from './aiService.js';
|
import { aiService } from './aiService.js';
|
||||||
import { toolSelector, type SelectionContext } from './toolSelector.js';
|
import { toolSelector, type SelectionContext } from './toolSelector.js';
|
||||||
import { confidenceScoring, type AnalysisContext } from './confidenceScoring.js';
|
import { confidenceScoring, type AnalysisContext } from './confidenceScoring.js';
|
||||||
import { embeddingsService } from './embeddings.js';
|
import { embeddingsService } from './embeddings.js';
|
||||||
import { auditService, type AuditEntry } from './auditService.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 { getPrompt } from '../config/prompts.js';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
@ -20,6 +21,11 @@ interface MicroTaskResult {
|
|||||||
processingTimeMs: number;
|
processingTimeMs: number;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
aiUsage?: {
|
||||||
|
promptTokens?: number;
|
||||||
|
completionTokens?: number;
|
||||||
|
totalTokens?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AnalysisResult {
|
interface AnalysisResult {
|
||||||
@ -32,6 +38,8 @@ interface AnalysisResult {
|
|||||||
microTasksCompleted: number;
|
microTasksCompleted: number;
|
||||||
microTasksFailed: number;
|
microTasksFailed: number;
|
||||||
contextContinuityUsed: boolean;
|
contextContinuityUsed: boolean;
|
||||||
|
totalAITokensUsed: number;
|
||||||
|
auditEntriesGenerated: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +72,7 @@ interface PipelineContext {
|
|||||||
|
|
||||||
class AIPipeline {
|
class AIPipeline {
|
||||||
private config: PipelineConfig;
|
private config: PipelineConfig;
|
||||||
|
private totalTokensUsed: number = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = {
|
this.config = {
|
||||||
@ -79,6 +88,7 @@ class AIPipeline {
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let completedTasks = 0;
|
let completedTasks = 0;
|
||||||
let failedTasks = 0;
|
let failedTasks = 0;
|
||||||
|
this.totalTokensUsed = 0;
|
||||||
|
|
||||||
console.log('[AI-PIPELINE] Starting', mode, 'analysis pipeline');
|
console.log('[AI-PIPELINE] Starting', mode, 'analysis pipeline');
|
||||||
|
|
||||||
@ -87,6 +97,8 @@ class AIPipeline {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const toolsData = await getCompressedToolsDataForAI();
|
const toolsData = await getCompressedToolsDataForAI();
|
||||||
|
const aiConfig = aiService.getConfig();
|
||||||
|
const toolsDataHash = getDataVersion?.() || 'unknown';
|
||||||
|
|
||||||
const context: PipelineContext = {
|
const context: PipelineContext = {
|
||||||
userQuery,
|
userQuery,
|
||||||
@ -99,67 +111,119 @@ class AIPipeline {
|
|||||||
embeddingsSimilarities: new Map<string, number>()
|
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');
|
console.log('[AI-PIPELINE] Phase 1: Tool candidate selection');
|
||||||
|
const candidateSelectionStart = Date.now();
|
||||||
|
|
||||||
const candidateData = await toolSelector.getIntelligentCandidates(userQuery, toolsData, mode, context);
|
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;
|
context.filteredData = candidateData;
|
||||||
|
|
||||||
this.addAuditEntry(
|
// Phase 2: Contextual analysis micro-tasks with enhanced logging
|
||||||
'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
|
|
||||||
console.log('[AI-PIPELINE] Phase 2: Contextual analysis');
|
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++;
|
if (analysisResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(analysisResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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++;
|
if (approachResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(approachResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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++;
|
if (considerationsResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(considerationsResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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');
|
console.log('[AI-PIPELINE] Phase 3: Tool-specific analysis');
|
||||||
|
|
||||||
if (mode === 'workflow') {
|
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 {
|
} 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');
|
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++;
|
if (knowledgeResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(knowledgeResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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++;
|
if (finalResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(finalResult.aiUsage);
|
||||||
|
|
||||||
// Build final recommendation
|
// Build final recommendation
|
||||||
const recommendation = this.buildRecommendation(context, mode, finalResult.content);
|
const recommendation = this.buildRecommendation(context, mode, finalResult.content);
|
||||||
|
|
||||||
this.addAuditEntry(
|
// AUDIT: Pipeline completion
|
||||||
|
auditService.addEntry(
|
||||||
'completion',
|
'completion',
|
||||||
'pipeline-end',
|
'pipeline-end',
|
||||||
{ completedTasks, failedTasks },
|
{ completedTasks, failedTasks, totalTokensUsed: this.totalTokensUsed },
|
||||||
{ finalRecommendation: !!recommendation, auditEntriesGenerated: auditService.getCurrentAuditTrail().length },
|
{
|
||||||
|
finalRecommendation: !!recommendation,
|
||||||
|
auditEntriesGenerated: auditService.getCurrentAuditTrail().length,
|
||||||
|
selectedToolsCount: context.selectedTools?.length || 0,
|
||||||
|
backgroundKnowledgeCount: context.backgroundKnowledge?.length || 0
|
||||||
|
},
|
||||||
completedTasks > failedTasks ? 85 : 60,
|
completedTasks > failedTasks ? 85 : 60,
|
||||||
startTime,
|
startTime,
|
||||||
{ totalProcessingTimeMs: Date.now() - startTime }
|
{
|
||||||
|
totalProcessingTimeMs: Date.now() - startTime,
|
||||||
|
aiModel: aiConfig.model,
|
||||||
|
finalTokenUsage: this.totalTokensUsed,
|
||||||
|
pipelineEfficiency: completedTasks / (completedTasks + failedTasks)
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const processingStats = {
|
const processingStats = {
|
||||||
@ -169,7 +233,9 @@ class AIPipeline {
|
|||||||
processingTimeMs: Date.now() - startTime,
|
processingTimeMs: Date.now() - startTime,
|
||||||
microTasksCompleted: completedTasks,
|
microTasksCompleted: completedTasks,
|
||||||
microTasksFailed: failedTasks,
|
microTasksFailed: failedTasks,
|
||||||
contextContinuityUsed: true
|
contextContinuityUsed: true,
|
||||||
|
totalAITokensUsed: this.totalTokensUsed,
|
||||||
|
auditEntriesGenerated: auditService.getCurrentAuditTrail().length
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[AI-PIPELINE] Pipeline completed successfully:', {
|
console.log('[AI-PIPELINE] Pipeline completed successfully:', {
|
||||||
@ -177,7 +243,9 @@ class AIPipeline {
|
|||||||
processingTimeMs: processingStats.processingTimeMs,
|
processingTimeMs: processingStats.processingTimeMs,
|
||||||
completedTasks,
|
completedTasks,
|
||||||
failedTasks,
|
failedTasks,
|
||||||
finalItems: processingStats.finalSelectedItems
|
finalItems: processingStats.finalSelectedItems,
|
||||||
|
totalTokensUsed: this.totalTokensUsed,
|
||||||
|
auditEntries: processingStats.auditEntriesGenerated
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finalize audit trail
|
// Finalize audit trail
|
||||||
@ -193,6 +261,18 @@ class AIPipeline {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AI-PIPELINE] Pipeline failed:', 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,23 +281,63 @@ class AIPipeline {
|
|||||||
context: PipelineContext,
|
context: PipelineContext,
|
||||||
toolsData: any,
|
toolsData: any,
|
||||||
completedTasks: number,
|
completedTasks: number,
|
||||||
failedTasks: number
|
failedTasks: number,
|
||||||
): Promise<void> {
|
pipelineStart: number
|
||||||
|
): Promise<{ completed: number; failed: number }> {
|
||||||
const phases = toolsData.phases || [];
|
const phases = toolsData.phases || [];
|
||||||
|
|
||||||
// Select tools for each phase
|
// Select tools for each phase with enhanced logging
|
||||||
for (const phase of phases) {
|
for (const phase of phases) {
|
||||||
|
const phaseStart = Date.now();
|
||||||
const phaseTools = context.filteredData.tools.filter((tool: any) =>
|
const phaseTools = context.filteredData.tools.filter((tool: any) =>
|
||||||
tool && tool.phases && Array.isArray(tool.phases) && tool.phases.includes(phase.id)
|
tool && tool.phases && Array.isArray(tool.phases) && tool.phases.includes(phase.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const selections = await toolSelector.selectToolsForPhase(context.userQuery, phase, phaseTools, context);
|
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) => {
|
selections.forEach((sel: any) => {
|
||||||
const tool = phaseTools.find((t: any) => t && t.name === sel.toolName);
|
const tool = phaseTools.find((t: any) => t && t.name === sel.toolName);
|
||||||
if (tool) {
|
if (tool) {
|
||||||
const priority = this.derivePriorityFromScore(sel.taskRelevance);
|
const priority = this.derivePriorityFromScore(sel.taskRelevance);
|
||||||
this.addToolToSelection(context, tool, phase.id, priority, sel.justification, sel.taskRelevance, sel.limitations);
|
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);
|
await this.delay(this.config.microTaskDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete underrepresented phases
|
// Complete underrepresented phases with enhanced logging
|
||||||
await this.completeUnderrepresentedPhases(context, toolsData);
|
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);
|
const topTools = context.filteredData.tools.slice(0, 3);
|
||||||
|
|
||||||
for (let i = 0; i < topTools.length; i++) {
|
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++;
|
if (evaluationResult.success) completedTasks++; else failedTasks++;
|
||||||
|
this.trackTokenUsage(evaluationResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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');
|
console.log('[AI-PIPELINE] Micro-task: Scenario analysis');
|
||||||
|
const taskStart = Date.now();
|
||||||
const isWorkflow = context.mode === 'workflow';
|
const isWorkflow = context.mode === 'workflow';
|
||||||
const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery);
|
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 (result.success) {
|
||||||
if (isWorkflow) {
|
if (isWorkflow) {
|
||||||
@ -254,52 +385,109 @@ class AIPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.addToContextHistory(context, `${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
|
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;
|
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');
|
console.log('[AI-PIPELINE] Micro-task: Investigation approach');
|
||||||
|
const taskStart = Date.now();
|
||||||
const isWorkflow = context.mode === 'workflow';
|
const isWorkflow = context.mode === 'workflow';
|
||||||
const prompt = getPrompt('investigationApproach', isWorkflow, context.userQuery);
|
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) {
|
if (result.success) {
|
||||||
context.investigationApproach = result.content;
|
context.investigationApproach = result.content;
|
||||||
this.addToContextHistory(context, `${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz: ${result.content.slice(0, 200)}...`);
|
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;
|
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');
|
console.log('[AI-PIPELINE] Micro-task: Critical considerations');
|
||||||
|
const taskStart = Date.now();
|
||||||
const isWorkflow = context.mode === 'workflow';
|
const isWorkflow = context.mode === 'workflow';
|
||||||
const prompt = getPrompt('criticalConsiderations', isWorkflow, context.userQuery);
|
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) {
|
if (result.success) {
|
||||||
context.criticalConsiderations = result.content;
|
context.criticalConsiderations = result.content;
|
||||||
this.addToContextHistory(context, `Kritische Überlegungen: ${result.content.slice(0, 200)}...`);
|
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;
|
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);
|
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 existingSelection = context.selectedTools?.find((st: any) => st.tool && st.tool.name === tool.name);
|
||||||
const taskRelevance = existingSelection?.taskRelevance || 70;
|
const taskRelevance = existingSelection?.taskRelevance || 70;
|
||||||
const priority = this.derivePriorityFromScore(taskRelevance);
|
const priority = this.derivePriorityFromScore(taskRelevance);
|
||||||
|
|
||||||
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank, 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) {
|
if (result.success) {
|
||||||
const evaluation = this.safeParseJSON(result.content, {
|
// FIXED: Use centralized JSON parsing
|
||||||
|
const evaluation = JSONParser.safeParseJSON(result.content, {
|
||||||
detailed_explanation: 'Evaluation failed',
|
detailed_explanation: 'Evaluation failed',
|
||||||
implementation_approach: '',
|
implementation_approach: '',
|
||||||
pros: [],
|
pros: [],
|
||||||
@ -315,13 +503,35 @@ class AIPipeline {
|
|||||||
task_relevance: taskRelevance
|
task_relevance: taskRelevance
|
||||||
}
|
}
|
||||||
}, 'evaluation', priority, evaluation.detailed_explanation, taskRelevance, evaluation.limitations);
|
}, '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;
|
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');
|
console.log('[AI-PIPELINE] Micro-task: Background knowledge selection');
|
||||||
|
const taskStart = Date.now();
|
||||||
const availableConcepts = context.filteredData.concepts;
|
const availableConcepts = context.filteredData.concepts;
|
||||||
|
|
||||||
if (availableConcepts.length === 0) {
|
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 selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
|
||||||
const prompt = getPrompt('backgroundKnowledgeSelection', context.userQuery, context.mode, selectedToolNames, availableConcepts);
|
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) {
|
if (result.success) {
|
||||||
const selections = this.safeParseJSON(result.content, []);
|
// FIXED: Use centralized JSON parsing
|
||||||
|
const selections = JSONParser.safeParseJSON(result.content, []);
|
||||||
|
|
||||||
if (Array.isArray(selections)) {
|
if (Array.isArray(selections)) {
|
||||||
context.backgroundKnowledge = selections.filter((sel: any) =>
|
context.backgroundKnowledge = selections.filter((sel: any) =>
|
||||||
@ -347,21 +558,66 @@ class AIPipeline {
|
|||||||
concept: availableConcepts.find((c: any) => c.name === sel.conceptName),
|
concept: availableConcepts.find((c: any) => c.name === sel.conceptName),
|
||||||
relevance: sel.relevance
|
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;
|
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');
|
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 selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
|
||||||
const prompt = getPrompt('finalRecommendations', context.mode === 'workflow', context.userQuery, selectedToolNames);
|
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 phases = toolsData.phases || [];
|
||||||
const selectedPhases = new Map<string, number>();
|
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(', '));
|
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) {
|
for (const phase of underrepresentedPhases) {
|
||||||
await this.completePhaseWithSemanticSearch(context, phase, toolsData);
|
await this.completePhaseWithSemanticSearch(context, phase, toolsData, pipelineStart);
|
||||||
await this.delay(this.config.microTaskDelay);
|
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 phaseStart = Date.now();
|
||||||
const phaseQuery = `forensic ${phase.name.toLowerCase()} tools methods`;
|
const phaseQuery = `forensic ${phase.name.toLowerCase()} tools methods`;
|
||||||
|
|
||||||
@ -397,6 +674,19 @@ class AIPipeline {
|
|||||||
try {
|
try {
|
||||||
const phaseResults = await embeddingsService.findSimilar(phaseQuery, 20, 0.2);
|
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) {
|
if (phaseResults.length === 0) {
|
||||||
console.log('[AI-PIPELINE] No semantic results for phase:', phase.id);
|
console.log('[AI-PIPELINE] No semantic results for phase:', phase.id);
|
||||||
return;
|
return;
|
||||||
@ -422,7 +712,7 @@ class AIPipeline {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add tools with justification
|
// Add tools with justification and audit each addition
|
||||||
for (const tool of phaseTools) {
|
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.`;
|
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']
|
['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);
|
console.log('[AI-PIPELINE] Added phase completion tool:', tool.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AI-PIPELINE] Phase completion failed for:', phase.id, 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 || []
|
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 {
|
return {
|
||||||
name: st.tool.name,
|
name: st.tool.name,
|
||||||
type: st.tool.type,
|
type: st.tool.type,
|
||||||
@ -508,6 +839,17 @@ class AIPipeline {
|
|||||||
st.limitations || []
|
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 {
|
return {
|
||||||
name: st.tool.name,
|
name: st.tool.name,
|
||||||
type: st.tool.type,
|
type: st.tool.type,
|
||||||
@ -532,7 +874,12 @@ class AIPipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods
|
// 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();
|
const startTime = Date.now();
|
||||||
|
|
||||||
let contextPrompt = prompt;
|
let contextPrompt = prompt;
|
||||||
@ -549,15 +896,16 @@ class AIPipeline {
|
|||||||
const response = await aiService.callMicroTaskAI(contextPrompt, maxTokens);
|
const response = await aiService.callMicroTaskAI(contextPrompt, maxTokens);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taskType: 'micro-task',
|
taskType,
|
||||||
content: response.content,
|
content: response.content,
|
||||||
processingTimeMs: Date.now() - startTime,
|
processingTimeMs: Date.now() - startTime,
|
||||||
success: true
|
success: true,
|
||||||
|
aiUsage: response.usage
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
taskType: 'micro-task',
|
taskType,
|
||||||
content: '',
|
content: '',
|
||||||
processingTimeMs: Date.now() - startTime,
|
processingTimeMs: Date.now() - startTime,
|
||||||
success: false,
|
success: false,
|
||||||
@ -608,48 +956,21 @@ class AIPipeline {
|
|||||||
return 'low';
|
return 'low';
|
||||||
}
|
}
|
||||||
|
|
||||||
private safeParseJSON(jsonString: string, fallback: any = null): any {
|
private truncateForAudit(text: string, maxLength: number = 200): string {
|
||||||
try {
|
if (typeof text !== 'string') return String(text);
|
||||||
let cleaned = jsonString.trim();
|
if (text.length <= maxLength) return text;
|
||||||
|
return text.slice(0, maxLength) + '...[audit-truncated]';
|
||||||
|
}
|
||||||
|
|
||||||
// Remove code block markers
|
private trackTokenUsage(usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number }): void {
|
||||||
const jsonBlockPatterns = [
|
if (usage?.totalTokens) {
|
||||||
/```json\s*([\s\S]*?)\s*```/i,
|
this.totalTokensUsed += usage.totalTokens;
|
||||||
/```\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 async delay(ms: number): Promise<void> {
|
private async delay(ms: number): Promise<void> {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
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();
|
export const aiPipeline = new AIPipeline();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/utils/auditService.ts - Refactored
|
// src/utils/auditService.ts - Enhanced for forensic-grade transparency
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
function env(key: string, fallback: string | undefined = undefined): string | undefined {
|
function env(key: string, fallback: string | undefined = undefined): string | undefined {
|
||||||
@ -19,7 +19,29 @@ export interface AuditEntry {
|
|||||||
output: any;
|
output: any;
|
||||||
confidence: number;
|
confidence: number;
|
||||||
processingTimeMs: 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 {
|
interface AuditConfig {
|
||||||
@ -87,6 +109,135 @@ class AuditService {
|
|||||||
console.log(`[AUDIT-SERVICE] ${phase}/${action}: ${confidence}% confidence, ${entry.processingTimeMs}ms`);
|
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[] {
|
getCurrentAuditTrail(): AuditEntry[] {
|
||||||
return [...this.activeAuditTrail];
|
return [...this.activeAuditTrail];
|
||||||
}
|
}
|
||||||
@ -135,6 +286,12 @@ class AuditService {
|
|||||||
return data;
|
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 {
|
isEnabled(): boolean {
|
||||||
return this.config.enabled;
|
return this.config.enabled;
|
||||||
}
|
}
|
||||||
@ -143,7 +300,7 @@ class AuditService {
|
|||||||
return { ...this.config };
|
return { ...this.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statistics and analysis methods
|
// Statistics and analysis methods (enhanced)
|
||||||
getAuditStatistics(auditTrail: AuditEntry[]): {
|
getAuditStatistics(auditTrail: AuditEntry[]): {
|
||||||
totalTime: number;
|
totalTime: number;
|
||||||
avgConfidence: number;
|
avgConfidence: number;
|
||||||
@ -151,6 +308,14 @@ class AuditService {
|
|||||||
highConfidenceSteps: number;
|
highConfidenceSteps: number;
|
||||||
lowConfidenceSteps: number;
|
lowConfidenceSteps: number;
|
||||||
phaseBreakdown: Record<string, { count: number; avgConfidence: number; totalTime: 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) {
|
if (!auditTrail || auditTrail.length === 0) {
|
||||||
return {
|
return {
|
||||||
@ -159,7 +324,15 @@ class AuditService {
|
|||||||
stepCount: 0,
|
stepCount: 0,
|
||||||
highConfidenceSteps: 0,
|
highConfidenceSteps: 0,
|
||||||
lowConfidenceSteps: 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 highConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) >= 80).length;
|
||||||
const lowConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) < 60).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
|
// Phase breakdown
|
||||||
const phaseBreakdown: Record<string, { count: number; avgConfidence: number; totalTime: number }> = {};
|
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 {
|
return {
|
||||||
totalTime,
|
totalTime,
|
||||||
avgConfidence,
|
avgConfidence,
|
||||||
stepCount: auditTrail.length,
|
stepCount: auditTrail.length,
|
||||||
highConfidenceSteps,
|
highConfidenceSteps,
|
||||||
lowConfidenceSteps,
|
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
|
// Data type validation
|
||||||
if (typeof entry.confidence !== 'number' || entry.confidence < 0 || entry.confidence > 100) {
|
if (typeof entry.confidence !== 'number' || entry.confidence < 0 || entry.confidence > 100) {
|
||||||
warnings.push(`Entry ${index} has invalid confidence value: ${entry.confidence}`);
|
warnings.push(`Entry ${index} has invalid confidence value: ${entry.confidence}`);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// src/utils/confidenceScoring.ts
|
// src/utils/confidenceScoring.ts
|
||||||
import { isToolHosted } from './toolHelpers.js';
|
import { isToolHosted } from './clientUtils.js';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
export interface ConfidenceMetrics {
|
export interface ConfidenceMetrics {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/utils/jsonUtils.ts
|
// src/utils/jsonUtils.ts - Centralized JSON parsing and utilities
|
||||||
export class JSONParser {
|
export class JSONParser {
|
||||||
static safeParseJSON(jsonString: string, fallback: any = null): any {
|
static safeParseJSON(jsonString: string, fallback: any = null): any {
|
||||||
try {
|
try {
|
||||||
@ -129,4 +129,246 @@ export class JSONParser {
|
|||||||
|
|
||||||
return { selectedTools, selectedConcepts };
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -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';
|
|
@ -2,6 +2,7 @@
|
|||||||
import { aiService } from './aiService.js';
|
import { aiService } from './aiService.js';
|
||||||
import { embeddingsService, type SimilarityResult } from './embeddings.js';
|
import { embeddingsService, type SimilarityResult } from './embeddings.js';
|
||||||
import { confidenceScoring } from './confidenceScoring.js';
|
import { confidenceScoring } from './confidenceScoring.js';
|
||||||
|
import { JSONParser } from './jsonUtils.js'; // FIXED: Use centralized JSON parsing
|
||||||
import { getPrompt } from '../config/prompts.js';
|
import { getPrompt } from '../config/prompts.js';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
@ -248,7 +249,8 @@ class ToolSelector {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await aiService.callAI(prompt, { maxTokens: 2500 });
|
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)) {
|
if (!result || !Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
|
||||||
console.error('[TOOL-SELECTOR] AI selection returned invalid structure');
|
console.error('[TOOL-SELECTOR] AI selection returned invalid structure');
|
||||||
@ -313,7 +315,8 @@ class ToolSelector {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await aiService.callMicroTaskAI(prompt, 1000);
|
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)) {
|
if (Array.isArray(selections)) {
|
||||||
const validSelections = selections.filter((sel: any) => {
|
const validSelections = selections.filter((sel: any) => {
|
||||||
@ -365,90 +368,7 @@ class ToolSelector {
|
|||||||
related_software: concept.related_software || []
|
related_software: concept.related_software || []
|
||||||
});
|
});
|
||||||
|
|
||||||
private safeParseJSON(jsonString: string, fallback: any = null): any {
|
// REMOVED: safeParseJSON method - now using centralized version from jsonUtils.ts
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfig(): ToolSelectionConfig {
|
getConfig(): ToolSelectionConfig {
|
||||||
return { ...this.config };
|
return { ...this.config };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user