Comparación de RAG, RAG sin vectores y GraphRAG
RAG vectorial clásico, recuperación sin vectores léxicos y GraphRAG: fragmentación, embeddings, BM25, grafos de múltiples saltos, y cuándo cada enfoque resulta útil.
Cómo responden los modelos de lenguaje actuales a partir de material que nunca apareció en el entrenamiento.
RAG en una frase
Retrieval Augmented Generation es exactamente lo que indica la sigla. Primero se obtiene material relacionado con la pregunta del usuario. Luego un modelo escribe una respuesta utilizando ese material junto con el prompt. El patrón es primero la recuperación de información y después la generación.
¿Por qué molestarse?
Los modelos solo conocen lo que estaba incluido en su fecha límite de entrenamiento. Todo lo demás es invisible.
Imagínese un libro escaso de 3,000 páginas que apenas aparece en línea y que nunca formó parte de ningún corpus de entrenamiento. Si le hace una pregunta al modelo, obtendrá respuestas vacías. Incluso si se utilizan herramientas de extracción de datos, seguirá habiendo respuestas vacías: no hay nada que pueda ser extraído.
RAG cierra esa brecha. Carga el PDF en la pipeline. En el momento de la consulta, se extraen las secciones más relevantes y se adjuntan como contexto. La tarea del modelo se reduce a: responder a partir de este texto, utilizando sus habilidades lingüísticas para dar forma a la respuesta.
No se requiere ajuste fino. Solo el contexto adecuado en el momento oportuno.
Estructura básica de la pipeline
Primero viene el indexado, y este comienza con la división en fragmentos.
Estrategias de fragmentación
Un PDF de miles de páginas no debe almacenarse en un repositorio vectorial como un único bloque. Hay que dividirlo para que cada fragmento pueda integrarse y recuperarse por separado.
Divisiones comunes:
Por página. Una página → un fragmento (3,000 páginas → 3,000 fragmentos). Útil cuando se buscan resultados precisos y detallados.
Por párrafo. Se realizan cortes más precisos en los límites de los párrafos. Puede dar una sensación de mayor agilidad, pero los tamaños varían enormemente (200 tokens junto a 2,000). Las longitudes irregulares generan embeddings irregulares y dañan silenciosamente la recuperación de información.
Ventanas fijas. Presupuestos de tokens constantes, generalmente 512, independientemente del lugar donde se realice el corte. Los tamaños uniformes producen embeddings más comparables. Cuando no se está seguro, la mayoría de los equipos eligen esta opción por defecto.
from langchain_text_splitters import CharacterTextSplitter
from langchain_core.documents import Document
def perform_fixed_size_chunking(document, chunk_size=1000, chunk_overlap=200😞
"""
Performs fixed-size chunking on a document with specified overlap.
Args:
document (str): The text document to process
chunk_size (int): The target size of each chunk in characters
chunk_overlap (int): The number of characters of overlap between chunks
Returns:
list: The chunked documents with metadata
"""
# Create the text splitter with optimal parameters
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len
)
# Split the text into chunks
chunks = text_splitter.split_text(document)
print(f"Document split into {len(chunks)} chunks")
# Convert to Document objects with metadata
documents = []
for i, chunk in enumerate(chunks):
doc = Document(
page_content=chunk,
metadata={
"chunk_id": i,
"total_chunks": len(chunks),
"chunk_size": len(chunk),
"chunk_type": "fixed-size"
}
)
documents.append(doc)
return documents
# Example usage
if __name__ == "__main__":
# Create the dummy document
document = create_dummy_document()
# Process with fixed-size chunking
chunked_docs = perform_fixed_size_chunking(
document,
chunk_size=1000,
chunk_overlap=200
)
# Display results
print("\n----- CHUNKING RESULTS -----")
print(f"Total chunks: {len(chunked_docs)}")
# Print an example chunk
print("\n----- EXAMPLE CHUNK -----")
middle_chunk_idx = len(chunked_docs) // 2
example_chunk = chunked_docs[middle_chunk_idx]
print(f"Chunk {middle_chunk_idx} content ({len(example_chunk.page_content)} characters):")
print("-" * 40)
print(example_chunk.page_content)
print("-" * 40)
print(f"Metadata: {example_chunk.metadata}")
# For integration with Databricks Vector Search
print("\nThese documents are ready for embedding and storage in Databricks Vector Search")
print("Example next steps:")
print("1. Create embeddings using the Databricks embedding endpoint")
print("2. Store documents and embeddings in Delta table")
print("3. Create Vector Search index for retrieval")
Embeddings
Un embedding convierte texto (palabra, oración, página) en un vector denso de alta dimensión que codifica el significado, de modo que las ideas similares se agrupen.
Cuatro fases dentro de RAG
- Lado del corpus. Insertar embedding en cada fragmento; almacenar el índice y el vector.
- Lado de la consulta. Insertar embedding en el mensaje del usuario para que la solicitud tenga una huella semántica.
- Búsqueda. Buscar en el almacenamiento los vecinos más cercanos, generalmente mediante similitud de coseno.
Dónde se almacenan los vectores
Se trata de un almacenamiento especializado para embeddings, no de una base de datos relacional u objetiva general. AlloyDB, Pinecone y Qdrant son ejemplos comunes; muchos equipos utilizan pgvector con PostgreSQL.
Dónde tiene dificultades el RAG vectorial tradicional
- Los recortes pueden ignorar el significado. Las ventanas relacionadas pueden separarse; una mayor superposición ayuda a veces, pero no es una solución universal.
- La similitud puede pasar por alto las paráfrasis: frases como “las ventas disminuyeron” y “la empresa está en declive” podrían no quedar cerca en el espacio vectorial.
- Los hechos de múltiples pasos fallan cuando la causa y el efecto se encuentran en fragmentos diferentes y solo se recupera uno de ellos.
- Crear, almacenar, indexar y volver a indexar embeddings es costoso.
También: una base de datos vectorial no es sinónimo de RAG. Es un tipo de backend de recuperación. El RAG sin vectores mantiene el enfoque “obtener luego generar”, eliminando la búsqueda mediante embeddings.
¿Por qué evitar los vectores? El costo de creación/reindexación de embeddings, un comportamiento deficiente en búsquedas exactas para IDs/números/códigos de error, y la necesidad de infraestructura adicional para su funcionamiento.
El enfoque sin vectores es una familia de métodos, no una única solución:
- Búsqueda léxica. BM25, Postgres
tsvector, Elasticsearch: los términos exactos son más eficaces que las semánticas difusas para SKUs, citas y líneas de registro.
from rank_bm25 import BM25Okapi
def vectorless_retrieve(query, corpus_chunks, top_k=3):
"""
Lexical retrieval over raw text chunks - no embeddings, no vector DB.
"""
tokenized_corpus = [chunk.lower().split() for chunk in corpus_chunks]
bm25 = BM25Okapi(tokenized_corpus)
tokenized_query = query.lower().split()
scores = bm25.get_scores(tokenized_query)
ranked = sorted(zip(corpus_chunks, scores), key=lambda x: x[1], reverse=True)
return [chunk for chunk, score in ranked[:top_k]]
- Recuperación mediante agentes/herramientas. Sin indexación previa; el modelo busca, llama a APIs de búsqueda o abre secciones según sea necesario, al igual que un agente de programación que explora un repositorio. Búsqueda en tiempo real basada en razonamiento.
Límites de los métodos sin vectores
Las palabras clave siguen sin capturar las paráfrasis, a veces incluso peor que los embeddings. Los bucles de tipo agente añaden latencia y más tokens por consulta. Los corpus enormes siguen favoreciendo un índice de vectores bien estructurado. Los métodos sin vectores suelen ser más eficaces a escala pequeña o media, o cuando la exactitud es más importante que la vaguedad.
GraphRAG
El RAG clásico puede separar la causa del efecto dentro de los diferentes fragmentos. Los métodos sin vectores sustituyen los embeddings por palabras clave o contexto largo. Ninguno de estos enfoques modela cómo se relacionan las ideas. Graph RAG busca subsanar esa deficiencia.
En lugar de preguntar “¿qué fragmento está más cerca?”, pregúntese “¿cómo se conectan estos conceptos?”. La respuesta es un grafo de conocimiento.
Indexación: hacer crecer el grafo
Omite primero los fragmentos o incrustaciones. Pasa el documento por un modelo que extrae entidades y relaciones. El resultado son nodos y aristas.
En ese largo libro teórico podrías obtener nodos como Marx, Engels, El Capital, El Manifiesto Comunista, plusvalía, materialismo dialéctico, y aristas como escribió, coescribió, introduce-concepto.
Las entidades se convierten en nodos; las relaciones, en aristas. Estás mapeando el significado, no dividiendo páginas.
Agrupa los nodos estrechamente vinculados en comunidades (Leiden es popular), luego resume cada comunidad con otra pasada del modelo.
Opera en dos niveles:
- Nodos para hechos específicos y enlaces directos
- Comunidades para temas y resúmenes
Preguntas específicas → nodos. Preguntas generales sobre “ideas centrales” → resúmenes comunitarios. Un índice, dos modos de recuperación.
Consultas: recorrer el grafo
Pregunta: “¿Quiénes coescribieron con Marx y qué escribieron juntos?”
RAG tradicional tiene dificultades: la coautoría está en un mismo fragmento, mientras que las obras se encuentran a cientos de páginas de distancia; hay que confiar en que los embeddings coincidan.
RAG basado en grafo recorre:
- Detectar a Marx como punto de anclaje
- Cargar el nodo y las aristas de Marx
- Siguir la relación coescribió → Engels
- Siguir las obras escritas por Engels → Manifiesto, La condición de la clase obrera y otras obras relacionadas
Las aristas direccionales mantienen intactas las relaciones. Las causas y efectos que en el RAG clásico se separan se convierten en nodos conectados. Ese patrón de múltiples saltos es difícil de manejar de forma limpia tanto en el RAG básico como en aquel sin vectores.
Se reúnen nodos, aristas y resúmenes comunitarios para formar el contexto; luego se genera el resultado como de costumbre.
from graphrag import GraphRAGPipeline
# Indexing — runs once
pipeline = GraphRAGPipeline(llm="claude-3", graph_store="neo4j")
pipeline.index(documents=["book.pdf"])
# Under the hood: entity extraction → graph build → community detection → summaries
# Querying
result = pipeline.query(
"Who co-wrote with Marx and what did they write together?",
mode="global" # uses community summaries for broad questions
# mode="local" # uses node-level traversal for specific facts
)
print(result.answer)
print(result.sources) # returns actual nodes + edges used, fully traceable
mode no es algo estético. Las implementaciones (incluida la pila de código abierto de Microsoft) distinguen entre enfoques globales y locales porque se trata de estrategias diferentes.
Costos de GraphRAG
La extracción es costosa. Es necesario leer todo el documento; los libros largos consumen muchos tokens; las referencias implícitas (“como se discutió anteriormente”) podrían no convertirse nunca en conexiones entre nodos.
La calidad del grafo limita la calidad del resultado. Una mala extracción genera un grafo deficiente, lo que a su vez afecta negativamente la recuperación de información. Las soluciones suelen requerir volver a leer todo, lo cual resulta problemático a gran escala.
En la práctica, elija el método de recuperación según el tipo de consulta en lugar de forzar siempre el mismo enfoque para cada pregunta.
Resumen: utilice RAG clásico para búsquedas semánticas, sin vectores cuando necesite precisión o menos recursos infraestructurales, y Graph RAG cuando las relaciones entre datos sean el elemento clave.
Elegir un estilo de recuperación sin dogmas
Una secuencia útil para tomar decisiones se ve así.
Comience con el RAG vectorial clásico cuando su corpus sea grande, el lenguaje varíe mucho y lo que suele necesitar son vecinos semánticos aproximados. Invierta desde el principio en la calidad del fragmentado y en los procesos de actualización de embeddings; esos son los factores que determinan la calidad.
Opte por técnicas sin vectores cuando los identificadores exactos sean más importantes que las paráfrasis, cuando el corpus sea lo suficientemente pequeño como para permitir búsquedas léxicas o contextos extensos, o cuando no quiera asumir los costos de la infraestructura de embeddings. BM25 y similares no son “obsoletos”: son la herramienta adecuada para códigos de producto, citas y cadenas de errores.
Utilice Graph RAG cuando la pregunta sobre el producto sea relacional: quién se conectó con quién, qué concepto introdujo qué idea, qué comunidad resume un tema. Espere costos de indexación más altos y trate la calidad de la extracción como una dependencia de primer orden.
Muchos equipos terminan adoptando un enfoque híbrido: un primer paso léxico, un segundo paso vectorial y un modelo gráfico para un subconjunto de dominios con múltiples saltos. El objetivo no es elegir una única metodología, sino adaptar el mecanismo de recuperación al tipo de fallos que realmente se presentan.
Notas operativas que las demostraciones omiten
Los horarios de reindexación son importantes. Los embeddings obsoletos deterioran silenciosamente el rendimiento del RAG vectorial, incluso cuando el modelo no ha cambiado. Los ajustes de superposición son cruciales cuando las ventanas adyacentes comparten significado. Los filtros de metadatos son esenciales cuando los usuarios no deben ver nunca los fragmentos de otros. Los conjuntos de evaluación son importantes cuando se afirma que algo es “mejor” sin etiquetas.
Para Graph RAG, hay que planificar intentos repetidos de extracción, actualizaciones parciales del grafo y resúmenes comunitarios cuando los documentos cambian. En el caso de la recuperación agente, es necesario establecer límites en cuanto al uso de herramientas y políticas de tiempo de espera, para que un modelo curioso no agote toda la reserva de tokens en una sola consulta.
Ninguna de estas notas es glamorosa. Representan la diferencia entre un diagrama y un sistema que puede resistir un mes de tráfico real.
Lecturas relacionadas
- Memoria de agente híbrido: fusionar BM25 y búsqueda vectorial con RRF en Python — Aprenda por qué la búsqueda vectorial pura falla como memoria de agente, cómo la fusión de rango recíproco combina BM25 y resultados densos en Python, y cuándo los resúmenes de GraphRAG son útiles.
- Explicación de la generación aumentada con recuperación: solucionando las deficiencias en el conocimiento de LLM — Entienda por qué los LLM generan información errónea y se vuelven obsoletos, y luego vea paso a paso cómo RAG recupera datos, los divide en fragmentos, los incrusta y mejora las solicitudes para solucionarlo.