Un produit arabophone construit par une équipe compétente échoue quand même là où son équivalent anglais ne bronche pas. Un utilisateur cherche الإسلام et n'obtient rien, alors que le document porte الاسلام. Un pipeline RAG ne retrouve aucun passage pour une question dont la réponse se trouve au deuxième paragraphe. La facture LLM du tenant arabe dépasse de 40% celle du tenant anglais à trafic égal. Une requête WHERE name = ? échoue sur un nom que l'utilisateur a pourtant correctement saisi.
Cela ressemble à quatre bugs distincts. C'est un seul, et il vit dans une couche que la plupart des équipes n'écrivent jamais : la normalisation du texte.
Le problème : un mot, plusieurs séquences d'octets
L'orthographe arabe fait que plusieurs points de code Unicode distincts s'affichent de façon identique ou quasi identique, et les locuteurs natifs les emploient indifféremment. Le mot « islam » s'écrit légitimement الإسلام (hamza sous alef, U+0625) ou الاسلام (alef nu, U+0627). Les deux sont corrects. Les deux sont fréquents. Pour une base de données, ce sont deux chaînes différentes.
Les principaux coupables :
| Catégorie | Points de code | Problème |
|---|---|---|
| Variantes de alef | ا أ إ آ ٱ (U+0627, 0623, 0625, 0622, 0671) | Employées indifféremment en pratique |
| Ta marbuta / ha | ة et ه (U+0629, 0647) | Souvent saisies à l'identique, surtout dans le Golfe |
| Alef maqsura / ya | ى et ي (U+0649, 064A) | Les claviers égyptiens les intervertissent couramment |
| Harakat (diacritiques) | U+064B–U+0652, U+0670 | Poids invisible : présents dans certaines sources, absents ailleurs |
| Tatweel | ـ (U+0640) | Pure décoration : مـــرحبا égale مرحبا |
| Chiffres arabo-indiens | ٠١٢٣ (U+0660–0669), ۰۱۲۳ (U+06F0–06F9) | Deux jeux complets, en plus des chiffres occidentaux |
| Formes de présentation | U+FB50–FDFF, U+FE70–FEFF | Sorties PDF et OCR héritées : s'affichent juste, comparent faux |
| Contrôles invisibles | ZWJ, ZWNJ, RLM, LRM, isolants bidi | Artefacts de copier-coller que personne ne voit |
Les deux dernières lignes provoquent la session de débogage à trois heures du matin. Le texte extrait d'un PDF arrive souvent en Arabic Presentation Forms-B, où chaque glyphe est encodé dans sa forme positionnelle plutôt que par sa lettre de base. Il s'affiche correctement à l'écran et ne correspond à rien.
Le pipeline de normalisation
Le correctif est une fonction pure appliquée à exactement deux endroits : à l'indexation et à la requête. Si ces deux points divergent un jour, vous revenez au point de départ.
L'ordre compte. Voici la séquence sur laquelle la littérature a convergé, celle qu'utilise AraToken, un article de 2026 sur la tokenisation arabe pour Qwen3 :
const HARAKAT = /[\u064B-\u065F\u0670]/g; // fathatan..alef suscrit
const TATWEEL = /\u0640/g; // kashida
const INVISIBLE = /[\u200B-\u200F\u202A-\u202E\u2066-\u2069]/g;
const LETTER_MAP: Record<string, string> = {
'أ': 'ا', 'إ': 'ا', 'آ': 'ا', 'ٱ': 'ا', // variantes de alef
'ى': 'ي', // alef maqsura
'ة': 'ه', // ta marbuta
'ؤ': 'و', 'ئ': 'ي', // supports de hamza
};
export function normalizeArabic(input: string): string {
return input
.normalize('NFKC') // 1. replier les formes de présentation
.replace(INVISIBLE, '') // 2. retirer ZWJ/ZWNJ/contrôles bidi
.replace(TATWEEL, '') // 3. retirer la kashida
.replace(HARAKAT, '') // 4. retirer les diacritiques
.replace(/[\u0623\u0625\u0622\u0671\u0649\u0629\u0624\u0626]/g, c => LETTER_MAP[c]) // 5. unifier les lettres
.replace(/[\u0660-\u0669]/g, d => // 6. chiffres arabo-indiens
String.fromCharCode(d.charCodeAt(0) - 0x0660 + 48))
.replace(/[\u06F0-\u06F9]/g, d => // 7. chiffres arabo-indiens étendus
String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 48))
.replace(/\s+/g, ' ')
.trim();
}L'étape 1 est celle que l'on saute, et c'est la plus importante. NFKC replie tout le bloc des formes de présentation vers les lettres de base en un seul appel — cela seul répare l'essentiel des dégâts PDF et OCR avant même que vos propres règles ne s'exécutent. Attention : NFKC est délibérément destructif, c'est une normalisation de compatibilité, exactement ce qu'il faut pour une clé de recherche et exactement ce qu'il ne faut pas appliquer à du texte destiné à l'affichage.
Deux colonnes, jamais une
La règle cardinale : normalisez pour comparer, jamais au détriment de l'original stocké.
ALTER TABLE documents
ADD COLUMN title_norm text
GENERATED ALWAYS AS (arabic_normalize(title)) STORED;
CREATE INDEX documents_title_norm_trgm
ON documents USING gin (title_norm gin_trgm_ops);L'utilisateur voit title. La requête frappe title_norm. Les diacritiques d'un verset coranique, la hamza exacte choisie par un poète, l'orthographe d'un document juridique : tout est préservé, tout reste cherchable. Détruire l'original pour faire fonctionner la recherche est un compromis que personne ne vous demande.
Sous PostgreSQL, arabic_normalize s'implémente directement avec translate() et regexp_replace(), ou via une extension Rust ou PL/Python. Sous Elasticsearch, l'équivalent est une chaîne d'analyse : icu_normalizer (nfkc_cf), puis arabic_normalization, puis decimal_digit, puis éventuellement arabic_stem. L'analyseur arabic intégré couvre une partie du travail, mais pas le repli des formes de présentation : ajoutez icu_normalizer en amont.
Pourquoi votre RAG en dépend
Les modèles d'embedding sont plus tolérants que la correspondance exacte, mais pas assez. L'échec de la recherche vectorielle en arabe n'est généralement pas un problème de qualité de modèle : c'est un problème de découpage et de normalisation qui se manifeste comme un problème de qualité de modèle.
Trois choses cassent spécifiquement :
Les frontières de chunks. Découper sur . et ? ignore ؟ (U+061F) et ، (U+060C). La prose arabe utilise en outre le و de coordination bien plus librement que l'anglais n'utilise « and », si bien que les heuristiques calibrées sur l'anglais produisent des chunks très déséquilibrés. Découpez explicitement sur la ponctuation arabe.
La dérive des embeddings. La même phrase avec et sans harakat produit des vecteurs mesurablement différents dans la plupart des modèles multilingues. Si votre corpus mélange sources vocalisées et non vocalisées — courant dès que l'on combine textes classiques et contenu web moderne — vous plongez deux dialectes de la même langue dans deux régions distinctes de l'espace. Normalisez avant l'embedding, et appliquez la même normalisation à la requête.
L'asymétrie de la recherche hybride. La branche BM25 d'un retriever hybride fonctionne par correspondance exacte : elle est donc pleinement exposée à toutes les variantes ci-dessus. Les équipes passent des semaines à régler la branche dense pendant que la branche creuse contribue silencieusement à un rappel proche de zéro en arabe.
Un diagnostic utile : prenez 50 requêtes dont vous savez qu'elles doivent aboutir, exécutez-les brutes puis normalisées, et comparez le recall@10. Si la normalisation seule déplace ce chiffre de plus de quelques points, la qualité du retrieval n'a jamais été votre goulot d'étranglement.
L'angle du coût en tokens
L'arabe coûte cher sur les API de LLM, et la raison est structurelle. Sur les tokenizers natifs des modèles, l'arabe tourne autour de 2,4 tokens par mot contre 1,5 à 1,6 pour l'anglais. Le BPE byte-level de Qwen2.5 encode l'arabe à 2,18 tokens par mot, soit environ 1,4 fois son taux anglais. Ce ratio se propage partout : coût du prompt, time-to-first-token, taille du cache KV, et nombre de sessions arabes concurrentes qu'un budget GPU fixe peut absorber.
La normalisation ne résout pas ce problème, mais elle en récupère une part réelle. Les travaux AraToken rapportent une fertilité qui passe de 1,311 à 1,199 token par mot avec un pipeline de normalisation placé devant un tokenizer SentencePiece — une réduction de 8,5%, qui s'élargit à environ 18% entre la meilleure et la pire configuration testée. Le mécanisme est simple : chaque tatweel égaré, chaque diacritique orpheline et chaque caractère de forme de présentation est un token que vous avez payé et dont le modèle n'a rien tiré.
Concrètement, pour un système RAG qui injecte 8 000 tokens de contexte arabe par requête à l'échelle, 8% de réduction représentent 8% du plus gros poste de la facture, sans changer de modèle et sans perte de qualité.
Deux remarques pratiques. D'abord, normalisez le contexte mais soyez prudent avec le prompt de l'utilisateur : s'il a écrit des diacritiques délibérément, les supprimer modifie sa question. Ensuite, si vous faites du fine-tuning ou du pré-entraînement continu en arabe, normalisez le corpus d'entraînement et le chemin d'inférence avec la même fonction, sous peine d'entraîner sur une distribution et de servir l'autre.
Ce qu'il faut faire cette semaine
- Écrivez une seule fonction
normalizeArabic. Un fichier, aucune dépendance, exportée depuis un paquet partagé. Chaque service importe la même. C'est toute l'architecture. - Ajoutez une colonne ou un champ normalisé à côté de chaque champ texte arabe que vous interrogez : colonne générée sous Postgres, sous-champ sous Elasticsearch. Indexez celui-là.
- Normalisez la requête avec la fonction identique. Le bug de production le plus fréquent en recherche arabe consiste à normaliser l'index et à oublier la requête.
- Écrivez la table de tests.
الإسلامcorrespond àالاسلام.مـــرحباàمرحبا.مُحَمَّدàمحمد.فاطمهàفاطمة.٢٠٢٦à2026. Une entrée en formes de présentation correspond à une entrée en formes de base. Six assertions attrapent presque tout. - Mesurez le rappel avant et après sur de vraies requêtes. Vous aurez besoin du chiffre pour justifier le travail, et il est généralement plus élevé qu'on ne l'imagine.
Rien de tout cela ne relève de la recherche. Ce sont cent lignes de code sans gloire que la plupart des produits arabophones n'ont tout simplement jamais écrites — d'où le fait que « la recherche arabe est mauvaise » se lise comme une fatalité de la langue, alors qu'il s'agit d'une couche non implémentée.
Si vous construisez des produits arabes en priorité, les pièces connexes méritent aussi la lecture : pourquoi les lettres arabes changent de forme explique le comportement de liaison derrière les formes de présentation, harakat et tashkeel couvre ce que vous supprimez et quand il ne faut pas le faire, et les erreurs courantes des sites arabes RTL traite le versant affichage du même problème.