seo-generator-server/lib/adversarial-generation/ComparisonFramework.js

466 lines
15 KiB
JavaScript

// ========================================
// FRAMEWORK DE COMPARAISON ADVERSARIAL
// Responsabilité: Comparer pipelines normales vs adversariales
// Utilisation: A/B testing et validation efficacité anti-détection
// ========================================
const { logSh } = require('../ErrorReporting');
const { tracer } = require('../trace');
// Pipelines à comparer
const { generateWithContext } = require('../ContentGeneration'); // Pipeline normale
const { generateWithAdversarialContext, compareAdversarialStrategies } = require('./ContentGenerationAdversarial'); // Pipeline adversariale
/**
* MAIN ENTRY POINT - COMPARAISON A/B PIPELINE
* Compare pipeline normale vs adversariale sur même input
*/
async function compareNormalVsAdversarial(input, options = {}) {
return await tracer.run('ComparisonFramework.compareNormalVsAdversarial()', async () => {
const {
hierarchy,
csvData,
adversarialConfig = {},
runBothPipelines = true,
analyzeContent = true
} = input;
const {
detectorTarget = 'general',
intensity = 1.0,
iterations = 1
} = options;
await tracer.annotate({
comparisonType: 'normal_vs_adversarial',
detectorTarget,
intensity,
iterations,
elementsCount: Object.keys(hierarchy).length
});
const startTime = Date.now();
logSh(`🆚 COMPARAISON A/B: Pipeline normale vs adversariale`, 'INFO');
logSh(` 🎯 Détecteur cible: ${detectorTarget} | Intensité: ${intensity} | Itérations: ${iterations}`, 'INFO');
const results = {
normal: null,
adversarial: null,
comparison: null,
iterations: []
};
try {
for (let i = 0; i < iterations; i++) {
logSh(`🔄 Itération ${i + 1}/${iterations}`, 'INFO');
const iterationResults = {
iteration: i + 1,
normal: null,
adversarial: null,
metrics: {}
};
// ========================================
// PIPELINE NORMALE
// ========================================
if (runBothPipelines) {
logSh(` 📊 Génération pipeline normale...`, 'DEBUG');
const normalStartTime = Date.now();
try {
const normalResult = await generateWithContext(hierarchy, csvData, {
technical: true,
transitions: true,
style: true
});
iterationResults.normal = {
success: true,
content: normalResult,
duration: Date.now() - normalStartTime,
elementsCount: Object.keys(normalResult).length
};
logSh(` ✅ Pipeline normale: ${iterationResults.normal.elementsCount} éléments (${iterationResults.normal.duration}ms)`, 'DEBUG');
} catch (error) {
iterationResults.normal = {
success: false,
error: error.message,
duration: Date.now() - normalStartTime
};
logSh(` ❌ Pipeline normale échouée: ${error.message}`, 'ERROR');
}
}
// ========================================
// PIPELINE ADVERSARIALE
// ========================================
logSh(` 🎯 Génération pipeline adversariale...`, 'DEBUG');
const adversarialStartTime = Date.now();
try {
const adversarialResult = await generateWithAdversarialContext({
hierarchy,
csvData,
adversarialConfig: {
detectorTarget,
intensity,
enableAllSteps: true,
...adversarialConfig
}
});
iterationResults.adversarial = {
success: true,
content: adversarialResult.content,
stats: adversarialResult.stats,
adversarialMetrics: adversarialResult.adversarialMetrics,
duration: Date.now() - adversarialStartTime,
elementsCount: Object.keys(adversarialResult.content).length
};
logSh(` ✅ Pipeline adversariale: ${iterationResults.adversarial.elementsCount} éléments (${iterationResults.adversarial.duration}ms)`, 'DEBUG');
logSh(` 📊 Score efficacité: ${adversarialResult.adversarialMetrics.effectivenessScore.toFixed(2)}%`, 'DEBUG');
} catch (error) {
iterationResults.adversarial = {
success: false,
error: error.message,
duration: Date.now() - adversarialStartTime
};
logSh(` ❌ Pipeline adversariale échouée: ${error.message}`, 'ERROR');
}
// ========================================
// ANALYSE COMPARATIVE ITÉRATION
// ========================================
if (analyzeContent && iterationResults.normal?.success && iterationResults.adversarial?.success) {
iterationResults.metrics = analyzeContentComparison(
iterationResults.normal.content,
iterationResults.adversarial.content
);
logSh(` 📈 Diversité: Normal=${iterationResults.metrics.diversity.normal.toFixed(2)}% | Adversarial=${iterationResults.metrics.diversity.adversarial.toFixed(2)}%`, 'DEBUG');
}
results.iterations.push(iterationResults);
}
// ========================================
// CONSOLIDATION RÉSULTATS
// ========================================
const totalDuration = Date.now() - startTime;
// Prendre les meilleurs résultats ou derniers si une seule itération
const lastIteration = results.iterations[results.iterations.length - 1];
results.normal = lastIteration.normal;
results.adversarial = lastIteration.adversarial;
// Analyse comparative globale
results.comparison = generateGlobalComparison(results.iterations, options);
logSh(`🆚 COMPARAISON TERMINÉE: ${iterations} itérations (${totalDuration}ms)`, 'INFO');
if (results.comparison.winner) {
logSh(`🏆 Gagnant: ${results.comparison.winner} (score: ${results.comparison.bestScore.toFixed(2)})`, 'INFO');
}
await tracer.event('Comparaison A/B terminée', {
iterations,
winner: results.comparison.winner,
totalDuration
});
return results;
} catch (error) {
const duration = Date.now() - startTime;
logSh(`❌ COMPARAISON A/B ÉCHOUÉE après ${duration}ms: ${error.message}`, 'ERROR');
throw new Error(`ComparisonFramework failed: ${error.message}`);
}
}, input);
}
/**
* COMPARAISON MULTI-DÉTECTEURS
*/
async function compareMultiDetectors(hierarchy, csvData, detectorTargets = ['general', 'gptZero', 'originality']) {
logSh(`🎯 COMPARAISON MULTI-DÉTECTEURS: ${detectorTargets.length} stratégies`, 'INFO');
const results = {};
const startTime = Date.now();
for (const detector of detectorTargets) {
logSh(` 🔍 Test détecteur: ${detector}`, 'DEBUG');
try {
const comparison = await compareNormalVsAdversarial({
hierarchy,
csvData,
adversarialConfig: { detectorTarget: detector }
}, {
detectorTarget: detector,
intensity: 1.0,
iterations: 1
});
results[detector] = {
success: true,
comparison,
effectivenessGain: comparison.adversarial?.adversarialMetrics?.effectivenessScore || 0
};
logSh(`${detector}: +${results[detector].effectivenessGain.toFixed(2)}% efficacité`, 'DEBUG');
} catch (error) {
results[detector] = {
success: false,
error: error.message,
effectivenessGain: 0
};
logSh(`${detector}: Échec - ${error.message}`, 'ERROR');
}
}
// Analyse du meilleur détecteur
const bestDetector = Object.keys(results).reduce((best, current) => {
if (!results[best]?.success) return current;
if (!results[current]?.success) return best;
return results[current].effectivenessGain > results[best].effectivenessGain ? current : best;
});
const totalDuration = Date.now() - startTime;
logSh(`🎯 MULTI-DÉTECTEURS TERMINÉ: Meilleur=${bestDetector} (${totalDuration}ms)`, 'INFO');
return {
results,
bestDetector,
bestScore: results[bestDetector]?.effectivenessGain || 0,
totalDuration
};
}
/**
* BENCHMARK PERFORMANCE
*/
async function benchmarkPerformance(hierarchy, csvData, configurations = []) {
const defaultConfigs = [
{ name: 'Normal', type: 'normal' },
{ name: 'Simple Adversarial', type: 'adversarial', detectorTarget: 'general', intensity: 0.5 },
{ name: 'Intense Adversarial', type: 'adversarial', detectorTarget: 'gptZero', intensity: 1.0 },
{ name: 'Max Adversarial', type: 'adversarial', detectorTarget: 'originality', intensity: 1.5 }
];
const configs = configurations.length > 0 ? configurations : defaultConfigs;
logSh(`⚡ BENCHMARK PERFORMANCE: ${configs.length} configurations`, 'INFO');
const results = [];
for (const config of configs) {
logSh(` 🔧 Test: ${config.name}`, 'DEBUG');
const startTime = Date.now();
try {
let result;
if (config.type === 'normal') {
result = await generateWithContext(hierarchy, csvData);
} else {
const adversarialResult = await generateWithAdversarialContext({
hierarchy,
csvData,
adversarialConfig: {
detectorTarget: config.detectorTarget || 'general',
intensity: config.intensity || 1.0
}
});
result = adversarialResult.content;
}
const duration = Date.now() - startTime;
results.push({
name: config.name,
type: config.type,
success: true,
duration,
elementsCount: Object.keys(result).length,
performance: Object.keys(result).length / (duration / 1000) // éléments par seconde
});
logSh(`${config.name}: ${Object.keys(result).length} éléments (${duration}ms)`, 'DEBUG');
} catch (error) {
results.push({
name: config.name,
type: config.type,
success: false,
error: error.message,
duration: Date.now() - startTime
});
logSh(`${config.name}: Échec - ${error.message}`, 'ERROR');
}
}
// Analyser les résultats
const successfulResults = results.filter(r => r.success);
const fastest = successfulResults.reduce((best, current) =>
current.duration < best.duration ? current : best, successfulResults[0]);
const mostEfficient = successfulResults.reduce((best, current) =>
current.performance > best.performance ? current : best, successfulResults[0]);
logSh(`⚡ BENCHMARK TERMINÉ: Fastest=${fastest?.name} | Most efficient=${mostEfficient?.name}`, 'INFO');
return {
results,
fastest,
mostEfficient,
summary: {
totalConfigs: configs.length,
successful: successfulResults.length,
failed: results.length - successfulResults.length
}
};
}
// ============= HELPER FUNCTIONS =============
/**
* Analyser différences de contenu entre normal et adversarial
*/
function analyzeContentComparison(normalContent, adversarialContent) {
const metrics = {
diversity: {
normal: analyzeDiversityScore(Object.values(normalContent).join(' ')),
adversarial: analyzeDiversityScore(Object.values(adversarialContent).join(' '))
},
length: {
normal: Object.values(normalContent).join(' ').length,
adversarial: Object.values(adversarialContent).join(' ').length
},
elementsCount: {
normal: Object.keys(normalContent).length,
adversarial: Object.keys(adversarialContent).length
},
differences: compareContentElements(normalContent, adversarialContent)
};
return metrics;
}
/**
* Score de diversité lexicale
*/
function analyzeDiversityScore(content) {
if (!content || typeof content !== 'string') return 0;
const words = content.split(/\s+/).filter(w => w.length > 2);
if (words.length === 0) return 0;
const uniqueWords = [...new Set(words.map(w => w.toLowerCase()))];
return (uniqueWords.length / words.length) * 100;
}
/**
* Comparer éléments de contenu
*/
function compareContentElements(normalContent, adversarialContent) {
const differences = {
modified: 0,
identical: 0,
totalElements: Math.max(Object.keys(normalContent).length, Object.keys(adversarialContent).length)
};
const allTags = [...new Set([...Object.keys(normalContent), ...Object.keys(adversarialContent)])];
allTags.forEach(tag => {
if (normalContent[tag] && adversarialContent[tag]) {
if (normalContent[tag] === adversarialContent[tag]) {
differences.identical++;
} else {
differences.modified++;
}
}
});
differences.modificationRate = differences.totalElements > 0 ?
(differences.modified / differences.totalElements) * 100 : 0;
return differences;
}
/**
* Générer analyse comparative globale
*/
function generateGlobalComparison(iterations, options) {
const successfulIterations = iterations.filter(it =>
it.normal?.success && it.adversarial?.success);
if (successfulIterations.length === 0) {
return {
winner: null,
bestScore: 0,
summary: 'Aucune itération réussie'
};
}
// Moyenner les métriques
const avgMetrics = {
diversity: {
normal: 0,
adversarial: 0
},
performance: {
normal: 0,
adversarial: 0
}
};
successfulIterations.forEach(iteration => {
if (iteration.metrics) {
avgMetrics.diversity.normal += iteration.metrics.diversity.normal;
avgMetrics.diversity.adversarial += iteration.metrics.diversity.adversarial;
}
avgMetrics.performance.normal += iteration.normal.elementsCount / (iteration.normal.duration / 1000);
avgMetrics.performance.adversarial += iteration.adversarial.elementsCount / (iteration.adversarial.duration / 1000);
});
const iterCount = successfulIterations.length;
avgMetrics.diversity.normal /= iterCount;
avgMetrics.diversity.adversarial /= iterCount;
avgMetrics.performance.normal /= iterCount;
avgMetrics.performance.adversarial /= iterCount;
// Déterminer le gagnant
const diversityGain = avgMetrics.diversity.adversarial - avgMetrics.diversity.normal;
const performanceLoss = avgMetrics.performance.normal - avgMetrics.performance.adversarial;
// Score composite (favorise diversité avec pénalité performance)
const adversarialScore = diversityGain * 2 - (performanceLoss * 0.5);
return {
winner: adversarialScore > 5 ? 'adversarial' : 'normal',
bestScore: Math.max(avgMetrics.diversity.normal, avgMetrics.diversity.adversarial),
diversityGain,
performanceLoss,
avgMetrics,
summary: `Diversité: +${diversityGain.toFixed(2)}%, Performance: ${performanceLoss > 0 ? '-' : '+'}${Math.abs(performanceLoss).toFixed(2)} elem/s`
};
}
module.exports = {
compareNormalVsAdversarial, // ← MAIN ENTRY POINT
compareMultiDetectors,
benchmarkPerformance,
analyzeContentComparison,
analyzeDiversityScore
};