Un dossier de PDFs sans aucun champ commun n’a pas besoin d’un index classique. La solution ? Le traiter comme un seul document avec une table des matières imbriquée. Explications.

UN DOSSIER DE PDFS SANS AUCUN LIEN, C’EST COMME UN LIVRE GÉANT

Imaginez un dossier rempli de fichiers sans aucun point commun : un catalogue de 492 pages sur les contrôles de sécurité, un document de 200 pages sur l’architecture zéro confiance, un cadre de gestion des risques liés à l’IA, trente-et-un articles sur le machine learning et sept rapports sur les marchés de matières premières. Aucun client, aucune date, aucun montant ne se retrouve dans tous ces fichiers. Impossible de construire un index classique avec des colonnes partagées.

Dans ce cas, la préparation du dossier se réduit à deux artefacts seulement : une ligne de résumé par fichier et la table des matières de chaque fichier. Rien de plus. C’est contre-intuitif, mais c’est la clé. Ce dossier n’a pas besoin d’un index complexe : il faut le traiter comme un seul document géant.

Un dossier de PDFs sans aucun point commun est un seul document géant dont les fichiers sont les chapitres et leurs sections, les sous-chapitres.

POURQUOI L’INDEX CLASSIQUE NE MARCHE PAS ICI

La méthode standard pour un système de Recherche augmentée par IA (RAG) consiste à construire un index : une ligne par document, une colonne par champ filtrant (client, date, montant…), puis un filtre devant la recherche. Mais cette approche suppose que les documents partagent des champs communs. Ce n’est pas le cas ici.

Pour un dossier comme celui-ci, il n’existe aucune colonne à remplir. On pourrait extraire des métadonnées comme le nom du fichier, le nombre de pages ou la date de création, mais aucune ne servirait à filtrer efficacement. Une colonne vide pour la plupart des lignes et qui signifie autre chose dans le reste n’est pas une colonne.

Trois questions permettent de vérifier si ce cas s’applique à votre dossier :

  • 1. Deux documents du dossier se référencent-ils mutuellement ? Par exemple, une annexe qui pointe vers un contrat principal. Si oui, le dossier a une structure à modéliser, et cet article n’est pas fait pour vous.
  • 2. Un utilisateur métier peut-il nommer un champ présent dans chaque document et qui a la même signification partout ? Comme « client », « date d’effet », « prime » ou « numéro de police ». Si la réponse est oui, le dossier est une base de données non structurée, et il faut la typer.
  • 3. Si les deux réponses sont non, alors vous êtes dans le cas où le dossier n’a rien en commun. Il faut le traiter comme un seul document.

LES MÉLANGES EXISTENT : DEUX CORPUS DANS UN MÊME DOSSIER

Un disque partagé peut contenir 5 000 contrats quasi identiques à côté de 300 fichiers divers sans propriétaire. Ce n’est pas une contradiction : ce sont deux corpus partageant le même espace. La solution ? Les séparer et appliquer l’architecture adaptée à chacun.

Le cas homogène, où l’entreprise connaît déjà les colonnes, permet trois choses impossibles ici :

  • Un filtre : une question comme « retailer X, en vigueur actuellement » devient une clause SQL, réduisant 18 000 lignes à trois avant même d’ouvrir un document.
  • Une agrégation : « Combien de polices expirent ce trimestre ? » peut être répondue par l’index lui-même, sans ouvrir un seul document.
  • Un vocabulaire partagé : chaque ligne a un client et une date, donc l’ontologie qui mappe les mots de l’utilisateur aux valeurs des colonnes fonctionne sur tout le corpus.

LA STRUCTURE IMBRIQUÉE : UNE TABLE DES MATIÈRES À DEUX NIVEAUX

Quand les documents n’ont rien en commun, la solution est simple : le dossier entier devient un seul document. Les fichiers sont ses chapitres. Leurs sections sont les sous-chapitres. La table des matières du dossier a deux niveaux :

  • Niveau 0 : la liste des fichiers, une ligne par fichier, avec un résumé en une phrase indiquant de quoi il parle.
  • Niveau 1 : la table des matières de chaque fichier.

Ensemble, ces deux niveaux forment une table des matières imbriquée : les chapitres du dossier, puis les sections de chaque chapitre. C’est tout ce qu’il faut préparer. Pas de classification, pas d’extraction de champs, pas de schéma, pas d’ontologie.

La qualité de cette solution repose presque entièrement sur la ligne de résumé. Elle n’est pas faite pour être lue : elle sert uniquement à la recherche. Elle doit être rédigée comme une définition de colonne, pas comme un texte promotionnel.

Trois éléments font la différence : le sujet dans les deux vocabulaires, la forme de la réponse attendue, et ce que le fichier n’est pas.

COMMENT RÉDIGER UNE BONNE LIGNE DE RÉSUMÉ

Une ligne de résumé efficace doit répondre à trois critères :

  • Le sujet dans les deux vocabulaires : les mots que l’utilisateur va taper et ceux que le document utilise. Une question sur la « gestion des comptes » doit pointer vers un fichier dont la ligne indique « gestion des comptes » et « AC-2 », car le routeur fonctionne sur du texte, pas sur une intention.
  • La forme de la réponse attendue : un contrôle numéroté par section est une promesse différente d’un rapport narratif. Cela indique au routeur si la réponse est une section ou un chapitre entier, ce que le deuxième niveau va exploiter.
  • Ce que le fichier n’est pas : c’est souvent négligé, mais c’est ce qui rend la recherche précise. « Pas un guide d’implémentation » élimine le fichier de toutes les questions sur les méthodes en une seule phrase. Les informations négatives permettent au routeur d’écarter 62 fichiers sans en ouvrir un seul.

COMMENT GÉNÉRER CES LIGNES DE RÉSUMÉ

Produire ces lignes est peu coûteux. Un appel par document à l’ingestion, sur les premières pages et la table des matières, donne un brouillon utilisable. Sur un dossier de cette taille, on peut aussi les rédiger à la main en une après-midi. Le coût est payé une fois par document et ne se répète que si le fichier change.

L’important est que ces lignes soient revues comme un schéma, pas comme un texte marketing. Le parser retourne déjà une table des matières sous forme de dataframe, avec une ligne par titre et sa plage de pages. Pour ce dossier, 47 des 63 PDF contiennent une table des matières native et nécessitent un seul parsing chacun. Les 16 restants n’en ont pas : un mémo scanné, une exportation de diapositives, une note de deux pages.

Pour ces 16 fichiers, deux solutions s’offrent :

  • Un fichier court devient une feuille : si le routeur le sélectionne, on le lit en entier et le niveau 1 ne s’exécute pas.
  • Un fichier long voit sa table des matières reconstruite d’abord.

Pour les cas où le PDF affiche une page de contenu mais ne l’encode pas comme une table des matières, un article dédié explique comment reconstruire la structure. Pour les cas plus complexes où il n’y a même pas de page de contenu et où les titres doivent être retrouvés à partir de la typographie du corps du texte, une autre méthode existe.

EXEMPLE CONCRET : UNE QUESTION, 63 LIGNES, 5 PAGES

Prenons une question : « Que demande le contrôle de gestion des comptes ? » La réponse se trouve dans cinq pages du NIST SP 800-53 Rev. 5 (Contrôles de sécurité et de confidentialité pour les systèmes d’information et les organisations, travail du gouvernement américain, domaine public aux États-Unis), contrôle AC-2, pages 46 à 50. Les 62 autres fichiers ne peuvent pas contenir la réponse, et aucun ne sera ouvert.

Le processus est le suivant :

  1. Le modèle lit 63 lignes, chacune contenant le nom du fichier, son résumé de routage et un comptage de mots-clés.
  2. Il retourne les identifiants des fichiers qui pourraient contenir la réponse, avec une raison pour chacun. Généralement, un à trois fichiers sont retournés.

Le comptage de mots-clés fonctionne en parallèle, mais pas à la place du résumé. Compter combien de termes de la question apparaissent dans la ligne de résumé et dans les titres de la table des matières ne coûte rien. Cela permet de repérer des codes exacts comme « AC-2 » ou « GV.OC-01 » que la phrase de résumé n’a pas explicitement mentionnés. Les deux signaux sont intégrés dans la même requête, et le modèle décide, selon le modèle établi dans un article précédent.

Ce processus est borné : 63 lignes courtes, quel que soit le poids du dossier sur le disque. Et il est auditable : la chaîne de raison indique pourquoi un fichier a été conservé, permettant de tracer une mauvaise réponse jusqu’à la décision de routage plutôt qu’à un score de similarité.

LE PROCESSUS DE RECHERCHE : UN DOCUMENT, DEUX NIVEAUX

Une fois les fichiers sélectionnés, le processus devient un cas classique de recherche sur un seul document. La table des matières du fichier sélectionné devient le niveau actuel. Le modèle choisit une branche, et si cette branche a des enfants plus fins, la boucle s’ouvre et se répète. Elle s’arrête à une feuille ou à une section suffisamment courte pour être lue en entier.

Pour ce catalogue, le processus est le suivant : 11 chapitres, puis 20 familles de contrôles, puis 25 contrôles, puis AC-2. Le modèle décide à chaque niveau si descendre plus profond apporte quelque chose, selon la même règle de terminaison que pour un document long.

LE CODE QUI FAIT TOUT MARCHER

Voici comment le code implémente cette logique. D’abord, on extrait le niveau 0 (les fichiers) de la table des matières globale :

level = corpustoc[corpustoc.level == 0]           # 63 lignes, une par fichier
files = reasonontoc(question, level,              # le MÊME appel de routage
                      sectionsignals=keywordhits(question, level))

Ensuite, pour chaque fichier sélectionné (généralement 1 à 3), on extrait son niveau 1 (ses sections) :

sections = []
for fileid in files.sectionids:
    level = corpustoc[(corpustoc.fileid == fileid)
                       & (corpus_toc.level == 1)]
    while True:                                     # la boucle du document unique
        pick    = reasonontoc(question, level)
        section = level[level.sectionid.isin(pick.sectionids)]
        kids    = immediatechildren(corpustoc, section)
        if kids.empty or section.n_pages.max() <= SMALL:
            break                                   # une feuille, ou suffisamment courte
        level = kids                                # ouvre-la, descends
    sections.append(section)

Résultat : 63 fichiers réduits à 1 fichier (AC-2 Gestion des comptes) contenant 5 pages sur un total de 4 211 pages.

POURQUOI NE PAS TOUT METTRE DANS UNE SEULE REQUÊTE

On pourrait sérialiser toute la table des matières imbriquée (63 lignes + 2 422 entrées) en une seule requête. Trois raisons de ne pas le faire :

  • Ça ne rentre plus dans la fenêtre de contexte : multipliez ce dossier par 20, et la table des matières seule devient un long prompt sans espace pour raisonner. Les dossiers qui ont besoin de cette architecture sont ceux qui continuent de grandir.
  • La précision chute avant la fenêtre : au niveau 0, le modèle compare 63 descriptions complètes de documents, ce qu’il fait bien. Avec 2 485 lignes mélangées, la section recherchée doit rivaliser avec 357 sœurs issues d’un document non pertinent. Un titre de section lu hors de son contexte est un signal faible.
  • Le coût est payé à chaque question : le niveau 0 coûte 63 lignes courtes à chaque fois. Le niveau 1 ne coûte que les tables des matières des fichiers conservés, donc un seul fichier au lieu de 63.

LA DESCENTE EST OPTIONNELLE

Dans un dossier de huit fichiers courts avec des tables des matières peu profondes, le niveau 0 sélectionne un fichier et il n’y a rien à descendre : la boucle s’exécute une fois et se comporte comme une recherche plate. Le modèle décide à chaque niveau si descendre plus profond apporte quelque chose, selon la même règle de terminaison que pour un document long.

QUATRE PIÈGES À ÉVITER

Quatre problèmes peuvent survenir avec un index de dossier à deux niveaux. Ils ne sont pas tous aussi graves :

  • Un problème d’écriture : « Un rapport technique sur la cybersécurité » correspond à douze fichiers. Le niveau 0 retourne les douze, ou en sélectionne trois sans raison reconstructible. Le signe distinctif : le niveau 0 conserve trop de fichiers, alors que le niveau 1 fonctionne parfaitement sur chacun. La solution ? Réécrire la ligne. Le test est simple : gardez dix questions réelles, exécutez l’appel niveau 0 sur chacune, et vérifiez que le fichier que vous auriez sélectionné manuellement est bien retourné. Dix questions suffisent à détecter la plupart des problèmes, car une mauvaise ligne de résumé est généralement mauvaise pour toute une classe de questions plutôt que pour une seule.
  • Niveau 1 vide : un fichier sans titres ne donne rien au routeur pour choisir une section. Le routeur lit alors le fichier en entier ou ne lit rien. Lire en entier est acceptable la plupart du temps : un mémo de trois pages est une feuille, et le schéma à deux niveaux gère naturellement les feuilles. Le cas problématique est rare mais précis : un fichier long et sans structure. Un dossier scanné de 200 pages sans titres ne peut pas être routé à aucun niveau, car il n’y a rien sur quoi router. Ce n’est pas un problème de recherche, mais un problème de parsing. Il se résout avant que cette architecture ne s’exécute, en reconstruisant une table des matières à partir des signaux disponibles dans le document.
  • Trop de fichiers au niveau 0 : quelques centaines de fichiers, c’est gérable. Quelques milliers, non. Le problème n’est pas ce à quoi on s’attend : ce n’est pas la fenêtre de contexte qui lâche en premier, mais la précision du routage. À 3 000 lignes, l’appel niveau 0 devient le même problème top-k que cette architecture a été conçue pour éviter, mais un étage plus haut.
  • Un plafond réel : la solution est d’ajouter un niveau, pas de changer d’architecture. Regroupez les fichiers par dossier, par source, par année, ou par toute autre classification naturelle que le classement déjà existant propose. Donnez à chaque groupe sa propre ligne de résumé. Le niveau 0 devient 40 lignes de groupe, le niveau 1 les fichiers à l’intérieur des groupes conservés, le niveau 2 leurs tables des matières. Même appel de routage, un tour de boucle en plus. C’est aussi à ce moment que ce cas commence à demander ce que le cas homogène a dès le départ : si le regroupement naturel se révèle être un champ présent dans chaque fichier, la deuxième question de la section 1.2 a changé de réponse, et le dossier veut un index après tout.

CE DONT CE CAS N’A PAS BESOIN

Il est utile de lister ce que ce cas n’a pas besoin, car chaque élément est un projet que quelqu’un proposera autrement :

  • Pas d’index relationnel : il n’y a pas de colonnes à remplir.
  • Pas d’ontologie : un vocabulaire partagé par un catalogue de contrôles et un rapport de matières premières ne décrit rien.
  • Pas d’agent SQL : il n’y a rien à requêter.
  • Pas de passage d’extraction d’entités sur le corpus : les entités ne se répètent pas entre les fichiers, donc il n’y a rien à lier.

Ce dont il a besoin ? Une phrase par fichier, écrite pour le routeur, et la table des matières que le parser retourne déjà. Ensuite, le routage s’exécute deux fois : une fois sur la liste des fichiers, une fois à l’intérieur des fichiers conservés. Soixante-trois fichiers et 4 211 pages, et le modèle lit 63 lignes plus une table des matières avant de tomber sur cinq pages.

LES SUITES DE CET ARTICLE

Deux articles complètent cette série :

  • Article 14C : le corpus homogène, où l’entreprise connaît déjà les colonnes. Cet article couvre le cas où la deuxième question de la section 1.2 a une réponse positive.
  • Article 14D : les dossiers de cas, un seul dossier sur une seule entité. Ce cas est hétérogène en interne et répétitif à travers les dossiers, et a besoin d’un peu des deux approches.

RÉFÉRENCES ET ARTICLES CONNEXES

Cet article réutilise plusieurs éléments déjà présentés dans la série :

  • Article 5B : les tables relationnelles qu’un parser retourne.
  • Article 5septies : reconstruire une table des matières à partir de la page qui l’imprime.
  • Article 7B : détecteurs parallèles, puis un appel final.
  • Article 7quater : recherche hiérarchique à l’intérieur d’un document long.
  • Article 14A : trois types de corpus, et ce que coûte le mauvais choix.
  • Articles 15 à 17 : l’index, l’ontologie et le chemin de requête SQL-first pour l’autre type de dossier.
Sources :
  • Towards Data Science

L'indépendance de CLODCO est votre garantie.

Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.

Soutenir CLODCO