Recherche en texte intégral
La recherche Docsbook est un index de recherche en texte intégral PostgreSQL sur vos pages, accessible via un bouton de recherche dans l’en-tête et un champ de recherche dans la barre latérale. Elle ne coûte rien par requête — aucun modèle n’est appelé — et les requêtes qui n’ont renvoyé aucun résultat constituent l’un des deux signaux les plus utiles produits par votre documentation.
Ce que vous obtenez#
- Un champ de recherche dans l’en-tête, la barre latérale ou les deux. Les deux peuvent fonctionner simultanément ; un seul suffit généralement.
- Des résultats accompagnés d’un extrait mis en évidence — une fenêtre contextuelle extraite du texte de la page autour de la correspondance, et non des 200 premiers caractères de la page.
- Des liens directs vers le titre exact. Une correspondance située dans une section utilise l’ancre réelle de cette section, afin que le lecteur arrive directement au paragraphe plutôt qu’en haut d’une longue page.
- Les pages traduites en premier. Si le lecteur consulte une version localisée, la ligne traduite est prioritaire ; les pages non traduites apparaissent tout de même, de sorte qu’un site partiellement traduit reste entièrement consultable.
- Un enregistrement de chaque recherche n’ayant renvoyé aucun résultat, sous la forme d’un webhook
search.no_resultset d’un rapport des recherches infructueuses.
Comment l’index est-il construit ?#
Écriture directe lors du rendu, pas lors du push. Une page entre dans l’index lorsque Docsbook la rend — après l’envoi de la réponse, de sorte que l’indexation ne retarde jamais l’affichage de la page. Une page traduite est indexée de la même manière lorsque sa traduction est mise en cache. Il n’y a rien à reconstruire manuellement ni de bouton de réindexation.
La conséquence mérite d’être connue : une page que personne n’a ouverte depuis sa publication ne figure pas encore dans l’index. Un site tout neuf renvoie donc peu de résultats tant que ses pages n’ont pas été visitées une première fois. Tant qu’aucune ligne n’existe, le champ de recherche se rabat sur la correspondance avec les noms de fichiers de la liste des pages, afin de ne jamais rester sans résultat.
Contenu d’une ligne :
| Champ | Contenu |
|---|---|
| Titre | Le title du frontmatter, sinon le H1 de la page, sinon le nom du fichier |
| Corps | Le texte brut de la page : le frontmatter, le balisage des titres, les images, la syntaxe des liens, les blocs de code délimités et la ponctuation Markdown inline sont supprimés |
| Sections | Une entrée par h2/h3, contenant l’ancre rendue du titre et son texte, les blocs <pre> étant supprimés |
| Langue | Vide pour la page originale, le code de langue pour chaque traduction |
La recherche s’effectue sur un tsvector généré dans lequel le titre a un poids de A et le corps un poids de B — les libellés de poids de PostgreSQL existent afin que « les mots provenant de différentes parties d’un document [puissent être] pondérés différemment par les fonctions de classement » (PostgreSQL : fonctionnalités supplémentaires de recherche textuelle). Cette colonne est indexée avec GIN.
Comment une requête reçoit-elle une réponse ?#
- Les mots vides sont supprimés.
websearch_to_tsquery« combine les termes de texte non placés entre guillemets avec l’opérateur&(AND) » (PostgreSQL : contrôle de la recherche de texte), de sorte qu’une phrase complète exige que la page contienne également chaque mot de remplissage. Une liste anglaise de 45 mots vides est d’abord supprimée, uniquement en dehors des guillemets — ainsi,"exact phrase",ORet-wordcontinuent de fonctionner. - Les requêtes en anglais sont réduites à leur racine. Pour l’anglais et le contenu non traduit, la requête et le document sont comparés avec la configuration
english, qui utilise un stemmer Snowball qui « réduit les formes variantes courantes des mots à une orthographe de base, ou racine » (PostgreSQL : dictionnaires). Sans cela, une page qui contient « served » ne correspond pas à une requête contenant « serve ». - Les autres langues utilisent l’index stocké. Les paramètres régionaux non anglais sont comparés au vecteur
simplestocké, qui « fonctionne en convertissant le jeton d’entrée en minuscules » et n’effectue pas de recherche de racine. L’application de règles de réduction à la racine anglaises à du texte non latin produit des résultats incohérents ; elles ne sont donc volontairement pas appliquées. - Le chemin anglais normalise le classement en fonction de la longueur. Pour les requêtes en anglais,
ts_ranks’exécute avec l’indicateur de normalisation 1, qui « divise le classement par 1 + le logarithme de la longueur du document ». Sans cela, un journal des modifications de 76 Ko qui mentionne au passage tous les termes de la requête dépasserait dans le classement la page courte qui traite réellement de la question. Le chemin non anglais appellets_ranksans indicateur de normalisation ; dans ces paramètres régionaux, une page longue n’est donc pas pénalisée en raison de sa longueur — voir Limites. - Une ligne par page. L’original et la traduction sont regroupés en un seul résultat, en privilégiant la langue du lecteur, puis le classement. Une correspondance dans le titre est placée au-dessus des correspondances dans le corps uniquement.
- L’extrait est généré côté serveur.
ts_headlinerenvoie un fragment de 5 à 18 mots autour de la correspondance ; le client met de nouveau les termes en évidence.
Que se passe-t-il en cas de faute de frappe ?#
Rien ne correspond. La recherche Docsbook n'effectue aucune correspondance approximative, ne calcule aucune similarité par trigrammes et ne propose aucun mécanisme de secours fondé sur la distance d'édition. La racinisation couvre les flexions — serve trouve served — mais pas les fautes d'orthographe : documnetation ne trouve rien.
Il s'agit d'un compromis délibéré, et non d'un oubli, qui s'accompagne d'un mécanisme compensatoire : chaque requête sans résultat vous est signalée. Le signalement est regroupé afin qu'une recherche constitue un seul signal, et non un signal par touche saisie :
- Le navigateur attend 1,5 seconde après que le lecteur a cessé de saisir avant de signaler un échec, et envoie immédiatement le signalement s'il ferme la boîte de dialogue en pleine recherche — lors d'une mesure effectuée sur un espace de travail réel, un lecteur qui saisissait un mot marquait une pause de 0,9 à 1,3 s entre les caractères, ce qui produisait huit signalements pour un seul mot avant la mise en place de ce mécanisme.
- Le serveur supprime indépendamment un échec qui constitue une extension préfixée stricte du précédent, provenant du même lecteur et survenant dans un court intervalle, car le point d'accès est public et ne peut pas supposer qu'un client a appliqué une quelconque temporisation.
Ainsi, le fait que documnetation figure dans votre rapport des recherches infructueuses n'est pas un bogue de la recherche : c'est la recherche qui vous indique qu'un lecteur n'a pas pu trouver la page, et c'est précisément sur cela que vous devez agir. Les fautes récurrentes sont à corriger de préférence dans votre contenu, en nommant le terme que le lecteur saisit réellement.
Où se place la boîte de recherche#
| Emplacement | Idéal pour | Compromis |
|---|---|---|
| Bouton dans l’en-tête | Les visiteurs qui découvrent le site et regardent d’abord la barre supérieure | Entre en concurrence avec vos liens d’en-tête pour l’espace disponible |
| Boîte dans la barre latérale | Les lecteurs qui naviguent déjà à l’aide de l’arborescence | Masquée sur les écrans étroits lorsque la barre latérale est réduite |
Activez le bouton d’en-tête dans Float Widget → Conception → En-tête → Bouton de recherche. Activez la boîte de la barre latérale dans Float Widget → Conception → Barre latérale gauche → Rechercher dans la barre latérale. Les deux sont gratuits avec tous les forfaits.
Pourquoi cette méthode est la bonne (preuves)#
| Règle | Pourquoi cela fonctionne | Source |
|---|---|---|
| Conserver un index lexical même lorsqu’une recherche sémantique est disponible | Sur plus de 18 jeux de données de recherche, « BM25 est une base robuste » dans les configurations zero-shot, tandis que les moteurs de recherche denses « sont souvent moins performants » — votre corpus est hors domaine pour tout modèle d’embeddings, et les termes exacts sont ceux que saisissent les lecteurs techniques | Thakur et al., 2021 — BEIR |
| Supprimer les mots vides avant que la requête n’atteigne Postgres | websearch_to_tsquery applique un AND aux termes non cités, de sorte qu’un seul mot de remplissage absent de la page fait échouer toute la correspondance |
PostgreSQL : contrôle de la recherche textuelle |
| Raciner l’anglais, mais pas les autres langues | La configuration simple met uniquement les termes en minuscules ; les racineurs Snowball sont spécifiques à chaque langue et réduisent les variantes à une racine |
PostgreSQL : dictionnaires |
| Normaliser le classement en fonction de la longueur du document (parcours anglais) | L’indicateur 1 « divise le classement par 1 + le logarithme de la longueur du document », de sorte qu’une longue mention incidente ne puisse pas surpasser une courte page consacrée au sujet | PostgreSQL : contrôle de la recherche textuelle |
| Accorder plus de poids au titre qu’au corps | Les étiquettes de pondération permettent au classement de traiter différemment les mots provenant de différentes parties d’un document | PostgreSQL : fonctionnalités supplémentaires de recherche textuelle |
Le même index est l’un des deux moteurs de recherche utilisés par le chat IA — il n’y constitue pas non plus une solution de repli de moindre qualité.
Limites#
- Aucune tolérance aux fautes de frappe. Voir ci-dessus. Si une faute d’orthographe est importante pour votre public, ajoutez le terme à la page.
- Les blocs de code ne peuvent pas être recherchés. Le code délimité est supprimé avant l’indexation, et les blocs
<pre>sont retirés du texte des sections. Un lecteur recherchant le nom d’une fonction qui apparaît uniquement dans un exemple de code ne le trouvera pas. Point encore en suspens : l’ancienne documentation affirmait que les blocs de code étaient indexés et « classés en dessous du texte » ; l’indexeur les supprime entièrement. - La couverture dépend du trafic, pas de votre dépôt. Une page entre dans l’index lors de son premier rendu. Une page publiée mais jamais ouverte est absente des résultats de recherche jusqu’à ce que quelqu’un l’ouvre.
- Nous ne publions aucun chiffre de latence. Les requêtes en anglais calculent leur
tsvectorau moment de la requête au lieu de lire l’indexsimplestocké, ce qui signifie que le chemin anglais effectue davantage de travail par requête à mesure que le corpus s’agrandit. Nous n’avons publié aucun benchmark et nous n’en citerons pas tant que nous n’en aurons pas. - La normalisation de la longueur est limitée à l’anglais. Le chemin de requête anglais transmet à
ts_rankl’indicateur de normalisation 1 ; le chemin utilisé pour les autres paramètres régionaux appellets_ranksans aucun indicateur, ce qui signifie qu’aucune normalisation de la longueur n’est effectuée. Sur un site non anglophone, une page très longue peut donc être mieux classée qu’une page courte traitant plus précisément la requête. Point encore en suspens jusqu’à la réconciliation des deux chemins. - La recherche s’effectue par projet. L’index est limité à un seul espace de travail ; il n’existe aucune recherche interprojets.
- Le signal de recherche infructueuse est regroupé, et non exact. C’est le premier échec d’une chaîne de saisie qui est transmis à un webhook en direct ; la requête transmise peut donc être un préfixe plus court de celle finalement saisie par le lecteur. Le rapport historique récupère la requête finale.
Voir aussi#
- Chat IA — l’assistant qui utilise cet index comme l’un de ses mécanismes de récupération.
- Qualité des réponses — comment la récupération lexicale et vectorielle sont fusionnées.
- Analyses : ce que les lecteurs ont recherché — les requêtes qui n’ont renvoyé aucun résultat.
- Retour sur la page — l’autre signal indiquant qu’une page est manquante ou mal nommée.