pipeline overhaul
This commit is contained in:
@@ -67,6 +67,8 @@ interface AnalysisContext {
|
||||
auditTrail: AuditEntry[];
|
||||
|
||||
embeddingsSimilarities: Map<string, number>;
|
||||
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<string, any>(candidateTools.map(tool => [tool.name, tool]));
|
||||
const conceptsMap = new Map<string, any>(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<AnalysisResult> {
|
||||
const startTime = Date.now();
|
||||
let completeTasks = 0;
|
||||
@@ -1078,15 +1296,34 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
|
||||
currentContextLength: 0,
|
||||
seenToolNames: new Set<string>(),
|
||||
auditTrail: [],
|
||||
embeddingsSimilarities: new Map<string, number>()
|
||||
embeddingsSimilarities: new Map<string, number>(),
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user