1240 lines
42 KiB
TypeScript
1240 lines
42 KiB
TypeScript
// src/utils/aiPipeline.ts - Fixed with accurate audit data and meaningful confidence
|
|
import { getCompressedToolsDataForAI, getDataVersion } from './dataService.js';
|
|
import { aiService } from './aiService.js';
|
|
import { toolSelector, type SelectionContext } from './toolSelector.js';
|
|
import { confidenceScoring, type AnalysisContext } from './confidenceScoring.js';
|
|
import { embeddingsService } from './embeddings.js';
|
|
import { auditService, type AuditEntry } from './auditService.js';
|
|
import { JSONParser } from './jsonUtils.js';
|
|
import { getPrompt } from '../config/prompts.js';
|
|
import 'dotenv/config';
|
|
|
|
interface PipelineConfig {
|
|
microTaskDelay: number;
|
|
maxContextTokens: number;
|
|
maxPromptTokens: 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: {
|
|
embeddingsUsed: boolean;
|
|
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[];
|
|
maxContextLength: number;
|
|
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>;
|
|
}
|
|
|
|
class AIPipeline {
|
|
private config: PipelineConfig;
|
|
private totalTokensUsed: number = 0;
|
|
|
|
constructor() {
|
|
this.config = {
|
|
microTaskDelay: parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10),
|
|
maxContextTokens: parseInt(process.env.AI_MAX_CONTEXT_TOKENS || '4000', 10),
|
|
maxPromptTokens: parseInt(process.env.AI_MAX_PROMPT_TOKENS || '1500', 10),
|
|
taskRelevanceModeration: {
|
|
maxInitialScore: 85,
|
|
maxWithPhaseBonus: 95,
|
|
moderationThreshold: 80
|
|
}
|
|
};
|
|
|
|
console.log('[AI-PIPELINE] Initialized with improved audit accuracy');
|
|
}
|
|
|
|
async processQuery(userQuery: string, mode: string): Promise<AnalysisResult> {
|
|
const startTime = Date.now();
|
|
let completedTasks = 0;
|
|
let failedTasks = 0;
|
|
this.totalTokensUsed = 0;
|
|
|
|
console.log('[AI-PIPELINE] Starting', mode, 'analysis pipeline');
|
|
|
|
auditService.clearAuditTrail();
|
|
|
|
try {
|
|
const toolsData = await getCompressedToolsDataForAI();
|
|
const aiConfig = aiService.getConfig();
|
|
const toolsDataHash = getDataVersion?.() || 'unknown';
|
|
|
|
const context: PipelineContext = {
|
|
userQuery,
|
|
mode,
|
|
filteredData: {},
|
|
contextHistory: [],
|
|
maxContextLength: this.config.maxContextTokens,
|
|
currentContextLength: 0,
|
|
seenToolNames: new Set<string>(),
|
|
embeddingsSimilarities: new Map<string, number>()
|
|
};
|
|
|
|
// Skip initialization audit entry - it doesn't add transparency value
|
|
|
|
console.log('[AI-PIPELINE] Phase 1: Tool candidate selection');
|
|
const candidateSelectionStart = Date.now();
|
|
|
|
const candidateData = await toolSelector.getIntelligentCandidates(userQuery, toolsData, mode, context);
|
|
|
|
// Calculate meaningful confidence for tool selection
|
|
const selectionConfidence = this.calculateToolSelectionConfidence(
|
|
candidateData.tools.length,
|
|
toolsData.tools.length,
|
|
candidateData.selectionMethod,
|
|
candidateData.concepts.length
|
|
);
|
|
|
|
auditService.addToolSelection(
|
|
candidateData.tools.map(t => t.name),
|
|
toolsData.tools.map(t => t.name),
|
|
candidateData.selectionMethod,
|
|
selectionConfidence,
|
|
candidateSelectionStart,
|
|
{
|
|
embeddingsUsed: embeddingsService.isEnabled(),
|
|
totalCandidatesFound: candidateData.tools.length + candidateData.concepts.length,
|
|
selectionMethod: candidateData.selectionMethod,
|
|
reductionRatio: candidateData.tools.length / toolsData.tools.length
|
|
}
|
|
);
|
|
|
|
context.filteredData = candidateData;
|
|
|
|
console.log('[AI-PIPELINE] Phase 2: Contextual analysis');
|
|
|
|
const analysisResult = await this.analyzeScenario(context, startTime);
|
|
if (analysisResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(analysisResult.aiUsage);
|
|
await this.delay(this.config.microTaskDelay);
|
|
|
|
const approachResult = await this.generateApproach(context, startTime);
|
|
if (approachResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(approachResult.aiUsage);
|
|
await this.delay(this.config.microTaskDelay);
|
|
|
|
const considerationsResult = await this.generateCriticalConsiderations(context, startTime);
|
|
if (considerationsResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(considerationsResult.aiUsage);
|
|
await this.delay(this.config.microTaskDelay);
|
|
|
|
console.log('[AI-PIPELINE] Phase 3: Tool-specific analysis');
|
|
|
|
if (mode === 'workflow') {
|
|
const workflowResults = await this.processWorkflowMode(context, toolsData, completedTasks, failedTasks, startTime);
|
|
completedTasks = workflowResults.completed;
|
|
failedTasks = workflowResults.failed;
|
|
} else {
|
|
const toolResults = await this.processToolMode(context, completedTasks, failedTasks, startTime);
|
|
completedTasks = toolResults.completed;
|
|
failedTasks = toolResults.failed;
|
|
}
|
|
|
|
console.log('[AI-PIPELINE] Phase 4: Knowledge synthesis');
|
|
|
|
const knowledgeResult = await this.selectBackgroundKnowledge(context, startTime);
|
|
if (knowledgeResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(knowledgeResult.aiUsage);
|
|
await this.delay(this.config.microTaskDelay);
|
|
|
|
const finalResult = await this.generateFinalRecommendations(context, startTime);
|
|
if (finalResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(finalResult.aiUsage);
|
|
|
|
const recommendation = this.buildRecommendation(context, mode, finalResult.content);
|
|
|
|
// Skip completion audit entry - it doesn't add transparency value
|
|
|
|
const processingStats = {
|
|
embeddingsUsed: embeddingsService.isEnabled(),
|
|
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: 2500
|
|
};
|
|
|
|
console.log('[AI-PIPELINE] Pipeline completed successfully:', {
|
|
mode,
|
|
processingTimeMs: processingStats.processingTimeMs,
|
|
completedTasks,
|
|
failedTasks,
|
|
finalItems: processingStats.finalSelectedItems,
|
|
totalTokensUsed: this.totalTokensUsed,
|
|
auditEntries: processingStats.auditEntriesGenerated
|
|
});
|
|
|
|
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 calculateToolSelectionConfidence(
|
|
selectedCount: number,
|
|
totalCount: number,
|
|
method: string,
|
|
conceptsCount: number
|
|
): number {
|
|
let confidence = 50;
|
|
|
|
const selectionRatio = selectedCount / totalCount;
|
|
|
|
// Good selection ratio (5-20% is optimal)
|
|
if (selectionRatio >= 0.05 && selectionRatio <= 0.20) {
|
|
confidence += 25;
|
|
} else if (selectionRatio < 0.05) {
|
|
confidence += 15; // Very selective
|
|
} else if (selectionRatio > 0.30) {
|
|
confidence -= 15; // Too inclusive
|
|
}
|
|
|
|
// Embeddings method bonus
|
|
if (method.includes('embeddings')) {
|
|
confidence += 15;
|
|
}
|
|
|
|
// Concepts also selected
|
|
if (conceptsCount > 0) {
|
|
confidence += 10;
|
|
}
|
|
|
|
// Reasonable absolute numbers
|
|
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
|
|
): 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) {
|
|
console.log(`[AI-PIPELINE] No tools available for phase: ${phase.id}`);
|
|
continue;
|
|
}
|
|
|
|
const selections = await toolSelector.selectToolsForPhase(context.userQuery, phase, phaseTools, context);
|
|
|
|
// Calculate meaningful confidence based on phase selection quality
|
|
const phaseConfidence = this.calculatePhaseSelectionConfidence(
|
|
selections.length,
|
|
phaseTools.length,
|
|
phase.id,
|
|
selections
|
|
);
|
|
|
|
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,
|
|
{
|
|
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);
|
|
|
|
this.addToolToSelection(context, tool, phase.id, priority, sel.justification, moderatedTaskRelevance, sel.limitations);
|
|
|
|
auditService.addEntry(
|
|
'tool-reasoning',
|
|
'tool-added-to-phase',
|
|
{
|
|
toolName: tool.name,
|
|
phaseId: phase.id,
|
|
taskRelevance: moderatedTaskRelevance,
|
|
priority: priority
|
|
},
|
|
{
|
|
justification: sel.justification,
|
|
limitations: sel.limitations,
|
|
addedToPhase: phase.name
|
|
},
|
|
moderatedTaskRelevance || 70,
|
|
phaseStart,
|
|
{
|
|
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);
|
|
completedTasks += completionResult.completed;
|
|
failedTasks += completionResult.failed;
|
|
|
|
return { completed: completedTasks, failed: failedTasks };
|
|
}
|
|
|
|
private calculatePhaseSelectionConfidence(
|
|
selectedCount: number,
|
|
availableCount: number,
|
|
phaseId: string,
|
|
selections: any[]
|
|
): number {
|
|
let confidence = 60;
|
|
|
|
// Phase-specific expectations
|
|
const criticalPhases = ['acquisition', 'examination', 'analysis'];
|
|
const isCritical = criticalPhases.includes(phaseId);
|
|
|
|
// Selection made
|
|
if (selectedCount > 0) {
|
|
confidence += 20;
|
|
} else {
|
|
return 30; // No selection is concerning
|
|
}
|
|
|
|
// Selection ratio (for phases, 20-50% is reasonable)
|
|
const ratio = selectedCount / availableCount;
|
|
if (ratio >= 0.2 && ratio <= 0.5) {
|
|
confidence += 15;
|
|
} else if (ratio < 0.2 && selectedCount >= 1) {
|
|
confidence += 10; // Selective is ok
|
|
}
|
|
|
|
// Critical phases should have adequate tools
|
|
if (isCritical && selectedCount >= 2) {
|
|
confidence += 10;
|
|
}
|
|
|
|
// Quality of selections (based on task relevance)
|
|
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 async processToolMode(
|
|
context: PipelineContext,
|
|
completedTasks: number,
|
|
failedTasks: number,
|
|
pipelineStart: number
|
|
): Promise<{ completed: number; failed: number }> {
|
|
const topTools = context.filteredData.tools.slice(0, 3);
|
|
|
|
for (let i = 0; i < topTools.length; i++) {
|
|
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1, pipelineStart);
|
|
if (evaluationResult.success) completedTasks++; else failedTasks++;
|
|
this.trackTokenUsage(evaluationResult.aiUsage);
|
|
await this.delay(this.config.microTaskDelay);
|
|
}
|
|
|
|
return { completed: completedTasks, failed: failedTasks };
|
|
}
|
|
|
|
private async completeUnderrepresentedPhases(
|
|
context: PipelineContext,
|
|
toolsData: any,
|
|
pipelineStart: number
|
|
): 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) {
|
|
console.log('[AI-PIPELINE] All phases adequately represented');
|
|
return { completed: 0, failed: 0 };
|
|
}
|
|
|
|
console.log('[AI-PIPELINE] Completing underrepresented phases:', underrepresentedPhases.map((p: any) => p.id).join(', '));
|
|
|
|
for (const phase of underrepresentedPhases) {
|
|
const result = await this.completePhaseWithSemanticSearchAndAI(context, phase, toolsData, pipelineStart);
|
|
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
|
|
): Promise<MicroTaskResult> {
|
|
const phaseStart = Date.now();
|
|
const phaseQuery = `forensic ${phase.name.toLowerCase()} tools methods`;
|
|
|
|
console.log('[AI-PIPELINE] Phase completion for:', phase.id);
|
|
|
|
try {
|
|
const phaseResults = await embeddingsService.findSimilar(phaseQuery, 20, 0.2);
|
|
|
|
auditService.addEmbeddingsSearch(
|
|
phaseQuery,
|
|
phaseResults,
|
|
0.2,
|
|
phaseStart,
|
|
{
|
|
phaseId: phase.id,
|
|
phaseName: phase.name,
|
|
completionPurpose: 'underrepresented-phase-enhancement'
|
|
}
|
|
);
|
|
|
|
if (phaseResults.length === 0) {
|
|
console.log('[AI-PIPELINE] No semantic results for phase:', phase.id);
|
|
return {
|
|
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) {
|
|
console.log('[AI-PIPELINE] No suitable tools for phase completion:', phase.id);
|
|
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, 800, 'phase-completion-selection');
|
|
|
|
if (!selectionResult.success) {
|
|
console.error('[AI-PIPELINE] Phase completion selection failed for:', phase.id);
|
|
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) {
|
|
console.log('[AI-PIPELINE] No valid tools selected for phase completion:', phase.id);
|
|
return {
|
|
taskType: 'phase-completion',
|
|
content: selection.completionReasoning || '',
|
|
processingTimeMs: Date.now() - phaseStart,
|
|
success: true
|
|
};
|
|
}
|
|
|
|
// This is the fix for "0 tools added" - use the actual valid tools
|
|
const actualToolsAdded = validTools.map(tool => tool.name);
|
|
|
|
for (const tool of validTools) {
|
|
console.log('[AI-PIPELINE] Generating AI reasoning for phase completion tool:', tool.name);
|
|
|
|
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, 400, '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);
|
|
}
|
|
|
|
this.addToolToSelection(
|
|
context,
|
|
tool,
|
|
phase.id,
|
|
'medium',
|
|
detailedJustification,
|
|
moderatedTaskRelevance,
|
|
['Nachträgliche Ergänzung via semantische Phasensuche mit KI-Bewertung']
|
|
);
|
|
|
|
console.log('[AI-PIPELINE] Added phase completion tool with AI reasoning:', tool.name);
|
|
}
|
|
|
|
// Use the actual tools added for audit
|
|
auditService.addPhaseCompletion(
|
|
phase.id,
|
|
actualToolsAdded, // This ensures correct count
|
|
selection.completionReasoning || `${actualToolsAdded.length} Tools für ${phase.name} hinzugefügt`,
|
|
phaseStart,
|
|
{
|
|
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) {
|
|
console.error('[AI-PIPELINE] Phase completion failed for:', phase.id, error);
|
|
|
|
return {
|
|
taskType: 'phase-completion',
|
|
content: '',
|
|
processingTimeMs: Date.now() - phaseStart,
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
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): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Scenario analysis');
|
|
const taskStart = Date.now();
|
|
const isWorkflow = context.mode === 'workflow';
|
|
const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery);
|
|
|
|
const result = await this.callMicroTaskAI(prompt, context, 400, '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 forensische Untersuchung`,
|
|
taskStart,
|
|
{
|
|
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): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Investigation approach');
|
|
const taskStart = Date.now();
|
|
const isWorkflow = context.mode === 'workflow';
|
|
const prompt = getPrompt('investigationApproach', isWorkflow, context.userQuery);
|
|
|
|
const result = await this.callMicroTaskAI(prompt, context, 400, '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,
|
|
{
|
|
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): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Critical considerations');
|
|
const taskStart = Date.now();
|
|
const isWorkflow = context.mode === 'workflow';
|
|
const prompt = getPrompt('criticalConsiderations', isWorkflow, context.userQuery);
|
|
|
|
const result = await this.callMicroTaskAI(prompt, context, 350, '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,
|
|
{
|
|
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
|
|
): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Tool evaluation for:', tool.name);
|
|
const taskStart = Date.now();
|
|
const existingSelection = context.selectedTools?.find((st: any) => st.tool && st.tool.name === tool.name);
|
|
const originalTaskRelevance = existingSelection?.taskRelevance || 70;
|
|
const moderatedTaskRelevance = this.moderateTaskRelevance(originalTaskRelevance);
|
|
const priority = this.derivePriorityFromScore(moderatedTaskRelevance);
|
|
|
|
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank, moderatedTaskRelevance);
|
|
const result = await this.callMicroTaskAI(prompt, context, 1000, 'tool-evaluation');
|
|
|
|
if (result.success) {
|
|
const evaluation = JSONParser.safeParseJSON(result.content, {
|
|
detailed_explanation: 'Evaluation failed',
|
|
implementation_approach: '',
|
|
pros: [],
|
|
limitations: [],
|
|
alternatives: ''
|
|
});
|
|
|
|
this.addToolToSelection(context, {
|
|
...tool,
|
|
evaluation: {
|
|
...evaluation,
|
|
rank,
|
|
task_relevance: moderatedTaskRelevance
|
|
}
|
|
}, 'evaluation', priority, evaluation.detailed_explanation, moderatedTaskRelevance, evaluation.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}) - Analysierte Eignung für spezifische Aufgabenstellung mit Fokus auf praktische Anwendbarkeit und methodische Integration`,
|
|
taskStart,
|
|
{
|
|
microTaskType: 'tool-evaluation',
|
|
toolName: tool.name,
|
|
toolType: tool.type,
|
|
rank,
|
|
originalTaskRelevance,
|
|
moderatedTaskRelevance,
|
|
responseConfidence,
|
|
finalConfidence,
|
|
moderationApplied: originalTaskRelevance !== moderatedTaskRelevance,
|
|
evaluationParsed: !!evaluation.detailed_explanation,
|
|
prosCount: evaluation.pros?.length || 0,
|
|
limitationsCount: evaluation.limitations?.length || 0,
|
|
decisionBasis: 'ai-analysis',
|
|
aiModel: aiService.getConfig().model,
|
|
...result.aiUsage
|
|
}
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async selectBackgroundKnowledge(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Background knowledge selection');
|
|
const taskStart = Date.now();
|
|
const availableConcepts = context.filteredData.concepts;
|
|
|
|
if (availableConcepts.length === 0) {
|
|
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, 700, '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'
|
|
);
|
|
|
|
// Calculate confidence based on quality of selections
|
|
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,
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Good selection ratio (10-30% of available concepts)
|
|
const ratio = selectedKnowledge.length / availableConcepts.length;
|
|
if (ratio >= 0.1 && ratio <= 0.3) {
|
|
bonus += 15;
|
|
}
|
|
|
|
// Quality reasoning provided
|
|
const hasGoodReasonings = selectedKnowledge.some(bk =>
|
|
bk.relevance && bk.relevance.length > 30
|
|
);
|
|
if (hasGoodReasonings) {
|
|
bonus += 10;
|
|
}
|
|
|
|
return bonus;
|
|
}
|
|
|
|
private async generateFinalRecommendations(context: PipelineContext, pipelineStart: number): Promise<MicroTaskResult> {
|
|
console.log('[AI-PIPELINE] Micro-task: Final recommendations');
|
|
const taskStart = Date.now();
|
|
const selectedToolNames = context.selectedTools?.map((st: any) => st.tool && st.tool.name).filter(Boolean) || [];
|
|
const prompt = getPrompt('finalRecommendations', context.mode === 'workflow', context.userQuery, selectedToolNames);
|
|
|
|
const result = await this.callMicroTaskAI(prompt, context, 350, 'final-recommendations');
|
|
|
|
if (result.success) {
|
|
const confidence = auditService.calculateAIResponseConfidence(
|
|
result.content,
|
|
{ min: 60, max: 250 },
|
|
'final-recommendations'
|
|
);
|
|
|
|
// Calculate bonus based on context quality
|
|
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,
|
|
{
|
|
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): any {
|
|
const isWorkflow = mode === 'workflow';
|
|
|
|
console.log('[AI-PIPELINE] Building recommendation for', mode, 'mode with', context.selectedTools?.length || 0, 'tools');
|
|
|
|
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(),
|
|
{
|
|
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(),
|
|
{
|
|
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,
|
|
maxTokens: number = 500,
|
|
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`;
|
|
const combinedPrompt = contextSection + prompt;
|
|
|
|
if (aiService.estimateTokens(combinedPrompt) <= this.config.maxPromptTokens) {
|
|
contextPrompt = combinedPrompt;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const response = await aiService.callMicroTaskAI(contextPrompt, maxTokens);
|
|
|
|
return {
|
|
taskType,
|
|
content: response.content,
|
|
processingTimeMs: Date.now() - startTime,
|
|
success: true,
|
|
aiUsage: response.usage
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
taskType,
|
|
content: '',
|
|
processingTimeMs: Date.now() - startTime,
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
private addToContextHistory(context: PipelineContext, newEntry: string): void {
|
|
const entryTokens = aiService.estimateTokens(newEntry);
|
|
|
|
context.contextHistory.push(newEntry);
|
|
context.currentContextLength += entryTokens;
|
|
|
|
while (context.currentContextLength > this.config.maxContextTokens && 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 }; |