Update app.py
Browse files
app.py
CHANGED
|
@@ -12,97 +12,107 @@ import re
|
|
| 12 |
# CONFIGURAZIONE LOGGING
|
| 13 |
# ---------------------------------------------------------------------------
|
| 14 |
logging.basicConfig(
|
| 15 |
-
level=logging.DEBUG, # DEBUG per un log più dettagliato
|
| 16 |
format="%(asctime)s - %(levelname)s - %(message)s",
|
| 17 |
handlers=[logging.FileHandler("app.log"), logging.StreamHandler()]
|
| 18 |
)
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
HF_API_KEY = os.getenv("HF_API_KEY")
|
| 24 |
-
HF_MODEL = "meta-llama/Llama-3.3-70B-Instruct" # modello per query SPARQL e risposte
|
| 25 |
-
ZERO_SHOT_MODEL = "facebook/bart-large-mnli" # modello per zero-shot classification
|
| 26 |
-
|
| 27 |
if not HF_API_KEY:
|
|
|
|
| 28 |
logger.error("HF_API_KEY non impostata.")
|
| 29 |
raise EnvironmentError("HF_API_KEY non impostata.")
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# ---------------------------------------------------------------------------
|
| 32 |
-
#
|
| 33 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
-
logger.info("Inizializzazione
|
| 36 |
-
|
| 37 |
token=HF_API_KEY,
|
| 38 |
-
model=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
)
|
| 40 |
-
logger.info("Client zero-shot creato con successo.")
|
| 41 |
except Exception as ex:
|
| 42 |
-
logger.error(f"Errore
|
| 43 |
-
raise
|
| 44 |
|
| 45 |
# ---------------------------------------------------------------------------
|
| 46 |
-
#
|
| 47 |
# ---------------------------------------------------------------------------
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
"""
|
| 54 |
-
try:
|
| 55 |
-
hypothesis_template = "Questa domanda è inerente all'arte o all'ontologia di un museo ({}), oppure no?"
|
| 56 |
-
|
| 57 |
-
# multi_label=False => elegge UNA sola label top
|
| 58 |
-
results = client_cls.zero_shot_classification(
|
| 59 |
-
text=text,
|
| 60 |
-
candidate_labels=CANDIDATE_LABELS,
|
| 61 |
-
multi_label=False,
|
| 62 |
-
hypothesis_template=hypothesis_template
|
| 63 |
-
)
|
| 64 |
-
# results è una lista di ZeroShotClassificationOutputElement
|
| 65 |
-
# es: [ZeroShotClassificationOutputElement(label='domanda_museo', score=0.85), ...]
|
| 66 |
-
top_label = results[0].label
|
| 67 |
-
top_score = results[0].score
|
| 68 |
-
logger.info(f"[ZeroShot] top_label={top_label}, score={top_score}")
|
| 69 |
-
return top_label
|
| 70 |
-
except Exception as e:
|
| 71 |
-
logger.error(f"Errore nella zero-shot classification: {e}")
|
| 72 |
-
return "fuori_contesto" # fallback in caso di errore
|
| 73 |
-
|
| 74 |
-
# Inizializziamo la nostra ontologia
|
| 75 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 76 |
-
RDF_FILE = os.path.join(BASE_DIR, "Ontologia_corretto.rdf")
|
| 77 |
-
|
| 78 |
ontology_graph = rdflib.Graph()
|
| 79 |
try:
|
| 80 |
-
# L'ontologia è in formato RDF/XML
|
| 81 |
logger.info(f"Caricamento ontologia da file: {RDF_FILE}")
|
|
|
|
| 82 |
ontology_graph.parse(RDF_FILE, format="xml")
|
| 83 |
logger.info("Ontologia RDF caricata correttamente (formato XML).")
|
| 84 |
except Exception as e:
|
| 85 |
logger.error(f"Errore nel caricamento dell'ontologia: {e}")
|
| 86 |
raise e
|
| 87 |
-
|
| 88 |
# ---------------------------------------------------------------------------
|
| 89 |
-
#
|
| 90 |
# ---------------------------------------------------------------------------
|
| 91 |
-
app = FastAPI()
|
| 92 |
-
|
| 93 |
-
# Modello di request
|
| 94 |
class AssistantRequest(BaseModel):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
message: str
|
| 96 |
max_tokens: int = 512
|
| 97 |
temperature: float = 0.5
|
| 98 |
|
| 99 |
# ---------------------------------------------------------------------------
|
| 100 |
-
# FUNZIONI DI SUPPORTO (Prompts, validazione SPARQL,
|
| 101 |
# ---------------------------------------------------------------------------
|
|
|
|
| 102 |
def create_system_prompt_for_sparql(ontology_turtle: str) -> str:
|
| 103 |
"""
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
"""
|
| 107 |
prompt = f"""SEI UN GENERATORE DI QUERY SPARQL PER L'ONTOLOGIA DI UN MUSEO.
|
| 108 |
DEVI GENERARE SOLO UNA QUERY SPARQL (IN UNA SOLA RIGA) SE LA DOMANDA RIGUARDA INFORMAZIONI NELL'ONTOLOGIA.
|
|
@@ -155,12 +165,13 @@ FINE ONTOLOGIA.
|
|
| 155 |
"""
|
| 156 |
logger.debug("[create_system_prompt_for_sparql] Prompt generato con ESEMPI e regole SPARQL.")
|
| 157 |
return prompt
|
| 158 |
-
|
| 159 |
|
| 160 |
def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
| 161 |
"""
|
| 162 |
Classifica la lingua della domanda e della risposta, quindi traduce la risposta
|
| 163 |
-
|
|
|
|
| 164 |
|
| 165 |
Parametri:
|
| 166 |
- question_text: Testo della domanda dell'utente.
|
|
@@ -169,17 +180,12 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
| 169 |
Restituisce:
|
| 170 |
- La risposta tradotta nella lingua della domanda o la risposta originale
|
| 171 |
se entrambe le lingue coincidono.
|
| 172 |
-
"""
|
| 173 |
-
# Costanti
|
| 174 |
-
LANG_DETECT_MODEL = "papluca/xlm-roberta-base-language-detection" # Modello per rilevamento lingua
|
| 175 |
-
TRANSLATOR_MODEL_PREFIX = "Helsinki-NLP/opus-mt" # Prefisso dei modelli di traduzione
|
| 176 |
-
|
| 177 |
-
# Crea il client per il rilevamento delle lingue
|
| 178 |
-
lang_detect_client = InferenceClient(
|
| 179 |
-
token=HF_API_KEY,
|
| 180 |
-
model=LANG_DETECT_MODEL
|
| 181 |
-
)
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
# Rileva la lingua della domanda
|
| 184 |
try:
|
| 185 |
question_lang_result = lang_detect_client.text_classification(text=question_text)
|
|
@@ -187,7 +193,7 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
| 187 |
logger.info(f"[LangDetect] Lingua della domanda: {question_lang}")
|
| 188 |
except Exception as e:
|
| 189 |
logger.error(f"Errore nel rilevamento della lingua della domanda: {e}")
|
| 190 |
-
question_lang = "en" #
|
| 191 |
|
| 192 |
# Rileva la lingua della risposta
|
| 193 |
try:
|
|
@@ -196,95 +202,104 @@ def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
|
| 196 |
logger.info(f"[LangDetect] Lingua della risposta: {answer_lang}")
|
| 197 |
except Exception as e:
|
| 198 |
logger.error(f"Errore nel rilevamento della lingua della risposta: {e}")
|
| 199 |
-
answer_lang = "
|
| 200 |
|
| 201 |
-
# Se
|
| 202 |
if question_lang == answer_lang:
|
| 203 |
-
logger.info("[Translate]
|
| 204 |
return model_answer_text
|
| 205 |
|
| 206 |
-
#
|
|
|
|
| 207 |
translator_model = f"{TRANSLATOR_MODEL_PREFIX}-{answer_lang}-{question_lang}"
|
| 208 |
-
|
| 209 |
-
# Crea il client per la traduzione
|
| 210 |
translator_client = InferenceClient(
|
| 211 |
token=HF_API_KEY,
|
| 212 |
model=translator_model
|
| 213 |
)
|
| 214 |
|
| 215 |
-
#
|
| 216 |
try:
|
| 217 |
translation_result = translator_client.translation(text=model_answer_text)
|
| 218 |
translated_answer = translation_result["translation_text"]
|
| 219 |
logger.info("[Translate] Risposta tradotta con successo.")
|
| 220 |
except Exception as e:
|
| 221 |
-
logger.error(f"Errore nella traduzione {answer_lang}->{question_lang}: {e}")
|
| 222 |
-
|
|
|
|
| 223 |
|
| 224 |
return translated_answer
|
| 225 |
|
| 226 |
|
| 227 |
def create_system_prompt_for_guide() -> str:
|
| 228 |
"""
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
"""
|
| 234 |
prompt = (
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
)
|
| 240 |
logger.debug("[create_system_prompt_for_guide] Prompt per la risposta guida museale generato.")
|
| 241 |
return prompt
|
| 242 |
|
| 243 |
|
| 244 |
def correct_sparql_syntax_advanced(query: str) -> str:
|
| 245 |
"""
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
"""
|
| 255 |
original_query = query
|
| 256 |
logger.debug(f"[correct_sparql_syntax_advanced] Query originaria:\n{original_query}")
|
| 257 |
|
| 258 |
-
# 1) Rimuoviamo newline e
|
| 259 |
query = query.replace('\n', ' ').replace('\r', ' ')
|
| 260 |
|
| 261 |
-
# 2) Se manca il PREFIX, lo aggiungiamo in testa
|
| 262 |
if 'PREFIX progettoMuseo:' not in query:
|
| 263 |
logger.debug("[correct_sparql_syntax_advanced] Aggiungo PREFIX progettoMuseo.")
|
| 264 |
-
query = (
|
| 265 |
-
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
# 3) Spazio dopo SELECT se manca
|
| 268 |
query = re.sub(r'(SELECT)(\?|\*)', r'\1 \2', query, flags=re.IGNORECASE)
|
| 269 |
|
| 270 |
-
# 4) Spazio dopo WHERE se manca
|
| 271 |
query = re.sub(r'(WHERE)\{', r'\1 {', query, flags=re.IGNORECASE)
|
| 272 |
|
| 273 |
-
# 5) Correggiamo
|
| 274 |
-
# "progettoMuseo:autoreOpera?autore" => "progettoMuseo:autoreOpera ?autore"
|
| 275 |
query = re.sub(r'(progettoMuseo:\w+)\?(\w+)', r'\1 ?\2', query)
|
| 276 |
|
| 277 |
# 6) Rimuoviamo spazi multipli
|
| 278 |
query = re.sub(r'\s+', ' ', query).strip()
|
| 279 |
|
| 280 |
-
# 7) Aggiungiamo '.'
|
| 281 |
query = re.sub(r'(\?\w+)\s*\}', r'\1 . }', query)
|
| 282 |
|
| 283 |
-
# 8) Se manca la clausola WHERE,
|
| 284 |
if 'WHERE' not in query.upper():
|
| 285 |
query = re.sub(r'(SELECT\s+[^\{]+)\{', r'\1 WHERE {', query, flags=re.IGNORECASE)
|
| 286 |
|
| 287 |
-
# 9) Pulizia
|
| 288 |
query = re.sub(r'\s+', ' ', query).strip()
|
| 289 |
|
| 290 |
logger.debug(f"[correct_sparql_syntax_advanced] Query dopo correzioni:\n{query}")
|
|
@@ -292,7 +307,10 @@ def correct_sparql_syntax_advanced(query: str) -> str:
|
|
| 292 |
|
| 293 |
|
| 294 |
def is_sparql_query_valid(query: str) -> bool:
|
| 295 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 296 |
logger.debug(f"[is_sparql_query_valid] Validazione SPARQL: {query}")
|
| 297 |
try:
|
| 298 |
parseQuery(query)
|
|
@@ -303,50 +321,63 @@ def is_sparql_query_valid(query: str) -> bool:
|
|
| 303 |
return False
|
| 304 |
|
| 305 |
# ---------------------------------------------------------------------------
|
| 306 |
-
# ENDPOINT UNICO
|
| 307 |
# ---------------------------------------------------------------------------
|
| 308 |
@app.post("/assistant")
|
| 309 |
def assistant_endpoint(req: AssistantRequest):
|
| 310 |
"""
|
| 311 |
-
Endpoint
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
"""
|
| 316 |
logger.info("Ricevuta chiamata POST su /assistant")
|
|
|
|
|
|
|
| 317 |
user_message = req.message
|
| 318 |
max_tokens = req.max_tokens
|
| 319 |
temperature = req.temperature
|
| 320 |
-
|
| 321 |
-
logger.info(label)
|
| 322 |
logger.debug(f"Parametri utente: message='{user_message}', max_tokens={max_tokens}, temperature={temperature}")
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
| 324 |
try:
|
| 325 |
-
|
| 326 |
ontology_turtle = ontology_graph.serialize(format="xml")
|
| 327 |
logger.debug("Ontologia serializzata con successo (XML).")
|
| 328 |
except Exception as e:
|
| 329 |
-
logger.warning(f"Impossibile serializzare l'ontologia in
|
| 330 |
ontology_turtle = ""
|
|
|
|
|
|
|
| 331 |
system_prompt_sparql = create_system_prompt_for_sparql(ontology_turtle)
|
| 332 |
-
# Inizializziamo client Hugging Face
|
| 333 |
-
try:
|
| 334 |
-
logger.debug(f"Inizializzazione InferenceClient con modello='{HF_MODEL}'.")
|
| 335 |
-
hf_client = InferenceClient(model=HF_MODEL, token=HF_API_KEY)
|
| 336 |
-
except Exception as ex:
|
| 337 |
-
logger.error(f"Errore inizializzazione HF client: {ex}")
|
| 338 |
-
raise HTTPException(status_code=500, detail="Impossibile inizializzare il modello Hugging Face.")
|
| 339 |
|
| 340 |
-
#
|
| 341 |
try:
|
| 342 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la query SPARQL...")
|
| 343 |
-
gen_sparql_output =
|
| 344 |
messages=[
|
| 345 |
{"role": "system", "content": system_prompt_sparql},
|
| 346 |
{"role": "user", "content": user_message}
|
| 347 |
],
|
| 348 |
-
max_tokens=512,
|
| 349 |
-
temperature=0
|
| 350 |
)
|
| 351 |
possible_query = gen_sparql_output["choices"][0]["message"]["content"].strip()
|
| 352 |
logger.info(f"[assistant_endpoint] Query generata dal modello: {possible_query}")
|
|
@@ -355,22 +386,24 @@ def assistant_endpoint(req: AssistantRequest):
|
|
| 355 |
# Se fallisce la generazione, consideriamo la query come "NO_SPARQL"
|
| 356 |
possible_query = "NO_SPARQL"
|
| 357 |
|
| 358 |
-
#
|
| 359 |
if possible_query.upper().startswith("NO_SPARQL"):
|
| 360 |
generated_query = None
|
| 361 |
-
logger.debug("[assistant_endpoint] Modello indica 'NO_SPARQL', nessuna query generata.")
|
| 362 |
else:
|
| 363 |
-
#
|
| 364 |
advanced_corrected = correct_sparql_syntax_advanced(possible_query)
|
| 365 |
-
#
|
| 366 |
if is_sparql_query_valid(advanced_corrected):
|
| 367 |
generated_query = advanced_corrected
|
| 368 |
logger.debug(f"[assistant_endpoint] Query SPARQL valida dopo correzione avanzata: {generated_query}")
|
| 369 |
else:
|
| 370 |
-
logger.debug("[assistant_endpoint] Query SPARQL non valida
|
| 371 |
generated_query = None
|
| 372 |
|
| 373 |
-
#
|
|
|
|
|
|
|
| 374 |
results = []
|
| 375 |
if generated_query:
|
| 376 |
logger.debug(f"[assistant_endpoint] Esecuzione della query SPARQL:\n{generated_query}")
|
|
@@ -381,17 +414,17 @@ def assistant_endpoint(req: AssistantRequest):
|
|
| 381 |
except Exception as ex:
|
| 382 |
logger.error(f"[assistant_endpoint] Errore nell'esecuzione della query: {ex}")
|
| 383 |
results = []
|
| 384 |
-
|
| 385 |
-
#
|
|
|
|
|
|
|
| 386 |
system_prompt_guide = create_system_prompt_for_guide()
|
|
|
|
| 387 |
if generated_query and results:
|
| 388 |
-
#
|
| 389 |
# Convertiamo i risultati in una stringa più leggibile
|
| 390 |
results_str = "\n".join(
|
| 391 |
-
f"{idx+1}) " + ", ".join(
|
| 392 |
-
f"{var}={row[var]}"
|
| 393 |
-
for var in row.labels
|
| 394 |
-
)
|
| 395 |
for idx, row in enumerate(results)
|
| 396 |
)
|
| 397 |
second_prompt = (
|
|
@@ -402,17 +435,19 @@ def assistant_endpoint(req: AssistantRequest):
|
|
| 402 |
"Rispondi in modo breve (max ~50 parole)."
|
| 403 |
)
|
| 404 |
logger.debug("[assistant_endpoint] Prompt di risposta con risultati SPARQL.")
|
|
|
|
| 405 |
elif generated_query and not results:
|
| 406 |
-
#
|
| 407 |
second_prompt = (
|
| 408 |
f"{system_prompt_guide}\n\n"
|
| 409 |
f"Domanda utente: {user_message}\n"
|
| 410 |
f"Query generata: {generated_query}\n"
|
| 411 |
"Nessun risultato dalla query. Prova comunque a rispondere con le tue conoscenze."
|
| 412 |
)
|
| 413 |
-
logger.debug("[assistant_endpoint] Prompt di risposta: query valida ma
|
|
|
|
| 414 |
else:
|
| 415 |
-
#
|
| 416 |
second_prompt = (
|
| 417 |
f"{system_prompt_guide}\n\n"
|
| 418 |
f"Domanda utente: {user_message}\n"
|
|
@@ -420,42 +455,56 @@ def assistant_endpoint(req: AssistantRequest):
|
|
| 420 |
)
|
| 421 |
logger.debug("[assistant_endpoint] Prompt di risposta: nessuna query generata.")
|
| 422 |
|
| 423 |
-
#
|
| 424 |
try:
|
| 425 |
-
logger.debug("[assistant_endpoint] Chiamata HF per la risposta
|
| 426 |
-
final_output =
|
| 427 |
messages=[
|
| 428 |
{"role": "system", "content": second_prompt},
|
| 429 |
{"role": "user", "content": "Fornisci la risposta finale."}
|
| 430 |
],
|
| 431 |
-
max_tokens=
|
| 432 |
-
temperature=
|
| 433 |
)
|
| 434 |
final_answer = final_output["choices"][0]["message"]["content"].strip()
|
| 435 |
logger.info(f"[assistant_endpoint] Risposta finale generata: {final_answer}")
|
| 436 |
except Exception as ex:
|
| 437 |
logger.error(f"Errore nella generazione della risposta finale: {ex}")
|
| 438 |
raise HTTPException(status_code=500, detail="Errore nella generazione della risposta in linguaggio naturale.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 439 |
final_ans = classify_and_translate(user_message, final_answer)
|
| 440 |
-
|
| 441 |
-
|
|
|
|
|
|
|
|
|
|
| 442 |
return {
|
| 443 |
"query": generated_query,
|
| 444 |
"response": final_ans
|
| 445 |
}
|
| 446 |
|
| 447 |
# ---------------------------------------------------------------------------
|
| 448 |
-
# ENDPOINT DI TEST
|
| 449 |
# ---------------------------------------------------------------------------
|
| 450 |
@app.get("/")
|
| 451 |
def home():
|
|
|
|
|
|
|
|
|
|
| 452 |
logger.debug("Chiamata GET su '/' - home.")
|
| 453 |
return {
|
| 454 |
-
"message": "Endpoint
|
| 455 |
}
|
| 456 |
|
| 457 |
# ---------------------------------------------------------------------------
|
| 458 |
# MAIN
|
| 459 |
# ---------------------------------------------------------------------------
|
| 460 |
if __name__ == "__main__":
|
| 461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
# CONFIGURAZIONE LOGGING
|
| 13 |
# ---------------------------------------------------------------------------
|
| 14 |
logging.basicConfig(
|
| 15 |
+
level=logging.DEBUG, # Utilizziamo il livello DEBUG per un log più dettagliato
|
| 16 |
format="%(asctime)s - %(levelname)s - %(message)s",
|
| 17 |
handlers=[logging.FileHandler("app.log"), logging.StreamHandler()]
|
| 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.")
|
| 29 |
raise EnvironmentError("HF_API_KEY non impostata.")
|
| 30 |
|
| 31 |
+
# Nome del modello Hugging Face per generare query SPARQL e risposte finali
|
| 32 |
+
HF_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
|
| 33 |
+
|
| 34 |
+
# Nome del modello Hugging Face per rilevamento lingua
|
| 35 |
+
LANG_DETECT_MODEL = "papluca/xlm-roberta-base-language-detection"
|
| 36 |
+
|
| 37 |
+
# Prefisso per i modelli di traduzione su Hugging Face
|
| 38 |
+
TRANSLATOR_MODEL_PREFIX = "Helsinki-NLP/opus-mt"
|
| 39 |
+
|
| 40 |
# ---------------------------------------------------------------------------
|
| 41 |
+
# INIZIALIZZAZIONE CLIENT HUGGING FACE (una volta sola)
|
| 42 |
# ---------------------------------------------------------------------------
|
| 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 |
+
"""
|
| 50 |
try:
|
| 51 |
+
logger.info("[Startup] Inizializzazione client HF per generazione (modello di LLM).")
|
| 52 |
+
hf_generation_client = InferenceClient(
|
| 53 |
token=HF_API_KEY,
|
| 54 |
+
model=HF_MODEL
|
| 55 |
+
)
|
| 56 |
+
logger.info("[Startup] Inizializzazione client HF per rilevamento lingua.")
|
| 57 |
+
lang_detect_client = InferenceClient(
|
| 58 |
+
token=HF_API_KEY,
|
| 59 |
+
model=LANG_DETECT_MODEL
|
| 60 |
)
|
|
|
|
| 61 |
except Exception as ex:
|
| 62 |
+
logger.error(f"Errore inizializzazione dei client Hugging Face: {ex}")
|
| 63 |
+
raise HTTPException(status_code=500, detail="Impossibile inizializzare i modelli Hugging Face.")
|
| 64 |
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
+
# CARICAMENTO ONTOLOGIA
|
| 67 |
# ---------------------------------------------------------------------------
|
| 68 |
+
"""
|
| 69 |
+
Carichiamo il file RDF/XML contenente l'ontologia del museo. Questo file è
|
| 70 |
+
fondamentale per l'esecuzione di query SPARQL, in quanto definisce le classi,
|
| 71 |
+
le proprietà e le istanze presenti nell'ontologia del museo.
|
| 72 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 74 |
+
RDF_FILE = os.path.join(BASE_DIR, "Ontologia_corretto-2.rdf")
|
| 75 |
+
|
| 76 |
ontology_graph = rdflib.Graph()
|
| 77 |
try:
|
|
|
|
| 78 |
logger.info(f"Caricamento ontologia da file: {RDF_FILE}")
|
| 79 |
+
# Indichiamo che l'ontologia è in formato RDF/XML
|
| 80 |
ontology_graph.parse(RDF_FILE, format="xml")
|
| 81 |
logger.info("Ontologia RDF caricata correttamente (formato XML).")
|
| 82 |
except Exception as e:
|
| 83 |
logger.error(f"Errore nel caricamento dell'ontologia: {e}")
|
| 84 |
raise e
|
|
|
|
| 85 |
# ---------------------------------------------------------------------------
|
| 86 |
+
# Pydantic Model per la richiesta
|
| 87 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
| 88 |
class AssistantRequest(BaseModel):
|
| 89 |
+
"""
|
| 90 |
+
Questo modello Pydantic definisce lo schema della richiesta che
|
| 91 |
+
riceverà l'endpoint /assistant. Contiene:
|
| 92 |
+
- message: la domanda del visitatore
|
| 93 |
+
- max_tokens: max di token per le risposte (di default 512)
|
| 94 |
+
- temperature: temperatura di generazione (di default 0.5)
|
| 95 |
+
"""
|
| 96 |
message: str
|
| 97 |
max_tokens: int = 512
|
| 98 |
temperature: float = 0.5
|
| 99 |
|
| 100 |
# ---------------------------------------------------------------------------
|
| 101 |
+
# FUNZIONI DI SUPPORTO (Prompts, validazione SPARQL, correzioni, ecc.)
|
| 102 |
# ---------------------------------------------------------------------------
|
| 103 |
+
|
| 104 |
def create_system_prompt_for_sparql(ontology_turtle: str) -> str:
|
| 105 |
"""
|
| 106 |
+
Genera il testo di prompt che istruisce il modello su come costruire
|
| 107 |
+
SOLO UNA query SPARQL, in un'unica riga, o in alternativa 'NO_SPARQL'
|
| 108 |
+
se la domanda non è pertinente all'ontologia. Il prompt include regole
|
| 109 |
+
di formattazione e alcuni esempi di domanda-risposta SPARQL.
|
| 110 |
+
|
| 111 |
+
Parametri:
|
| 112 |
+
- ontology_turtle: una stringa con l'ontologia in formato Turtle (o simile).
|
| 113 |
+
|
| 114 |
+
Ritorna:
|
| 115 |
+
- Il testo da usare come "system prompt" per il modello generativo.
|
| 116 |
"""
|
| 117 |
prompt = f"""SEI UN GENERATORE DI QUERY SPARQL PER L'ONTOLOGIA DI UN MUSEO.
|
| 118 |
DEVI GENERARE SOLO UNA QUERY SPARQL (IN UNA SOLA RIGA) SE LA DOMANDA RIGUARDA INFORMAZIONI NELL'ONTOLOGIA.
|
|
|
|
| 165 |
"""
|
| 166 |
logger.debug("[create_system_prompt_for_sparql] Prompt generato con ESEMPI e regole SPARQL.")
|
| 167 |
return prompt
|
| 168 |
+
|
| 169 |
|
| 170 |
def classify_and_translate(question_text: str, model_answer_text: str) -> str:
|
| 171 |
"""
|
| 172 |
Classifica la lingua della domanda e della risposta, quindi traduce la risposta
|
| 173 |
+
se la lingua è diversa da quella della domanda. L'idea è di restituire una
|
| 174 |
+
risposta nella stessa lingua dell'utente.
|
| 175 |
|
| 176 |
Parametri:
|
| 177 |
- question_text: Testo della domanda dell'utente.
|
|
|
|
| 180 |
Restituisce:
|
| 181 |
- La risposta tradotta nella lingua della domanda o la risposta originale
|
| 182 |
se entrambe le lingue coincidono.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
NB: Qui l'oggetto 'lang_detect_client' (per rilevamento lingua) è già
|
| 185 |
+
stato inizializzato all'avvio dell'app. Mentre il 'translator_client'
|
| 186 |
+
viene creato 'al volo' poiché la direzione di traduzione dipende
|
| 187 |
+
dalle due lingue effettive.
|
| 188 |
+
"""
|
| 189 |
# Rileva la lingua della domanda
|
| 190 |
try:
|
| 191 |
question_lang_result = lang_detect_client.text_classification(text=question_text)
|
|
|
|
| 193 |
logger.info(f"[LangDetect] Lingua della domanda: {question_lang}")
|
| 194 |
except Exception as e:
|
| 195 |
logger.error(f"Errore nel rilevamento della lingua della domanda: {e}")
|
| 196 |
+
question_lang = "en" # Fallback se non riusciamo a rilevare la lingua
|
| 197 |
|
| 198 |
# Rileva la lingua della risposta
|
| 199 |
try:
|
|
|
|
| 202 |
logger.info(f"[LangDetect] Lingua della risposta: {answer_lang}")
|
| 203 |
except Exception as e:
|
| 204 |
logger.error(f"Errore nel rilevamento della lingua della risposta: {e}")
|
| 205 |
+
answer_lang = "it" # Fallback se non riusciamo a rilevare la lingua
|
| 206 |
|
| 207 |
+
# Se domanda e risposta sono nella stessa lingua, non traduciamo
|
| 208 |
if question_lang == answer_lang:
|
| 209 |
+
logger.info("[Translate] Nessuna traduzione necessaria: stessa lingua.")
|
| 210 |
return model_answer_text
|
| 211 |
|
| 212 |
+
# Altrimenti, costruiamo "al volo" il modello di traduzione appropriato
|
| 213 |
+
# (es: "Helsinki-NLP/opus-mt-en-it", "Helsinki-NLP/opus-mt-fr-en", ecc.)
|
| 214 |
translator_model = f"{TRANSLATOR_MODEL_PREFIX}-{answer_lang}-{question_lang}"
|
|
|
|
|
|
|
| 215 |
translator_client = InferenceClient(
|
| 216 |
token=HF_API_KEY,
|
| 217 |
model=translator_model
|
| 218 |
)
|
| 219 |
|
| 220 |
+
# Traduzione della risposta
|
| 221 |
try:
|
| 222 |
translation_result = translator_client.translation(text=model_answer_text)
|
| 223 |
translated_answer = translation_result["translation_text"]
|
| 224 |
logger.info("[Translate] Risposta tradotta con successo.")
|
| 225 |
except Exception as e:
|
| 226 |
+
logger.error(f"Errore nella traduzione {answer_lang} -> {question_lang}: {e}")
|
| 227 |
+
# Se fallisce, restituiamo la risposta originale come fallback
|
| 228 |
+
translated_answer = model_answer_text
|
| 229 |
|
| 230 |
return translated_answer
|
| 231 |
|
| 232 |
|
| 233 |
def create_system_prompt_for_guide() -> str:
|
| 234 |
"""
|
| 235 |
+
Genera un testo di prompt che istruisce il modello a rispondere
|
| 236 |
+
come "guida museale virtuale", in modo breve (~50 parole), riassumendo
|
| 237 |
+
i risultati SPARQL (se presenti) o fornendo comunque una risposta
|
| 238 |
+
in base alle conoscenze pregresse.
|
| 239 |
"""
|
| 240 |
prompt = (
|
| 241 |
+
"SEI UNA GUIDA MUSEALE VIRTUALE. "
|
| 242 |
+
"RISPONDI IN MODO BREVE (~50 PAROLE), SENZA SALUTI O INTRODUZIONI PROLISSE. "
|
| 243 |
+
"SE HAI RISULTATI SPARQL, USALI. "
|
| 244 |
+
"SE NON HAI RISULTATI O NON HAI UNA QUERY, RISPONDI COMUNQUE CERCANDO DI RIARRANGIARE LE TUE CONOSCENZE."
|
| 245 |
+
)
|
| 246 |
logger.debug("[create_system_prompt_for_guide] Prompt per la risposta guida museale generato.")
|
| 247 |
return prompt
|
| 248 |
|
| 249 |
|
| 250 |
def correct_sparql_syntax_advanced(query: str) -> str:
|
| 251 |
"""
|
| 252 |
+
Applica correzioni sintattiche (euristiche) su una query SPARQL eventualmente
|
| 253 |
+
mal formattata, generata dal modello.
|
| 254 |
+
Passi:
|
| 255 |
+
1. Rimuove newline.
|
| 256 |
+
2. Verifica l'esistenza di 'PREFIX progettoMuseo:' e lo aggiunge se mancante.
|
| 257 |
+
3. Inserisce spazi dopo SELECT, WHERE (se mancanti).
|
| 258 |
+
4. Se c'è 'progettoMuseo:autoreOpera?autore' lo trasforma in 'progettoMuseo:autoreOpera ?autore'.
|
| 259 |
+
5. Rimuove spazi multipli.
|
| 260 |
+
6. Aggiunge '.' prima di '}' se manca.
|
| 261 |
+
7. Aggiunge la clausola WHERE se non presente.
|
| 262 |
+
|
| 263 |
+
Parametri:
|
| 264 |
+
- query: stringa con la query SPARQL potenzialmente mal formattata.
|
| 265 |
+
|
| 266 |
+
Ritorna:
|
| 267 |
+
- La query SPARQL corretta se possibile, in singola riga.
|
| 268 |
"""
|
| 269 |
original_query = query
|
| 270 |
logger.debug(f"[correct_sparql_syntax_advanced] Query originaria:\n{original_query}")
|
| 271 |
|
| 272 |
+
# 1) Rimuoviamo newline e normalizziamo a una singola riga
|
| 273 |
query = query.replace('\n', ' ').replace('\r', ' ')
|
| 274 |
|
| 275 |
+
# 2) Se manca il PREFIX museo, lo aggiungiamo in testa
|
| 276 |
if 'PREFIX progettoMuseo:' not in query:
|
| 277 |
logger.debug("[correct_sparql_syntax_advanced] Aggiungo PREFIX progettoMuseo.")
|
| 278 |
+
query = (
|
| 279 |
+
"PREFIX progettoMuseo: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> "
|
| 280 |
+
+ query
|
| 281 |
+
)
|
| 282 |
|
| 283 |
+
# 3) Spazio dopo SELECT se manca (SELECT?autore => SELECT ?autore)
|
| 284 |
query = re.sub(r'(SELECT)(\?|\*)', r'\1 \2', query, flags=re.IGNORECASE)
|
| 285 |
|
| 286 |
+
# 4) Spazio dopo WHERE se manca (WHERE{ => WHERE {)
|
| 287 |
query = re.sub(r'(WHERE)\{', r'\1 {', query, flags=re.IGNORECASE)
|
| 288 |
|
| 289 |
+
# 5) Correggiamo le incollature: progettoMuseo:autoreOpera?autore => progettoMuseo:autoreOpera ?autore
|
|
|
|
| 290 |
query = re.sub(r'(progettoMuseo:\w+)\?(\w+)', r'\1 ?\2', query)
|
| 291 |
|
| 292 |
# 6) Rimuoviamo spazi multipli
|
| 293 |
query = re.sub(r'\s+', ' ', query).strip()
|
| 294 |
|
| 295 |
+
# 7) Aggiungiamo '.' prima di '}' se manca
|
| 296 |
query = re.sub(r'(\?\w+)\s*\}', r'\1 . }', query)
|
| 297 |
|
| 298 |
+
# 8) Se manca la clausola WHERE, la aggiungiamo
|
| 299 |
if 'WHERE' not in query.upper():
|
| 300 |
query = re.sub(r'(SELECT\s+[^\{]+)\{', r'\1 WHERE {', query, flags=re.IGNORECASE)
|
| 301 |
|
| 302 |
+
# 9) Pulizia spazi superflui
|
| 303 |
query = re.sub(r'\s+', ' ', query).strip()
|
| 304 |
|
| 305 |
logger.debug(f"[correct_sparql_syntax_advanced] Query dopo correzioni:\n{query}")
|
|
|
|
| 307 |
|
| 308 |
|
| 309 |
def is_sparql_query_valid(query: str) -> bool:
|
| 310 |
+
"""
|
| 311 |
+
Verifica la validità sintattica di una query SPARQL usando rdflib.
|
| 312 |
+
Ritorna True se la query è sintatticamente corretta, False altrimenti.
|
| 313 |
+
"""
|
| 314 |
logger.debug(f"[is_sparql_query_valid] Validazione SPARQL: {query}")
|
| 315 |
try:
|
| 316 |
parseQuery(query)
|
|
|
|
| 321 |
return False
|
| 322 |
|
| 323 |
# ---------------------------------------------------------------------------
|
| 324 |
+
# ENDPOINT UNICO: /assistant
|
| 325 |
# ---------------------------------------------------------------------------
|
| 326 |
@app.post("/assistant")
|
| 327 |
def assistant_endpoint(req: AssistantRequest):
|
| 328 |
"""
|
| 329 |
+
Endpoint che gestisce l'intera pipeline:
|
| 330 |
+
1) Genera una query SPARQL dal messaggio dell'utente (prompt dedicato).
|
| 331 |
+
2) Verifica la validità della query e, se valida, la esegue sull'ontologia RDF.
|
| 332 |
+
3) Crea un "prompt da guida museale" e genera una risposta finale breve (max ~50 parole).
|
| 333 |
+
4) Eventualmente, traduce la risposta nella lingua dell'utente.
|
| 334 |
+
|
| 335 |
+
Parametri:
|
| 336 |
+
- req (AssistantRequest): un oggetto contenente:
|
| 337 |
+
- message (str): la domanda dell'utente
|
| 338 |
+
- max_tokens (int, opzionale): numero massimo di token per la generazione
|
| 339 |
+
- temperature (float, opzionale): temperatura per la generazione
|
| 340 |
+
|
| 341 |
+
Ritorna:
|
| 342 |
+
- Un JSON con:
|
| 343 |
+
{
|
| 344 |
+
"query": <la query SPARQL generata o None>,
|
| 345 |
+
"response": <la risposta finale in linguaggio naturale>
|
| 346 |
+
}
|
| 347 |
"""
|
| 348 |
logger.info("Ricevuta chiamata POST su /assistant")
|
| 349 |
+
|
| 350 |
+
# Estraggo i campi dal body della richiesta
|
| 351 |
user_message = req.message
|
| 352 |
max_tokens = req.max_tokens
|
| 353 |
temperature = req.temperature
|
| 354 |
+
|
|
|
|
| 355 |
logger.debug(f"Parametri utente: message='{user_message}', max_tokens={max_tokens}, temperature={temperature}")
|
| 356 |
+
|
| 357 |
+
# -----------------------------------------------------------------------
|
| 358 |
+
# STEP 1: Generazione della query SPARQL
|
| 359 |
+
# -----------------------------------------------------------------------
|
| 360 |
try:
|
| 361 |
+
# Serializziamo l'ontologia in XML per fornirla al prompt (anche se si chiama 'turtle' va bene così).
|
| 362 |
ontology_turtle = ontology_graph.serialize(format="xml")
|
| 363 |
logger.debug("Ontologia serializzata con successo (XML).")
|
| 364 |
except Exception as e:
|
| 365 |
+
logger.warning(f"Impossibile serializzare l'ontologia in formato XML: {e}")
|
| 366 |
ontology_turtle = ""
|
| 367 |
+
|
| 368 |
+
# Creiamo il prompt di sistema per la generazione SPARQL
|
| 369 |
system_prompt_sparql = create_system_prompt_for_sparql(ontology_turtle)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
+
# Chiamata al modello per generare la query SPARQL
|
| 372 |
try:
|
| 373 |
logger.debug("[assistant_endpoint] Chiamata HF per generare la query SPARQL...")
|
| 374 |
+
gen_sparql_output = hf_generation_client.chat.completions.create(
|
| 375 |
messages=[
|
| 376 |
{"role": "system", "content": system_prompt_sparql},
|
| 377 |
{"role": "user", "content": user_message}
|
| 378 |
],
|
| 379 |
+
max_tokens=512, # max_tokens per la generazione della query
|
| 380 |
+
temperature=0.2 # temperatura bassa per avere risposte più "deterministiche"
|
| 381 |
)
|
| 382 |
possible_query = gen_sparql_output["choices"][0]["message"]["content"].strip()
|
| 383 |
logger.info(f"[assistant_endpoint] Query generata dal modello: {possible_query}")
|
|
|
|
| 386 |
# Se fallisce la generazione, consideriamo la query come "NO_SPARQL"
|
| 387 |
possible_query = "NO_SPARQL"
|
| 388 |
|
| 389 |
+
# Verifichiamo se la query è "NO_SPARQL"
|
| 390 |
if possible_query.upper().startswith("NO_SPARQL"):
|
| 391 |
generated_query = None
|
| 392 |
+
logger.debug("[assistant_endpoint] Modello indica 'NO_SPARQL', quindi nessuna query generata.")
|
| 393 |
else:
|
| 394 |
+
# Applichiamo la correzione avanzata
|
| 395 |
advanced_corrected = correct_sparql_syntax_advanced(possible_query)
|
| 396 |
+
# Verifichiamo la validità della query
|
| 397 |
if is_sparql_query_valid(advanced_corrected):
|
| 398 |
generated_query = advanced_corrected
|
| 399 |
logger.debug(f"[assistant_endpoint] Query SPARQL valida dopo correzione avanzata: {generated_query}")
|
| 400 |
else:
|
| 401 |
+
logger.debug("[assistant_endpoint] Query SPARQL non valida. Verrà ignorata.")
|
| 402 |
generated_query = None
|
| 403 |
|
| 404 |
+
# -----------------------------------------------------------------------
|
| 405 |
+
# STEP 2: Esecuzione della query, se disponibile
|
| 406 |
+
# -----------------------------------------------------------------------
|
| 407 |
results = []
|
| 408 |
if generated_query:
|
| 409 |
logger.debug(f"[assistant_endpoint] Esecuzione della query SPARQL:\n{generated_query}")
|
|
|
|
| 414 |
except Exception as ex:
|
| 415 |
logger.error(f"[assistant_endpoint] Errore nell'esecuzione della query: {ex}")
|
| 416 |
results = []
|
| 417 |
+
|
| 418 |
+
# -----------------------------------------------------------------------
|
| 419 |
+
# STEP 3: Generazione della risposta finale stile "guida museale"
|
| 420 |
+
# -----------------------------------------------------------------------
|
| 421 |
system_prompt_guide = create_system_prompt_for_guide()
|
| 422 |
+
|
| 423 |
if generated_query and results:
|
| 424 |
+
# Caso: query generata + risultati SPARQL
|
| 425 |
# Convertiamo i risultati in una stringa più leggibile
|
| 426 |
results_str = "\n".join(
|
| 427 |
+
f"{idx+1}) " + ", ".join(f"{var}={row[var]}" for var in row.labels)
|
|
|
|
|
|
|
|
|
|
| 428 |
for idx, row in enumerate(results)
|
| 429 |
)
|
| 430 |
second_prompt = (
|
|
|
|
| 435 |
"Rispondi in modo breve (max ~50 parole)."
|
| 436 |
)
|
| 437 |
logger.debug("[assistant_endpoint] Prompt di risposta con risultati SPARQL.")
|
| 438 |
+
|
| 439 |
elif generated_query and not results:
|
| 440 |
+
# Caso: query valida ma 0 risultati
|
| 441 |
second_prompt = (
|
| 442 |
f"{system_prompt_guide}\n\n"
|
| 443 |
f"Domanda utente: {user_message}\n"
|
| 444 |
f"Query generata: {generated_query}\n"
|
| 445 |
"Nessun risultato dalla query. Prova comunque a rispondere con le tue conoscenze."
|
| 446 |
)
|
| 447 |
+
logger.debug("[assistant_endpoint] Prompt di risposta: query valida ma senza risultati.")
|
| 448 |
+
|
| 449 |
else:
|
| 450 |
+
# Caso: nessuna query generata
|
| 451 |
second_prompt = (
|
| 452 |
f"{system_prompt_guide}\n\n"
|
| 453 |
f"Domanda utente: {user_message}\n"
|
|
|
|
| 455 |
)
|
| 456 |
logger.debug("[assistant_endpoint] Prompt di risposta: nessuna query generata.")
|
| 457 |
|
| 458 |
+
# Chiamata finale al modello per la risposta "guida museale"
|
| 459 |
try:
|
| 460 |
+
logger.debug("[assistant_endpoint] Chiamata HF per generare la risposta finale...")
|
| 461 |
+
final_output = hf_generation_client.chat.completions.create(
|
| 462 |
messages=[
|
| 463 |
{"role": "system", "content": second_prompt},
|
| 464 |
{"role": "user", "content": "Fornisci la risposta finale."}
|
| 465 |
],
|
| 466 |
+
max_tokens=max_tokens,
|
| 467 |
+
temperature=temperature
|
| 468 |
)
|
| 469 |
final_answer = final_output["choices"][0]["message"]["content"].strip()
|
| 470 |
logger.info(f"[assistant_endpoint] Risposta finale generata: {final_answer}")
|
| 471 |
except Exception as ex:
|
| 472 |
logger.error(f"Errore nella generazione della risposta finale: {ex}")
|
| 473 |
raise HTTPException(status_code=500, detail="Errore nella generazione della risposta in linguaggio naturale.")
|
| 474 |
+
|
| 475 |
+
# -----------------------------------------------------------------------
|
| 476 |
+
# STEP 4: Traduzione (se necessario)
|
| 477 |
+
# -----------------------------------------------------------------------
|
| 478 |
final_ans = classify_and_translate(user_message, final_answer)
|
| 479 |
+
final_ans = final_ans.replace('\\"', "").replace('\"', "")
|
| 480 |
+
# -----------------------------------------------------------------------
|
| 481 |
+
# Restituzione in formato JSON
|
| 482 |
+
# -----------------------------------------------------------------------
|
| 483 |
+
logger.debug("[assistant_endpoint] Fine elaborazione, restituzione risposta JSON.")
|
| 484 |
return {
|
| 485 |
"query": generated_query,
|
| 486 |
"response": final_ans
|
| 487 |
}
|
| 488 |
|
| 489 |
# ---------------------------------------------------------------------------
|
| 490 |
+
# ENDPOINT DI TEST / HOME
|
| 491 |
# ---------------------------------------------------------------------------
|
| 492 |
@app.get("/")
|
| 493 |
def home():
|
| 494 |
+
"""
|
| 495 |
+
Endpoint di test per verificare se l'applicazione è in esecuzione.
|
| 496 |
+
"""
|
| 497 |
logger.debug("Chiamata GET su '/' - home.")
|
| 498 |
return {
|
| 499 |
+
"message": "Endpoint attivo. Esempio di backend per generare query SPARQL e risposte guida museale."
|
| 500 |
}
|
| 501 |
|
| 502 |
# ---------------------------------------------------------------------------
|
| 503 |
# MAIN
|
| 504 |
# ---------------------------------------------------------------------------
|
| 505 |
if __name__ == "__main__":
|
| 506 |
+
"""
|
| 507 |
+
Avvio dell'applicazione FastAPI sulla porta 8000,
|
| 508 |
+
utile se eseguito come script principale.
|
| 509 |
+
"""
|
| 510 |
+
logger.info("Avvio dell'applicazione FastAPI.")
|