- Add intermediate saves (v1.0-v1.4) to Generated_Articles_Versioned - Fix compiled_text pipeline (generatedTexts object structure) - Add /api/workflow-modulaire endpoint with version tracking - Create test-modulaire.html interface with real-time logs - Support parent-child linking via Parent_Article_ID
435 lines
15 KiB
JavaScript
435 lines
15 KiB
JavaScript
// ========================================
|
|
// VALIDATEUR IA MULTI-CRITÈRES
|
|
// Validation intelligente du contenu généré avec scores objectifs
|
|
// ========================================
|
|
|
|
// Import correct du LLMManager
|
|
let LLMManager;
|
|
try {
|
|
const llmModule = require('../../lib/LLMManager');
|
|
|
|
// Essai direct avec callLLM
|
|
if (llmModule && typeof llmModule.callLLM === 'function') {
|
|
LLMManager = llmModule;
|
|
}
|
|
// Essai avec propriété
|
|
else if (llmModule && llmModule.LLMManager && typeof llmModule.LLMManager.callLLM === 'function') {
|
|
LLMManager = llmModule.LLMManager;
|
|
}
|
|
// Import par défaut module.exports
|
|
else if (llmModule && llmModule.default && typeof llmModule.default.callLLM === 'function') {
|
|
LLMManager = llmModule.default;
|
|
}
|
|
} catch (error) {
|
|
console.warn('LLMManager non disponible:', error.message);
|
|
}
|
|
const { logSh } = require('../../lib/ErrorReporting');
|
|
|
|
/**
|
|
* Validateur de contenu utilisant l'IA pour évaluer la qualité
|
|
* Scores objectifs sur 4 critères principaux
|
|
*/
|
|
class AIContentValidator {
|
|
|
|
static CRITERIA = {
|
|
QUALITY: 'quality', // Cohérence, fluidité, pertinence
|
|
ANTI_DETECTION: 'antiDetection', // Variabilité, naturalité
|
|
PERSONALITY: 'personality', // Respect du profil sélectionné
|
|
TECHNICAL: 'technical' // SEO, structure, mots-clés
|
|
};
|
|
|
|
static THRESHOLDS = {
|
|
EXCELLENT: 90,
|
|
GOOD: 75,
|
|
ACCEPTABLE: 60,
|
|
POOR: 40
|
|
};
|
|
|
|
/**
|
|
* 🧪 VALIDATION PRINCIPALE - CŒUR DU VALIDATEUR IA
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Appel LLM OpenAI avec prompt structuré pour évaluation objective
|
|
* ✅ Parse JSON de la réponse IA avec validation des scores (0-100)
|
|
* ✅ Calcul score agrégé pondéré selon importance des critères
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Construction prompt intelligent selon critères demandés
|
|
* - Appel OpenAI avec temperature=0.1 (cohérence évaluations)
|
|
* - Parsing JSON robuste avec fallback sur erreurs
|
|
* - Calcul moyenne pondérée : Quality(30%) + Anti-detection(30%) + Personality(20%) + Technical(20%)
|
|
*/
|
|
static async validateContent(content, criteria = [], context = {}) {
|
|
try {
|
|
logSh(`🧪 Validation IA démarrée - Critères: ${criteria.join(', ')}`, 'DEBUG');
|
|
|
|
// Préparation du prompt de validation
|
|
const prompt = this.buildValidationPrompt(content, criteria, context);
|
|
|
|
// Vérification et appel LLM pour validation
|
|
if (!LLMManager || typeof LLMManager.callLLM !== 'function') {
|
|
throw new Error('LLMManager not available or callLLM method missing');
|
|
}
|
|
|
|
const response = await LLMManager.callLLM(
|
|
'openai',
|
|
prompt,
|
|
{
|
|
temperature: 0.1, // Faible pour cohérence des évaluations
|
|
max_tokens: 1500
|
|
}
|
|
);
|
|
|
|
// Parse de la réponse
|
|
const validationResult = this.parseValidationResponse(response, criteria);
|
|
|
|
// Calcul scores agrégés
|
|
const aggregated = this.calculateAggregatedScores(validationResult);
|
|
|
|
logSh(`✅ Validation IA terminée - Score global: ${aggregated.overall}/100`, 'INFO');
|
|
|
|
return {
|
|
...validationResult,
|
|
...aggregated,
|
|
timestamp: new Date().toISOString(),
|
|
criteria: criteria,
|
|
context: context
|
|
};
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur validation IA: ${error.message}`, 'ERROR');
|
|
|
|
// Fallback avec scores neutres
|
|
return this.getFallbackValidation(criteria, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🔧 CONSTRUCTION PROMPT - GÉNÉRATEUR DE PROMPTS ADAPTATIFS
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Construction dynamique prompt selon critères sélectionnés
|
|
* ✅ Intégration contexte (personnalité, mots-clés, longueur attendue)
|
|
* ✅ Format JSON strict demandé à l'IA pour parsing automatique
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Template de prompt avec placeholders dynamiques
|
|
* - Ajout conditionnel sections selon critères (Quality, Anti-detection, etc.)
|
|
* - Troncature contenu à 2000 chars pour éviter timeout LLM
|
|
* - Format réponse JSON strict avec scores 0-100 + feedback + confidence
|
|
*/
|
|
static buildValidationPrompt(content, criteria, context) {
|
|
const basePrompt = `
|
|
VALIDATION CONTENU IA - ANALYSE MULTI-CRITÈRES OBJECTIVE
|
|
|
|
Tu es un expert en évaluation de contenu généré par IA. Évalue ce contenu selon des critères objectifs et mesurables.
|
|
|
|
CONTENU À ANALYSER :
|
|
---
|
|
${content.substring(0, 2000)}${content.length > 2000 ? '...[TRONQUÉ]' : ''}
|
|
---
|
|
|
|
CONTEXTE :
|
|
${context.personality ? `- Personnalité cible : ${context.personality.nom || context.personality} (${context.personality.style || 'style non défini'})` : ''}
|
|
${context.keywords ? `- Mots-clés attendus : ${Array.isArray(context.keywords) ? context.keywords.join(', ') : context.keywords}` : ''}
|
|
${context.expectedLength ? `- Longueur attendue : ${context.expectedLength} caractères` : ''}
|
|
${context.targetAudience ? `- Audience cible : ${context.targetAudience}` : ''}
|
|
|
|
CRITÈRES D'ÉVALUATION (0-100) :`;
|
|
|
|
let criteriaDetails = '';
|
|
|
|
if (criteria.includes(this.CRITERIA.QUALITY)) {
|
|
criteriaDetails += `
|
|
|
|
1. QUALITÉ (0-100) :
|
|
- Cohérence du propos et logique argumentaire
|
|
- Fluidité de lecture et transitions naturelles
|
|
- Pertinence par rapport au sujet
|
|
- Richesse du vocabulaire et variété syntaxique`;
|
|
}
|
|
|
|
if (criteria.includes(this.CRITERIA.ANTI_DETECTION)) {
|
|
criteriaDetails += `
|
|
|
|
2. ANTI-DÉTECTION (0-100) :
|
|
- Absence de formulations typiquement IA ("comprehensive", "robust", etc.)
|
|
- Variabilité dans les structures de phrases
|
|
- Naturalité du ton et spontanéité
|
|
- Présence d'imperfections humaines subtiles`;
|
|
}
|
|
|
|
if (criteria.includes(this.CRITERIA.PERSONALITY)) {
|
|
criteriaDetails += `
|
|
|
|
3. PERSONNALITÉ (0-100) :
|
|
- Respect du style de la personnalité cible
|
|
- Cohérence avec le ton attendu
|
|
- Vocabulaire approprié au profil
|
|
- Authenticité de la voix narrative`;
|
|
}
|
|
|
|
if (criteria.includes(this.CRITERIA.TECHNICAL)) {
|
|
criteriaDetails += `
|
|
|
|
4. TECHNIQUE/SEO (0-100) :
|
|
- Intégration naturelle des mots-clés
|
|
- Structure optimisée pour le référencement
|
|
- Respect des bonnes pratiques de rédaction web
|
|
- Équilibre lisibilité/optimisation`;
|
|
}
|
|
|
|
const responseFormat = `
|
|
|
|
RÉPONSE REQUISE - FORMAT JSON STRICT :
|
|
{
|
|
${criteria.map(c => `"${c}": 85`).join(',\n ')},
|
|
"feedback": "Analyse détaillée : points forts, points faibles, recommandations d'amélioration (max 200 mots)",
|
|
"confidence": 0.9
|
|
}
|
|
|
|
IMPORTANT : Réponds UNIQUEMENT avec du JSON valide, sans texte avant ou après.`;
|
|
|
|
return basePrompt + criteriaDetails + responseFormat;
|
|
}
|
|
|
|
/**
|
|
* 🔍 PARSING RÉPONSE IA - EXTRACTEUR JSON ROBUSTE
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Extraction JSON depuis réponse LLM (même avec texte parasite)
|
|
* ✅ Validation scores numériques dans range 0-100
|
|
* ✅ Fallback automatique avec scores neutres sur erreurs parsing
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Regex extraction JSON : /{[\s\S]*}/ pour isoler contenu JSON
|
|
* - Validation type number + range [0,100] pour chaque score
|
|
* - Arrondi Math.round() des scores décimaux
|
|
* - Clamp confidence dans [0,1] avec Math.min/Math.max
|
|
* - Score fallback = 50 (neutre) sur erreurs
|
|
*/
|
|
static parseValidationResponse(response, criteria) {
|
|
try {
|
|
// Nettoyage de la réponse
|
|
let jsonStr = response.trim();
|
|
|
|
// Extraction du JSON si emballé dans du texte
|
|
const jsonMatch = jsonStr.match(/\{[\s\S]*\}/);
|
|
if (jsonMatch) {
|
|
jsonStr = jsonMatch[0];
|
|
}
|
|
|
|
const parsed = JSON.parse(jsonStr);
|
|
|
|
// Validation des scores
|
|
const validated = {};
|
|
criteria.forEach(criterion => {
|
|
const score = parsed[criterion];
|
|
if (typeof score === 'number' && score >= 0 && score <= 100) {
|
|
validated[criterion] = Math.round(score);
|
|
} else {
|
|
logSh(`⚠️ Score invalide pour ${criterion}: ${score}`, 'WARNING');
|
|
validated[criterion] = 50; // Score neutre par défaut
|
|
}
|
|
});
|
|
|
|
return {
|
|
scores: validated,
|
|
feedback: parsed.feedback || 'Aucun feedback fourni',
|
|
confidence: Math.min(Math.max(parsed.confidence || 0.5, 0), 1),
|
|
rawResponse: response
|
|
};
|
|
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur parsing réponse validation: ${error.message}`, 'WARNING');
|
|
|
|
// Scores de fallback
|
|
const fallbackScores = {};
|
|
criteria.forEach(c => fallbackScores[c] = 50);
|
|
|
|
return {
|
|
scores: fallbackScores,
|
|
feedback: `Erreur parsing: ${error.message}`,
|
|
confidence: 0.1,
|
|
rawResponse: response
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 📊 CALCUL SCORES AGRÉGÉS - ALGORITHME DE PONDÉRATION
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Moyenne pondérée selon importance relative des critères
|
|
* ✅ Classification automatique en niveaux (EXCELLENT/GOOD/ACCEPTABLE/POOR)
|
|
* ✅ Calcul breakdown détaillé par critère
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Pondération : Quality(30%) + Anti-detection(30%) + Personality(20%) + Technical(20%)
|
|
* - Formula : weightedSum = Σ(score[i] * weight[i]) / totalWeight
|
|
* - Classification par seuils : ≥90=EXCELLENT, ≥75=GOOD, ≥60=ACCEPTABLE, <60=POOR
|
|
* - Math.round() pour score entier final
|
|
*/
|
|
static calculateAggregatedScores(validationResult) {
|
|
const scores = validationResult.scores;
|
|
const scoreValues = Object.values(scores);
|
|
|
|
if (scoreValues.length === 0) {
|
|
return { overall: 0, level: 'UNKNOWN' };
|
|
}
|
|
|
|
// Moyenne pondérée (qualité et anti-détection plus importants)
|
|
const weights = {
|
|
[this.CRITERIA.QUALITY]: 0.3,
|
|
[this.CRITERIA.ANTI_DETECTION]: 0.3,
|
|
[this.CRITERIA.PERSONALITY]: 0.2,
|
|
[this.CRITERIA.TECHNICAL]: 0.2
|
|
};
|
|
|
|
let weightedSum = 0;
|
|
let totalWeight = 0;
|
|
|
|
Object.keys(scores).forEach(criterion => {
|
|
const weight = weights[criterion] || 0.25;
|
|
weightedSum += scores[criterion] * weight;
|
|
totalWeight += weight;
|
|
});
|
|
|
|
const overall = Math.round(weightedSum / totalWeight);
|
|
|
|
// Détermination du niveau
|
|
let level;
|
|
if (overall >= this.THRESHOLDS.EXCELLENT) level = 'EXCELLENT';
|
|
else if (overall >= this.THRESHOLDS.GOOD) level = 'GOOD';
|
|
else if (overall >= this.THRESHOLDS.ACCEPTABLE) level = 'ACCEPTABLE';
|
|
else level = 'POOR';
|
|
|
|
return {
|
|
overall,
|
|
level,
|
|
breakdown: scores,
|
|
confidence: validationResult.confidence
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 🚨 FALLBACK VALIDATION - GESTION ROBUSTE DES ERREURS
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Génération scores neutres quand LLM indisponible/échec
|
|
* ✅ Structure réponse identique pour compatibilité code appelant
|
|
* ✅ Logging erreur pour debug + confidence=0 pour signaler échec
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Attribution score=50 (neutre) pour tous les critères demandés
|
|
* - overall=50, level='UNKNOWN' pour indiquer validation échouée
|
|
* - confidence=0.0 pour signaler résultat non fiable
|
|
* - Timestamp ISO pour traçabilité
|
|
*/
|
|
static getFallbackValidation(criteria, error) {
|
|
const fallbackScores = {};
|
|
criteria.forEach(c => fallbackScores[c] = 50);
|
|
|
|
return {
|
|
scores: fallbackScores,
|
|
overall: 50,
|
|
level: 'UNKNOWN',
|
|
breakdown: fallbackScores,
|
|
feedback: `Validation échouée: ${error.message}`,
|
|
confidence: 0.0,
|
|
error: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
criteria: criteria
|
|
};
|
|
}
|
|
|
|
/**
|
|
* ⚡ VALIDATION RAPIDE - PRESET OPTIMISÉ PERFORMANCE
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Évaluation accélérée sur 2 critères essentiels seulement
|
|
* ✅ Quality + Anti-detection (critères les plus discriminants)
|
|
* ✅ Délai <3 secondes vs ~8 secondes pour validation complète
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Appel validateContent() avec criteria=[QUALITY, ANTI_DETECTION]
|
|
* - Prompt réduit (moins de sections = moins de tokens)
|
|
* - Même pondération mais seulement 2 critères
|
|
* - Idéal pour validation temps réel ou batch processing
|
|
*/
|
|
static async quickValidate(content, context = {}) {
|
|
return this.validateContent(
|
|
content,
|
|
[this.CRITERIA.QUALITY, this.CRITERIA.ANTI_DETECTION],
|
|
context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 🔬 VALIDATION COMPLÈTE - ANALYSE EXHAUSTIVE 4 CRITÈRES
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Évaluation approfondie sur TOUS les critères disponibles
|
|
* ✅ Quality + Anti-detection + Personality + Technical/SEO
|
|
* ✅ Maximum de précision pour validation finale avant publication
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Appel validateContent() avec Object.values(CRITERIA) = tous critères
|
|
* - Prompt complet avec 4 sections détaillées
|
|
* - Pondération complète 30%+30%+20%+20%
|
|
* - Délai ~8-12 secondes pour analyse exhaustive
|
|
*/
|
|
static async fullValidate(content, context = {}) {
|
|
return this.validateContent(
|
|
content,
|
|
Object.values(this.CRITERIA),
|
|
context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 🔄 COMPARAISON CONTENUS - ALGORITHME DE DIFFÉRENTIEL
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Validation parallèle des 2 contenus (Promise.all pour performance)
|
|
* ✅ Calcul différences score par score pour chaque critère
|
|
* ✅ Détermination gagnant automatique + score d'amélioration
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Double appel validateContent() en parallèle (versionA, versionB)
|
|
* - Delta calculation : diff[criterion] = scoreB - scoreA pour chaque critère
|
|
* - overallDiff = validation2.overall - validation1.overall
|
|
* - Winner logic : diff>0='B', diff<0='A', diff=0='TIE'
|
|
* - Structure comparison complète pour analyse détaillée
|
|
*/
|
|
static async compareContent(content1, content2, criteria = [], context = {}) {
|
|
logSh('🔄 Comparaison de deux contenus', 'DEBUG');
|
|
|
|
const [validation1, validation2] = await Promise.all([
|
|
this.validateContent(content1, criteria, { ...context, label: 'Version A' }),
|
|
this.validateContent(content2, criteria, { ...context, label: 'Version B' })
|
|
]);
|
|
|
|
// Calcul des différences
|
|
const comparison = {
|
|
versionA: validation1,
|
|
versionB: validation2,
|
|
differences: {},
|
|
winner: null,
|
|
improvement: 0
|
|
};
|
|
|
|
criteria.forEach(criterion => {
|
|
const diff = validation2.scores[criterion] - validation1.scores[criterion];
|
|
comparison.differences[criterion] = diff;
|
|
});
|
|
|
|
const overallDiff = validation2.overall - validation1.overall;
|
|
comparison.improvement = overallDiff;
|
|
comparison.winner = overallDiff > 0 ? 'B' : (overallDiff < 0 ? 'A' : 'TIE');
|
|
|
|
return comparison;
|
|
}
|
|
}
|
|
|
|
module.exports = { AIContentValidator }; |