seo-generator-server/tests/validators/AIContentValidator.js

345 lines
10 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 multi-critères
*/
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 du prompt de validation intelligent
*/
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;
}
/**
* Parse de la réponse IA avec validation
*/
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 des scores agrégés
*/
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
};
}
/**
* Validation de fallback en cas d'erreur
*/
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 avec critères par défaut
*/
static async quickValidate(content, context = {}) {
return this.validateContent(
content,
[this.CRITERIA.QUALITY, this.CRITERIA.ANTI_DETECTION],
context
);
}
/**
* Validation complète tous critères
*/
static async fullValidate(content, context = {}) {
return this.validateContent(
content,
Object.values(this.CRITERIA),
context
);
}
/**
* Comparaison entre deux contenus
*/
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 };