RAG-Anything con 65 libros sobre vinos: dónde la recuperación de grafos tiene éxito y donde falla.
Una prueba práctica del grafo de conocimientos de RAG-Anything con 65 PDFs sobre vinos: elección del analizador, costos, respuestas basadas en grafos frente a vectores, y el punto ciego relacionado con los vinos espumosos.
La forma más segura de juzgar un framework es crear algo real con él, poner a prueba sus límites y medir qué sucede, en lugar de confiar en pruebas de rendimiento o en un archivo README. Aquí es donde RAG-Anything recibe ese trato. Se trata de un proyecto de código abierto de la HKU que afirma poder generar automáticamente un grafo de conocimiento a partir de documentos multimodales, y el material de prueba es una biblioteca personal de libros sobre vinos recopilada por alguien que completó los cursos y exámenes WSET y simplemente le apasiona el tema.
Por qué el vino es una buena prueba de estrés
El vino es un ámbito naturalmente relacionado. Las taninas determinan cuán bien envejece un vino, la acidez interactúa con las taninas, y las levaduras impulsan la fermentación que produce compuestos de sabor. Las uvas están vinculadas a las regiones, las regiones a los climas y los climas a los estilos. Si la extracción estructurada de entidades y relaciones puede superar a la búsqueda vectorial plana en cualquier contexto, debería hacerlo también aquí. La pregunta a evaluar es: ¿logra RAG-Anything crear un grafo de conocimiento útil a partir de libros PDF reales, y responde mejor a preguntas sobre vino que la búsqueda vectorial simple?
En su interior, RAG-Anything rodea a LightRAG. Sobre esta base se añaden el análisis multimodal (PDF, DOCX, imágenes), la extracción automática de entidades y relaciones para formar un grafo, y cinco modos de consulta que equilibran el recorrido del grafo con la recuperación vectorial. Los analizadores incluyen MinerU, Docling y PaddleOCR; funcionan varios backends de LLM, y las tablas, imágenes y ecuaciones se procesan en el momento de la ingesta. Con más de 15,000 estrellas en GitHub, ha recibido verdadera atención, aunque las estrellas apenas son la primera métrica a considerar. A continuación: resultados medidos, código adaptable y una visión de cuándo RAG basado en grafo resulta útil y cuándo no.
Arquitectura
Existen dos fases: la ingesta y la consulta. La ingesta hace pasar un PDF por este pipeline, comenzando con un analizador de documentos, ya sea MinerU (basado en VLM) o el más ligero y económico Docling:
PDF Document
|
v
[Document Parser] ─── MinerU (VLM-based) or Docling (lighter/cheaper)
|
v
[Content Extraction] ─── text, tables, images, equations
|
v
[Text Chunking] ─── split into manageable pieces
|
v
[LLM Entity/Relation Extraction] ─── LLM extracts entities + relationships
|
├──> [Knowledge Graph] ─── entities as nodes, relations as edges (GraphML)
└──> [Vector Embeddings] ─── chunks embedded for similarity search (JSON)
Un LLM lee cada fragmento y extrae entidades como “Cabernet Sauvignon”, “fermentación maloláctica” y “Bordeaux”, además de los vínculos entre ellas: el Cabernet Sauvignon crece en Burdeos, y la fermentación maloláctica suaviza la acidez. Los resultados se almacenan como nodos y aristas en un grafo GraphML persistente.
La consulta ofrece cinco modos, cada uno con su propia estrategia de recuperación. La tabla enumera qué busca cada uno, para qué es adecuado y cuáles son sus puntos débiles:
+--------+------------------------------+-------------------------------+---------------------------------+
| Mode | What It Searches | Best For | Weakness |
+--------+------------------------------+-------------------------------+---------------------------------+
| naive | Vector similarity only | Factoid questions, robustness | Misses relational structure |
| local | Graph neighborhood traversal | Entity-specific deep dives | Blind to entities not extracted |
| global | Community-level summaries | Broad thematic questions | Less specific, slower |
| hybrid | local + global | Balanced depth and breadth | No vector fallback |
| mix | Graph + vector together | General-purpose (recommended) | Slowest mode |
+--------+------------------------------+-------------------------------+---------------------------------+
En cuanto a los modelos, GPT-4o-mini se encarga de la generación de texto, lo que permite que los cientos de llamadas a modelos de lenguaje grande durante la extracción de entidades sean económicas. text-embedding-3-small genera embeddings, y el procesamiento de imágenes está desactivado para reducir costos:
from openai import AsyncOpenAI
from lightrag.utils import EmbeddingFunc
from raganything import RAGAnythingConfig
aclient = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Text LLM - handles entity extraction and answer synthesis
async def llm_model_func(prompt, system_prompt=None, history_messages=None, **kwargs):
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
if history_messages:
messages.extend(history_messages)
messages.append({"role": "user", "content": prompt})
response = await aclient.chat.completions.create(
model="gpt-4o-mini", messages=messages, temperature=0.0,
)
return response.choices[0].message.content
# Embeddings - 1,536 dimensions, 8K token window
async def _embed_texts(texts, **kwargs):
response = await aclient.embeddings.create(model="text-embedding-3-small", input=texts)
return np.array([item.embedding for item in response.data])
embedding_func = EmbeddingFunc(embedding_dim=1536, max_token_size=8192, func=_embed_texts)
# Configuration - Docling parser, tables enabled, images disabled for cost
rag_config = RAGAnythingConfig(
working_dir="./rag_storage",
parser="docling",
enable_image_processing=False, # skipping images - valid for my use-case
enable_table_processing=True,
enable_equation_processing=True,
)
El costo representa un verdadero compromiso. Todas las páginas de todos los libros se analizan, se dividen en fragmentos y se procesan con GPT-4o-mini para su extracción, y la posibilidad de manejar imágenes añadiría llamadas visuales a GPT-4o encima de todo esto. Para este experimento, el texto y las tablas fueron suficientes.
El corpus y los analizadores
La colección consta de 65 PDFs seleccionados para abarcar diferentes enfoques. Los libros de WSET son materiales didácticos estructurados con tablas y vocabulario técnico, mientras que los demás tienen un estilo más conversacional, compuesto por consejos en formato de lista o historias sobre la elaboración del vino. Esa combinación sirve para probar si el modelo puede sintetizar información a partir de distintos estilos de escritura y niveles de complejidad.
MinerU, el analizador predeterminado, depende de un modelo visión-lenguaje para leer el diseño de la página. Es exhaustivo pero lento. La primera vez que se utiliza, descarga el VLM en unos 7 minutos, y cada página tarda aproximadamente 15 segundos en procesarse en la CPU; por lo tanto, un libro de 50 páginas requirió alrededor de 20 minutos de análisis antes de comenzar la extracción. Los libros de 300 páginas lo hicieron colapsar por completo, incluso al dividirlos en secciones de 50 páginas, debido a que el OCR se agotó el tiempo asignado.
Docling, un analizador más ligero que no requiere VLM, gestionó el mismo libro sin colapsos y completó todo el proceso de extracción, por lo que se utilizó para los libros restantes. La lección práctica es que MinerU ofrece una comprensión más detallada del diseño, como la detección de figuras y la distinción más precisa entre encabezados y texto principal, mientras que Docling es la opción realista para configuraciones con solo CPU. Se planea realizar una nueva ejecución en una GPU local.
El código de ingestión es breve, con una llamada por documento:
from raganything import RAGAnything
rag = RAGAnything(
config=rag_config,
llm_model_func=llm_model_func,
embedding_func=embedding_func,
)
for pdf_path in sorted(Path("./data").glob("*.pdf")):
file_start = time.time()
await rag.process_document_complete(
file_path=str(pdf_path),
output_dir="./output",
)
print(f"Completed {pdf_path.name} in {time.time() - file_start:.1f}s")
await rag.finalize_storages()
El marco maneja internamente la análisis sintáctico, la división en fragmentos, la extracción de entidades, la construcción de grafos y el embedding vectorial. La API está bien diseñada, casi hasta el punto de simplificarla en exceso.
Todos los libros se extrajeron en 6 horas a una velocidad promedio de 30 páginas por minuto. El análisis sintáctico no es el cuello de botella; sí lo son los cientos de llamadas a modelos LLM para la extracción de entidades, ya que cada fragmento pasa por GPT-4o-mini para identificar entidades y relaciones. Aun así, la factura por el uso de los modelos LLM, sin incluir imágenes, fue baja, alrededor de 5 dólares para todo el experimento, incluidas las pruebas de evaluación. La tabla que sigue a la procesamiento resume el tamaño del grafo:
+----------------------------+------------------------------+
| Metric | Value |
+----------------------------+------------------------------+
| Entities (graph nodes) | 37,132 |
| Relations (graph edges) | 47,650 |
| Text chunks | 6,247 |
| Storage on disk | ~185 MB (all JSON + GraphML) |
| Indexed documents | 65 |
| Load time at query startup | ~10 seconds |
+----------------------------+------------------------------+
No se utiliza ninguna base de datos externa. Un archivo GraphML almacena el grafo, mientras que los archivos JSON se encargan de los almacenamientos vectoriales y los datos clave-valor. Toda la base de conocimientos cabe en una imagen Docker sin necesidad de infraestructura adicional.
Gráfico versus vector
El experimento central envió 6 consultas a través de dos modos: local (solo recorrido del grafo) y naive (solo similitud vectorial), y luego comparó las respuestas. Se seleccionaron consultas para poner a prueba el razonamiento relacional, comparativo, causal y entre documentos.
async def compare_modes(rag, query):
"""Compare different retrieval modes on the same query."""
for mode in ["local", "naive"]:
result = await rag.aquery(query, mode=mode)
print(f"[{mode}] {len(result)} chars, {elapsed:.1f}s")
A continuación se presentan los resultados de cada consulta, junto con los tiempos de ejecución.
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
| Query | Graph (local) | Vector (naive) | Winner |
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
| How does soil type influence wine character? | 32.5s / 3,464 chars | 28.2s / 2,531 chars | Tie |
| Relationship between tannins, acidity, and aging? | 24.8s / 2,266 chars | 25.8s / 2,289 chars | Graph (structure) |
| Compare red vs. white winemaking | 32.9s / 3,119 chars | 42.0s / 3,423 chars | Graph (speed + structure). |
| What role does yeast play in fermentation? | 27.8s / 2,587 chars | 26.8s / 2,403 chars | Tie |
| How do fortified wines differ from table wines? | 28.5s / 2,635 chars | 23.1s / 2,597 chars | Tie |
| Sparkling wine production methods? | 10.7s / 48 chars | 48.4s / 2,900 chars | Vector (graph fails) |
+---------------------------------------------------+----------------------+---------------------+-------------------------------+
Taninos, acidez y envejecimiento. Una pregunta relacional clásica. El grafo presenta aristas explícitas desde los taninos hacia el potencial de envejecimiento, desde la acidez hacia la frescura, y desde ambos hacia la calidad. Seguir esas aristas permite obtener una descripción estructurada de cómo interactúan los taninos y la acidez, lo que de otro modo implicaría combinar fragmentos que podrían estar muy separados en el texto original.
Elaboración de vinos tintos frente a vinos blancos. El grafo expresa esta diferencia a través de enlaces entre entidades: la elaboración de vinos tintos y la de vinos blancos se conectan cada una con entidades de procesos comunes (fermentación, prensado, maduración) mediante aristas de relación diferentes. Respondió en 32,9 segundos, mientras que la búsqueda vectorial tardó 42,0 segundos, y el resultado fue más organizado.
Vinos fortificados frente a vinos de mesa. Esto requiere síntesis entre varios documentos, y el grafo conectó de manera fluida las entidades a través de los límites entre documentos.
En preguntas simples basadas en hechos, como cómo el suelo influye en las características del vino o qué función cumple la levadura en la fermentación, los dos modos dieron respuestas comparables. Los fragmentos contienen esa información directamente, por lo que la similitud vectorial recupera los pasajes con la misma eficacia que el recorrido por el texto.
El fallo más importante
Ante una pregunta sobre los métodos de producción de vinos espumosos, el modo gráfico respondió que no contaba con suficiente información. Esa respuesta constó de 48 caracteres y tardó 10.7 segundos en generarse. El modo vectorial, en cambio, proporcionó una respuesta detallada de 2,900 caracteres que abarcaba los métodos tradicional, Charmat, de transferencia y ancestral, en 48.4 segundos.
La extracción de entidades había pasado por alto los conceptos clave relacionados con los vinos espumosos. No existían nodos para “méthode champenoise”, “método de tanque” ni “riddling”, por lo que una consulta en el grafo sobre la producción de vinos espumosos no encontró nada sobre lo que trabajar. El grafo era incapaz de detectar ese tema. La búsqueda vectorial simplemente localizaba los fragmentos de texto que contenían esos términos y los pasaba al LLM, y tolera los fallos en la extracción ya que funciona con texto sin procesar en lugar de un conjunto de entidades cuidadosamente seleccionado.
La limitación fundamental es la siguiente: cuando la extracción omite un concepto, las consultas en el grafo no pueden detectarlo, por lo que el grafo solo es tan completo como el proceso de extracción. La búsqueda vectorial no presenta este tipo de fallos. Por eso existe el modo mix, y por qué el uso exclusivo de local es una mala opción.
También merece atención el tiempo de ejecución. Las consultas a grafos pasaron de 10.7 segundos (en el caso de fallas que no encontraban nada rápidamente) a 32.9 segundos, mientras que las consultas vectoriales aumentaron de 23.1 a 48.4 segundos. La síntesis con LLM domina en ambos casos, no la recuperación de información, y el caché hace que las consultas repetidas sobre temas similares sean mucho más rápidas.
Examinando el grafo
Para inspeccionar el grafo en sí, una visualización interactiva creada con NetworkX y pyvis mostró las 120 entidades centrales más conectadas:
import networkx as nx
from pyvis.network import Network
G = nx.read_graphml("rag_storage/graph_chunk_entity_relation.graphml")
# Focus on the most connected nodes (hubs)
degree_dict = dict(G.degree())
top_nodes = sorted(degree_dict, key=degree_dict.get, reverse=True)[:120]
subG = G.subgraph(top_nodes).copy()
# Categorize nodes by wine domain keywords
def categorize(name):
lower = name.lower()
if any(k in lower for k in ["cabernet", "merlot", "pinot", "riesling", ...]):
return "grape" # Red nodes
if any(k in lower for k in ["bordeaux", "california", "champagne", ...]):
return "region" # Blue nodes
if any(k in lower for k in ["fermentation", "aging", "maceration", ...]):
return "process" # Green nodes
if any(k in lower for k in ["port", "sherry", "sparkling", ...]):
return "wine_type" # Orange nodes
return "general" # Purple nodes
La imagen colorea los nodos por categoría (rojo para uvas, azul para regiones, verde para procesos, naranja para tipos de vino, púrpura para conceptos generales) y ajusta el tamaño del nodo según la cantidad de conexiones.
Las 15 entidades más conectadas muestran lo que el gráfico capturó con precisión. “Vino”, una entidad general, lidera con 2,136 conexiones:
+--------------------+-------------+-----------+
| Entity | Connections | Category |
+--------------------+-------------+-----------+
| Wine | 2,136 | General |
| Wine Production | 1,344 | General |
| Italian Wines | 768 | General |
| Bordeaux | 442 | Region |
| Cabernet Sauvignon | 420 | Grape |
| Riesling | 412 | Grape |
| Champagne | 348 | Region |
| California | 321 | Region |
| Grapes | 287 | Grape |
| Port | 243 | Wine Type |
+--------------------+-------------+-----------+
Los patrones visibles incluyen un grupo denso de procesos de fermentación, variedades de uva que se relacionan con regiones específicas, y un subgrupo de vinos fortificados (Oporto, Jerez, Madeira) con sus propios nodos de producción. Las áreas escasas también son reveladoras. Los conceptos relacionados con los vinos espumosos están subrepresentados, lo que explica el fallo en la consulta. La colección también contiene pocos libros sobre vinos espumosos, por lo que la brecha podría deberse en parte a los datos disponibles.
Una pequeña interfaz de demostración
Para compartir y realizar demostraciones no técnicas, la interfaz de consulta puede envolverse en Gradio:
import gradio as gr
def query_wine(question, mode):
t0 = time.time()
result = _run_async(_query(question, mode))
elapsed = time.time() - t0
return result, f"**Mode:** {mode} | **Time:** {elapsed:.1f}s"
with gr.Blocks(title="Wine Knowledge RAG") as demo:
question = gr.Textbox(label="Ask a wine question", lines=2)
mode = gr.Radio(
choices=["mix", "local", "global", "hybrid", "naive"],
value="mix", label="Retrieval Mode",
)
submit_btn = gr.Button("Ask", variant="primary")
stats = gr.Markdown("")
answer = gr.Markdown(label="Answer")
submit_btn.click(fn=query_wine, inputs=[question, mode], outputs=[answer, stats])
La aplicación carga el grafo preconstruido en unos 20 segundos y no necesita GPU para las consultas, ya que todo el cálculo se realiza a través de llamadas a la API de OpenAI.
Lo que muestra el experimento
Graph RAG mejora claramente las respuestas para tres tipos de consultas:
- Preguntas relacionales, que buscan saber cómo una cosa influye en otra: los enlaces entre entidades significan que la respuesta no requiere que ambas compartan un fragmento de texto. La relación entre los taninos, la acidez y el envejecimiento fue un claro ejemplo de éxito.
- Comparaciones entre dos cosas: los contrastes se organizan por sí solos. La elaboración de vinos tintos y blancos reutilizó nodos de proceso a través de diferentes enlaces relacionales, lo que permitió obtener respuestas ordenadas una al lado de la otra.
- Síntesis a partir de varios libros: la búsqueda vectorial también puede manejarlo, pero el grafo muestra claramente las conexiones y no depende de que los embeddings estén cerca unos de otros.
Se destacan tres debilidades:
- La extracción de entidades es el eslabón débil. Los métodos relacionados con los vinos espumosos estaban presentes en el texto sin procesar, pero nunca se convirtieron en entidades del grafo, lo cual es una limitación de cualquier enfoque basado en extracción.
- Las consultas de tipo factoid no necesitan un grafo. Una pregunta como a qué temperatura almacenar el vino se resuelve perfectamente con búsquedas vectoriales, y el uso del grafo añade complejidad sin aportar beneficios.
- El costo y la latencia son reales. La ingesta de datos requiere cientos de llamadas a modelos LLM, y la latencia de las consultas, de 25 a 30 segundos, está determinada principalmente por la síntesis realizada por los modelos LLM.
Una matriz de decisión relaciona los tipos de consultas con las tres opciones: vectorial (ingenuo), basado en grafo (local) y mixto, siendo esta última la opción recomendada:
+----------------------------+----------------+---------------+-------------------+
| Query Type | Vector (naive) | Graph (local) | Mix (recommended) |
+----------------------------+----------------+---------------+-------------------+
| Factoid lookup | Good | Good | Good |
| Relational ("X affects Y") | OK | Best | Best |
| Comparative ("A vs B") | OK | Best | Best |
| Cross-document synthesis | OK | Good | Best |
| Topic with extraction gaps | Best | Fails | Good |
+----------------------------+----------------+---------------+-------------------+
Cuatro conclusiones prácticas:
- Use siempre el modo
mix. No se debe confiar en la recuperación basada únicamente en el grafo, y la falta de una cobertura completa lo demuestra. - Elija Docling en lugar de MinerU para implementaciones con solo CPU, ya que el VLM de MinerU es exhaustivo pero frágil.
- Asigne tiempo para la ingesta de datos, especialmente en el caso de corpus más grandes.
- Inspeccione el grafo. Verifique que los conceptos clave del dominio aparezcan realmente como entidades y evalúe las métricas estructurales del grafo antes de confiar en él.
Veredicto
RAG-Anything cumple su promesa principal. Extrae conocimiento estructurado de los PDFs y lo organiza en un grafo que mejora significativamente las consultas relacionales y comparativas: el análisis permitió obtener respuestas más organizadas sobre taninos, acidez y envejecimiento; la comparación entre vinos tintos y blancos se realizó de forma más rápida y ordenada; además, funcionó bien la síntesis de información de libros de diferentes editores. Es prometedor pero aún está en sus inicios, y la fiabilidad del analizador y el alcance de la extracción son aspectos que deben vigilarse. Quienes estén considerando usarlo en entornos de producción deberían repetir este experimento con sus propios datos, ya que un archivo README no mostrará dónde falla.
Leer más
- Comparación de 16 arquitecturas RAG: qué patrón se adapta a qué problema — Un recorrido por dieciséis patrones RAG en cuatro niveles, desde recuperación estándar e híbrida hasta diseños basados en agentes, grafos y federados, con un orden de construcción mes a mes.
- Observabilidad para agentes de IA empresarial con Langfuse y OCI — Por qué los registros, métricas y trazas son insuficientes para los agentes de GenAI, y cómo rastrear prompts, llamadas a herramientas, tokens y sesiones con Langfuse de código abierto en OCI Agents.