AshenClock
commited on
Update app.py
Browse files
app.py
CHANGED
@@ -7,7 +7,8 @@ import rdflib
|
|
7 |
from rdflib.plugins.sparql.parser import parseQuery
|
8 |
from huggingface_hub import InferenceClient
|
9 |
import re
|
10 |
-
|
|
|
11 |
# ---------------------------------------------------------------------------
|
12 |
# CONFIGURAZIONE LOGGING
|
13 |
# ---------------------------------------------------------------------------
|
@@ -18,11 +19,27 @@ logging.basicConfig(
|
|
18 |
)
|
19 |
logger = logging.getLogger(__name__)
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
# ---------------------------------------------------------------------------
|
22 |
# COSTANTI / CHIAVI / MODELLI
|
23 |
# ---------------------------------------------------------------------------
|
24 |
# Nota: HF_API_KEY deve essere impostata a una chiave valida di Hugging Face.
|
25 |
-
HF_API_KEY = os.getenv("HF_API_KEY")
|
26 |
if not HF_API_KEY:
|
27 |
# Se la chiave API non è impostata, solleva un errore
|
28 |
logger.error("HF_API_KEY non impostata.")
|
@@ -43,7 +60,6 @@ TRANSLATOR_MODEL_PREFIX = "Helsinki-NLP/opus-mt"
|
|
43 |
"""
|
44 |
Qui inizializziamo i client necessari. In questo modo, evitiamo di istanziare
|
45 |
continuamente nuovi oggetti InferenceClient a ogni chiamata delle funzioni.
|
46 |
-
|
47 |
- hf_generation_client: per generare query SPARQL e risposte stile "guida museale"
|
48 |
- lang_detect_client: per rilevare la lingua della domanda e della risposta
|
49 |
"""
|
@@ -104,13 +120,29 @@ class AssistantRequest(BaseModel):
|
|
104 |
# ---------------------------------------------------------------------------
|
105 |
# FUNZIONI DI SUPPORTO (Prompts, validazione SPARQL, correzioni, ecc.)
|
106 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
|
|
|
|
|
|
|
|
|
108 |
def create_system_prompt_for_sparql(ontology_turtle: str) -> str:
|
109 |
"""
|
110 |
Genera il testo di prompt che istruisce il modello su come costruire
|
111 |
SOLO UNA query SPARQL, in un'unica riga, o in alternativa 'NO_SPARQL'
|
112 |
se la domanda non è pertinente all'ontologia. Il prompt include regole
|
113 |
-
di formattazione
|
|
|
114 |
|
115 |
Parametri:
|
116 |
- ontology_turtle: una stringa con l'ontologia in formato Turtle (o simile).
|
@@ -121,7 +153,6 @@ def create_system_prompt_for_sparql(ontology_turtle: str) -> str:
|
|
121 |
prompt = f"""SEI UN GENERATORE DI QUERY SPARQL PER L'ONTOLOGIA DI UN MUSEO.
|
122 |
DEVI GENERARE SOLO UNA QUERY SPARQL (IN UNA SOLA RIGA) SE LA DOMANDA RIGUARDA INFORMAZIONI NELL'ONTOLOGIA.
|
123 |
SE LA DOMANDA NON È ATTINENTE, RISPONDI 'NO_SPARQL'.
|
124 |
-
|
125 |
REGOLE SINTATTICHE RIGOROSE:
|
126 |
1) Usare: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
|
127 |
2) Query in UNA SOLA RIGA (niente a capo), forma: PREFIX progettoMuseo: <...> SELECT ?x WHERE {{ ... }} LIMIT N
|
@@ -135,43 +166,51 @@ REGOLE SINTATTICHE RIGOROSE:
|
|
135 |
Esempi di Domande Specifiche e relative Query:
|
136 |
1) Utente: Chi ha creato l'opera 'Afrodite di Milo'?
|
137 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?autore WHERE {{ progettoMuseo:AfroditeDiMilo progettoMuseo:autoreOpera ?autore . }} LIMIT 10
|
138 |
-
|
139 |
2) Utente: Quali sono le tecniche utilizzate nelle opere?
|
140 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?tecnica WHERE {{ ?opera progettoMuseo:tecnicaOpera ?tecnica . }} LIMIT 100
|
141 |
-
|
142 |
3) Utente: Quali sono le dimensioni delle opere?
|
143 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?dimensione WHERE {{ ?opera progettoMuseo:dimensioneOpera ?dimensione . }} LIMIT 100
|
144 |
-
|
145 |
4) Utente: Quali opere sono esposte nella stanza Greca?
|
146 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ progettoMuseo:StanzaGrecia progettoMuseo:Espone ?opera . }} LIMIT 100
|
147 |
-
|
148 |
5) Utente: Quali sono le proprietà e i tipi delle proprietà nell'ontologia?
|
149 |
Risposta: PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX owl: <http://www.w3.org/2002/07/owl#> PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT DISTINCT ?property ?type WHERE {{ ?property rdf:type ?type . FILTER(?type IN (owl:ObjectProperty, owl:DatatypeProperty)) }}
|
150 |
-
|
151 |
6) Utente: Recupera tutti i biglietti e i tipi di biglietto.
|
152 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?biglietto ?tipoBiglietto WHERE {{ ?biglietto rdf:type progettoMuseo:Biglietto . ?biglietto progettoMuseo:tipoBiglietto ?tipoBiglietto . }} LIMIT 100
|
153 |
-
|
154 |
7) Utente: Recupera tutti i visitatori e i tour a cui partecipano.
|
155 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?visitatore ?tour WHERE {{ ?visitatore progettoMuseo:Partecipazione_a_Evento ?tour . }} LIMIT 100
|
156 |
-
|
157 |
8) Utente: Recupera tutte le stanze tematiche e le opere esposte.
|
158 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?stanza ?opera WHERE {{ ?stanza rdf:type progettoMuseo:Stanza_Tematica . ?stanza progettoMuseo:Espone ?opera . }} LIMIT 100
|
159 |
-
|
160 |
9) Utente: Recupera tutte le opere con materiale 'Marmo'.
|
161 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ ?opera progettoMuseo:materialeOpera "Marmo"@it . }} LIMIT 100
|
162 |
-
|
163 |
10) Utente: Recupera tutti i visitatori con data di nascita dopo il 2000.
|
164 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?visitatore WHERE {{ ?visitatore rdf:type progettoMuseo:Visitatore_Individuale . ?visitatore progettoMuseo:dataDiNascitaVisitatore ?data . FILTER(?data > "2000-01-01T00:00:00"^^xsd:dateTime) . }} LIMIT 100
|
165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
ECCO L'ONTOLOGIA (TURTLE) PER CONTESTO:
|
167 |
{ontology_turtle}
|
168 |
FINE ONTOLOGIA.
|
169 |
"""
|
170 |
-
logger.debug("[create_system_prompt_for_sparql] Prompt generato con
|
171 |
return prompt
|
172 |
|
173 |
|
174 |
-
|
|
|
175 |
"""
|
176 |
Classifica la lingua della domanda e della risposta, quindi traduce la risposta
|
177 |
se la lingua è diversa da quella della domanda. L'idea è di restituire una
|
@@ -184,7 +223,6 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
184 |
Restituisce:
|
185 |
- La risposta tradotta nella lingua della domanda o la risposta originale
|
186 |
se entrambe le lingue coincidono.
|
187 |
-
|
188 |
NB: Qui l'oggetto 'lang_detect_client' (per rilevamento lingua) è già
|
189 |
stato inizializzato all'avvio dell'app. Mentre il 'translator_client'
|
190 |
viene creato 'al volo' poiché la direzione di traduzione dipende
|
@@ -194,23 +232,27 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
194 |
try:
|
195 |
question_lang_result = lang_detect_client.text_classification(text=question_text)
|
196 |
question_lang = question_lang_result[0]['label']
|
|
|
197 |
logger.info(f"[LangDetect] Lingua della domanda: {question_lang}")
|
198 |
except Exception as e:
|
199 |
logger.error(f"Errore nel rilevamento della lingua della domanda: {e}")
|
200 |
question_lang = "en" # Fallback se non riusciamo a rilevare la lingua
|
201 |
-
|
202 |
# Rileva la lingua della risposta
|
203 |
try:
|
204 |
answer_lang_result = lang_detect_client.text_classification(text=model_answer_text)
|
205 |
answer_lang = answer_lang_result[0]['label']
|
|
|
206 |
logger.info(f"[LangDetect] Lingua della risposta: {answer_lang}")
|
207 |
except Exception as e:
|
208 |
logger.error(f"Errore nel rilevamento della lingua della risposta: {e}")
|
209 |
answer_lang = "it" # Fallback se non riusciamo a rilevare la lingua
|
|
|
210 |
|
211 |
# Se domanda e risposta sono nella stessa lingua, non traduciamo
|
212 |
if question_lang == answer_lang:
|
213 |
logger.info("[Translate] Nessuna traduzione necessaria: stessa lingua.")
|
|
|
214 |
return model_answer_text
|
215 |
|
216 |
# Altrimenti, costruiamo "al volo" il modello di traduzione appropriato
|
@@ -220,15 +262,17 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
220 |
token=HF_API_KEY,
|
221 |
model=translator_model
|
222 |
)
|
223 |
-
|
224 |
# Traduzione della risposta
|
225 |
try:
|
226 |
translation_result = translator_client.translation(text=model_answer_text)
|
227 |
translated_answer = translation_result["translation_text"]
|
228 |
-
|
229 |
except Exception as e:
|
230 |
logger.error(f"Errore nella traduzione {answer_lang} -> {question_lang}: {e}")
|
|
|
231 |
# Se fallisce, restituiamo la risposta originale come fallback
|
|
|
232 |
translated_answer = model_answer_text
|
233 |
|
234 |
return translated_answer
|
@@ -243,9 +287,12 @@ def create_system_prompt_for_guide() -> str:
|
|
243 |
"""
|
244 |
prompt = (
|
245 |
"SEI UNA GUIDA MUSEALE VIRTUALE. "
|
246 |
-
"RISPONDI IN MODO
|
247 |
-
"SE HAI RISULTATI SPARQL, USALI. "
|
248 |
-
"
|
|
|
|
|
|
|
249 |
)
|
250 |
logger.debug("[create_system_prompt_for_guide] Prompt per la risposta guida museale generato.")
|
251 |
return prompt
|
@@ -266,7 +313,6 @@ def correct_sparql_syntax_advanced(query: str) -> str:
|
|
266 |
|
267 |
Parametri:
|
268 |
- query: stringa con la query SPARQL potenzialmente mal formattata.
|
269 |
-
|
270 |
Ritorna:
|
271 |
- La query SPARQL corretta se possibile, in singola riga.
|
272 |
"""
|
@@ -329,6 +375,7 @@ def is_sparql_query_valid(query: str) -> bool:
|
|
329 |
# ---------------------------------------------------------------------------
|
330 |
@app.post("/assistant")
|
331 |
def assistant_endpoint(req: AssistantRequest):
|
|
|
332 |
"""
|
333 |
Endpoint che gestisce l'intera pipeline:
|
334 |
1) Genera una query SPARQL dal messaggio dell'utente (prompt dedicato).
|
@@ -350,14 +397,37 @@ def assistant_endpoint(req: AssistantRequest):
|
|
350 |
}
|
351 |
"""
|
352 |
logger.info("Ricevuta chiamata POST su /assistant")
|
353 |
-
|
354 |
# Estraggo i campi dal body della richiesta
|
355 |
user_message = req.message
|
356 |
max_tokens = req.max_tokens
|
357 |
temperature = req.temperature
|
358 |
-
|
359 |
logger.debug(f"Parametri utente: message='{user_message}', max_tokens={max_tokens}, temperature={temperature}")
|
360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
361 |
# -----------------------------------------------------------------------
|
362 |
# STEP 1: Generazione della query SPARQL
|
363 |
# -----------------------------------------------------------------------
|
@@ -365,16 +435,19 @@ def assistant_endpoint(req: AssistantRequest):
|
|
365 |
# Serializziamo l'ontologia in XML per fornirla al prompt (anche se si chiama 'turtle' va bene così).
|
366 |
ontology_turtle = ontology_graph.serialize(format="xml")
|
367 |
logger.debug("Ontologia serializzata con successo (XML).")
|
|
|
368 |
except Exception as e:
|
369 |
logger.warning(f"Impossibile serializzare l'ontologia in formato XML: {e}")
|
|
|
370 |
ontology_turtle = ""
|
371 |
|
372 |
# Creiamo il prompt di sistema per la generazione SPARQL
|
373 |
system_prompt_sparql = create_system_prompt_for_sparql(ontology_turtle)
|
374 |
-
|
375 |
# Chiamata al modello per generare la query SPARQL
|
376 |
try:
|
377 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la query SPARQL...")
|
|
|
378 |
gen_sparql_output = hf_generation_client.chat.completions.create(
|
379 |
messages=[
|
380 |
{"role": "system", "content": system_prompt_sparql},
|
@@ -385,8 +458,10 @@ def assistant_endpoint(req: AssistantRequest):
|
|
385 |
)
|
386 |
possible_query = gen_sparql_output["choices"][0]["message"]["content"].strip()
|
387 |
logger.info(f"[assistant_endpoint] Query generata dal modello: {possible_query}")
|
|
|
388 |
except Exception as ex:
|
389 |
logger.error(f"Errore nella generazione della query SPARQL: {ex}")
|
|
|
390 |
# Se fallisce la generazione, consideriamo la query come "NO_SPARQL"
|
391 |
possible_query = "NO_SPARQL"
|
392 |
|
@@ -397,12 +472,15 @@ def assistant_endpoint(req: AssistantRequest):
|
|
397 |
else:
|
398 |
# Applichiamo la correzione avanzata
|
399 |
advanced_corrected = correct_sparql_syntax_advanced(possible_query)
|
|
|
400 |
# Verifichiamo la validità della query
|
401 |
if is_sparql_query_valid(advanced_corrected):
|
402 |
generated_query = advanced_corrected
|
403 |
logger.debug(f"[assistant_endpoint] Query SPARQL valida dopo correzione avanzata: {generated_query}")
|
|
|
404 |
else:
|
405 |
logger.debug("[assistant_endpoint] Query SPARQL non valida. Verrà ignorata.")
|
|
|
406 |
generated_query = None
|
407 |
|
408 |
# -----------------------------------------------------------------------
|
@@ -411,19 +489,23 @@ def assistant_endpoint(req: AssistantRequest):
|
|
411 |
results = []
|
412 |
if generated_query:
|
413 |
logger.debug(f"[assistant_endpoint] Esecuzione della query SPARQL:\n{generated_query}")
|
|
|
414 |
try:
|
415 |
query_result = ontology_graph.query(generated_query)
|
416 |
results = list(query_result)
|
417 |
logger.info(f"[assistant_endpoint] Query eseguita con successo. Numero risultati = {len(results)}")
|
|
|
418 |
except Exception as ex:
|
419 |
logger.error(f"[assistant_endpoint] Errore nell'esecuzione della query: {ex}")
|
|
|
420 |
results = []
|
421 |
-
|
|
|
422 |
# -----------------------------------------------------------------------
|
423 |
# STEP 3: Generazione della risposta finale stile "guida museale"
|
424 |
# -----------------------------------------------------------------------
|
425 |
system_prompt_guide = create_system_prompt_for_guide()
|
426 |
-
|
427 |
if generated_query and results:
|
428 |
# Caso: query generata + risultati SPARQL
|
429 |
# Convertiamo i risultati in una stringa più leggibile
|
@@ -439,7 +521,7 @@ def assistant_endpoint(req: AssistantRequest):
|
|
439 |
"Rispondi in modo breve (max ~50 parole)."
|
440 |
)
|
441 |
logger.debug("[assistant_endpoint] Prompt di risposta con risultati SPARQL.")
|
442 |
-
|
443 |
elif generated_query and not results:
|
444 |
# Caso: query valida ma 0 risultati
|
445 |
second_prompt = (
|
@@ -449,7 +531,7 @@ def assistant_endpoint(req: AssistantRequest):
|
|
449 |
"Nessun risultato dalla query. Prova comunque a rispondere con le tue conoscenze."
|
450 |
)
|
451 |
logger.debug("[assistant_endpoint] Prompt di risposta: query valida ma senza risultati.")
|
452 |
-
|
453 |
else:
|
454 |
# Caso: nessuna query generata
|
455 |
second_prompt = (
|
@@ -458,22 +540,25 @@ def assistant_endpoint(req: AssistantRequest):
|
|
458 |
"Nessuna query SPARQL generata. Rispondi come puoi, riarrangiando le tue conoscenze."
|
459 |
)
|
460 |
logger.debug("[assistant_endpoint] Prompt di risposta: nessuna query generata.")
|
461 |
-
|
462 |
# Chiamata finale al modello per la risposta "guida museale"
|
463 |
try:
|
464 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la risposta finale...")
|
|
|
465 |
final_output = hf_generation_client.chat.completions.create(
|
466 |
messages=[
|
467 |
{"role": "system", "content": second_prompt},
|
468 |
{"role": "user", "content": "Fornisci la risposta finale."}
|
469 |
],
|
470 |
-
max_tokens=
|
471 |
-
temperature=
|
472 |
)
|
473 |
final_answer = final_output["choices"][0]["message"]["content"].strip()
|
474 |
logger.info(f"[assistant_endpoint] Risposta finale generata: {final_answer}")
|
|
|
475 |
except Exception as ex:
|
476 |
logger.error(f"Errore nella generazione della risposta finale: {ex}")
|
|
|
477 |
raise HTTPException(status_code=500, detail="Errore nella generazione della risposta in linguaggio naturale.")
|
478 |
|
479 |
# -----------------------------------------------------------------------
|
@@ -481,15 +566,16 @@ def assistant_endpoint(req: AssistantRequest):
|
|
481 |
# -----------------------------------------------------------------------
|
482 |
final_ans = classify_and_translate(user_message, final_answer)
|
483 |
final_ans = final_ans.replace('\\"', "").replace('\"', "")
|
|
|
484 |
# -----------------------------------------------------------------------
|
485 |
# Restituzione in formato JSON
|
486 |
# -----------------------------------------------------------------------
|
487 |
logger.debug("[assistant_endpoint] Fine elaborazione, restituzione risposta JSON.")
|
488 |
return {
|
489 |
"query": generated_query,
|
490 |
-
"response": final_ans
|
|
|
491 |
}
|
492 |
-
|
493 |
# ---------------------------------------------------------------------------
|
494 |
# ENDPOINT DI TEST / HOME
|
495 |
# ---------------------------------------------------------------------------
|
@@ -502,7 +588,78 @@ def home():
|
|
502 |
return {
|
503 |
"message": "Endpoint attivo. Esempio di backend per generare query SPARQL e risposte guida museale."
|
504 |
}
|
505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
# ---------------------------------------------------------------------------
|
507 |
# MAIN
|
508 |
# ---------------------------------------------------------------------------
|
@@ -511,4 +668,5 @@ if __name__ == "__main__":
|
|
511 |
Avvio dell'applicazione FastAPI sulla porta 8000,
|
512 |
utile se eseguito come script principale.
|
513 |
"""
|
514 |
-
logger.info("Avvio dell'applicazione FastAPI.")
|
|
|
|
7 |
from rdflib.plugins.sparql.parser import parseQuery
|
8 |
from huggingface_hub import InferenceClient
|
9 |
import re
|
10 |
+
import torch
|
11 |
+
from transformers import DistilBertForSequenceClassification, DistilBertTokenizer
|
12 |
# ---------------------------------------------------------------------------
|
13 |
# CONFIGURAZIONE LOGGING
|
14 |
# ---------------------------------------------------------------------------
|
|
|
19 |
)
|
20 |
logger = logging.getLogger(__name__)
|
21 |
|
22 |
+
|
23 |
+
# Determina il device (GPU se disponibile, altrimenti CPU)
|
24 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
25 |
+
logger.info(f"Device per il classificatore: {device}")
|
26 |
+
|
27 |
+
# Carica il modello e il tokenizer del classificatore fine-tuned
|
28 |
+
try:
|
29 |
+
logger.info("Caricamento del modello di classificazione fine-tuned da 'finetuned-bert-model'.")
|
30 |
+
classifier_model = DistilBertForSequenceClassification.from_pretrained("finetuned-bert-model")
|
31 |
+
classifier_tokenizer = DistilBertTokenizer.from_pretrained("finetuned-bert-model")
|
32 |
+
classifier_model.to(device)
|
33 |
+
logger.info("Modello di classificazione caricato correttamente.")
|
34 |
+
except Exception as e:
|
35 |
+
logger.error(f"Errore nel caricamento del modello di classificazione: {e}")
|
36 |
+
classifier_model = None
|
37 |
+
explanation_dict = {}
|
38 |
# ---------------------------------------------------------------------------
|
39 |
# COSTANTI / CHIAVI / MODELLI
|
40 |
# ---------------------------------------------------------------------------
|
41 |
# Nota: HF_API_KEY deve essere impostata a una chiave valida di Hugging Face.
|
42 |
+
HF_API_KEY = os.getenv("HF_API_KEY")
|
43 |
if not HF_API_KEY:
|
44 |
# Se la chiave API non è impostata, solleva un errore
|
45 |
logger.error("HF_API_KEY non impostata.")
|
|
|
60 |
"""
|
61 |
Qui inizializziamo i client necessari. In questo modo, evitiamo di istanziare
|
62 |
continuamente nuovi oggetti InferenceClient a ogni chiamata delle funzioni.
|
|
|
63 |
- hf_generation_client: per generare query SPARQL e risposte stile "guida museale"
|
64 |
- lang_detect_client: per rilevare la lingua della domanda e della risposta
|
65 |
"""
|
|
|
120 |
# ---------------------------------------------------------------------------
|
121 |
# FUNZIONI DI SUPPORTO (Prompts, validazione SPARQL, correzioni, ecc.)
|
122 |
# ---------------------------------------------------------------------------
|
123 |
+
def create_system_prompt_for_classification(ontology_turtle:str) -> str:
|
124 |
+
prompt = f"""
|
125 |
+
SEI UN CLASSIFICATORE DI DOMANDE NEL CONTESTO DI UN MUSEO.
|
126 |
+
DEVI ANALIZZARE LA DOMANDA DELL'UTENTE E, BASANDOTI SUL CONTESTO DELL'ONTOLOGIA (CHE TI VIENE FORNITA QUI SOTTO), RISPONDERE SOLAMENTE CON "SI" SE LA DOMANDA È PERTINENTE AL MUSEO, O CON "NO" SE LA DOMANDA NON È PERTINENTE.
|
127 |
+
RICORDA:
|
128 |
+
- "PERTINENTE" SIGNIFICA CHE LA DOMANDA RIGUARDA OPERE D'ARTE, ESPOSTE, BIGLIETTI, VISITATORI, CURATORI, RESTAURI, STANZE E TUTTI GLI ASPETTI RELATIVI ALL'AMBIENTE MUSEALE.
|
129 |
+
- "NON PERTINENTE" SIGNIFICA CHE LA DOMANDA RIGUARDA ARGOMENTI ESTERNI AL CONTESTO MUSEALE (PER ESEMPIO, TECNOLOGIA, SPORT, POLITICA, CUCINA, ECC.).
|
130 |
+
NON FORNIRE ULTERIORI SPIEGAZIONI O COMMENTI: LA TUA RISPOSTA DEVE ESSERE ESCLUSIVAMENTE "SI" O "NO".
|
131 |
+
|
132 |
+
ONTOLOGIA (TURTLE/XML/ALTRO FORMATO):
|
133 |
+
{ontology_turtle}
|
134 |
|
135 |
+
FINE ONTOLOGIA.
|
136 |
+
|
137 |
+
"""
|
138 |
+
return prompt
|
139 |
def create_system_prompt_for_sparql(ontology_turtle: str) -> str:
|
140 |
"""
|
141 |
Genera il testo di prompt che istruisce il modello su come costruire
|
142 |
SOLO UNA query SPARQL, in un'unica riga, o in alternativa 'NO_SPARQL'
|
143 |
se la domanda non è pertinente all'ontologia. Il prompt include regole
|
144 |
+
di formattazione, esempi di domanda-risposta SPARQL e regole rigorose
|
145 |
+
per la gestione della posizione dell'opera.
|
146 |
|
147 |
Parametri:
|
148 |
- ontology_turtle: una stringa con l'ontologia in formato Turtle (o simile).
|
|
|
153 |
prompt = f"""SEI UN GENERATORE DI QUERY SPARQL PER L'ONTOLOGIA DI UN MUSEO.
|
154 |
DEVI GENERARE SOLO UNA QUERY SPARQL (IN UNA SOLA RIGA) SE LA DOMANDA RIGUARDA INFORMAZIONI NELL'ONTOLOGIA.
|
155 |
SE LA DOMANDA NON È ATTINENTE, RISPONDI 'NO_SPARQL'.
|
|
|
156 |
REGOLE SINTATTICHE RIGOROSE:
|
157 |
1) Usare: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
|
158 |
2) Query in UNA SOLA RIGA (niente a capo), forma: PREFIX progettoMuseo: <...> SELECT ?x WHERE {{ ... }} LIMIT N
|
|
|
166 |
Esempi di Domande Specifiche e relative Query:
|
167 |
1) Utente: Chi ha creato l'opera 'Afrodite di Milo'?
|
168 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?autore WHERE {{ progettoMuseo:AfroditeDiMilo progettoMuseo:autoreOpera ?autore . }} LIMIT 10
|
|
|
169 |
2) Utente: Quali sono le tecniche utilizzate nelle opere?
|
170 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?tecnica WHERE {{ ?opera progettoMuseo:tecnicaOpera ?tecnica . }} LIMIT 100
|
|
|
171 |
3) Utente: Quali sono le dimensioni delle opere?
|
172 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?dimensione WHERE {{ ?opera progettoMuseo:dimensioneOpera ?dimensione . }} LIMIT 100
|
|
|
173 |
4) Utente: Quali opere sono esposte nella stanza Greca?
|
174 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ progettoMuseo:StanzaGrecia progettoMuseo:Espone ?opera . }} LIMIT 100
|
|
|
175 |
5) Utente: Quali sono le proprietà e i tipi delle proprietà nell'ontologia?
|
176 |
Risposta: PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX owl: <http://www.w3.org/2002/07/owl#> PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT DISTINCT ?property ?type WHERE {{ ?property rdf:type ?type . FILTER(?type IN (owl:ObjectProperty, owl:DatatypeProperty)) }}
|
|
|
177 |
6) Utente: Recupera tutti i biglietti e i tipi di biglietto.
|
178 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?biglietto ?tipoBiglietto WHERE {{ ?biglietto rdf:type progettoMuseo:Biglietto . ?biglietto progettoMuseo:tipoBiglietto ?tipoBiglietto . }} LIMIT 100
|
|
|
179 |
7) Utente: Recupera tutti i visitatori e i tour a cui partecipano.
|
180 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?visitatore ?tour WHERE {{ ?visitatore progettoMuseo:Partecipazione_a_Evento ?tour . }} LIMIT 100
|
|
|
181 |
8) Utente: Recupera tutte le stanze tematiche e le opere esposte.
|
182 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?stanza ?opera WHERE {{ ?stanza rdf:type progettoMuseo:Stanza_Tematica . ?stanza progettoMuseo:Espone ?opera . }} LIMIT 100
|
|
|
183 |
9) Utente: Recupera tutte le opere con materiale 'Marmo'.
|
184 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ ?opera progettoMuseo:materialeOpera "Marmo"@it . }} LIMIT 100
|
|
|
185 |
10) Utente: Recupera tutti i visitatori con data di nascita dopo il 2000.
|
186 |
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?visitatore WHERE {{ ?visitatore rdf:type progettoMuseo:Visitatore_Individuale . ?visitatore progettoMuseo:dataDiNascitaVisitatore ?data . FILTER(?data > "2000-01-01T00:00:00"^^xsd:dateTime) . }} LIMIT 100
|
187 |
|
188 |
+
NUOVE REGOLE RIGUARDANTI LA POSIZIONE DELL'OPERA:
|
189 |
+
- SE la domanda include richieste come "fammi vedere l'opera X", "mi fai vedere l'opera X", "portami all'opera X", "voglio vedere l'opera X" o simili, la query SPARQL DEVE restituire la posizione dell'opera includendo la proprietà progettoMuseo:posizioneOpera.
|
190 |
+
- SE la domanda include espressioni come "dove si trova l'opera X", "ubicazione dell'opera X" o simili, la query SPARQL NON deve restituire la proprietà progettoMuseo:posizioneOpera, limitandosi al massimo a riportare la relazione esistente.
|
191 |
+
|
192 |
+
Esempi Aggiuntivi:
|
193 |
+
11) Utente: Fammi vedere l'opera 'Discobolo'.
|
194 |
+
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?posizione WHERE {{ progettoMuseo:Discobolo progettoMuseo:posizioneOpera ?posizione . }} LIMIT 10
|
195 |
+
12) Utente: Mi fai vedere l'opera 'Gioconda'?
|
196 |
+
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?posizione WHERE {{ progettoMuseo:Gioconda progettoMuseo:posizioneOpera ?posizione . }} LIMIT 10
|
197 |
+
13) Utente: Portami all'opera 'Autoritratto'.
|
198 |
+
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera ?posizione WHERE {{ progettoMuseo:Autoritratto progettoMuseo:posizioneOpera ?posizione . }} LIMIT 10
|
199 |
+
14) Utente: Dove si trova l'opera 'La Notte Stellata'?
|
200 |
+
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ progettoMuseo:LaNotteStellata ?rel ?info . FILTER(?rel != progettoMuseo:posizioneOpera) }} LIMIT 10
|
201 |
+
15) Utente: Qual è l'ubicazione dell'opera 'Ramo Di Mandorlo Fiorito'?
|
202 |
+
Risposta: PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?opera WHERE {{ progettoMuseo:RamoDiMandorloFiorito ?rel ?info . FILTER(?rel != progettoMuseo:posizioneOpera) }} LIMIT 10
|
203 |
+
|
204 |
ECCO L'ONTOLOGIA (TURTLE) PER CONTESTO:
|
205 |
{ontology_turtle}
|
206 |
FINE ONTOLOGIA.
|
207 |
"""
|
208 |
+
logger.debug("[create_system_prompt_for_sparql] Prompt generato con esempi originali e nuove regole rigorose sulla posizione.")
|
209 |
return prompt
|
210 |
|
211 |
|
212 |
+
|
213 |
+
def classify_and_translate(question_text: str, model_answer_text: str):
|
214 |
"""
|
215 |
Classifica la lingua della domanda e della risposta, quindi traduce la risposta
|
216 |
se la lingua è diversa da quella della domanda. L'idea è di restituire una
|
|
|
223 |
Restituisce:
|
224 |
- La risposta tradotta nella lingua della domanda o la risposta originale
|
225 |
se entrambe le lingue coincidono.
|
|
|
226 |
NB: Qui l'oggetto 'lang_detect_client' (per rilevamento lingua) è già
|
227 |
stato inizializzato all'avvio dell'app. Mentre il 'translator_client'
|
228 |
viene creato 'al volo' poiché la direzione di traduzione dipende
|
|
|
232 |
try:
|
233 |
question_lang_result = lang_detect_client.text_classification(text=question_text)
|
234 |
question_lang = question_lang_result[0]['label']
|
235 |
+
explanation_dict['language_detection'] = f"Domanda in: {question_lang}."
|
236 |
logger.info(f"[LangDetect] Lingua della domanda: {question_lang}")
|
237 |
except Exception as e:
|
238 |
logger.error(f"Errore nel rilevamento della lingua della domanda: {e}")
|
239 |
question_lang = "en" # Fallback se non riusciamo a rilevare la lingua
|
240 |
+
explanation_dict['language_detection'] = "Lingua domanda non rilevata, impostata a 'en'."
|
241 |
# Rileva la lingua della risposta
|
242 |
try:
|
243 |
answer_lang_result = lang_detect_client.text_classification(text=model_answer_text)
|
244 |
answer_lang = answer_lang_result[0]['label']
|
245 |
+
explanation_dict['language_detection'] += f" Risposta in: {answer_lang}."
|
246 |
logger.info(f"[LangDetect] Lingua della risposta: {answer_lang}")
|
247 |
except Exception as e:
|
248 |
logger.error(f"Errore nel rilevamento della lingua della risposta: {e}")
|
249 |
answer_lang = "it" # Fallback se non riusciamo a rilevare la lingua
|
250 |
+
explanation_dict['language_detection'] += " Lingua risposta non rilevata, impostata a 'it'."
|
251 |
|
252 |
# Se domanda e risposta sono nella stessa lingua, non traduciamo
|
253 |
if question_lang == answer_lang:
|
254 |
logger.info("[Translate] Nessuna traduzione necessaria: stessa lingua.")
|
255 |
+
explanation_dict['translation'] = "Nessuna traduzione necessaria."
|
256 |
return model_answer_text
|
257 |
|
258 |
# Altrimenti, costruiamo "al volo" il modello di traduzione appropriato
|
|
|
262 |
token=HF_API_KEY,
|
263 |
model=translator_model
|
264 |
)
|
265 |
+
explanation_dict['translation'] = f"Usato modello: {translator_model}."
|
266 |
# Traduzione della risposta
|
267 |
try:
|
268 |
translation_result = translator_client.translation(text=model_answer_text)
|
269 |
translated_answer = translation_result["translation_text"]
|
270 |
+
explanation_dict['translation'] += " Traduzione riuscita."
|
271 |
except Exception as e:
|
272 |
logger.error(f"Errore nella traduzione {answer_lang} -> {question_lang}: {e}")
|
273 |
+
explanation_dict['translation'] += " Traduzione fallita, risposta originale usata."
|
274 |
# Se fallisce, restituiamo la risposta originale come fallback
|
275 |
+
explanation_dict['translation'] = "Errore inizializzazione traduttore."
|
276 |
translated_answer = model_answer_text
|
277 |
|
278 |
return translated_answer
|
|
|
287 |
"""
|
288 |
prompt = (
|
289 |
"SEI UNA GUIDA MUSEALE VIRTUALE. "
|
290 |
+
"RISPONDI IN MODO DISCORSIVO E NATURALE (circa 100 parole), SENZA SALUTI O INTRODUZIONI PROLISSE. "
|
291 |
+
"SE HAI RISULTATI SPARQL, USALI; SE NON CI SONO, RISPONDI BASANDOTI SULLE TUE CONOSCENZE. "
|
292 |
+
"QUALORA LA DOMANDA CONTENGA ESPRESSIONI COME 'fammi vedere', 'portami', 'mi fai vedere', O SIMILI, "
|
293 |
+
"TRADUCI LA RICHIESTA IN UN INVITO ALL'AZIONE, AD ESEMPIO 'Adesso ti accompagno all'opera', "
|
294 |
+
"SENZA RIPETERE DETTAGLI SPAZIALI O TECNICI (ES. COORDINATE, DISTANZE, POSITIONI FISICHE). "
|
295 |
+
"PER ALTRE DOMANDE, RISPONDI IN MODO DESCRITTIVO MA SENZA INCLUDERE INFORMAZIONI TECNICHE SULLA POSIZIONE."
|
296 |
)
|
297 |
logger.debug("[create_system_prompt_for_guide] Prompt per la risposta guida museale generato.")
|
298 |
return prompt
|
|
|
313 |
|
314 |
Parametri:
|
315 |
- query: stringa con la query SPARQL potenzialmente mal formattata.
|
|
|
316 |
Ritorna:
|
317 |
- La query SPARQL corretta se possibile, in singola riga.
|
318 |
"""
|
|
|
375 |
# ---------------------------------------------------------------------------
|
376 |
@app.post("/assistant")
|
377 |
def assistant_endpoint(req: AssistantRequest):
|
378 |
+
explanation_dict = {}
|
379 |
"""
|
380 |
Endpoint che gestisce l'intera pipeline:
|
381 |
1) Genera una query SPARQL dal messaggio dell'utente (prompt dedicato).
|
|
|
397 |
}
|
398 |
"""
|
399 |
logger.info("Ricevuta chiamata POST su /assistant")
|
|
|
400 |
# Estraggo i campi dal body della richiesta
|
401 |
user_message = req.message
|
402 |
max_tokens = req.max_tokens
|
403 |
temperature = req.temperature
|
|
|
404 |
logger.debug(f"Parametri utente: message='{user_message}', max_tokens={max_tokens}, temperature={temperature}")
|
405 |
+
# -------------------------------
|
406 |
+
# CLASSIFICAZIONE DEL TESTO RICEVUTO
|
407 |
+
# -------------------------------
|
408 |
+
if classifier_model is not None:
|
409 |
+
try:
|
410 |
+
# Prepara l'input per il modello di classificazione
|
411 |
+
inputs = classifier_tokenizer(user_message, return_tensors="pt", truncation=True, padding=True)
|
412 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
413 |
+
|
414 |
+
# Disattiva il calcolo del gradiente per velocizzare l'inferenza
|
415 |
+
with torch.no_grad():
|
416 |
+
outputs = classifier_model(**inputs)
|
417 |
+
logits = outputs.logits
|
418 |
+
pred = torch.argmax(logits, dim=1).item()
|
419 |
+
|
420 |
+
# Mappa l'etichetta numerica a una stringa (modifica secondo la tua logica)
|
421 |
+
label_mapping = {0: "NON PERTINENTE", 1: "PERTINENTE"}
|
422 |
+
classification_result = label_mapping.get(pred, f"Etichetta {pred}")
|
423 |
+
logger.info(f"[Classificazione] La domanda classificata come: {classification_result}")
|
424 |
+
explanation_dict['classification'] = f"Risultato classificazione: {classification_result}"
|
425 |
+
except Exception as e:
|
426 |
+
logger.error(f"Errore durante la classificazione della domanda: {e}")
|
427 |
+
explanation_dict['classification'] = f"Errore classificazione: {e}"
|
428 |
+
else:
|
429 |
+
logger.warning("Modello di classificazione non disponibile.")
|
430 |
+
explanation_dict['classification'] = "Modello di classificazione non disponibile."
|
431 |
# -----------------------------------------------------------------------
|
432 |
# STEP 1: Generazione della query SPARQL
|
433 |
# -----------------------------------------------------------------------
|
|
|
435 |
# Serializziamo l'ontologia in XML per fornirla al prompt (anche se si chiama 'turtle' va bene così).
|
436 |
ontology_turtle = ontology_graph.serialize(format="xml")
|
437 |
logger.debug("Ontologia serializzata con successo (XML).")
|
438 |
+
explanation_dict['ontology'] = "Ontologia serializzata."
|
439 |
except Exception as e:
|
440 |
logger.warning(f"Impossibile serializzare l'ontologia in formato XML: {e}")
|
441 |
+
explanation_dict['ontology'] = f"Errore serializzazione: {e}."
|
442 |
ontology_turtle = ""
|
443 |
|
444 |
# Creiamo il prompt di sistema per la generazione SPARQL
|
445 |
system_prompt_sparql = create_system_prompt_for_sparql(ontology_turtle)
|
446 |
+
explanation_dict['sparql_prompt'] = "Prompt SPARQL creato."
|
447 |
# Chiamata al modello per generare la query SPARQL
|
448 |
try:
|
449 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la query SPARQL...")
|
450 |
+
explanation_dict['sparql_generation'] = "Generazione query SPARQL iniziata."
|
451 |
gen_sparql_output = hf_generation_client.chat.completions.create(
|
452 |
messages=[
|
453 |
{"role": "system", "content": system_prompt_sparql},
|
|
|
458 |
)
|
459 |
possible_query = gen_sparql_output["choices"][0]["message"]["content"].strip()
|
460 |
logger.info(f"[assistant_endpoint] Query generata dal modello: {possible_query}")
|
461 |
+
explanation_dict['sparql_generation'] += f" Query: {possible_query}."
|
462 |
except Exception as ex:
|
463 |
logger.error(f"Errore nella generazione della query SPARQL: {ex}")
|
464 |
+
explanation_dict['sparql_generation'] = f"Errore generazione SPARQL: {ex}."
|
465 |
# Se fallisce la generazione, consideriamo la query come "NO_SPARQL"
|
466 |
possible_query = "NO_SPARQL"
|
467 |
|
|
|
472 |
else:
|
473 |
# Applichiamo la correzione avanzata
|
474 |
advanced_corrected = correct_sparql_syntax_advanced(possible_query)
|
475 |
+
explanation_dict['sparql_correction'] = "Sintassi SPARQL corretta."
|
476 |
# Verifichiamo la validità della query
|
477 |
if is_sparql_query_valid(advanced_corrected):
|
478 |
generated_query = advanced_corrected
|
479 |
logger.debug(f"[assistant_endpoint] Query SPARQL valida dopo correzione avanzata: {generated_query}")
|
480 |
+
explanation_dict['sparql_validation'] = "Query SPARQL valida."
|
481 |
else:
|
482 |
logger.debug("[assistant_endpoint] Query SPARQL non valida. Verrà ignorata.")
|
483 |
+
explanation_dict['sparql_validation'] = "Query SPARQL non valida."
|
484 |
generated_query = None
|
485 |
|
486 |
# -----------------------------------------------------------------------
|
|
|
489 |
results = []
|
490 |
if generated_query:
|
491 |
logger.debug(f"[assistant_endpoint] Esecuzione della query SPARQL:\n{generated_query}")
|
492 |
+
explanation_dict['sparql_execution'] = "Esecuzione query SPARQL."
|
493 |
try:
|
494 |
query_result = ontology_graph.query(generated_query)
|
495 |
results = list(query_result)
|
496 |
logger.info(f"[assistant_endpoint] Query eseguita con successo. Numero risultati = {len(results)}")
|
497 |
+
explanation_dict['sparql_execution'] += f" Risultati: {len(results)}."
|
498 |
except Exception as ex:
|
499 |
logger.error(f"[assistant_endpoint] Errore nell'esecuzione della query: {ex}")
|
500 |
+
explanation_dict['sparql_execution'] = f"Errore esecuzione: {ex}."
|
501 |
results = []
|
502 |
+
else:
|
503 |
+
explanation_dict['sparql_execution'] = "Nessuna query eseguita."
|
504 |
# -----------------------------------------------------------------------
|
505 |
# STEP 3: Generazione della risposta finale stile "guida museale"
|
506 |
# -----------------------------------------------------------------------
|
507 |
system_prompt_guide = create_system_prompt_for_guide()
|
508 |
+
explanation_dict['guide_prompt'] = "Prompt guida museale creato."
|
509 |
if generated_query and results:
|
510 |
# Caso: query generata + risultati SPARQL
|
511 |
# Convertiamo i risultati in una stringa più leggibile
|
|
|
521 |
"Rispondi in modo breve (max ~50 parole)."
|
522 |
)
|
523 |
logger.debug("[assistant_endpoint] Prompt di risposta con risultati SPARQL.")
|
524 |
+
explanation_dict['guide_prompt'] += " Inclusi risultati SPARQL."
|
525 |
elif generated_query and not results:
|
526 |
# Caso: query valida ma 0 risultati
|
527 |
second_prompt = (
|
|
|
531 |
"Nessun risultato dalla query. Prova comunque a rispondere con le tue conoscenze."
|
532 |
)
|
533 |
logger.debug("[assistant_endpoint] Prompt di risposta: query valida ma senza risultati.")
|
534 |
+
explanation_dict['guide_prompt'] += " Query valida senza risultati."
|
535 |
else:
|
536 |
# Caso: nessuna query generata
|
537 |
second_prompt = (
|
|
|
540 |
"Nessuna query SPARQL generata. Rispondi come puoi, riarrangiando le tue conoscenze."
|
541 |
)
|
542 |
logger.debug("[assistant_endpoint] Prompt di risposta: nessuna query generata.")
|
543 |
+
explanation_dict['guide_prompt'] += " Nessuna query generata."
|
544 |
# Chiamata finale al modello per la risposta "guida museale"
|
545 |
try:
|
546 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la risposta finale...")
|
547 |
+
explanation_dict['response_generation'] = "Generazione risposta finale iniziata."
|
548 |
final_output = hf_generation_client.chat.completions.create(
|
549 |
messages=[
|
550 |
{"role": "system", "content": second_prompt},
|
551 |
{"role": "user", "content": "Fornisci la risposta finale."}
|
552 |
],
|
553 |
+
max_tokens=1024,
|
554 |
+
temperature=0.8
|
555 |
)
|
556 |
final_answer = final_output["choices"][0]["message"]["content"].strip()
|
557 |
logger.info(f"[assistant_endpoint] Risposta finale generata: {final_answer}")
|
558 |
+
explanation_dict['response_generation'] += " Risposta generata."
|
559 |
except Exception as ex:
|
560 |
logger.error(f"Errore nella generazione della risposta finale: {ex}")
|
561 |
+
explanation_dict['response_generation'] = f"Errore generazione risposta finale: {ex}."
|
562 |
raise HTTPException(status_code=500, detail="Errore nella generazione della risposta in linguaggio naturale.")
|
563 |
|
564 |
# -----------------------------------------------------------------------
|
|
|
566 |
# -----------------------------------------------------------------------
|
567 |
final_ans = classify_and_translate(user_message, final_answer)
|
568 |
final_ans = final_ans.replace('\\"', "").replace('\"', "")
|
569 |
+
explanation_dict['translation_completion'] = "Traduzione completata."
|
570 |
# -----------------------------------------------------------------------
|
571 |
# Restituzione in formato JSON
|
572 |
# -----------------------------------------------------------------------
|
573 |
logger.debug("[assistant_endpoint] Fine elaborazione, restituzione risposta JSON.")
|
574 |
return {
|
575 |
"query": generated_query,
|
576 |
+
"response": final_ans,
|
577 |
+
"explanation": explanation_dict
|
578 |
}
|
|
|
579 |
# ---------------------------------------------------------------------------
|
580 |
# ENDPOINT DI TEST / HOME
|
581 |
# ---------------------------------------------------------------------------
|
|
|
588 |
return {
|
589 |
"message": "Endpoint attivo. Esempio di backend per generare query SPARQL e risposte guida museale."
|
590 |
}
|
591 |
+
# ---------------------------------------------------------------------------
|
592 |
+
# ENDPOINT QUERY STANZE /QUERY_STANZE
|
593 |
+
# ---------------------------------------------------------------------------
|
594 |
+
@app.get("/query_stanze")
|
595 |
+
def query_stanze_endpoint():
|
596 |
+
"""
|
597 |
+
Endpoint per restituire le stanze con le opere esposte e relativi punti.
|
598 |
+
La query restituisce, per ogni triple, la stanza, l'opera e il valore della proprietà
|
599 |
+
progettoMuseo:posizioneOpera (facoltativo). Successivamente, in Python, si raggruppa
|
600 |
+
per stanza in un dizionario. Per ogni stanza, viene creata una lista di dizionari,
|
601 |
+
dove ogni dizionario rappresenta un'opera e contiene:
|
602 |
+
- "nome": il localName dell'opera (la parte dopo il simbolo "#")
|
603 |
+
- "punto": il valore della proprietà progettoMuseo:posizioneOpera (se presente, altrimenti una stringa vuota)
|
604 |
+
|
605 |
+
Query usata (in una riga):
|
606 |
+
PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
|
607 |
+
SELECT ?stanza ?opera ?p
|
608 |
+
WHERE {
|
609 |
+
?opera progettoMuseo:èEsposto ?stanza.
|
610 |
+
OPTIONAL { ?opera progettoMuseo:posizioneOpera ?p. }
|
611 |
+
}
|
612 |
+
"""
|
613 |
+
# Definiamo la query in una singola riga
|
614 |
+
query_str = (
|
615 |
+
"PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> "
|
616 |
+
"SELECT ?stanza ?opera ?p WHERE { ?opera progettoMuseo:èEsposto ?stanza. "
|
617 |
+
"OPTIONAL { ?opera progettoMuseo:posizioneOpera ?p. } }"
|
618 |
+
)
|
619 |
+
|
620 |
+
# Applichiamo eventuali correzioni sintattiche
|
621 |
+
corrected_query = correct_sparql_syntax_advanced(query_str)
|
622 |
+
logger.debug(f"[query_stanze_endpoint] Query corretta:\n{corrected_query}")
|
623 |
+
|
624 |
+
# Verifichiamo la validità della query utilizzando il parser di rdflib
|
625 |
+
if not is_sparql_query_valid(corrected_query):
|
626 |
+
logger.error("[query_stanze_endpoint] Query SPARQL non valida.")
|
627 |
+
raise HTTPException(status_code=400, detail="Query SPARQL non valida.")
|
628 |
+
|
629 |
+
try:
|
630 |
+
query_result = ontology_graph.query(corrected_query)
|
631 |
+
|
632 |
+
# Costruiamo un dizionario: per ogni stanza (localName), una lista di dizionari
|
633 |
+
# con chiavi "nome" (localName dell'opera) e "punto" (valore di progettoMuseo:posizioneOpera)
|
634 |
+
dict_stanze = {}
|
635 |
+
for row in query_result:
|
636 |
+
# Estraiamo il localName della stanza
|
637 |
+
stanza_uri = str(row["stanza"])
|
638 |
+
stanza_local = stanza_uri.split("#")[-1].strip() if "#" in stanza_uri else stanza_uri
|
639 |
+
|
640 |
+
# Estraiamo il localName dell'opera
|
641 |
+
opera_uri = str(row["opera"])
|
642 |
+
opera_local = opera_uri.split("#")[-1].strip() if "#" in opera_uri else opera_uri
|
643 |
+
|
644 |
+
# Estraiamo il punto (se presente)
|
645 |
+
punto = str(row["p"]) if row["p"] is not None else ""
|
646 |
+
|
647 |
+
# Costruiamo il dizionario per l'opera
|
648 |
+
opera_dict = {"nome": opera_local, "punto": punto}
|
649 |
+
|
650 |
+
# Raggruppiamo per stanza
|
651 |
+
if stanza_local not in dict_stanze:
|
652 |
+
dict_stanze[stanza_local] = []
|
653 |
+
dict_stanze[stanza_local].append(opera_dict)
|
654 |
+
|
655 |
+
logger.info(f"[query_stanze_endpoint] Trovate {len(dict_stanze)} stanze.")
|
656 |
+
except Exception as ex:
|
657 |
+
logger.error(f"[query_stanze_endpoint] Errore nell'esecuzione della query: {ex}")
|
658 |
+
raise HTTPException(status_code=500, detail="Errore nell'esecuzione della query SPARQL.")
|
659 |
+
|
660 |
+
return {
|
661 |
+
"stanze": dict_stanze
|
662 |
+
}
|
663 |
# ---------------------------------------------------------------------------
|
664 |
# MAIN
|
665 |
# ---------------------------------------------------------------------------
|
|
|
668 |
Avvio dell'applicazione FastAPI sulla porta 8000,
|
669 |
utile se eseguito come script principale.
|
670 |
"""
|
671 |
+
logger.info("Avvio dell'applicazione FastAPI.")
|
672 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|