Inicio / Artículos

Razonamiento sobre un árbol de documentos en lugar de buscar vectores

PageIndex crea un índice jerárquico que permite a un LLM navegar por él, omitiendo el procesamiento en bloques y los embeddings. Cómo funciona, su resultado en FinanceBench y cuándo utilizarlo.

Editor: REACTAPP.TOP10 min de lectura2394 palabras

Fuentes: I Threw Out My Vector Database. RAG Got Way Better With PageIndex

Pocas cosas son tan embarazosas como cuando un modelo cita con confianza la sección incorrecta de un informe anual de 200 páginas en medio de una demostración en vivo. Sigue una secuencia familiar: el equipo “arregla” el problema mediante una mejor división del contenido, una superposición más inteligente y un modelo de incrustación ajustado con mayor cuidado, el típico ciclo de actualización por el que pasa todo ingeniero RAG, convencido de que el próximo ajuste funcionará finalmente. Luego el sistema responde a una pregunta sobre las proyecciones de ingresos para 2023 con un párrafo del plan de gastos de capital de 2021. Las dos secciones utilizaban un lenguaje financiero similar, por lo que los modelos de incrustación las consideraron relacionadas. Pero no lo estaban.

Ese tipo de fallo plantea una pregunta legítima sobre si todo el enfoque está defectuoso desde sus cimientos.

Por qué la similitud no basta

Cada tutorial sobre RAG enseña la misma metodología: dividir el documento en fragmentos de 500 palabras, incrustarlos, buscar los vectores más cercanos para cada pregunta y pasar esos fragmentos a un LLM. Suena elegante, como una búsqueda semántica combinada con generación de lenguaje.

Lo que esta metodología omite es que la similitud no equivale a relevancia. Una base de datos de vectores encuentra texto semánticamente cercano a la pregunta, pero no necesariamente texto que la responda. Un párrafo sobre los factores de riesgo de un fármaco y otro sobre su eficacia se encuentran cerca uno del otro en el espacio de incrustación, pero solo uno de ellos aborda una pregunta sobre los riesgos financieros de la empresa.

El fragmentado genera un segundo problema. Al dividir un documento legal de 200 páginas en secciones de 500 tokens, todo lo que su autor construyó con esmero desaparece: secciones, referencias cruzadas, flujo lógico. Lo que queda es un montón de fragmentos sin ninguna idea de dónde provienen o cómo se relacionan entre sí. Un fragmentado mejorado lo simplifica un poco, pero nunca resuelve el problema fundamental, ya que la estructura descartada no puede recuperarse.

Un analista humano nunca leería de esa manera. Revisaría el índice, iría directamente a la sección correspondiente, la leería teniendo en cuenta el capítulo circundante y comprobaría las notas al pie. Navega por el documento como tal.

Un árbol en lugar de vectores

El equipo de VectifyAI se preguntó cómo sería un sistema RAG si funcionara de esa manera, y creó PageIndex. Una vez expresada en voz alta, la idea clave parece increíblemente obvia. Omitan la conversión de documentos en vectores y la búsqueda por similitud. En su lugar, construyan un índice en forma de árbol jerárquico del documento, que en esencia es un índice de contenidos legible por máquinas, y dejen que un LLM razoné a lo largo de ese árbol hasta encontrar el material relevante.

Eso significa que no hay base de datos de vectores, ni particionamiento, ni puntuaciones de similitud; solo indexación estructurada y navegación inteligente.

Paso uno: crear el índice

PageIndex no fragmenta el documento que se le proporciona. Analiza su estructura natural, es decir, las secciones, subsecciones, encabezados y agrupaciones lógicas, y genera una jerarquía similar a la forma en que un experto organizaría el contenido. Imagine un índice generado automáticamente, pero más completo. Cada nodo contiene algo más que un título: un resumen de lo que abarca su ramificación, las preguntas que podría responder de manera plausible y su relación con los nodos hermanos y padre.

Una especificación técnica de 100 páginas podría dar como resultado un árbol de tres o cuatro niveles de profundidad, con las principales áreas funcionales en la parte superior y temas más específicos en los nodos subordinados. La jerarquía lógica del documento se mantiene tal como la organizó su autor. Este proceso se ejecuta una sola vez por documento, al principio, y el índice se almacena mientras el contenido original permanece intacto y accesible.

Paso dos: razonamiento a través del árbol

Cuando llega una consulta, PageIndex no la incrusta ni realiza una búsqueda de vecino más cercano. En su lugar, un LLM navega por el árbol de índice. Lee los nodos de nivel superior y determina qué ramificación es la más probable que contenga la respuesta, sigue esa ramificación, lee el nivel siguiente y vuelve a razonar. Continúa descendiendo hasta localizar las secciones relevantes.

Esa es la parte que cambia las cosas. A continuación, obtiene esas secciones completas del documento original, sin fragmentación, y las utiliza como contexto para la respuesta.

La recuperación de información es explicable por diseño. Es posible ver exactamente qué nodos recorrió el LLM y en qué páginas del documento original se detuvo. No existe una puntuación de coseno de caja negra, y la cadena de razonamiento es visible.

Probándolo

La configuración es sencilla. Comience clonando el repositorio e instalando sus dependencias:

git clone https://github.com/VectifyAI/PageIndex.git
cd PageIndex
pip3 install --upgrade -r requirements.txt

Luego cree un archivo .env en el directorio raíz que contenga la clave de la API:

CHATGPT_API_KEY=your_openai_api_key_here

Para indexar un PDF, dirija la secuencia de comandos hacia él. Un informe financiero es un buen ejemplo, ya que es allí donde la herramienta funciona mejor:

python3 run_pageindex.py --pdf_path /path/to/annual_report.pdf

Los documentos Markdown utilizan un indicador diferente:

python3 run_pageindex.py --md_path /path/to/technical_spec.md

Eso completa la configuración. No hay base de datos vectorial que configurar, ningún modelo de embedding que elegir, ni parámetros de particionamiento sobre los que preocuparse.

El procesamiento de un documento genera una estructura de índice similar a esta, simplificada aquí:

{
  "document": "Apple Inc. Annual Report 2023",
  "index": {
    "title": "Apple Inc. Annual Report 2023",
    "summary": "Comprehensive financial and operational report covering revenue, product segments, risks, and strategic outlook",
    "children": [
      {
        "title": "Business Overview",
        "summary": "Company description, product lines, and market position",
        "pages": [1, 8],
        "children": [...]
      },
      {
        "title": "Financial Results",
        "summary": "Revenue, operating income, EPS, and segment performance for fiscal 2023",
        "pages": [45, 72],
        "children": [
          {
            "title": "Revenue by Product Category",
            "summary": "iPhone, Mac, iPad, Wearables, and Services revenue breakdown",
            "pages": [46, 52]
          },
          {
            "title": "Geographic Revenue Distribution",
            "summary": "Americas, Europe, Greater China, Japan, Rest of Asia Pacific",
            "pages": [53, 58]
          }
        ]
      },
      {
        "title": "Risk Factors",
        "summary": "Operational, market, regulatory, and competitive risks",
        "pages": [89, 110]
      }
    ]
  }
}

Imagínese a un analista que solicita los ingresos por iPhone en el ejercicio fiscal 2023. En lugar de buscar fragmentos de texto similares, el LLM lee el índice, concluye que “Resultados financieros > Ingresos por categoría de producto” es la sección a consultar, se dirige allí y obtiene íntegramente las páginas 46 a 52. La respuesta llega con una cita exacta de la página, y como el modelo nunca vio la sección de Factores de riesgo, nada perturbador la enturbia.

Una versión simplificada de esta recuperación basada en razonamiento se ve así en código:

import json
from openai import OpenAI

client = OpenAI()
def navigate_index(query: str, index_node: dict, depth: int = 0) -> list[dict]:
    """
    Recursively navigate the document index using LLM reasoning.
    Returns list of relevant leaf nodes with page references.
    """
    children = index_node.get("children", [])
    if not children:
        # Leaf node: return this section as relevant
        return [index_node]
    # Ask the LLM which branches are relevant to the query
    children_summary = "\n".join([
        f"[{i}] {child['title']}: {child['summary']}"
        for i, child in enumerate(children)
    ])
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    "You are navigating a document index to find sections relevant "
                    "to a query. Select the index numbers of sections that are likely "
                    "to contain the answer. Return a JSON array of selected indices."
                )
            },
            {
                "role": "user",
                "content": (
                    f"Query: {query}\n\n"
                    f"Available sections:\n{children_summary}\n\n"
                    f"Which sections should I look into? Return JSON array of indices only."
                )
            }
        ],
        temperature=0,
        response_format={"type": "json_object"}
    )
    selected = json.loads(response.choices[0].message.content).get("indices", [])
    relevant_nodes = []
    for idx in selected:
        if idx             # Recurse into selected branches
            relevant_nodes.extend(
                navigate_index(query, children[idx], depth + 1)
            )
    return relevant_nodes

def answer_with_pageindex(query: str, index: dict, document_pages: dict) -> str:
    """
    Full PageIndex retrieval and answer generation.
    """
    # Navigate the index to find relevant sections
    relevant_nodes = navigate_index(query, index)
    # Retrieve full text from identified pages
    context_parts = []
    citations = []
    for node in relevant_nodes:
        pages = node.get("pages", [])
        if pages:
            page_start, page_end = pages[0], pages[1]
            for page_num in range(page_start, page_end + 1):
                if page_num in document_pages:
                    context_parts.append(document_pages[page_num])
                    citations.append(f"p.{page_num}")
    context = "\n\n".join(context_parts)
    # Generate answer with full, unchunked context
    answer_response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    "Answer the question based on the provided document sections. "
                    "Be precise. If the answer involves numbers or dates, quote them exactly."
                )
            },
            {
                "role": "user",
                "content": f"Document sections:\n{context}\n\nQuestion: {query}"
            }
        ],
        temperature=0
    )
    answer = answer_response.choices[0].message.content
    citation_str = ", ".join(set(citations))
    return f"{answer}\n\n**Source:** {citation_str}"

Mire lo que está ausente: embeddings, similitud coseno, bases de datos vectoriales y lógica de fragmentación. El razonamiento estructurado sobre un índice de documentos reemplaza todo el proceso de recuperación.

Evidencia de un benchmark financiero

FinanceBench recopila preguntas sobre documentos financieros reales como presentaciones ante la SEC, informes anuales y llamadas de resultados. Las respuestas deben ser numéricamente precisas, las secciones deben referenciarse entre sí con exactitud y las notas al pie deben leerse cuidadosamente. El RAG basado en vectores tiende a tener dificultades aquí, ya que los documentos financieros están repletos de lenguaje similar en temas diferentes.

Mafin 2.5, basado en PageIndex, alcanzó una precisión del 98,7% en FinanceBench. Los sistemas vectoriales RAG típicos en pruebas financieras comparables obtienen resultados entre el 65% y el 80%, según la configuración. Esa diferencia no es insignificante: separa una herramienta en la que un analista de inversiones pueda confiar realmente de una que genera tonterías plausibles que deben verificarse línea por línea.

La estructura lo explica. Los documentos financieros son precisamente aquellos donde la jerarquía es de suma importancia. Un estado de cuentas consolidado, las notas a los estados financieros y el MD&A siguen un diseño claro y deliberado en el que los analistas confían constantemente. PageIndex preserva y utiliza ese diseño, mientras que el fragmentado vectorial lo destruye.

Cuándo elegir uno u otro

PageIndex no reemplaza universalmente a los sistemas vectoriales RAG. La elección adecuada depende del proyecto.

Funciona bien cuando:

  • Los documentos tienen una jerarquía clara, al igual que los informes, manuales, contratos y especificaciones.
  • La precisión y las citaciones exactas son indispensables.
  • El área es especializada, como el ámbito financiero, legal, médico o regulatorio.
  • Las preguntas requieren referencias cruzadas entre varias secciones de un mismo documento.
  • Es importante la explicabilidad, y el sistema debe mostrar por qué devolvió lo que devolvió.

El RAG vectorial tradicional sigue siendo adecuado cuando:

  • El corpus está formado por miles de documentos cortos e independientes, como tickets de soporte o reseñas de productos.
  • Los documentos no tienen una estructura consistente.
  • El objetivo es una búsqueda semántica aproximada en un corpus amplio.
  • La latencia es la principal preocupación y el procesamiento en bloques ya está optimizado.

La mayoría de los casos de uso de documentos empresariales, en los que un sistema debe ser verdaderamente útil y no solo impresionante en una demostración, se asemejan mucho más a la primera lista que a la segunda.

Para un uso en producción sin la carga de infraestructura, PageIndex también ofrece una API REST e integración MCP. La configuración para desarrolladores es la siguiente:

import requests

PAGEINDEX_API_KEY = "your_api_key"
BASE_URL = "https://api.pageindex.ai/v1"
def upload_document(file_path: str) -> str:
    """Upload a document and get back a document_id."""
    with open(file_path, "rb") as f:
        response = requests.post(
            f"{BASE_URL}/documents",
            headers={"Authorization": f"Bearer {PAGEINDEX_API_KEY}"},
            files={"file": f}
        )
    return response.json()["document_id"]

def query_document(document_id: str, question: str) -> dict:
    """Query an indexed document and get a cited answer."""
    response = requests.post(
        f"{BASE_URL}/query",
        headers={
            "Authorization": f"Bearer {PAGEINDEX_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "document_id": document_id,
            "question": question
        }
    )
    return response.json()

# Example usage
doc_id = upload_document("q3_earnings_report.pdf")
result = query_document(doc_id, "What was total revenue in Q3?")
print(result["answer"])
print(f"Sources: {result['citations']}")

Al cargar el contenido, la API crea el índice automáticamente, y al realizar una consulta, devuelve respuestas estructuradas con citas de página que pueden rastrearse.

La idea principal

Más allá de las puntuaciones de referencia, ocurre algo más profundo. El RAG vectorial estándar considera un documento como un conjunto de fragmentos de texto relacionados e ignora la estructura que su autor estableció. Cada encabezado, salto de sección y referencia cruzada es una señal, la forma en que el autor indica a los lectores hacia dónde dirigirse, pero el método de fragmentación descarta todo esto antes de intentar reconstruir la relevancia a partir de la nada. PageIndex trata a un documento tal como es: un artefacto estructurado diseñado para ser navegado por un lector competente.

Un analista senior que revisa un informe anual no lee desde la primera página en adelante. Consulta el índice, va directamente a la sección que le interesa, verifica una cifra comparándola con las notas y vuelve al apartado de MD&A. El LLM de PageIndex hace lo mismo.

Esa es también la razón por la cual este enfoque se aplica a áreas especializadas. Un informe de ensayos farmacológicos tiene una jerarquía diferente a la de un contrato legal o una especificación técnica, pero en cada caso el autor siguió una lógica interna de la que los lectores expertos se aprovechan. PageIndex captura esa lógica y permite recuperar información mediante ella.

Un diagnóstico diferente

La conclusión preocupante es que años de esfuerzo podrían haberse dirigido al problema equivocado. La atención se centró en las estrategias de fragmentación mientras que el defecto real estaba destruyendo la estructura desde el principio. Los modelos de incrustación se ajustaron a pesar de que la similitud semántica nunca fue la señal adecuada para la recuperación de información. Se añadieron herramientas de reclasificación para mejorar la calidad de la recuperación, cuando en realidad el método de recuperación en sí era el cuello de botella.

El ecosistema RAG desarrolló mecanismos complejos para compensar el error original de la fragmentación. PageIndex propone una alternativa: sin fragmentos, sin incrustaciones, sin búsquedas por similitud. Se debe crear un índice que refleje la estructura real del documento y navegar a través de él mediante el razonamiento.

No responde a todo. La búsqueda vectorial sigue siendo la mejor opción para la búsqueda semántica en millones de documentos cortos y no estructurados. Sin embargo, para la amplia gama de preguntas relacionadas con documentos largos y estructurados, que abarcan la mayoría de las necesidades empresariales, un árbol jerárquico merece una atención seria. Vuelva a ejecutar la demostración que falló dos veces, preguntando por las proyecciones de ingresos para 2023, y ahora el sistema dirige al usuario a la sección correcta, muestra las páginas adecuadas y responde con citas exactas. Ningún año incorrecto, ninguna sección equivocada, ni confianza mal planteada.

PageIndex es de código abierto y se puede alojar uno mismo en GitHub; además, existe una interfaz de chat alojada y documentación de API con referencias a REST y MCP para probarlo con sus propios documentos.

Leer más

  • Spring AI: Mejores resultados RAG con búsqueda híbrida utilizando Lucene y RRF — Combine la búsqueda vectorial con Lucene BM25 en un recuperador de Spring AI y fusione las clasificaciones con Reciprocal Rank Fusion para que RAG coincida tanto con el significado como con los términos exactos.
  • Cómo la API Agents de OpenAI cambia la economía del trabajo con IA — El agente gestiona modelos, llamadas a herramientas, entornos de pruebas, intentos repetidos y revisión humana, por lo que los conteos de tokens ya no indican el costo. Mida en su lugar las tareas aceptadas.
  • Búsqueda híbrida para RAG: BM25, fusión de rangos y reclasificación superan a los vectores — Por qué la búsqueda puramente vectorial no detecta términos exactos en RAG, y cómo BM25 combinado con búsqueda semántica, fusión de rangos recíprocos y un reclasificador con codificador cruzado elevaron la precisión de la búsqueda al 91%.
  • Pinecone, Weaviate, Milvus, Qdrant o Chroma: Cómo elegir una base de datos vectorial — Explicación en lenguaje sencillo de las características de cada base de datos vectorial, desde búsquedas sin servidor e híbridas hasta indexación con Rust y GPU, incluyendo sus ventajas y desventajas y cuándo cada una es la más adecuada.