Retour à la liste des articles
21/07/2026Équipe produit CCT CRM

Query Rewriting + Hybrid Search + Reranking : faire passer la base de connaissances IA de « capable de chercher » à « capable de trouver juste »

La recherche vectorielle n'est qu'un point de départ. Nous avons mis en ligne sur CCTCRM trois techniques d'amélioration RAG — Query Rewriting, Hybrid Search (fusion RRF) et Reranking — pour pousser plus loin la précision de recherche de la base de connaissances par des moyens d'ingénierie. Cet article retrace les choix techniques, les détails d'implémentation et les pièges rencontrés.

RAGQuery RewritingHybrid SearchRerankingRRFrecherche vectorielleCCT CRMbase de connaissances IA
Partager cet article

Notre base de connaissances RAG est en ligne depuis six mois. Recherche vectorielle par Embedding en 768 dimensions, tri par similarité cosinus, couvrant neuf domaines métier : clients, commandes, produits, expéditions, production, stocks, finance, etc. La fonctionnalité fonctionne, les utilisateurs l'ont adoptée.

Mais entre « ça fonctionne » et « c'est vraiment utilisable », il y a un fossé.

Un commercial demande « combien de conteneurs des commandes envoyées aux États-Unis le mois dernier ne sont pas encore arrivés à port », le premier résultat renvoyé par le système est un document sur la politique tarifaire américaine. Sémantiquement c'est lié — les deux mentionnent « États-Unis » et « commandes ». Mais ce que l'utilisateur veut, ce sont des données d'expédition, pas la politique tarifaire.

Ce genre de décalage est extrêmement courant en recherche vectorielle. Le modèle d'Embedding saisit la similarité sémantique, pas la pertinence métier. Deux passages proches dans l'espace vectoriel ne sont pas nécessairement la même chose dans un contexte métier.

Nous avons passé trois semaines à ajouter une couche de traitement en amont et en aval de la recherche vectorielle, pour pousser la précision de recherche d'un cran. Trois techniques, un seul objectif : que ce qui est trouvé soit ce que l'utilisateur veut.

Pipeline d'amélioration RAG
Pipeline d'amélioration RAG : Query Rewriting → Hybrid Search (fusion RRF) → Reranking

D'où vient le problème

Avant de nous attaquer à l'optimisation, il faut d'abord identifier les trois défauts structurels du RAG naïf.

Premièrement, l'utilisateur parle en langage naturel, le moteur de recherche veut des mots-clés. Un commercial ne saisira pas « États-Unis commandes exportation nombre conteneurs statistiques expédition ». Il dira « combien de conteneurs des commandes envoyées aux États-Unis le mois dernier ne sont pas encore arrivés à port ». Une fois cette phrase passée dans le modèle d'Embedding, les mots de liaison comme « le mois dernier », « combien de », « ne sont pas encore arrivés à port » diluent le signal des entités principales. Dans l'espace vectoriel, cette phrase peut être très proche d'un document qui parle de « procédure de dédouanement américain » — parce que sémantiquement ça se ressemble — tout en étant éloignée des vraies données du tableau statistique d'expédition.

Deuxièmement, la recherche vectorielle est bonne pour « la ressemblance sémantique », mais mauvaise pour « la correspondance exacte ». Supposons qu'un document de la base de connaissances ait pour titre « Spécifications statistiques d'expédition des commandes vers les États-Unis ». La recherche vectorielle ne le mettra pas nécessairement en première position — parce que la distance sémantique entre ce titre et la question en langage naturel de l'utilisateur n'est peut-être pas la plus courte. Mais une recherche par mots-clés (MySQL LIKE ou BM25) le trouvera immédiatement. À l'inverse, la recherche par mots-clés ne comprend ni les synonymes ni les associations sémantiques. Les deux approches ont chacune leurs angles morts.

Troisièmement, pour les résultats récupérés, une similarité vectorielle élevée n'équivaut pas à une pertinence métier élevée. La similarité cosinus mesure l'alignement directionnel de deux passages dans l'espace vectoriel. Deux passages peuvent être alignés simplement parce qu'ils traitent du même sujet, alors que leur position, leur contexte et leur signification métier sont totalement différents. L'utilisateur demande « comment traiter les réclamations pour livraison en retard », le système renvoie un document sur « l'analyse des causes de retard de livraison » — similarité 0.87, mais à côté de la question.

Ces trois problèmes ne sont pas les défauts d'un modèle d'Embedding particulier, ce sont les lacunes structurelles de l'architecture RAG naïve. L'industrie dispose déjà de solutions éprouvées : Query Rewriting règle le problème côté entrée, Hybrid Search comble les angles morts côté recherche, Reranking corrige les biais côté tri. Ces trois techniques combinées forment un pipeline d'amélioration « réécriture → recherche hybride → reranking » $TRAE_REF.

Première couche : Query Rewriting — laisser le LLM aider l'utilisateur à bien formuler sa demande

L'idée est directe : avant d'envoyer la question de l'utilisateur dans la recherche vectorielle, on fait d'abord passer la question en langage naturel par un modèle léger (nous utilisons le groupe de modèles lite, au coût le plus bas) pour la réécrire en une combinaison de mots-clés plus adaptée à la recherche.

L'utilisateur dit : « combien de conteneurs des commandes envoyées aux États-Unis le mois dernier ne sont pas encore arrivés à port »

Après réécriture : « États-Unis commandes exportation conteneurs statistiques expédition mois dernier volume export »

Le texte réécrit est ensuite envoyé pour générer le vecteur d'Embedding. L'effet est double : d'une part on supprime les modificateurs en langage naturel comme « le mois dernier », « combien de », « ne sont pas encore arrivés à port » qui n'apportent rien à la recherche, d'autre part on ajoute des synonymes et termes associés comme « statistiques expédition », « volume export », ce qui élargit le rappel sémantique.

Côté implémentation, plusieurs choix de conception sont essentiels.

Le Prompt est le cœur du dispositif. La qualité de la réécriture dépend entièrement de la conception du system prompt. Notre prompt comporte cinq règles : extraire les entités principales (noms de personnes, produits, pays, types de documents), ajouter des synonymes et termes associés, supprimer les mots de liaison, sortir du texte brut sans explication, conserver la langue d'origine. En plus des règles, deux exemples few-shot sont fournis, couvrant les cas chinois et anglais. Sans few-shot, le modèle sort une format préfixé du type « requête réécrite : XXX », ce qui casse l'analyse en aval.

Les requêtes courtes ne sont pas réécrites. Les entrées de moins de 6 caractères contournent l'étape de réécriture. Ces requêtes sont généralement des mots-clés comme « client », « commande », où la réécriture n'apporte aucun gain et ajoute de la latence. Ce seuil est le point d'inflexion que nous avons observé en test : au-delà de 6 caractères, la réécriture apporte un gain positif, en deçà le gain n'est pas significatif.

Cache de 5 minutes. Une même requête ne déclenche pas un appel au modèle dans les 5 minutes. On utilise une ConcurrentHashMap pour stocker la correspondance hash → résultat réécrit, avec nettoyage automatique à l'expiration. La limite du cache est de 200 entrées, au-delà on nettoie les éléments expirés. Cette conception fait que les requêtes répétitives à haute fréquence (par exemple plusieurs commerciaux qui interrogent le même client en même temps) ne déclenchent pas à chaque fois un appel au modèle, ce qui maintient le coût dans une fourchette acceptable.

Dégradation en cas d'échec. Échec de l'appel au modèle, quota dépassé, timeout réseau — toute exception dégrade silencieusement en renvoyant la requête d'origine. La réécriture est un bonus, pas une nécessité. Il ne faut pas que l'indisponibilité du service de réécriture fasse tomber tout le pipeline RAG.

Deuxième couche : Hybrid Search — vecteur + mots-clés, fusionnés par RRF

Cette couche attaque le problème « recherche vectorielle et recherche par mots-clés ont chacune leurs angles morts ».

La solution consiste à lancer les deux recherches en parallèle, puis à fusionner les résultats. La recherche vectorielle gère le rappel sémantique — compréhension des synonymes, des concepts associés, des correspondances multilingues. La recherche par mots-clés gère la correspondance exacte — les mots bruts contenus dans les titres et les champs de mots-clés. Les deux flux de résultats sont fusionnés par l'algorithme Reciprocal Rank Fusion (RRF).

L'algorithme RRF

RRF est une méthode de rank fusion proposée en 2009 par Cormack et al. $TRAE_REF. L'idée centrale est extrêmement simple : on ne se préoccupe pas des scores bruts de chaque recherche (similarité vectorielle vs score BM25 ne sont pas comparables), on ne s'intéresse qu'au classement donné par chaque recherche.

Formule :

RRF_score(d) = Σ  1 / (k + rank_i(d))
             i∈lists

rank_i(d) est le rang du document d dans le i-ème résultat de recherche (à partir de 0), et k est une constante de lissage (nous utilisons 60).

Supposons que le document A soit classé 1er dans la recherche vectorielle et 5e dans la recherche par mots-clés :

RRF_score(A) = 1/(60+1) + 1/(60+6) = 0.0164 + 0.0152 = 0.0316

Le document B est classé 3e dans la recherche vectorielle et 2e dans la recherche par mots-clés :

RRF_score(B) = 1/(60+4) + 1/(60+3) = 0.0156 + 0.0159 = 0.0315

Les scores RRF des documents A et B sont presque identiques — même si A était bien plus haut que B dans la recherche vectorielle. C'est la caractéristique de RRF : il ne privilégie pas les écarts de scores absolus d'une recherche ou d'une autre, il ne regarde que la performance globale du classement. Un document qui figure en haut dans les deux recherches à la fois aura un score RRF élevé.

Schéma de fusion RRF
Fusion RRF : résultats de recherche vectorielle et par mots-clés fusionnés par rang

Implémentation

Notre classe RagEnhancementService implémente la logique de fusion RRF. Le flux est le suivant :

  1. La recherche vectorielle renvoie le top 20 des résultats (par similarité cosinus décroissante)
  2. La recherche par mots-clés renvoie le top 20 des résultats (MySQL LIKE sur les champs title/content/keywords)
  3. Les deux flux de résultats entrent dans la méthode reciprocalRankFusion, avec k=60
  4. Après fusion RRF, on trie par score décroissant et on prend le top K

L'implémentation de la recherche par mots-clés est très simple : on tokenise par espaces et ponctuation, on prend les tokens de longueur ≥ 2, et on fait une requête OR avec la condition LIKE de MyBatis-Plus. Nous n'avons pas utilisé l'index FULLTEXT de MySQL, parce que notre base de connaissances est de taille modeste (quelques milliers d'entrées) et que les performances de LIKE sont largement suffisantes. Si la base de connaissances grandit au-delà de plusieurs dizaines de milliers d'entrées, nous envisagerons une migration vers BM25 ou Elasticsearch.

Le choix de k dans RRF. Plus k est grand, plus l'effet des écarts de rang sur le score est lissé ; plus k est petit, plus l'avantage des documents bien classés est marqué. Nous utilisons 60, c'est la valeur par défaut dans l'article original de RRF et dans Elasticsearch $TRAE_REF. En test, k=60 s'est comporté de façon stable, pas besoin de le réajuster.

Un document qui apparaît dans les deux recherches aura un score RRF plus élevé qu'un document qui n'apparaît que dans une seule. C'est une propriété naturelle de RRF, et c'est exactement l'effet recherché — un document que les deux recherches jugent pertinent a toutes les chances d'être celui que l'utilisateur cherche.

Troisième couche : Reranking — laisser le LLM faire le dernier filtre

Hybrid Search a réglé le problème du « trouver ». Mais l'ordre dans lequel les résultats sont présentés reste déterminé par le score RRF — or ce score ne reflète que le classement, pas la pertinence métier.

Revenons à l'exemple précédent. L'utilisateur demande « comment traiter les réclamations pour livraison en retard ». Parmi les résultats de recherche, on peut avoir :

  • Document A : « Procédure de traitement des réclamations pour livraison en retard » (véritablement pertinent)
  • Document B : « Analyse des causes de retard de livraison » (sujet lié, mais à côté de la question)
  • Document C : « Statistiques 2024 du taux de livraison à temps » (lié aux données, mais mauvais contexte)

La recherche vectorielle peut mettre B devant A (parce que la formulation de B est plus proche de la question de l'utilisateur), la recherche par mots-clés peut mettre C devant (parce que le mot « livraison » compte plus de correspondances). Après fusion RRF, l'ordre peut encore être B > A > C.

Le Reranking consiste à ajouter un reranking LLM juste avant que les résultats fusionnés ne soient renvoyés. On envoie la requête et les documents candidats ensemble au modèle lite, pour qu'il juge quel document est le plus pertinent.

Méthode d'implémentation

Ce que nous demandons au LLM est une version simplifiée du tri pointwise. Concrètement :

On numérote les documents candidats (10 par défaut) et on les envoie avec la requête de l'utilisateur au modèle lite, le prompt demande au modèle de sortir la séquence des numéros par pertinence décroissante. Par exemple, avec 10 documents en entrée, le modèle sort « 2,0,5,1,3,8,4,7,6,9 », ce qui signifie que le document n° 2 est le plus pertinent et le n° 9 le moins pertinent.

Nous avons envisagé d'utiliser un modèle Cross-Encoder pour le reranking — par exemple le BGE-Reranker-v2-m3 de l'Institut Z.ai $TRAE_REF. Un Cross-Encoder concatène la requête et le document en une seule séquence d'entrée, fait un encodage conjoint et sort un score de pertinence entre 0 et 1. La précision est effectivement plus élevée qu'un tri par prompt LLM.

Mais nous avons finalement retenu la solution LLM, pour des raisons de coût de déploiement. Un Cross-Encoder suppose le déploiement d'un service de modèle supplémentaire (BGE-Reranker-v2-m3 fait environ 2.8 Go $TRAE_REF), alors que nous utilisons déjà le modèle lite pour le Query Rewriting, et le Reranking réutilise la même API modèle, à coût de déploiement supplémentaire nul. Pour la taille de notre base de connaissances (quelques milliers de documents), la précision du reranking LLM est suffisante. Si la base s'étend à plusieurs dizaines de milliers d'entrées ou que la précision exigée augmente, on pourra basculer vers un Cross-Encoder — l'interface est déjà abstraite, il suffira de remplacer la classe d'implémentation.

Contrôle du nombre de candidats. Le Reranking ne traite par défaut que les 10 premiers candidats. Envoyer trop de documents au modèle allonge le prompt, augmente la latence et fait chuter la précision (l'attention du modèle se dilue). 10 est le point d'équilibre entre latence et couverture.

Stratégie de dégradation. Comme pour le Query Rewriting, en cas d'échec de l'appel au modèle on dégrade silencieusement et on renvoie l'ordre brut après fusion RRF. Le Reranking est une optimisation de précision, pas une dépendance fonctionnelle.

Conception de l'architecture : couche SDK + interface SPI

L'implémentation des trois techniques d'amélioration n'a pas été placée dans le système métier cct-crm-backend, mais a été descendue dans notre bibliothèque cœur ai-sdk. La raison est que ces capacités ne sont pas spécifiques au commerce international — tout système utilisant RAG peut les réutiliser.

L'architecture est en trois couches :

┌─────────────────────────────────────────┐
│  cct-crm-backend (宿主系统)              │
│  RagEnhancementProviderImpl              │
│  → callLiteModel (HTTP 调用 LLM API)     │
│  → keywordSearch (MySQL LIKE 检索)        │
│  → getConfig (读取云端配置)               │
└──────────────┬──────────────────────────┘
               │ implements SPI
┌──────────────▼──────────────────────────┐
│  ai-sdk-core (SDK 核心层)               │
│  RagEnhancementService                  │
│  → rewriteQuery (Prompt 构建 + 缓存)     │
│  → reciprocalRankFusion (RRF 算法)      │
│  → hybridSearch (编排: 调 Provider + RRF)│
│  → rerankResults (Prompt 构建 + 解析)     │
│  → RagSearchResult (数据类)              │
└──────────────┬──────────────────────────┘
               │ uses
┌──────────────▼──────────────────────────┐
│  RagEnhancementProvider (SPI 接口)      │
│  → callLiteModel                        │
│  → keywordSearch                        │
│  → getConfig                            │
└─────────────────────────────────────────┘

Les algorithmes cœur (RRF, construction du Prompt, analyse des résultats) sont dans la couche SDK, sans aucune dépendance Spring. Le système hôte n'a qu'à implémenter l'interface SPI et à fournir trois capacités : appel LLM, recherche par mots-clés et lecture de configuration. L'avantage de cette conception est que, si d'autres systèmes s'intègrent à l'ai-sdk à l'avenir, ils n'auront qu'à implémenter l'interface Provider pour bénéficier de la capacité complète d'amélioration RAG, sans développement redondant.

Tous les commutateurs de configuration sont en base de données. Cinq items de configuration sont stockés dans la table cloud pb_ai_config, avec model_group = 'rag_enhancement' :

Clé de configuration Valeur par défaut Description
rag_query_rewrite_enabled true Commutateur Query Rewriting
rag_hybrid_search_enabled true Commutateur Hybrid Search
rag_reranking_enabled true Commutateur Reranking
rag_hybrid_candidate_count 20 Nombre de rappels de la recherche par mots-clés
rag_rerank_candidate_count 10 Nombre de candidats pour le Reranking

La configuration est rafraîchie toutes les 5 minutes, une modification en base prend effet immédiatement (avec un délai maximal de 5 minutes). Chaque fonction d'amélioration a son propre commutateur et peut être désactivée séparément. Si une fonction se comporte mal dans un scénario particulier ou qu'il faut maîtriser les coûts, on peut ne couper qu'elle sans toucher aux deux autres.

Résultats et coûts

Après la mise en ligne des trois fonctions d'amélioration, voici ce que nous avons observé en test interne :

Précision de recherche. Nous avons préparé 50 jeux de requêtes de test (couvrant cinq scénarios : requêtes client, requêtes commande, requêtes produit, requêtes expédition, requêtes politique), pour comparer les taux de réussite top-1 et top-3 avant et après amélioration. Avant amélioration, le taux de réussite top-1 était d'environ 68 %, après amélioration d'environ 82 %, soit un gain de 14 points. Le taux de réussite top-3 est passé de 85 % à 94 %. Ces chiffres proviennent de tests internes, ce n'est pas une évaluation académique rigoureuse, mais la tendance est nette.

Comparaison de la précision de recherche
Précision de recherche avant vs après amélioration (50 requêtes de test internes)

Surplus de latence. Avec les trois couches d'amélioration activées, la latence d'une recherche passe de ~200 ms à ~800 ms-1.2 s (essentiellement à cause des deux appels LLM). Pour un scénario de recherche en base de connaissances, cette latence reste acceptable — un utilisateur qui attend 1 seconde pour un résultat plus pertinent a une meilleure expérience qu'un utilisateur qui attend 200 ms pour un résultat hors sujet. Si la latence devient sensible, on peut désactiver le Reranking (qui représente environ 60 % du surplus de latence) et ne garder que Query Rewriting + Hybrid Search, le surplus de latence retombe à environ 300 ms.

Coût. Query Rewriting et Reranking utilisent tous deux le modèle lite, un appel consomme environ 200-500 tokens. Selon notre tarif d'API, une recherche améliorée complète coûte environ 0.001-0.002 yuan. Avec le cache de 5 minutes, les requêtes répétitives à haute fréquence ne déclenchent pas d'appels répétés au modèle, le coût réel est encore plus bas.

Pistes futures

Une fois les trois techniques d'amélioration en production, la précision du RAG a effectivement progressé. Mais le plafond du RAG naïf ne s'arrête pas là. En planifiant la suite, nous avons identifié plusieurs pistes dignes d'attention.

Remplacer le Reranking LLM par un Cross-Encoder. Le choix actuel du modèle lite pour le Reranking est un compromis dicté par le coût. Si l'on déploie un modèle de reranking dédié comme BGE-Reranker-v2-m3, la précision peut encore grimper d'un cran $TRAE_REF. L'avantage du Cross-Encoder est qu'il encode conjointement la requête et le document, saisit les relations fines entre les deux textes, alors que le LLM ne fait qu'un tri par prompt. L'interface est déjà abstraite, le bascule est un problème de charge de travail, pas d'architecture.

Context Engineering. À partir de 2025, l'industrie commence à évoluer du RAG vers le Context Engineering $TRAE_REF. Le RAG s'intéresse à « ce qui est récupéré », le Context Engineering s'intéresse à « comment construire le contexte envoyé au modèle ». Au-delà des résultats de recherche, il faut intégrer le rôle de l'utilisateur, les données temps réel, l'historique de conversation, les périmètres de permissions et d'autres dimensions, pour que le modèle reçoive non pas une liste de documents, mais une instruction de travail structurée et contextualisée. Notre prototype de modèle causal (apprentissage d'expérience + routage de modèle) fait déjà du Context Engineering — injecter l'expérience dans le system prompt, c'est déjà de la construction de contexte. L'étape suivante est de fusionner plus profondément les résultats de recherche RAG avec l'injection d'expérience et le routage de modèle.

Multimodal RAG. Le commerce international contient beaucoup de connaissances visuelles — photos produits, scans de rapports qualité, photos de listes de colisage $TRAE_REF. Le RAG actuel ne traite que le texte, ces informations visuelles sont soit saisies manuellement sous forme de description textuelle (perte d'information), soit totalement impossibles à retrouver par l'IA. Le Multimodal RAG utilise un modèle de langage visuel pour calculer directement l'Embedding des images, pour que l'IA puisse « comprendre » les photos produits et les retrouver. Cela apporte beaucoup à notre base de connaissances produit et à notre module qualité.

Agentic RAG. Le RAG actuel est monocycle — l'utilisateur pose une question, le système cherche une fois. L'Agentic RAG laisse l'IA décider elle-même s'il faut faire plusieurs recherches, des recherches inter-domaines, ou des recherches de relance à partir des premiers résultats $TRAE_REF. Par exemple, si l'utilisateur demande « comment se comparent les tendances d'achat de ce client sur les trois dernières années à la moyenne du secteur », l'IA peut avoir besoin de d'abord interroger les données de commande du client, puis les données de référence du secteur, puis faire elle-même l'analyse comparative. Cela suppose une architecture Agent, que nous n'avons pas encore déployée, mais c'est une direction claire — quand la recherche monocycle ne suffit plus pour les requêtes complexes, l'Agentic RAG est l'évolution naturelle.

Conclusion

Trois techniques, un seul objectif : faire passer la recherche vectorielle de « capable de chercher » à « capable de trouver juste ».

Query Rewriting règle le problème côté entrée — le fossé entre la question en langage naturel de l'utilisateur et l'entrée par mots-clés qu'attend le moteur de recherche. Hybrid Search comble les angles morts côté recherche — la recherche vectorielle et la recherche par mots-clés ont chacune leurs lacunes, la fusion RRF fait que les deux flux se complètent. Reranking corrige les biais côté tri — une similarité vectorielle élevée n'équivaut pas à une pertinence métier élevée, le LLM fait le dernier filtre sémantique.

Une fois les trois couches empilées, la précision de recherche passe de 68 % à 82 % (taux de réussite top-1), le coût d'une recherche est d'environ 0.001-0.002 yuan, et le surplus de latence reste maîtrisé. Les trois fonctions ont chacune un commutateur indépendant, on peut les activer ou les désactiver au besoin.

Ce pipeline d'amélioration n'est pas une fin en soi. La prochaine étape du RAG, c'est le Context Engineering — passer de « récupérer des documents » à « construire du contexte », de « renvoyer des résultats » à « livrer des réponses ». Nous sommes déjà en route.

Partager cet article

Comments

No comments yet. Be the first!