File size: 9,751 Bytes
ac52c4d 89f944e ac52c4d 89f944e 8607561 ac52c4d 8607561 2ecaeab 8607561 2ecaeab 89f944e 2ecaeab ac52c4d 8607561 ac52c4d 89f944e 8607561 89f944e ac52c4d 8607561 ac52c4d 89f944e ac52c4d 69d6e40 89f944e ac52c4d 89f944e ac52c4d 89f944e 8607561 89f944e 8607561 89f944e 8607561 89f944e 8607561 89f944e 8607561 89f944e 8607561 ac52c4d 8607561 4c13256 8607561 89f944e 8607561 3aa01bd 8607561 69d6e40 8607561 059bef5 8607561 89f944e 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 8607561 ac52c4d 89f944e ac52c4d 89f944e 8607561 3aa01bd 8607561 89f944e 8607561 ac52c4d 8607561 ac52c4d 8607561 3aa01bd 8607561 69d6e40 8607561 69d6e40 8607561 ac52c4d 8607561 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
import os
import logging
from typing import Optional
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException
import rdflib
from huggingface_hub import InferenceClient
# ==============================
# CONFIGURAZIONE LOGGING
# ==============================
logging.basicConfig(
level=logging.DEBUG, # Se vuoi log dettagliato
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("app.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# ==============================
# PARAMETRI
# ==============================
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"
MAX_TRIPLES = 600 # Numero di triple da includere nel prompt
HF_MODEL = "Qwen/Qwen2.5-72B-Instruct" # Nome del modello Hugging Face
# ==============================
# STEP 0: Caricamento e Preprocessing dell'Ontologia
# ==============================
def load_ontology_as_text(rdf_file: str, max_triples: int = 300) -> str:
"""
Legge un numero limitato di triple dall'ontologia
e le converte in una stringa 'knowledge_text'.
"""
if not os.path.exists(rdf_file):
return "Nessun file RDF trovato."
g = rdflib.Graph()
try:
g.parse(rdf_file, format="xml")
except Exception as e:
logger.error(f"Errore parsing RDF: {e}")
return "Errore nel parsing RDF."
lines = []
count = 0
for s, p, o in g:
if count >= max_triples:
break
s_str = str(s)[:200]
p_str = str(p)[:200]
o_str = str(o)[:200]
line = f"- {s_str} | {p_str} | {o_str}"
lines.append(line)
count += 1
knowledge_text = "\n".join(lines)
return knowledge_text
knowledge_text = load_ontology_as_text(RDF_FILE, max_triples=MAX_TRIPLES)
logger.debug(f"[ONTOLOGY TEXT] {knowledge_text[:1000]} ...")
# ==============================
# FUNZIONI UTILI
# ==============================
def create_initial_system_message(knowledge_text: str) -> str:
"""
System prompt 'stringente' che ordina di generare SEMPRE una query SPARQL.
"""
system_prompt = f"""
Sei un assistente di un museo. Hai accesso a queste triple (max {MAX_TRIPLES}):
{knowledge_text}
REGOLE TASSATIVE:
1) Per qualunque domanda riguardi il contenuto di queste triple, DEVI generare una query SPARQL
con il prefisso:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
2) Se la query produce 0 risultati o fallisce, devi provare una seconda volta
cambiando la sintassi (ad es. usando FILTER(STR(?nome)) o @it).
3) Se la domanda è chat generica, rispondi brevemente, ma se appare una parte di knowledge
potresti tentare comunque una query.
4) Se trovi almeno un risultato, fornisci la query SPARQL come tua risposta.
5) NON inventare triple che non esistono in questo testo.
6) Usa esclusivamente i dati contenuti qui. Se non trovi nulla, di' "Non ci sono info".
7) Se la query è generata ma produce 0 results, prova un secondo tentativo.
Buona fortuna!
"""
return system_prompt
def create_explanation_prompt(results_str: str) -> str:
"""
Prompt da passare al modello per spiegare i risultati.
"""
# Chiedi una spiegazione dettagliata ma non prolissa.
explanation_prompt = f"""
Abbiamo ottenuto questi risultati SPARQL:
{results_str}
Per favore spiega in modo dettagliato (ma non eccessivamente lungo) i risultati
come farebbe una guida museale. Cita, se serve, periodi storici o materiali
trovati, e fa' un breve richiamo all'ontologia. Non inventare nulla oltre.
"""
return explanation_prompt
async def call_model(messages, temperature=0.7, max_tokens=2048) -> str:
"""
Chiama HuggingFace Inference endpoint con i messaggi forniti.
Logga diversi step per debugging.
"""
logger.debug("[call_model] MESSAGGI INVIATI AL MODELLO:")
for msg in messages:
logger.debug(f"ROLE={msg['role']} CONTENT={msg['content'][:500]}")
logger.info("[call_model] Chiamata al modello Hugging Face...")
try:
response = client.chat.completions.create(
model=HF_MODEL,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=0.9
)
raw_text = response["choices"][0]["message"]["content"]
logger.debug("[call_model] Risposta raw del modello HF:\n" + raw_text)
return raw_text.replace("\n", " ").strip()
except Exception as e:
logger.error(f"[call_model] Errore durante la chiamata: {e}")
raise HTTPException(status_code=500, detail=str(e))
# ==============================
# FASTAPI
# ==============================
app = FastAPI()
class QueryRequest(BaseModel):
message: str
max_tokens: int = 1024
temperature: float = 0.5
@app.post("/generate-response/")
async def generate_response(req: QueryRequest):
user_input = req.message
logger.info(f"[REQUEST] Ricevuta richiesta: {user_input}")
# 1) Creiamo system message con l'ontologia
system_prompt = create_initial_system_message(knowledge_text)
logger.debug("[SYSTEM PROMPT] " + system_prompt[:1000] + "...")
# 2) Prima chiamata => generare query SPARQL
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
response_text = await call_model(messages, req.temperature, req.max_tokens)
logger.info(f"[PRIMA RISPOSTA MODELLO] {response_text}")
# 3) Se non abbiamo "PREFIX base:" => second attempt
if not response_text.startswith("PREFIX base:"):
second_try_prompt = f"""
Non hai fornito una query SPARQL. Ricorda di generarla SEMPRE.
Riprova con un approccio differente per questa domanda:
{user_input}
"""
second_messages = [
{"role": "system", "content": system_prompt},
{"role": "assistant", "content": response_text},
{"role": "user", "content": second_try_prompt}
]
response_text_2 = await call_model(second_messages, req.temperature, req.max_tokens)
logger.info(f"[SECONDA RISPOSTA MODELLO] {response_text_2}")
if response_text_2.startswith("PREFIX base:"):
response_text = response_text_2
else:
# Neanche al secondo tentativo => restituiamo come "chat"
logger.info("[FALLBACK] Nessuna query generata anche al secondo tentativo.")
return {
"type": "NATURAL",
"response": response_text_2
}
# 4) Ora dovremmo avere una query SPARQL in 'response_text'
sparql_query = response_text
logger.info(f"[FINAL QUERY] {sparql_query}")
# 5) Eseguiamo la query con rdflib
g = rdflib.Graph()
try:
g.parse(RDF_FILE, format="xml")
except Exception as e:
logger.error(f"[ERROR] Parsing RDF: {e}")
return {"type": "ERROR", "response": "Errore nel parsing dell'ontologia."}
# 6) Validazione + esecuzione
try:
results = g.query(sparql_query)
except Exception as e:
logger.warning(f"[QUERY FAIL] {e}")
# Tenta un 2° fallback in caso la query non sia valida
second_try_prompt2 = f"""
La query SPARQL che hai fornito non è valida o non produce risultati.
Riprova con una sintassi differente.
Domanda utente: {user_input}
"""
second_messages2 = [
{"role": "system", "content": system_prompt},
{"role": "assistant", "content": sparql_query},
{"role": "user", "content": second_try_prompt2}
]
second_response2 = await call_model(second_messages2, req.temperature, req.max_tokens)
logger.info(f"[TERZO TENTATIVO MODELLO] {second_response2}")
if second_response2.startswith("PREFIX base:"):
# eseguiamo di nuovo
sparql_query = second_response2
try:
results = g.query(sparql_query)
except Exception as e2:
logger.error(f"[QUERY FAIL 2] {e2}")
return {"type": "ERROR", "response": "Query fallita di nuovo."}
else:
return {"type": "NATURAL", "response": second_response2}
# 7) Se 0 results => fine
if len(results) == 0:
logger.info("[SPARQL RESULT] 0 risultati.")
return {"type": "NATURAL", "response": "Nessun risultato trovato nella nostra ontologia."}
# 8) Ok => costruiamo una stringa con i risultati e facciamo un NUOVO prompt di interpretazione
row_list = []
for row in results:
row_str = ", ".join([f"{k}:{v}" for k, v in row.asdict().items()])
row_list.append(row_str)
results_str = "\n".join(row_list)
logger.info(f"[SPARQL OK] Trovati {len(results)} risultati.\n{results_str}")
# 9) Creiamo prompt di interpretazione
explain_prompt = create_explanation_prompt(results_str)
explain_messages = [
{"role": "system", "content": explain_prompt},
{"role": "user", "content": ""}
]
explanation_text = await call_model(explain_messages, req.temperature, req.max_tokens)
logger.info(f"[EXPLANATION RESPONSE] {explanation_text}")
# 10) Restituiamo i risultati e la spiegazione finale
return {
"type": "NATURAL",
"sparql_query": sparql_query,
"sparql_results": row_list, # Lista di stringhe
"explanation": explanation_text
}
@app.get("/")
async def root():
return {"message": "Server con ontologia in system prompt, doppio tentativo SPARQL e interpretazione."}
|