AshenClock commited on
Commit
42dba13
·
verified ·
1 Parent(s): d613212

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -39
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 e alcuni esempi di domanda-risposta SPARQL.
 
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 ESEMPI e regole SPARQL.")
171
  return prompt
172
 
173
 
174
- def classify_and_translate(question_text: str, model_answer_text: str) -> str:
 
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
- logger.info("[Translate] Risposta tradotta con successo.")
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 BREVE (~50 PAROLE), SENZA SALUTI O INTRODUZIONI PROLISSE. "
247
- "SE HAI RISULTATI SPARQL, USALI. "
248
- "SE NON HAI RISULTATI O NON HAI UNA QUERY, RISPONDI COMUNQUE CERCANDO DI RIARRANGIARE LE TUE CONOSCENZE."
 
 
 
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=max_tokens,
471
- temperature=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)