Files
forensic-pathways/src/utils/aiPipeline.ts
2025-08-29 14:50:11 +02:00

1415 lines
48 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// src/utils/aiPipeline.ts
import { getCompressedToolsDataForAI, getDataVersion } from './dataService.js';
import { aiService } from './aiService.js';
import { toolSelector, type SelectionContext } from './toolSelector.js';
import { confidenceScoring, type AnalysisContext } from './confidenceScoring.js';
import { embeddingsService } from './embeddings.js';
import { auditService } from './auditService.js';
import { JSONParser } from './jsonUtils.js';
import { getPrompt } from '../config/prompts.js';
interface PipelineConfig {
microTaskDelay: number;
taskRelevanceModeration: {
maxInitialScore: number;
maxWithPhaseBonus: number;
moderationThreshold: number;
};
}
interface MicroTaskResult {
taskType: string;
content: string;
processingTimeMs: number;
success: boolean;
error?: string;
aiUsage?: {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
};
}
interface AnalysisResult {
recommendation: any;
processingStats: {
candidatesFromEmbeddings: number;
finalSelectedItems: number;
processingTimeMs: number;
microTasksCompleted: number;
microTasksFailed: number;
contextContinuityUsed: boolean;
totalAITokensUsed: number;
auditEntriesGenerated: number;
aiModel: string;
toolsDataHash: string;
temperature: number;
maxTokensUsed: number;
};
}
interface PipelineContext {
userQuery: string;
mode: string;
filteredData: any;
contextHistory: string[];
currentContextLength: number;
scenarioAnalysis?: string;
problemAnalysis?: string;
investigationApproach?: string;
criticalConsiderations?: string;
selectedTools?: Array<{
tool: any;
phase: string;
priority: string;
justification?: string;
taskRelevance?: number;
limitations?: string[];
}>;
backgroundKnowledge?: Array<{
concept: any;
relevance: string;
}>;
seenToolNames: Set<string>;
embeddingsSimilarities: Map<string, number>;
phaseMetadata?: {
phases: any[];
criticalPhaseIds: string[];
phaseComplexity: Map<string, number>;
};
}
class AIPipeline {
private config: PipelineConfig;
private totalTokensUsed: number = 0;
constructor() {
this.config = {
microTaskDelay: parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10),
taskRelevanceModeration: {
maxInitialScore: 85,
maxWithPhaseBonus: 95,
moderationThreshold: 80
}
};
}
async processQuery(userQuery: string, mode: string): Promise<AnalysisResult> {
const startTime = Date.now();
let completedTasks = 0;
let failedTasks = 0;
this.totalTokensUsed = 0;
auditService.clearAuditTrail();
try {
const toolsData = await getCompressedToolsDataForAI();
const aiConfig = aiService.getConfig();
const toolsDataHash = getDataVersion?.() || 'unknown';
auditService.addEntry(
'initialization',
'tools-data-loaded',
{
toolsFile: 'tools.yaml',
hashAlgorithm: 'SHA256'
},
{
toolsDataHash: toolsDataHash,
toolsCount: toolsData.tools.length,
conceptsCount: toolsData.concepts.length,
domainsCount: toolsData.domains.length,
phasesCount: toolsData.phases.length
},
100,
Date.now(),
{
toolsDataHash: toolsDataHash,
verification: `Users can verify with: sha256sum src/data/tools.yaml`,
dataVersion: toolsDataHash.slice(0, 12),
reasoning: `Geladen: ${toolsData.tools.length} Tools, ${toolsData.concepts.length} Konzepte aus tools.yaml (Hash: ${toolsDataHash.slice(0, 12)}...)`
}
);
const context: PipelineContext = {
userQuery,
mode,
filteredData: {},
contextHistory: [],
currentContextLength: 0,
seenToolNames: new Set<string>(),
embeddingsSimilarities: new Map<string, number>(),
phaseMetadata: this.initializePhaseMetadata(toolsData.phases)
};
const candidateSelectionStart = Date.now();
const candidateData = await toolSelector.getIntelligentCandidates(userQuery, toolsData, mode, context);
const selectionConfidence = this.calculateToolSelectionConfidence(
candidateData.tools.length,
toolsData.tools.length,
candidateData.concepts.length
);
auditService.addToolSelection(
candidateData.tools.map(t => t.name),
toolsData.tools.map(t => t.name),
selectionConfidence,
candidateSelectionStart,
{
toolsDataHash: toolsDataHash,
totalCandidatesFound: candidateData.tools.length + candidateData.concepts.length,
reductionRatio: candidateData.tools.length / toolsData.tools.length
}
);
context.filteredData = candidateData;
const analysisResult = await this.analyzeScenario(context, startTime, toolsDataHash);
if (analysisResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(analysisResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const approachResult = await this.generateApproach(context, startTime, toolsDataHash);
if (approachResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(approachResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const considerationsResult = await this.generateCriticalConsiderations(context, startTime, toolsDataHash);
if (considerationsResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(considerationsResult.aiUsage);
await this.delay(this.config.microTaskDelay);
if (mode === 'workflow') {
const workflowResults = await this.processWorkflowMode(context, toolsData, completedTasks, failedTasks, startTime, toolsDataHash);
completedTasks = workflowResults.completed;
failedTasks = workflowResults.failed;
} else {
const toolResults = await this.processToolMode(context, completedTasks, failedTasks, startTime, toolsDataHash);
completedTasks = toolResults.completed;
failedTasks = toolResults.failed;
}
const knowledgeResult = await this.selectBackgroundKnowledge(context, startTime, toolsDataHash);
if (knowledgeResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(knowledgeResult.aiUsage);
await this.delay(this.config.microTaskDelay);
const finalResult = await this.generateFinalRecommendations(context, startTime, toolsDataHash);
if (finalResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(finalResult.aiUsage);
const recommendation = this.buildRecommendation(context, mode, finalResult.content, toolsDataHash);
const processingStats = {
candidatesFromEmbeddings: candidateData.tools.length,
finalSelectedItems: (context.selectedTools?.length || 0) + (context.backgroundKnowledge?.length || 0),
processingTimeMs: Date.now() - startTime,
microTasksCompleted: completedTasks,
microTasksFailed: failedTasks,
contextContinuityUsed: true,
totalAITokensUsed: this.totalTokensUsed,
auditEntriesGenerated: auditService.getCurrentAuditTrail().length,
aiModel: aiConfig.model,
toolsDataHash,
temperature: 0.3,
maxTokensUsed: 32768
};
const finalAuditTrail = auditService.finalizeAuditTrail();
return {
recommendation: {
...recommendation,
auditTrail: auditService.isEnabled() ? finalAuditTrail : undefined,
processingStats
},
processingStats
};
} catch (error) {
console.error('[AI-PIPELINE] Pipeline failed:', error);
throw error;
}
}
private initializePhaseMetadata(phases: any[]): {
phases: any[];
criticalPhaseIds: string[];
phaseComplexity: Map<string, number>;
} {
if (!phases || !Array.isArray(phases)) {
return {
phases: [],
criticalPhaseIds: [],
phaseComplexity: new Map()
};
}
const criticalPhaseIds = phases
.filter(phase => {
const typicalToolsCount = phase.typical_tools?.length || 0;
const keyActivitiesCount = phase.key_activities?.length || 0;
return typicalToolsCount >= 3 || keyActivitiesCount >= 2;
})
.map(phase => phase.id);
const phaseComplexity = new Map<string, number>();
phases.forEach(phase => {
let complexity = 1;
if (phase.typical_tools?.length > 5) complexity += 1;
if (phase.key_activities?.length > 3) complexity += 1;
if (phase.description?.length > 100) complexity += 1;
phaseComplexity.set(phase.id, complexity);
});
return {
phases,
criticalPhaseIds,
phaseComplexity
};
}
private calculateToolSelectionConfidence(
selectedCount: number,
totalCount: number,
conceptsCount: number
): number {
let confidence = 50;
const selectionRatio = selectedCount / totalCount;
if (selectionRatio >= 0.05 && selectionRatio <= 0.20) {
confidence += 25;
} else if (selectionRatio < 0.05) {
confidence += 15;
} else if (selectionRatio > 0.30) {
confidence -= 15;
}
if (conceptsCount > 0) confidence += 10;
if (selectedCount >= 8 && selectedCount <= 25) confidence += 10;
return Math.min(95, Math.max(40, confidence));
}
private async processWorkflowMode(
context: PipelineContext,
toolsData: any,
completedTasks: number,
failedTasks: number,
pipelineStart: number,
toolsDataHash: string
): Promise<{ completed: number; failed: number }> {
const phases = toolsData.phases || [];
for (const phase of phases) {
const phaseStart = Date.now();
const phaseTools = context.filteredData.tools.filter((tool: any) =>
tool && tool.phases && Array.isArray(tool.phases) && tool.phases.includes(phase.id)
);
if (phaseTools.length === 0) continue;
const selections = await toolSelector.selectToolsForPhase(context.userQuery, phase, phaseTools, context);
const phaseConfidence = this.calculatePhaseSelectionConfidence(
selections.length,
phaseTools.length,
phase.id,
selections,
context.phaseMetadata
);
auditService.addEntry(
'workflow-phase',
'phase-tool-selection',
{
phaseId: phase.id,
phaseName: phase.name,
availableTools: phaseTools.map(t => t.name),
toolCount: phaseTools.length
},
{
selectedTools: selections.map(s => s.toolName),
selectionCount: selections.length,
avgTaskRelevance: selections.length > 0 ?
Math.round(selections.reduce((sum, s) => sum + (s.taskRelevance || 70), 0) / selections.length) : 0
},
phaseConfidence,
phaseStart,
{
toolsDataHash: toolsDataHash,
phaseId: phase.id,
availableToolsCount: phaseTools.length,
selectedToolsCount: selections.length,
microTaskType: 'phase-tool-selection',
reasoning: `${selections.length} von ${phaseTools.length} verfügbaren Tools für ${phase.name} ausgewählt - KI bewertete Eignung für spezifische Phasenaufgaben`
}
);
selections.forEach((sel: any) => {
const tool = phaseTools.find((t: any) => t && t.name === sel.toolName);
if (tool) {
const moderatedTaskRelevance = this.moderateTaskRelevance(sel.taskRelevance);
const priority = this.derivePriorityFromScore(moderatedTaskRelevance);
const dynamicLimitations = this.generateDynamicLimitations(tool, phase, sel);
this.addToolToSelection(context, tool, phase.id, priority, sel.justification, moderatedTaskRelevance, dynamicLimitations);
auditService.addEntry(
'tool-reasoning',
'tool-added-to-phase',
{
toolName: tool.name,
phaseId: phase.id,
taskRelevance: moderatedTaskRelevance,
priority: priority
},
{
justification: sel.justification,
limitations: dynamicLimitations,
addedToPhase: phase.name
},
moderatedTaskRelevance || 70,
phaseStart,
{
toolsDataHash: toolsDataHash,
toolType: tool.type,
priority,
moderationApplied: sel.taskRelevance !== moderatedTaskRelevance,
reasoning: `${tool.name} als ${priority}-Priorität für ${phase.name} ausgewählt: ${sel.justification?.slice(0, 100)}...`
}
);
}
});
if (selections.length > 0) completedTasks++; else failedTasks++;
await this.delay(this.config.microTaskDelay);
}
const completionResult = await this.completeUnderrepresentedPhases(context, toolsData, pipelineStart, toolsDataHash);
completedTasks += completionResult.completed;
failedTasks += completionResult.failed;
return { completed: completedTasks, failed: failedTasks };
}
private calculatePhaseSelectionConfidence(
selectedCount: number,
availableCount: number,
phaseId: string,
selections: any[],
phaseMetadata?: {
phases: any[];
criticalPhaseIds: string[];
phaseComplexity: Map<string, number>;
}
): number {
let confidence = 60;
const isCritical = phaseMetadata?.criticalPhaseIds.includes(phaseId) || false;
const phaseComplexity = phaseMetadata?.phaseComplexity.get(phaseId) || 1;
if (selectedCount > 0) {
confidence += 20;
} else {
return 30;
}
const ratio = selectedCount / availableCount;
if (ratio >= 0.2 && ratio <= 0.5) {
confidence += 15;
} else if (ratio < 0.2 && selectedCount >= 1) {
confidence += 10;
}
if (isCritical && selectedCount >= 2) confidence += 10;
if (phaseComplexity > 2 && selectedCount >= phaseComplexity) confidence += 5;
const avgRelevance = selections.length > 0 ?
selections.reduce((sum, s) => sum + (s.taskRelevance || 70), 0) / selections.length : 0;
if (avgRelevance >= 75) {
confidence += 10;
} else if (avgRelevance >= 65) {
confidence += 5;
}
return Math.min(95, Math.max(30, confidence));
}
private generateDynamicLimitations(tool: any, phase: any, selection: any): string[] {
const limitations: string[] = [];
if (selection.limitations && Array.isArray(selection.limitations)) {
limitations.push(...selection.limitations);
}
if (tool.type === 'software' && !tool.projectUrl) {
limitations.push('Installation und Konfiguration erforderlich');
}
if (tool.skillLevel === 'expert') {
limitations.push('Erfordert spezialisierte Kenntnisse');
}
if (tool.license === 'Proprietary') {
limitations.push('Kommerzielle Lizenz erforderlich');
}
if (phase.id === 'acquisition' && tool.type === 'method') {
limitations.push('Sorgfältige Dokumentation für Chain of Custody erforderlich');
}
return limitations.slice(0, 3);
}
private async processToolMode(
context: PipelineContext,
completedTasks: number,
failedTasks: number,
pipelineStart: number,
toolsDataHash: string
): Promise<{ completed: number; failed: number }> {
const candidates = context.filteredData.tools || [];
if (!Array.isArray(candidates) || candidates.length === 0) {
return { completed: completedTasks, failed: failedTasks };
}
for (let i = 0; i < candidates.length; i++) {
const evaluationResult = await this.evaluateSpecificTool(context, candidates[i], i + 1, pipelineStart, toolsDataHash);
if (evaluationResult.success) completedTasks++; else failedTasks++;
this.trackTokenUsage(evaluationResult.aiUsage);
await this.delay(this.config.microTaskDelay);
}
if (Array.isArray(context.selectedTools) && context.selectedTools.length > 0) {
context.selectedTools.sort((a: any, b: any) => {
const ar = typeof a.taskRelevance === 'number' ? a.taskRelevance : -1;
const br = typeof b.taskRelevance === 'number' ? b.taskRelevance : -1;
if (br !== ar) return br - ar;
const aLen = (a.justification || '').length;
const bLen = (b.justification || '').length;
if (bLen !== aLen) return bLen - aLen;
const aRank = a.tool?.evaluation?.rank ?? Number.MAX_SAFE_INTEGER;
const bRank = b.tool?.evaluation?.rank ?? Number.MAX_SAFE_INTEGER;
return aRank - bRank;
});
context.selectedTools = context.selectedTools.slice(0, 3);
}
return { completed: completedTasks, failed: failedTasks };
}
private async completeUnderrepresentedPhases(
context: PipelineContext,
toolsData: any,
pipelineStart: number,
toolsDataHash: string
): Promise<{ completed: number; failed: number }> {
const phases = toolsData.phases || [];
const selectedPhases = new Map<string, number>();
let completedTasks = 0;
let failedTasks = 0;
context.selectedTools?.forEach((st: any) => {
const count = selectedPhases.get(st.phase) || 0;
selectedPhases.set(st.phase, count + 1);
});
const underrepresentedPhases = phases.filter((phase: any) => {
const count = selectedPhases.get(phase.id) || 0;
return count <= 1;
});
if (underrepresentedPhases.length === 0) return { completed: 0, failed: 0 };
for (const phase of underrepresentedPhases) {
const result = await this.completePhaseWithSemanticSearchAndAI(context, phase, toolsData, pipelineStart, toolsDataHash);
if (result.success) completedTasks++; else failedTasks++;
await this.delay(this.config.microTaskDelay);
}
return { completed: completedTasks, failed: failedTasks };
}
private async completePhaseWithSemanticSearchAndAI(
context: PipelineContext,
phase: any,
toolsData: any,
pipelineStart: number,
toolsDataHash: string
): Promise<MicroTaskResult> {
const phaseStart = Date.now();
const phaseQuery = `forensic ${phase.name.toLowerCase()} tools methods`;
try {
const phaseResults = await embeddingsService.findSimilar(phaseQuery, 20, 0.2);
auditService.addEmbeddingsSearch(
phaseQuery,
phaseResults,
0.2,
phaseStart,
{
toolsDataHash: toolsDataHash,
phaseId: phase.id,
phaseName: phase.name,
completionPurpose: 'underrepresented-phase-enhancement'
}
);
if (phaseResults.length === 0) {
return {
taskType: 'phase-completion',
content: '',
processingTimeMs: Date.now() - phaseStart,
success: true
};
}
const toolsMap = new Map(toolsData.tools.map((tool: any) => [tool.name, tool]));
const conceptsMap = new Map(toolsData.concepts.map((concept: any) => [concept.name, concept]));
const phaseTools = phaseResults
.filter((result: any) => result.type === 'tool')
.map((result: any) => toolsMap.get(result.name))
.filter((tool: any): tool is NonNullable<any> =>
tool !== undefined &&
tool !== null &&
tool.phases &&
Array.isArray(tool.phases) &&
tool.phases.includes(phase.id) &&
!context.seenToolNames.has(tool.name)
)
.slice(0, 5);
const phaseConcepts = phaseResults
.filter((result: any) => result.type === 'concept')
.map((result: any) => conceptsMap.get(result.name))
.filter((concept: any): concept is NonNullable<any> => concept !== undefined && concept !== null)
.slice(0, 2);
if (phaseTools.length === 0) {
return {
taskType: 'phase-completion',
content: '',
processingTimeMs: Date.now() - phaseStart,
success: true
};
}
const selectionPrompt = getPrompt('generatePhaseCompletionPrompt', context.userQuery, phase, phaseTools, phaseConcepts);
const selectionResult = await this.callMicroTaskAI(selectionPrompt, context, 'phase-completion-selection');
if (!selectionResult.success) {
return {
taskType: 'phase-completion',
content: '',
processingTimeMs: Date.now() - phaseStart,
success: false,
error: 'Selection micro-task failed'
};
}
const selection = JSONParser.safeParseJSON(selectionResult.content, {
selectedTools: [],
selectedConcepts: [],
completionReasoning: ''
});
const validTools = selection.selectedTools
.map((name: string) => phaseTools.find((t: any) => t && t.name === name))
.filter((tool: any): tool is NonNullable<any> => tool !== undefined && tool !== null)
.slice(0, 2);
if (validTools.length === 0) {
return {
taskType: 'phase-completion',
content: selection.completionReasoning || '',
processingTimeMs: Date.now() - phaseStart,
success: true
};
}
const actualToolsAdded = validTools.map(tool => tool.name);
for (const tool of validTools) {
const reasoningPrompt = getPrompt(
'phaseCompletionReasoning',
context.userQuery,
phase,
tool.name,
tool,
selection.completionReasoning || 'Nachergänzung zur Vervollständigung der Phasenabdeckung'
);
const reasoningResult = await this.callMicroTaskAI(reasoningPrompt, context, 'phase-completion-reasoning');
let detailedJustification: string;
let moderatedTaskRelevance = 75;
if (reasoningResult.success && reasoningResult.content.trim()) {
detailedJustification = reasoningResult.content.trim();
moderatedTaskRelevance = this.moderateTaskRelevance(80);
} else {
detailedJustification = `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.`;
moderatedTaskRelevance = this.moderateTaskRelevance(75);
}
const dynamicLimitations = this.generateCompletionLimitations(tool, phase, selection);
this.addToolToSelection(
context,
tool,
phase.id,
'medium',
detailedJustification,
moderatedTaskRelevance,
dynamicLimitations
);
}
auditService.addPhaseCompletion(
phase.id,
actualToolsAdded,
selection.completionReasoning || `${actualToolsAdded.length} Tools für ${phase.name} hinzugefügt`,
phaseStart,
{
toolsDataHash: toolsDataHash,
toolsAdded: actualToolsAdded,
toolType: validTools[0]?.type,
semanticSimilarity: phaseResults.find(r => r.name === validTools[0]?.name)?.similarity,
completionReason: 'underrepresented-phase',
originalSelectionMissed: true,
aiReasoningUsed: true,
moderatedTaskRelevance: 75
}
);
return {
taskType: 'phase-completion',
content: selection.completionReasoning || '',
processingTimeMs: Date.now() - phaseStart,
success: true
};
} catch (error) {
return {
taskType: 'phase-completion',
content: '',
processingTimeMs: Date.now() - phaseStart,
success: false,
error: error.message
};
}
}
private generateCompletionLimitations(tool: any, phase: any, selection: any): string[] {
const limitations: string[] = [];
limitations.push('Nachträgliche Ergänzung - ursprünglich nicht als Hauptmethode identifiziert');
if (tool.skillLevel === 'expert') {
limitations.push('Erfordert Expertenwissen für optimale Nutzung');
}
if (tool.type === 'software' && !tool.projectUrl) {
limitations.push('Zusätzliche Setup-Zeit erforderlich');
}
if (phase.typical_tools && !phase.typical_tools.includes(tool.name)) {
limitations.push(`Nicht typisch für ${phase.name}-Phase - alternative Anwendung`);
}
return limitations.slice(0, 3);
}
private moderateTaskRelevance(taskRelevance: number): number {
if (typeof taskRelevance !== 'number') return 70;
let moderated = Math.min(taskRelevance, this.config.taskRelevanceModeration.maxInitialScore);
if (moderated > this.config.taskRelevanceModeration.moderationThreshold) {
const excess = moderated - this.config.taskRelevanceModeration.moderationThreshold;
moderated = this.config.taskRelevanceModeration.moderationThreshold + (excess * 0.7);
}
return Math.round(Math.min(moderated, this.config.taskRelevanceModeration.maxInitialScore));
}
private async analyzeScenario(context: PipelineContext, pipelineStart: number, toolsDataHash: string): Promise<MicroTaskResult> {
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 'scenario-analysis');
if (result.success) {
if (isWorkflow) {
context.scenarioAnalysis = result.content;
} else {
context.problemAnalysis = result.content;
}
this.addToContextHistory(context, `${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
const confidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 50, max: 300 },
'scenario-analysis'
);
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
confidence,
`Analysierte ${isWorkflow ? 'Szenario' : 'Problem'} basierend auf Nutzereingabe: "${context.userQuery.slice(0, 100)}..." - Identifizierte Kernaspekte und Herausforderungen für Untersuchung`,
taskStart,
{
toolsDataHash: toolsDataHash,
microTaskType: 'scenario-analysis',
analysisType: isWorkflow ? 'scenario' : 'problem',
contentLength: result.content.length,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...result.aiUsage
}
);
}
return result;
}
private async generateApproach(context: PipelineContext, pipelineStart: number, toolsDataHash: string): Promise<MicroTaskResult> {
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('investigationApproach', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 'investigation-approach');
if (result.success) {
context.investigationApproach = result.content;
this.addToContextHistory(context, `${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz: ${result.content.slice(0, 200)}...`);
const confidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 50, max: 300 },
'investigation-approach'
);
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
confidence,
`Entwickelte ${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz unter Berücksichtigung der Szenario-Analyse - Strukturierte Herangehensweise für forensische Methodik`,
taskStart,
{
toolsDataHash: toolsDataHash,
microTaskType: 'investigation-approach',
approachType: isWorkflow ? 'investigation' : 'solution',
contentLength: result.content.length,
contextHistoryLength: context.contextHistory.length,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...result.aiUsage
}
);
}
return result;
}
private async generateCriticalConsiderations(context: PipelineContext, pipelineStart: number, toolsDataHash: string): Promise<MicroTaskResult> {
const taskStart = Date.now();
const isWorkflow = context.mode === 'workflow';
const prompt = getPrompt('criticalConsiderations', isWorkflow, context.userQuery);
const result = await this.callMicroTaskAI(prompt, context, 'critical-considerations');
if (result.success) {
context.criticalConsiderations = result.content;
this.addToContextHistory(context, `Kritische Überlegungen: ${result.content.slice(0, 200)}...`);
const confidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 40, max: 250 },
'critical-considerations'
);
auditService.addAIDecision(
'contextual-analysis',
prompt,
result.content,
confidence,
'Identifizierte kritische Überlegungen für forensische Untersuchung - Berücksichtigung von Beweissicherung, Chain of Custody und methodischen Herausforderungen',
taskStart,
{
toolsDataHash: toolsDataHash,
microTaskType: 'critical-considerations',
contentLength: result.content.length,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...result.aiUsage
}
);
}
return result;
}
private async evaluateSpecificTool(
context: PipelineContext,
tool: any,
rank: number,
pipelineStart: number,
toolsDataHash: string
): Promise<MicroTaskResult> {
const taskStart = Date.now();
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank);
const result = await this.callMicroTaskAI(prompt, context, 'tool-evaluation');
if (!result.success) {
return result;
}
const evaluation = JSONParser.safeParseJSON(result.content, null);
const aiProvided = evaluation && typeof evaluation.taskRelevance === 'number' && Number.isFinite(evaluation.taskRelevance)
? Math.round(evaluation.taskRelevance)
: null;
if (aiProvided === null) {
auditService.addAIDecision(
'tool-evaluation',
prompt,
result.content,
0,
`Bewertung für "${tool.name}" ignoriert: fehlender/ungültiger taskRelevance`,
taskStart,
{
toolsDataHash,
microTaskType: 'tool-evaluation',
toolName: tool.name,
toolType: tool.type,
rank,
evaluationParsed: false,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...(result.aiUsage || {})
}
);
return result;
}
const moderatedTaskRelevance = this.moderateTaskRelevance(aiProvided);
const priority = this.derivePriorityFromScore(moderatedTaskRelevance);
const detailed_explanation = String(evaluation?.detailed_explanation || '').trim();
const implementation_approach = String(evaluation?.implementation_approach || '').trim();
const pros = Array.isArray(evaluation?.pros) ? evaluation.pros : [];
const limitations = Array.isArray(evaluation?.limitations) ? evaluation.limitations : [];
const alternatives = String(evaluation?.alternatives || '').trim();
this.addToolToSelection(
context,
{
...tool,
evaluation: {
detailed_explanation,
implementation_approach,
pros,
limitations,
alternatives,
rank,
task_relevance: moderatedTaskRelevance
}
},
'evaluation',
priority,
detailed_explanation,
moderatedTaskRelevance,
limitations
);
const responseConfidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 200, max: 800 },
'tool-evaluation'
);
const finalConfidence = Math.max(responseConfidence, moderatedTaskRelevance);
auditService.addAIDecision(
'tool-evaluation',
prompt,
result.content,
finalConfidence,
`Bewertete Tool "${tool.name}" (Rang ${rank}) AI-Score ${aiProvided}, moderiert ${moderatedTaskRelevance}`,
taskStart,
{
toolsDataHash,
microTaskType: 'tool-evaluation',
toolName: tool.name,
toolType: tool.type,
rank,
aiProvidedTaskRelevance: aiProvided,
moderatedTaskRelevance,
responseConfidence,
finalConfidence,
moderationApplied: aiProvided !== moderatedTaskRelevance,
evaluationParsed: true,
prosCount: pros.length,
limitationsCount: limitations.length,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...(result.aiUsage || {})
}
);
return result;
}
private async selectBackgroundKnowledge(context: PipelineContext, pipelineStart: number, toolsDataHash: string): Promise<MicroTaskResult> {
const taskStart = Date.now();
const availableConcepts = context.filteredData.concepts;
if (availableConcepts.length === 0) {
return {
taskType: 'background-knowledge',
content: JSON.stringify([]),
processingTimeMs: 0,
success: true
};
}
const selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
const prompt = getPrompt('backgroundKnowledgeSelection', context.userQuery, context.mode, selectedToolNames, availableConcepts);
const result = await this.callMicroTaskAI(prompt, context, 'background-knowledge');
if (result.success) {
const selections = JSONParser.safeParseJSON(result.content, []);
if (Array.isArray(selections)) {
context.backgroundKnowledge = selections.filter((sel: any) =>
sel.conceptName && availableConcepts.some((concept: any) => concept.name === sel.conceptName)
).map((sel: any) => ({
concept: availableConcepts.find((c: any) => c.name === sel.conceptName),
relevance: sel.relevance
}));
const responseConfidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 100, max: 500 },
'background-knowledge'
);
const selectionQualityBonus = this.calculateKnowledgeSelectionBonus(context.backgroundKnowledge, availableConcepts);
const finalConfidence = Math.min(95, responseConfidence + selectionQualityBonus);
auditService.addEntry(
'knowledge-synthesis',
'concept-selection',
{
availableConcepts: availableConcepts.map(c => c.name),
selectedToolsContext: selectedToolNames,
selectionCriteria: 'methodische Fundierung'
},
{
selectedConcepts: context.backgroundKnowledge.map(bk => bk.concept.name),
selectionReasonings: context.backgroundKnowledge.map(bk => bk.relevance)
},
finalConfidence,
taskStart,
{
toolsDataHash: toolsDataHash,
microTaskType: 'background-knowledge',
availableConceptsCount: availableConcepts.length,
selectedConceptsCount: context.backgroundKnowledge.length,
selectionRatio: context.backgroundKnowledge.length / availableConcepts.length,
responseConfidence,
selectionQualityBonus,
decisionBasis: 'ai-analysis',
reasoning: `Wählte ${context.backgroundKnowledge.length} von ${availableConcepts.length} verfügbaren Konzepten für methodische Fundierung der Empfehlungen`,
aiModel: aiService.getConfig().model,
...result.aiUsage
}
);
}
}
return result;
}
private calculateKnowledgeSelectionBonus(
selectedKnowledge: Array<{concept: any; relevance: string}>,
availableConcepts: any[]
): number {
let bonus = 0;
if (selectedKnowledge.length > 0) bonus += 10;
const ratio = selectedKnowledge.length / availableConcepts.length;
if (ratio >= 0.1 && ratio <= 0.3) bonus += 15;
const hasGoodReasonings = selectedKnowledge.some(bk =>
bk.relevance && bk.relevance.length > 30
);
if (hasGoodReasonings) bonus += 10;
return bonus;
}
private async generateFinalRecommendations(context: PipelineContext, pipelineStart: number, toolsDataHash: string): Promise<MicroTaskResult> {
const taskStart = Date.now();
const selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
const prompt = getPrompt('finalRecommendations', context.mode === 'workflow', context.userQuery, selectedToolNames);
const result = await this.callMicroTaskAI(prompt, context, 'final-recommendations');
if (result.success) {
const confidence = auditService.calculateAIResponseConfidence(
result.content,
{ min: 60, max: 250 },
'final-recommendations'
);
const contextBonus = this.calculateSynthesisBonus(selectedToolNames, context);
const finalConfidence = Math.min(95, confidence + contextBonus);
auditService.addAIDecision(
'synthesis',
prompt,
result.content,
finalConfidence,
`Generierte abschließende ${context.mode}-Empfehlungen basierend auf ausgewählten ${selectedToolNames.length} Tools - Synthese aller Analyseschritte zu kohärenter Handlungsempfehlung`,
taskStart,
{
toolsDataHash: toolsDataHash,
microTaskType: 'final-recommendations',
mode: context.mode,
selectedToolsCount: selectedToolNames.length,
contentLength: result.content.length,
responseConfidence: confidence,
contextBonus,
decisionBasis: 'ai-analysis',
aiModel: aiService.getConfig().model,
...result.aiUsage
}
);
}
return result;
}
private calculateSynthesisBonus(selectedToolNames: string[], context: PipelineContext): number {
let bonus = 0;
if (selectedToolNames.length >= 3) bonus += 10;
if (context.backgroundKnowledge && context.backgroundKnowledge.length > 0) bonus += 10;
if (context.scenarioAnalysis || context.problemAnalysis) bonus += 5;
if (context.investigationApproach) bonus += 5;
return bonus;
}
private buildRecommendation(context: PipelineContext, mode: string, finalContent: string, toolsDataHash: string): any {
const isWorkflow = mode === 'workflow';
const base = {
[isWorkflow ? 'scenario_analysis' : 'problem_analysis']:
isWorkflow ? context.scenarioAnalysis : context.problemAnalysis,
investigation_approach: context.investigationApproach,
critical_considerations: context.criticalConsiderations,
background_knowledge: context.backgroundKnowledge?.map((bk: any) => ({
concept_name: bk.concept.name,
relevance: bk.relevance
})) || []
};
if (isWorkflow) {
const recommendedToolsWithConfidence = context.selectedTools?.map((st: any) => {
const analysisContext: AnalysisContext = {
userQuery: context.userQuery,
mode: context.mode,
embeddingsSimilarities: context.embeddingsSimilarities,
selectedTools: context.selectedTools
};
const confidence = confidenceScoring.calculateRecommendationConfidence(
st.tool,
analysisContext,
st.taskRelevance || 70,
st.limitations || []
);
auditService.addConfidenceCalculation(
st.tool.name,
confidence,
Date.now(),
{
toolsDataHash: toolsDataHash,
phase: st.phase,
priority: st.priority,
toolType: st.tool.type,
moderatedTaskRelevance: st.taskRelevance
}
);
return {
name: st.tool.name,
type: st.tool.type,
phase: st.phase,
priority: st.priority,
justification: st.justification || `Empfohlen für ${st.phase}`,
confidence: confidence,
recommendationStrength: confidenceScoring.getConfidenceLevel(confidence.overall)
};
}) || [];
return {
...base,
recommended_tools: recommendedToolsWithConfidence,
workflow_suggestion: finalContent
};
} else {
const recommendedToolsWithConfidence = context.selectedTools?.map((st: any) => {
const analysisContext: AnalysisContext = {
userQuery: context.userQuery,
mode: context.mode,
embeddingsSimilarities: context.embeddingsSimilarities,
selectedTools: context.selectedTools
};
const confidence = confidenceScoring.calculateRecommendationConfidence(
st.tool,
analysisContext,
st.taskRelevance || 70,
st.limitations || []
);
auditService.addConfidenceCalculation(
st.tool.name,
confidence,
Date.now(),
{
toolsDataHash: toolsDataHash,
rank: st.tool.evaluation?.rank || 1,
toolType: st.tool.type,
moderatedTaskRelevance: st.taskRelevance
}
);
return {
name: st.tool.name,
type: st.tool.type,
rank: st.tool.evaluation?.rank || 1,
suitability_score: st.priority,
detailed_explanation: st.tool.evaluation?.detailed_explanation || '',
implementation_approach: st.tool.evaluation?.implementation_approach || '',
pros: st.tool.evaluation?.pros || [],
cons: st.tool.evaluation?.limitations || [],
alternatives: st.tool.evaluation?.alternatives || '',
confidence: confidence,
recommendationStrength: confidenceScoring.getConfidenceLevel(confidence.overall)
};
}) || [];
return {
...base,
recommended_tools: recommendedToolsWithConfidence,
additional_considerations: finalContent
};
}
}
private async callMicroTaskAI(
prompt: string,
context: PipelineContext,
taskType: string = 'micro-task'
): Promise<MicroTaskResult> {
const startTime = Date.now();
let contextPrompt = prompt;
if (context.contextHistory.length > 0) {
const contextSection = `BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n`;
contextPrompt = contextSection + prompt;
}
try {
const response = await aiService.callMicroTaskAI(contextPrompt);
const toolsDataHash = getDataVersion?.() || 'unknown';
const aiConfig = aiService.getConfig();
const responseConfidence = auditService.calculateAIResponseConfidence(
response.content,
this.getExpectedLengthForTaskType(taskType),
taskType
);
auditService.addAIDecision(
this.getPhaseForTaskType(taskType),
prompt,
response.content,
responseConfidence,
this.getReasoningForTaskType(taskType, response.content),
startTime,
{
toolsDataHash: toolsDataHash,
microTaskType: taskType,
aiModel: aiConfig.model,
contextLength: contextPrompt.length,
originalPromptLength: prompt.length,
contextHistoryUsed: context.contextHistory.length > 0,
decisionBasis: 'ai-analysis',
...response.usage
}
);
return {
taskType,
content: response.content,
processingTimeMs: Date.now() - startTime,
success: true,
aiUsage: response.usage
};
} catch (error) {
auditService.addEntry(
this.getPhaseForTaskType(taskType),
'ai-decision-failed',
{
prompt: prompt.slice(0, 200) + '...',
taskType: taskType,
error: error.message
},
{
error: error.message,
success: false
},
0,
startTime,
{
toolsDataHash: getDataVersion?.() || 'unknown',
microTaskType: taskType,
failed: true,
decisionBasis: 'ai-analysis'
}
);
return {
taskType,
content: '',
processingTimeMs: Date.now() - startTime,
success: false,
error: error.message
};
}
}
private getPhaseForTaskType(taskType: string): string {
const phaseMap: Record<string, string> = {
'scenario-analysis': 'contextual-analysis',
'investigation-approach': 'contextual-analysis',
'critical-considerations': 'contextual-analysis',
'tool-evaluation': 'tool-evaluation',
'background-knowledge': 'knowledge-synthesis',
'final-recommendations': 'synthesis',
'phase-completion-selection': 'phase-completion',
'phase-completion-reasoning': 'phase-completion'
};
return phaseMap[taskType] || 'contextual-analysis';
}
private getExpectedLengthForTaskType(taskType: string): { min: number; max: number } {
const lengthMap: Record<string, { min: number; max: number }> = {
'scenario-analysis': { min: 100, max: 500 },
'investigation-approach': { min: 100, max: 400 },
'critical-considerations': { min: 80, max: 300 },
'tool-evaluation': { min: 200, max: 800 },
'background-knowledge': { min: 50, max: 300 },
'final-recommendations': { min: 150, max: 600 },
'phase-completion-selection': { min: 50, max: 200 },
'phase-completion-reasoning': { min: 100, max: 300 }
};
return lengthMap[taskType] || { min: 50, max: 300 };
}
private getReasoningForTaskType(taskType: string, response: string): string {
const responseLength = response.length;
const taskNames: Record<string, string> = {
'scenario-analysis': 'Szenario-Analyse',
'investigation-approach': 'Untersuchungsansatz',
'critical-considerations': 'Kritische Überlegungen',
'tool-evaluation': 'Tool-Bewertung',
'background-knowledge': 'Hintergrundwissen-Auswahl',
'final-recommendations': 'Abschließende Empfehlungen',
'phase-completion-selection': 'Phasen-Vervollständigung',
'phase-completion-reasoning': 'Phasen-Begründung'
};
const taskName = taskNames[taskType] || taskType;
return `KI generierte ${taskName} (${responseLength} Zeichen) - Analyse mit methodischer Begründung`;
}
private addToContextHistory(context: PipelineContext, newEntry: string): void {
const entryTokens = aiService.estimateTokens(newEntry);
context.contextHistory.push(newEntry);
context.currentContextLength += entryTokens;
if (context.contextHistory.length > 1) {
const removed = context.contextHistory.shift()!;
context.currentContextLength -= aiService.estimateTokens(removed);
}
}
private addToolToSelection(
context: PipelineContext,
tool: any,
phase: string,
priority: string,
justification?: string,
taskRelevance?: number,
limitations?: string[]
): boolean {
context.seenToolNames.add(tool.name);
if (!context.selectedTools) context.selectedTools = [];
context.selectedTools.push({
tool,
phase,
priority,
justification,
taskRelevance,
limitations
});
return true;
}
private derivePriorityFromScore(taskRelevance: number): string {
if (taskRelevance >= 80) return 'high';
if (taskRelevance >= 60) return 'medium';
return 'low';
}
private trackTokenUsage(usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number }): void {
if (usage?.totalTokens) {
this.totalTokensUsed += usage.totalTokens;
}
}
private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export const aiPipeline = new AIPipeline();
export type { AnalysisResult };