diff --git a/.env.example b/.env.example index 95157ed..cf2ad4b 100644 --- a/.env.example +++ b/.env.example @@ -13,68 +13,60 @@ OIDC_ENDPOINT=https://your-oidc-provider.com OIDC_CLIENT_ID=your-client-id OIDC_CLIENT_SECRET=your-client-secret -# === AI Configuration === +# =================================================================== +# AI CONFIGURATION - Complete Reference for Improved Pipeline +# =================================================================== -# Selector AI (for selection stage, choode a good model) -AI_SELECTOR_ENDPOINT=https://llm.mikoshi.de -AI_SELECTOR_API_KEY=sk-DzREDACTEDHA -AI_SELECTOR_MODEL=mistral/mistral-medium-latest +# === CORE AI ENDPOINTS & MODELS === +AI_API_ENDPOINT=https://llm.mikoshi.de +AI_API_KEY=sREDACTED3w +AI_MODEL='mistral/mistral-small-latest' -# Analyzer AI (for analysis stage, choose a smaller model) +# === IMPROVED PIPELINE: Use separate analyzer model (mistral-small is fine) === AI_ANALYZER_ENDPOINT=https://llm.mikoshi.de -AI_ANALYZER_API_KEY=sk-DzREDACTEDnHA -AI_ANALYZER_MODEL=mistral/mistral-small-latest +AI_ANALYZER_API_KEY=skREDACTEDw3w +AI_ANALYZER_MODEL='mistral/mistral-small-latest' -# === Embeddings Configuration === -# Enable/disable semantic embeddings pre-selection +# === EMBEDDINGS CONFIGURATION === AI_EMBEDDINGS_ENABLED=true - -# Embeddings API (Mistral recommended) AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings -AI_EMBEDDINGS_API_KEY=ZSpREDACTED3wL +AI_EMBEDDINGS_API_KEY=ZREDACTED3wL AI_EMBEDDINGS_MODEL=mistral-embed - -# Embeddings performance settings AI_EMBEDDINGS_BATCH_SIZE=20 AI_EMBEDDINGS_BATCH_DELAY_MS=1000 -AI_EMBEDDING_CANDIDATES=30 -AI_SIMILARITY_THRESHOLD=0.3 -# Delay between micro-tasks to respect rate limits (milliseconds) -AI_MICRO_TASK_DELAY_MS=500 +# === PIPELINE: VectorIndex (HNSW) Configuration === +AI_MAX_SELECTED_ITEMS=60 # Tools visible to each micro-task +AI_EMBEDDING_CANDIDATES=60 # VectorIndex candidates (HNSW is more efficient) +AI_SIMILARITY_THRESHOLD=0.3 # Not used by VectorIndex (uses cosine distance internally) -# Micro-task specific rate limiting (requests per minute per user) -AI_MICRO_TASK_RATE_LIMIT=30 +# === MICRO-TASK CONFIGURATION === +AI_MICRO_TASK_DELAY_MS=500 # Delay between micro-tasks +AI_MICRO_TASK_TIMEOUT_MS=25000 # Timeout per micro-task (increased for full context) -# Maximum parallel micro-tasks (for future parallel processing) -AI_MAX_PARALLEL_TASKS=3 +# === RATE LIMITING === +AI_RATE_LIMIT_DELAY_MS=3000 # Main rate limit delay +AI_RATE_LIMIT_MAX_REQUESTS=6 # Main requests per minute (reduced - fewer but richer calls) +AI_MICRO_TASK_RATE_LIMIT=15 # Micro-task requests per minute (was 30) -# Micro-task timeout settings (milliseconds) -AI_MICRO_TASK_TIMEOUT_MS=15000 - -# ENHANCED: Rate Limiting Configuration -# Main query rate limiting (reduced due to micro-tasks) -AI_RATE_LIMIT_DELAY_MS=3000 -AI_RATE_LIMIT_MAX_REQUESTS=8 - -# Smart prompting rate limiting -AI_SMART_PROMPTING_RATE_LIMIT=5 -AI_SMART_PROMPTING_WINDOW_MS=60000 - -# Queue management settings +# === QUEUE MANAGEMENT === AI_QUEUE_MAX_SIZE=50 AI_QUEUE_CLEANUP_INTERVAL_MS=300000 -# === Performance & Monitoring === -# Enable detailed micro-task logging -AI_MICRO_TASK_DEBUG=false - -# Enable performance metrics collection +# === PERFORMANCE & MONITORING === +AI_MICRO_TASK_DEBUG=true AI_PERFORMANCE_METRICS=true - -# Cache settings for AI responses AI_RESPONSE_CACHE_TTL_MS=3600000 +# =================================================================== +# LEGACY VARIABLES (still used but less important) +# =================================================================== + +# These are still used by other parts of the system: +AI_RESPONSE_CACHE_TTL_MS=3600000 # For caching responses +AI_QUEUE_MAX_SIZE=50 # Queue management +AI_QUEUE_CLEANUP_INTERVAL_MS=300000 # Queue cleanup + # === Application Configuration === PUBLIC_BASE_URL=http://localhost:4321 NODE_ENV=development diff --git a/src/utils/aiPipeline.ts b/src/utils/aiPipeline.ts index 466b407..39a8105 100644 --- a/src/utils/aiPipeline.ts +++ b/src/utils/aiPipeline.ts @@ -1,8 +1,8 @@ -// src/utils/aiPipeline.ts +// src/utils/aiPipeline.ts - FIXED: Restore proper structure with context continuity import { getCompressedToolsDataForAI } from './dataService.js'; import { embeddingsService, type EmbeddingData } from './embeddings.js'; -import { vectorIndex } from "./vectorIndex.js"; +import { vectorIndex } from './vectorIndex.js'; interface AIConfig { endpoint: string; @@ -31,15 +31,15 @@ interface AnalysisResult { }; } -// Context object that builds up through pipeline +// FIXED: Context object that builds up through pipeline interface AnalysisContext { userQuery: string; mode: string; filteredData: any; - // Context continuity + // ADDED: Context continuity contextHistory: string[]; - // Results + // Results (same as original) scenarioAnalysis?: string; problemAnalysis?: string; investigationApproach?: string; @@ -48,9 +48,6 @@ interface AnalysisContext { backgroundKnowledge?: Array<{concept: any, relevance: string}>; } -/** - * Improved DFIR micro‑task pipeline – 2025‑08‑01 revision (bug‑fixed) - */ class ImprovedMicroTaskAIPipeline { private config: AIConfig; private maxSelectedItems: number; @@ -61,50 +58,63 @@ class ImprovedMicroTaskAIPipeline { constructor() { this.config = { endpoint: this.getEnv('AI_ANALYZER_ENDPOINT'), - apiKey: this.getEnv('AI_ANALYZER_API_KEY'), + apiKey: this.getEnv('AI_ANALYZER_API_KEY'), model: this.getEnv('AI_ANALYZER_MODEL') }; - // Candidate selection tuned for higher precision + // 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 || '40', 10); - this.similarityThreshold = parseFloat(process.env.AI_SIMILARITY_THRESHOLD || '0.5'); + 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.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}`); + if (!value) { + throw new Error(`Missing environment variable: ${key}`); + } return value; } - /** Embedding → LLM blended selector */ + // IMPROVED: AI-driven selection (no hard-coded keywords) private async getIntelligentCandidates(userQuery: string, toolsData: any, mode: string) { - const candidateTools = new Set(); - const candidateConcepts = new Set(); - + let candidateTools = new Set(); + let candidateConcepts = new Set(); + + // Method 1: Embeddings-based selection (primary) if (embeddingsService.isEnabled()) { - const similarItems = await vectorIndex.findSimilar(userQuery, this.embeddingCandidates); - + const similarItems = await embeddingsService.findSimilar( + userQuery, + this.embeddingCandidates, + this.similarityThreshold + ); + 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`); + + console.log(`[IMPROVED PIPELINE] Embeddings selected: ${candidateTools.size} tools, ${candidateConcepts.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'] + }; + } } - - 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); + + // Method 2: Fallback AI selection (like original selector) + console.log(`[IMPROVED PIPELINE] Using AI selector fallback`); + return await this.fallbackAISelection(userQuery, toolsData, mode); } - /** Language‑model based selector (no 50‑item cap) */ - private async aiSelection(userQuery: string, toolsData: any, mode: string) { + // Fallback AI selection + private async fallbackAISelection(userQuery: string, toolsData: any, mode: string) { const toolsList = toolsData.tools.map((tool: any) => ({ name: tool.name, type: tool.type, @@ -124,17 +134,16 @@ class ImprovedMicroTaskAIPipeline { 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 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)} +${JSON.stringify(toolsList.slice(0, 50), null, 2)} AVAILABLE CONCEPTS: ${JSON.stringify(conceptsList, null, 2)} @@ -154,19 +163,20 @@ Respond with ONLY this JSON format: 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`); + 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)); } - console.log(`[PIPELINE] LLM selector → ${result.selectedTools.length} tools / ${result.selectedConcepts.length} concepts`); + console.log(`[IMPROVED PIPELINE] AI selector: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`); + console.log(`[IMPROVED PIPELINE] AI reasoning: ${result.reasoning}`); return { tools: toolsData.tools.filter((tool: any) => result.selectedTools.includes(tool.name)), @@ -175,25 +185,43 @@ Respond with ONLY this JSON format: phases: toolsData.phases, 'domain-agnostic-software': toolsData['domain-agnostic-software'] }; - } catch (err) { - console.error('[PIPELINE] Failed to parse selector response'); + } catch (error) { + console.error('[IMPROVED 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 { - const start = Date.now(); - const contextPrompt = context.contextHistory.length - ? `BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n${prompt}` - : prompt; + private async delay(ms: number): Promise { + 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 { + 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; + 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 }; + + return { + taskType: 'micro-task', + content: response.trim(), + processingTimeMs: Date.now() - startTime, + success: true + }; + + } catch (error) { + return { + taskType: 'micro-task', + content: '', + processingTimeMs: Date.now() - startTime, + success: false, + error: error.message + }; } } @@ -210,8 +238,13 @@ ${isWorkflow ? 'FORENSISCHES SZENARIO' : 'TECHNISCHES PROBLEM'}: "${context.user 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` + `- Angriffsvektoren und Bedrohungsmodellierung nach MITRE ATT&CK +- Betroffene Systeme und kritische Infrastrukturen +- Zeitkritische Faktoren und Beweiserhaltung +- Forensische Artefakte und Datenquellen` : + `- Spezifische forensische Herausforderungen +- Verfügbare Datenquellen und deren Integrität +- Methodische Anforderungen für rechtssichere Analyse` } WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählungen oder Markdown-Formatierung. Maximum 150 Wörter.`; @@ -237,16 +270,19 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun 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'}. + const prompt = `Basierend auf der Analyse entwickeln Sie einen fundierten ${isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz'} nach NIST SP 800-86 Methodik. ${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` + `- Triage-Prioritäten nach forensischer Dringlichkeit +- Phasenabfolge nach NIST-Methodik +- Kontaminationsvermeidung und forensische Isolierung` : + `- Methodik-Auswahl nach wissenschaftlichen Kriterien +- Validierung und Verifizierung der gewählten Ansätze +- Integration in bestehende forensische Workflows` } WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 150 Wörter.`; @@ -269,11 +305,17 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo ${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${context.userQuery}" -Berücksichtigen Sie folgende Aspekte: +Berücksichtigen Sie folgende forensische Aspekte: ${isWorkflow ? - `- Szenariobezogene typische Problemstellungen, die auftreten können` : - `- Problembezogene Schwierigkeiten, die das Ergebnis negativ beeinträchtigen könnten` + `- Time-sensitive evidence preservation +- Chain of custody requirements und rechtliche Verwertbarkeit +- Incident containment vs. evidence preservation Dilemma +- Privacy- und Compliance-Anforderungen` : + `- Tool-Validierung und Nachvollziehbarkeit +- False positive/negative Risiken bei der gewählten Methodik +- Qualifikationsanforderungen für die Durchführung +- Dokumentations- und Reporting-Standards` } WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 120 Wörter.`; @@ -311,9 +353,10 @@ 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 +- Court admissibility und Chain of Custody Kompatibilität +- Integration in forensische Standard-Workflows +- Reproduzierbarkeit und Dokumentationsqualität +- Objektivität Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text): [ @@ -363,7 +406,7 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text): // MICRO-TASK 5: Tool Evaluation (Tool mode) private async evaluateSpecificTool(context: AnalysisContext, tool: any, rank: number): Promise { - const prompt = `Bewerten Sie diese Methode/Tool fallbezogen für das spezifische Problem. + const prompt = `Bewerten Sie diese Methode/Tool fallbezogen für das spezifische Problem nach forensischen Qualitätskriterien. PROBLEM: "${context.userQuery}" @@ -437,13 +480,13 @@ 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. +Wählen Sie 2-4 Konzepte aus, die für das Verständnis der forensischen Methodik 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" + "relevance": "Forensische Relevanz: Warum dieses Konzept für das Verständnis der Methodik kritisch ist" } ]`; @@ -478,21 +521,21 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format: const isWorkflow = context.mode === 'workflow'; const prompt = isWorkflow ? - `Erstellen Sie eine forensisch fundierte Workflow-Empfehlung unter Anwendung der gewählten Methoden/Tools. + `Erstellen Sie eine forensisch fundierte Workflow-Empfehlung basierend auf DFIR-Prinzipien. 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). +Erstellen Sie konkrete methodische Workflow-Schritte für dieses spezifische Szenario unter Berücksichtigung forensischer Best Practices, Objektivität und rechtlicher Verwertbarkeit. 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. + `Erstellen Sie wichtige methodische Ü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. +Geben Sie kritische methodische Überlegungen, Validierungsanforderungen und Qualitätssicherungsmaßnahmen 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.`; @@ -531,16 +574,26 @@ 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 { const startTime = Date.now(); let completedTasks = 0; let failedTasks = 0; + + console.log(`[IMPROVED PIPELINE] Starting ${mode} query processing with context continuity`); try { + // Stage 1: Get intelligent candidates (embeddings + AI selection) const toolsData = await getCompressedToolsDataForAI(); const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode); - - const context: AnalysisContext = { userQuery, mode, filteredData, contextHistory: [] }; + + // Initialize context with continuity + const context: AnalysisContext = { + userQuery, + mode, + filteredData, + contextHistory: [] // ADDED: Context continuity + }; console.log(`[IMPROVED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`); @@ -571,8 +624,8 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo await this.delay(this.microTaskDelay); } } else { - const shuffled = [...filteredData.tools].sort(() => Math.random() - 0.5); // FIX - const topTools = shuffled.slice(0, 3); + // Evaluate top 3 tools for specific problem + const topTools = filteredData.tools.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++; @@ -589,21 +642,31 @@ 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++; - const recommendation = this.buildRecommendation(context, mode, ''); // finalContent injected inside omitted logic + // Build final recommendation (same as original) + 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), + finalSelectedItems: (context.selectedTools?.length || 0) + + (context.backgroundKnowledge?.length || 0), processingTimeMs: Date.now() - startTime, microTasksCompleted: completedTasks, microTasksFailed: failedTasks, contextContinuityUsed: true }; - return { recommendation, processingStats }; + console.log(`[IMPROVED PIPELINE] Completed: ${completedTasks} tasks, Failed: ${failedTasks} tasks`); + console.log(`[IMPROVED PIPELINE] VectorIndex used: ${embeddingsService.isEnabled()}, Candidates: ${filteredData.tools.length}`); + + return { + recommendation, + processingStats + }; + } catch (error) { - console.error('[PIPELINE] Processing failed:', error); + console.error('[IMPROVED PIPELINE] Processing failed:', error); throw error; } }