Skip to content

Architecture — Guide de réglage de la recherche

Trois leviers principaux contrôlent la pertinence de la recherche. Tous ont une valeur par défaut (variable d'env) et peuvent être surchargés par requête (query param). Le but de cette page : savoir lequel toucher quand un résultat semble faux.

Les trois leviers

LevierEnv varQuery paramPlageDéfaut
Pondération RRFDEFAULT_RRF_VECTOR_WEIGHTrrfVectorWeight[0, 1]0.5
Plancher sémantiqueDEFAULT_SEMANTIC_FLOORsemanticFloor[0, 1]0.55
Plancher texteDEFAULT_TSVECTOR_FLOORtsvectorFloor≥ 00.05

Compose en dev (docker-compose.yml) utilise des valeurs un peu plus généreuses : 0.4 / 0.5 / 0.0 pour exposer plus de résultats pendant l'itération.

Voir architecture/hybrid-search.md pour la mécanique sous-jacente — cette page se concentre sur le « quel bouton tourner ».

Diagnostic rapide

                  ┌────────────────────────┐
                  │  Plainte utilisateur   │
                  └────────────┬───────────┘

            ┌──────────────────┼──────────────────┐
            ▼                  ▼                  ▼
   « Pas assez de       « Trop de bruit /    « Mauvais ordre »
     résultats »         hors sujet »
            │                  │                  │
            ▼                  ▼                  ▼
   Baisser les          Monter les          Ajuster
   floors               floors              rrfVectorWeight

Cas concrets

Cas 1 — « ski en Autriche » ne renvoie rien alors que le voyage Kitzbühel existe

Diagnostic : le terme « Autriche » ne figure peut-être pas dans name/subtitle/description (juste dans country.name). FTS échoue → text_score < tsvector_floor. La similarité sémantique pourrait sauver le coup, mais elle aussi est sous le seuil.

Solutions :

  1. Vérifier dans la réponse les champs semantic_score et tsvector_score (exposés via apply_info_density même en mode min).
  2. Pousser le poids vers le sémantique : ?rrfVectorWeight=0.8.
  3. Si toujours rien : baisser temporairement le plancher sémantique : ?semanticFloor=0.4.

Cas 2 — « Wengen » renvoie 50 résultats dont seulement 1 est le voyage cherché

Diagnostic : « Wengen » est un nom propre rare. Le sémantique tire des voyages « montagne / ski » alentour. Le texte trouve un match exact pour le voyage de Wengen.

Solutions :

  1. Pousser vers le texte : ?rrfVectorWeight=0.2.
  2. Monter le plancher sémantique : ?semanticFloor=0.7 (ne garde que les voyages très sémantiquement proches).
  3. La combinaison des deux donne typiquement le meilleur résultat sur ces requêtes nominatives.

Cas 3 — La requête « pleine d'expression évocatrice » remonte du bruit

Ex: « vacances ressourcement nature montagne », ?search=… renvoie 80 résultats peu pertinents.

Diagnostic : sémantique large → similarité ≥ 0.55 sur beaucoup de candidats. FTS aussi (chaque mot stem-match plein de descriptions).

Solutions :

  1. Monter ?semanticFloor=0.7 ou 0.75 pour ne garder que la cible sémantique précise.
  2. Monter ?tsvectorFloor=0.1 pour éliminer les matches FTS faiblards (1 mot vague qui matche).

Cas 4 — Le tri par pertinence semble incohérent entre deux pages

Diagnostic : le RRF dépend des rangs. Si vous filtrez (?destination=FR), les rangs sont calculés sur l'ensemble filtré, donc différents qu'en non filtré. Ce n'est pas un bug.

Solution : pas de solution applicative — c'est la nature du RRF. Si l'utilisateur attend un score absolu stable, exposer _semantic_score et _tsvector_score côté front (ils ne dépendent pas du filtre) et trier différemment.

Cas 5 — Le score n'est pas reproductible (les embeddings changent)

Diagnostic : l'API Infomaniak est déterministe sur une même requête, donc le score devrait être stable. Mais :

  • Si le record a été ré-embeddé entre deux requêtes (reindex), son vecteur peut avoir changé (texte source modifié côté Horizon).
  • Le rang dépend de tous les autres documents en base.

Solution : pour des tests de régression de pertinence, geler le dataset (snapshot) et la requête.

Réglages typiques

ProfilrrfVectorWeightsemanticFloortsvectorFloor
Défaut prod (équilibré)0.50.550.05
Dev (docker-compose.yml) — plus permissif0.40.50.0
« Mode SEO » (mot-clé exact)0.20.50.1
« Mode discovery » (requête vague, sémantique)0.80.60.0
Debug (tout voir)0.50.00.0

Méthodologie pour changer le défaut

Quand vous voulez modifier DEFAULT_* côté prod, ne pas y aller à l'aveugle :

  1. Reproduire : enregistrer 5-10 requêtes utilisateur réelles depuis les logs (voir modules/monitoring.md — les recherches non vides sont loggées par server.run_search).
  2. Tester localement : pour chaque requête, comparer la sortie avec l'ancien et le nouveau défaut. Inspecter _semantic_score, _tsvector_score, _relevance_score.
  3. A/B sur staging : déployer le nouveau défaut sur latest-staging, laisser tourner 24-48h, comparer les logs (n_results_in_search côté Prometheus).
  4. Rollback simple : revenir à l'ancienne valeur via une nouvelle release, ou (en urgence) modifier la var d'env du service et redémarrer.

Que faire si la pertinence reste insatisfaisante après réglage ?

  • Améliorer le texte embeddé. get_embedding_text() (src/_utils.py) concatène name + subtitle + description + servicesIncluded + highLights. Si une info pertinente n'est dans aucun de ces champs, elle ne fait pas partie du vecteur. Voir modules/embeddings.md.
  • Améliorer la pondération FTS. Les poids A/B/C sur name/subtitle/description sont définis dans la colonne générée search_vector (migrations/0001_baseline.sql lignes 49-53). Changer ça = migration + reindex complet.
  • Changer de modèle d'embedding. Voir architecture/adr/0003-bge-multilingual-gemma2.md pour les options. Migration coûteuse (3584D → autre dim, reindex complet).
  • Index vectoriel hnsw. Si le catalogue grossit massivement, un index hnsw sur embedding accélère le ORDER BY embedding <=> %s mais introduit une approximation. À ce jour, pas nécessaire.

Pièges fréquents

  • Confondre rrfVectorWeight et semanticFloor. Le premier décide qui domine dans le classement. Le second décide qui rentre. Augmenter rrfVectorWeight sans toucher semanticFloor peut quand même faire passer des résultats faibles.
  • Mettre semanticFloor=0 en prod. Ça désactive complètement le filtre sémantique. Vous afficherez des résultats avec _semantic_score ≈ 0.1 qui n'ont rien à voir. Toujours laisser au moins 0.3-0.5.
  • Croire que tsvectorFloor=0 veut dire « pas de filtre texte ». Ça veut dire « accepter même les matches très faibles ». Mais la branche FTS du SQL ajoute déjà AND search_vector @@ websearch_to_tsquery(...), donc un score 0 strict n'est jamais inclus côté texte — sauf via la sémantique.
  • Ajuster les seuils sans regarder les scores réels. ?infoDensity=card expose semantic_score et tsvector_score dans chaque record retourné. Toujours les vérifier avant de bouger un seuil.

Contributors

No contributors

Changelog

No recent changes