EKG + RAG/LLM para retroalimentación de códigoTrabajo de Fin de Máster · UNED
Consulta y administración con SPARQL

Consulta y administración con SPARQL

Reúno aquí las dos caras de SPARQL 1.1 que toco en el TFM. La primera es la de consulta sobre el grafo de conocimiento (EKG): patrones SELECT y CONSTRUCT, los property paths y una consulta federada a Wikidata. La segunda es la de administración del grafo con UPDATE (INSERT/DELETE), que sigo por indicación del director: el mismo lenguaje que interroga el grafo también lo mantiene. Todas las cifras de salida que cito proceden de las ejecuciones reales del entregable; las parafraseo, no invento ninguna.

1Consultas SELECT sobre el EKG

El EKG canónico (ekg-python-150.ttl) reúne 157 conceptos en 1772 triples afirmados, que el cierre OWL 2 RL eleva a 4786. Sobre ese grafo, las consultas SELECT son la forma natural de interrogarlo. La consulta básica del entregable agrupa los conceptos por tema y los devuelve en español, combinando GROUP BY, GROUP_CONCAT y FILTER(LANG(…)).

157
conceptos en el EKG canónico
16
temas que devuelve la consulta 01
1772 → 4786
triples afirmados → tras OWL 2 RL
PREFIX pyedu: <https://w3id.org/ekg-python/schema#>
PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>

# Conceptos agrupados por tema, en español (consulta 01).
SELECT ?tema (GROUP_CONCAT(?etiqueta; SEPARATOR=", ") AS ?conceptos)
WHERE {
  ?concepto pyedu:perteneceATema ?t .
  ?t rdfs:label ?tema .
  ?concepto rdfs:label ?etiqueta .
  FILTER(LANG(?tema) = "es")
  FILTER(LANG(?etiqueta) = "es")
}
GROUP BY ?tema
ORDER BY ?tema
# → 16 filas (un tema por fila, con sus conceptos).

Una segunda consulta SELECT está pensada para evidenciar la inferencia: recupera todo lo que es pyedu:Concepto. Sin razonador devuelve 0 filas (ningún individuo se tipa a mano como concepto), y con OWL 2 RL devuelve 157, porque rdfs:subClassOf propaga el tipo. Ese contraste 0 → 157 es la prueba directa de que el razonador está actuando.

Salida real de tres consultas SELECT del entregable, ejecutadas con scripts/consultar.py sobre el grafo inferido (4786 triples).
ConsultaQué demuestraSalida real
01_conceptos_por_tema.rqSELECT + GROUP BY + GROUP_CONCAT + FILTER(LANG)16 temas con sus conceptos
02_inferencia_conceptos.rqSELECT como diagnóstico de la inferencia de tipos0 sin inferencia → 157 con OWL 2 RL
05_errores_por_concepto.rqOPTIONAL sobre reificación rdf:Statement16 errores (uno con fuente Keuning 2019)
El contraste 0 → 157 es la inferencia hecha visible

El salto procede de rdfs:subClassOf: toda subclase de concepto es pyedu:Concepto. Como el enlazado a Wikidata usa skos:exactMatch y no owl:sameAs, las 30 entidades enlazadas no se infieren como concepto; por eso el resultado es 157 y no 187.

2Property paths: el cierre transitivo en la consulta

Los property paths de SPARQL 1.1 permiten recorrer una cadena de relaciones sin razonador. El operador + sobre pyedu:requierePrerrequisito calcula, en tiempo de consulta, los prerrequisitos directos e indirectos de un concepto. Es el contraste didáctico con owl:TransitiveProperty, que materializaría los mismos hechos por inferencia en lugar de en la consulta.

Cierre transitivo de prerrequisitos de "Búsqueda binaria" La consulta con property path + parte de Búsqueda binaria y recorre la cadena de prerrequisitos: Bucle while e Indexación de forma directa, y siguiendo esos enlaces alcanza Condicional, Variable y Lista. El conjunto recuperado son cinco prerrequisitos. requiererequiere Búsquedabinaria Buclewhile Indexación Condicional Variable Lista
Cierre transitivo recorrido por pyedu:requierePrerrequisito+ desde "Búsqueda binaria": la consulta 03 devuelve 5 prerrequisitos. Representación esquemática con la paleta del proyecto; el conjunto exacto de nodos es ilustrativo del recorrido.
PREFIX pyedu: <https://w3id.org/ekg-python/schema#>
PREFIX pyr:   <https://w3id.org/ekg-python/id/>
PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>

# Prerrequisitos directos e indirectos de "Búsqueda binaria"
# mediante un property path (+); no necesita razonador (consulta 03).
SELECT DISTINCT ?prerequisito ?etiqueta
WHERE {
  pyr:busqueda_binaria pyedu:requierePrerrequisito+ ?prerequisito .
  ?prerequisito rdfs:label ?etiqueta .
  FILTER(LANG(?etiqueta) = "es")
}
ORDER BY ?etiqueta
# → 5 prerrequisitos de búsqueda binaria.

La misma idea, ordenando los pasos por dificultad creciente, da una ruta de aprendizaje sugerida hacia un concepto objetivo (consulta 04). Allí el + se combina con OPTIONAL sobre pyedu:tieneDificultad y ORDER BY ?dificultad.

El operador estrella *: cierre transitivo reflexivo

Junto al + (uno o más pasos), SPARQL 1.1 ofrece el operador * (cero o más pasos), que añade el camino de longitud cero e incluye el propio nodo de partida. Lo aprovecho sobre la jerarquía conceptual SKOS (skos:broader) para recuperar los conceptos más generales que subsumen a uno dado, contándolo a él mismo (consulta 10). La diferencia entre * y + es ese nodo de partida.

PREFIX pyr:   <https://w3id.org/ekg-python/id/>
PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos:  <http://www.w3.org/2004/02/skos/core#>

# Conceptos más generales de "Recorrido en anchura (BFS)" por la
# jerarquía SKOS, INCLUIDO él mismo (cierre reflexivo con *; consulta 10).
SELECT DISTINCT ?general ?etiqueta
WHERE {
  pyr:bfs skos:broader* ?general .
  ?general rdfs:label ?etiqueta .
  FILTER(LANG(?etiqueta) = "es")
}
ORDER BY ?etiqueta
# → 2 filas con * (BFS + Grafo); con + serían 1 (solo Grafo).
Cuándo elegir * y cuándo +

Se usa * cuando el resultado debe contener también el concepto de partida (p. ej. "este concepto y todos sus generales") y + cuando interesan solo los alcanzados por al menos un salto (p. ej. "sus prerrequisitos", sin incluirse a sí mismo). Ninguno de los dos necesita razonador: el motor de consulta recorre la conectividad del grafo en el momento de evaluar el patrón.

3CONSTRUCT: materializar un grafo nuevo

Mientras SELECT devuelve una tabla, CONSTRUCT devuelve un grafo RDF nuevo. Lo aprovecho para aplanar el cierre transitivo de prerrequisitos en triples directos (pyedu:tienePrerrequisitoTransitivo), útil para exportar o cargar el resultado en un sistema que no razone. Sobre el grafo sin inferencia produce 340 triples; con OWL 2 RL, 356.

PREFIX pyedu: <https://w3id.org/ekg-python/schema#>

# Materializa como triples directos el cierre transitivo
# de prerrequisitos: devuelve un grafo nuevo (consulta 06).
CONSTRUCT {
  ?concepto pyedu:tienePrerrequisitoTransitivo ?prerequisito .
}
WHERE {
  ?concepto pyedu:requierePrerrequisito+ ?prerequisito .
}
# → 340 triples sin inferencia; 356 con OWL 2 RL.
340 → 356
triples del CONSTRUCT (sin → con OWL 2 RL)
+16
triples que aporta la inferencia

4SPARQL como administración: UPDATE, INSERT y DELETE

SPARQL no sirve solo para preguntar. Su capa UPDATE (la que ejecuto en GraphDB desde la consola Query & Update, o en cualquier almacén RDF4J) es el mecanismo con el que se administra el grafo: dar de alta hechos, corregirlos y retirarlos. Lo incluyo por indicación del director, para mostrar que el mantenimiento del EKG vive en el mismo lenguaje que lo consulta. Los bloques de abajo operan sobre el esquema y los identificadores reales del proyecto (pyedu: y pyr:), parafraseados de la ontología ekg-python-150.ttl.

Operaciones de mantenimiento, no del benchmark

Estos UPDATE son ejemplos de administración del grafo sobre el modelo real; no forman parte de las ejecuciones del benchmark ni alteran las cifras canónicas (157 / 1772 / 4786), que se recalculan tras cada cambio del grafo. Se ilustra la operativa, no se reportan recuentos de filas afectadas.

Alta de hechos con INSERT DATA

Para incorporar un concepto nuevo y enlazarlo a su tema y a sus prerrequisitos, INSERT DATA añade triples concretos. El patrón es el mismo que ya emplea el Turtle del grafo, solo que expresado como operación de escritura.

PREFIX pyedu: <https://w3id.org/ekg-python/schema#>
PREFIX pyr:   <https://w3id.org/ekg-python/id/>
PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos:  <http://www.w3.org/2004/02/skos/core#>

# Alta de un concepto, con su tema, su prerrequisito y su dificultad.
INSERT DATA {
  pyr:comprension_listas a pyedu:EstructuraDeDatos ;
      rdfs:label "List comprehension"@en, "Comprensión de listas"@es ;
      skos:prefLabel "Comprensión de listas"@es ;
      pyedu:perteneceATema pyr:T_colecciones ;
      pyedu:requierePrerrequisito pyr:bucle_for ;
      pyedu:tieneDificultad 3 .
}

Corrección con DELETE / INSERT … WHERE

Para corregir un valor existente, el patrón DELETE … INSERT … WHERE retira el triple antiguo y coloca el nuevo en una sola transacción. Aquí reajusto la dificultad declarada de un concepto:

PREFIX pyedu: <https://w3id.org/ekg-python/schema#>
PREFIX pyr:   <https://w3id.org/ekg-python/id/>

# Reajusta la dificultad de "Recursión" de su valor actual a 5.
DELETE { pyr:recursion pyedu:tieneDificultad ?d . }
INSERT { pyr:recursion pyedu:tieneDificultad 5 . }
WHERE   { pyr:recursion pyedu:tieneDificultad ?d . }

Retirada de hechos con DELETE WHERE

Y para dar de baja todo lo afirmado sobre un recurso obsoleto, DELETE WHERE elimina los triples que casen con el patrón. Conviene usarlo con cuidado y dentro de la transacción de mantenimiento:

PREFIX pyr: <https://w3id.org/ekg-python/id/>

# Retira todos los triples cuyo sujeto sea un concepto obsoleto.
DELETE WHERE { pyr:concepto_obsoleto ?p ?o . }
Tras administrar, revalidar

Después de cualquier INSERT/DELETE conviene reejecutar la validación SHACL (el grafo canónico es conforme, 0 violaciones; el control negativo ejemplo-invalido.ttl dispara 6) y recalcular las cifras del grafo, ya que cambian con cada edición.

5Consulta federada a Wikidata

La consulta federada conecta el EKG con la nube de datos enlazados. Mediante SERVICE, recupera del endpoint público de Wikidata la descripción en español de cada concepto enlazado con skos:exactMatch. Hay 30 enlaces, pero la federada devuelve 20 filas, porque solo 20 entidades tienen schema:description en español recuperable. Es la única consulta del entregable que requiere red; la página que estás leyendo es offline y muestra su salida ya capturada.

PREFIX pyedu:  <https://w3id.org/ekg-python/schema#>
PREFIX rdfs:   <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos:   <http://www.w3.org/2004/02/skos/core#>
PREFIX schema: <http://schema.org/>

# Consulta FEDERADA: descripción @es desde Wikidata (consulta 08).
SELECT ?concepto ?etiqueta ?descripcionWikidata
WHERE {
  ?concepto skos:exactMatch ?wd ; rdfs:label ?etiqueta .
  FILTER(STRSTARTS(STR(?wd), "http://www.wikidata.org/entity/"))
  FILTER(LANG(?etiqueta) = "es")
  SERVICE <https://query.wikidata.org/sparql> {
    ?wd schema:description ?descripcionWikidata .
    FILTER(LANG(?descripcionWikidata) = "es")
  }
}
# → 20 filas. Ej.: python → "lenguaje de programación de alto nivel".
Una muestra de la salida real (id · etiqueta · descripción Wikidata @es)
Cinco de las 20 filas reales de 08_federada_wikidata.rq (captura del entregable).
idEtiquetaDescripción (Wikidata, es)
pythonPythonlenguaje de programación de alto nivel
recursionRecursiónmétodo en ciencias de computación
busqueda_binariaBúsqueda binariaalgoritmo de búsqueda
diccionarioDiccionarioestructura de datos que asocia claves con valores
claseClaseen programación orientada a objetos, plantilla para la creación de objetos de datos según un modelo predefinido

6Ver también

El grafo de conocimiento

La ontología EKG, sus métricas canónicas, la grafoteca interactiva y las ocho consultas con su salida real.

Sistema RAG

Cómo el subgrafo recuperado por SPARQL se inyecta en el prompt de los sistemas C y D.

Recursos

Ficheros del entregable: ontología, consultas .rq, formas SHACL y guía de carga en GraphDB.

Cómo citar

Si este trabajo te resulta útil y quieres referenciarlo, esta es la cita recomendada.

Bueno Junquero, A. (2026). Integración de un grafo de conocimiento educativo con un LLM mediante RAG. Trabajo Fin de Máster, Máster Universitario en Investigación en Inteligencia Artificial, UNED. Director, José Luis Fernández Vindel.