AshenClock commited on
Commit
848d0c0
·
verified ·
1 Parent(s): 8607561

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -161
app.py CHANGED
@@ -6,11 +6,8 @@ from fastapi import FastAPI, HTTPException
6
  import rdflib
7
  from huggingface_hub import InferenceClient
8
 
9
- # ==============================
10
- # CONFIGURAZIONE LOGGING
11
- # ==============================
12
  logging.basicConfig(
13
- level=logging.DEBUG, # Se vuoi log dettagliato
14
  format="%(asctime)s - %(levelname)s - %(message)s",
15
  handlers=[
16
  logging.FileHandler("app.log"),
@@ -19,129 +16,131 @@ logging.basicConfig(
19
  )
20
  logger = logging.getLogger(__name__)
21
 
22
- # ==============================
23
- # PARAMETRI
24
- # ==============================
25
  API_KEY = os.getenv("HF_API_KEY")
26
  if not API_KEY:
27
- logger.error("HF_API_KEY non impostata nell'ambiente.")
28
- raise EnvironmentError("HF_API_KEY non impostata nell'ambiente.")
29
 
30
  client = InferenceClient(api_key=API_KEY)
31
 
32
  RDF_FILE = "Ontologia.rdf"
33
- MAX_TRIPLES = 600 # Numero di triple da includere nel prompt
34
- HF_MODEL = "Qwen/Qwen2.5-72B-Instruct" # Nome del modello Hugging Face
35
 
36
- # ==============================
37
- # STEP 0: Caricamento e Preprocessing dell'Ontologia
38
- # ==============================
39
- def load_ontology_as_text(rdf_file: str, max_triples: int = 300) -> str:
40
  """
41
- Legge un numero limitato di triple dall'ontologia
42
- e le converte in una stringa 'knowledge_text'.
43
  """
44
  if not os.path.exists(rdf_file):
45
- return "Nessun file RDF trovato."
 
46
 
47
  g = rdflib.Graph()
48
  try:
49
  g.parse(rdf_file, format="xml")
50
  except Exception as e:
51
  logger.error(f"Errore parsing RDF: {e}")
52
- return "Errore nel parsing RDF."
53
 
54
  lines = []
55
  count = 0
56
- for s, p, o in g:
57
  if count >= max_triples:
58
  break
59
- s_str = str(s)[:200]
60
- p_str = str(p)[:200]
61
- o_str = str(o)[:200]
62
- line = f"- {s_str} | {p_str} | {o_str}"
63
- lines.append(line)
64
- count += 1
65
-
66
- knowledge_text = "\n".join(lines)
67
- return knowledge_text
68
-
69
- knowledge_text = load_ontology_as_text(RDF_FILE, max_triples=MAX_TRIPLES)
70
- logger.debug(f"[ONTOLOGY TEXT] {knowledge_text[:1000]} ...")
71
-
72
- # ==============================
73
- # FUNZIONI UTILI
74
- # ==============================
75
- def create_initial_system_message(knowledge_text: str) -> str:
76
  """
77
- System prompt 'stringente' che ordina di generare SEMPRE una query SPARQL.
 
 
 
78
  """
79
- system_prompt = f"""
80
- Sei un assistente di un museo. Hai accesso a queste triple (max {MAX_TRIPLES}):
81
- {knowledge_text}
82
 
83
- REGOLE TASSATIVE:
84
- 1) Per qualunque domanda riguardi il contenuto di queste triple, DEVI generare una query SPARQL
 
 
 
 
85
  con il prefisso:
86
  PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
87
- 2) Se la query produce 0 risultati o fallisce, devi provare una seconda volta
88
- cambiando la sintassi (ad es. usando FILTER(STR(?nome)) o @it).
89
- 3) Se la domanda è chat generica, rispondi brevemente, ma se appare una parte di knowledge
90
- potresti tentare comunque una query.
91
- 4) Se trovi almeno un risultato, fornisci la query SPARQL come tua risposta.
92
- 5) NON inventare triple che non esistono in questo testo.
93
- 6) Usa esclusivamente i dati contenuti qui. Se non trovi nulla, di' "Non ci sono info".
94
- 7) Se la query è generata ma produce 0 results, prova un secondo tentativo.
95
-
96
- Buona fortuna!
 
 
 
 
97
  """
98
- return system_prompt
99
 
100
  def create_explanation_prompt(results_str: str) -> str:
101
  """
102
- Prompt da passare al modello per spiegare i risultati.
 
 
 
103
  """
104
- # Chiedi una spiegazione dettagliata ma non prolissa.
105
- explanation_prompt = f"""
106
- Abbiamo ottenuto questi risultati SPARQL:
107
-
108
  {results_str}
109
 
110
- Per favore spiega in modo dettagliato (ma non eccessivamente lungo) i risultati
111
- come farebbe una guida museale. Cita, se serve, periodi storici o materiali
112
- trovati, e fa' un breve richiamo all'ontologia. Non inventare nulla oltre.
113
  """
114
- return explanation_prompt
115
 
116
- async def call_model(messages, temperature=0.7, max_tokens=2048) -> str:
117
  """
118
- Chiama HuggingFace Inference endpoint con i messaggi forniti.
119
- Logga diversi step per debugging.
120
  """
121
- logger.debug("[call_model] MESSAGGI INVIATI AL MODELLO:")
122
- for msg in messages:
123
- logger.debug(f"ROLE={msg['role']} CONTENT={msg['content'][:500]}")
124
 
125
- logger.info("[call_model] Chiamata al modello Hugging Face...")
126
  try:
127
- response = client.chat.completions.create(
128
  model=HF_MODEL,
129
  messages=messages,
130
  temperature=temperature,
131
  max_tokens=max_tokens,
132
  top_p=0.9
133
  )
134
- raw_text = response["choices"][0]["message"]["content"]
135
- logger.debug("[call_model] Risposta raw del modello HF:\n" + raw_text)
136
- return raw_text.replace("\n", " ").strip()
137
-
138
  except Exception as e:
139
- logger.error(f"[call_model] Errore durante la chiamata: {e}")
140
  raise HTTPException(status_code=500, detail=str(e))
141
 
142
- # ==============================
143
- # FASTAPI
144
- # ==============================
145
  app = FastAPI()
146
 
147
  class QueryRequest(BaseModel):
@@ -152,117 +151,91 @@ class QueryRequest(BaseModel):
152
  @app.post("/generate-response/")
153
  async def generate_response(req: QueryRequest):
154
  user_input = req.message
155
- logger.info(f"[REQUEST] Ricevuta richiesta: {user_input}")
156
-
157
- # 1) Creiamo system message con l'ontologia
158
- system_prompt = create_initial_system_message(knowledge_text)
159
- logger.debug("[SYSTEM PROMPT] " + system_prompt[:1000] + "...")
160
 
161
- # 2) Prima chiamata => generare query SPARQL
162
  messages = [
163
- {"role": "system", "content": system_prompt},
164
- {"role": "user", "content": user_input}
165
  ]
166
- response_text = await call_model(messages, req.temperature, req.max_tokens)
167
- logger.info(f"[PRIMA RISPOSTA MODELLO] {response_text}")
168
-
169
- # 3) Se non abbiamo "PREFIX base:" => second attempt
170
- if not response_text.startswith("PREFIX base:"):
171
- second_try_prompt = f"""
172
- Non hai fornito una query SPARQL. Ricorda di generarla SEMPRE.
173
- Riprova con un approccio differente per questa domanda:
174
- {user_input}
175
- """
176
- second_messages = [
177
- {"role": "system", "content": system_prompt},
178
- {"role": "assistant", "content": response_text},
179
- {"role": "user", "content": second_try_prompt}
180
  ]
181
- response_text_2 = await call_model(second_messages, req.temperature, req.max_tokens)
182
- logger.info(f"[SECONDA RISPOSTA MODELLO] {response_text_2}")
183
-
184
- if response_text_2.startswith("PREFIX base:"):
185
- response_text = response_text_2
186
  else:
187
- # Neanche al secondo tentativo => restituiamo come "chat"
188
- logger.info("[FALLBACK] Nessuna query generata anche al secondo tentativo.")
189
- return {
190
- "type": "NATURAL",
191
- "response": response_text_2
192
- }
193
-
194
- # 4) Ora dovremmo avere una query SPARQL in 'response_text'
195
- sparql_query = response_text
196
- logger.info(f"[FINAL QUERY] {sparql_query}")
197
 
198
- # 5) Eseguiamo la query con rdflib
199
  g = rdflib.Graph()
200
  try:
201
  g.parse(RDF_FILE, format="xml")
202
  except Exception as e:
203
- logger.error(f"[ERROR] Parsing RDF: {e}")
204
- return {"type": "ERROR", "response": "Errore nel parsing dell'ontologia."}
205
 
206
- # 6) Validazione + esecuzione
207
  try:
208
  results = g.query(sparql_query)
209
  except Exception as e:
210
- logger.warning(f"[QUERY FAIL] {e}")
211
- # Tenta un fallback in caso la query non sia valida
212
- second_try_prompt2 = f"""
213
- La query SPARQL che hai fornito non è valida o non produce risultati.
214
- Riprova con una sintassi differente.
215
- Domanda utente: {user_input}
216
- """
217
- second_messages2 = [
218
- {"role": "system", "content": system_prompt},
219
- {"role": "assistant", "content": sparql_query},
220
- {"role": "user", "content": second_try_prompt2}
221
  ]
222
- second_response2 = await call_model(second_messages2, req.temperature, req.max_tokens)
223
- logger.info(f"[TERZO TENTATIVO MODELLO] {second_response2}")
224
- if second_response2.startswith("PREFIX base:"):
225
- # eseguiamo di nuovo
226
- sparql_query = second_response2
227
  try:
228
  results = g.query(sparql_query)
229
  except Exception as e2:
230
- logger.error(f"[QUERY FAIL 2] {e2}")
231
- return {"type": "ERROR", "response": "Query fallita di nuovo."}
232
  else:
233
- return {"type": "NATURAL", "response": second_response2}
234
 
235
- # 7) Se 0 results => fine
236
- if len(results) == 0:
237
- logger.info("[SPARQL RESULT] 0 risultati.")
238
- return {"type": "NATURAL", "response": "Nessun risultato trovato nella nostra ontologia."}
239
 
240
- # 8) Ok => costruiamo una stringa con i risultati e facciamo un NUOVO prompt di interpretazione
241
  row_list = []
242
  for row in results:
243
- row_str = ", ".join([f"{k}:{v}" for k, v in row.asdict().items()])
244
- row_list.append(row_str)
245
  results_str = "\n".join(row_list)
 
246
 
247
- logger.info(f"[SPARQL OK] Trovati {len(results)} risultati.\n{results_str}")
248
-
249
- # 9) Creiamo prompt di interpretazione
250
- explain_prompt = create_explanation_prompt(results_str)
251
- explain_messages = [
252
- {"role": "system", "content": explain_prompt},
253
- {"role": "user", "content": ""}
254
  ]
255
- explanation_text = await call_model(explain_messages, req.temperature, req.max_tokens)
256
- logger.info(f"[EXPLANATION RESPONSE] {explanation_text}")
257
 
258
- # 10) Restituiamo i risultati e la spiegazione finale
259
  return {
260
- "type": "NATURAL",
261
  "sparql_query": sparql_query,
262
- "sparql_results": row_list, # Lista di stringhe
263
- "explanation": explanation_text
264
  }
265
 
266
  @app.get("/")
267
- async def root():
268
- return {"message": "Server con ontologia in system prompt, doppio tentativo SPARQL e interpretazione."}
 
6
  import rdflib
7
  from huggingface_hub import InferenceClient
8
 
 
 
 
9
  logging.basicConfig(
10
+ level=logging.DEBUG,
11
  format="%(asctime)s - %(levelname)s - %(message)s",
12
  handlers=[
13
  logging.FileHandler("app.log"),
 
16
  )
17
  logger = logging.getLogger(__name__)
18
 
19
+ # Imposta la tua HF_API_KEY
 
 
20
  API_KEY = os.getenv("HF_API_KEY")
21
  if not API_KEY:
22
+ logger.error("HF_API_KEY non impostata.")
23
+ raise EnvironmentError("HF_API_KEY non impostata.")
24
 
25
  client = InferenceClient(api_key=API_KEY)
26
 
27
  RDF_FILE = "Ontologia.rdf"
28
+ MAX_TRIPLES = 300 # Se la tua ontologia è enorme, abbassa questo
29
+ HF_MODEL = "Qwen/Qwen2.5-72B-Instruct"
30
 
31
+ # Carica e "preprocessa" l'ontologia
32
+ def load_ontology_as_text(rdf_file: str, max_triples: int=300) -> str:
 
 
33
  """
34
+ Legge un numero limitato di triple dal file RDF e le concatena in
35
+ una stringa. Limita i literal in lunghezza per non gonfiare troppo il prompt.
36
  """
37
  if not os.path.exists(rdf_file):
38
+ logger.warning("File RDF non trovato.")
39
+ return "NO_RDF"
40
 
41
  g = rdflib.Graph()
42
  try:
43
  g.parse(rdf_file, format="xml")
44
  except Exception as e:
45
  logger.error(f"Errore parsing RDF: {e}")
46
+ return "PARSING_ERROR"
47
 
48
  lines = []
49
  count = 0
50
+ for s,p,o in g:
51
  if count >= max_triples:
52
  break
53
+ # Taglia i literal in modo da non superare un tot di caratteri
54
+ s_str = str(s)[:100].replace("\n"," ")
55
+ p_str = str(p)[:100].replace("\n"," ")
56
+ o_str = str(o)[:100].replace("\n"," ")
57
+ lines.append(f"{s_str}|{p_str}|{o_str}")
58
+ count+=1
59
+
60
+ # Concateniamo le triple su righe separate (più facile da leggere).
61
+ # Se preferisci su un'unica riga, puoi usare " ".join(lines).
62
+ ontology_text = "\n".join(lines)
63
+ logger.debug(f"Caricate {count} triple.")
64
+ return ontology_text
65
+
66
+ knowledge_text = load_ontology_as_text(RDF_FILE, MAX_TRIPLES)
67
+
68
+ def create_system_message(ont_text: str) -> str:
 
69
  """
70
+ Prompt di sistema robusto e stringente.
71
+ - Forza query SPARQL in UNA sola riga
72
+ - Richiede secondi tentativi
73
+ - Domande generiche -> risposte brevi, ma se c'è un modo di fare query, farlo
74
  """
75
+ system_msg = f"""
76
+ Sei un assistente museale. Hai a disposizione un estratto di triple RDF (massimo {MAX_TRIPLES}):
 
77
 
78
+ --- TRIPLE ---
79
+ {ont_text}
80
+ --- FINE TRIPLE ---
81
+
82
+ REGOLE STRINGENTI:
83
+ 1) Se l'utente chiede informazioni correlate alle triple, DEVI generare una query SPARQL in UNA SOLA RIGA,
84
  con il prefisso:
85
  PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
86
+ Esempio: PREFIX base: <...> SELECT ?x WHERE {{ ... }} (tutto su una sola riga).
87
+ 2) Se la query produce 0 risultati o fallisce, devi ritentare con un secondo tentativo,
88
+ magari usando FILTER(STR(...)) o @it, o cambiando minuscole/maiuscole.
89
+ 3) Se la domanda è una chat generica, rispondi breve (saluto, ecc.). Ma se c'è qualcosa
90
+ di correlato, prova comunque la query.
91
+ 4) Se generi la query e trovi risultati, la risposta finale deve essere la query SPARQL
92
+ (una sola riga). Non inventare triple inesistenti.
93
+ 5) Se non trovi nulla, rispondi 'Non ci sono informazioni in queste triple.'
94
+ 6) Non ignorare: se l'utente fa domanda su 'David', 'Amore e Psiche', etc., devi
95
+ estrarre dai triple tutti i dettagli possibili con SPARQL.
96
+ 7) Se la query produce 0, prova un secondo tentativo con sintassi differente.
97
+ 8) Non fare risposte su più righe per la query: una sola riga.
98
+
99
+ FINE REGOLE.
100
  """
101
+ return system_msg
102
 
103
  def create_explanation_prompt(results_str: str) -> str:
104
  """
105
+ Prompt per spiegazione finale, robusto:
106
+ - Spiega i risultati come guida museale
107
+ - Non inventare
108
+ - 10-15 righe max
109
  """
110
+ msg = f"""
111
+ Ho ottenuto questi risultati SPARQL:
 
 
112
  {results_str}
113
 
114
+ Ora fornisci una spiegazione come guida museale, in modo comprensibile e dettagliato (max ~10-15 righe).
115
+ Cita eventuali materiali, periodi storici, autore, facendo riferimento al contesto RDF (ma senza contraddizioni).
116
+ Non inventare nulla oltre ciò che appare nei risultati.
117
  """
118
+ return msg
119
 
120
+ async def call_hf_model(messages, temperature=0.7, max_tokens=1024) -> str:
121
  """
122
+ Funzione di chiamata al modello su HuggingFace,
123
+ con log e robustezza. max_tokens=1024 come richiesto.
124
  """
125
+ logger.debug("Chiamo HF con i seguenti messaggi:")
126
+ for m in messages:
127
+ logger.debug(f"ROLE={m['role']} -> {m['content'][:500]}")
128
 
 
129
  try:
130
+ resp = client.chat.completions.create(
131
  model=HF_MODEL,
132
  messages=messages,
133
  temperature=temperature,
134
  max_tokens=max_tokens,
135
  top_p=0.9
136
  )
137
+ raw = resp["choices"][0]["message"]["content"]
138
+ logger.debug(f"Risposta HF: {raw}")
139
+ return raw.replace("\n"," ").strip()
 
140
  except Exception as e:
141
+ logger.error(f"Errore HF: {e}")
142
  raise HTTPException(status_code=500, detail=str(e))
143
 
 
 
 
144
  app = FastAPI()
145
 
146
  class QueryRequest(BaseModel):
 
151
  @app.post("/generate-response/")
152
  async def generate_response(req: QueryRequest):
153
  user_input = req.message
154
+ logger.info(f"Utente chiede: {user_input}")
 
 
 
 
155
 
156
+ system_msg = create_system_message(knowledge_text)
157
  messages = [
158
+ {"role":"system","content":system_msg},
159
+ {"role":"user","content":user_input}
160
  ]
161
+ # Prima risposta
162
+ first_response = await call_hf_model(messages, req.temperature, req.max_tokens)
163
+ logger.info(f"Prima risposta generata:\n{first_response}")
164
+
165
+ # Se non inizia con 'PREFIX base:' => second attempt
166
+ if not first_response.startswith("PREFIX base:"):
167
+ second_prompt = f"Non hai risposto con query SPARQL in una sola riga. Riprova. Domanda: {user_input}"
168
+ second_msgs = [
169
+ {"role":"system","content":system_msg},
170
+ {"role":"assistant","content":first_response},
171
+ {"role":"user","content":second_prompt}
 
 
 
172
  ]
173
+ second_response = await call_hf_model(second_msgs, req.temperature, req.max_tokens)
174
+ logger.info(f"Seconda risposta:\n{second_response}")
175
+ if second_response.startswith("PREFIX base:"):
176
+ sparql_query = second_response
 
177
  else:
178
+ return {"type":"NATURAL","response": second_response}
179
+ else:
180
+ sparql_query = first_response
 
 
 
 
 
 
 
181
 
182
+ # Eseguiamo la query con rdflib
183
  g = rdflib.Graph()
184
  try:
185
  g.parse(RDF_FILE, format="xml")
186
  except Exception as e:
187
+ logger.error("Errore parse RDF: ", e)
188
+ return {"type":"ERROR","response":"Parsing RDF fallito."}
189
 
190
+ # Se la query è malformata, second attempt
191
  try:
192
  results = g.query(sparql_query)
193
  except Exception as e:
194
+ logger.warning(f"La query SPARQL ha fallito: {e}")
195
+ fallback_prompt = f"La query SPARQL è fallita. Riprova con altra sintassi. Domanda: {user_input}"
196
+ fallback_msgs = [
197
+ {"role":"system","content":system_msg},
198
+ {"role":"assistant","content":sparql_query},
199
+ {"role":"user","content":fallback_prompt}
 
 
 
 
 
200
  ]
201
+ fallback_resp = await call_hf_model(fallback_msgs, req.temperature, req.max_tokens)
202
+ if fallback_resp.startswith("PREFIX base:"):
203
+ sparql_query = fallback_resp
 
 
204
  try:
205
  results = g.query(sparql_query)
206
  except Exception as e2:
207
+ return {"type":"ERROR","response":f"Query fallita di nuovo: {e2}"}
 
208
  else:
209
+ return {"type":"NATURAL","response": fallback_resp}
210
 
211
+ if len(results)==0:
212
+ logger.info("0 risultati SPARQL.")
213
+ return {"type":"NATURAL","sparql_query": sparql_query,"response":"Nessun risultato."}
 
214
 
215
+ # Prepara i risultati per la spiegazione
216
  row_list = []
217
  for row in results:
218
+ row_list.append(", ".join([f"{k}:{v}" for k,v in row.asdict().items()]))
 
219
  results_str = "\n".join(row_list)
220
+ logger.info(f"Risultati trovati ({len(results)}):\n{results_str}")
221
 
222
+ # Prompt di interpretazione
223
+ explain_sys = create_explanation_prompt(results_str)
224
+ explain_msgs = [
225
+ {"role":"system","content":explain_sys},
226
+ {"role":"user","content":""}
 
 
227
  ]
228
+ explanation = await call_hf_model(explain_msgs, req.temperature, req.max_tokens)
229
+ logger.info(f"Spiegazione:\n{explanation}")
230
 
231
+ # Ritorniamo query, risultati e spiegazione
232
  return {
233
+ "type":"NATURAL",
234
  "sparql_query": sparql_query,
235
+ "sparql_results": row_list,
236
+ "explanation": explanation
237
  }
238
 
239
  @app.get("/")
240
+ def home():
241
+ return {"message":"OK robusto con regole rigide e doppio tentativo."}