File size: 8,835 Bytes
034a1e6 fe3bdda 034a1e6 ee1f7e6 fe3bdda 4c52594 fe3bdda 4c52594 1575481 fe3bdda 034a1e6 fe3bdda 21119e3 034a1e6 fe3bdda 70bec3c fe3bdda 70bec3c 034a1e6 a1f8fd1 fe3bdda 034a1e6 8c772f5 4c52594 034a1e6 fe3bdda 70bec3c c191446 fe3bdda c191446 034a1e6 70bec3c 3031ece 034a1e6 e623027 c191446 70bec3c c191446 fe3bdda baa2201 fe3bdda 70bec3c fe3bdda 70bec3c 034a1e6 baa2201 034a1e6 3b0ec68 ee1f7e6 034a1e6 70bec3c fe3bdda 70bec3c 034a1e6 70bec3c fe3bdda 70bec3c aa07e9d 034a1e6 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c fe3bdda 70bec3c 1575481 034a1e6 fe3bdda |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
import os
import logging
from rdflib import Graph
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException
from huggingface_hub import InferenceClient
# Configurazione logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Configurazione API Hugging Face
API_KEY = os.getenv("HF_API_KEY")
client = InferenceClient(api_key=API_KEY)
# File RDF
RDF_FILE = "Ontologia.rdf"
####################################
# Caricamento RDF (riassunto)
####################################
def load_rdf_summary():
"""
Carica un riassunto dell'ontologia dal file RDF (se necessario).
Qui puoi usare parse e scansionare classi e proprietà reali.
"""
if not os.path.exists(RDF_FILE):
return "Nessun file RDF trovato."
try:
g = Graph()
g.parse(RDF_FILE, format="xml")
# Esempio di estrazione semplificata di classi e proprietà
classes = set()
properties = set()
for s, p, o in g.triples((None, None, None)):
if "Class" in str(o):
classes.add(s)
if "Property" in str(o):
properties.add(s)
class_summary = "\n".join([f"- Classe: {cls}" for cls in classes])
prop_summary = "\n".join([f"- Proprietà: {prop}" for prop in properties])
return f"Classi:\n{class_summary}\n\nProprietà:\n{prop_summary}"
except Exception as e:
logger.error(f"Errore durante il parsing del file RDF: {e}")
return "Errore nel caricamento del file RDF."
rdf_context = load_rdf_summary()
logger.info("RDF Summary: %s", rdf_context)
####################################
# Validazione SPARQL
####################################
def validate_sparql_query(query: str, rdf_file_path: str) -> bool:
"""
Esegue il parsing e l'esecuzione di test della query su RDF,
per verificare che sia sintatticamente e semanticamente corretta.
"""
g = Graph()
try:
g.parse(rdf_file_path, format="xml")
g.query(query) # Se c'è errore di sintassi o referenza, solleva eccezione
return True
except Exception as e:
logger.error(f"Errore durante la validazione della query SPARQL: {e}")
return False
####################################
# Prompt di Sistema molto stringente
####################################
def create_system_message(rdf_context: str) -> str:
"""
Prompt di sistema estremo:
- impone l'uso di un SOLO prefisso
- vieta righe multiple
- vieta di inventare prefissi
- obbliga a iniziare con `PREFIX base: ... SELECT` o `ASK`
"""
return f"""
Sei un assistente esperto nella generazione di query SPARQL basate su un'ontologia RDF.
Ecco un riassunto dell'ontologia su cui devi lavorare:
{rdf_context}
DI SEGUITO LE REGOLE TASSATIVE:
1. DEVI usare ESCLUSIVAMENTE questo prefisso di base (e NON modificarlo in nessun modo):
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/>
2. La query deve stare in UNA SOLA RIGA, senza andare a capo.
3. La query deve INIZIARE con:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> SELECT
oppure
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> ASK
4. Se devi indicare una classe, usa: ?qualcosa a base:NomeClasse .
5. Se devi indicare una proprietà, usa: ?s base:NomeProprieta ?o .
6. NON generare alcun altro prefisso.
7. NON utilizzare URI lunghe senza < > e NON inventare prefissi o risorse inesistenti.
8. Se non puoi rispondere con una query SPARQL valida secondo questi criteri, scrivi:
"Non posso generare una query SPARQL per questa richiesta."
Esempio di query corretta (fittizia) in una sola riga:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> SELECT ?stanza WHERE {{ ?stanza a base:Stanza . }} LIMIT 10
RISPONDI ESCLUSIVAMENTE CON LA QUERY O IL MESSAGGIO DI IMPOSSIBILITA'.
"""
####################################
# Prompt di "correzione"
####################################
def create_correction_message(rdf_context: str, errore: str) -> str:
"""
Questo prompt serve per la seconda iterazione se la query non è valida.
Invita a correggere la query e a rispettare le regole.
"""
return f"""
La query che hai fornito è risultata NON valida per il seguente motivo:
{errore}
RICORDA LE REGOLE TASSATIVE, in particolare l'uso ESATTO del prefisso:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/>
Riscrivi la query in UNA SOLA RIGA, rispettando la sintassi SPARQL e usando solo classi e proprietà presenti nell'ontologia.
Se non riesci, dì: "Non posso generare una query SPARQL per questa richiesta."
"""
####################################
# Funzione per chiamare il modello
####################################
async def call_model(messages, temperature=0.7, max_tokens=2048):
try:
response = client.chat.completions.create(
model="Qwen/Qwen2.5-72B-Instruct",
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=0.7,
stream=False
)
raw_text = response["choices"][0]["message"]["content"]
# Rimuoviamo eventuali newline per forzare la singola riga
return raw_text.replace("\n", " ").strip()
except Exception as e:
logger.error(f"Errore nel modello: {e}")
raise HTTPException(status_code=500, detail=str(e))
####################################
# FastAPI
####################################
app = FastAPI()
class QueryRequest(BaseModel):
message: str
max_tokens: int = 2048
temperature: float = 0.7
@app.post("/generate-query/")
async def generate_query(request: QueryRequest):
# 1) Prima iterazione
system_msg = create_system_message(rdf_context)
user_msg = request.message
messages_first = [
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg}
]
response1 = await call_model(messages_first, request.temperature, request.max_tokens)
logger.info(f"[Prima iterazione] Risposta generata dal modello: {response1}")
# Controllo se comincia con il prefisso esatto
mandated_prefix = "PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/>"
if not (response1.startswith(mandated_prefix + " SELECT") or response1.startswith(mandated_prefix + " ASK")):
return {
"query": None,
"explanation": "Il modello non ha usato il prefisso obbligatorio o non ha usato SELECT/ASK."
}
# Verifichiamo se la query è valida
if validate_sparql_query(response1, RDF_FILE):
return {"query": response1, "explanation": "Query valida alla prima iterazione."}
else:
# 2) Seconda iterazione (correzione)
correction_msg = create_correction_message(rdf_context, "Query non valida alla prima iterazione.")
# Comunichiamo al modello la query precedente come contesto e chiediamo la correzione
messages_second = [
{"role": "system", "content": system_msg}, # Prompt di sistema invariato
{"role": "assistant", "content": response1}, # La risposta 'errata'
{"role": "system", "content": correction_msg} # Istruzione di correzione
]
response2 = await call_model(messages_second, request.temperature, request.max_tokens)
logger.info(f"[Seconda iterazione] Risposta generata dal modello: {response2}")
# Ricontrollo se comincia con il prefisso esatto
if not (response2.startswith(mandated_prefix + " SELECT") and not response2.startswith(mandated_prefix + " ASK")):
# O se manca la SELECT e l'ASK
# Con un piccolo fix: potresti voler controllare sia SELECT che ASK qui
if not (response2.startswith(mandated_prefix + " SELECT") or response2.startswith(mandated_prefix + " ASK")):
return {
"query": None,
"explanation": "Anche la seconda iterazione non ha usato il prefisso e SELECT/ASK corretti."
}
# Validazione della seconda risposta
if validate_sparql_query(response2, RDF_FILE):
return {"query": response2, "explanation": "Query valida alla seconda iterazione (corretta)."}
else:
return {
"query": None,
"explanation": "Anche la seconda iterazione ha prodotto una query non valida. Interrompo."
}
@app.get("/")
async def root():
return {"message": "Server attivo e pronto a generare query SPARQL!"} |