345 lines
10 KiB
JavaScript
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 }; |