Nouveau système qui analyse le texte français et génère des prompts optimisés en incluant uniquement le vocabulaire pertinent du lexique, réduisant drastiquement le nombre de tokens. # Backend - contextAnalyzer.js : Analyse contextuelle avec lemmatisation française - Tokenization avec normalisation des accents - Recherche intelligente (correspondances exactes, synonymes, formes conjuguées) - Calcul dynamique du nombre max d'entrées selon longueur (30/50/100) - Expansion sémantique niveau 1 (modulaire pour futur) - Fallback racines (309 racines si mots inconnus) - promptBuilder.js : Génération de prompts optimisés - Templates de base sans lexique massif - Injection ciblée du vocabulaire pertinent - Formatage par type (racines sacrées, standards, verbes) - Support fallback avec toutes les racines - server.js : Intégration API avec structure 3 layers - Layer 1: Traduction pure - Layer 2: Métadonnées contextuelles (mots trouvés, optimisation) - Layer 3: Explications du LLM (décomposition, notes) - lexiqueLoader.js : Fusion du lexique simple data/lexique-francais-confluent.json - Charge 636 entrées (516 ancien + 120 merged) # Frontend - index.html : Interface 3 layers collapsibles - Layer 1 (toujours visible) : Traduction avec mise en valeur - Layer 2 (collapsible) : Contexte lexical + statistiques d'optimisation - Layer 3 (collapsible) : Explications linguistiques du LLM - Design dark complet (fix fond blanc + listes déroulantes) - Animations smooth pour expand/collapse # Documentation - docs/PROMPT_CONTEXTUEL_INTELLIGENT.md : Plan complet validé - Architecture technique détaillée - Cas d'usage et décisions de design - Métriques de succès # Tests - Tests exhaustifs avec validation exigeante - Économie moyenne : 81% de tokens - Économie minimale : 52% (même avec fallback) - Context skimming opérationnel et validé # Corrections - ancien-confluent/lexique/02-racines-standards.json : Fix erreur JSON ligne 527 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
224 lines
6.3 KiB
JavaScript
224 lines
6.3 KiB
JavaScript
/**
|
|
* Prompt Builder - Génère des prompts contextuels optimisés
|
|
*
|
|
* Fonctionnalités:
|
|
* 1. Templates de base (règles linguistiques sans lexique massif)
|
|
* 2. Injection de vocabulaire ciblé
|
|
* 3. Fallback racines
|
|
* 4. Formatage optimisé pour le LLM
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Charge le template de prompt de base depuis les fichiers
|
|
* @param {string} variant - 'proto' ou 'ancien'
|
|
* @returns {string} - Template de prompt
|
|
*/
|
|
function loadBaseTemplate(variant) {
|
|
const templatePath = path.join(__dirname, 'prompts', `${variant}-system.txt`);
|
|
|
|
if (!fs.existsSync(templatePath)) {
|
|
throw new Error(`Template not found: ${templatePath}`);
|
|
}
|
|
|
|
return fs.readFileSync(templatePath, 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Génère la section vocabulaire pour le prompt
|
|
* Format compact et structuré
|
|
* @param {Array} entries - Entrées du lexique pertinentes
|
|
* @returns {string} - Section vocabulaire formatée
|
|
*/
|
|
function formatVocabularySection(entries) {
|
|
if (!entries || entries.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
const lines = ['\n# VOCABULAIRE PERTINENT POUR CETTE TRADUCTION\n'];
|
|
|
|
// Grouper par type
|
|
const byType = {
|
|
racine_sacree: [],
|
|
racine: [],
|
|
verbe: [],
|
|
nom: [],
|
|
autre: []
|
|
};
|
|
|
|
entries.forEach(entry => {
|
|
if (entry.traductions && entry.traductions.length > 0) {
|
|
const trad = entry.traductions[0];
|
|
const type = trad.type || 'autre';
|
|
const key = type === 'racine_sacree' ? 'racine_sacree' :
|
|
type === 'racine' ? 'racine' :
|
|
type === 'verbe' ? 'verbe' :
|
|
type === 'nom' ? 'nom' : 'autre';
|
|
|
|
byType[key].push({
|
|
fr: entry.mot_francais,
|
|
conf: trad.confluent,
|
|
forme_liee: trad.forme_liee || trad.confluent,
|
|
domaine: trad.domaine || '',
|
|
note: trad.note || ''
|
|
});
|
|
}
|
|
});
|
|
|
|
// Formatter par type
|
|
if (byType.racine_sacree.length > 0) {
|
|
lines.push('## Racines sacrées (voyelle initiale)\n');
|
|
byType.racine_sacree.forEach(item => {
|
|
lines.push(`- ${item.conf} (${item.fr}) [forme liée: ${item.forme_liee}]`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
if (byType.racine.length > 0) {
|
|
lines.push('## Racines standards\n');
|
|
byType.racine.forEach(item => {
|
|
lines.push(`- ${item.conf} (${item.fr}) [forme liée: ${item.forme_liee}]`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
if (byType.verbe.length > 0) {
|
|
lines.push('## Verbes\n');
|
|
byType.verbe.forEach(item => {
|
|
lines.push(`- ${item.fr} → ${item.conf}`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
if (byType.nom.length > 0) {
|
|
lines.push('## Noms et concepts\n');
|
|
byType.nom.forEach(item => {
|
|
lines.push(`- ${item.fr} → ${item.conf}`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
if (byType.autre.length > 0) {
|
|
lines.push('## Autres\n');
|
|
byType.autre.forEach(item => {
|
|
lines.push(`- ${item.fr} → ${item.conf}`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Génère la section de fallback avec toutes les racines
|
|
* @param {Array} roots - Liste des racines
|
|
* @returns {string} - Section racines formatée
|
|
*/
|
|
function formatRootsFallback(roots) {
|
|
if (!roots || roots.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
const lines = ['\n# RACINES DISPONIBLES (à composer)\n'];
|
|
lines.push('⚠️ Les mots demandés ne sont pas dans le lexique. Compose-les à partir des racines ci-dessous.\n');
|
|
|
|
const sacrees = roots.filter(r => r.sacree);
|
|
const standards = roots.filter(r => !r.sacree);
|
|
|
|
if (sacrees.length > 0) {
|
|
lines.push(`## Racines sacrées (${sacrees.length})\n`);
|
|
sacrees.forEach(r => {
|
|
lines.push(`- ${r.confluent} (${r.mot_francais}) [forme liée: ${r.forme_liee}] - ${r.domaine}`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
if (standards.length > 0) {
|
|
lines.push(`## Racines standards (${standards.length})\n`);
|
|
standards.forEach(r => {
|
|
lines.push(`- ${r.confluent} (${r.mot_francais}) [forme liée: ${r.forme_liee}] - ${r.domaine}`);
|
|
});
|
|
lines.push('');
|
|
}
|
|
|
|
lines.push('IMPORTANT: Utilise les liaisons sacrées pour composer les mots manquants.\n');
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Construit un prompt contextuel complet
|
|
* @param {Object} contextResult - Résultat de analyzeContext()
|
|
* @param {string} variant - 'proto' ou 'ancien'
|
|
* @returns {string} - Prompt complet optimisé
|
|
*/
|
|
function buildContextualPrompt(contextResult, variant = 'ancien') {
|
|
// Charger le template de base
|
|
const basePrompt = loadBaseTemplate(variant);
|
|
|
|
// Si fallback, injecter toutes les racines
|
|
if (contextResult.useFallback) {
|
|
const rootsSection = formatRootsFallback(contextResult.rootsFallback);
|
|
return basePrompt + '\n' + rootsSection;
|
|
}
|
|
|
|
// Sinon, injecter uniquement le vocabulaire pertinent
|
|
const vocabularySection = formatVocabularySection(contextResult.entries);
|
|
return basePrompt + '\n' + vocabularySection;
|
|
}
|
|
|
|
/**
|
|
* Construit le prompt de base sans aucun lexique (pour useLexique=false)
|
|
* @param {string} variant - 'proto' ou 'ancien'
|
|
* @returns {string} - Prompt de base uniquement
|
|
*/
|
|
function getBasePrompt(variant = 'ancien') {
|
|
return loadBaseTemplate(variant);
|
|
}
|
|
|
|
/**
|
|
* Estime le nombre de tokens dans un texte
|
|
* Estimation simple : ~1 token pour 4 caractères
|
|
* @param {string} text - Texte à estimer
|
|
* @returns {number} - Nombre de tokens estimé
|
|
*/
|
|
function estimateTokens(text) {
|
|
return Math.ceil(text.length / 4);
|
|
}
|
|
|
|
/**
|
|
* Génère des statistiques sur le prompt généré
|
|
* @param {string} prompt - Prompt généré
|
|
* @param {Object} contextResult - Résultat du contexte
|
|
* @returns {Object} - Statistiques
|
|
*/
|
|
function getPromptStats(prompt, contextResult) {
|
|
const promptTokens = estimateTokens(prompt);
|
|
const fullLexiqueTokens = contextResult.metadata.tokensFullLexique;
|
|
const saved = fullLexiqueTokens - promptTokens;
|
|
const savingsPercent = Math.round((saved / fullLexiqueTokens) * 100);
|
|
|
|
return {
|
|
promptTokens,
|
|
fullLexiqueTokens,
|
|
tokensSaved: saved,
|
|
savingsPercent,
|
|
entriesUsed: contextResult.metadata.entriesUsed,
|
|
useFallback: contextResult.useFallback,
|
|
wordsFound: contextResult.metadata.wordsFound.length,
|
|
wordsNotFound: contextResult.metadata.wordsNotFound.length
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
loadBaseTemplate,
|
|
formatVocabularySection,
|
|
formatRootsFallback,
|
|
buildContextualPrompt,
|
|
getBasePrompt,
|
|
estimateTokens,
|
|
getPromptStats
|
|
};
|