// ======================================== // VALIDATEUR ANTI-DÉTECTION IA // Détection des patterns typiques d'IA et évaluation de naturalité // ======================================== // Import du LLMManager pour validation IA let LLMManager; try { const llmModule = require('../../lib/LLMManager'); if (llmModule && typeof llmModule.callLLM === 'function') { LLMManager = llmModule; } } catch (error) { console.warn('LLMManager non disponible pour AntiDetectionValidator:', error.message); } /** * Validateur spécialisé dans la détection d'empreintes IA * Score de naturalité et détection de patterns suspects */ class AntiDetectionValidator { // Base de données des empreintes IA suspectes static AI_FINGERPRINTS = { // Mots/expressions typiquement IA (niveau critique) critical: { english: [ 'comprehensive', 'robust', 'seamless', 'innovative', 'cutting-edge', 'state-of-the-art', 'furthermore', 'moreover', 'in conclusion', 'it is important to note', 'it\'s worth noting', 'additionally' ], french: [ 'complet et exhaustif', 'robuste et fiable', 'innovant et révolutionnaire', 'de pointe', 'il est important de souligner', 'il convient de noter', 'en outre', 'par ailleurs', 'en définitive', 'en fin de compte' ] }, // Patterns de structure suspects (niveau élevé) high: { patterns: [ /^(Premièrement|Deuxièmement|Troisièmement).+(Deuxièmement|Troisièmement|Enfin)/s, /En conclusion.+(il est|nous pouvons|il convient)/i, /(Il est important|Il faut souligner|Il convient de noter).+(que|de)/gi, /^\d+\.\s.+\n\d+\.\s.+\n\d+\.\s/m // Listes numérotées systématiques ], phrases: [ 'permet de', 'contribue à', 'favorise le développement', 'optimise les performances', 'améliore significativement' ] }, // Indicateurs moyens (niveau modéré) moderate: { overused: [ 'cependant', 'néanmoins', 'toutefois', 'ainsi', 'donc', 'par conséquent', 'en effet', 'effectivement', 'naturellement' ], formal: [ 'il s\'avère que', 'force est de constater', 'il ressort que', 'on peut considérer', 'il est possible de' ] } }; // Patterns de transitions trop parfaites static PERFECT_TRANSITIONS = [ /\.\s+(Cependant|Néanmoins|Toutefois|Par ailleurs),/g, /\.\s+(En outre|De plus|Par ailleurs),/g, /\.\s+(Ainsi|Donc|Par conséquent),/g, /\.\s+(En effet|Effectivement|Naturellement),/g ]; // Structures de phrases trop régulières static REGULAR_PATTERNS = [ /^[A-Z][^.]{50,80}\./gm, // Phrases de longueur trop uniforme /^[A-Z][^.]+, [^.]+, [^.]+\./gm, // Structure triple répétitive ]; /** * Validation anti-détection principale */ static async validateAntiDetection(content, options = {}) { // Analyse statique (patterns) const staticAnalysis = { fingerprintScore: this.analyzeFingerprintScore(content), variabilityScore: this.analyzeVariabilityScore(content), naturalness: this.analyzeNaturalness(content), humanErrors: this.detectHumanLikeErrors(content), overallScore: 0, details: {} }; // Calcul score global pondéré avec vérification NaN const fp = isNaN(staticAnalysis.fingerprintScore?.score) ? 50 : staticAnalysis.fingerprintScore.score; const vs = isNaN(staticAnalysis.variabilityScore) ? 50 : staticAnalysis.variabilityScore; const nat = isNaN(staticAnalysis.naturalness) ? 50 : staticAnalysis.naturalness; const he = isNaN(staticAnalysis.humanErrors) ? 50 : staticAnalysis.humanErrors; staticAnalysis.overallScore = Math.round( fp * 0.3 + vs * 0.25 + nat * 0.25 + he * 0.2 ); // Classification du niveau de détection staticAnalysis.detectionRisk = this.calculateDetectionRisk(staticAnalysis.overallScore); // Validation IA complémentaire (si LLM disponible) let aiAnalysis = null; if (LLMManager && options.enableLLM !== false) { try { aiAnalysis = await this.validateWithLLM(content); // Fusion des scores (moyenne pondérée) const combinedScore = Math.round(staticAnalysis.overallScore * 0.7 + aiAnalysis.score * 0.3); return { ...staticAnalysis, overallScore: combinedScore, aiAnalysis: aiAnalysis, detectionRisk: this.calculateDetectionRisk(combinedScore) }; } catch (error) { console.warn('Erreur validation LLM anti-détection:', error.message); } } return staticAnalysis; } /** * Validation avec LLM pour analyse qualitative */ static async validateWithLLM(content) { const prompt = ` MISSION: Analyse si ce texte a été généré par une IA ou écrit par un humain. TEXTE À ANALYSER: --- ${content.substring(0, 1500)}${content.length > 1500 ? '...[TRONQUÉ]' : ''} --- CRITÈRES D'ANALYSE: 1. EMPREINTES IA: Expressions typiques ("comprehensive", "robuste", "il convient de noter", transitions parfaites) 2. VARIABILITÉ: Longueur des phrases, structures variées, vocabulaire diversifié 3. NATURALITÉ: Hésitations, nuances, imperfections subtiles, ton spontané 4. FLUIDITÉ: Transitions naturelles vs mécaniques RÉPONSE JSON STRICTE: { "score": 85, "humanLikelihood": "high", "aiFingerprints": ["expressions trouvées"], "naturalElements": ["éléments humains détectés"], "feedback": "Analyse détaillée en 2-3 phrases", "confidence": 0.8 } SCORE: 0-100 (0=clairement IA, 100=clairement humain)`; const response = await LLMManager.callLLM('openai', prompt, { temperature: 0.1, max_tokens: 500 }); try { // Parse JSON de la réponse let jsonStr = response.trim(); const jsonMatch = jsonStr.match(/\{[\s\S]*\}/); if (jsonMatch) { jsonStr = jsonMatch[0]; } const parsed = JSON.parse(jsonStr); return { score: Math.max(0, Math.min(100, parsed.score || 50)), likelihood: parsed.humanLikelihood || 'medium', fingerprints: parsed.aiFingerprints || [], naturalElements: parsed.naturalElements || [], feedback: parsed.feedback || 'Analyse non disponible', confidence: Math.max(0, Math.min(1, parsed.confidence || 0.5)), source: 'llm' }; } catch (error) { console.warn('Erreur parsing réponse LLM anti-détection:', error.message); return { score: 50, feedback: `Erreur parsing: ${error.message}`, confidence: 0.1, source: 'fallback' }; } } /** * Analyse des empreintes IA suspectes */ static analyzeFingerprintScore(content) { let score = 100; const details = { critical: [], high: [], moderate: [] }; const lowerContent = content.toLowerCase(); // Vérification empreintes critiques this.AI_FINGERPRINTS.critical.english.forEach(phrase => { const count = (lowerContent.match(new RegExp(phrase.toLowerCase(), 'g')) || []).length; if (count > 0) { score -= count * 15; // -15 points par occurrence critique details.critical.push({ phrase, count }); } }); this.AI_FINGERPRINTS.critical.french.forEach(phrase => { const count = (lowerContent.match(new RegExp(phrase.toLowerCase(), 'g')) || []).length; if (count > 0) { score -= count * 15; details.critical.push({ phrase, count }); } }); // Vérification patterns suspects this.AI_FINGERPRINTS.high.patterns.forEach(pattern => { const matches = content.match(pattern); if (matches) { score -= matches.length * 10; // -10 points par pattern details.high.push({ pattern: pattern.toString(), matches: matches.length }); } }); // Vérification phrases surutilisées this.AI_FINGERPRINTS.moderate.overused.forEach(phrase => { const count = (lowerContent.split(phrase.toLowerCase()).length - 1); if (count > 2) { // Plus de 2 occurrences = suspect score -= (count - 2) * 5; details.moderate.push({ phrase, count }); } }); return { score: Math.max(0, score), details: details }; } /** * Analyse de la variabilité linguistique */ static analyzeVariabilityScore(content) { const sentences = this.getSentences(content); if (sentences.length < 3) return 50; // Variabilité longueur des phrases const lengths = sentences.map(s => s.trim().length); const lengthVariation = this.calculateCoeffVariation(lengths); // Variabilité structure (début de phrase) const starters = sentences.map(s => s.trim().substring(0, 10).toLowerCase()); const uniqueStarters = new Set(starters).size; const starterVariation = uniqueStarters / sentences.length; // Variabilité ponctuation const punctuationTypes = this.analyzePunctuationVariety(content); // Score combiné let variabilityScore = 0; variabilityScore += Math.min(lengthVariation * 30, 30); variabilityScore += starterVariation * 40; variabilityScore += punctuationTypes * 10; // Pénalité pour transitions trop parfaites const perfectTransitions = this.countPerfectTransitions(content); variabilityScore -= perfectTransitions * 5; return Math.max(0, Math.min(100, Math.round(variabilityScore))); } /** * Analyse de naturalité (imperfections humaines) */ static analyzeNaturalness(content) { let naturalness = 50; // Score de base // Présence d'hésitations ou nuances humaines const hesitationMarkers = [ 'peut-être', 'probablement', 'il semble que', 'on dirait que', 'j\'ai l\'impression', 'si je ne me trompe', 'en quelque sorte' ]; hesitationMarkers.forEach(marker => { if (content.toLowerCase().includes(marker)) { naturalness += 8; } }); // Présence d'expressions familières const colloquialisms = [ 'du coup', 'en fait', 'franchement', 'carrément', 'pas mal de', 'un tas de', 'histoire de' ]; colloquialisms.forEach(expr => { if (content.toLowerCase().includes(expr)) { naturalness += 5; } }); // Variation dans les connecteurs (évite la répétition) const connectorVariety = this.analyzeConnectorVariety(content); naturalness += connectorVariety; // Présence de parenthèses ou commentaires entre tirets const asides = (content.match(/\([^)]+\)/g) || []).length + (content.match(/—[^—]+—/g) || []).length; naturalness += Math.min(asides * 3, 15); return Math.max(0, Math.min(100, Math.round(naturalness))); } /** * Détection d'erreurs/imperfections humaines positives */ static detectHumanLikeErrors(content) { let humanScore = 20; // Score de base faible // Phrases incomplètes (positif pour naturalité) const incompleteMarkers = content.match(/\.\.\./g) || []; humanScore += Math.min(incompleteMarkers.length * 10, 30); // Questions rhétoriques const questions = (content.match(/\?/g) || []).length; humanScore += Math.min(questions * 8, 25); // Exclamations (modération) const exclamations = (content.match(/!/g) || []).length; if (exclamations > 0 && exclamations < 5) { humanScore += exclamations * 5; } // Répétitions légères (positives si modérées) const repetitionScore = this.analyzePositiveRepetition(content); humanScore += repetitionScore; // Longueurs de phrases irrégulières (bon signe) const sentences = this.getSentences(content); const lengthIrregularity = this.calculateLengthIrregularity(sentences); humanScore += lengthIrregularity; return Math.max(0, Math.min(100, Math.round(humanScore))); } /** * Calcul du risque de détection */ static calculateDetectionRisk(overallScore) { if (overallScore >= 85) return { level: 'LOW', text: 'Risque faible' }; if (overallScore >= 70) return { level: 'MODERATE', text: 'Risque modéré' }; if (overallScore >= 50) return { level: 'HIGH', text: 'Risque élevé' }; return { level: 'CRITICAL', text: 'Risque critique' }; } /** * Utilitaires de calcul */ static getSentences(content) { return content.split(/[.!?]+/).filter(s => s.trim().length > 10); } static calculateCoeffVariation(numbers) { if (numbers.length < 2) return 0; const mean = numbers.reduce((a, b) => a + b) / numbers.length; const variance = numbers.reduce((sum, n) => sum + Math.pow(n - mean, 2), 0) / numbers.length; const stdDev = Math.sqrt(variance); return mean > 0 ? stdDev / mean : 0; } static analyzePunctuationVariety(content) { const punctTypes = ['.', ',', ';', ':', '!', '?', '...', '—'].filter(p => content.includes(p) ); return punctTypes.length; } static countPerfectTransitions(content) { let count = 0; this.PERFECT_TRANSITIONS.forEach(pattern => { const matches = content.match(pattern); if (matches) count += matches.length; }); return count; } static analyzeConnectorVariety(content) { const connectors = [ 'mais', 'cependant', 'toutefois', 'néanmoins', 'ainsi', 'donc', 'par conséquent', 'en effet', 'par ailleurs', 'en outre' ]; const used = connectors.filter(c => content.toLowerCase().includes(c)); return Math.min(used.length * 5, 25); // Max 25 points } static analyzePositiveRepetition(content) { const words = content.toLowerCase().split(/\s+/); const importantWords = words.filter(w => w.length > 5); const repetitions = {}; importantWords.forEach(word => { repetitions[word] = (repetitions[word] || 0) + 1; }); // Répétitions modérées = humain (2-3 fois max) let positiveReps = 0; Object.values(repetitions).forEach(count => { if (count === 2) positiveReps += 3; if (count === 3) positiveReps += 2; if (count > 3) positiveReps -= 2; // Pénalité excessive }); return Math.max(0, Math.min(positiveReps, 15)); } static calculateLengthIrregularity(sentences) { if (sentences.length < 3) return 0; const lengths = sentences.map(s => s.trim().length); const coeffVar = this.calculateCoeffVariation(lengths); // Irrégularité modérée = bon signe humain if (coeffVar >= 0.2 && coeffVar <= 0.5) { return 20; } else if (coeffVar > 0.1) { return 10; } return 0; } /** * Rapport détaillé pour debugging */ static generateDetailedReport(content) { const validation = this.validateAntiDetection(content); return { score: validation.overallScore, risk: validation.detectionRisk, breakdown: { fingerprints: validation.fingerprintScore, variability: validation.variabilityScore, naturalness: validation.naturalness, humanErrors: validation.humanErrors }, recommendations: this.generateRecommendations(validation), timestamp: new Date().toISOString() }; } /** * Génération de recommandations d'amélioration */ static generateRecommendations(validation) { const recommendations = []; if (validation.fingerprintScore.score < 70) { recommendations.push('Réduire les expressions typiquement IA identifiées'); } if (validation.variabilityScore < 60) { recommendations.push('Augmenter la variabilité des structures de phrases'); } if (validation.naturalness < 60) { recommendations.push('Ajouter des nuances et hésitations humaines'); } if (validation.humanErrors < 40) { recommendations.push('Introduire des imperfections subtiles pour plus de naturalité'); } return recommendations; } } module.exports = { AntiDetectionValidator };