seo-generator-server/lib/pipeline/PipelineDefinition.js
StillHammer 0244521f5c feat(selective-smart-touch): Add intelligent analysis-driven enhancement system + validation spec
## SelectiveSmartTouch (NEW)
- Architecture révolutionnaire: Analyse intelligente → Améliorations ciblées précises
- 5 modules: SmartAnalysisLayer, SmartTechnicalLayer, SmartStyleLayer, SmartReadabilityLayer, SmartTouchCore
- Système 10% segments: amélioration uniquement des segments les plus faibles (intensity-based)
- Détection contexte globale pour prompts adaptatifs multi-secteurs
- Intégration complète dans PipelineExecutor et PipelineDefinition

## Pipeline Validator Spec (NEW)
- Spécification complète système validation qualité par LLM
- 5 critères universels: Qualité, Verbosité, SEO, Répétitions, Naturalité
- Échantillonnage intelligent par filtrage balises (pas XML)
- Évaluation multi-versions avec justifications détaillées
- Coût estimé: ~$1/validation (260 appels LLM)

## Optimizations
- Réduction intensités fullEnhancement (technical 1.0→0.7, style 0.8→0.5)
- Ajout gardes-fous anti-familiarité excessive dans StyleLayer
- Sauvegarde étapes intermédiaires activée par défaut (pipeline-runner)

## Fixes
- Fix typo critique SmartTouchCore.js:110 (determineLayers ToApply → determineLayersToApply)
- Prompts généralisés multi-secteurs (e-commerce, SaaS, services, informatif)

🚀 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 15:01:02 +08:00

388 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* PipelineDefinition.js
*
* Schemas et validation pour les pipelines modulaires flexibles.
* Permet de définir des workflows custom avec n'importe quelle combinaison de modules.
*/
const { logSh } = require('../ErrorReporting');
const { getLLMProvidersList } = require('../LLMManager');
/**
* Providers LLM disponibles (source unique depuis LLMManager)
*/
const AVAILABLE_LLM_PROVIDERS = getLLMProvidersList();
/**
* Modules disponibles dans le pipeline
*/
const AVAILABLE_MODULES = {
generation: {
name: 'Generation',
description: 'Génération initiale du contenu',
modes: ['simple'],
defaultIntensity: 1.0,
defaultLLM: 'claude-sonnet-4-5',
parameters: {
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'claude-sonnet-4-5' }
}
},
selective: {
name: 'Selective Enhancement',
description: 'Amélioration sélective par couches',
modes: [
'lightEnhancement',
'standardEnhancement',
'fullEnhancement',
'personalityFocus',
'fluidityFocus',
'adaptive'
],
defaultIntensity: 1.0,
defaultLLM: 'gpt-4o-mini',
parameters: {
layers: { type: 'array', description: 'Couches spécifiques à appliquer' },
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'gpt-4o-mini' }
}
},
smarttouch: {
name: 'SmartTouch (Analyse→Ciblé)',
description: 'Analyse intelligente puis améliorations précises ciblées (nouvelle génération)',
modes: [
'full', // Analyse + Technical + Style + Readability
'analysis_only', // Analyse uniquement sans amélioration
'technical_only', // Améliorations techniques ciblées uniquement
'style_only', // Améliorations style ciblées uniquement
'readability_only' // Améliorations lisibilité ciblées uniquement
],
defaultIntensity: 1.0,
defaultLLM: 'gpt-4o-mini',
parameters: {
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'gpt-4o-mini' },
skipAnalysis: { type: 'boolean', default: false, description: 'Passer l\'analyse (mode legacy)' },
layersOrder: { type: 'array', default: ['technical', 'style', 'readability'], description: 'Ordre d\'application des couches' }
}
},
adversarial: {
name: 'Adversarial Generation',
description: 'Techniques anti-détection',
modes: ['none', 'light', 'standard', 'heavy', 'adaptive'],
defaultIntensity: 1.0,
defaultLLM: 'gemini-pro',
parameters: {
detector: { type: 'string', enum: ['general', 'gptZero', 'originality'], default: 'general' },
method: { type: 'string', enum: ['enhancement', 'regeneration', 'hybrid'], default: 'regeneration' },
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'gemini-pro' }
}
},
human: {
name: 'Human Simulation',
description: 'Simulation comportement humain',
modes: [
'none',
'lightSimulation',
'standardSimulation',
'heavySimulation',
'adaptiveSimulation',
'personalityFocus',
'temporalFocus'
],
defaultIntensity: 1.0,
defaultLLM: 'mistral-small',
parameters: {
fatigueLevel: { type: 'number', min: 0, max: 1, default: 0.5 },
errorRate: { type: 'number', min: 0, max: 1, default: 0.3 },
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'mistral-small' }
}
},
pattern: {
name: 'Pattern Breaking',
description: 'Cassage patterns LLM',
modes: [
'none',
'lightPatternBreaking',
'standardPatternBreaking',
'heavyPatternBreaking',
'adaptivePatternBreaking',
'syntaxFocus',
'connectorsFocus'
],
defaultIntensity: 1.0,
defaultLLM: 'deepseek-chat',
parameters: {
focus: { type: 'string', enum: ['syntax', 'connectors', 'both'], default: 'both' },
llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'deepseek-chat' }
}
}
};
/**
* Schema d'une étape de pipeline
*/
const STEP_SCHEMA = {
step: { type: 'number', required: true, description: 'Numéro séquentiel de l\'étape' },
module: { type: 'string', required: true, enum: Object.keys(AVAILABLE_MODULES), description: 'Module à exécuter' },
mode: { type: 'string', required: true, description: 'Mode du module' },
intensity: { type: 'number', required: false, min: 0.1, max: 2.0, default: 1.0, description: 'Intensité d\'application' },
parameters: { type: 'object', required: false, default: {}, description: 'Paramètres spécifiques au module' },
saveCheckpoint: { type: 'boolean', required: false, default: false, description: 'Sauvegarder checkpoint après cette étape' },
enabled: { type: 'boolean', required: false, default: true, description: 'Activer/désactiver l\'étape' }
};
/**
* Schema complet d'un pipeline
*/
const PIPELINE_SCHEMA = {
name: { type: 'string', required: true, minLength: 3, maxLength: 100 },
description: { type: 'string', required: false, maxLength: 500 },
pipeline: { type: 'array', required: true, minLength: 1, maxLength: 20 },
metadata: {
type: 'object',
required: false,
properties: {
author: { type: 'string' },
created: { type: 'string' },
version: { type: 'string' },
tags: { type: 'array' }
}
}
};
/**
* Classe PipelineDefinition
*/
class PipelineDefinition {
constructor(definition = null) {
this.definition = definition;
}
/**
* Valide un pipeline complet
*/
static validate(pipeline) {
const errors = [];
// Validation schema principal
if (!pipeline.name || typeof pipeline.name !== 'string' || pipeline.name.length < 3) {
errors.push('Le nom du pipeline doit contenir au moins 3 caractères');
}
if (!Array.isArray(pipeline.pipeline) || pipeline.pipeline.length === 0) {
errors.push('Le pipeline doit contenir au moins une étape');
}
if (pipeline.pipeline && pipeline.pipeline.length > 20) {
errors.push('Le pipeline ne peut pas contenir plus de 20 étapes');
}
// Validation des étapes
if (Array.isArray(pipeline.pipeline)) {
pipeline.pipeline.forEach((step, index) => {
const stepErrors = PipelineDefinition.validateStep(step, index);
errors.push(...stepErrors);
});
// Vérifier séquence des steps
const steps = pipeline.pipeline.map(s => s.step).sort((a, b) => a - b);
for (let i = 0; i < steps.length; i++) {
if (steps[i] !== i + 1) {
errors.push(`Numérotation des étapes incorrecte: attendu ${i + 1}, trouvé ${steps[i]}`);
break;
}
}
}
if (errors.length > 0) {
logSh(`❌ Pipeline validation failed: ${errors.join(', ')}`, 'ERROR');
return { valid: false, errors };
}
logSh(`✅ Pipeline "${pipeline.name}" validé: ${pipeline.pipeline.length} étapes`, 'DEBUG');
return { valid: true, errors: [] };
}
/**
* Valide une étape individuelle
*/
static validateStep(step, index) {
const errors = [];
// Step number
if (typeof step.step !== 'number' || step.step < 1) {
errors.push(`Étape ${index}: 'step' doit être un nombre >= 1`);
}
// Module
if (!step.module || !AVAILABLE_MODULES[step.module]) {
errors.push(`Étape ${index}: module '${step.module}' inconnu. Disponibles: ${Object.keys(AVAILABLE_MODULES).join(', ')}`);
return errors; // Stop si module invalide
}
const moduleConfig = AVAILABLE_MODULES[step.module];
// Mode
if (!step.mode) {
errors.push(`Étape ${index}: 'mode' requis pour module ${step.module}`);
} else if (!moduleConfig.modes.includes(step.mode)) {
errors.push(`Étape ${index}: mode '${step.mode}' invalide pour ${step.module}. Disponibles: ${moduleConfig.modes.join(', ')}`);
}
// Intensity
if (step.intensity !== undefined) {
if (typeof step.intensity !== 'number' || step.intensity < 0.1 || step.intensity > 2.0) {
errors.push(`Étape ${index}: intensity doit être entre 0.1 et 2.0`);
}
}
// Parameters (validation basique)
if (step.parameters && typeof step.parameters !== 'object') {
errors.push(`Étape ${index}: parameters doit être un objet`);
}
return errors;
}
/**
* Crée une étape de pipeline valide
*/
static createStep(stepNumber, module, mode, options = {}) {
const moduleConfig = AVAILABLE_MODULES[module];
if (!moduleConfig) {
throw new Error(`Module inconnu: ${module}`);
}
if (!moduleConfig.modes.includes(mode)) {
throw new Error(`Mode ${mode} invalide pour module ${module}`);
}
return {
step: stepNumber,
module,
mode,
intensity: options.intensity ?? moduleConfig.defaultIntensity,
parameters: options.parameters ?? {},
saveCheckpoint: options.saveCheckpoint ?? false,
enabled: options.enabled ?? true
};
}
/**
* Crée un pipeline vide
*/
static createEmpty(name, description = '') {
return {
name,
description,
pipeline: [],
metadata: {
author: 'system',
created: new Date().toISOString(),
version: '1.0',
tags: []
}
};
}
/**
* Clone un pipeline
*/
static clone(pipeline, newName = null) {
const cloned = JSON.parse(JSON.stringify(pipeline));
if (newName) {
cloned.name = newName;
}
cloned.metadata = {
...cloned.metadata,
created: new Date().toISOString(),
clonedFrom: pipeline.name
};
return cloned;
}
/**
* Estime la durée d'un pipeline
*/
static estimateDuration(pipeline) {
// Durées moyennes par module (en secondes)
const DURATIONS = {
generation: 15,
selective: 20,
smarttouch: 25, // ✅ AJOUTÉ: smarttouch (analyse + améliorations ciblées)
adversarial: 25,
human: 15,
pattern: 18
};
let totalSeconds = 0;
pipeline.pipeline.forEach(step => {
if (!step.enabled) return;
const baseDuration = DURATIONS[step.module] || 20;
const intensityFactor = step.intensity || 1.0;
totalSeconds += baseDuration * intensityFactor;
});
return {
seconds: Math.round(totalSeconds),
formatted: PipelineDefinition.formatDuration(totalSeconds)
};
}
/**
* Formate une durée en secondes
*/
static formatDuration(seconds) {
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${minutes}m ${secs}s`;
}
/**
* Obtient les infos d'un module
*/
static getModuleInfo(moduleName) {
return AVAILABLE_MODULES[moduleName] || null;
}
/**
* Liste tous les modules disponibles
*/
static listModules() {
return Object.entries(AVAILABLE_MODULES).map(([key, config]) => ({
id: key,
...config
}));
}
/**
* Génère un résumé lisible du pipeline
*/
static getSummary(pipeline) {
const enabledSteps = pipeline.pipeline.filter(s => s.enabled !== false);
const moduleCount = {};
enabledSteps.forEach(step => {
moduleCount[step.module] = (moduleCount[step.module] || 0) + 1;
});
const summary = Object.entries(moduleCount)
.map(([module, count]) => `${module}×${count}`)
.join(' → ');
return {
totalSteps: enabledSteps.length,
summary,
duration: PipelineDefinition.estimateDuration(pipeline)
};
}
}
module.exports = {
PipelineDefinition,
AVAILABLE_MODULES,
AVAILABLE_LLM_PROVIDERS,
PIPELINE_SCHEMA,
STEP_SCHEMA
};