AshenClock commited on
Commit
e250196
·
verified ·
1 Parent(s): 51695fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -110
app.py CHANGED
@@ -24,22 +24,13 @@ client = InferenceClient(api_key=API_KEY)
24
  RDF_FILE = "Ontologia.rdf"
25
  HF_MODEL = "Qwen/Qwen2.5-72B-Instruct"
26
 
27
- # Limiti per non sforare la dimensione del prompt
28
- MAX_CLASSES = 30
29
- MAX_PROPERTIES = 30
30
- MAX_INDIVIDUALS = 50
31
- MAX_TRIPLES_PER_IND = 20
32
- MAX_LITERAL_CHARS = 100
33
-
34
- def extract_ontology_summaries(rdf_file: str) -> str:
35
  """
36
- 1) Carica l'ontologia con rdflib.
37
- 2) Estrae:
38
- - un elenco (massimo MAX_CLASSES) di classi
39
- - un elenco (massimo MAX_PROPERTIES) di proprietà
40
- - un estratto di triple relative alle istanze (NamedIndividual)
41
- (massimo MAX_INDIVIDUALS individui, e MAX_TRIPLES_PER_IND triple per individuo).
42
- 3) Ritorna una stringa 'knowledge_text' che unisce questi contenuti.
43
  """
44
  if not os.path.exists(rdf_file):
45
  return "NO_RDF_FILE"
@@ -51,21 +42,16 @@ def extract_ontology_summaries(rdf_file: str) -> str:
51
  logger.error(f"Parsing RDF error: {e}")
52
  return "PARSING_ERROR"
53
 
54
- # ====== Troviamo le Classi ======
55
- # Con un pattern: (s, RDF.type, OWL.Class) o RDFS.Class
56
- # Alcune ontologie usano direct typing, altre no.
57
  classes_found = set()
58
  for s in g.subjects(RDF.type, OWL.Class):
59
  classes_found.add(s)
60
- # Alcune volte ci sono (s, RDF.type, RDFS.Class)
61
  for s in g.subjects(RDF.type, RDFS.Class):
62
  classes_found.add(s)
63
-
64
  classes_list = sorted(str(c) for c in classes_found)
65
  classes_list = classes_list[:MAX_CLASSES]
66
 
67
- # ====== Troviamo le Proprietà ======
68
- # Cerchiamo soggetti con RDF.type in {OWL.ObjectProperty, OWL.DatatypeProperty, RDF.Property}
69
  props_found = set()
70
  for p in g.subjects(RDF.type, OWL.ObjectProperty):
71
  props_found.add(p)
@@ -76,92 +62,58 @@ def extract_ontology_summaries(rdf_file: str) -> str:
76
  props_list = sorted(str(x) for x in props_found)
77
  props_list = props_list[:MAX_PROPERTIES]
78
 
79
- # ====== Troviamo NamedIndividuals e relative triple ======
80
- named_inds = set()
81
- for s in g.subjects(RDF.type, OWL.NamedIndividual):
82
- named_inds.add(s)
83
- logger.debug(f"Found {len(named_inds)} individuals.")
84
- inds_list = sorted(named_inds)[:MAX_INDIVIDUALS]
85
-
86
- # Costruisci un testo con le triple di ogni individuo
87
- lines_inds = []
88
- for ind in inds_list:
89
- triple_count = 0
90
- for p,o in g.predicate_objects(ind):
91
- if triple_count >= MAX_TRIPLES_PER_IND:
92
- break
93
- s_str = str(ind)[:80]
94
- p_str = str(p)[:80]
95
- o_str = str(o)[:MAX_LITERAL_CHARS].replace("\n"," ")
96
- lines_inds.append(f"{s_str}|{p_str}|{o_str}")
97
- triple_count += 1
98
-
99
- # Ora componiamo la stringa finale
100
  txt_classes = "\n".join([f"- CLASSE: {c}" for c in classes_list])
101
  txt_props = "\n".join([f"- PROPRIETA': {p}" for p in props_list])
102
- txt_inds = "\n".join(lines_inds)
103
 
104
- # Il knowledge_text unisce tre sezioni
105
- knowledge_text = f"""\
106
- # CLASSES (max {MAX_CLASSES})
107
  {txt_classes}
108
 
109
- # PROPERTIES (max {MAX_PROPERTIES})
110
  {txt_props}
111
-
112
- # INDIVIDUALS
113
- {txt_inds}
114
  """
115
- return knowledge_text
116
 
117
- knowledge_text = extract_ontology_summaries(RDF_FILE)
118
 
119
- def create_system_message(ont_text: str) -> str:
120
  """
121
- Prompt di sistema robusto con regole stringenti:
122
- - Query SPARQL su una sola riga
123
- - Se 0 results => secondo tentativo
124
- - Chat generica => risposte brevi
125
  """
126
  return f"""
127
- Sei un assistente museale. Qui sotto c'è un riassunto dell'ontologia:
128
- (1) elenco di classi,
129
- (2) elenco di proprietà,
130
- (3) triple relative agli individui (NamedIndividual).
131
 
132
  --- ONTOLOGIA ---
133
  {ont_text}
134
- --- FINE ONTOLOGIA ---
135
-
136
- REGOLE FONDAMENTALI:
137
- 1) Se la domanda dell'utente riguarda info contenute in questa ontologia, produci SEMPRE una query SPARQL in UNA SOLA RIGA,
138
- con: PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
139
- Esempio: PREFIX base: <...> SELECT ?x WHERE {{ ... }}
140
- 2) Se la query produce 0 risultati o fallisce, fai un secondo tentativo (magari con FILTER).
141
- 3) Se la domanda è di chat generica (es. ciao come stai?), rispondi breve.
142
- 4) Se trovi risultati, la tua risposta finale dev'essere la query SPARQL su una riga.
143
- 5) Non inventare triple inesistenti. Se non c'è info, di' "Nessuna info."
144
- 6) Non fare risposte multiline per la query. DEVI metterla su una singola riga.
145
- 7) Usa i dati qui sopra. Se la domanda menziona 'David' o 'Amore e Psiche', controlla le triple Individuals.
146
 
147
  FINE REGOLE
148
  """
149
 
150
  def create_explanation_prompt(results_str:str)->str:
151
  return f"""
152
- Risultati SPARQL:
153
  {results_str}
154
 
155
- Fornisci una spiegazione come guida museale, in ~10 righe, riferendo a materiali, autori, periodi,
156
- senza inventare nulla che non sia presente.
157
  """
158
 
159
  async def call_hf_model(messages, temperature=0.7, max_tokens=1024)->str:
160
- logging.debug("Chiamo modello HF con i seguenti msg:")
161
  for m in messages:
162
- logging.debug(f"ROLE={m['role']} => {m['content'][:300]}")
163
  try:
164
- resp=client.chat.completions.create(
165
  model=HF_MODEL,
166
  messages=messages,
167
  temperature=temperature,
@@ -171,8 +123,11 @@ async def call_hf_model(messages, temperature=0.7, max_tokens=1024)->str:
171
  raw=resp["choices"][0]["message"]["content"]
172
  return raw.replace("\n"," ").strip()
173
  except Exception as e:
 
174
  raise HTTPException(status_code=500, detail=str(e))
175
 
 
 
176
  app=FastAPI()
177
 
178
  class QueryRequest(BaseModel):
@@ -183,77 +138,77 @@ class QueryRequest(BaseModel):
183
  @app.post("/generate-response/")
184
  async def generate_response(req:QueryRequest):
185
  user_input=req.message
186
- logging.info(f"Utente dice: {user_input}")
187
- # 1) Prompt di sistema
188
  sys_msg=create_system_message(knowledge_text)
189
  msgs=[
190
  {"role":"system","content":sys_msg},
191
  {"role":"user","content":user_input}
192
  ]
193
- # 2) Prima risposta
194
- r1=await call_hf_model(msgs, req.temperature, req.max_tokens)
195
- logging.info(f"PRIMA RISPOSTA:\n{r1}")
196
 
197
- # Se non inizia con "PREFIX base:"
198
- if not r1.startswith("PREFIX base:"):
199
- second_q=f"Non hai risposto con query SPARQL su una sola riga. Ritenta. Domanda: {user_input}"
200
  msgs2=[
201
  {"role":"system","content":sys_msg},
202
- {"role":"assistant","content":r1},
203
- {"role":"user","content":second_q}
204
  ]
205
- r2=await call_hf_model(msgs2,req.temperature,req.max_tokens)
206
- logging.info(f"SECONDA RISPOSTA:\n{r2}")
207
- if r2.startswith("PREFIX base:"):
208
- sparql_query=r2
209
  else:
210
- return {"type":"NATURAL","response": r2}
211
  else:
212
- sparql_query=r1
213
 
214
- # 3) Esegui la query su rdflib
 
215
  g=rdflib.Graph()
216
  try:
217
  g.parse(RDF_FILE,format="xml")
218
  except Exception as e:
219
- return {"type":"ERROR","response":f"Parsing RDF error: {e}"}
 
 
220
  try:
221
  results=g.query(sparql_query)
222
  except Exception as e:
223
  # fallback
224
- fallback=f"Query fallita. Riprova con altra sintassi. Domanda: {user_input}"
225
  msgs3=[
226
  {"role":"system","content":sys_msg},
227
  {"role":"assistant","content":sparql_query},
228
  {"role":"user","content":fallback}
229
  ]
230
- r3=await call_hf_model(msgs3,req.temperature,req.max_tokens)
231
- if r3.startswith("PREFIX base:"):
232
- sparql_query=r3
233
  try:
234
  results=g.query(sparql_query)
235
  except Exception as e2:
236
  return {"type":"ERROR","response":f"Query fallita ancora: {e2}"}
237
  else:
238
- return {"type":"NATURAL","response":r3}
239
 
240
  if len(results)==0:
241
  return {"type":"NATURAL","sparql_query":sparql_query,"response":"Nessun risultato."}
242
 
243
- # 4) Costruisci i result row
244
  row_list=[]
245
  for row in results:
246
- row_txt=", ".join([f"{k}:{v}" for k,v in row.asdict().items()])
247
- row_list.append(row_txt)
248
  results_str="\n".join(row_list)
249
 
250
- # 5) Spiegazione
251
  exp_prompt=create_explanation_prompt(results_str)
252
- exp_msgs=[
253
  {"role":"system","content":exp_prompt},
254
  {"role":"user","content":""}
255
  ]
256
- explanation=await call_hf_model(exp_msgs,req.temperature,req.max_tokens)
257
 
258
  return {
259
  "type":"NATURAL",
@@ -264,4 +219,4 @@ async def generate_response(req:QueryRequest):
264
 
265
  @app.get("/")
266
  def home():
267
- return {"msg":"Ok con sunto di classi, proprietà e triple di NamedIndividuals."}
 
24
  RDF_FILE = "Ontologia.rdf"
25
  HF_MODEL = "Qwen/Qwen2.5-72B-Instruct"
26
 
27
+ MAX_CLASSES = 30
28
+ MAX_PROPERTIES = 30
29
+
30
+ def extract_classes_and_properties(rdf_file:str) -> str:
 
 
 
 
31
  """
32
+ Carica l'ontologia e crea un 'sunto' solo di Classi e Proprietà
33
+ (senza riportare NamedIndividuals o triple).
 
 
 
 
 
34
  """
35
  if not os.path.exists(rdf_file):
36
  return "NO_RDF_FILE"
 
42
  logger.error(f"Parsing RDF error: {e}")
43
  return "PARSING_ERROR"
44
 
45
+ # Troviamo le classi
 
 
46
  classes_found = set()
47
  for s in g.subjects(RDF.type, OWL.Class):
48
  classes_found.add(s)
 
49
  for s in g.subjects(RDF.type, RDFS.Class):
50
  classes_found.add(s)
 
51
  classes_list = sorted(str(c) for c in classes_found)
52
  classes_list = classes_list[:MAX_CLASSES]
53
 
54
+ # Troviamo le proprietà
 
55
  props_found = set()
56
  for p in g.subjects(RDF.type, OWL.ObjectProperty):
57
  props_found.add(p)
 
62
  props_list = sorted(str(x) for x in props_found)
63
  props_list = props_list[:MAX_PROPERTIES]
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  txt_classes = "\n".join([f"- CLASSE: {c}" for c in classes_list])
66
  txt_props = "\n".join([f"- PROPRIETA': {p}" for p in props_list])
 
67
 
68
+ summary = f"""\
69
+ # CLASSI (max {MAX_CLASSES})
 
70
  {txt_classes}
71
 
72
+ # PROPRIETA' (max {MAX_PROPERTIES})
73
  {txt_props}
 
 
 
74
  """
75
+ return summary
76
 
77
+ knowledge_text = extract_classes_and_properties(RDF_FILE)
78
 
79
+ def create_system_message(ont_text:str)->str:
80
  """
81
+ Prompt di sistema con regole stringenti e SENZA NamedIndividuals.
 
 
 
82
  """
83
  return f"""
84
+ Sei un assistente museale. Hai un elenco di Classi e Proprietà dell'ontologia:
 
 
 
85
 
86
  --- ONTOLOGIA ---
87
  {ont_text}
88
+ --- FINE ---
89
+
90
+ Regole Fondamentali:
91
+ 1) Se l'utente fa una domanda correlata a queste Classi/Proprietà, genera SEMPRE una query SPARQL
92
+ in UNA SOLA RIGA, con prefix:
93
+ PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
94
+ 2) Se la query produce 0 risultati o è invalida, devi fare un secondo tentativo (magari con FILTER).
95
+ 3) Se è una domanda generica (es. come stai?), rispondi breve.
96
+ 4) Se trovi risultati, la risposta finale è la query SPARQL su una singola riga.
97
+ 5) Se non trovi nulla, di' "Nessuna info".
98
+ 6) Non scrivere risposte multiline per la query. UNA SOLA RIGA.
 
99
 
100
  FINE REGOLE
101
  """
102
 
103
  def create_explanation_prompt(results_str:str)->str:
104
  return f"""
105
+ Ho ottenuto questi risultati SPARQL:
106
  {results_str}
107
 
108
+ Fornisci una breve spiegazione museale (massimo 10 righe), coerente e senza inventare.
 
109
  """
110
 
111
  async def call_hf_model(messages, temperature=0.7, max_tokens=1024)->str:
112
+ logger.debug("Chiamo HF con i seguenti messaggi:")
113
  for m in messages:
114
+ logger.debug(f"ROLE={m['role']} => {m['content'][:300]}")
115
  try:
116
+ resp = client.chat.completions.create(
117
  model=HF_MODEL,
118
  messages=messages,
119
  temperature=temperature,
 
123
  raw=resp["choices"][0]["message"]["content"]
124
  return raw.replace("\n"," ").strip()
125
  except Exception as e:
126
+ logger.error(f"HuggingFace error: {e}")
127
  raise HTTPException(status_code=500, detail=str(e))
128
 
129
+ from fastapi import FastAPI
130
+
131
  app=FastAPI()
132
 
133
  class QueryRequest(BaseModel):
 
138
  @app.post("/generate-response/")
139
  async def generate_response(req:QueryRequest):
140
  user_input=req.message
141
+ logger.info(f"Utente dice: {user_input}")
142
+
143
  sys_msg=create_system_message(knowledge_text)
144
  msgs=[
145
  {"role":"system","content":sys_msg},
146
  {"role":"user","content":user_input}
147
  ]
148
+ first=await call_hf_model(msgs, req.temperature, req.max_tokens)
149
+ logger.info(f"PRIMA RISPOSTA:\n{first}")
 
150
 
151
+ if not first.startswith("PREFIX base:"):
152
+ second_msg=f"Non hai fatto query SPARQL su una riga. Ritenta. Domanda: {user_input}"
 
153
  msgs2=[
154
  {"role":"system","content":sys_msg},
155
+ {"role":"assistant","content":first},
156
+ {"role":"user","content":second_msg}
157
  ]
158
+ second=await call_hf_model(msgs2, req.temperature, req.max_tokens)
159
+ logger.info(f"SECONDA RISPOSTA:\n{second}")
160
+ if second.startswith("PREFIX base:"):
161
+ sparql_query=second
162
  else:
163
+ return {"type":"NATURAL","response": second}
164
  else:
165
+ sparql_query=first
166
 
167
+ # Eseguiamo la query
168
+ import rdflib
169
  g=rdflib.Graph()
170
  try:
171
  g.parse(RDF_FILE,format="xml")
172
  except Exception as e:
173
+ logger.error(f"Parse error: {e}")
174
+ return {"type":"ERROR","response":"Parsing RDF error"}
175
+
176
  try:
177
  results=g.query(sparql_query)
178
  except Exception as e:
179
  # fallback
180
+ fallback=f"Query fallita. Riprova. Domanda: {user_input}"
181
  msgs3=[
182
  {"role":"system","content":sys_msg},
183
  {"role":"assistant","content":sparql_query},
184
  {"role":"user","content":fallback}
185
  ]
186
+ res3=await call_hf_model(msgs3,req.temperature,req.max_tokens)
187
+ if res3.startswith("PREFIX base:"):
188
+ sparql_query=res3
189
  try:
190
  results=g.query(sparql_query)
191
  except Exception as e2:
192
  return {"type":"ERROR","response":f"Query fallita ancora: {e2}"}
193
  else:
194
+ return {"type":"NATURAL","response":res3}
195
 
196
  if len(results)==0:
197
  return {"type":"NATURAL","sparql_query":sparql_query,"response":"Nessun risultato."}
198
 
 
199
  row_list=[]
200
  for row in results:
201
+ row_str=", ".join([f"{k}:{v}" for k,v in row.asdict().items()])
202
+ row_list.append(row_str)
203
  results_str="\n".join(row_list)
204
 
205
+ # Spiegazione
206
  exp_prompt=create_explanation_prompt(results_str)
207
+ msgs4=[
208
  {"role":"system","content":exp_prompt},
209
  {"role":"user","content":""}
210
  ]
211
+ explanation=await call_hf_model(msgs4,req.temperature,req.max_tokens)
212
 
213
  return {
214
  "type":"NATURAL",
 
219
 
220
  @app.get("/")
221
  def home():
222
+ return {"message":"Ok con sole classi e proprietà. Se l'utente cerca istanze, non le trova."}