15 KiB
Travail : Système de Racines Françaises
Objectif
Améliorer le matching des mots français conjugués/accordés en utilisant un système de racines françaises au lieu de se fier uniquement à la lemmatisation automatique.
Problème actuel
- "manger" trouve "mukis" ✅
- "mangé" ne trouve RIEN ❌
- "vu" ne trouve pas "voir" ❌
- "pris" ne trouve pas "prendre" ❌
Le lemmatizer actuel est trop basique et rate beaucoup de formes conjuguées.
Solution : Système hybride avec racines manuelles
1. Déclaration manuelle des racines françaises dans le lexique
Principe : Ajouter un champ racine_fr dans chaque entrée du lexique avec le dénominateur commun le plus long qui couvre toutes les conjugaisons/formes du mot.
IMPORTANT : Ce n'est PAS une extraction automatique des 4 premières lettres. C'est un travail manuel intelligent pour déterminer la meilleure racine pour chaque mot.
Exemples :
- "manger" →
racine_fr: "mang"(4 lettres) → couvre mangé, mange, mangeait, mangerons... - "donner" →
racine_fr: "donn"(4 lettres) → couvre donné, donne, donnait, donnerons... - "comparer" →
racine_fr: "compar"(6 lettres) → couvre comparé, compare, comparaison... - "comprendre" →
racine_fr: "compr"(5 lettres) → couvre compris, comprend, comprenait...
Structure dans le lexique :
"manger": {
"racine_fr": "mang",
"traductions": [...],
"synonymes_fr": ["mange", "mangé", "mangeait"]
}
"comparer": {
"racine_fr": "compar",
"traductions": [...],
"synonymes_fr": ["comparé", "compare", "comparaison"]
}
"comprendre": {
"racine_fr": "compr",
"traductions": [...],
"synonymes_fr": ["compris", "comprend", "comprenait"]
}
Comportement du matching :
Quand l'utilisateur tape "comparé" :
- Le système cherche les entrées dont
racine_frmatche le début de "comparé" - Il trouve
"compar"→ match "comparer" ✅ - Il ne trouve PAS
"compr"(trop court) → ne propose pas "comprendre" ✅
Quand l'utilisateur tape "comprend" :
- Le système trouve :
"compr"(5 lettres) → match "comprendre" ✅"comp"pourrait aussi matcher si on avait un mot avec cette racine
- En cas de multiples matches, le système retourne TOUS les candidats au LLM
- Le LLM choisit selon le contexte de la phrase
Avantages :
- Contrôle total sur la longueur de chaque racine (4, 5, 6 lettres selon le besoin)
- Gère les ambiguïtés en laissant le LLM arbitrer
- Plus précis qu'une extraction automatique aveugle
2. Formes irrégulières dans synonymes_fr
Principe : Ajouter toutes les formes conjuguées dans le champ synonymes_fr du lexique
Exemples de cas nécessitant synonymes_fr :
Formes trop différentes de la racine
-
"voir" →
racine_fr: "voi"couvre "voit", "voyait", "voyons"- MAIS "vu", "vus", "vue", "vues" sont trop différents → à mettre dans
synonymes_fr
- MAIS "vu", "vus", "vue", "vues" sont trop différents → à mettre dans
-
"être" → formes trop différentes (est, suis, sont, été, était, sera, fut...)
- Impossible d'avoir une racine unique → tout dans
synonymes_fr
- Impossible d'avoir une racine unique → tout dans
-
"avoir" → formes trop différentes (a, ai, ont, eu, avait, aura...)
- Impossible d'avoir une racine unique → tout dans
synonymes_fr
- Impossible d'avoir une racine unique → tout dans
Verbes irréguliers avec changement de radical
-
"prendre" →
racine_fr: "pren"couvre "prend", "prenait", "prendra"- MAIS "pris", "prit", "prise" ont un radical différent → dans
synonymes_fr
- MAIS "pris", "prit", "prise" ont un radical différent → dans
-
"faire" →
racine_fr: "fai"couvre "fais", "faisait", "faisons"- MAIS "fait", "faite", "faites" ont un radical différent → dans
synonymes_fr
- MAIS "fait", "faite", "faites" ont un radical différent → dans
Combinaison racine + synonymes :
"voir": {
"racine_fr": "voi",
"synonymes_fr": ["vu", "vus", "vue", "vues", "observer", "regarder"]
}
"prendre": {
"racine_fr": "pren",
"synonymes_fr": ["pris", "prit", "prise", "prises"]
}
Le système essaie d'abord la racine, puis les synonymes.
TRAVAIL 1 : Modification du code (contextAnalyzer.js)
Fichier : ConfluentTranslator/contextAnalyzer.js
Modifications à faire :
A. Modifier searchWord() pour utiliser le champ racine_fr
Principe : Chercher les entrées dont le champ racine_fr est contenu au début du mot recherché.
Emplacement : ligne ~124 dans la fonction searchWord()
Modifier la logique de matching pour ajouter :
// NOUVEAU: Correspondance sur racine française déclarée
// Si l'entrée a un champ racine_fr et que le mot commence par cette racine
else if (entry.racine_fr && word.startsWith(entry.racine_fr.toLowerCase())) {
score = 0.75;
}
Insertion dans la cascade de matching (après synonymes lemmatisés, avant le return) :
// Correspondance exacte sur le mot français
if (key === word || entry.mot_francais?.toLowerCase() === word) {
score = 1.0;
}
// Correspondance sur formes lemmatisées
else if (lemmas.some(lemma => key === lemma || entry.mot_francais?.toLowerCase() === lemma)) {
score = 0.95;
}
// Correspondance sur synonymes
else if (entry.synonymes_fr?.some(syn => syn.toLowerCase() === word)) {
score = 0.9;
}
// Correspondance sur synonymes lemmatisés
else if (entry.synonymes_fr?.some(syn => lemmas.includes(syn.toLowerCase()))) {
score = 0.85;
}
// NOUVEAU: Correspondance sur racine française déclarée
else if (entry.racine_fr && word.startsWith(entry.racine_fr.toLowerCase())) {
score = 0.75;
}
Code final de searchWord() :
function searchWord(word, dictionnaire) {
const results = [];
const lemmas = simpleLemmatize(word);
for (const [key, entry] of Object.entries(dictionnaire)) {
let score = 0;
// Correspondance exacte sur le mot français
if (key === word || entry.mot_francais?.toLowerCase() === word) {
score = 1.0;
}
// Correspondance sur formes lemmatisées
else if (lemmas.some(lemma => key === lemma || entry.mot_francais?.toLowerCase() === lemma)) {
score = 0.95;
}
// Correspondance sur synonymes
else if (entry.synonymes_fr?.some(syn => syn.toLowerCase() === word)) {
score = 0.9;
}
// Correspondance sur synonymes lemmatisés
else if (entry.synonymes_fr?.some(syn => lemmas.includes(syn.toLowerCase()))) {
score = 0.85;
}
// NOUVEAU: Correspondance sur racine française déclarée dans le lexique
else if (entry.racine_fr && word.startsWith(entry.racine_fr.toLowerCase())) {
score = 0.75;
}
if (score > 0) {
results.push({
mot_francais: entry.mot_francais || key,
traductions: entry.traductions || [],
score,
source: entry.source_files || []
});
}
}
return results;
}
Note : Pas besoin d'exporter de nouvelle fonction, on utilise simplement le champ racine_fr du lexique.
TRAVAIL 2 : Compléter le lexique avec racines et synonymes
Objectif : Pour chaque verbe du lexique, ajouter :
- Le champ
racine_fravec le dénominateur commun optimal - Les formes conjuguées irrégulières dans
synonymes_fr
Fichiers à modifier :
ancien-confluent/lexique/06-actions.json
Structure à suivre pour chaque verbe
"[verbe_infinitif]": {
"racine_fr": "[racine_optimale]",
"traductions": [...],
"synonymes_fr": ["[formes_irrégulières]", "[autres_synonymes]"]
}
Verbes à compléter (par priorité)
Priorité 1 : Verbes très courants
1. voir
"voir": {
"racine_fr": "voi",
"traductions": [...],
"synonymes_fr": ["observer", "regarder", "vu", "vus", "vue", "vues"]
}
Note : "voit", "voyait", "voyons", "verra" sont couverts par la racine "voi". Seules les formes avec radical différent (vu/vue) vont dans synonymes_fr.
2. prendre
"prendre": {
"racine_fr": "pren",
"traductions": [...],
"synonymes_fr": ["pris", "prit", "prise", "prises"]
}
Note : "prend", "prenait", "prendra" sont couverts par la racine "pren".
3. faire
"faire": {
"racine_fr": "fai",
"traductions": [...],
"synonymes_fr": ["créer", "fait", "faits", "faite", "faites"]
}
Note : "fais", "faisait", "faisons" sont couverts par la racine "fai".
4. manger (nouveau verbe si pas déjà présent)
"manger": {
"racine_fr": "mang",
"traductions": [...],
"synonymes_fr": []
}
Note : Toutes les formes (mange, mangé, mangeait, mangera) sont couvertes par la racine "mang". Pas besoin de synonymes.
5. aller
"aller": {
"racine_fr": "all",
"traductions": [...],
"synonymes_fr": ["va", "vas", "vais", "ira", "iras", "iront"]
}
Note : "allé", "allait" sont couverts par "all". Les formes irrégulières (va/vais, ira) vont dans synonymes_fr.
6. donner
"donner": {
"racine_fr": "donn",
"traductions": [...],
"synonymes_fr": []
}
Note : Toutes les formes sont couvertes par "donn".
7. dire
"dire": {
"racine_fr": "di",
"traductions": [...],
"synonymes_fr": ["parler", "dit", "dits", "dite", "dites"]
}
Note : "dis", "disait", "dira" sont couverts par "di". "dit/dite" ont un radical différent donc dans synonymes_fr.
Priorité 2 : Verbes auxiliaires (très irréguliers)
8. être
"être": {
"racine_fr": null,
"traductions": [...],
"synonymes_fr": ["est", "es", "suis", "sont", "sommes", "êtes", "été", "était", "étais", "étant", "sera", "seras", "seront", "fut", "fus"]
}
Note : Aucune racine commune possible. Toutes les formes dans synonymes_fr.
9. avoir
"avoir": {
"racine_fr": "av",
"traductions": [...],
"synonymes_fr": ["a", "as", "ai", "ont", "eu", "eue", "eus", "eues", "aura", "auras", "auront"]
}
Note : "avait", "avais", "avons", "avez" couverts par "av". Formes irrégulières (a/ai/ont, eu, aura) dans synonymes_fr.
Priorité 3 : Autres verbes courants
10. savoir
"savoir": {
"racine_fr": "sav",
"traductions": [...],
"synonymes_fr": ["connaître", "sait", "sais", "su", "sue", "sus", "sues", "saura", "sauront"]
}
Note : "savait", "savons", "sachant" couverts par "sav". Formes irrégulières (sait/sais, su, saura) dans synonymes_fr.
11. chasser
"chasser": {
"racine_fr": "chass",
"traductions": [...],
"synonymes_fr": ["traquer"]
}
Note : Toutes les conjugaisons couvertes par "chass".
12. transmettre
"transmettre": {
"racine_fr": "transm",
"traductions": [...],
"synonymes_fr": ["enseigner", "transmis", "transmise", "transmises"]
}
Note : "transmet", "transmettait", "transmettra" couverts par "transm". Participe passé (transmis) dans synonymes_fr.
13. garder
"garder": {
"racine_fr": "gard",
"traductions": [...],
"synonymes_fr": ["protéger"]
}
Note : Toutes les conjugaisons couvertes par "gard".
14. porter
"porter": {
"racine_fr": "port",
"traductions": [...],
"synonymes_fr": ["transporter"]
}
Note : Toutes les conjugaisons couvertes par "port".
15. apprendre
"apprendre": {
"racine_fr": "appren",
"traductions": [...],
"synonymes_fr": ["appris", "apprise", "apprises"]
}
Note : "apprend", "apprenait", "apprendra" couverts par "appren". Participe passé (appris) dans synonymes_fr.
Tests à faire après modifications
Test 1 : Vérifier les racines automatiques
curl -X POST http://localhost:3000/api/debug/prompt \
-H "Content-Type: application/json" \
-d '{"text": "Il mangeait le pain", "target": "ancien"}' | grep -A 5 "wordsFound"
Résultat attendu : "mangeait" → trouve "mukis" via racine "mang"
Test 2 : Vérifier les exceptions manuelles
curl -X POST http://localhost:3000/api/debug/prompt \
-H "Content-Type: application/json" \
-d '{"text": "Il a vu et pris", "target": "ancien"}' | grep -A 10 "wordsFound"
Résultat attendu :
- "vu" → trouve "mirak" via synonymes_fr
- "pris" → trouve "pasak" via synonymes_fr
Test 3 : Phrase complète
# Même test que le problématique
curl -X POST http://localhost:3000/api/debug/prompt \
-H "Content-Type: application/json" \
-d '{"text": "C est le collier du loup qui a mange mon frere", "target": "ancien"}' | python -c "import sys, json; data=json.load(sys.stdin); print('Found:', [w['input'] for w in data['metadata']['wordsFound']])"
Résultat attendu : ["collier", "loup", "mange", "frere"] tous trouvés
Estimation de travail
- Code (contextAnalyzer.js) : ~5 lignes à ajouter (une seule condition else if), 5 minutes
- Lexique (06-actions.json) :
- Ajouter champ
racine_frpour 15 verbes : ~15 minutes - Ajouter formes irrégulières dans
synonymes_fr: ~30 formes uniques, ~20 minutes
- Ajouter champ
Total : ~40 minutes de travail
Révision par rapport à l'estimation initiale : Beaucoup plus simple car on ne fait pas d'extraction automatique, juste du matching sur un champ déclaré.
Notes importantes
- Lowercase : Tout est déjà en lowercase dans tokenizeFrench(), pas besoin de le refaire dans le matching
- Normalisation des accents : Déjà fait en amont (mangé → mange dans la tokenization)
- Ordre de priorité matching :
- 1.0 = Exacte
- 0.95 = Lemma
- 0.9 = Synonyme exact
- 0.85 = Synonyme lemmatisé
- 0.75 = Racine française déclarée (NOUVEAU)
- Choix de la racine :
- Doit être le dénominateur commun le plus long qui couvre la majorité des conjugaisons
- Exemples : "mang" (4 lettres), "compar" (6 lettres), "transm" (6 lettres)
- Pas de règle fixe sur la longueur : décision au cas par cas
- Multiples matches :
- Si plusieurs entrées matchent (ex: "comp" et "compr"), TOUTES sont retournées au LLM
- Le LLM choisit selon le contexte de la phrase
- Pas de filtrage automatique
Résumé du système final
Workflow de matching pour un mot tapé par l'utilisateur
- L'utilisateur tape "comparé"
- Le système tokenize et met en lowercase → "compare" (accent normalisé)
- Le système cherche dans le lexique avec cette cascade :
- Score 1.0 : Match exact sur clé ou
mot_francais→ ❌ - Score 0.95 : Match sur lemme → ❌
- Score 0.9 : Match exact dans
synonymes_fr→ ❌ - Score 0.85 : Match lemme dans
synonymes_fr→ ❌ - Score 0.75 :
"compare".startsWith(entry.racine_fr)→ ✅ trouve "compar" dans l'entrée "comparer"
- Score 1.0 : Match exact sur clé ou
- Le système retourne l'entrée "comparer" avec score 0.75
- Le LLM utilise la traduction confluente
Avantages de cette approche
- ✅ Contrôle total : chaque racine est choisie manuellement pour être optimale
- ✅ Ambiguïté gérée : multiples matches possibles, le LLM arbitre
- ✅ Performance : pas de calcul complexe, juste du
startsWith() - ✅ Maintenance : facile d'ajuster une racine si elle ne convient pas
- ✅ Hybride intelligent : racines pour les formes régulières, synonymes pour les irrégulières