|
import os |
|
import logging |
|
from rdflib import Graph |
|
from pydantic import BaseModel |
|
from fastapi import FastAPI, HTTPException |
|
from huggingface_hub import InferenceClient |
|
from typing import Optional |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format="%(asctime)s - %(levelname)s - %(message)s", |
|
handlers=[ |
|
logging.FileHandler("app.log"), |
|
logging.StreamHandler() |
|
] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
API_KEY = os.getenv("HF_API_KEY") |
|
if not API_KEY: |
|
logger.error("HF_API_KEY non impostata nell'ambiente.") |
|
raise EnvironmentError("HF_API_KEY non impostata nell'ambiente.") |
|
|
|
client = InferenceClient(api_key=API_KEY) |
|
|
|
|
|
RDF_FILE = "Ontologia.rdf" |
|
|
|
|
|
|
|
|
|
def load_rdf_summary(): |
|
""" |
|
Carica un riassunto dell'ontologia dal file RDF. |
|
Estrae le classi e le proprietà presenti nell'ontologia. |
|
""" |
|
logger.info("Inizio caricamento del file RDF.") |
|
if not os.path.exists(RDF_FILE): |
|
logger.error("Nessun file RDF trovato.") |
|
return "Nessun file RDF trovato." |
|
try: |
|
g = Graph() |
|
g.parse(RDF_FILE, format="xml") |
|
|
|
|
|
classes = set() |
|
properties = set() |
|
for s, p, o in g.triples((None, None, None)): |
|
if "Class" in str(o): |
|
classes.add(s) |
|
if "Property" in str(o): |
|
properties.add(s) |
|
|
|
class_summary = "\n".join([f"- Classe: {cls}" for cls in classes]) |
|
prop_summary = "\n".join([f"- Proprietà: {prop}" for prop in properties]) |
|
summary = f"Classi:\n{class_summary}\n\nProprietà:\n{prop_summary}" |
|
logger.info("Caricamento RDF completato con successo.") |
|
return summary |
|
except Exception as e: |
|
logger.error(f"Errore durante il parsing del file RDF: {e}") |
|
return "Errore nel caricamento del file RDF." |
|
|
|
rdf_context = load_rdf_summary() |
|
logger.info(f"RDF Summary:\n{rdf_context}") |
|
|
|
|
|
|
|
|
|
def validate_sparql_query(query: str, rdf_file_path: str) -> bool: |
|
""" |
|
Verifica la validità della query SPARQL. |
|
""" |
|
logger.info("Inizio validazione della query SPARQL.") |
|
g = Graph() |
|
try: |
|
g.parse(rdf_file_path, format="xml") |
|
g.query(query) |
|
logger.info("Validazione della query SPARQL riuscita.") |
|
return True |
|
except Exception as e: |
|
logger.error(f"Errore durante la validazione della query SPARQL: {e}") |
|
return False |
|
|
|
|
|
|
|
|
|
def create_system_message(rdf_context: str) -> str: |
|
""" |
|
Crea il messaggio di sistema per il modello di linguaggio naturale. |
|
""" |
|
return f""" |
|
Sei un'assistente esperta nella generazione di query SPARQL basate su un'ontologia RDF, nell'interpretazione dei risultati delle query SPARQL in risposte naturali, e nel fare chatting minimale con i visitatori. In base alla domanda dell'utente, devi decidere se: |
|
|
|
1. Generare una query SPARQL per interrogare la base di conoscenza. |
|
2. Fornire una risposta naturale basata sui risultati di una query SPARQL. |
|
3. Rispondere con una risposta di chat minimale. |
|
|
|
Ecco un riassunto dell'ontologia su cui devi lavorare: |
|
{rdf_context} |
|
|
|
Regole TASSATIVE: |
|
1. Se la domanda richiede una query SPARQL, restituisci la query SPARQL come testo semplice. |
|
2. Se la domanda richiede l'interpretazione di risultati SPARQL, restituisci una risposta naturale basata sui risultati. |
|
3. Se la domanda è una chat minimale, restituisci una risposta di chat. |
|
4. DEVI usare ESCLUSIVAMENTE questo prefisso di base (e NON modificarlo in nessun modo): |
|
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> |
|
5. NON generare alcun altro prefisso o URI inventato. |
|
6. Se non puoi rispondere con una query SPARQL valida, interpretare i risultati o fare chatting, scrivi: |
|
"Non posso generare una query SPARQL, interpretare i risultati o fare una risposta di chat per questa richiesta." |
|
|
|
Esempi: |
|
- Domanda: "Quali sono le statue esposte del periodo medievale?" |
|
Risposta: |
|
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> SELECT ?statua WHERE {{ ?statua a base:Statua . ?statua base:Periodo_Storico "Medioevo" . }} |
|
|
|
- Domanda: "La query ha restituito 5 statue. Puoi descriverle?" |
|
Risposta: |
|
Ecco le 5 statue medievali trovate: Statua1, Statua2, Statua3, Statua4, Statua5. |
|
|
|
- Domanda: "Ciao!" |
|
Risposta: |
|
Ciao! Come posso aiutarti oggi? |
|
|
|
RISPONDI ESCLUSIVAMENTE CON IL FORMATO SPECIFICATO. |
|
""" |
|
|
|
|
|
|
|
|
|
async def call_model(messages, temperature=0.7, max_tokens=2048): |
|
""" |
|
Chiama il modello di linguaggio naturale con i messaggi forniti. |
|
""" |
|
logger.info("Chiamata al modello iniziata.") |
|
try: |
|
response = client.chat.completions.create( |
|
model="Qwen/Qwen2.5-72B-Instruct", |
|
messages=messages, |
|
temperature=temperature, |
|
max_tokens=max_tokens, |
|
top_p=0.7, |
|
stream=False |
|
) |
|
raw_text = response["choices"][0]["message"]["content"] |
|
logger.info(f"Risposta del modello ricevuta: {raw_text}") |
|
|
|
return raw_text.replace("\n", " ").strip() |
|
except Exception as e: |
|
logger.error(f"Errore durante la chiamata al modello: {e}") |
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
|
async def interpret_sparql_results(results): |
|
""" |
|
Invia i risultati delle query SPARQL al modello per ottenere una risposta naturale. |
|
""" |
|
logger.info("Inizio interpretazione dei risultati SPARQL.") |
|
if not results: |
|
logger.info("Nessun risultato trovato per la query SPARQL.") |
|
return "Mi dispiace, non sono riuscita a trovare le informazioni che stavi cercando." |
|
|
|
|
|
results_str = "\n".join([", ".join([f"{k}: {v}" for k, v in row.asdict().items()]) for row in results]) |
|
logger.debug(f"Risultati SPARQL:\n{results_str}") |
|
|
|
|
|
interpret_prompt = f""" |
|
Mi hai fornito i seguenti risultati di una query SPARQL: |
|
{results_str} |
|
|
|
Per favore, interpreta questi risultati e fornisci una risposta naturale ed enfatica come farebbe una guida museale femminile. |
|
""" |
|
|
|
messages = [ |
|
{"role": "system", "content": interpret_prompt}, |
|
{"role": "user", "content": ""} |
|
] |
|
|
|
logger.info("Invio dei risultati SPARQL al modello per l'interpretazione.") |
|
natural_response = await call_model(messages, temperature=0.7, max_tokens=2048) |
|
logger.info(f"Risposta interpretata ricevuta dal modello: {natural_response}") |
|
return natural_response |
|
|
|
|
|
|
|
|
|
app = FastAPI() |
|
|
|
class QueryRequest(BaseModel): |
|
message: str |
|
max_tokens: int = 2048 |
|
temperature: float = 0.7 |
|
|
|
@app.post("/generate-response/") |
|
async def generate_response(request: QueryRequest): |
|
user_msg = request.message |
|
logger.info(f"Ricevuta richiesta: {user_msg}") |
|
|
|
|
|
system_msg = create_system_message(rdf_context) |
|
messages = [ |
|
{"role": "system", "content": system_msg}, |
|
{"role": "user", "content": user_msg} |
|
] |
|
response_text = await call_model(messages, request.temperature, request.max_tokens) |
|
|
|
logger.info(f"Risposta generata dal modello: {response_text}") |
|
|
|
|
|
if response_text.startswith("PREFIX base:"): |
|
sparql_query = response_text |
|
logger.info("La risposta è stata identificata come una query SPARQL.") |
|
|
|
if validate_sparql_query(sparql_query, RDF_FILE): |
|
logger.info("La query SPARQL è valida. Inizio esecuzione della query.") |
|
|
|
try: |
|
g = Graph() |
|
g.parse(RDF_FILE, format="xml") |
|
results = g.query(sparql_query) |
|
logger.info(f"Query SPARQL eseguita con successo. Numero di risultati: {len(results)}") |
|
|
|
interpreted_response = await interpret_sparql_results(results) |
|
logger.info(f"Risposta naturale interpretata: {interpreted_response}") |
|
return {"type": "NATURAL", "response": interpreted_response} |
|
except Exception as e: |
|
logger.error(f"Errore durante l'esecuzione della query SPARQL: {e}") |
|
return {"type": "ERROR", "response": "Mi dispiace, c'è stato un errore nell'esecuzione della tua richiesta."} |
|
else: |
|
logger.warning("La query SPARQL generata non è valida.") |
|
return {"type": "ERROR", "response": "La query SPARQL generata non è valida. Per favore, riprova con una domanda diversa."} |
|
|
|
elif "Non posso generare una query SPARQL" in response_text: |
|
|
|
logger.warning("Il modello ha risposto con un messaggio di errore.") |
|
return {"type": "ERROR", "response": response_text} |
|
|
|
else: |
|
|
|
logger.info("La risposta è stata identificata come una risposta naturale o di chat.") |
|
return {"type": "NATURAL", "response": response_text} |
|
|
|
@app.get("/") |
|
async def root(): |
|
return {"message": "Server attivo e pronto a generare risposte!"} |