Saltearse al contenido

GraphRAG con Neo4j

En un grafo los datos están unidos: cada incidencia, con su cliente, su máquina y el técnico que la atiende. Un RAG de fragmentos de texto ve trozos sueltos; un grafo ve las relaciones. Por eso responde preguntas que cruzan datos o que cuentan, como «¿cuántas incidencias abiertas tiene Lucía?» o «¿qué cliente ha tenido más averías?».

En NappAI todo se hace con el componente Neo4j GraphRAG: guardar los datos en el grafo y responder a partir de él. Se conecta a tu base de datos Neo4j con una credencial Neo4j API (dirección, usuario y contraseña; consulta Credenciales).

Cuándo un grafo gana a los fragmentos

Lo medimos el 17 y el 18 de octubre de 2026 con seis preguntas de relaciones y recuentos sobre las incidencias de un servicio técnico (24 incidencias, 8 clientes, 4 técnicos, 4 modelos de máquina). Cada respuesta se dio por buena solo si era exacta.

Cómo se buscóRespuestas correctas
Fragmentos de los informes mensuales, búsqueda básica0 de 6
Fragmentos con búsqueda híbrida y Local Rerank (10 resultados)2 de 6
Los tres informes enteros dentro de las instrucciones del modelo3 de 6
Neo4j GraphRAG, operación Vector + Graph (10 resultados)3 de 6
Neo4j GraphRAG, operación Text2Cypher sin ejemplos5 de 6
Neo4j GraphRAG, operación Text2Cypher con 2 ejemplos5 de 6

Lo que muestra:

  • Los fragmentos mezclan estados. Una incidencia abierta en julio y cerrada en agosto aparece en dos informes, y el modelo la cuenta como abierta o se niega a responder.
  • Recorrer el grafo ayuda a cruzar datos, por ejemplo «las incidencias abiertas de este cliente y quién las lleva», pero solo ve los resultados que encuentra, así que no cuenta bien.
  • Solo la consulta al grafo (Text2Cypher) cuenta, ordena y filtra sobre todos los datos. Lo que más le ayuda es conocer los valores reales del grafo (que el estado se escribe abierta, que las fechas son de 2026), y el componente se los enseña: sin esos valores, y sin ejemplos, se quedaba en 2 de 6.
  • La pregunta que falla en todos los casos es «¿qué técnicos han reparado máquinas Vellani?»: la consulta devuelve también a un técnico cuya única incidencia Vellani sigue abierta. Entendida como «han trabajado en», sería correcta.
  • Poner todos los documentos en las instrucciones tampoco sirve para contar: el modelo se equivocó en los dos recuentos.

Son solo seis preguntas sobre datos de prueba: muestran la diferencia entre técnicas, no garantizan el resultado con los tuyos.

Míralo funcionando

El vídeo guarda el historial de un servicio técnico de cafeteras en Neo4j de las dos maneras, y luego monta un agente que lo consulta sin escribir Cypher: 24 incidencias, 8 bares, 4 técnicos, 4 máquinas y 72 relaciones.

Lo que ves en el vídeo

  1. Guardar registros (flujo «Neo4j GraphRAG · guardar registros»): Storage Files lee incidencias.json con Content en Text, y Neo4j GraphRAG en la operación Add Records guarda cada incidencia con Node Label Incidencia, Key Property codigo y tres líneas en Relationships que la unen a su bar, su técnico y su máquina. Al ejecutarlo: 24 incidencias, 72 relaciones y los dos índices de búsqueda creados solos, en menos de un segundo.
  2. Graph Schema enseña lo que hay ahora en el grafo: las etiquetas con cuántos nodos tiene cada una, sus propiedades con valores de ejemplo, las relaciones y los índices.
  3. Guardar documentos (flujo «Neo4j GraphRAG · guardar documentos»): Storage Search encuentra los tres informes mensuales en PDF, Storage Files los lee en lote y Neo4j GraphRAG en la operación Build Graph saca las entidades con DeepSeek, guiado por Allowed Patterns, Node Types y Extraction Instructions. Resultado: 40 nodos y 72 relaciones a partir del texto.
  4. El agente (flujo «Neo4j GraphRAG · agente») tiene tres herramientas de Neo4j GraphRAG: «Incidencias de un técnico» en Cypher Query, con una consulta fija cuyos $tecnico y $estado rellena el agente; «Casos parecidos» en Hybrid + Graph con la Retrieval Query vacía, que trae solo los vecinos de cada resultado; y «Consultar el grafo» en Text2Cypher, con dos ejemplos en Examples.
  5. «¿Qué incidencias abiertas tiene Lucía Ortega?»: el agente llama a la consulta fija con tecnico Lucía Ortega y estado abierta, y responde con las tres, cada una con su bar y su máquina.
  6. «Una Aurora no dosifica y marca E07: ¿ha pasado antes y quién lo arregló?»: la búsqueda encuentra los dos casos iguales, resueltos por Javier Ruiz y Rocío Benítez cambiando el caudalímetro VL-1180.
  7. «¿Cuántas incidencias ha tenido cada marca?»: Text2Cypher escribe la consulta que cuenta (Vellani 9, Jaltec 8, Molinar 7). Puedes ver la consulta que escribió al abrir la herramienta.

Cómo guardar datos en Neo4j

Un grafo responde tan bien como esté construido. Hay tres formas de guardar los datos, según de dónde vengan. Las tres se hacen con Neo4j GraphRAG, sin escribir Cypher, y crean solas los índices de búsqueda.

Registros estructuradosGrafo extraído de documentosFragmentos enlazados a entidades
OperaciónAdd Records con RelationshipsBuild Graph con esquemaBuild Graph con Save Source Chunks
Para datos que…ya tienen campos: tablas, hojas, bases de datos, otras aplicacionesestán en texto: informes, partes de trabajo, correosson textos largos de consulta: manuales, políticas, preguntas frecuentes
Usa un modelo de IANoUna lectura por documentoUna lectura por fragmento
Lo que medimos24 filas: 0,8 s, ninguna llamada al modelo9 documentos: 10 lecturas, 11 s80 fragmentos de 800 caracteres: 80 lecturas, 21 s
Calidad del grafoExacta: 24 incidencias, 8 clientes, 4 técnicos, 4 máquinas, 72 relaciones correctasLa misma, con el esquema completo: 72 relaciones correctas y el estado final de cada incidenciaLas mismas entidades y, además, 80 fragmentos enlazados a las entidades que mencionan
Qué permiteText2Cypher, Cypher Query y búsquedas «+ Graph» exactasLo mismo, a partir de textoBuscar pasajes como una base vectorial y llegar desde ellos a las entidades

1. Registros estructurados: Add Records

Si tus datos ya tienen columnas, es la forma más exacta y barata: no interviene ningún modelo. Cada fila se convierte en un nodo de tipo Node Label, identificado por Key Property, y las columnas que nombres en Relationships se convierten en nodos enlazados:

cliente -> DE_CLIENTE -> Cliente.nombre
tecnico -> ASIGNADA_A -> Tecnico.nombre
modelo -> EN_MAQUINA -> Maquina.modelo (marca)

Cada línea dice: el valor de la columna cliente es el nombre de un nodo Cliente, y la incidencia se une a él con DE_CLIENTE. Si dos filas tienen el mismo cliente, comparten el nodo. Las columnas entre paréntesis (marca) se guardan en el nodo enlazado.

Con las 24 incidencias de prueba, el grafo resultante fue idéntico al que montamos a mano: los mismos nodos y las mismas 72 relaciones, sin una de más ni de menos. Repetir la carga no duplica nada, y si una fila cambia de técnico, la relación antigua se sustituye. Si necesitas algo que no cabe en esas tres líneas, como guardar la fecha como fecha, Write Query te deja escribir la consulta completa (consulta el ejemplo).

2. Grafo extraído de documentos: Build Graph con esquema

Cuando la información está en texto, un modelo de IA la lee y extrae las entidades y relaciones. La clave es darle un esquema. Lo medimos con los 9 documentos de la base de conocimiento (3 manuales, una política de garantía, una guía, unas preguntas frecuentes y 3 informes mensuales de incidencias), con DeepSeek:

EsquemaLo que quedó en el grafo
Ninguno252 nodos de 48 tipos distintos para 4 tipos reales («Incident» e «Incidencia», «Client» y «Cliente»…), 58 tipos de relación y una incidencia partida en dos. El modelo escribió más de 4 veces más texto y tardó 34 s.
Node Types y Relationship Types (o solo Allowed Patterns)24 incidencias, 8 clientes y 4 técnicos, con las 72 relaciones correctas. Pero 14 máquinas: el modelo tomó recambios de los manuales por máquinas y escribió el mismo modelo con y sin marca.
Además, las propiedades de cada tipo en Node TypesDesaparecen los recambios: 7 máquinas, aún con el modelo escrito con y sin marca.
Además, una línea en Extraction Instructions4 máquinas. El grafo coincide con el de los registros: 24, 8, 4 y 4 nodos y 72 relaciones correctas.
Además, los valores del estado en Node TypesCada incidencia queda con su estado final aunque se cerrase un mes después, y una lista de «pendientes» no cambia abierta por pendiente.

El esquema que dio el mejor resultado:

  • Allowed Patterns:

    (Incidencia)-[:DE_CLIENTE]->(Cliente)
    (Incidencia)-[:ASIGNADA_A]->(Tecnico)
    (Incidencia)-[:EN_MAQUINA]->(Maquina)
  • Node Types: Incidencia(codigo, fecha, estado: abierta | cerrada, codigo_error), Cliente(nombre), Tecnico(nombre), Maquina(modelo, marca). La primera propiedad de cada tipo es la que lo identifica: dos menciones con el mismo codigo son la misma incidencia.

  • Extraction Instructions: «El modelo de una Maquina se escribe sin la marca (Brío 2G, Aurora 3G, M64 OD); la marca va en marca.»

Con él, Text2Cypher respondió bien 5 de las 6 preguntas, igual que con el grafo de registros.

3. Fragmentos enlazados a entidades: Save Source Chunks

Con Save Source Chunks, además de las entidades, cada texto que entra se guarda como un fragmento enlazado con las entidades que menciona y con el fragmento siguiente del mismo documento. Conecta antes un divisor de texto, como Recursive Character Text Splitter, para que cada fragmento sea un pasaje corto.

Con los 9 documentos partidos en 80 fragmentos:

  • Buscar en los fragmentos (escribe chunk_vector en Vector Index y chunk_text en Full-text Index) funcionó como una base vectorial con los mismos fragmentos: en 30 preguntas de consulta (cómo se hace algo, qué es una pieza, qué dice una política o un informe), el pasaje con la respuesta estaba entre los 5 primeros resultados en el 83 % de los casos, y era el primero en el 63 % con Hybrid.
  • Con Hybrid + Graph, cada fragmento llega con las incidencias, clientes y máquinas que menciona.
  • Cuesta una lectura del modelo por fragmento (80 frente a 10) y el modelo ve menos contexto en cada una: apareció una incidencia de más, sin código.

Cuál elegir

  • Datos con campos: Add Records. Siempre que puedas sacar los datos como filas, es lo más exacto.
  • Datos en documentos: Build Graph con esquema completo. Sin esquema, el grafo sirve para explorar, no para contar.
  • Textos de consulta (cómo se hace algo, qué dice una política): una base vectorial o Save Source Chunks si además quieres llegar a las entidades desde los pasajes.
  • Se pueden combinar en el mismo grafo: los registros con Add Records y los manuales con Save Source Chunks.

Las operaciones de Neo4j GraphRAG

OperaciónQué hacePregunta de ejemplo
Add RecordsGuarda filas como nodos y relaciones.—
Build GraphExtrae entidades y relaciones de documentos.—
VectorBusca por significado.«Averías de goteo»
HybridBusca por significado y por palabras.«Incidencias con la junta del grupo»
Vector + Graph / Hybrid + GraphLo mismo, con los nodos relacionados con cada resultado.«Una cafetera pierde agua por debajo»
Text2CypherEl modelo escribe la consulta que responde.«¿Cuántas incidencias abiertas tiene Ana Ruiz?»
Cypher QueryEjecuta tu consulta con los valores que le den.Las incidencias de un técnico en un estado
Graph SchemaDescribe lo que contiene el grafo.—

Buscar sin saber Cypher

No necesitas escribir consultas para sacar partido al grafo:

  • Vector + Graph e Hybrid + Graph con Retrieval Query vacía: cada resultado llega con los nodos con los que está conectado, en líneas fáciles de leer:

    Incidencia INC-2608-07 (fecha: 2026-08-26, estado: abierta, codigo_error: E01): La máquina mostraba E01 por falta de agua en la red…
    EN_MAQUINA -> Maquina Aurora 3G (marca: Vellani)
    DE_CLIENTE -> Cliente Bar El Postigo
    ASIGNADA_A -> Tecnico Manuel Campos
  • Graph Schema: describe el grafo con los tipos de nodo y cuántos hay, sus propiedades con valores de ejemplo (estado: one of 'cerrada', 'abierta', fecha: from '2026-07-03' to '2026-09-29'), las relaciones y los índices. Es lo primero que conviene mirar antes de escribir ejemplos o consultas.

  • Text2Cypher: el modelo escribe la consulta. Si falla o no encuentra nada, la revisa una vez.

  • Cypher Query: alguien que sepa Cypher escribe la consulta una vez, con huecos como $tecnico, y a partir de ahí la usa cualquiera: el nodo muestra un campo por hueco y, como herramienta, el agente los rellena.

Los nombres de los índices tampoco hacen falta: al guardar se crean como incidencia_vector e incidencia_text, y al buscar con los campos vacíos se usa el único índice de cada clase que haya.

Índices de búsqueda

Add Records y Build Graph crean los índices al guardar, si no existen:

  1. Con un modelo en Embedding, el índice de búsqueda por significado (Vector Index, por defecto <tipo>_vector).
  2. El índice de búsqueda por palabras (Full-text Index, por defecto <tipo>_text), en el idioma de Full-text Analyzer. En Add Records cubre el texto y las columnas cortas, como el código o el estado, así que una pregunta por «INC-2608-07» encuentra su incidencia.
  3. Si un índice con ese nombre ya existe para otros datos o con vectores de otro tamaño (otro modelo de embeddings), el componente lo avisa en lugar de mezclarlos.

Si tu grafo ya existía en Neo4j y prefieres crear los índices a mano desde Neo4j Browser o Neo4j Aura, estas son las consultas equivalentes para nodos Incidencia:

CREATE VECTOR INDEX incidencia_vector IF NOT EXISTS
FOR (n:Incidencia) ON n.embedding
OPTIONS {indexConfig: {`vector.dimensions`: 768, `vector.similarity_function`: 'cosine'}}
CREATE FULLTEXT INDEX incidencia_text IF NOT EXISTS
FOR (n:Incidencia) ON EACH [n.text]
OPTIONS {indexConfig: {`fulltext.analyzer`: 'spanish'}}

Recetas de Retrieval Query

En las operaciones «+ Graph», Retrieval Query es opcional: vacía, cada resultado trae sus nodos relacionados. Escríbela cuando quieras elegir qué traer. Se ejecuta sobre cada resultado: node es el nodo encontrado y score su puntuación.

Traer solo algunos nodos relacionados (la del vídeo). OPTIONAL MATCH mantiene el resultado aunque falte alguna relación:

OPTIONAL MATCH (node)-[:DE_CLIENTE]->(c:Cliente)
OPTIONAL MATCH (node)-[:ASIGNADA_A]->(t:Tecnico)
OPTIONAL MATCH (node)-[:EN_MAQUINA]->(m:Maquina)
RETURN node.text AS text, node.codigo AS codigo, node.estado AS estado,
toString(node.fecha) AS fecha, c.nombre AS cliente, t.nombre AS tecnico,
m.modelo AS maquina, score

Filtrar por estado, por ejemplo para quedarte solo con las incidencias cerradas, que son las que tienen solución. El filtro se aplica a los resultados encontrados, así que sube Number of Results si filtras mucho:

WITH node, score WHERE node.estado = 'cerrada'
OPTIONAL MATCH (node)-[:ASIGNADA_A]->(t:Tecnico)
RETURN node.text AS text, node.codigo AS codigo, t.nombre AS tecnico, score

Devolver un texto legible, para que el agente lo lea como una frase:

OPTIONAL MATCH (node)-[:DE_CLIENTE]->(c:Cliente)
OPTIONAL MATCH (node)-[:ASIGNADA_A]->(t:Tecnico)
RETURN 'Incidencia ' + node.codigo + ' (' + node.estado + '), cliente ' + coalesce(c.nombre, '-')
+ ', técnico ' + coalesce(t.nombre, '-') + ': ' + node.text AS text, score

Cypher para agentes

Un agente consulta el grafo con las herramientas que le des. Esta combinación cubre casi todo:

HerramientaOperaciónPara qué la usa el agente
consultar_servicio_tecnicoText2CypherPreguntas concretas: quién, cuántas, cuáles, en qué estado.
buscar_averias_parecidasHybrid + GraphEncontrar casos parecidos a una descripción, con su contexto.
esquema_del_grafoGraph SchemaSaber qué hay en el grafo antes de preguntar, o cuando una consulta no encuentra nada.
incidencias_de_tecnicoCypher QueryUna pregunta frecuente con una consulta fija y exacta: el agente solo escribe el técnico y el estado.

Recetas de Examples para Text2Cypher

En Examples escribes preguntas típicas con la consulta que las responde, con los nombres reales de tu grafo (los ves en Graph Schema). El modelo los toma como patrón para las preguntas nuevas. Cubre los patrones que más se preguntan:

Pregunta: ¿Cuántas incidencias cerradas tiene Rocío Benítez?
Cypher: MATCH (i:Incidencia)-[:ASIGNADA_A]->(t:Tecnico {nombre: 'Rocío Benítez'}) WHERE i.estado = 'cerrada' RETURN count(i) AS total
Pregunta: ¿Qué clientes tuvieron incidencias en máquinas Jaltec en agosto?
Cypher: MATCH (i:Incidencia)-[:EN_MAQUINA]->(m:Maquina {marca: 'Jaltec'}), (i)-[:DE_CLIENTE]->(c:Cliente) WHERE i.fecha STARTS WITH '2026-08' RETURN DISTINCT c.nombre AS cliente
Pregunta: ¿Qué incidencias ha tenido la Brío 2G del Bar La Giralda y quién las lleva?
Cypher: MATCH (i:Incidencia)-[:DE_CLIENTE]->(c:Cliente {nombre: 'Bar La Giralda'}), (i)-[:EN_MAQUINA]->(m:Maquina {modelo: 'Brío 2G'}) OPTIONAL MATCH (i)-[:ASIGNADA_A]->(t:Tecnico) RETURN i.codigo AS codigo, i.estado AS estado, t.nombre AS tecnico
Pregunta: ¿Qué técnico tiene más incidencias abiertas?
Cypher: MATCH (i:Incidencia {estado: 'abierta'})-[:ASIGNADA_A]->(t:Tecnico) RETURN t.nombre AS tecnico, count(i) AS abiertas ORDER BY abiertas DESC LIMIT 1

Consejos para tus ejemplos:

  • Dos a cuatro bastan: contar con un filtro, recorrer dos relaciones, filtrar por fechas y «el que más».
  • Usa los valores tal como están en el grafo: 'abierta', no 'Abierta'.
  • Si guardas las fechas como fecha (con Write Query y date(...)), filtra con i.fecha >= date('2026-08-01'); si son texto, con STARTS WITH '2026-08'.
  • Devuelve columnas con nombre (AS total, AS cliente): el agente lee esos nombres.
  • Cada respuesta de Text2Cypher lleva la consulta usada. Si una sale mal, añade un ejemplo con ese patrón.

Nombres y descripciones de las herramientas

En la pestaña Tool de cada componente, la descripción tiene que decir qué contiene el grafo y qué preguntas responde. Es lo que lee el agente para decidir:

ComponenteTool NameTool Description
Text2Cypherconsultar_servicio_tecnicoResponde preguntas concretas sobre las incidencias del servicio técnico (código, fecha, estado abierta o cerrada, código de error), sus clientes, sus técnicos y sus máquinas: cuántas, de quién, cuáles, en qué estado.
Hybrid + Graphbuscar_averias_parecidasBusca incidencias parecidas a una avería descrita y devuelve cómo se resolvieron, el cliente, el técnico y la máquina.
Graph Schemaesquema_del_grafoDescribe qué tipos de datos, relaciones y valores hay en el grafo del servicio técnico.
Cypher Queryincidencias_de_tecnicoLista las incidencias de un técnico en un estado (abierta o cerrada), con su cliente.

Instrucciones del agente

En el System Prompt del agente, di cuándo usar cada herramienta y qué hacer si no encuentran nada:

Responde siempre con los datos del servicio técnico, usando tus herramientas:
- consultar_servicio_tecnico, para preguntas concretas sobre incidencias, clientes,
técnicos y máquinas: quién, cuántas, cuáles, en qué estado.
- buscar_averias_parecidas, para encontrar averías parecidas a la que te describen,
cómo se resolvieron y quién se encargó.
- Si una consulta no devuelve nada, mira esquema_del_grafo para comprobar cómo se
escriben los nombres y los valores, y vuelve a intentarlo.
No inventes nada: si las herramientas no lo traen, dilo.

El mismo agente puede tener también una búsqueda en documentos, como en el asistente técnico.

Seguridad

Text2Cypher, Cypher Query y Graph Schema se ejecutan siempre en modo de solo lectura: si una consulta intentase crear, cambiar o borrar datos, Neo4j la rechaza y no se toca nada. Solo escriben los nodos que configuras en Add Records o Build Graph. Aun así, para un agente que atiende a clientes usa en la credencial un usuario de Neo4j que solo pueda leer.

Consejos

  • El mismo modelo de vectores al guardar (Add Records / Build Graph) y al buscar (Vector / Hybrid).
  • Number of Results (avanzado): pide 5 o más en las búsquedas. El componente pide siempre al menos 50 candidatos al índice y se queda con los mejores, así que no pierdes precisión por pedir pocos. En Text2Cypher y Cypher Query limita las filas que recibe el agente (hasta 20 por cada resultado).
  • El analizador del índice de texto tiene que ser el de tu idioma (spanish, english…). Con el estándar, las búsquedas por palabras en español encuentran menos.
  • Los símbolos de la pregunta (barras, dos puntos, comillas) no son un problema: el componente los trata como texto normal en la búsqueda por palabras, y un código como INC-2608-07 se busca entero.