Accueil / Articles

PageIndex : RAG sans vecteurs, basé sur le raisonnement, avec un index arborescent

PageIndex abandonne la base de données vectorielle au profit d’un index arborescent géré par un LLM. Comment fonctionne ce RAG basé sur le raisonnement, avec du code Python, une connexion MCP et des chiffres de benchmark.

Éditeur : REACTAPP.TOP14 min de lecture3118 mots

Sources: PageIndex: The RAG Framework That Threw Out Vector Databases and Still Hit 98.7% Accuracy

Imaginez un framework RAG qui élimine complètement les bases de données vectorielles et qui affiche ensuite un résultat de benchmark auquel aucun système basé sur des embeddings ne peut rivaliser. Il s’agit de PageIndex, un projet open source de VectifyAI. Sur FinanceBench, le benchmark standard pour la réponse à des questions sur des documents financiers, les systèmes RAG classiques basés sur des vectors atteignent une performance d’environ 30 à 50 pour cent. PageIndex affiche quant à lui 98,7 pour cent. Il n’y est pas parvenu grâce à un modèle d’embedding amélioré, mais en éliminant complètement les embeddings.

Ce résultat mérite une seconde analyse, car la réponse à des questions financières à partir de documents déposés auprès de la SEC constitue l’un des tests de récupération les plus exigeants dans l’IA en production. Il requiert un raisonnement en plusieurs étapes, des sauts entre différentes sections ainsi que des chiffres précis. Le meilleur système disponible ne fait pas usage de vectors, ni de segmentation en chunks, ni de bases de données vectorielles.

Voici ce que fait PageIndex, comment il a été conçu, du code Python fonctionnel, ainsi que les domaines où il excelle et ceux où il est moins performant.

Le mur auquel se heurtent les documents longs

Tout ceux qui ont développé des systèmes RAG à partir de documents plus longs qu’un article de blog connaissent ce problème. Un utilisateur demande à un système basé sur un rapport annuel de 200 pages de comparer le chiffre d’affaires net total de l’exercice 2024 à celui de l’exercice 2023. Le flux de travail standard se déroule comme suit :

  1. Diviser le document en blocs de taille fixe, généralement de 300 à 500 tokens.
  2. Incorporer chaque bloc à l’aide d’un modèle tel que text-embedding-3-large.
  3. Stocker les vecteurs dans Pinecone, Weaviate, Milvus, Chroma, pgvector ou un système similaire.
  4. Lors de la requête, incorporer la question et récupérer les k vecteurs les plus proches selon la similarité cosinus.
  5. Transmettre ces blocs au LLM pour qu’il rédige la réponse.

Cela fonctionne très bien pour des documents courts et non structurés tels que des articles de blog, des tickets de support ou des documents produits. En revanche, un rapport 10-K le divise de manière prévisible.

La requête ne ressemble pas à la réponse. La récupération vectorielle part du principe que le texte le plus similaire à la requête est le plus pertinent. Or, une requête exprime une intention, pas un contenu. Une question sur les tendances des revenus ne met pas en évidence la seule ligne du tableau de la page 47 ; elle met en évidence tous les paragraphes qui parlent des revenus.

Un terme, soixante occurrences. Dans un long rapport annuel, l’expression « revenu d’exploitation » apparaît des dizaines de fois dans différents contextes. La similarité les classe à peu près de manière égale, et le passage qui répond réellement à la question n’atteint peut-être jamais le top trois.

Le découpage déforme les tableaux. Un en-tête de tableau se trouve dans le chunk 14 tandis que la ligne dont on a besoin se trouve dans le chunk 15. Aucun des deux n’a de sens isolément. Les fenêtres superposées, le découpage sémantique et les analyseurs conscients des tableaux aident tous, mais aucun ne résout le problème de fond.

Aucun souvenir entre les tours de dialogue. Si le message précédent demandait des informations sur les actifs et le suivant demande « et les passifs ? », le système de récupération perçoit une requête entièrement nouvelle. Rien ne lui indique que l’utilisateur fait très probablement référence à la même section du même rapport.

Les références croisées restent cachées. Supposons que la page 12 renvoie les lecteurs à l’Appendice G, qui se trouve à la page 87. Le texte de l’appendice n’a aucun chevauchement sémantique avec la question initiale, donc le système vectoriel RAG ne suivra jamais ce lien. Injecter des métadonnées de référence croisée dans les blocs de texte constitue une solution de fortune, mais elle est fragile et peu scalable.

Ces cas limites ne sont pas rares. Ils découlent de l’architecture même du système. Comme l’a souligné Mingtian Zhang, cofondateur de VectifyAI, dans l’annonce du projet, ce dont a vraiment besoin la récupération d’informations, c’est de la pertinence, et la pertinence exige du raisonnement.

Qu’est-ce que PageIndex ?

PageIndex est un framework RAG sans vecteurs et basé sur le raisonnement, publié par VectifyAI en septembre 2025, créé par Zhang et Yu Tang. Il est licencié sous MIT, et son dépôt a connu une croissance rapide, dépassant 23 000 étoiles et près de 2 000 forks à la fin du mois de mars 2026.

Son idée centrale provient d’AlphaGo. Au lieu de scanner exhaustivement un espace en comparant chaque fragment par similitude vectorielle, il utilise une stratégie apprise pour le parcourir de manière intelligente. PageIndex applique cette approche aux documents, avec trois propriétés notables :

  • Aucune base de données vectorielle. La structure des documents et le raisonnement du LLM remplacent la recherche par similitude, il n’y a donc ni Pinecone, ni Chroma, ni embeddings.
  • Aucun découpage en fragments. Les documents restent organisés en sections naturelles, chapitres, paragraphes et tableaux, et non en fenêtres arbitraires de 500 tokens.
  • Navigation de type humain. Il fonctionne comme une personne qui cherche manuellement dans un document : elle consulte la table des matières, trouve la section qui contient probablement la réponse et la lit.

La méthode comporte deux étapes.

Étape 1 : un index en arbre hiérarchique

L’ingestion d’un document ne crée aucun embedding. PageIndex analyse la structure du document et génère un arbre hiérarchique, essentiellement une table des matières adaptée aux modèles LLM. Chaque nœud contient un titre, un bref résumé de la section, une plage de pages ainsi que des nœuds enfants pour les sous-sections, imbriqués autant que nécessaire.

Un dossier SEC de 50 pages peut donner naissance à un arbre de 30 à 50 nœuds, stocké sous forme d’un seul objet JSON. Rien n’est stocké dans une base de données vectorielle, il s’agit uniquement de JSON. Tout l’objet tient dans la fenêtre de contexte d’un modèle LLM, et il est possible d’ouvrir n’importe quel nœud pour le lire. Un nœud a l’aspect suivant :

{
  "node_id": "0006",
  "title": "Financial Stability",
  "start_index": 21,
  "end_index": 22,
  "summary": "Covers the Federal Reserve's financial stability oversight...",
  "sub_nodes": [
    {
      "node_id": "0007",
      "title": "Monitoring Financial Vulnerabilities",
      "start_index": 22,
      "end_index": 28,
      "summary": "Describes the Fed's vulnerability monitoring framework..."
    },
    {
      "node_id": "0008",
      "title": "Domestic and International Cooperation",
      "start_index": 28,
      "end_index": 31,
      "summary": "Federal Reserve collaboration with international bodies..."
    }
  ]
}

Chaque node_id fait référence au contenu brut de cette section : texte, tableaux et images. Le LLM peut demander le contenu de n’importe quel nœud lorsqu’il en a besoin.

L’aspect architectural important réside dans l’emplacement de cet index. Une base de données vectorielle conserve un index statique distinct des embeddings en dehors du modèle. Ici, l’arbre JSON se trouve à l’intérieur du contexte actif du LLM pendant l’inférence. VectifyAI le qualifie d’index in-context, et le modèle ne se contente pas de le consulter, il en tire des conclusions en temps réel.

Étape 2 : recherche dans l’arbre pilotée par le raisonnement

Lorsqu’une question arrive, PageIndex fournit au LLM l’arborescence, ne contenant que des titres et des résumés sans texte brut, et demande en fait où chercher en fonction de cette structure et de cette question. Le modèle lit les titres et les résumés des nœuds, détermine quels sections contiennent probablement la réponse et renvoie une liste d’ID de nœuds. Le système récupère ensuite le contenu réel de ces nœuds.

La récupération des informations s’effectue en boucle :

  1. Scanner la table des matières pour comprendre la structure et identifier les sections candidates.
  2. Sélectionner une section qui a le plus de chances de contenir ce qui est nécessaire.
  3. La lire, en extrayant le texte brut de ce nœud.
  4. Vérifier si cela suffit. Si oui, écrire la réponse. Sinon, revenir à l’étape initiale et essayer une autre section.

Ce paradigme diffère de la recherche vectorielle. Une base de données vectorielle calcule en parallèle la similarité cosinus pour chaque morceau de texte, ce qui est rapide mais superficiel. PageIndex, quant à lui, fait appel à un modèle LLM pour déterminer où se trouve probablement la réponse. Au cours de ce processus, le modèle peut suivre des références croisées, reconnaître qu’une question concernant des données d’appendice doit être associée au nœud correspondant à l’appendice, et comprendre qu’une question à plusieurs parties nécessite du contenu provenant de deux sections différentes. Chaque étape laisse également une trace du raisonnement, ce qui permet de voir quels nœuds ont été consultés et pourquoi. La récupération des informations cesse ainsi d’être une boîte noire.

Exemple de l’Appendice G

Une démonstration basée sur l’intégration MCP de VectifyAI montre pourquoi cela est important. Quelqu’un demande le total des actifs différés dans un rapport annuel de la Réserve fédérale. Le corps principal, aux pages 75 à 82, aborde l’évolution des actifs différés mais ne donne jamais ce total. Cependant, la page 77 contient une phrase indiquant que le tableau 5.3 résume les revenus, les dépenses et les distributions, et que l’annexe G fournit des informations plus détaillées.

Vector RAG bloque à cet étape. L’annexe G étant un tableau de chiffres sans similitude sémantique avec une question portant sur les actifs différés, la base de données l’ignore. PageIndex fonctionne différemment : le LLM lit l’arborescence, se rend dans la section financière, repère la référence à l’annexe G, la suit à travers l’arborescence, récupère le bon tableau et retourne la valeur exacte, avec un suivi des raisonnements montrant chaque étape.

Python, de bout en bout

Le code ci-dessous est adapté du livre de recettes officiel PageIndex, avec des commentaires pour illustrer le flux. Commencez par l’installation :

pip install pageindex openai

Importations :

import os
import json
import asyncio
from pageindex import PageIndexClient
from openai import AsyncOpenAI

Ensuite, les identifiants et les clients. Une clé API provient du tableau de bord PageIndex :

# Grab an API key from https://dash.pageindex.ai/api-keys
PAGEINDEX_API_KEY = os.environ["PAGEINDEX_API_KEY"]
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]pi_client = PageIndexClient(api_key=PAGEINDEX_API_KEY)
openai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)

Construire l’arbre

Intégrer les outils nécessaires :

import pageindex.utils as utils

Téléchargez un PDF et laissez PageIndex générer l’arbre. L’indexation prend un peu de temps, donc le script vérifie régulièrement son état jusqu’à ce qu’elle soit terminée, puis affiche le résultat :

# Upload a PDF; PageIndex handles the tree generation
doc = pi_client.upload("annual_report_2024.pdf")
doc_id = doc["doc_id"]# Tree generation takes a bit, so we poll
while not pi_client.is_retrieval_ready(doc_id):
    print("Still indexing...")
    import time; time.sleep(5)# Grab the tree and take a look
tree = pi_client.get_tree(doc_id, node_summary=True)["result"]
print("Document Tree:")
utils.print_tree(tree)

La sortie affiche chaque section, sous-section et plage de pages, ce qui permet de savoir immédiatement si l’arbre a bien capturé le layout.

Permettre au LLM de rechercher dans l’arbre

C’est l’étape clé. La fonction reçoit l’arbre ainsi qu’une requête :

async def find_relevant_nodes(tree: dict, query: str) -> list:
    """LLM reasons over tree structure to identify relevant nodes."""

Dans ce cas, le texte brut est supprimé afin d’économiser des tokens, puisque le modèle n’a besoin que des titres et des résumés. L’instruction présente le modèle comme un expert en récupération de documents, lui demande de suivre les références croisées et exige un JSON contenant son raisonnement ainsi qu’une liste des IDs des nœuds :

    # Strip raw text to save tokens; the LLM only needs titles and summaries
    tree_without_text = utils.remove_fields(
        tree.copy(), fields=["text"]
    )    search_prompt = f"""
    You are a document retrieval expert. Given a question and
    a hierarchical tree structure of a document, identify all
    nodes likely to contain the answer.    Each node has a node_id, title, and summary.
    Follow cross-references if a section mentions another.    Question: {query}    Document tree structure:
    {json.dumps(tree_without_text, indent=2)}    Reply in this JSON format only:
    {{
        "thinking": "<reasoning about which nodes are relevant>",
        "node_list": ["node_id_1", "node_id_2"]
    }}
    """    response = await openai_client.chat.completions.create(
        model="gpt-4.1",
        messages=[{"role": "user", "content": search_prompt}],
        temperature=0,
        response_format={"type": "json_object"},
    )    result = json.loads(response.choices[0].message.content)
    print(f"LLM reasoning: {result['thinking']}")
    return result["node_list"]

Récupérer le contenu et répondre

La fonction suivante rassemble le texte brut des nœuds sélectionnés :

def collect_node_content(tree: dict, node_ids: list) -> str:
    """Pull raw text from the nodes the LLM selected."""
    all_nodes = utils.flatten_tree(tree)
    context_parts = []

Ensuite, le corps de la boucle assemble le contexte, chaque section étant étiquetée par un titre et une plage de pages, et tout le processus l’entoure : sélectionner des nœuds, récupérer du contenu, générer une réponse avec des citations, puis retourner cette réponse ainsi que les nœuds utilisés.

    for node in all_nodes:
        if node["node_id"] in node_ids:
            title = node.get("title", "Untitled")
            pages = f"pages {node.get('start_index', '?')}-{node.get('end_index', '?')}"
            text = node.get("text", "")
            context_parts.append(
                f"[{title} | {pages}]\n{text}"
            )
    return "\n\n---\n\n".join(context_parts)
async def answer_query(tree: dict, query: str) -> dict:
    """Full vectorless RAG pipeline: tree search + answer generation."""    # Step 1: LLM picks the nodes
    node_ids = await find_relevant_nodes(tree, query)    # Step 2: Fetch content from those nodes
    context = collect_node_content(tree, node_ids)    # Step 3: Generate answer with citations
    answer_prompt = f"""
    Answer the question using only the provided context.
    Cite specific pages and sections in your answer.    Context:
    {context}    Question: {query}
    """    response = await openai_client.chat.completions.create(
        model="gpt-4.1",
        messages=[{"role": "user", "content": answer_prompt}],
        temperature=0,
    )    return {
        "answer": response.choices[0].message.content,
        "retrieved_nodes": node_ids,
        "context_length": len(context),
    }
# Run it
query = "What was the total value of deferred assets in 2023?"
result = asyncio.run(answer_query(tree, query))
print(result["answer"])
print(f"Nodes used: {result['retrieved_nodes']}")

Le connecter à MCP

Pour Claude, Cursor ou tout autre agent compatible MCP, PageIndex se connecte directement via un serveur hébergé :

{
  "mcpServers": {
    "pageindex": {
      "type": "http",
      "url": "https://api.pageindex.ai/mcp",
      "headers": {
        "Authorization": "Bearer your_api_key"
      }
    }
  }
}

Un serveur MCP local est également disponible pour télécharger des fichiers depuis le disque, et il nécessite Node.js 18 ou une version ultérieure :

{
  "mcpServers": {
    "pageindex": {
      "command": "npx",
      "args": ["-y", "@pageindex/mcp"]
    }
  }
}

Les chiffres, dans leur contexte

Le chiffre principal provient de FinanceBench, qui s’appuie sur des documents officiels déposés auprès de la SEC, notamment les 10-K, 10-Q et 8-K, et ne prend en compte que des réponses exactes. Mafin 2.5, l’agent d’analyse financière de VectifyAI basé sur PageIndex, a été testé sur l’ensemble des données. Précision rapportée :

Système Précision Couverture du référentiel
Mafin 2.5 (PageIndex) 98,7 % 100 %
Vector RAG traditionnel environ 50 % varie
GPT-4o sans RAG environ 31 % 100 %
Perplexity environ 45 % varie

Un écart d’environ 49 points de pourcentage par rapport au Vector RAG conventionnel n’est pas une amélioration mineure : il s’agit d’une catégorie complètement différente de résultats. Trois facteurs reviennent constamment :

  • Références croisées. Les indications internes au document telles que « voir l’Appendice G » sont naviguées à travers l’arbre. La recherche par similarité ne prend pas en compte de telles références.
  • Préservation de la structure. Les tableaux possèdent des en-têtes, des notes de bas de page et des relations entre cellules. PageIndex les conserve en tant que nœuds d’arbre, tandis que le fractionnement les détruit.
  • Raisonnement en plusieurs étapes. Une question comparant l’exercice 2023 et l’exercice 2024, mentionnée dans différentes parties du document, est traitée naturellement par la boucle itérative.

Ses limites

PageIndex est impressionnant pour son domaine d’application, mais il ne remplace pas le RAG vectoriel partout.

  • C’est plus lent. Chaque récupération implique plusieurs appels à un LLM : lecture de l’index, analyse des nœuds, récupération du contenu, vérification de la suffisance, éventuellement boucle. Vector RAG répond en millisecondes, PageIndex en secondes. Le cofondateur a souligné que, comme la récupération a lieu en temps réel pendant la génération, le temps nécessaire pour obtenir le premier token peut être comparable à celui d’un appel normal à un LLM, mais la latence totale reste plus élevée.
  • C’est plus coûteux. Plusieurs appels d’inférence par requête contre une seule recherche d’embedding. À grande échelle, les coûts deviennent considérables.
  • C’est conçu pour la profondeur, pas pour la largeur. Il excelle dans l’extraction approfondie à partir d’un seul document long. La recherche simultanée de 10 000 documents courts est le domaine où les bases de données vectorielles se distinguent, car construire et analyser un arbre pour chacun d’eux n’est pas pratique.
  • La structure est essentielle. Un dossier SEC bien structuré produit un arbre d’information excellent. Une scan mal formatée, sans sections logiques, génère un sommaire dégradé, ce qui affecte tout le reste. Un mode basé sur la vision existe pour de tels cas, mais les documents structurés restent la solution optimale.
  • Le raisonnement des LLM n’est pas non plus parfait. Vector RAG fonctionne mal car les embeddings ne représentent que de manière approximative le sens des textes. PageIndex échoue lorsque le raisonnement est approximatif : le modèle peut se rendre dans la mauvaise section, interpréter incorrectement un résumé ou ignorer un sous-nœud pertinent. Ces défaillances sont plus faciles à interpréter et potentiellement plus aisées à déboguer, mais elles se produisent néanmoins.
  • Un seul critère d’évaluation. FinanceBench mesure la qualité des vérifications sur des dossiers déposés auprès de la SEC bien structurés. Les résultats obtenus avec des requêtes complexes, couvrant plusieurs domaines et étant ambiguës, sur des types de documents très différents, n’ont pas été validés de manière indépendante au même degré. Le taux de 98,7 % est réel, mais il est important de savoir ce qu’il mesure.
  • Quand l’utiliser

    Préférez PageIndex lorsque :

    • les documents sont longs, structurés et professionnels : rapports annuels, contrats, dossiers réglementaires, manuels techniques
    • la précision prime sur la vitesse, par exemple lorsque une réponse erronée entraîne des pertes financières ou des risques de non-conformité
    • les requêtes nécessitent une réflexion en plusieurs étapes ou un déplacement entre différentes sections
    • vous avez besoin de traces d’audit, car chaque réponse est accompagnée d’un historique de raisonnement et de références aux nœuds, ce qui est apprécié dans les secteurs réglementés

    Préférez le RAG vectoriel lorsque :

    • vous avez besoin de réponses en quelques millisecondes avec un volume élevé de requêtes
    • vous effectuez des recherches dans un grand corpus de documents courts
    • les documents sont non structurés ou de nature conversationnelle
    • une précision de 90 pour cent est suffisante
    • le coût par requête est la principale contrainte

    Une approche hybride n’a probablement pas été suffisamment explorée. Utilisez la recherche vectorielle pour identifier le document pertinent dans un grand corpus, puis transmettez-le à PageIndex pour une extraction approfondie.

    Quoi de nouveau depuis le lancement

    Le projet a évolué rapidement depuis septembre 2025.

    RAG vectorless agentic. La dernière mise à jour intègre un PageIndex auto-hébergé dans le SDK OpenAI Agents. L’agent dispose de trois outils (get_document(), get_document_structure() et get_page_content()) et analyse l’arborescence par lui-même, en appelant les outils appropriés et en récupérant le contenu sans sélection manuelle des nœuds. C’est l’exemple de référence pour les workflows agentic :

    pip install openai-agents
    python3 examples/agentic_vectorless_rag_demo.py
    

    Mode basé sur la vision, sans OCR. Un pipeline conçu spécifiquement pour la vision évite désormais l’OCR. Les images des pages sont directement transmises à un modèle spécialisé dans la vision, et l’arborescence est générée à partir de ce que ce modèle perçoit sur la page. Cela est crucial pour les documents financiers où la disposition d’un bilan a autant d’importance que le texte lui-même. Les graphiques, les tableaux à cellules fusionnées et les notes de bas de page restent intacts, car rien n’est d’abord converti en texte.

    Un SDK en TypeScript, pageindex-js-sdk, est apparu au début de 2026 pour les intégrations Node.js et les outils de traitement de documents en ligne. Une extension de bureau .mcpb en un clic installe PageIndex dans Claude Desktop, avec un traitement automatique des authentifications OAuth. ChatIndex, un dépôt distinct depuis janvier 2026, applique la même méthode d’indexation par arborescence aux longs historiques de conversation.

    Le changement plus large

    PageIndex s’inscrit dans une évolution plus large, allant d’une récupération passive qui affiche tout ce qui semble similaire à une récupération active et proactive qui réfléchit à l’endroit où chercher. Cela est déjà visible dans les outils de codage : Claude Code et Cursor ont abandonné les systèmes RAG basés sur des vecteurs pour le code au profit d’une exploration active du codebase, et l’équipe de PageIndex s’attend à ce que les documents suivent cette tendance. Pour les documents structurés, cette prédiction semble plausible.

    Les bases de données vectorielles ne disparaîtront pas. Elles sont rapides, peu coûteuses et suffisamment performantes pour une grande variété de problèmes. Mais là où la précision, la transparence et une compréhension approfondie des documents sont indispensables, comme dans les audits financiers, les recherches juridiques, les dossiers médicaux ou le respect des réglementations, une récupération basée sur le raisonnement est nettement plus adaptée.

    Le code et les documents se trouvent à https://github.com/VectifyAI/PageIndex, avec une documentation disponible à https://docs.pageindex.ai/.

    Lectures complémentaires

  • pgvector ou SQL Server 2025 en tant que stockage vectoriel RAG : configuration, requêtes et pièges à éviter — Utilisation de PostgreSQL avec pgvector et des vecteurs natifs de SQL Server 2025 pour RAG : configuration, récupération par cosinus, jointures avec des données métier, ainsi que six pièges liés aux préversions de SQL Server à éviter.
  • Chunking parent-enfant et indexation hiérarchique pour une meilleure récupération dans RAG — Pourquoi de petits chunks permettent de trouver des correspondances mais pas les réponses, et comment le chunking parent-enfant, l’expansion anti-duplication et l’indexation hiérarchique restituent le contexte lors de la récupération dans RAG.
  • Comment fonctionne RAG : les embeddings, les bases de données vectorielles et le chunking — Suivez un morceau de texte depuis sa tokenisation, en passant par le pooling, jusqu’à la recherche par similarité cosinus, et découvrez pourquoi le chunking ainsi que les modèles d’embeddings déterminent la qualité de RAG.
  • RAG sans vecteurs avec PageIndex : la récupération par raisonnement plutôt que par vecteurs — Comment PageIndex remplace les embeddings et le chunking par un arbre de documents navigué par un LLM, avec un schéma pas à pas en Python et des indications sur les cas où RAG vectoriel reste préférable.
  • RAG sans framework : chunk, score, budget, prompt — Explication de RAG comme simple sélection de tokens : un bref exemple en Python avec une évaluation basée sur le chevauchement des mots, un mécanisme de contrôle du budget de tokens, et ce que les embeddings et les bases de données vectorielles améliorent réellement.