diff --git a/.env.example b/.env.example index c4cf130..d2a8622 100644 --- a/.env.example +++ b/.env.example @@ -80,6 +80,14 @@ AI_SIMILARITY_THRESHOLD=0.3 AI_EMBEDDING_SELECTION_LIMIT=30 AI_EMBEDDING_CONCEPTS_LIMIT=15 +# === METHOD/TOOL BALANCE CONFIGURATION === +# Controls the ratio of methods vs software tools sent to AI +# Methods = procedural guidance, best practices, workflows +# Software = actual tools and applications +# Values should sum to less than 1.0 (remainder is buffer) +AI_METHOD_SELECTION_RATIO=0.4 # 40% methods (increase for more procedural guidance) +AI_SOFTWARE_SELECTION_RATIO=0.5 # 50% software tools (increase for more tool recommendations) + # AI selection limits AI_MAX_SELECTED_ITEMS=25 AI_MAX_TOOLS_TO_ANALYZE=20 @@ -155,6 +163,14 @@ CONFIDENCE_HIGH_THRESHOLD=80 # AI_MAX_TOOLS_TO_ANALYZE=10 # AI_EMBEDDINGS_ENABLED=false +# 🔬 METHOD-FOCUSED (more procedural guidance, less tools): +# AI_METHOD_SELECTION_RATIO=0.6 +# AI_SOFTWARE_SELECTION_RATIO=0.3 + +# đŸ› ïž TOOL-FOCUSED (more software recommendations, less methods): +# AI_METHOD_SELECTION_RATIO=0.2 +# AI_SOFTWARE_SELECTION_RATIO=0.7 + # ============================================================================ # 🌐 AI SERVICE EXAMPLES # ============================================================================ diff --git a/src/config/prompts.ts b/src/config/prompts.ts index 25f45fb..bc51f4e 100644 --- a/src/config/prompts.ts +++ b/src/config/prompts.ts @@ -1,212 +1,207 @@ -// src/config/prompts.ts +// src/config/prompts.ts - Centralized German prompts for AI pipeline export const AI_PROMPTS = { toolSelection: (mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number) => { const modeInstruction = mode === 'workflow' - ? 'Der Benutzer möchte einen UMFASSENDEN WORKFLOW ĂŒber mehrere Phasen. WĂ€hlen Sie 15–25 Elemente, die den Zyklus Datensammlung → Auswertung → Analyse → Bericht abdecken.' - : 'Der Benutzer möchte SPEZIFISCHE LÖSUNGEN. WĂ€hlen Sie 4–10 Elemente, die das Problem direkt adressieren.'; + ? 'Workflow mit 15-25 Items ĂŒber alle Phasen. PFLICHT: Mindestens 40% Methoden, Rest Tools/Konzepte.' + : 'Spezifische Lösung mit 4-10 Items. PFLICHT: Mindestens 30% Methoden wenn verfĂŒgbar.'; - return `Sie sind DFIR-Experte mit Zugriff auf eine vollstĂ€ndige Datenbank aus Methoden (type: "method") und Tools (type: "software"/"os"). Ihre Aufgabe ist es, die relevantesten Elemente fĂŒr die Anfrage auszuwĂ€hlen. + return `Du bist ein DFIR-Experte. WĂ€hle die BESTEN Items aus dem vorgefilterten Set. AUSWAHLMETHODE: ${selectionMethod} ${selectionMethod === 'embeddings_candidates' ? - 'Die angezeigten Kandidaten wurden bereits semantisch vorgefiltert. WĂ€hlen Sie daraus die BESTEN.' : - 'Sie sehen die vollstĂ€ndige Datenbasis. WĂ€hlen Sie die relevantesten Elemente.'} - -WICHTIG – MISCHUNG ERZWINGEN: -- Der finale Vorschlag MUSS eine Mischung aus Methoden UND Tools enthalten (sofern verfĂŒgbar). -- Ziel: grob 40–60 % Methoden und 40–60 % Tools. Wenn eine Kategorie knapp ist, wĂ€hlen Sie so ausgewogen wie möglich. BegrĂŒnden Sie Abweichungen im reasoning. -- Bezeichnen Sie Elemente konsequent als "Methode" oder "Tool" je nach Typ. + '✓ Semantisch relevante Items bereits vorgefiltert\n✓ WĂ€hle die BESTEN fĂŒr die konkrete Aufgabe' : + '✓ VollstĂ€ndige Datenbank verfĂŒgbar\n✓ WĂ€hle die relevantesten Items'} ${modeInstruction} -BENUTZER-ANFRAGE: "${userQuery}" +ANFRAGE: "${userQuery}" -KRITISCHE AUSWAHLPRINZIPIEN: -1) KONTEXT > POPULARITÄT: Nicht automatisch zu populĂ€ren Tools greifen. WĂ€hlen Sie strikt szenariobezogen. -2) METHODIK vs. SOFTWARE: - - Dringend/Triage → Methoden fĂŒr schnelle Reaktion priorisieren. - - Zeitkritisch → Triage-Methoden vor Tiefenanalyse-Tools. - - Umfassende Analyse → dann spezialisierte Tools ergĂ€nzen. -3) SPEZIFITÄT: - - ICS/SCADA → spezialisierte ICS-Methoden/-Tools statt generischer Netzwerk-Tools. - - Mobile (Android/iOS) → mobile-spezifische Methoden/Tools. - - Speicheranalyse dringend → schnelle Memory-Methoden/Tools vor Vollanalyse. -4) LESEN SIE DIE VOLLBESCHREIBUNG UND METADATEN (Tags, Plattformen, Phasen, Lizenz, Access). +VERFÜGBARE ITEM-TYPEN: +- TOOLS (type: "software"/"method") → praktische Anwendungen und Vorgehensweisen +- KONZEPTE (type: "concept") → theoretisches Wissen und Methodiken -UI-KÜRZE: -- Denken Sie mit: FĂŒr das UI sind kurze, prĂ€gnante Beschreibungen nötig. Bevorzugen Sie Elemente, die sich knapp zusammenfassen lassen. Formulieren Sie BegrĂŒndungen fokussiert und ohne Floskeln. +AUSWAHLSTRATEGIE: +1. **ERSTE PRIORITÄT: Relevanz zur Anfrage** + - Direkt anwendbar auf das Problem + - Löst die Kernherausforderung -WĂ€hlen Sie die relevantesten Elemente (max ${maxSelectedItems} gesamt). +2. **ZWEITE PRIORITÄT: Ausgewogene Mischung** + - Tools/Methoden fĂŒr praktische Umsetzung → selectedTools + - Konzepte fĂŒr methodisches VerstĂ€ndnis → selectedConcepts + - WICHTIG: Auch Konzepte auswĂ€hlen, nicht nur Tools! -ANTWORTFORMAT (JSON, SCHEMA UNVERÄNDERT): +3. **QUALITÄT > QUANTITÄT** + - Lieber weniger perfekte Items als viele mittelmĂ€ĂŸige + - Jedes Item muss begrĂŒndbar sein + +AUSWAHLREGELN: +- WĂ€hle ${mode === 'workflow' ? '15-25' : '4-10'} Items total, max ${maxSelectedItems} +- BEIDE Arrays fĂŒllen: selectedTools UND selectedConcepts +- Mindestens 1-2 Konzepte auswĂ€hlen fĂŒr methodische Fundierung +- Tools: 40% Methoden (type="method"), Rest Software (type="software") + +ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT: { - "selectedTools": ["Name 1", "Name 2", ...], - "selectedConcepts": ["Konzept 1", "Konzept 2", ...], - "reasoning": "BegrĂŒnden Sie die Auswahl, die erzwungene Mischung Methode/Tool und warum bestimmte populĂ€re Elemente nicht geeignet sind. Halten Sie die Sprache prĂ€zise und UI-tauglich." + "selectedTools": ["ToolName1", "MethodName1", ...], + "selectedConcepts": ["ConceptName1", "ConceptName2", ...], + "reasoning": "Kurze BegrĂŒndung mit ErwĂ€hnung der Tool/Konzept-Balance" }`; }, scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => { - const analysisType = isWorkflow ? 'Untersuchungsszenario' : 'technische Problemstellung'; - const considerations = isWorkflow ? - `- Angriffsvektoren (MITRE ATT&CK) und Bedrohungsmodell -- Betroffene Systeme/Assets und KritikalitĂ€t -- Zeitdruck, Beweiserhalt, Chain of Custody -- Relevante Artefakte und Datenquellen` : - `- Konkrete forensische HĂŒrden -- VerfĂŒgbare Datenquellen und IntegritĂ€t -- Anforderungen fĂŒr rechtssichere Auswertung`; + const analysisType = isWorkflow ? 'Szenario' : 'Problem'; + const focus = isWorkflow ? + 'Angriffsvektoren, betroffene Systeme, ZeitkritikalitĂ€t' : + 'Kernherausforderung, verfĂŒgbare Daten, methodische Anforderungen'; - return `Analysieren Sie das ${analysisType} prĂ€gnant in einem kurzen Fließtext (max. 220 Wörter). + return `DFIR-Experte: Analysiere das ${analysisType}. ${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}" -BerĂŒcksichtigen: -${considerations} +Fokus: ${focus} -Stil: Deutsch, ohne Listen/Markdown; klare, knappe Formulierungen, aber nicht telegrafisch (vollstĂ€ndige SĂ€tze).`; +Antwort: Fließtext ohne Listen, max 100 Wörter.`; }, investigationApproach: (isWorkflow: boolean, userQuery: string) => { const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz'; - const considerations = isWorkflow ? - `- Triage-PrioritĂ€ten nach forensischer Dringlichkeit -- Phasenabfolge: Datensammlung → Auswertung → Analyse → Bericht -- Kontaminationsvermeidung/Isolierung -- ObjektivitĂ€t und Nachvollziehbarkeit` : - `- Methodenwahl nach wissenschaftlichen Kriterien -- Validierung/Verifizierung der Vorgehensweise -- Integration in bestehende DFIR-Workflows -- Reproduzierbarkeit und Transparenz`; + const focus = isWorkflow ? + 'Triage-PrioritĂ€ten, Phasenabfolge, Kontaminationsvermeidung' : + 'Methodenauswahl, Validierung, Integration'; - return `Formulieren Sie einen knappen ${approachType} (max. 220 Wörter), der explizit eine Mischung aus Methoden und Tools vorsieht. + return `Entwickle einen ${approachType}. ${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}" -BerĂŒcksichtigen: -${considerations} +Fokus: ${focus} -Stil: Deutsch, ohne Listen/Markdown, UI-tauglich und fokussiert.`; +Antwort: Fließtext ohne Listen, max 100 Wörter.`; }, criticalConsiderations: (isWorkflow: boolean, userQuery: string) => { - const considerationType = isWorkflow ? 'kritische forensische Überlegungen' : 'methodische Voraussetzungen'; - const aspects = isWorkflow ? - `- Beweissicherung vs. GrĂŒndlichkeit (Zeitdruck) -- Chain of Custody und rechtliche Verwertbarkeit -- Transparenz, Reproduzierbarkeit, Dokumentationspflichten` : - `- Validierung/Nachvollziehbarkeit der Methode/Tools -- Risiken fĂŒr False Positives/Negatives -- Qualifikationsanforderungen und Reporting-Standards`; + const focus = isWorkflow ? + 'Beweissicherung vs. GrĂŒndlichkeit, Chain of Custody' : + 'Tool-Validierung, False Positives/Negatives, Qualifikationen'; - return `Identifizieren Sie ${considerationType} in einem kurzen Fließtext (max. 220 Wörter) und achten Sie auf UI-KĂŒrze. + return `Identifiziere kritische Überlegungen. ${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}" -Aspekte: -${aspects} +Fokus: ${focus} -Stil: Deutsch, ohne Listen/Markdown.`; +Antwort: Fließtext ohne Listen, max 100 Wörter.`; }, phaseToolSelection: (userQuery: string, phase: any, phaseTools: any[]) => { - // In der Liste bewusst kurze Ausschnitte und klare Typ-Bezeichnung (Methode/Tool). - const items = phaseTools.map((tool: any, index: number) => { - const typ = tool.type === 'method' ? 'Methode' : 'Tool'; - const desc = (tool.description || '').replace(/\s+/g, ' ').trim().slice(0, 320); - return `${index + 1}. [${typ}] ${tool.name}: ${desc}
\n - Plattformen: ${tool.platforms?.join(', ') || 'N/A'}\n - Skill Level: ${tool.skillLevel}\n - Tags: ${tool.tags?.join(', ') || 'N/A'}`; - }).join('\n\n'); - - return `WĂ€hlen Sie 2–3 Elemente fĂŒr die Phase "${phase.name}" und bewerten Sie diese VERGLEICHEND. Erzwingen Sie eine Mischung aus mindestens einer Methode und einem Tool, sofern verfĂŒgbar. + const methods = phaseTools.filter(t => t.type === 'method'); + const tools = phaseTools.filter(t => t.type === 'software'); + + if (phaseTools.length === 0) { + return `Keine Tools fĂŒr Phase "${phase.name}" verfĂŒgbar. Antworte mit leerem Array: []`; + } + + return `Du bist ein DFIR-Experte. WĂ€hle die 2-3 BESTEN Items fĂŒr Phase "${phase.name}". SZENARIO: "${userQuery}" -PHASE: ${phase.name} – ${phase.description || 'Forensische Untersuchungsphase'} +PHASE: ${phase.name} - ${phase.description || ''} -VERFÜGBARE ELEMENTE FÜR ${phase.name.toUpperCase()}: -${items} +VERFÜGBARE ITEMS (bereits von KI vorausgewĂ€hlt): +${methods.length > 0 ? ` +METHODEN (${methods.length}): +${methods.map((method: any) => + `- ${method.name} + Typ: ${method.type} + Beschreibung: ${method.description.slice(0, 150)}... + Domains: ${method.domains?.join(', ') || 'N/A'} + Skill Level: ${method.skillLevel}` +).join('\n\n')} +` : 'Keine Methoden verfĂŒgbar'} -BEWERTUNGSKRITERIEN: -- Eignung fĂŒr das konkrete Szenario und GENAU diese Phase -- Vergleich zu den Alternativen in der Liste -- PraktikabilitĂ€t (Zeit, Setup, Reproduzierbarkeit) +${tools.length > 0 ? ` +SOFTWARE TOOLS (${tools.length}): +${tools.map((tool: any) => + `- ${tool.name} + Typ: ${tool.type} + Beschreibung: ${tool.description.slice(0, 150)}... + Plattformen: ${tool.platforms?.join(', ') || 'N/A'} + Skill Level: ${tool.skillLevel}` +).join('\n\n')} +` : 'Keine Software-Tools verfĂŒgbar'} -UI-KÜRZE: -- Beginnen Sie in "justification" mit einer 2–4-Satz Kurzfassung (≈300–360 Zeichen), danach 2–3 kurze, prĂ€zise SĂ€tze mit Details. -- Bezeichnen Sie Elemente als "Methode" oder "Tool". +AUSWAHLREGELN FÜR PHASE "${phase.name}": +1. WĂ€hle die 2-3 BESTEN Items fĂŒr diese spezifische Phase +2. Priorisiere Items, die DIREKT fĂŒr "${phase.name}" relevant sind +3. Mindestens 1 Methode wenn verfĂŒgbar, Rest Software-Tools +4. BegrĂŒnde WARUM jedes Item fĂŒr diese Phase optimal ist -ANTWORT NUR ALS JSON (Schema UNVERÄNDERT): +WICHTIG: Verwende EXAKT die Namen wie oben aufgelistet (ohne PrĂ€fixe wie M1./T2.)! + +ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB: [ { - "toolName": "Exakter Methoden/Tool-Name", - "taskRelevance": 0–100, - "justification": "Kurzfassung zuerst (≈300–360 Zeichen). Danach knappe, vergleichende BegrĂŒndung, warum dieses Element in dieser Phase besser geeignet ist.", - "limitations": ["Konkrete EinschrĂ€nkung 1", "EinschrĂ€nkung 2"] + "toolName": "Exakter Name aus der Liste oben", + "taskRelevance": 85, + "justification": "Spezifische BegrĂŒndung warum optimal fĂŒr ${phase.name}", + "limitations": ["Mögliche EinschrĂ€nkung fĂŒr diese Phase"] } -] - -WĂ€hlen Sie nur die 2–3 BESTEN Elemente und achten Sie auf die Mischung Methode+Tool.`; +]`; }, - toolEvaluation: (userQuery: string, tool: any, rank: number, taskRelevance: number) => { - const typ = tool.type === 'method' ? 'Methode' : 'Tool'; - return `Sie sind DFIR-Experte. ErklĂ€ren Sie DETAILLIERT die Anwendung dieses bereits bewerteten Elements. + const itemType = tool.type === 'method' ? 'Methode' : 'Tool'; + + return `ErklĂ€re die Anwendung dieser/dieses ${itemType}. PROBLEM: "${userQuery}" -ELEMENT: ${typ} ${tool.name} (Aufgaben-Eignung ${taskRelevance}%) -BESCHREIBUNG: ${(tool.description || '').replace(/\s+/g, ' ').trim()} +${itemType.toUpperCase()}: ${tool.name} (${taskRelevance}% Eignung) +TYP: ${tool.type} -ANTWORT AUSSCHLIESSLICH ALS JSON (Schema UNVERÄNDERT): +Bereits als Rang ${rank} bewertet. + +ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB DER JSON-STRUKTUR: { - "detailed_explanation": "Beginnen Sie mit einer prĂ€gnanten Kurzfassung (≈300–360 Zeichen) fĂŒr das UI: 'Kurz: 
'. Danach eine verdichtete Hauptpassage (≈80–140 Wörter) mit prĂ€ziser, technisch korrekter ErlĂ€uterung, warum und wie diese ${typ.toLowerCase()} fĂŒr dieses Problem eingesetzt wird.", - "implementation_approach": "Konkrete, reproduzierbare Schrittfolge. Kurze Schritte, kein Ausschweifen.", - "pros": ["Spezifischer Vorteil 1", "Vorteil 2"], - "limitations": ["Konkrete EinschrĂ€nkung 1", "EinschrĂ€nkung 2"], - "alternatives": "Knappe Alternativen, falls dieses Element nicht verfĂŒgbar ist (Methoden und Tools anfĂŒhren)." -} - -WICHTIG: -- Keine erneute Bewertung – nur ErklĂ€rung/Anwendung. -- Behalten Sie die korrekte Bezeichnung (Methode/Tool) bei. -- UI-KĂŒrze: Kurzfassung am Anfang der detailed_explanation.`; + "detailed_explanation": "Warum und wie einsetzen", + "implementation_approach": "Konkrete Schritte", + "pros": ["Vorteil 1", "Vorteil 2"], + "limitations": ["EinschrĂ€nkung 1"], + "alternatives": "Alternative AnsĂ€tze" +}`; }, backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => { - const conceptsList = availableConcepts.slice(0, 15).map((concept: any) => `- ${concept.name}: ${(concept.description || '').replace(/\s+/g, ' ').trim().slice(0, 80)}
`).join('\n'); - return `WĂ€hlen Sie 2–4 forensische Konzepte, die fĂŒr die Anwendung der empfohlenen Elemente (Methoden + Tools) wesentlich sind. + return `WĂ€hle 2-4 relevante Konzepte. ${mode === 'workflow' ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}" -EMPFOHLENE ELEMENTE: ${selectedToolNames.join(', ')} +AUSGEWÄHLTE TOOLS: ${selectedToolNames.join(', ')} -VERFÜGBARE KONZEPTE: -${conceptsList} +VERFÜGBARE KONZEPTE (${availableConcepts.length} KI-kuratiert): +${availableConcepts.map((c: any) => + `- ${c.name}: ${c.description}...` +).join('\n')} -ANTWORT NUR ALS JSON (Schema UNVERÄNDERT): +ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB DER JSON-STRUKTUR: [ { - "conceptName": "Exakter Konzept-Name", - "relevance": "Knappe BegrĂŒndung, warum dieses Konzept fĂŒr das VerstĂ€ndnis der Methodik/Tools kritisch ist (UI-tauglich)." + "conceptName": "Name", + "relevance": "Warum kritisch fĂŒr Methodik" } ]`; }, finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => { - if (isWorkflow) { - return `Erstellen Sie einen knappen, methodisch korrekten WORKFLOW-Fließtext (max. 220 Wörter), der EXPLIZIT eine Mischung aus Methoden und Tools nutzt. + const focus = isWorkflow ? + 'Workflow-Schritte, Best Practices, ObjektivitĂ€t' : + 'Methodische Überlegungen, Validierung, QualitĂ€tssicherung'; -SZENARIO: "${userQuery}" -AUSGEWÄHLTE ELEMENTE: ${selectedToolNames.join(', ') || 'Keine Auswahl'} + return `Erstelle ${isWorkflow ? 'Workflow-Empfehlung' : 'methodische Überlegungen'}. -Vorgaben: klare Phasen (Datensammlung → Auswertung → Analyse → Bericht), BeweisfĂŒhrung/Chain of Custody, Reproduzierbarkeit. Sprache prĂ€zise und UI-tauglich. Keine Listen/Markdown.`; - } +${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}" +AUSGEWÄHLT: ${selectedToolNames.join(', ')}${selectedToolNames.length > 5 ? '...' : ''} - return `Formulieren Sie knappe, kritische ÜBERLEGUNGEN (max. 200 Wörter) zur korrekten Anwendung der empfohlenen Elemente (Methoden + Tools). +Fokus: ${focus} -PROBLEM: "${userQuery}" -EMPFOHLENE ELEMENTE: ${selectedToolNames.join(', ') || 'Keine Auswahl'} - -Fokus: Validierung/QualitĂ€tssicherung, Risiken (FP/FN), Dokumentation/Reporting. Stil deutsch, ohne Listen/Markdown, UI-tauglich.`; +Antwort: Fließtext ohne Listen, max ${isWorkflow ? '100' : '80'} Wörter.`; } } as const; @@ -231,4 +226,4 @@ export function getPrompt(promptKey: keyof typeof AI_PROMPTS, ...args: any[]): s console.error(`[PROMPTS] Error generating prompt ${promptKey}:`, error); return 'Error: Failed to generate prompt'; } -} +} \ No newline at end of file diff --git a/src/utils/aiPipeline.ts b/src/utils/aiPipeline.ts index a15408e..cceec47 100644 --- a/src/utils/aiPipeline.ts +++ b/src/utils/aiPipeline.ts @@ -67,6 +67,8 @@ interface AnalysisContext { auditTrail: AuditEntry[]; embeddingsSimilarities: Map; + aiSelectedTools?: any[]; + aiSelectedConcepts?: any[]; } interface ConfidenceMetrics { @@ -93,6 +95,9 @@ class ImprovedMicroTaskAIPipeline { private embeddingsMinTools: number; private embeddingsMaxReductionRatio: number; + private methodSelectionRatio: number; + private softwareSelectionRatio: number; + private maxContextTokens: number; private maxPromptTokens: number; @@ -127,12 +132,15 @@ class ImprovedMicroTaskAIPipeline { this.embeddingSelectionLimit = parseInt(process.env.AI_EMBEDDING_SELECTION_LIMIT || '30', 10); this.embeddingConceptsLimit = parseInt(process.env.AI_EMBEDDING_CONCEPTS_LIMIT || '15', 10); - this.noEmbeddingsToolLimit = parseInt(process.env.AI_NO_EMBEDDINGS_TOOL_LIMIT || '0', 10); - this.noEmbeddingsConceptLimit = parseInt(process.env.AI_NO_EMBEDDINGS_CONCEPT_LIMIT || '0', 10); + this.noEmbeddingsToolLimit = parseInt(process.env.AI_NO_EMBEDDINGS_TOOL_LIMIT || '25', 10); + this.noEmbeddingsConceptLimit = parseInt(process.env.AI_NO_EMBEDDINGS_CONCEPT_LIMIT || '10', 10); this.embeddingsMinTools = parseInt(process.env.AI_EMBEDDINGS_MIN_TOOLS || '8', 10); this.embeddingsMaxReductionRatio = parseFloat(process.env.AI_EMBEDDINGS_MAX_REDUCTION_RATIO || '0.75'); + this.methodSelectionRatio = parseFloat(process.env.AI_METHOD_SELECTION_RATIO || '0.4'); + this.softwareSelectionRatio = parseFloat(process.env.AI_SOFTWARE_SELECTION_RATIO || '0.5'); + this.maxContextTokens = parseInt(process.env.AI_MAX_CONTEXT_TOKENS || '4000', 10); this.maxPromptTokens = parseInt(process.env.AI_MAX_PROMPT_TOKENS || '1500', 10); @@ -142,6 +150,7 @@ class ImprovedMicroTaskAIPipeline { }; console.log('[AI PIPELINE] Audit trail enabled:', this.auditConfig.enabled); + console.log('[AI PIPELINE] Method/Software balance:', `${(this.methodSelectionRatio * 100).toFixed(0)}%/${(this.softwareSelectionRatio * 100).toFixed(0)}%`); this.confidenceConfig = { semanticWeight: parseFloat(process.env.CONFIDENCE_SEMANTIC_WEIGHT || '0.3'), @@ -163,7 +172,7 @@ class ImprovedMicroTaskAIPipeline { FORENSIC_AUDIT_DETAIL_LEVEL: process.env.FORENSIC_AUDIT_DETAIL_LEVEL, NODE_ENV: process.env.NODE_ENV, allEnvKeys: Object.keys(process.env).filter(k => k.includes('AUDIT')), - dotenvLoaded: !!process.env.PUBLIC_BASE_URL // Proxy for "dotenv worked" + dotenvLoaded: !!process.env.PUBLIC_BASE_URL }); console.log('[AI PIPELINE] Final audit config:', this.auditConfig); @@ -260,11 +269,40 @@ class ImprovedMicroTaskAIPipeline { private safeParseJSON(jsonString: string, fallback: any = null): any { try { - let cleaned = jsonString - .replace(/^```json\s*/i, '') - .replace(/\s*```\s*$/g, '') - .trim(); + // First, try to extract JSON block from markdown or mixed content + let cleaned = jsonString.trim(); + // Look for JSON block patterns (most specific first) + const jsonBlockPatterns = [ + /```json\s*([\s\S]*?)\s*```/i, // ```json ... ``` + /```\s*([\s\S]*?)\s*```/i, // ``` ... ``` + /\{[\s\S]*\}/, // Anything that looks like JSON object + ]; + + let jsonMatch: RegExpMatchArray | null = null; + for (const pattern of jsonBlockPatterns) { + jsonMatch = cleaned.match(pattern); + if (jsonMatch) { + cleaned = jsonMatch[1] || jsonMatch[0]; + console.log('[AI PIPELINE] Extracted JSON block using pattern:', pattern.source); + break; + } + } + + // If no pattern matched, try to find JSON-like content manually + if (!jsonMatch) { + const jsonStart = cleaned.indexOf('{'); + const jsonEnd = cleaned.lastIndexOf('}'); + if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) { + cleaned = cleaned.substring(jsonStart, jsonEnd + 1); + console.log('[AI PIPELINE] Manually extracted JSON from position', jsonStart, 'to', jsonEnd); + } + } + + // Clean up the extracted content + cleaned = cleaned.trim(); + + // Handle truncated JSON by finding the last complete structure if (!cleaned.endsWith('}') && !cleaned.endsWith(']')) { console.warn('[AI PIPELINE] JSON appears truncated, attempting recovery...'); @@ -305,9 +343,10 @@ class ImprovedMicroTaskAIPipeline { } if (lastCompleteStructure) { - console.log('[AI PIPELINE] Attempting to parse recovered JSON structure...'); + console.log('[AI PIPELINE] Using recovered JSON structure'); cleaned = lastCompleteStructure; } else { + // Try to close unclosed braces/brackets if (braceCount > 0) { cleaned += '}'; console.log('[AI PIPELINE] Added closing brace to truncated JSON'); @@ -321,40 +360,82 @@ class ImprovedMicroTaskAIPipeline { const parsed = JSON.parse(cleaned); + // Ensure the structure has the expected fields if (parsed && typeof parsed === 'object') { if (parsed.selectedTools === undefined) parsed.selectedTools = []; if (parsed.selectedConcepts === undefined) parsed.selectedConcepts = []; if (!Array.isArray(parsed.selectedTools)) parsed.selectedTools = []; if (!Array.isArray(parsed.selectedConcepts)) parsed.selectedConcepts = []; + + console.log(`[AI PIPELINE] Successfully parsed JSON: ${parsed.selectedTools.length} tools, ${parsed.selectedConcepts.length} concepts`); } return parsed; + } catch (error) { console.warn('[AI PIPELINE] JSON parsing failed:', error.message); console.warn('[AI PIPELINE] Raw content (first 300 chars):', jsonString.slice(0, 300)); console.warn('[AI PIPELINE] Raw content (last 300 chars):', jsonString.slice(-300)); - if (jsonString.includes('selectedTools')) { - const toolMatches = jsonString.match(/"([^"]+)"/g); - if (toolMatches && toolMatches.length > 0) { - console.log('[AI PIPELINE] Attempting partial recovery from broken JSON...'); - const possibleTools = toolMatches - .map(match => match.replace(/"/g, '')) - .filter(name => name.length > 2 && !['selectedTools', 'selectedConcepts', 'reasoning'].includes(name)) - .slice(0, 15); - - if (possibleTools.length > 0) { - console.log(`[AI PIPELINE] Recovered ${possibleTools.length} possible tool names from broken JSON`); - return { - selectedTools: possibleTools, - selectedConcepts: [], - reasoning: 'Recovered from truncated response' - }; + // Enhanced recovery mechanism that preserves tool/concept distinction + if (jsonString.includes('selectedTools') || jsonString.includes('selectedConcepts')) { + console.log('[AI PIPELINE] Attempting enhanced recovery from broken JSON...'); + + // Try to extract tool and concept arrays separately + const selectedTools: string[] = []; + const selectedConcepts: string[] = []; + + // Look for selectedTools array + const toolsMatch = jsonString.match(/"selectedTools"\s*:\s*\[([\s\S]*?)\]/i); + if (toolsMatch) { + const toolsContent = toolsMatch[1]; + const toolMatches = toolsContent.match(/"([^"]+)"/g); + if (toolMatches) { + selectedTools.push(...toolMatches.map(match => match.replace(/"/g, ''))); } } + + // Look for selectedConcepts array + const conceptsMatch = jsonString.match(/"selectedConcepts"\s*:\s*\[([\s\S]*?)\]/i); + if (conceptsMatch) { + const conceptsContent = conceptsMatch[1]; + const conceptMatches = conceptsContent.match(/"([^"]+)"/g); + if (conceptMatches) { + selectedConcepts.push(...conceptMatches.map(match => match.replace(/"/g, ''))); + } + } + + // If we couldn't parse arrays separately, fall back to generic name extraction + if (selectedTools.length === 0 && selectedConcepts.length === 0) { + const allMatches = jsonString.match(/"([^"]+)"/g); + if (allMatches) { + const possibleNames = allMatches + .map(match => match.replace(/"/g, '')) + .filter(name => + name.length > 2 && + !['selectedTools', 'selectedConcepts', 'reasoning'].includes(name) && + !name.includes(':') && // Avoid JSON keys + !name.match(/^\d+$/) // Avoid pure numbers + ) + .slice(0, 15); + + // Assume all recovered names are tools (since concepts are usually fewer) + selectedTools.push(...possibleNames); + } + } + + if (selectedTools.length > 0 || selectedConcepts.length > 0) { + console.log(`[AI PIPELINE] Recovery successful: ${selectedTools.length} tools, ${selectedConcepts.length} concepts`); + return { + selectedTools, + selectedConcepts, + reasoning: 'Recovered from malformed JSON response' + }; + } } + console.error('[AI PIPELINE] All recovery attempts failed'); return fallback; } } @@ -491,7 +572,29 @@ class ImprovedMicroTaskAIPipeline { ) { const selectionStart = Date.now(); - const toolsWithFullData = candidateTools.map((tool: any) => ({ + const candidateMethods = candidateTools.filter(tool => tool.type === 'method'); + const candidateSoftware = candidateTools.filter(tool => tool.type === 'software'); + + console.log(`[AI PIPELINE] Candidates: ${candidateMethods.length} methods, ${candidateSoftware.length} software, ${candidateConcepts.length} concepts`); + + const methodsWithFullData = candidateMethods.map((tool: any) => ({ + name: tool.name, + type: tool.type, + description: tool.description, + domains: tool.domains, + phases: tool.phases, + 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 softwareWithFullData = candidateSoftware.map((tool: any) => ({ name: tool.name, type: tool.type, description: tool.description, @@ -524,36 +627,72 @@ class ImprovedMicroTaskAIPipeline { let conceptsToSend: any[]; if (selectionMethod === 'embeddings_candidates') { - toolsToSend = toolsWithFullData.slice(0, this.embeddingSelectionLimit); + const totalLimit = this.embeddingSelectionLimit; + + const methodLimit = Math.ceil(totalLimit * this.methodSelectionRatio); + const softwareLimit = Math.floor(totalLimit * this.softwareSelectionRatio); + + const selectedMethods = methodsWithFullData.slice(0, methodLimit); + const selectedSoftware = softwareWithFullData.slice(0, softwareLimit); + + toolsToSend = [...selectedMethods, ...selectedSoftware]; + + const remainingCapacity = totalLimit - toolsToSend.length; + if (remainingCapacity > 0) { + if (methodsWithFullData.length > methodLimit) { + toolsToSend.push(...methodsWithFullData.slice(methodLimit, methodLimit + remainingCapacity)); + } else if (softwareWithFullData.length > softwareLimit) { + toolsToSend.push(...softwareWithFullData.slice(softwareLimit, softwareLimit + remainingCapacity)); + } + } + conceptsToSend = conceptsWithFullData.slice(0, this.embeddingConceptsLimit); - console.log(`[AI PIPELINE] Embeddings enabled: sending top ${toolsToSend.length} similarity-ordered tools`); + console.log(`[AI PIPELINE] Balanced selection: ${selectedMethods.length} methods, ${selectedSoftware.length} software, ${conceptsToSend.length} concepts`); + console.log(`[AI PIPELINE] Method names: ${selectedMethods.slice(0, 5).map(m => m.name).join(', ')}${selectedMethods.length > 5 ? '...' : ''}`); + console.log(`[AI PIPELINE] Software names: ${selectedSoftware.slice(0, 5).map(s => s.name).join(', ')}${selectedSoftware.length > 5 ? '...' : ''}`); + console.log(`[AI PIPELINE] Concept names: ${conceptsToSend.map(c => c.name).join(', ')}`); + } else { - const maxTools = this.noEmbeddingsToolLimit > 0 ? - Math.min(this.noEmbeddingsToolLimit, candidateTools.length) : - candidateTools.length; + const maxTools = this.noEmbeddingsToolLimit > 0 ? this.noEmbeddingsToolLimit : 25; + const maxConcepts = this.noEmbeddingsConceptLimit > 0 ? this.noEmbeddingsConceptLimit : 10; - const maxConcepts = this.noEmbeddingsConceptLimit > 0 ? - Math.min(this.noEmbeddingsConceptLimit, candidateConcepts.length) : - candidateConcepts.length; + const methodLimit = Math.ceil(maxTools * 0.4); + const softwareLimit = Math.floor(maxTools * 0.5); + + const selectedMethods = methodsWithFullData.slice(0, methodLimit); + const selectedSoftware = softwareWithFullData.slice(0, softwareLimit); + + toolsToSend = [...selectedMethods, ...selectedSoftware]; + + const remainingCapacity = maxTools - toolsToSend.length; + if (remainingCapacity > 0) { + if (methodsWithFullData.length > methodLimit) { + toolsToSend.push(...methodsWithFullData.slice(methodLimit, methodLimit + remainingCapacity)); + } else if (softwareWithFullData.length > softwareLimit) { + toolsToSend.push(...softwareWithFullData.slice(softwareLimit, softwareLimit + remainingCapacity)); + } + } - toolsToSend = toolsWithFullData.slice(0, maxTools); conceptsToSend = conceptsWithFullData.slice(0, maxConcepts); - console.log(`[AI PIPELINE] Embeddings disabled: sending ${toolsToSend.length}/${candidateTools.length} tools (limit: ${this.noEmbeddingsToolLimit || 'none'})`); + console.log(`[AI PIPELINE] Balanced selection (no embeddings): ${selectedMethods.length} methods, ${selectedSoftware.length} software, ${conceptsToSend.length} concepts`); } const basePrompt = getPrompt('toolSelection', mode, userQuery, selectionMethod, this.maxSelectedItems); const prompt = `${basePrompt} -VERFÜGBARE TOOLS (mit vollstĂ€ndigen Daten): -${JSON.stringify(toolsToSend, null, 2)} + VERFÜGBARE TOOLS (${toolsToSend.length} Items - Methoden und Software): + ${JSON.stringify(toolsToSend, null, 2)} -VERFÜGBARE KONZEPTE (mit vollstĂ€ndigen Daten): -${JSON.stringify(conceptsToSend, null, 2)}`; + VERFÜGBARE KONZEPTE (${conceptsToSend.length} Items - theoretisches Wissen): + ${JSON.stringify(conceptsToSend, null, 2)} + + WICHTIGER HINWEIS: WĂ€hle sowohl aus TOOLS als auch aus KONZEPTEN aus! Konzepte sind essentiell fĂŒr methodische Fundierung.`; const estimatedTokens = this.estimateTokens(prompt); - console.log(`[AI PIPELINE] Method: ${selectionMethod}, Tools: ${toolsToSend.length}, Estimated tokens: ~${estimatedTokens}`); + console.log(`[AI PIPELINE] Sending to AI: ${toolsToSend.filter(t => t.type === 'method').length} methods, ${toolsToSend.filter(t => t.type === 'software').length} software, ${conceptsToSend.length} concepts`); + console.log(`[AI PIPELINE] Estimated tokens: ~${estimatedTokens}`); if (estimatedTokens > 35000) { console.warn(`[AI PIPELINE] WARNING: Prompt tokens (${estimatedTokens}) may exceed model limits`); @@ -569,43 +708,61 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; if (this.auditConfig.enabled) { this.addAuditEntry(context, 'selection', 'ai-tool-selection-failed', - { candidateCount: candidateTools.length, mode, prompt: prompt.slice(0, 200) }, + { candidateCount: candidateTools.length, candidateConceptsCount: candidateConcepts.length, mode, prompt: prompt.slice(0, 200) }, { error: 'Invalid JSON structure', response: response.slice(0, 200) }, 10, selectionStart, - { aiModel: this.config.model, selectionMethod, tokensSent: estimatedTokens, toolsSent: toolsToSend.length } + { aiModel: this.config.model, selectionMethod, tokensSent: estimatedTokens, toolsSent: toolsToSend.length, conceptsSent: conceptsToSend.length } ); } - throw new Error('AI selection failed to return valid tool selection'); + throw new Error('AI selection failed to return valid tool and concept selection'); } const totalSelected = result.selectedTools.length + result.selectedConcepts.length; if (totalSelected === 0) { - console.error('[AI PIPELINE] AI selection returned no tools'); + console.error('[AI PIPELINE] AI selection returned no tools or concepts'); throw new Error('AI selection returned empty selection'); } - console.log(`[AI PIPELINE] AI selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts from ${toolsToSend.length} candidates`); - - const selectedTools = candidateTools.filter(tool => result.selectedTools.includes(tool.name)); - const selectedConcepts = candidateConcepts.filter(concept => result.selectedConcepts.includes(concept.name)); + // Create lookup maps for efficient filtering + const toolsMap = new Map(candidateTools.map(tool => [tool.name, tool])); + const conceptsMap = new Map(candidateConcepts.map(concept => [concept.name, concept])); + const selectedTools = result.selectedTools + .map(name => toolsMap.get(name)) + .filter((tool): tool is any => tool !== undefined); + + const selectedConcepts = result.selectedConcepts + .map(name => conceptsMap.get(name)) + .filter((concept): concept is any => concept !== undefined); + + const selectedMethods = selectedTools.filter(t => t.type === 'method'); + const selectedSoftware = selectedTools.filter(t => t.type === 'software'); + + console.log(`[AI PIPELINE] AI selected: ${selectedMethods.length} methods, ${selectedSoftware.length} software, ${selectedConcepts.length} concepts`); + console.log(`[AI PIPELINE] Selection balance: ${((selectedMethods.length / (selectedTools.length || 1)) * 100).toFixed(0)}% methods`); + console.log(`[AI PIPELINE] Selected tool names: ${selectedTools.map(t => t.name).join(', ')}`); + console.log(`[AI PIPELINE] Selected concept names: ${selectedConcepts.map(c => c.name).join(', ')}`); + if (this.auditConfig.enabled) { - const confidence = this.calculateSelectionConfidence(result, candidateTools.length); + const confidence = this.calculateSelectionConfidence(result, candidateTools.length + candidateConcepts.length); this.addAuditEntry(context, 'selection', 'ai-tool-selection', - { candidateCount: candidateTools.length, mode, promptLength: prompt.length }, + { candidateCount: candidateTools.length, candidateConceptsCount: candidateConcepts.length, mode, promptLength: prompt.length }, { - selectedToolCount: result.selectedTools.length, - selectedConceptCount: result.selectedConcepts.length, + selectedMethodCount: selectedMethods.length, + selectedSoftwareCount: selectedSoftware.length, + selectedConceptCount: selectedConcepts.length, reasoning: result.reasoning?.slice(0, 200) + '...', finalToolNames: selectedTools.map(t => t.name), - selectionEfficiency: `${toolsToSend.length} → ${result.selectedTools.length}` + finalConceptNames: selectedConcepts.map(c => c.name), + methodBalance: `${((selectedMethods.length / (selectedTools.length || 1)) * 100).toFixed(0)}%`, + conceptsSelected: selectedConcepts.length > 0 }, confidence, selectionStart, - { aiModel: this.config.model, selectionMethod, promptTokens: estimatedTokens, toolsSent: toolsToSend.length } + { aiModel: this.config.model, selectionMethod, promptTokens: estimatedTokens, toolsSent: toolsToSend.length, conceptsSent: conceptsToSend.length } ); } @@ -619,7 +776,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; if (this.auditConfig.enabled) { this.addAuditEntry(context, 'selection', 'ai-tool-selection-error', - { candidateCount: candidateTools.length, mode }, + { candidateCount: candidateTools.length, candidateConceptsCount: candidateConcepts.length, mode }, { error: error.message }, 5, selectionStart, @@ -851,7 +1008,11 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; tool.phases && tool.phases.includes(phase.id) ); + console.log(`[AI PIPELINE] Phase ${phase.id} (${phase.name}): Found ${phaseTools.length} matching tools`); + console.log(`[AI PIPELINE] Available tools for phase: ${phaseTools.map(t => `${t.name}(${t.type})`).join(', ')}`); + if (phaseTools.length === 0) { + console.log(`[AI PIPELINE] No tools available for phase ${phase.id}, skipping`); return { taskType: 'tool-selection', content: JSON.stringify([]), @@ -860,17 +1021,38 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; }; } + const phaseMethods = phaseTools.filter(t => t.type === 'method'); + const phaseSoftware = phaseTools.filter(t => t.type === 'software'); + + console.log(`[AI PIPELINE] Phase ${phase.id}: ${phaseMethods.length} methods, ${phaseSoftware.length} software`); + const prompt = getPrompt('phaseToolSelection', context.userQuery, phase, phaseTools); const result = await this.callMicroTaskAI(prompt, context, 1000); if (result.success) { + console.log(`[AI PIPELINE] Phase ${phase.id} AI response length: ${result.content.length}`); + console.log(`[AI PIPELINE] Phase ${phase.id} AI response preview: ${result.content.slice(0, 200)}...`); + const selections = this.safeParseJSON(result.content, []); if (Array.isArray(selections)) { - const validSelections = selections.filter((sel: any) => - sel.toolName && phaseTools.some((tool: any) => tool.name === sel.toolName) - ); + console.log(`[AI PIPELINE] Phase ${phase.id}: Parsed ${selections.length} selections from AI`); + + const validSelections = selections.filter((sel: any) => { + if (!sel.toolName) return false; + + let matchingTool = phaseTools.find((tool: any) => tool.name === sel.toolName); + + const isValid = !!matchingTool; + if (!isValid) { + console.warn(`[AI PIPELINE] Phase ${phase.id}: Invalid selection: ${JSON.stringify(sel)}`); + console.warn(`[AI PIPELINE] Phase ${phase.id}: Available tool names: ${phaseTools.map(t => t.name).join(', ')}`); + } + return isValid; + }); + + console.log(`[AI PIPELINE] Phase ${phase.id}: ${validSelections.length} valid selections after filtering`); validSelections.forEach((sel: any) => { const tool = phaseTools.find((t: any) => t.name === sel.toolName); @@ -880,25 +1062,35 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; const priority = this.derivePriorityFromScore(taskRelevance); + console.log(`[AI PIPELINE] Phase ${phase.id}: Adding ${tool.name} (${tool.type}) with priority ${priority}, relevance ${taskRelevance}%`); + this.addToolToSelection(context, tool, phase.id, priority, sel.justification, taskRelevance, sel.limitations); } }); this.addAuditEntry(context, 'micro-task', 'phase-tool-selection', - { phase: phase.id, availableTools: phaseTools.length }, + { phase: phase.id, availableTools: phaseTools.length, availableMethods: phaseMethods.length, availableSoftware: phaseSoftware.length }, { validSelections: validSelections.length, selectedTools: validSelections.map(s => ({ name: s.toolName, taskRelevance: s.taskRelevance, derivedPriority: this.derivePriorityFromScore(s.taskRelevance) - })) + })), + methodsSelected: validSelections.filter(s => { + const tool = phaseTools.find(t => t.name === s.toolName); + return tool && tool.type === 'method'; + }).length }, validSelections.length > 0 ? 75 : 30, Date.now() - result.processingTimeMs, { phaseName: phase.name, comparativeEvaluation: true, priorityDerived: true } ); + } else { + console.error(`[AI PIPELINE] Phase ${phase.id}: Failed to parse selections as array:`, selections); } + } else { + console.error(`[AI PIPELINE] Phase ${phase.id}: AI call failed:`, result.error); } return result; @@ -1057,6 +1249,32 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; return 'low'; } + private async performAISelection( + filteredData: any, + userQuery: string, + mode: string, + context: AnalysisContext + ): Promise<{ tools: any[], concepts: any[] }> { + const selectionStart = Date.now(); + + // Call the existing aiSelectionWithFullData + const result = await this.aiSelectionWithFullData( + userQuery, + filteredData.tools, + filteredData.concepts, + mode, + embeddingsService.isEnabled() ? 'embeddings_candidates' : 'full_dataset', + context + ); + + console.log(`[AI PIPELINE] AI selection complete: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`); + + return { + tools: result.selectedTools, + concepts: result.selectedConcepts + }; + } + async processQuery(userQuery: string, mode: string): Promise { const startTime = Date.now(); let completeTasks = 0; @@ -1078,15 +1296,34 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; currentContextLength: 0, seenToolNames: new Set(), auditTrail: [], - embeddingsSimilarities: new Map() + embeddingsSimilarities: new Map(), + // Add this new property to store AI selections + aiSelectedTools: [], + aiSelectedConcepts: [] }; + // Get embedding-filtered candidates const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode, context); - context.filteredData = filteredData; + + // IMPORTANT: Now do the AI selection from those candidates + const aiSelection = await this.performAISelection(filteredData, userQuery, mode, context); + + // Store the AI's selections in context + context.aiSelectedTools = aiSelection.tools; + context.aiSelectedConcepts = aiSelection.concepts; + + // Update filteredData to only include what AI selected + context.filteredData = { + tools: aiSelection.tools, + concepts: aiSelection.concepts, + domains: filteredData.domains, + phases: filteredData.phases, + 'domain-agnostic-software': filteredData['domain-agnostic-software'] + }; this.mergeTemporaryAuditEntries(context); - console.log(`[AI PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`); + console.log(`[AI PIPELINE] Starting micro-tasks with ${context.filteredData.tools.length} AI-selected tools`); this.addAuditEntry(context, 'initialization', 'pipeline-start', { userQuery, mode, toolsDataLoaded: !!toolsData }, @@ -1110,6 +1347,27 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; if (mode === 'workflow') { const phases = toolsData.phases || []; + + console.log(`[AI PIPELINE] Debug: Starting phase-specific selection with ${context.filteredData.tools.length} AI-selected tools`); + console.log(`[AI PIPELINE] Debug: Available phases: ${phases.map(p => p.id).join(', ')}`); + + context.filteredData.tools.forEach(tool => { + console.log(`[AI PIPELINE] Debug: ${tool.name} (${tool.type}) - phases: ${tool.phases?.join(', ') || 'NO PHASES'}`); + }); + + phases.forEach(phase => { + const matchingTools = context.filteredData.tools.filter(tool => + tool.phases && tool.phases.includes(phase.id) + ); + const matchingMethods = matchingTools.filter(t => t.type === 'method'); + const matchingSoftware = matchingTools.filter(t => t.type === 'software'); + + console.log(`[AI PIPELINE] Debug: Phase ${phase.id} has ${matchingTools.length} matching tools (${matchingMethods.length} methods, ${matchingSoftware.length} software)`); + if (matchingTools.length > 0) { + console.log(`[AI PIPELINE] Debug: Phase ${phase.id} tools: ${matchingTools.map(t => `${t.name}(${t.type})`).join(', ')}`); + } + }); + for (const phase of phases) { const toolSelectionResult = await this.selectToolsForPhase(context, phase); if (toolSelectionResult.success) completeTasks++; else failedTasks++; @@ -1176,6 +1434,24 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any { const isWorkflow = mode === 'workflow'; + console.log(`[AI PIPELINE] Building recommendation for ${mode} mode`); + console.log(`[AI PIPELINE] Selected tools count: ${context.selectedTools?.length || 0}`); + + if (context.selectedTools && context.selectedTools.length > 0) { + const methods = context.selectedTools.filter(st => st.tool.type === 'method'); + const software = context.selectedTools.filter(st => st.tool.type === 'software'); + + console.log(`[AI PIPELINE] Final selection breakdown: ${methods.length} methods, ${software.length} software`); + console.log(`[AI PIPELINE] Method names: ${methods.map(m => m.tool.name).join(', ')}`); + console.log(`[AI PIPELINE] Software names: ${software.map(s => s.tool.name).join(', ')}`); + + context.selectedTools.forEach((st, index) => { + console.log(`[AI PIPELINE] Selected tool ${index + 1}: ${st.tool.name} (${st.tool.type}) - Phase: ${st.phase}, Priority: ${st.priority}`); + }); + } else { + console.warn(`[AI PIPELINE] WARNING: No tools in selectedTools array!`); + } + const base = { [isWorkflow ? 'scenario_analysis' : 'problem_analysis']: isWorkflow ? context.scenarioAnalysis : context.problemAnalysis, @@ -1201,7 +1477,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; ); this.addAuditEntry(context, 'validation', 'confidence-scoring', - { toolName: st.tool.name, phase: st.phase }, + { toolName: st.tool.name, toolType: st.tool.type, phase: st.phase }, { overall: confidence.overall, components: { @@ -1216,6 +1492,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; return { name: st.tool.name, + type: st.tool.type, phase: st.phase, priority: st.priority, justification: st.justification || `Empfohlen fĂŒr ${st.phase}`, @@ -1225,6 +1502,11 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; }; }) || []; + console.log(`[AI PIPELINE] Final workflow recommendations: ${recommendedToolsWithConfidence.length} tools`); + const finalMethods = recommendedToolsWithConfidence.filter(r => r.type === 'method'); + const finalSoftware = recommendedToolsWithConfidence.filter(r => r.type === 'software'); + console.log(`[AI PIPELINE] Final breakdown: ${finalMethods.length} methods, ${finalSoftware.length} software`); + return { ...base, recommended_tools: recommendedToolsWithConfidence, @@ -1241,7 +1523,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; ); this.addAuditEntry(context, 'validation', 'confidence-scoring', - { toolName: st.tool.name, rank: st.tool.evaluation?.rank || 1 }, + { toolName: st.tool.name, toolType: st.tool.type, rank: st.tool.evaluation?.rank || 1 }, { overall: confidence.overall, suitabilityAlignment: st.priority === 'high' && confidence.overall >= this.confidenceConfig.highThreshold, @@ -1254,6 +1536,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; 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 || '', @@ -1267,6 +1550,8 @@ ${JSON.stringify(conceptsToSend, null, 2)}`; }; }) || []; + console.log(`[AI PIPELINE] Final tool recommendations: ${recommendedToolsWithConfidence.length} tools`); + return { ...base, recommended_tools: recommendedToolsWithConfidence,