466 lines
15 KiB
JavaScript
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
|
|
}; |