Passer au contenu
megapromotingDiscutons

Expertise · Bases de connaissances et recherche sémantique

Nous construisons le corpus à partir duquel un assistant est autorisé à répondre, avec des règles sur ce qui y entre, comment il est découpé, comment il est recherché et ce qui se passe quand la source change.

Un assistant qui répond aux clients n’est exactement aussi bon que le corpus dans lequel il lit. Nous nous occupons du corpus : extraction depuis les documents et le site, découpage en fragments, indexation, recherche, gate d’approbation humaine et actualisation. Cela inclut la réponse à la question de quand la recherche sémantique ne vaut PAS la peine.

Déjà construitExistă implementări proprii pentru fiecare etapă, dar starea lor diferă și trebuie spusă pe față. Ce rulează cu vectori proprii: memoria unui board intern folosit zilnic, cu coloană `vector(1536)`, index `ivfflat` pe distanță cosinus, prag de similaritate 0,35 și o buclă care re-indexează la 30 de minute doar ce s-a schimbat. Ce rulează cu vectori la furnizor: baza de cunoștințe a agenților vocali, sincronizată către motorul de regăsire al furnizorului cu fragmente de 500 și suprapunere de 100. Ce rulează în producție **fără** vectori: baza de cunoștințe a platformei de asistenți text — extragere, împărțire, configurare per asistent și regăsire hibridă sunt scrise și funcționează, dar calea de indexare scrie coloana de vector cu `null`, deci scorul hibrid degradează la lexical, iar codul își spune singur asta prin eticheta `keyword-only`. Ce e scris și încă nu e pornit în producție: pipeline-ul propriu pe `pgvector` al platformei vocale, a cărui migrare poartă în antet mențiunea „LOCAL ONLY — do not push to prod”. Un serviciu se vinde cu harta asta pe masă, nu fără ea.

Un assistant qui parle aux clients ne « sait » rien. À chaque question, quelqu’un cherche au préalable quelques fragments de texte et les lui met sous les yeux, puis il formule la réponse à partir d’eux. La qualité de la réponse se décide presque entièrement à cette étape de recherche, pas dans le choix du modèle. C’est pourquoi ce service concerne le corpus et la recherche, pas le modèle.

Le travail comporte quatre parties qui se dégradent indépendamment les unes des autres. L’extraction : à partir de quels fichiers et de quelles pages nous extrayons un texte utilisable, et que faisons-nous quand nous ne pouvons pas. Le découpage : quelle taille ont les fragments, quel chevauchement ils ont, où l’on coupe une phrase. La recherche : comment les fragments sont choisis pour une question donnée, combien, et selon quel critère. Le rafraîchissement : ce qui se passe quand le document source est modifié. Un système peut être impeccable sur trois d’entre elles et inutile à cause de la quatrième.

Il existe une étape supplémentaire, que beaucoup sautent et qui change complètement le résultat : la porte d’approbation. Un corpus qui grandit tout seul, à partir des conversations, finit vite par contenir des réponses que personne n’a validées. La boucle que nous avons construite fonctionne à l’inverse : quand l’agent dit « je n’ai pas cette information », la question est enregistrée avec l’état « en attente », une personne reçoit une notification et répond, puis sa réponse passe à l’état « approuvé » et n’entre alors qu’à ce moment dans le corpus. La connaissance grandit à partir de manques réels, avec une personne dans la chaîne.

Et la partie impopulaire : la recherche sémantique n’est pas toujours le bon choix. Pour une plateforme publique propre, nous l’avons volontairement refusée. Le corpus est le texte même de la plateforme, découpé en courts passages qui portent chacun la page d’où ils viennent, ponctués par un chevauchement de mots. Sans vecteurs, sans dépôt vectoriel, sans requête réseau supplémentaire — et, plus important encore, sans aucune source en dehors du dépôt. L’assistant ne peut que répéter ce qui est déjà écrit sur le site, et toute réponse se vérifie dans une page écrite par une personne. C’est tout le mécanisme anti-invention.

Ce que cela comprend

Le travail, par composantes

Nous extrayons le texte et disons clairement quand nous ne pouvons pas

À partir de texte simple, markdown, journaux, CSV, JSON, DOCX et PDF. Le DOCX est décompressé comme une archive et le document XML interne est lu ; le PDF passe par l’extracteur standard en conservant la mise en page, et s’il manque, il existe un recours qui lit directement les opérateurs de texte du fichier. Le cas important est celui qui ne fonctionne pas : un PDF scanné, qui est une image, ne contient pas de texte sélectionnable, et le système renvoie un message explicite — chargez un DOCX ou du texte, ou passez le fichier par reconnaissance optique d’abord. La reconnaissance optique n’est pas implémentée et nous ne faisons pas semblant qu’elle l’est.

Nous découpons en fragments avec chevauchement, en coupant entre les phrases, pas à l’intérieur

Les valeurs par défaut que nous utilisons : 500 unités lexicales avec 50 de chevauchement pour le pipeline vocal, 1.200 caractères avec 150 de chevauchement pour le texte ; du côté du fournisseur vocal, 500 avec 100. Le chevauchement existe parce qu’une phrase coupée au milieu entre deux fragments devient inutilisable pour les deux. Le séparateur de phrases est écrit pour les langues avec lesquelles nous travaillons : il reconnaît la fin de phrase suivie d’une majuscule latine **ou cyrillique**, y compris les diacritiques roumains. Un séparateur écrit pour l’anglais coupe mal en roumain et encore plus mal en russe.

Nous indexons avec des vecteurs là où nous l’avons lancé, et disons où non

Le modèle de représentation par défaut est `text-embedding-3-small`, avec 1.536 dimensions, demandé via notre porte de modèles, pas directement au fournisseur — ainsi, le fournisseur peut changer sans toucher au code de la base de connaissances. Dans la base, la colonne est de type vector avec un index `ivfflat` sur la distance cosinus. Il existe une stratégie de secours écrite dans la migration : si l’extension vectorielle n’est pas disponible, le schéma peut à tout moment être créé avec un index trigram sur le texte, et la recherche bascule vers la similarité lexicale. Ce n’est pas hypothétique — cela a été écrit précisément parce que les droits sur l’extension ne sont pas garantis sur n’importe quelle installation.

Recherche hybride, avec les pondérations visibles

Le score final est de 0,7 pour la similarité vectorielle plus 0,3 pour la correspondance des mots, avec la partie lexicale normalisée. Un ensemble de candidats est noté et les premiers fragments sont retenus, et le contexte envoyé au modèle tronque chaque fragment à une longueur maximale, afin qu’un seul long document ne prenne pas toute la place. La configuration est par assistant, à la base : recherche activée ou désactivée, nombre maximal de fragments par question (par défaut 5, limité entre 1 et 20), seuil de similarité, type de recherche — sémantique, lexicale ou hybride — et pondération de la partie lexicale.

La porte d’approbation : la connaissance grandit à partir des manques, avec un humain dans la boucle

Lorsque l’agent répond qu’il n’a pas l’information, la question est inscrite dans un tableau avec le statut « en attente », marqué par le canal d’où elle est venue — voix ou widget. Un humain reçoit la notification et répond directement au message ; un webhook récupère la réponse, fait passer l’entrée au statut « approuvé », puis elle entre de là dans le contexte de l’agent. Les statuts possibles sont quatre : en attente, approuvé, ignoré, dupliqué — et les doublons sont liés à la question originale par une référence, afin que dix formulations de la même lacune ne s’accumulent pas. La livraison à l’agent se fait une seule fois par entrée approuvée.

Actualisation : que se passe-t-il lorsque la source change

À l’ajout, l’empreinte SHA-256 du contenu est calculée ; si le texte est identique à quelque chose déjà indexé, il est réutilisé et aucune nouvelle représentation vectorielle n’est payée. Lorsque le document est modifié, ses fragments sont intégralement supprimés puis réécrits — remplacement complet par source, et non mise à jour partielle, car une mise à jour partielle laisse derrière elle des fragments orphelins de l’ancienne version, et ce sont eux qui produisent des réponses contredites par le site. La source a un cycle d’états visible : en attente, en traitement, en retraitement, prêt ou en échec, et la réindexation peut être demandée ponctuellement, par source.

Nous ne réindexons pas ce qui n’a pas changé

Dans une boucle qui s’exécute toutes les trente minutes sur une liste qui ne fait que croître, recalculer la représentation d’un élément intact est un paiement récurrent pour un vecteur qui n’avait aucune raison de bouger. Notre boucle compare le moment du dernier contact avec le moment du dernier indexage et saute ce qui n’a pas été modifié. Les représentations sont demandées par lots, non une par une. Ce sont des détails de coût, mais le coût mensuel d’une base de connaissances se décide presque entièrement ici.

Un catalogue connecté n’est pas une liste chargée

La différence n’est pas de format, elle est de moment. Une liste chargée est vraie au moment du chargement : le prix, le stock et les nouveaux produits restent figés jusqu’à ce que quelqu’un se souvienne de charger à nouveau — et en général on découvre que personne ne s’en souvient, quand un client demande un ancien prix de trois mois. Un catalogue connecté se lit au moment de la question, à partir de la source qui tient les comptes. Ce que nous avons construit dans cette direction : un extracteur qui reconnaît les collections de produits dans un JSON ou un CSV à partir des clés habituelles et, lorsque la structure est inhabituelle, demande l’aide d’un modèle avec l’obligation de répondre strictement dans une forme donnée ; et un parcours qui lit le prix depuis la page en direct de la boutique. La meilleure voie reste l’interface structurée propre de la boutique, lorsqu’elle existe — mais cela se vérifie boutique par boutique, cela ne se présume pas.

Parcours d’un site : d’abord la carte, ensuite l’exploration

On cherche d’abord la carte du site, dans les variantes usuelles de nom, puis on déploie les cartes imbriquées sur deux niveaux. Ce n’est que s’il n’y a pas de carte que l’on passe à une exploration en largeur, strictement sur la même origine, avec une limite de pages configurable (par défaut 50, plafonnée à 200). La stratégie effectivement utilisée est renvoyée en retour — carte ou exploration — parce que c’est à partir d’elle que l’on lit si un résultat pauvre vient du site ou de la méthode. Le fichier d’exclusion du site est respecté.

À quoi cela ressemble

Le parcours, étape par étape.

01

Nous définissons ce que l’assistant a le droit de savoir, avant tout import

La liste des sources admises et, tout aussi important, la liste de celles rejetées, avec le motif. Un fichier interne de prix d’achat, un export de contacts, une archive d’e-mails — ce sont exactement les choses qui finissent dans le corpus « pour avoir du contexte » et deviennent ensuite une réponse à un client. Nous livrons l’inventaire des sources avec la décision et le motif pour chacune, plus la règle sur ce qui n’a jamais le droit d’entrer.

02

Nous importons, découpons et vérifions ce qui est sorti en lisant effectivement les fragments

Extraction, découpe avec chevauchement, indexation. L’étape de vérification n’est pas sautée : on lit des fragments au hasard dans le corpus obtenu. C’est là que se révèlent les tableaux transformés en chaînes de mots collés, les PDF numérisés qui ont produit zéro texte et les pages de menu de navigation entrées comme documents à part entière. Nous livrons le corpus plus le rapport sur ce qui n’a pas pu être extrait et pourquoi.

03

Nous réglons la recherche sur de vraies questions, pas sur des questions commodes

On construit un ensemble de questions à partir de ce que les clients demandent réellement, y compris les formulations mal écrites et celles dans deux langues mélangées, et l’on ajuste le nombre de fragments, le seuil et la pondération de la partie lexicale. L’objectif n’est pas que chaque question reçoive une réponse, mais que les questions sans couverture dans le corpus reçoivent « je n’ai pas cette information ». Nous livrons l’ensemble des questions avec les résultats et la configuration finale.

04

Nous mettons la porte d’approbation et la boucle de croissance

L’enregistrement des questions sans réponse, la notification à la personne qui approuve, le chemin de réponse, le passage dans le corpus et le lien des doublons. Nous livrons la boucle fonctionnelle et l’instruction écrite pour la personne qui approuve — y compris ce qui ne se copie pas de la question du client.

05

Nous remettons la procédure de rafraîchissement, avec le nom de la personne qui l’exécute

Quelle source est rafraîchie, à quelle fréquence, qui appuie et comment on voit que cela s’est produit. Ici, nous disons ouvertement la limite : sur les plateformes que nous exploitons, la réindexation est une action demandée, non une action programmée automatiquement. Une procédure sans nom de personne à côté est une procédure qui ne s’exécute pas.

DocumenteConversațiiSurseSintezăAcțiuneEchipăContexteazăSurse autorizate. Acțiuni revizuite.
Drumul unei întrebări prin sistem: sursele aprobate intră în extragere și împărțire, fragmentele ajung în index; la întrebare se punctează candidații — vectorial și lexical — și doar câteva fragmente ajung în context; iar când nu se găsește nimic, întrebarea iese pe ramura de aprobare umană și se întoarce în corpus.

Les données

Ce que nous touchons, où elles sont et combien de temps elles restent

Les questions que pose toute personne ayant un délégué à la protection des données — posées ici avant qu'il ne les pose.

Ce qui arrive dans le corpus
Seulement ce qui est explicitement approuvé : documents téléchargés, pages choisies, paires question-réponse, textes écrits à la main. Les sources sont typées — texte, document, site, paires question-réponse — et ont un objectif déclaré : produits, documentation, questions fréquentes ou autres. Chaque source a son propre commutateur, et le commutateur est vérifié à chaque requête de recherche, pas seulement dans l’interface — une source arrêtée ne peut pas arriver dans le contexte, même par erreur.
Où se trouvent les fragments et leurs représentations
Dans la base de données de la plateforme concernée, à côté du reste des données du client, et non dans un service externe séparé de recherche. Pour les installations sur PostgreSQL, dans des tables propres avec l’extension vectorielle activée explicitement ; pour la plateforme d’assistants texte, dans MySQL, avec la représentation dans une colonne JSON et le calcul de la distance effectué dans l’application. Le deuxième modèle fonctionne à petite échelle et ne passe pas à l’échelle des centaines de milliers de fragments — c’est une limite d’architecture, dite comme telle.
Les données personnelles dans les conversations
Le corpus ne devrait pas contenir de données personnelles, mais la boucle d’apprentissage peut les y amener, parce que la question d’un client est un texte écrit par le client. C’est pourquoi l’entrée dans le corpus passe par l’approbation d’une personne : c’est là que le filtrage se fait. La règle opérationnelle qui va avec : la personne qui approuve les réponses reçoit l’instruction explicite de ne pas copier dans le corpus les noms, les numéros de téléphone ou les détails de commande de la question originale.
Ce qui est conservé sur les requêtes
Il existe une table de journal des requêtes de recherche, avec les scores obtenus, le temps de recherche et le nombre de fragments renvoyés. C’est l’outil avec lequel on répond à « pourquoi a-t-il dit cela » — sans lui, l’ajustement des seuils devient une devinette. Le journal est conservé tant qu’il est utile pour le réglage et entre dans les délais de rétention de la plateforme, pas dans un régime séparé.
Ce qui n’existe pas et est déclaré comme tel
Il n’existe pas d’application d’un terme d’expiration sur les documents : le champ est déclaré dans le schéma, mais rien ne le lit, donc un document ne disparaît pas tout seul du corpus. Il n’existe pas de réindexation automatique programmée dans les plateformes client — le rafraîchissement est demandé explicitement, par source. Il n’existe pas de réordonnancement avec un modèle dédié ni de classement lexical de type BM25 ; ce que nous appelons « hybride » est une somme pondérée entre correspondance de mots et distance vectorielle, et cette formulation est la correcte.

Un cas

Une base de connaissances qui a grandi à partir des questions auxquelles l’agent ne savait pas répondre

La situation

Une chaîne de restaurants avec livraison avait un agent vocal au téléphone et un assistant sur la page web. Le corpus initial avait été chargé une fois, au lancement. Le problème n’était pas que l’agent se trompait, mais qu’il disait très souvent « je n’ai pas cette information » — pour des questions parfaitement raisonnables que personne n’avait anticipées lors de l’écriture du corpus. Chaque réponse de ce type était une conversation perdue, et la liste de celles-ci n’existait nulle part.

Ce que nous avons construit

Nous avons transformé le manque en entrée système. Quand l’agent répond qu’il n’a pas l’information, la question est écrite dans une table avec l’état « en attente » et avec le canal d’où elle est venue — voix ou widget. La personne responsable côté client reçoit une notification sur le canal de messagerie qu’elle utilise de toute façon, avec l’instruction de répondre directement au message. Un webhook récupère la réponse, fait passer l’entrée à l’état « approuvé », et de là la réponse entre dans le contexte de l’agent. Les formulations différentes du même manque sont liées à l’entrée originale par une référence, et la livraison à l’agent se fait une seule fois par entrée, afin qu’une réponse approuvée ne soit pas réinjectée à chaque cycle.

Ce qui en est sorti

Le corpus a cessé d’être un fichier chargé au lancement et est devenu une liste de manques réels, ordonnée par les clients, et non par nos suppositions. La personne qui approuve voit exactement ce qui a été demandé et dans quel canal, donc elle rédige la réponse sous la forme dans laquelle elle sera utilisée. Les états sont quatre — en attente, approuvé, ignoré, doublon — parce que « ignoré » est un résultat légitime : certaines questions ne doivent jamais recevoir de réponse automatique.

Ce que le cas ne dit pas

La boucle fonctionne exactement aussi longtemps que fonctionne l’humain qui s’y trouve. Si personne ne répond aux notifications, les entrées « en attente » s’accumulent et l’agent continue de ne pas savoir — le mécanisme rend le manque visible, il ne le résout pas tout seul. Deuxième limite, tout aussi importante : l’implémentation a été câblée pour le flux de ce client, avec le tableau, la notification et l’injection écrits pour lui. Comme fonction générique, disponible depuis l’interface pour n’importe quel compte, elle n’existe pas encore.

Questions

Ce que les gens nous demandent avant d'appeler

Pourquoi l’assistant répond-il mal si l’information est dans les documents chargés ?

Presque toujours parce que le bon fragment n’a pas été retrouvé, et non parce que le modèle serait « mauvais ». Les causes fréquentes, dans l’ordre où nous les vérifions : des fragments trop grands, où l’information utile se perd parmi les autres ; une coupe au milieu de la phrase, sans chevauchement ; trop peu de fragments amenés dans le contexte ; ou un corpus dans lequel la même information apparaît en trois versions différentes et l’ancienne ressort mieux. Le journal des requêtes, avec les scores obtenus, montre lequel de ces cas s’applique — sans lui, on devine.

Utilisez-vous vraiment la recherche sémantique partout ?

Non, et il est plus honnête de dire exactement où. Avec des vecteurs propres : la mémoire d’un board interne utilisé quotidiennement, sur colonne vectorielle avec index sur la distance cosinus et seuil de similarité 0,35. Avec des vecteurs chez le fournisseur : la base de connaissances des agents vocaux, synchronisée vers le moteur de récupération du fournisseur. **Sans** vecteurs, aujourd’hui, en production : la base de connaissances de la plateforme d’assistants texte — le chemin d’indexation écrit une colonne vectorielle vide, donc la récupération s’exécute lexicalement, et le code étiquette lui-même son module comme `keyword-only`. Il existe aussi un pipeline propre entièrement écrit, avec la migration marquée « à ne pas publier en production » jusqu’à confirmation des droits sur l’extension vectorielle. C’est la carte réelle.

Que se passe-t-il quand nous modifions un document déjà indexé ?

Les fragments de la source sont supprimés intégralement et réécrits à partir de la nouvelle version. Nous avons choisi le remplacement complet plutôt qu’une mise à jour partielle parce que la partie difficile n’est pas l’ajout, mais la suppression : les fragments restés de l’ancienne version continuent d’être retrouvés et produisent des réponses que le client contredit avec la page d’en face. Lors de l’ajout, il y a aussi une vérification d’empreinte du contenu — si le texte est identique, rien n’est recalculé et rien n’est payé pour rien.

Se met-il à jour tout seul quand le site ou le catalogue change ?

Pas automatiquement, dans les plateformes que nous opérons aujourd’hui. Le réindexage est demandé explicitement, par source, via une action dédiée. Il existe une seule exception, dans un système interne chez nous, où une boucle s’exécute toutes les trente minutes et ne réindexe que ce qui a changé. La planification automatique peut aussi être construite et c’est une étape séparée ; ce que nous ne faisons pas, c’est laisser entendre qu’elle existe déjà. Il existe aussi un champ d’expiration déclaré dans le schéma que rien ne lit — donc un document ne sort pas tout seul du corpus.

Quelle est la différence entre un catalogue connecté et une liste de produits chargée ?

Le moment de vérité. La liste chargée est correcte le jour du chargement ; dès le lendemain, le prix, le stock et les nouveaux produits divergent silencieusement, et la divergence est découverte lorsqu’un client demande un prix qui n’existe plus. Le catalogue connecté se lit au moment de la question, dans le système qui tient les comptes, donc il ne peut pas prendre de retard. Le coût est différent : une dépendance à la disponibilité de ce système apparaît, et il faut décider ce que l’assistant dit quand la source ne répond pas — la bonne réponse étant « je ne peux pas confirmer le prix maintenant », et non la dernière valeur connue.

Pouvez-vous indexer des PDF numérisés ?

Pas directement. Un PDF numérisé est une image dans un emballage PDF et ne contient pas de texte sélectionnable ; notre extracteur le détecte et renvoie un message explicite au lieu d’indexer un document vide — le pire résultat possible étant une source marquée « prête » qui ne contient rien. La solution est la reconnaissance optique avant l’importation, ou la fourniture du document en DOCX ou en texte. Nous n’avons pas la reconnaissance optique implémentée dans la chaîne actuelle.

Cela fonctionne en roumain et en russe ?

Oui, et il existe un détail qui compte plus qu’il n’y paraît. Le découpage en fragments coupe entre les phrases, et notre règle reconnaît la fin de phrase suivie d’une majuscule latine **ou** cyrillique, y compris les diacritiques roumains. Un séparateur écrit pour l’anglais ne reconnaît pas le début d’une phrase en cyrillique et produit des fragments collés, avec un effet direct sur la qualité de la récupération. Pour la partie vocale, le modèle de représentation utilisé chez le fournisseur est multilingue.

Quand la recherche sémantique NE vaut-elle PAS la peine ?

Quand le corpus est petit, stable et rédigé par vous — par exemple le contenu de votre propre site. Dans une plateforme publique propre, nous avons délibérément choisi la récupération par chevauchement de mots sur le texte même de la plateforme, découpé en courts passages qui portent chacun la page d’origine. Sans vecteurs, sans dépôt vectoriel, sans requête réseau supplémentaire et, surtout, sans aucune source extérieure au dépôt. L’assistant ne peut que répéter ce que dit déjà le site, et chaque réponse peut être vérifiée dans une page écrite par un humain. Quand l’enjeu principal est de ne rien inventer, cela bat la recherche sémantique.

Nous tenez-vous captifs dans votre plateforme ?

Les sources sont les vôtres et restent dans leur forme originale ; les fragments et les représentations résident dans la base de données de votre installation, pas dans un service externe de recherche auquel seuls nous avons accès. Le modèle de représentation est demandé via une gate de modèles propre, donc le changement de fournisseur n’affecte pas le code de la base de connaissances. Les représentations vectorielles sont toutefois liées au modèle qui les a produites : changer de modèle exige de réindexer tout le corpus. C’est une opération à calculer, pas une opération gratuite, et il vaut mieux le savoir au début que le découvrir au milieu.

Sur quoi reposent les affirmations ci-dessus (29 sources)

29 d'entre elles sont du code et des fichiers de nos dépôts. Nous n'en publions ni le nom ni la ligne : réunies sur une seule page, elles décriraient trop précisément comment sont construits des systèmes qui ne sont pas seulement les nôtres. Nous les parcourons avec vous, dans le dépôt, sur demande — la vérification reste possible, elle se fait simplement lors d'un échange.

Qu'est-ce que vous voudriez voir mieux fonctionner ?

Racontez-nous votre processus. Ensemble, nous décidons ce qui vaut la peine d'être construit, ce que nous pouvons connecter et comment nous vérifions le résultat.

Discutons