improve AI

This commit is contained in:
overcuriousity
2025-08-01 22:29:38 +02:00
parent 1b9d9b437b
commit 8693cd87d4
6 changed files with 426 additions and 269 deletions

View File

@@ -1,8 +1,7 @@
// src/utils/aiPipeline.ts - FIXED: Restore proper structure with context continuity
// src/utils/aiPipeline.ts - FIXED: Critical error corrections
import { getCompressedToolsDataForAI } from './dataService.js';
import { embeddingsService, type EmbeddingData } from './embeddings.js';
import { vectorIndex } from './vectorIndex.js';
interface AIConfig {
endpoint: string;
@@ -31,21 +30,25 @@ interface AnalysisResult {
};
}
// FIXED: Context object that builds up through pipeline
interface AnalysisContext {
userQuery: string;
mode: string;
filteredData: any;
// ADDED: Context continuity
contextHistory: string[];
// Results (same as original)
// FIXED: Add max context length tracking
maxContextLength: number;
currentContextLength: number;
scenarioAnalysis?: string;
problemAnalysis?: string;
investigationApproach?: string;
criticalConsiderations?: string;
selectedTools?: Array<{tool: any, phase: string, priority: string, justification?: string}>;
backgroundKnowledge?: Array<{concept: any, relevance: string}>;
// FIXED: Add seen tools tracking to prevent duplicates
seenToolNames: Set<string>;
}
class ImprovedMicroTaskAIPipeline {
@@ -54,6 +57,10 @@ class ImprovedMicroTaskAIPipeline {
private embeddingCandidates: number;
private similarityThreshold: number;
private microTaskDelay: number;
// FIXED: Add proper token management
private maxContextTokens: number;
private maxPromptTokens: number;
constructor() {
this.config = {
@@ -62,11 +69,14 @@ class ImprovedMicroTaskAIPipeline {
model: this.getEnv('AI_ANALYZER_MODEL')
};
// FIXED: Optimized for vectorIndex (HNSW) usage
this.maxSelectedItems = parseInt(process.env.AI_MAX_SELECTED_ITEMS || '60', 10);
this.embeddingCandidates = parseInt(process.env.AI_EMBEDDING_CANDIDATES || '60', 10); // HNSW is more efficient
this.similarityThreshold = 0.3; // Not used by vectorIndex, kept for fallback compatibility
this.embeddingCandidates = parseInt(process.env.AI_EMBEDDING_CANDIDATES || '60', 10);
this.similarityThreshold = 0.3;
this.microTaskDelay = parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10);
// FIXED: Token management
this.maxContextTokens = parseInt(process.env.AI_MAX_CONTEXT_TOKENS || '4000', 10);
this.maxPromptTokens = parseInt(process.env.AI_MAX_PROMPT_TOKENS || '1500', 10);
}
private getEnv(key: string): string {
@@ -77,12 +87,68 @@ class ImprovedMicroTaskAIPipeline {
return value;
}
// IMPROVED: AI-driven selection (no hard-coded keywords)
private async getIntelligentCandidates(userQuery: string, toolsData: any, mode: string) {
let candidateTools = new Set<string>();
let candidateConcepts = new Set<string>();
// FIXED: Estimate token count (rough approximation)
private estimateTokens(text: string): number {
return Math.ceil(text.length / 4); // Rough estimate: 4 chars per token
}
// FIXED: Manage context history with token limits
private addToContextHistory(context: AnalysisContext, newEntry: string): void {
const entryTokens = this.estimateTokens(newEntry);
// Add new entry
context.contextHistory.push(newEntry);
context.currentContextLength += entryTokens;
// Prune old entries if exceeding limits
while (context.currentContextLength > this.maxContextTokens && context.contextHistory.length > 1) {
const removed = context.contextHistory.shift()!;
context.currentContextLength -= this.estimateTokens(removed);
}
}
// FIXED: Safe JSON parsing with validation
private safeParseJSON(jsonString: string, fallback: any = null): any {
try {
const cleaned = jsonString
.replace(/^```json\s*/i, '')
.replace(/\s*```\s*$/g, '')
.trim();
const parsed = JSON.parse(cleaned);
return parsed;
} catch (error) {
console.warn('[AI PIPELINE] JSON parsing failed:', error.message);
console.warn('[AI PIPELINE] Raw content:', jsonString.slice(0, 200));
return fallback;
}
}
// FIXED: Add tool deduplication
private addToolToSelection(context: AnalysisContext, tool: any, phase: string, priority: string, justification?: string): boolean {
if (context.seenToolNames.has(tool.name)) {
console.log(`[AI PIPELINE] Skipping duplicate tool: ${tool.name}`);
return false;
}
context.seenToolNames.add(tool.name);
if (!context.selectedTools) context.selectedTools = [];
context.selectedTools.push({
tool,
phase,
priority,
justification
});
return true;
}
private async getIntelligentCandidates(userQuery: string, toolsData: any, mode: string) {
let candidateTools: any[] = [];
let candidateConcepts: any[] = [];
let selectionMethod = 'unknown';
// Method 1: Embeddings-based selection (primary)
if (embeddingsService.isEnabled()) {
const similarItems = await embeddingsService.findSimilar(
userQuery,
@@ -90,119 +156,249 @@ class ImprovedMicroTaskAIPipeline {
this.similarityThreshold
);
const toolNames = new Set<string>();
const conceptNames = new Set<string>();
similarItems.forEach(item => {
if (item.type === 'tool') candidateTools.add(item.name);
if (item.type === 'concept') candidateConcepts.add(item.name);
if (item.type === 'tool') toolNames.add(item.name);
if (item.type === 'concept') conceptNames.add(item.name);
});
console.log(`[IMPROVED PIPELINE] Embeddings selected: ${candidateTools.size} tools, ${candidateConcepts.size} concepts`);
console.log(`[IMPROVED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`);
if (candidateTools.size >= 20) {
return {
tools: toolsData.tools.filter((tool: any) => candidateTools.has(tool.name)),
concepts: toolsData.concepts.filter((concept: any) => candidateConcepts.has(concept.name)),
domains: toolsData.domains,
phases: toolsData.phases,
'domain-agnostic-software': toolsData['domain-agnostic-software']
};
// FIXED: Use your expected flow - get full data of embeddings results
if (toolNames.size >= 15) { // Reasonable threshold for quality
candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name));
candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name));
selectionMethod = 'embeddings_candidates';
console.log(`[IMPROVED PIPELINE] Using embeddings candidates: ${candidateTools.length} tools`);
} else {
console.log(`[IMPROVED PIPELINE] Embeddings insufficient (${toolNames.size} < 15), using full dataset`);
candidateTools = toolsData.tools;
candidateConcepts = toolsData.concepts;
selectionMethod = 'full_dataset';
}
} else {
console.log(`[IMPROVED PIPELINE] Embeddings disabled, using full dataset`);
candidateTools = toolsData.tools;
candidateConcepts = toolsData.concepts;
selectionMethod = 'full_dataset';
}
// FIXED: NOW AI ANALYZES FULL DATA of the candidates
console.log(`[IMPROVED PIPELINE] AI will analyze FULL DATA of ${candidateTools.length} candidate tools`);
const finalSelection = await this.aiSelectionWithFullData(userQuery, candidateTools, candidateConcepts, mode, selectionMethod);
// Method 2: Fallback AI selection (like original selector)
console.log(`[IMPROVED PIPELINE] Using AI selector fallback`);
return await this.fallbackAISelection(userQuery, toolsData, mode);
return {
tools: finalSelection.selectedTools,
concepts: finalSelection.selectedConcepts,
domains: toolsData.domains,
phases: toolsData.phases,
'domain-agnostic-software': toolsData['domain-agnostic-software']
};
}
// Fallback AI selection
private async fallbackAISelection(userQuery: string, toolsData: any, mode: string) {
const toolsList = toolsData.tools.map((tool: any) => ({
// src/utils/aiPipeline.ts - FIXED: De-biased AI selection prompt
private async aiSelectionWithFullData(
userQuery: string,
candidateTools: any[],
candidateConcepts: any[],
mode: string,
selectionMethod: string
) {
const modeInstruction = mode === 'workflow'
? 'The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases. Select 15-25 tools that cover the full investigation lifecycle.'
: 'The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem. Select 3-8 tools that are most relevant and effective.';
// FIXED: Give AI the COMPLETE tool data, not truncated
const toolsWithFullData = candidateTools.map((tool: any) => ({
name: tool.name,
type: tool.type,
description: tool.description.slice(0, 200) + '...',
description: tool.description,
domains: tool.domains,
phases: tool.phases,
tags: tool.tags?.slice(0, 5) || [],
skillLevel: tool.skillLevel
platforms: tool.platforms || [],
tags: tool.tags || [],
skillLevel: tool.skillLevel,
license: tool.license,
accessType: tool.accessType,
projectUrl: tool.projectUrl,
knowledgebase: tool.knowledgebase,
related_concepts: tool.related_concepts || [],
related_software: tool.related_software || []
}));
const conceptsList = toolsData.concepts.map((concept: any) => ({
const conceptsWithFullData = candidateConcepts.map((concept: any) => ({
name: concept.name,
type: 'concept',
description: concept.description.slice(0, 200) + '...',
description: concept.description,
domains: concept.domains,
phases: concept.phases,
tags: concept.tags?.slice(0, 5) || []
tags: concept.tags || [],
skillLevel: concept.skillLevel,
related_concepts: concept.related_concepts || [],
related_software: concept.related_software || []
}));
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 with access to the complete forensics tool database. You need to select the most relevant tools and concepts for this specific query.
const prompt = `You are a DFIR expert tasked with selecting the most relevant tools and concepts for a user query.
SELECTION METHOD: ${selectionMethod}
${selectionMethod === 'embeddings_candidates' ?
'These tools were pre-filtered by vector similarity, so they are already relevant. Your job is to select the BEST ones from this relevant set.' :
'You have access to the full tool database. Select the most relevant tools for the query.'}
${modeInstruction}
AVAILABLE TOOLS:
${JSON.stringify(toolsList.slice(0, 50), 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.
CRITICAL SELECTION PRINCIPLES:
1. **CONTEXT OVER POPULARITY**: Don't default to "famous" tools like Volatility, Wireshark, or Autopsy just because they're well-known. Choose based on SPECIFIC scenario needs.
2. **METHODOLOGY vs SOFTWARE**:
- For RAPID/URGENT scenarios → Prioritize METHODS and rapid response approaches
- For TIME-CRITICAL incidents → Choose triage methods over deep analysis tools
- For COMPREHENSIVE analysis → Then consider detailed software tools
- METHODS (type: "method") are often better than SOFTWARE for procedural guidance
3. **SCENARIO-SPECIFIC LOGIC**:
- "Rapid/Quick/Urgent/Triage" scenarios → Rapid Incident Response and Triage METHOD > Volatility
- "Industrial/SCADA/ICS" scenarios → Specialized ICS tools > generic network tools
- "Mobile/Android/iOS" scenarios → Mobile-specific tools > desktop forensics tools
- "Memory analysis needed urgently" → Quick memory tools/methods > comprehensive Volatility analysis
4. **AVOID TOOL BIAS**:
- Volatility is NOT always the answer for memory analysis
- Wireshark is NOT always the answer for network analysis
- Autopsy is NOT always the answer for disk analysis
- Consider lighter, faster, more appropriate alternatives
AVAILABLE TOOLS (with complete data):
${JSON.stringify(toolsWithFullData.slice(0, 30), null, 2)}
AVAILABLE CONCEPTS (with complete data):
${JSON.stringify(conceptsWithFullData.slice(0, 10), null, 2)}
ANALYSIS INSTRUCTIONS:
1. Read the FULL description of each tool/concept
2. Consider ALL tags, platforms, related tools, and metadata
3. **MATCH URGENCY LEVEL**: Rapid scenarios need rapid methods, not deep analysis tools
4. **MATCH SPECIFICITY**: Specialized scenarios need specialized tools, not generic ones
5. **CONSIDER TYPE**: Methods provide procedural guidance, software provides technical capability
6. For SCADA/ICS queries: prioritize specialized ICS tools over generic network tools
7. For mobile queries: prioritize mobile-specific tools over desktop tools
8. For rapid/urgent queries: prioritize methodology and triage approaches
BIAS PREVENTION:
- If query mentions "rapid", "quick", "urgent", "triage" → Strongly favor METHODS over deep analysis SOFTWARE
- If query mentions specific technologies (SCADA, Android, etc.) → Strongly favor specialized tools
- Don't recommend Volatility unless deep memory analysis is specifically needed AND time allows
- Don't recommend generic tools when specialized ones are available
- Consider the SKILL LEVEL and TIME CONSTRAINTS implied by the query
Select the most relevant items (max ${this.maxSelectedItems} total).
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"
"reasoning": "Detailed explanation of why these specific tools were selected for this query, addressing why certain popular tools were NOT selected if they were inappropriate for the scenario context"
}`;
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);
const response = await this.callAI(prompt, 2500); // More tokens for bias prevention logic
if (!Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
throw new Error('Invalid selection result structure');
const result = this.safeParseJSON(response, null);
if (!result || !Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
console.error('[IMPROVED PIPELINE] AI selection returned invalid structure:', response.slice(0, 200));
throw new Error('AI selection failed to return valid tool selection');
}
const totalSelected = result.selectedTools.length + result.selectedConcepts.length;
if (totalSelected > this.maxSelectedItems) {
console.warn(`[IMPROVED 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));
if (totalSelected === 0) {
console.error('[IMPROVED PIPELINE] AI selection returned no tools');
throw new Error('AI selection returned empty selection');
}
console.log(`[IMPROVED PIPELINE] AI selector: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
console.log(`[IMPROVED PIPELINE] AI selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
console.log(`[IMPROVED PIPELINE] AI reasoning: ${result.reasoning}`);
// Return the actual tool/concept objects
const selectedTools = candidateTools.filter(tool => result.selectedTools.includes(tool.name));
const selectedConcepts = candidateConcepts.filter(concept => result.selectedConcepts.includes(concept.name));
console.log(`[IMPROVED PIPELINE] Final selection: ${selectedTools.length} tools with bias prevention applied`);
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']
selectedTools,
selectedConcepts
};
} catch (error) {
console.error('[IMPROVED PIPELINE] Failed to parse selector response');
throw new Error('Invalid JSON response from selector AI');
console.error('[IMPROVED PIPELINE] AI selection failed:', error);
// Emergency fallback with bias awareness
console.log('[IMPROVED PIPELINE] Using emergency keyword-based selection');
return this.emergencyKeywordSelection(userQuery, candidateTools, candidateConcepts, mode);
}
}
private emergencyKeywordSelection(userQuery: string, candidateTools: any[], candidateConcepts: any[], mode: string) {
const queryLower = userQuery.toLowerCase();
const keywords = queryLower.split(/\s+/).filter(word => word.length > 3);
// Score tools based on keyword matches in full data
const scoredTools = candidateTools.map(tool => {
const toolText = (
tool.name + ' ' +
tool.description + ' ' +
(tool.tags || []).join(' ') + ' ' +
(tool.platforms || []).join(' ') + ' ' +
(tool.domains || []).join(' ')
).toLowerCase();
const score = keywords.reduce((acc, keyword) => {
return acc + (toolText.includes(keyword) ? 1 : 0);
}, 0);
return { tool, score };
}).filter(item => item.score > 0)
.sort((a, b) => b.score - a.score);
const maxTools = mode === 'workflow' ? 20 : 8;
const selectedTools = scoredTools.slice(0, maxTools).map(item => item.tool);
console.log(`[IMPROVED PIPELINE] Emergency selection: ${selectedTools.length} tools, keywords: ${keywords.slice(0, 5).join(', ')}`);
return {
selectedTools,
selectedConcepts: candidateConcepts.slice(0, 3)
};
}
private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// IMPROVED: Enhanced micro-task with context history
private async callMicroTaskAI(prompt: string, context: AnalysisContext, maxTokens: number = 300): Promise<MicroTaskResult> {
const startTime = Date.now();
// ADDED: Include context history for continuity
const contextPrompt = context.contextHistory.length > 0 ?
`BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n${prompt}` :
prompt;
// FIXED: Build context prompt with token management
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;
// Check if combined prompt exceeds limits
if (this.estimateTokens(combinedPrompt) <= this.maxPromptTokens) {
contextPrompt = combinedPrompt;
} else {
console.warn('[AI PIPELINE] Context too long, using prompt only');
// Could implement smarter context truncation here
}
}
try {
const response = await this.callAI(contextPrompt, maxTokens);
@@ -225,9 +421,6 @@ Respond with ONLY this JSON format:
}
}
// 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';
@@ -258,17 +451,15 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
context.problemAnalysis = result.content;
}
// ADDED: Build context history
context.contextHistory.push(`${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
// FIXED: Use new context management
this.addToContextHistory(context, `${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'} nach NIST SP 800-86 Methodik.
@@ -291,13 +482,12 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
if (result.success) {
context.investigationApproach = result.content;
context.contextHistory.push(`${isWorkflow ? 'Untersuchungs' : 'Lösungs'}ansatz: ${result.content.slice(0, 200)}...`);
this.addToContextHistory(context, `${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';
@@ -324,13 +514,12 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
if (result.success) {
context.criticalConsiderations = result.content;
context.contextHistory.push(`Kritische Überlegungen: ${result.content.slice(0, 200)}...`);
this.addToContextHistory(context, `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)
@@ -370,41 +559,27 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text):
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());
// FIXED: Safe JSON parsing with validation
const selections = this.safeParseJSON(result.content, []);
if (Array.isArray(selections)) {
const validSelections = selections.filter((sel: any) =>
phaseTools.some((tool: any) => tool.name === sel.toolName)
sel.toolName && 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
});
// FIXED: Use deduplication helper
this.addToolToSelection(context, tool, phase.id, sel.priority, 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 nach forensischen Qualitätskriterien.
@@ -428,36 +603,29 @@ Bewerten Sie nach forensischen Standards und antworten Sie AUSSCHLIESSLICH mit d
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'
};
}
// FIXED: Safe JSON parsing
const evaluation = this.safeParseJSON(result.content, {
suitability_score: 'medium',
detailed_explanation: 'Evaluation failed',
implementation_approach: '',
pros: [],
cons: [],
alternatives: ''
});
// FIXED: Use deduplication helper
this.addToolToSelection(context, {
...tool,
evaluation: {
...evaluation,
rank
}
}, 'evaluation', evaluation.suitability_score);
}
return result;
}
// MICRO-TASK 6: Background Knowledge
private async selectBackgroundKnowledge(context: AnalysisContext): Promise<MicroTaskResult> {
const availableConcepts = context.filteredData.concepts;
@@ -493,30 +661,22 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
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());
// FIXED: Safe JSON parsing
const selections = this.safeParseJSON(result.content, []);
if (Array.isArray(selections)) {
context.backgroundKnowledge = selections.filter((sel: any) =>
availableConcepts.some((concept: any) => concept.name === sel.conceptName)
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
}));
} 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';
@@ -543,7 +703,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
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',
@@ -574,7 +733,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
return content;
}
// MAIN PROCESSING: Restored original structure with context continuity
async processQuery(userQuery: string, mode: string): Promise<AnalysisResult> {
const startTime = Date.now();
let completedTasks = 0;
@@ -587,17 +745,20 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
const toolsData = await getCompressedToolsDataForAI();
const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode);
// Initialize context with continuity
// FIXED: Initialize context with proper state management
const context: AnalysisContext = {
userQuery,
mode,
filteredData,
contextHistory: [] // ADDED: Context continuity
contextHistory: [],
maxContextLength: this.maxContextTokens,
currentContextLength: 0,
seenToolNames: new Set<string>() // FIXED: Add deduplication tracking
};
console.log(`[IMPROVED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`);
// MICRO-TASK SEQUENCE (restored original structure)
// MICRO-TASK SEQUENCE
// Task 1: Scenario/Problem Analysis
const analysisResult = await this.analyzeScenario(context);
@@ -642,12 +803,11 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
const finalResult = await this.generateFinalRecommendations(context);
if (finalResult.success) completedTasks++; else failedTasks++;
// Build final recommendation (same as original)
// Build final recommendation
const recommendation = this.buildRecommendation(context, mode, finalResult.content);
const processingStats = {
embeddingsUsed: embeddingsService.isEnabled(),
vectorIndexUsed: embeddingsService.isEnabled(), // VectorIndex is used when embeddings are enabled
candidatesFromEmbeddings: filteredData.tools.length,
finalSelectedItems: (context.selectedTools?.length || 0) +
(context.backgroundKnowledge?.length || 0),
@@ -658,7 +818,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
};
console.log(`[IMPROVED PIPELINE] Completed: ${completedTasks} tasks, Failed: ${failedTasks} tasks`);
console.log(`[IMPROVED PIPELINE] VectorIndex used: ${embeddingsService.isEnabled()}, Candidates: ${filteredData.tools.length}`);
console.log(`[IMPROVED PIPELINE] Unique tools selected: ${context.seenToolNames.size}`);
return {
recommendation,
@@ -671,7 +831,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
}
}
// Build recommendation (same as original structure)
// Build recommendation (same structure but using fixed context)
private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any {
const isWorkflow = mode === 'workflow';