RAG sin vectores: cómo PageIndex se recupera mediante razonamiento

La recuperación es donde la mayoría de los sistemas RAG fallan silenciosamente. Los canales tradicionales se basan en la similitud de vectores: incorporan consultas y fragmentos de documentos en el mismo espacio y obtienen las coincidencias “más cercanas”. Pero la similitud es un indicador débil de lo que realmente necesitamos: relevancia basada en el razonamiento. En documentos largos y profesionales, como informes financieros, trabajos de investigación o textos legales, la respuesta correcta a menudo no está en el párrafo semánticamente más similar. Requiere navegar por la estructura, comprender el contexto y realizar un razonamiento de varios pasos en las secciones. Aquí es exactamente donde el RAG basado en vectores comienza a desmoronarse.

PageIndex está diseñado para resolver esta brecha repensando la recuperación desde los primeros principios. En lugar de fragmentar documentos y buscar mediante incrustaciones, crea un índice de árbol estilo tabla de contenido jerárquico y utiliza LLM para razonar sobre esa estructura, de manera muy similar a un experto humano que escanea secciones, profundiza y conecta ideas. Esto permite un proceso de recuperación basado en el razonamiento y sin vectores que es más interpretable, rastreable y alineado con la forma en que realmente se extrae el conocimiento de documentos complejos. Al reemplazar la búsqueda de similitudes con exploración estructurada y razonamiento basado en árboles, PageIndex ofrece una precisión de recuperación significativamente mayor (lo que se demuestra por su sólido desempeño en puntos de referencia como FinanceBench), lo que lo hace particularmente efectivo para dominios que exigen precisión y comprensión profunda.

En este artículo, usaremos PageIndex para indexar el artículo fundamental de Transformer, “La atención es todo lo que necesita”, y ejecutaremos dos consultas transversales sin un solo vector ni incrustación. En lugar de fragmentar el PDF y recuperarlo por similitud, PageIndex crea un árbol jerárquico de las secciones del documento, luego usa GPT-5.4 para razonar sobre los resúmenes de los nodos e identificar exactamente qué secciones contienen la respuesta, antes de leer una sola palabra del texto completo.

Configurando las dependencias

Para este tutorial, necesitará las claves API de PageIndex y OpenAI. Puede obtener lo mismo en https://dash.pageindex.ai/api-keys y https://platform.openai.com/api-keys respectivamente.

Copiar códigoCopiadoUtilice un navegador diferente

solicitudes de openai de índice de página de instalación de pip

Copiar códigoCopiadoUtilice un navegador diferente

desde pageindex importar PageIndexClient importar pageindex.utils como utilidades importar sistema operativo desde getpass importar getpass PAGEINDEX_API_KEY = getpass(‘Ingrese la clave API de PageIndex: ‘) pi_client = PageIndexClient(api_key=PAGEINDEX_API_KEY)

Importamos el cliente OpenAI y lo configuramos con una clave API para permitir el acceso a los LLM. Luego, definimos una función auxiliar asincrónica que envía mensajes al modelo y devuelve la respuesta generada.

Copiar códigoCopiadoUtilice un navegador diferente

importar openai OPENAI_API_KEY = getpass(‘Ingrese la clave API de OpenAI: ‘) async def call_llm(prompt, model=”gpt-5.4″, temperatura=0): cliente = openai.AsyncOpenAI(api_key=OPENAI_API_KEY) respuesta = esperar client.chat.completions.create( modelo=modelo, mensajes=[{“role”: “user”, “content”: prompt}]temperatura=temperatura ) devolver respuesta.opciones[0].mensaje.content.strip()

Construyendo el árbol PageIndex

En este fragmento, descargamos el documento de Transformer directamente desde arXiv y lo enviamos a PageIndex, que procesa el PDF y crea un árbol jerárquico de sus secciones: cada nodo almacena un título, un resumen y el texto completo de la sección. Una vez que el árbol está listo, lo imprimimos para inspeccionar la estructura que PageIndex ha inferido: cada capítulo, subsección y encabezado anidado se convierte en un nodo en el árbol, preservando la organización natural del documento exactamente como lo concibieron los autores.

Copiar códigoCopiadoUtilice un navegador diferente

# ────────────────────── ─────────────────────── # Paso 1: construir el árbol PageIndex # ────────────────────── ─────────────────────── # 1.1 Descargue el documento de Transformer y envíelo import os, solicitudes pdf_url = “https://arxiv.org/pdf/1706.03762.pdf” pdf_path = os.path.join(“data”, pdf_url.split(“/”)[-1]) os.makedirs(“data”, exist_ok=True) print(“Descargar ‘Atención es todo lo que necesita’…”) respuesta = request.get(pdf_url) con open(pdf_path, “wb”) como f: f.write(response.content) print(f”✅ Guardado en {pdf_path}”) doc_id = pi_client.submit_document(pdf_path)[“doc_id”]
imprimir(f”📄 Documento presentado. doc_id: {doc_id}”) # 1.2 Recuperar el árbol (encuesta hasta que esté listo) tiempo de importación print(“\nEsperando que el árbol PageIndex esté listo”, end=””) mientras no pi_client.is_retrieval_ready(doc_id): print(“.”, end=””, flush=True) time.sleep(5) tree = pi_client.get_tree(doc_id, node_summary=True)[“result”]
imprimir(“\n\n📂 Estructura del árbol del documento:”) utils.print_tree(árbol)

Recuperación basada en el razonamiento

Una vez construido el árbol, ahora ejecutamos una consulta que es intencionalmente transversal, una que no puede ser respondida por una sola sección del documento. Eliminamos el texto completo de cada nodo, dejando solo títulos y resúmenes, y pasamos toda la estructura de árbol a GPT-5.4. Luego, el modelo analiza estos resúmenes para identificar cada nodo que probablemente contenga una respuesta relevante, devolviendo tanto su pensamiento paso a paso como una lista de ID de nodos coincidentes. Este es el núcleo de lo que hace que PageIndex sea diferente: el LLM decide dónde buscar antes de cargar el texto completo.

Copiar códigoCopiadoUtilice un navegador diferente

# ────────────────────── ─────────────────────── # Paso 2: Recuperación basada en el razonamiento # ────────────────────── ─────────────────────── # 2.1 Definir una consulta que requiera navegar a través de secciones import json # Esta consulta es intencionalmente transversal: no puede ser respondida # por una sola sección, que es donde la búsqueda en árbol brilla sobre top-k. query = “¿Por qué los autores eligieron la autoatención en lugar de la recurrencia y cuáles son las ventajas y desventajas de complejidad que compararon?” árbol_sin_texto = utils.remove_fields(árbol.copia(), campos=[“text”]) search_prompt = f””” Se le proporciona una pregunta y una estructura de árbol jerárquica de un trabajo de investigación. Cada nodo tiene un nodo_id, un título y un resumen de su contenido. Su tarea: identificar TODOS los nodos que probablemente contengan información relevante para responder la pregunta. Piénselo detenidamente: la respuesta puede estar distribuida en varias secciones. Pregunta: {query} Árbol de documentos: {json.dumps(tree_ without_text, indent=2)} Responda SÓLO en este JSON formato, sin preámbulo: {{ “thinking”: ““, “node_list”: [“node_id_1”, “node_id_2”, …]
}} “”” imprimir(f’🔍 Consulta: “{query}”\n’) print(“Ejecutando la búsqueda de árbol con GPT-5.4…”) tree_search_result = await call_llm(search_prompt) # 2.2 Inspeccionar el razonamiento de recuperación y los nodos coincidentes node_map = utils.create_node_mapping(tree) result_json = json.loads(tree_search_result) print(“\n🧠 Razonamiento de LLM:”) utils.print_wrapped(result_json[“thinking”]) imprimir(“\n📌 Nodos recuperados:”) para node_id en result_json[“node_list”]: nodo = mapa_nodo[node_id]
imprimir(f” • [{node[‘node_id’]}]Página {nodo[‘page_index’]:>2} — {nodo[‘title’]}”)

Generación de respuestas

Una vez que se identifican los nodos relevantes, extraemos su texto completo y lo unimos en un único bloque de contexto: cada sección está claramente etiquetada para que el modelo sepa de dónde proviene cada pieza de información. Luego, ese contexto combinado se entrega a GPT-5.4 con un mensaje estructurado que solicita la motivación central, los números de complejidad específicos y cualquier advertencia que reconocieron los autores. El modelo responde utilizando únicamente lo que se recuperó, fundamentando cada afirmación directamente en el texto del artículo.

Copiar códigoCopiadoUtilice un navegador diferente

# ────────────────────── ─────────────────────── # Paso 3: Generación de respuestas # ────────────────────── ─────────────────────── # 3.1 Unir el contexto de todos los nodos recuperados node_list = result_json[“node_list”]
contenido_relevante = “\n\n—\n\n”.join( f”[Section: {node_map[nid][‘title’]}]\n{mapa_nodo[nid][‘text’]}” para nid en node_list ) print(f”\n📖 Vista previa de contexto recuperada (primeros 1200 caracteres):\n”) utils.print_wrapped(relevant_content[:1200] + “…\n”) # 3.2 Genere una respuesta estructurada basada en las secciones recuperadas respuesta_prompt = f””” Usted es un asistente técnico. Responda la siguiente pregunta utilizando SÓLO el contexto proporcionado. Sea específico: haga referencia a las opciones de diseño reales, los números y las compensaciones mencionadas en el texto. Pregunta: {query} Contexto: {relevant_content} Estructura su respuesta como: 1. La motivación principal para elegir la autoatención 2. Las comparaciones de complejidad específicas realizadas (incluya cualquier tabla o números) 3. Cualquier advertencia o limitación que los autores reconocieron “”” print(“💬 Generando respuesta…\n”) respuesta = espera call_llm(answer_prompt) print(“─” * 60) print(“✅ Respuesta final:\n”) utils.print_wrapped(respuesta) print(“─” * 60)

Prueba con una segunda consulta

Para demostrar que el árbol se construye una vez y se reutiliza sin costo adicional, ejecutamos una segunda consulta, esta vez dirigida a un mecanismo localizado en lugar de una decisión de diseño transversal. La misma estructura de árbol se pasa a GPT-5.4, que limita su búsqueda solo a las subsecciones de atención, recupera su texto completo y genera una explicación clara de cómo funciona la atención de múltiples cabezas y por qué es importante el factor de escala. No es necesario volver a indexar ni volver a incrustar: solo una nueva pregunta en el mismo árbol.

Copiar códigoCopiadoUtilice un navegador diferente

query2 = “¿Cómo funciona el mecanismo de atención de múltiples cabezales y cuál es el papel del escalado en la atención del producto escalable?” search_prompt2 = f””” Se le proporciona una pregunta y una estructura de árbol jerárquica de un trabajo de investigación. Identifique todos los nodos que probablemente contengan la respuesta. Pregunta: {query2} Árbol de documentos: {json.dumps(tree_ without_text, indent=2)} Responda SÓLO en este formato JSON: {{ “thinking”: ““, “node_list”: [“node_id_1”, …]
}} “”” imprimir(f’\n\n🔍 Segunda consulta: “{query2}”\n’) result2_raw = await call_llm(search_prompt2) result2 = json.loads(result2_raw) print(“🧠 Razonamiento:”) utils.print_wrapped(resultado2[“thinking”]) contenido_relevante2 = “\n\n—\n\n”.join( f”[Section: {node_map[nid][‘title’]}]\n{mapa_nodo[nid][‘text’]}” para nid en resultado2[“node_list”]
) respuesta_prompt2 = f””” Responda la siguiente pregunta utilizando SÓLO el contexto proporcionado. Explique el mecanismo claramente, como si fuera una publicación de blog técnica. Pregunta: {query2} Contexto: {relevant_content2} “”” respuesta2 = await call_llm(answer_prompt2) print(“\n✅ Respuesta:\n”) utils.print_wrapped(respuesta2)

Consulte los códigos completos aquí. Encuentre cientos de cuadernos de Colab de ciencia de datos y aprendizaje automático aquí. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 130.000 ML y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.

¿Necesita asociarse con nosotros para promocionar su repositorio de GitHub O su página principal de Hugging O su lanzamiento de producto O seminario web, etc.? Conéctate con nosotros

La publicación RAG sin vectores: cómo se recupera PageIndex mediante razonamiento apareció por primera vez en MarkTechPost.