forensic-pathways/src/utils/aiPipeline.ts
overcuriousity 8c9bdf0710 vector index
2025-08-01 13:24:43 +02:00

659 lines
24 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 } from './dataService.js';
import { embeddingsService, type EmbeddingData } from './embeddings.js';
import { vectorIndex } from "./vectorIndex.js";
interface AIConfig {
endpoint: string;
apiKey: string;
model: string;
}
interface MicroTaskResult {
taskType: string;
content: string;
processingTimeMs: number;
success: boolean;
error?: string;
}
interface AnalysisResult {
recommendation: any;
processingStats: {
embeddingsUsed: boolean;
candidatesFromEmbeddings: number;
finalSelectedItems: number;
processingTimeMs: number;
microTasksCompleted: number;
microTasksFailed: number;
contextContinuityUsed: boolean;
};
}
// Context object that builds up through pipeline
interface AnalysisContext {
userQuery: string;
mode: string;
filteredData: any;
// Context continuity
contextHistory: string[];
// Results
scenarioAnalysis?: string;
problemAnalysis?: string;
investigationApproach?: string;
criticalConsiderations?: string;
selectedTools?: Array<{tool: any, phase: string, priority: string, justification?: string}>;
backgroundKnowledge?: Array<{concept: any, relevance: string}>;
}
/**
* Improved DFIR microtask pipeline 20250801 revision (bugfixed)
*/
class ImprovedMicroTaskAIPipeline {
private config: AIConfig;
private maxSelectedItems: number;
private embeddingCandidates: number;
private similarityThreshold: number;
private microTaskDelay: number;
constructor() {
this.config = {
endpoint: this.getEnv('AI_ANALYZER_ENDPOINT'),
apiKey: this.getEnv('AI_ANALYZER_API_KEY'),
model: this.getEnv('AI_ANALYZER_MODEL')
};
// Candidate selection tuned for higher precision
this.maxSelectedItems = parseInt(process.env.AI_MAX_SELECTED_ITEMS || '60', 10);
this.embeddingCandidates = parseInt(process.env.AI_EMBEDDING_CANDIDATES || '40', 10);
this.similarityThreshold = parseFloat(process.env.AI_SIMILARITY_THRESHOLD || '0.5');
this.microTaskDelay = parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10);
}
private getEnv(key: string): string {
const value = process.env[key];
if (!value) throw new Error(`Missing environment variable: ${key}`);
return value;
}
/** Embedding → LLM blended selector */
private async getIntelligentCandidates(userQuery: string, toolsData: any, mode: string) {
const candidateTools = new Set<string>();
const candidateConcepts = new Set<string>();
if (embeddingsService.isEnabled()) {
const similarItems = await vectorIndex.findSimilar(userQuery, this.embeddingCandidates);
similarItems.forEach(item => {
if (item.type === 'tool') candidateTools.add(item.name);
if (item.type === 'concept') candidateConcepts.add(item.name);
});
console.log(`[PIPELINE] Embedding hits → ${candidateTools.size} tools / ${candidateConcepts.size} concepts`);
}
const reducedData = {
...toolsData,
tools: candidateTools.size ? toolsData.tools.filter((t: any) => candidateTools.has(t.name)) : toolsData.tools,
concepts: candidateConcepts.size ? toolsData.concepts.filter((c: any) => candidateConcepts.has(c.name)) : toolsData.concepts
};
return this.aiSelection(userQuery, reducedData, mode);
}
/** Languagemodel based selector (no 50item cap) */
private async aiSelection(userQuery: string, toolsData: any, mode: string) {
const toolsList = toolsData.tools.map((tool: any) => ({
name: tool.name,
type: tool.type,
description: tool.description.slice(0, 200) + '...',
domains: tool.domains,
phases: tool.phases,
tags: tool.tags?.slice(0, 5) || [],
skillLevel: tool.skillLevel
}));
const conceptsList = toolsData.concepts.map((concept: any) => ({
name: concept.name,
type: 'concept',
description: concept.description.slice(0, 200) + '...',
domains: concept.domains,
phases: concept.phases,
tags: concept.tags?.slice(0, 5) || []
}));
const modeInstruction =
mode === 'workflow'
? 'The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases.'
: 'The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem.';
const prompt = `You are a DFIR expert tasked with selecting the most relevant tools and concepts for a user query.
${modeInstruction}
AVAILABLE TOOLS:
${JSON.stringify(toolsList, null, 2)}
AVAILABLE CONCEPTS:
${JSON.stringify(conceptsList, null, 2)}
USER QUERY: "${userQuery}"
Select the most relevant items (max ${this.maxSelectedItems} total). For workflow mode, prioritize breadth across phases. For tool mode, prioritize specificity and direct relevance.
Respond with ONLY this JSON format:
{
"selectedTools": ["Tool Name 1", "Tool Name 2", ...],
"selectedConcepts": ["Concept Name 1", "Concept Name 2", ...],
"reasoning": "Brief explanation of selection criteria and approach"
}`;
try {
const response = await this.callAI(prompt, 1500);
const cleaned = response.replace(/^```json\s*/i, '').replace(/\s*```\s*$/g, '').trim();
const result = JSON.parse(cleaned);
if (!Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
throw new Error('Invalid selection result structure');
}
const totalSelected = result.selectedTools.length + result.selectedConcepts.length;
if (totalSelected > this.maxSelectedItems) {
console.warn(`[PIPELINE] Selection exceeded limit (${totalSelected}), truncating`);
result.selectedTools = result.selectedTools.slice(0, Math.floor(this.maxSelectedItems * 0.8));
result.selectedConcepts = result.selectedConcepts.slice(0, Math.ceil(this.maxSelectedItems * 0.2));
}
console.log(`[PIPELINE] LLM selector → ${result.selectedTools.length} tools / ${result.selectedConcepts.length} concepts`);
return {
tools: toolsData.tools.filter((tool: any) => result.selectedTools.includes(tool.name)),
concepts: toolsData.concepts.filter((concept: any) => result.selectedConcepts.includes(concept.name)),
domains: toolsData.domains,
phases: toolsData.phases,
'domain-agnostic-software': toolsData['domain-agnostic-software']
};
} catch (err) {
console.error('[PIPELINE] Failed to parse selector response');
throw new Error('Invalid JSON response from selector AI');
}
}
private delay(ms: number) { return new Promise(res => setTimeout(res, ms)); }
private async callMicroTaskAI(prompt: string, context: AnalysisContext, maxTokens = 300): Promise<MicroTaskResult> {
const start = Date.now();
const contextPrompt = context.contextHistory.length
? `BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n${prompt}`
: prompt;
try {
const response = await this.callAI(contextPrompt, maxTokens);
return { taskType: 'micro-task', content: response.trim(), processingTimeMs: Date.now() - start, success: true };
} catch (e) {
return { taskType: 'micro-task', content: '', processingTimeMs: Date.now() - start, success: false, error: (e as Error).message };
}
}
// FIXED: Restore original micro-task structure with context continuity
// MICRO-TASK 1: Scenario/Problem Analysis
private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> {
const isWorkflow = context.mode === 'workflow';
const prompt = `Sie sind ein erfahrener DFIR-Experte. Analysieren Sie das folgende ${isWorkflow ? 'forensische Szenario' : 'technische Problem'}.
${isWorkflow ? 'FORENSISCHES SZENARIO' : 'TECHNISCHES PROBLEM'}: "${context.userQuery}"
Führen Sie eine systematische ${isWorkflow ? 'Szenario-Analyse' : 'Problem-Analyse'} durch und berücksichtigen Sie dabei:
${isWorkflow ?
`- Auf das Szenario bezogene Problemstellungen` :
`- konkrete problembezogene Aufgabenstellung`
}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählungen oder Markdown-Formatierung. Maximum 150 Wörter.`;
const result = await this.callMicroTaskAI(prompt, context, 220);
if (result.success) {
if (isWorkflow) {
context.scenarioAnalysis = result.content;
} else {
context.problemAnalysis = result.content;
}
// ADDED: Build context history
context.contextHistory.push(`${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
}
return result;
}
// MICRO-TASK 2: Investigation/Solution Approach
private async generateApproach(context: AnalysisContext): Promise<MicroTaskResult> {
const isWorkflow = context.mode === 'workflow';
const analysis = isWorkflow ? context.scenarioAnalysis : context.problemAnalysis;
const prompt = `Basierend auf der Analyse entwickeln Sie einen fundierten ${isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz'}.
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${context.userQuery}"
Entwickeln Sie einen systematischen ${isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz'} unter Berücksichtigung von:
${isWorkflow ?
`- Triage-Prioritäten nach forensischer Dringlichkeit (wenn zutreffend)
- Phasenabfolge nach NIST SP 800-86-Methodik (Datensammlung - Auswertung - Analyse - Report)` :
`- pragmatischer, zielorientierter Lösungsansatz im benehmen mit Anforderungen an die Reproduzierbarkeit`
}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 150 Wörter.`;
const result = await this.callMicroTaskAI(prompt, context, 220);
if (result.success) {
context.investigationApproach = result.content;
context.contextHistory.push(`${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz: ${result.content.slice(0, 200)}...`);
}
return result;
}
// MICRO-TASK 3: Critical Considerations
private async generateCriticalConsiderations(context: AnalysisContext): Promise<MicroTaskResult> {
const isWorkflow = context.mode === 'workflow';
const prompt = `Identifizieren Sie ${isWorkflow ? 'kritische forensische Überlegungen' : 'wichtige methodische Voraussetzungen'} für diesen Fall.
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${context.userQuery}"
Berücksichtigen Sie folgende Aspekte:
${isWorkflow ?
`- Szenariobezogene typische Problemstellungen, die auftreten können` :
`- Problembezogene Schwierigkeiten, die das Ergebnis negativ beeinträchtigen könnten`
}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 120 Wörter.`;
const result = await this.callMicroTaskAI(prompt, context, 180);
if (result.success) {
context.criticalConsiderations = result.content;
context.contextHistory.push(`Kritische Überlegungen: ${result.content.slice(0, 200)}...`);
}
return result;
}
// MICRO-TASK 4: Tool Selection for Phase (Workflow mode)
private async selectToolsForPhase(context: AnalysisContext, phase: any): Promise<MicroTaskResult> {
const phaseTools = context.filteredData.tools.filter((tool: any) =>
tool.phases && tool.phases.includes(phase.id)
);
if (phaseTools.length === 0) {
return {
taskType: 'tool-selection',
content: JSON.stringify([]),
processingTimeMs: 0,
success: true
};
}
const prompt = `Wählen Sie 2-3 Methoden/Tools für die Phase "${phase.name}" basierend auf objektiven, fallbezogenen Kriterien.
SZENARIO: "${context.userQuery}"
VERFÜGBARE TOOLS FÜR ${phase.name.toUpperCase()}:
${phaseTools.map((tool: any) => `- ${tool.name}: ${tool.description.slice(0, 100)}...`).join('\n')}
Wählen Sie Methoden/Tools nach forensischen Kriterien aus:
- Eignung für die spezifische Lösung des Problems
- besondere Fähigkeiten der Methode/des Tools, das sie von anderen abgrenzt
- Reproduzierbarkeit und Objektivität
Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text):
[
{
"toolName": "Exakter Methoden/Tool-Name",
"priority": "high|medium|low",
"justification": "Objektive Begründung warum diese Methode/Tool für das spezifische Szenario besser geeignet ist"
}
]`;
const result = await this.callMicroTaskAI(prompt, context, 450);
if (result.success) {
try {
const selections = JSON.parse(result.content.replace(/^```json\s*/i, '').replace(/\s*```\s*$/g, '').trim());
const validSelections = selections.filter((sel: any) =>
phaseTools.some((tool: any) => tool.name === sel.toolName)
);
if (!context.selectedTools) context.selectedTools = [];
validSelections.forEach((sel: any) => {
const tool = phaseTools.find((t: any) => t.name === sel.toolName);
if (tool) {
context.selectedTools!.push({
tool,
phase: phase.id,
priority: sel.priority,
justification: sel.justification
});
}
});
} catch (parseError) {
console.warn(`[IMPROVED PIPELINE] Failed to parse tool selection for ${phase.name}:`, result.content.slice(0, 200));
return {
...result,
success: false,
error: 'JSON parsing failed'
};
}
}
return result;
}
// MICRO-TASK 5: Tool Evaluation (Tool mode)
private async evaluateSpecificTool(context: AnalysisContext, tool: any, rank: number): Promise<MicroTaskResult> {
const prompt = `Bewerten Sie diese Methode/Tool fallbezogen für das spezifische Problem.
PROBLEM: "${context.userQuery}"
TOOL: ${tool.name}
BESCHREIBUNG: ${tool.description}
PLATTFORMEN: ${tool.platforms?.join(', ') || 'N/A'}
SKILL LEVEL: ${tool.skillLevel}
Bewerten Sie nach forensischen Standards und antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
{
"suitability_score": "high|medium|low",
"detailed_explanation": "Detaillierte forensische Begründung warum diese Methode/Tool das Problem löst",
"implementation_approach": "Konkrete methodische Schritte zur korrekten Anwendung für dieses spezifische Problem",
"pros": ["Forensischer Vorteil 1", "Validierter Vorteil 2"],
"cons": ["Methodische Limitation 1", "Potenzielle Schwäche 2"],
"alternatives": "Alternative Ansätze falls diese Methode/Tool nicht optimal ist"
}`;
const result = await this.callMicroTaskAI(prompt, context, 650);
if (result.success) {
try {
const evaluation = JSON.parse(result.content.replace(/^```json\s*/i, '').replace(/\s*```\s*$/g, '').trim());
if (!context.selectedTools) context.selectedTools = [];
context.selectedTools.push({
tool: {
...tool,
evaluation: {
...evaluation,
rank
}
},
phase: 'evaluation',
priority: evaluation.suitability_score
});
} catch (parseError) {
console.warn(`[IMPROVED PIPELINE] Failed to parse tool evaluation for ${tool.name}:`, result.content.slice(0, 200));
return {
...result,
success: false,
error: 'JSON parsing failed'
};
}
}
return result;
}
// MICRO-TASK 6: Background Knowledge
private async selectBackgroundKnowledge(context: AnalysisContext): Promise<MicroTaskResult> {
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 => st.tool.name) || [];
const prompt = `Wählen Sie relevante forensische Konzepte für das Verständnis der empfohlenen Methodik.
${context.mode === 'workflow' ? 'SZENARIO' : 'PROBLEM'}: "${context.userQuery}"
EMPFOHLENE TOOLS: ${selectedToolNames.join(', ')}
VERFÜGBARE KONZEPTE:
${availableConcepts.slice(0, 15).map((concept: any) => `- ${concept.name}: ${concept.description.slice(0, 80)}...`).join('\n')}
Wählen Sie 2-4 Konzepte aus, die für die Lösung des Problems essentiell sind.
Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
[
{
"conceptName": "Exakter Konzept-Name",
"relevance": "Forensische Relevanz: Warum dieses Konzept für die Lösung des Problems kritisch ist"
}
]`;
const result = await this.callMicroTaskAI(prompt, context, 400);
if (result.success) {
try {
const selections = JSON.parse(result.content.replace(/^```json\s*/i, '').replace(/\s*```\s*$/g, '').trim());
context.backgroundKnowledge = selections.filter((sel: any) =>
availableConcepts.some((concept: any) => concept.name === sel.conceptName)
).map((sel: any) => ({
concept: availableConcepts.find((c: any) => c.name === sel.conceptName),
relevance: sel.relevance
}));
} catch (parseError) {
console.warn('[IMPROVED PIPELINE] Failed to parse background knowledge selection:', result.content.slice(0, 200));
return {
...result,
success: false,
error: 'JSON parsing failed'
};
}
}
return result;
}
// MICRO-TASK 7: Final Recommendations
private async generateFinalRecommendations(context: AnalysisContext): Promise<MicroTaskResult> {
const isWorkflow = context.mode === 'workflow';
const prompt = isWorkflow ?
`Erstellen Sie eine forensisch fundierte Workflow-Empfehlung unter Anwendung der gewählten Methoden/Tools.
SZENARIO: "${context.userQuery}"
AUSGEWÄHLTE TOOLS: ${context.selectedTools?.map(st => st.tool.name).join(', ') || 'Keine Tools ausgewählt'}
Erstellen Sie konkrete Workflow-Schritte für dieses spezifische Szenario unter Berücksichtigung von Objektivität und rechtlicher Verwertbarkeit (Reproduzierbarkeit, Transparenz).
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 120 Wörter.` :
`Erstellen Sie wichtige Überlegungen für die korrekte Methoden-/Tool-Anwendung.
PROBLEM: "${context.userQuery}"
EMPFOHLENE TOOLS: ${context.selectedTools?.map(st => st.tool.name).join(', ') || 'Keine Methoden/Tools ausgewählt'}
Geben Sie kritische Überlegungen für die korrekte Anwendung der empfohlenen Methoden/Tools.
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 100 Wörter.`;
const result = await this.callMicroTaskAI(prompt, context, 180);
return result;
}
// Helper method for AI calls
private async callAI(prompt: string, maxTokens: number = 1000): Promise<string> {
const response = await fetch(`${this.config.endpoint}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`
},
body: JSON.stringify({
model: this.config.model,
messages: [{ role: 'user', content: prompt }],
max_tokens: maxTokens,
temperature: 0.3
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`AI API error: ${response.status} - ${errorText}`);
}
const data = await response.json();
const content = data.choices?.[0]?.message?.content;
if (!content) {
throw new Error('No response from AI model');
}
return content;
}
async processQuery(userQuery: string, mode: string): Promise<AnalysisResult> {
const startTime = Date.now();
let completedTasks = 0;
let failedTasks = 0;
try {
const toolsData = await getCompressedToolsDataForAI();
const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode);
const context: AnalysisContext = { userQuery, mode, filteredData, contextHistory: [] };
console.log(`[IMPROVED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`);
// MICRO-TASK SEQUENCE (restored original structure)
// Task 1: Scenario/Problem Analysis
const analysisResult = await this.analyzeScenario(context);
if (analysisResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
// Task 2: Investigation/Solution Approach
const approachResult = await this.generateApproach(context);
if (approachResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
// Task 3: Critical Considerations
const considerationsResult = await this.generateCriticalConsiderations(context);
if (considerationsResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
// Task 4: Tool Selection/Evaluation (mode-dependent)
if (mode === 'workflow') {
// Select tools for each phase
const phases = toolsData.phases || [];
for (const phase of phases) {
const toolSelectionResult = await this.selectToolsForPhase(context, phase);
if (toolSelectionResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
}
} else {
const shuffled = [...filteredData.tools].sort(() => Math.random() - 0.5); // FIX
const topTools = shuffled.slice(0, 3);
for (let i = 0; i < topTools.length; i++) {
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1);
if (evaluationResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
}
}
// Task 5: Background Knowledge Selection
const knowledgeResult = await this.selectBackgroundKnowledge(context);
if (knowledgeResult.success) completedTasks++; else failedTasks++;
await this.delay(this.microTaskDelay);
// Task 6: Final Recommendations
const finalResult = await this.generateFinalRecommendations(context);
if (finalResult.success) completedTasks++; else failedTasks++;
const recommendation = this.buildRecommendation(context, mode, ''); // finalContent injected inside omitted logic
const processingStats = {
embeddingsUsed: embeddingsService.isEnabled(),
candidatesFromEmbeddings: filteredData.tools.length,
finalSelectedItems: (context.selectedTools?.length || 0) + (context.backgroundKnowledge?.length || 0),
processingTimeMs: Date.now() - startTime,
microTasksCompleted: completedTasks,
microTasksFailed: failedTasks,
contextContinuityUsed: true
};
return { recommendation, processingStats };
} catch (error) {
console.error('[PIPELINE] Processing failed:', error);
throw error;
}
}
// Build recommendation (same as original structure)
private buildRecommendation(context: AnalysisContext, mode: string, finalContent: 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 => ({
concept_name: bk.concept.name,
relevance: bk.relevance
})) || []
};
if (isWorkflow) {
return {
...base,
recommended_tools: context.selectedTools?.map(st => ({
name: st.tool.name,
phase: st.phase,
priority: st.priority,
justification: st.justification || `Empfohlen für ${st.phase}`
})) || [],
workflow_suggestion: finalContent
};
} else {
return {
...base,
recommended_tools: context.selectedTools?.map(st => ({
name: st.tool.name,
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?.cons || [],
alternatives: st.tool.evaluation?.alternatives || ''
})) || [],
additional_considerations: finalContent
};
}
}
}
// Global instance
const aiPipeline = new ImprovedMicroTaskAIPipeline();
export { aiPipeline, type AnalysisResult };