AshenClock commited on
Commit
2ecaeb3
·
verified ·
1 Parent(s): 93232b8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -210
app.py CHANGED
@@ -17,7 +17,7 @@ load_dotenv()
17
 
18
  # Configura il logging
19
  logging.basicConfig(
20
- level=logging.INFO, # Ridotto da DEBUG per migliorare le prestazioni
21
  format="%(asctime)s - %(levelname)s - %(message)s",
22
  handlers=[logging.FileHandler("app.log"), logging.StreamHandler()]
23
  )
@@ -34,118 +34,56 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
34
  RDF_FILE = os.path.join(BASE_DIR, "Ontologia.rdf")
35
  HF_MODEL = "Qwen/Qwen2.5-72B-Instruct" # Modello ottimizzato per seguire istruzioni
36
 
37
- MAX_CLASSES = 30
38
- MAX_PROPERTIES = 30
39
-
40
  # Percorsi dei file generati
41
  DOCUMENTS_FILE = os.path.join(BASE_DIR, "data", "documents.json")
42
  FAISS_INDEX_FILE = os.path.join(BASE_DIR, "data", "faiss.index")
43
 
 
 
 
 
 
 
 
 
44
  def create_data_directory():
45
  """Crea la directory 'data/' se non esiste."""
46
  os.makedirs(os.path.join(BASE_DIR, "data"), exist_ok=True)
47
  logger.info("Directory 'data/' creata o già esistente.")
48
 
49
- def extract_ontology(rdf_file: str, output_file: str):
50
  """
51
- Estrae classi, proprietà ed entità dall'ontologia RDF e le salva in un file JSON come un unico documento.
 
52
  """
53
- logger.info(f"Inizio estrazione dell'ontologia da {rdf_file}.")
54
- g = rdflib.Graph()
55
- try:
56
- g.parse(rdf_file, format="xml")
57
- logger.info(f"Parsing RDF di {rdf_file} riuscito.")
58
- except Exception as e:
59
- logger.error(f"Errore nel parsing RDF: {e}")
60
- raise e
61
-
62
- # Estrai Classi
63
- classes = []
64
- for cls in g.subjects(RDF.type, OWL.Class):
65
- label = g.value(cls, RDFS.label, default=str(cls))
66
- description = g.value(cls, RDFS.comment, default="No description.")
67
- classes.append({"class": str(cls), "label": str(label), "description": str(description)})
68
-
69
- for cls in g.subjects(RDF.type, RDFS.Class):
70
- label = g.value(cls, RDFS.label, default=str(cls))
71
- description = g.value(cls, RDFS.comment, default="No description.")
72
- classes.append({"class": str(cls), "label": str(label), "description": str(description)})
73
-
74
- # Estrai Proprietà
75
- properties = []
76
- for prop in g.subjects(RDF.type, OWL.ObjectProperty):
77
- label = g.value(prop, RDFS.label, default=str(prop))
78
- description = g.value(prop, RDFS.comment, default="No description.")
79
- properties.append({"property": str(prop), "label": str(label), "description": str(description)})
80
-
81
- for prop in g.subjects(RDF.type, OWL.DatatypeProperty):
82
- label = g.value(prop, RDFS.label, default=str(prop))
83
- description = g.value(prop, RDFS.comment, default="No description.")
84
- properties.append({"property": str(prop), "label": str(label), "description": str(description)})
85
-
86
- for prop in g.subjects(RDF.type, RDF.Property):
87
- label = g.value(prop, RDFS.label, default=str(prop))
88
- description = g.value(prop, RDFS.comment, default="No description.")
89
- properties.append({"property": str(prop), "label": str(label), "description": str(description)})
90
-
91
- # Estrai Entità (NamedIndividuals)
92
- entities = []
93
- for entity in g.subjects(RDF.type, OWL.NamedIndividual):
94
- label = g.value(entity, RDFS.label, default=str(entity))
95
- description = g.value(entity, RDFS.comment, default="No description.")
96
- # Se l'etichetta è un URI, estrai il fragment
97
- if isinstance(label, URIRef):
98
- label = label.split('#')[-1].replace('_', ' ')
99
- else:
100
- label = str(label)
101
- # Estrai le proprietà dell'entità
102
- entity_properties = {}
103
- for predicate, obj in g.predicate_objects(entity):
104
- if predicate not in [RDFS.label, RDFS.comment]:
105
- entity_properties[str(predicate)] = str(obj)
106
- entities.append({
107
- "entity": str(entity),
108
- "label": str(label),
109
- "description": str(description),
110
- "properties": entity_properties
111
- })
112
-
113
- # Crea un unico documento
114
- ontology_summary = {
115
- "title": "Ontologia Museo",
116
- "classes": classes[:MAX_CLASSES],
117
- "properties": properties[:MAX_PROPERTIES],
118
- "entities": entities, # Aggiungi le entità
119
- "full_ontology": g.serialize(format="xml").decode('utf-8') if isinstance(g.serialize(format="xml"), bytes) else g.serialize(format="xml") # Decodifica rimossa
120
- }
121
-
122
- # Salva il documento in JSON
123
  try:
 
 
 
 
 
124
  with open(output_file, "w", encoding="utf-8") as f:
125
- json.dump(ontology_summary, f, ensure_ascii=False, indent=2)
126
- logger.info(f"Ontologia estratta e salvata in {output_file}")
127
  except Exception as e:
128
- logger.error(f"Errore nel salvataggio di {output_file}: {e}")
129
  raise e
130
 
131
- def create_faiss_index(documents_file: str, index_file: str, embedding_model: str = 'all-MiniLM-L6-v2'):
132
  """
133
- Crea un indice FAISS a partire dal documento estratto.
134
  """
135
  logger.info(f"Inizio creazione dell'indice FAISS da {documents_file}.")
136
  try:
137
  # Carica il documento
138
  with open(documents_file, "r", encoding="utf-8") as f:
139
  document = json.load(f)
140
- logger.info(f"Documento caricato da {documents_file}.")
 
141
 
142
  # Genera embedding
143
- model = SentenceTransformer(embedding_model)
144
- # Concatenazione delle classi, proprietà e entità per l'embedding
145
- texts = [f"Classe: {cls['label']}. Descrizione: {cls['description']}" for cls in document['classes']]
146
- texts += [f"Proprietà: {prop['label']}. Descrizione: {prop['description']}" for prop in document['properties']]
147
- texts += [f"Entità: {entity['label']}. Descrizione: {entity['description']}. Proprietà: {entity['properties']}" for entity in document.get('entities', [])]
148
- embeddings = model.encode(texts, convert_to_numpy=True)
149
  logger.info("Embedding generati con SentenceTransformer.")
150
 
151
  # Crea l'indice FAISS
@@ -161,7 +99,7 @@ def create_faiss_index(documents_file: str, index_file: str, embedding_model: st
161
  logger.error(f"Errore nella creazione dell'indice FAISS: {e}")
162
  raise e
163
 
164
- def prepare_retrieval():
165
  """Prepara i file necessari per l'approccio RAG."""
166
  logger.info("Inizio preparazione per il retrieval.")
167
  create_data_directory()
@@ -175,11 +113,11 @@ def prepare_retrieval():
175
 
176
  # Verifica se documents.json esiste, altrimenti generarlo
177
  if not os.path.exists(DOCUMENTS_FILE):
178
- logger.info(f"File {DOCUMENTS_FILE} non trovato. Estrazione dell'ontologia.")
179
  try:
180
- extract_ontology(RDF_FILE, DOCUMENTS_FILE)
181
  except Exception as e:
182
- logger.error(f"Errore nell'estrazione dell'ontologia: {e}")
183
  raise e
184
  else:
185
  logger.info(f"File {DOCUMENTS_FILE} trovato.")
@@ -188,144 +126,80 @@ def prepare_retrieval():
188
  if not os.path.exists(FAISS_INDEX_FILE):
189
  logger.info(f"File {FAISS_INDEX_FILE} non trovato. Creazione dell'indice FAISS.")
190
  try:
191
- create_faiss_index(DOCUMENTS_FILE, FAISS_INDEX_FILE)
192
  except Exception as e:
193
  logger.error(f"Errore nella creazione dell'indice FAISS: {e}")
194
  raise e
195
  else:
196
  logger.info(f"File {FAISS_INDEX_FILE} trovato.")
197
 
198
- def extract_classes_and_properties(rdf_file: str) -> str:
199
- """
200
- Carica l'ontologia e crea un 'sunto' di Classi, Proprietà ed Entità
201
- (senza NamedIndividuals) per ridurre i token.
202
- """
203
- logger.info(f"Inizio estrazione di classi, proprietà ed entità da {rdf_file}.")
204
- g = rdflib.Graph()
205
- try:
206
- g.parse(rdf_file, format="xml")
207
- logger.info(f"Parsing RDF di {rdf_file} riuscito.")
208
- except Exception as e:
209
- logger.error(f"Errore nel parsing RDF: {e}")
210
- return "PARSING_ERROR"
211
-
212
- # Troviamo le classi
213
- classes_found = set()
214
- for s in g.subjects(RDF.type, OWL.Class):
215
- classes_found.add(s)
216
- for s in g.subjects(RDF.type, RDFS.Class):
217
- classes_found.add(s)
218
- classes_list = sorted(str(c) for c in classes_found)
219
- classes_list = classes_list[:MAX_CLASSES]
220
-
221
- # Troviamo le proprietà
222
- props_found = set()
223
- for p in g.subjects(RDF.type, OWL.ObjectProperty):
224
- props_found.add(p)
225
- for p in g.subjects(RDF.type, OWL.DatatypeProperty):
226
- props_found.add(p)
227
- for p in g.subjects(RDF.type, RDF.Property):
228
- props_found.add(p)
229
- props_list = sorted(str(x) for x in props_found)
230
- props_list = props_list[:MAX_PROPERTIES]
231
-
232
- # Troviamo le entità
233
- entities_found = set()
234
- for e in g.subjects(RDF.type, OWL.NamedIndividual):
235
- entities_found.add(e)
236
- entities_list = sorted(str(e) for e in entities_found)
237
- entities_list = entities_list[:MAX_CLASSES] # Puoi impostare un limite adeguato
238
-
239
- txt_classes = "\n".join([f"- CLASSE: {c}" for c in classes_list])
240
- txt_props = "\n".join([f"- PROPRIETÀ: {p}" for p in props_list])
241
- txt_entities = "\n".join([f"- ENTITÀ: {e}" for e in entities_list])
242
-
243
- summary = f"""\
244
- # CLASSI (max {MAX_CLASSES})
245
- {txt_classes}
246
- # PROPRIETÀ (max {MAX_PROPERTIES})
247
- {txt_props}
248
- # ENTITÀ (max {MAX_CLASSES})
249
- {txt_entities}
250
- """
251
- logger.info("Estrazione di classi, proprietà ed entità completata.")
252
- return summary
253
-
254
- def retrieve_relevant_documents(query: str, top_k: int = 5):
255
- """Recupera i documenti rilevanti usando FAISS."""
256
- logger.info(f"Recupero dei documenti rilevanti per la query: {query}")
257
  try:
258
  # Carica il documento
259
  with open(DOCUMENTS_FILE, "r", encoding="utf-8") as f:
260
  document = json.load(f)
261
- logger.info(f"Documento caricato da {DOCUMENTS_FILE}.")
 
262
 
263
  # Carica l'indice FAISS
264
  index = faiss.read_index(FAISS_INDEX_FILE)
265
  logger.info(f"Indice FAISS caricato da {FAISS_INDEX_FILE}.")
266
 
267
  # Genera embedding della query
268
- model = SentenceTransformer('all-MiniLM-L6-v2')
269
- query_embedding = model.encode([query], convert_to_numpy=True)
 
 
 
270
  logger.info("Embedding della query generati.")
271
 
272
  # Ricerca nell'indice
273
  distances, indices = index.search(query_embedding, top_k)
274
  logger.info(f"Ricerca FAISS completata. Risultati ottenuti: {len(indices[0])}")
275
 
276
- # Concatenazione delle descrizioni per la ricerca
277
- texts = [f"Classe: {cls['label']}. Descrizione: {cls['description']}" for cls in document['classes']]
278
- texts += [f"Proprietà: {prop['label']}. Descrizione: {prop['description']}" for prop in document['properties']]
279
- texts += [f"Entità: {entity['label']}. Descrizione: {entity['description']}. Proprietà: {entity['properties']}" for entity in document.get('entities', [])]
280
-
281
- # Recupera i testi rilevanti
282
- relevant_texts = [texts[idx] for idx in indices[0] if idx < len(texts)]
283
  retrieved_docs = "\n".join(relevant_texts)
284
- logger.info(f"Documenti rilevanti recuperati: {len(relevant_texts)}")
285
  return retrieved_docs
286
  except Exception as e:
287
- logger.error(f"Errore nel recupero dei documenti rilevanti: {e}")
288
  raise e
289
 
290
- def create_system_message(ont_text: str, retrieved_docs: str) -> str:
291
  """
292
  Prompt di sistema robusto, con regole su query in una riga e
293
  informazioni recuperate tramite RAG.
294
  """
295
- return f"""\
296
- ### Istruzioni ###
297
  Sei un assistente museale esperto in ontologie RDF. Utilizza le informazioni fornite per generare query SPARQL precise e pertinenti.
298
 
299
  ### Ontologia ###
300
- {ont_text}
301
- ### FINE Ontologia ###
302
-
303
- Ecco alcune informazioni rilevanti recuperate dalla base di conoscenza:
304
  {retrieved_docs}
 
305
 
306
  ### Regole Stringenti ###
307
  1) Se l'utente chiede informazioni su questa ontologia, genera SEMPRE una query SPARQL in UNA SOLA RIGA, con prefix:
308
- PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettomuseo#>
309
- 2) La query SPARQL deve essere precisa e cercare esattamente le entità specificate dall'utente. Ad esempio, se l'utente chiede "Chi ha creato l'opera 'Amore e Psiche'?", la query dovrebbe cercare l'opera esattamente con quel nome.
310
  3) Se la query produce 0 risultati o fallisce, ritenta con un secondo tentativo.
311
- 4) Se la domanda è generica (tipo 'Ciao, come stai?'), rispondi breve.
312
  5) Se trovi risultati, la risposta finale deve essere la query SPARQL (una sola riga).
313
  6) Se non trovi nulla, rispondi con 'Nessuna info.'
314
- 7) Non multiline. Esempio: PREFIX base: <...> SELECT ?x WHERE {{ ... }}.
315
  Esempio:
316
  Utente: Chi ha creato l'opera 'Amore e Psiche'?
317
- Risposta: PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettomuseo#> SELECT ?creatore WHERE {{ ?opera base:hasName "Amore e Psiche" . ?opera base:creatoDa ?creatore . }}
318
- FINE REGOLE
319
 
320
  ### Conversazione ###
321
- Utente: che ore sono?
322
- Assistente:
323
  """
324
 
325
  def create_explanation_prompt(results_str: str) -> str:
326
  """Prompt per generare una spiegazione museale dei risultati SPARQL."""
327
- return f"""\
328
- Ho ottenuto questi risultati SPARQL:
329
  {results_str}
330
  Ora fornisci una breve spiegazione museale (massimo ~10 righe), senza inventare oltre i risultati.
331
  """
@@ -377,22 +251,21 @@ async def call_hf_model(prompt: str, temperature: float = 0.5, max_tokens: int =
377
  # Variabile globale per le etichette delle entità
378
  entity_labels: List[str] = []
379
 
380
- def load_entity_labels(documents_file: str):
381
- """Carica le etichette delle entità dal file documents.json."""
382
  global entity_labels
383
  try:
384
- with open(documents_file, "r", encoding="utf-8") as f:
385
- document = json.load(f)
386
- # Estrai etichette vere e proprie
387
- entity_labels = []
388
- for entity in document.get('entities', []):
389
- label = entity.get('label', '')
390
- if label.startswith("http://") or label.startswith("https://"):
391
- # Estrai il fragment dell'URI
392
  label = label.split('#')[-1].replace('_', ' ')
393
  else:
394
- label = label.replace('_', ' ')
395
- entity_labels.append(label.lower())
 
396
  logger.info(f"Elenco delle etichette delle entità caricato: {entity_labels}")
397
  except Exception as e:
398
  logger.error(f"Errore nel caricamento delle etichette delle entità: {e}")
@@ -408,21 +281,13 @@ def is_ontology_related(query: str) -> bool:
408
  return True
409
  return False
410
 
411
- # Prepara i file necessari per RAG
412
- prepare_retrieval()
413
-
414
- # Carica il 'sunto' di classi, proprietà ed entità
415
- knowledge_text = extract_classes_and_properties(RDF_FILE)
416
-
417
- # Carica le etichette delle entità
418
- load_entity_labels(DOCUMENTS_FILE)
419
-
420
  app = FastAPI()
421
 
422
  class QueryRequest(BaseModel):
423
  message: str
424
- max_tokens: int = 150 # Ridotto per risposte concise
425
  temperature: float = 0.5
 
426
  @app.post("/generate-response/")
427
  async def generate_response(req: QueryRequest):
428
  user_input = req.message
@@ -445,13 +310,13 @@ async def generate_response(req: QueryRequest):
445
  }
446
 
447
  try:
448
- # Recupera documenti rilevanti usando RAG
449
- retrieved_docs = retrieve_relevant_documents(user_input, top_k=3)
450
  except Exception as e:
451
- logger.error(f"Errore nel recupero dei documenti rilevanti: {e}")
452
- return {"type": "ERROR", "response": f"Errore nel recupero dei documenti: {e}"}
453
 
454
- sys_msg = create_system_message(knowledge_text, retrieved_docs)
455
  prompt = f"{sys_msg}\nUtente: {user_input}\nAssistente:"
456
 
457
  # Primo tentativo
@@ -512,12 +377,13 @@ async def generate_response(req: QueryRequest):
512
  return {"type": "ERROR", "response": f"Errore durante il fallback della risposta: {e}"}
513
 
514
  if len(results) == 0:
515
- return {"type": "NATURAL", "sparql_query": sparql_query, "response": "Nessun risultato."}
516
 
517
  # Confeziona risultati
518
  row_list = []
519
  for row in results:
520
- row_dict = row.asdict()
 
521
  row_str = ", ".join([f"{k}: {v}" for k, v in row_dict.items()])
522
  row_list.append(row_str)
523
  results_str = "\n".join(row_list)
@@ -540,3 +406,13 @@ async def generate_response(req: QueryRequest):
540
  @app.get("/")
541
  def home():
542
  return {"message": "Assistente Museale con supporto SPARQL."}
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # Configura il logging
19
  logging.basicConfig(
20
+ level=logging.INFO, # Mantiene INFO per ambiente di produzione
21
  format="%(asctime)s - %(levelname)s - %(message)s",
22
  handlers=[logging.FileHandler("app.log"), logging.StreamHandler()]
23
  )
 
34
  RDF_FILE = os.path.join(BASE_DIR, "Ontologia.rdf")
35
  HF_MODEL = "Qwen/Qwen2.5-72B-Instruct" # Modello ottimizzato per seguire istruzioni
36
 
 
 
 
37
  # Percorsi dei file generati
38
  DOCUMENTS_FILE = os.path.join(BASE_DIR, "data", "documents.json")
39
  FAISS_INDEX_FILE = os.path.join(BASE_DIR, "data", "faiss.index")
40
 
41
+ # Carica il modello di embedding una sola volta per migliorare le prestazioni
42
+ try:
43
+ embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
44
+ logger.info("Modello SentenceTransformer caricato con successo.")
45
+ except Exception as e:
46
+ logger.error(f"Errore nel caricamento del modello SentenceTransformer: {e}")
47
+ raise e
48
+
49
  def create_data_directory():
50
  """Crea la directory 'data/' se non esiste."""
51
  os.makedirs(os.path.join(BASE_DIR, "data"), exist_ok=True)
52
  logger.info("Directory 'data/' creata o già esistente.")
53
 
54
+ def extract_lines(rdf_file: str, output_file: str):
55
  """
56
+ Estrae ogni riga dell'ontologia RDF e la salva in un file JSON.
57
+ Questo permette di indicizzare ogni riga singolarmente.
58
  """
59
+ logger.info(f"Inizio estrazione delle linee dall'ontologia da {rdf_file}.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  try:
61
+ with open(rdf_file, "r", encoding="utf-8") as f:
62
+ lines = f.readlines()
63
+ # Rimuovi spazi vuoti e newline
64
+ lines = [line.strip() for line in lines if line.strip()]
65
+ # Salva come lista di documenti
66
  with open(output_file, "w", encoding="utf-8") as f:
67
+ json.dump({"lines": lines}, f, ensure_ascii=False, indent=2)
68
+ logger.info(f"Linee estratte e salvate in {output_file}")
69
  except Exception as e:
70
+ logger.error(f"Errore nell'estrazione delle linee: {e}")
71
  raise e
72
 
73
+ def create_faiss_index(documents_file: str, index_file: str, embedding_model_instance: SentenceTransformer):
74
  """
75
+ Crea un indice FAISS a partire dalle linee estratte.
76
  """
77
  logger.info(f"Inizio creazione dell'indice FAISS da {documents_file}.")
78
  try:
79
  # Carica il documento
80
  with open(documents_file, "r", encoding="utf-8") as f:
81
  document = json.load(f)
82
+ lines = document['lines']
83
+ logger.info(f"{len(lines)} linee caricate da {documents_file}.")
84
 
85
  # Genera embedding
86
+ embeddings = embedding_model_instance.encode(lines, convert_to_numpy=True, show_progress_bar=True)
 
 
 
 
 
87
  logger.info("Embedding generati con SentenceTransformer.")
88
 
89
  # Crea l'indice FAISS
 
99
  logger.error(f"Errore nella creazione dell'indice FAISS: {e}")
100
  raise e
101
 
102
+ def prepare_retrieval(embedding_model_instance: SentenceTransformer):
103
  """Prepara i file necessari per l'approccio RAG."""
104
  logger.info("Inizio preparazione per il retrieval.")
105
  create_data_directory()
 
113
 
114
  # Verifica se documents.json esiste, altrimenti generarlo
115
  if not os.path.exists(DOCUMENTS_FILE):
116
+ logger.info(f"File {DOCUMENTS_FILE} non trovato. Estrazione delle linee dell'ontologia.")
117
  try:
118
+ extract_lines(RDF_FILE, DOCUMENTS_FILE)
119
  except Exception as e:
120
+ logger.error(f"Errore nell'estrazione delle linee: {e}")
121
  raise e
122
  else:
123
  logger.info(f"File {DOCUMENTS_FILE} trovato.")
 
126
  if not os.path.exists(FAISS_INDEX_FILE):
127
  logger.info(f"File {FAISS_INDEX_FILE} non trovato. Creazione dell'indice FAISS.")
128
  try:
129
+ create_faiss_index(DOCUMENTS_FILE, FAISS_INDEX_FILE, embedding_model_instance)
130
  except Exception as e:
131
  logger.error(f"Errore nella creazione dell'indice FAISS: {e}")
132
  raise e
133
  else:
134
  logger.info(f"File {FAISS_INDEX_FILE} trovato.")
135
 
136
+ def retrieve_relevant_lines(query: str, top_k: int = 5, embedding_model_instance: SentenceTransformer = None):
137
+ """Recupera le linee rilevanti usando FAISS."""
138
+ logger.info(f"Recupero delle linee rilevanti per la query: {query}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  try:
140
  # Carica il documento
141
  with open(DOCUMENTS_FILE, "r", encoding="utf-8") as f:
142
  document = json.load(f)
143
+ lines = document['lines']
144
+ logger.info(f"{len(lines)} linee caricate da {DOCUMENTS_FILE}.")
145
 
146
  # Carica l'indice FAISS
147
  index = faiss.read_index(FAISS_INDEX_FILE)
148
  logger.info(f"Indice FAISS caricato da {FAISS_INDEX_FILE}.")
149
 
150
  # Genera embedding della query
151
+ if embedding_model_instance is None:
152
+ embedding_model_instance = SentenceTransformer('all-MiniLM-L6-v2')
153
+ logger.info("Modello SentenceTransformer caricato per l'embedding della query.")
154
+
155
+ query_embedding = embedding_model_instance.encode([query], convert_to_numpy=True)
156
  logger.info("Embedding della query generati.")
157
 
158
  # Ricerca nell'indice
159
  distances, indices = index.search(query_embedding, top_k)
160
  logger.info(f"Ricerca FAISS completata. Risultati ottenuti: {len(indices[0])}")
161
 
162
+ # Recupera le linee rilevanti
163
+ relevant_texts = [lines[idx] for idx in indices[0] if idx < len(lines)]
 
 
 
 
 
164
  retrieved_docs = "\n".join(relevant_texts)
165
+ logger.info(f"Linee rilevanti recuperate: {len(relevant_texts)}")
166
  return retrieved_docs
167
  except Exception as e:
168
+ logger.error(f"Errore nel recupero delle linee rilevanti: {e}")
169
  raise e
170
 
171
+ def create_system_message(retrieved_docs: str) -> str:
172
  """
173
  Prompt di sistema robusto, con regole su query in una riga e
174
  informazioni recuperate tramite RAG.
175
  """
176
+ return f"""### Istruzioni ###
 
177
  Sei un assistente museale esperto in ontologie RDF. Utilizza le informazioni fornite per generare query SPARQL precise e pertinenti.
178
 
179
  ### Ontologia ###
 
 
 
 
180
  {retrieved_docs}
181
+ ### FINE Ontologia ###
182
 
183
  ### Regole Stringenti ###
184
  1) Se l'utente chiede informazioni su questa ontologia, genera SEMPRE una query SPARQL in UNA SOLA RIGA, con prefix:
185
+ PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
186
+ 2) La query SPARQL deve essere precisa e cercare esattamente le entità specificate dall'utente. Ad esempio, se l'utente chiede "Chi ha creato l'opera 'Amore e Psiche'?", la query dovrebbe cercere l'opera esattamente con quel nome.
187
  3) Se la query produce 0 risultati o fallisce, ritenta con un secondo tentativo.
188
+ 4) Se la domanda è generica (tipo 'Ciao, come stai?'), rispondi brevemente.
189
  5) Se trovi risultati, la risposta finale deve essere la query SPARQL (una sola riga).
190
  6) Se non trovi nulla, rispondi con 'Nessuna info.'
191
+ 7) Non multiline. Esempio: PREFIX base: <...> SELECT ?x WHERE { ... }.
192
  Esempio:
193
  Utente: Chi ha creato l'opera 'Amore e Psiche'?
194
+ Risposta: PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> SELECT ?creatore WHERE { ?opera base:hasName "Amore e Psiche" . ?opera base:creatoDa ?creatore . }
195
+ ### FINE REGOLE ###
196
 
197
  ### Conversazione ###
 
 
198
  """
199
 
200
  def create_explanation_prompt(results_str: str) -> str:
201
  """Prompt per generare una spiegazione museale dei risultati SPARQL."""
202
+ return f"""Ho ottenuto questi risultati SPARQL:
 
203
  {results_str}
204
  Ora fornisci una breve spiegazione museale (massimo ~10 righe), senza inventare oltre i risultati.
205
  """
 
251
  # Variabile globale per le etichette delle entità
252
  entity_labels: List[str] = []
253
 
254
+ def load_entity_labels(rdf_file: str):
255
+ """Carica le etichette delle entità dall'ontologia RDF."""
256
  global entity_labels
257
  try:
258
+ g = rdflib.Graph()
259
+ g.parse(rdf_file, format="xml")
260
+ entities = set()
261
+ for s in g.subjects(RDF.type, OWL.NamedIndividual):
262
+ label = g.value(s, RDFS.label, default=str(s))
263
+ if isinstance(label, URIRef):
 
 
264
  label = label.split('#')[-1].replace('_', ' ')
265
  else:
266
+ label = str(label)
267
+ entities.add(label.lower())
268
+ entity_labels = list(entities)
269
  logger.info(f"Elenco delle etichette delle entità caricato: {entity_labels}")
270
  except Exception as e:
271
  logger.error(f"Errore nel caricamento delle etichette delle entità: {e}")
 
281
  return True
282
  return False
283
 
 
 
 
 
 
 
 
 
 
284
  app = FastAPI()
285
 
286
  class QueryRequest(BaseModel):
287
  message: str
288
+ max_tokens: int = 512 # Aumentato per risposte più dettagliate
289
  temperature: float = 0.5
290
+
291
  @app.post("/generate-response/")
292
  async def generate_response(req: QueryRequest):
293
  user_input = req.message
 
310
  }
311
 
312
  try:
313
+ # Recupera linee rilevanti usando FAISS
314
+ retrieved_docs = retrieve_relevant_lines(user_input, top_k=5, embedding_model_instance=embedding_model)
315
  except Exception as e:
316
+ logger.error(f"Errore nel recupero delle linee rilevanti: {e}")
317
+ return {"type": "ERROR", "response": f"Errore nel recupero delle linee: {e}"}
318
 
319
+ sys_msg = create_system_message(retrieved_docs)
320
  prompt = f"{sys_msg}\nUtente: {user_input}\nAssistente:"
321
 
322
  # Primo tentativo
 
377
  return {"type": "ERROR", "response": f"Errore durante il fallback della risposta: {e}"}
378
 
379
  if len(results) == 0:
380
+ return {"type": "NATURAL", "sparql_query": sparql_query, "response": "Nessuna info."}
381
 
382
  # Confeziona risultati
383
  row_list = []
384
  for row in results:
385
+ # Converti il risultato della query in un dizionario
386
+ row_dict = dict(row)
387
  row_str = ", ".join([f"{k}: {v}" for k, v in row_dict.items()])
388
  row_list.append(row_str)
389
  results_str = "\n".join(row_list)
 
406
  @app.get("/")
407
  def home():
408
  return {"message": "Assistente Museale con supporto SPARQL."}
409
+
410
+ # Avvia la preparazione al caricamento delle linee e indicizzazione
411
+ try:
412
+ create_data_directory()
413
+ prepare_retrieval(embedding_model)
414
+ load_entity_labels(RDF_FILE)
415
+ logger.info("Applicazione avviata e pronta per ricevere richieste.")
416
+ except Exception as e:
417
+ logger.error(f"Errore durante la preparazione dell'applicazione: {e}")
418
+ raise e