GaetanoParente's picture
riviste le varie sezioni e i commenti
c1b1880
import os
from rdflib import Graph, Literal, RDF, URIRef, Namespace
from rdflib.namespace import SKOS, XSD
from pyshacl import validate
class SemanticValidator:
def __init__(self):
# Carico le regole SHACL.
# Se l'LLM ha un'allucinazione e inventa relazioni assurde, SHACL lo blocca qui.
self.shapes_file = os.path.join(os.path.dirname(__file__), "shapes/schema_constraints.ttl")
# Mappatura dei namespace di ArCo.
# Il namespace 'ex' ci serve come discarica/fallback per tutte le entità testuali pure
# (es. "Colosseo", "Monumento") che l'LLM non ha saputo ancorare a un'URI ufficiale.
self.namespaces = {
"arco": Namespace("https://w3id.org/arco/ontology/arco/"),
"core": Namespace("https://w3id.org/arco/ontology/core/"),
"a-loc": Namespace("https://w3id.org/arco/ontology/location/"),
"cis": Namespace("http://dati.beniculturali.it/cis/"),
"ex": Namespace("http://activadigital.it/ontology/")
}
if os.path.exists(self.shapes_file):
self.shacl_graph = Graph()
self.shacl_graph.parse(self.shapes_file, format="turtle")
print("🛡️ SHACL Constraints caricati.")
else:
print("⚠️ File SHACL non trovato. Validazione disabilitata (pericoloso in prod!).")
self.shacl_graph = None
def _get_uri(self, text_val):
# L'LLM ci restituisce stringhe come "arco:CulturalProperty" o semplice testo "Statua di bronzo".
# rdflib ha bisogno di URIRef veri, quindi faccio un po' di parsing per convertirli.
if ":" in text_val and not text_val.startswith("http"):
prefix, name = text_val.split(":", 1)
if prefix in self.namespaces:
return self.namespaces[prefix][name]
# Se è testo libero senza namespace, lo ripulisco per evitare che gli spazi
# rompano l'URI e lo forzo nel nostro namespace custom.
clean_name = text_val.replace(" ", "_").replace("'", "").replace('"', "")
return self.namespaces["ex"][clean_name]
def _json_to_rdf(self, entities, triples):
# Il validatore pyshacl non digerisce i nostri oggetti Pydantic o i JSON nativi.
# Devo ricostruire un micro-grafo RDF al volo solo per fargli fare il check formale.
g = Graph()
# Registro i prefissi nel grafo per facilitare l'eventuale debug testuale
for prefix, ns in self.namespaces.items():
g.bind(prefix, ns)
g.bind("skos", SKOS)
# 1. Recupero entità orfane (trovate nel testo ma non agganciate a nessuna tripla)
if entities:
for ent in entities:
# Gestisco il tipo di dato a seconda di cosa è uscito dal resolver
label = ent["label"] if isinstance(ent, dict) else str(ent)
ent_uri = self._get_uri(label)
g.add((ent_uri, SKOS.prefLabel, Literal(label, lang="it")))
# 2. Ricostruzione delle Triple relazionali
if triples:
for t in triples:
subj_uri = self._get_uri(t.subject)
# Le nostre regole SHACL (schema_constraints.ttl) esigono tipicamente che i nodi
# non siano scatole vuote (NodeLabelShape). Ci appiccico sempre la prefLabel in italiano.
g.add((subj_uri, SKOS.prefLabel, Literal(t.subject, lang="it")))
# Separo le classificazioni dalle relazioni standard
if t.predicate.lower() in ["rdf:type", "a", "type", "rdf_type"]:
obj_uri = self._get_uri(t.object)
g.add((subj_uri, RDF.type, obj_uri))
else:
# Relazione standard (es. a-loc:hasCurrentLocation)
pred_uri = self._get_uri(t.predicate)
obj_uri = self._get_uri(t.object)
g.add((subj_uri, pred_uri, obj_uri))
# Anche il nodo di destinazione deve avere un nome umano
g.add((obj_uri, SKOS.prefLabel, Literal(t.object, lang="it")))
return g
def validate_batch(self, entities, triples):
"""
Scatena il motore di regole SHACL sia sulle entità isolate che sulle triple.
Ritorna l'esito, il report testuale degli errori, e il grafo temporaneo.
"""
if not self.shacl_graph:
return True, "No Constraints", None
# Converto la pappa di Pydantic in un vero grafo RDF
data_graph = self._json_to_rdf(entities, triples)
print("🔍 Esecuzione Validazione SHACL...")
# Abilito inference='rdfs' così se una regola si applica a una super-classe,
# pyshacl lo deduce da solo scendendo l'albero gerarchico.
conforms, report_graph, report_text = validate(
data_graph,
shacl_graph=self.shacl_graph,
inference='rdfs',
serialize_report_graph=True
)
return conforms, report_text, data_graph