AshenClock commited on
Commit
89f944e
·
verified ·
1 Parent(s): 059bef5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -215
app.py CHANGED
@@ -1,163 +1,117 @@
1
  import os
2
  import logging
3
- from rdflib import Graph
4
  from pydantic import BaseModel
5
  from fastapi import FastAPI, HTTPException
6
  from huggingface_hub import InferenceClient
7
- from typing import Optional
8
 
9
  # Configurazione logging
10
  logging.basicConfig(
11
- level=logging.DEBUG, # Livello di log aumentato per maggiori dettagli
12
  format="%(asctime)s - %(levelname)s - %(message)s",
13
  handlers=[
14
- logging.FileHandler("app.log"), # Log su file
15
- logging.StreamHandler() # Log su console
16
  ]
17
  )
18
  logger = logging.getLogger(__name__)
19
 
20
- # Configurazione API Hugging Face
 
 
21
  API_KEY = os.getenv("HF_API_KEY")
22
  if not API_KEY:
23
  logger.error("HF_API_KEY non impostata nell'ambiente.")
24
  raise EnvironmentError("HF_API_KEY non impostata nell'ambiente.")
25
 
26
- client = InferenceClient(api_key=API_KEY)
27
-
28
- # File RDF
29
  RDF_FILE = "Ontologia.rdf"
30
 
31
- ####################################
32
- # Caricamento RDF (riassunto)
33
- ####################################
34
- def load_rdf_summary() -> str:
 
 
 
35
  """
36
- Carica un riassunto dell'ontologia dal file RDF.
37
- Estrae le classi e le proprietà presenti nell'ontologia.
 
 
38
  """
39
- logger.info("Inizio caricamento del file RDF.")
40
- if not os.path.exists(RDF_FILE):
41
- logger.error("Nessun file RDF trovato.")
42
  return "Nessun file RDF trovato."
43
 
 
44
  try:
45
- g = Graph()
46
- g.parse(RDF_FILE, format="xml")
47
-
48
- classes = set()
49
- properties = set()
50
- for s, p, o in g.triples((None, None, None)):
51
- if "Class" in str(o):
52
- classes.add(s)
53
- if "Property" in str(o):
54
- properties.add(s)
55
-
56
- class_summary = "\n".join([f"- Classe: {cls}" for cls in classes])
57
- prop_summary = "\n".join([f"- Proprietà: {prop}" for prop in properties])
58
-
59
- summary = f"Classi:\n{class_summary}\n\nProprietà:\n{prop_summary}"
60
- logger.info("Caricamento RDF completato con successo.")
61
- return summary
62
  except Exception as e:
63
- logger.error(f"Errore durante il parsing del file RDF: {e}")
64
- return "Errore nel caricamento del file RDF."
65
-
66
- rdf_context = load_rdf_summary()
67
- logger.debug(f"RDF Summary:\n{rdf_context}")
68
-
69
- ####################################
70
- # Validazione SPARQL
71
- ####################################
72
- def validate_sparql_query(query: str, rdf_file_path: str) -> bool:
73
- """
74
- Verifica la validità della query SPARQL.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  """
76
- logger.info("Inizio validazione della query SPARQL.")
77
- g = Graph()
78
- try:
79
- g.parse(rdf_file_path, format="xml")
80
- g.query(query) # Solleva un'eccezione se la query non è valida
81
- logger.info("Validazione della query SPARQL riuscita.")
82
- return True
83
- except Exception as e:
84
- logger.error(f"Errore durante la validazione della query SPARQL: {e}")
85
- return False
86
-
87
- ####################################
88
- # Prompt di Sistema (Rafforzato)
89
- ####################################
90
- def create_system_message(rdf_context: str) -> str:
91
  """
92
- Crea il messaggio di sistema per il modello di linguaggio naturale.
93
- Abbiamo rafforzato le istruzioni per gestire i literal con @it.
94
- """
95
- return f"""
96
- Sei un'assistente esperta nella generazione di query SPARQL basate su un'ontologia RDF,
97
- nell'interpretazione dei risultati delle query SPARQL in risposte naturali,
98
- e nel fare chatting minimale con i visitatori in diverse lingue (italiano, francese, inglese).
99
-
100
- In base alla domanda dell'utente, devi decidere se:
101
- 1) generare una query SPARQL
102
- 2) fornire una risposta naturale basata su una query SPARQL
103
- 3) rispondere con una chat minimale.
104
-
105
- ATTENZIONE IMPORTANTE:
106
- - L'ontologia è su: http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#
107
- - Alcuni literal (es. 'nomeOpera') hanno il tag di lingua '@it' (es. 'Amore e Psiche'@it).
108
- - Se vuoi confrontare una stringa come 'Amore e Psiche', DEVI usare:
109
- - base:nomeOpera 'Amore e Psiche'@it
110
- OPPURE
111
- - un FILTER(STR(?label) = 'Amore e Psiche')
112
- per ignorare il tag di lingua.
113
- - Se manca '@it', potresti non trovare risultati.
114
-
115
- Regole TASSATIVE:
116
- 1. Se la domanda richiede una query SPARQL, restituisci SOLO la query SPARQL in testo semplice.
117
- 2. Se la domanda richiede interpretazione di risultati, dai una risposta naturale che spieghi quei risultati.
118
- 3. Se la domanda è una chat minimale, rispondi con una breve chat amichevole nella lingua dell'utente.
119
- 4. DEVI usare ESCLUSIVAMENTE questo prefisso di base (e NON modificarlo in nessun modo):
120
  PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
121
- 5. NON generare alcun altro prefisso o URI inventato.
122
- 6. Se non riesci a rispondere con una query SPARQL, interpretare i risultati o fare chat,
123
- scrivi: "Non posso generare una query SPARQL, interpretare i risultati o fare una risposta di chat per questa richiesta."
124
 
125
- Esempi:
126
- - Domanda: "Quali sono le statue esposte del periodo medievale?"
127
- Risposta SPARQL:
128
- PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
129
- SELECT ?statua WHERE {{
130
- ?statua a base:Statua ;
131
- base:periodoStoricoOpera "Medioevo"@it .
132
- }}
133
 
134
- - Domanda: "Ciao!"
135
- Risposta (chat):
136
- Ciao! Benvenuto al nostro museo. Come posso aiutarti oggi?
137
 
138
- - Domanda: "Da chi è stato scolpito Amore e Psiche?"
139
- (Se devi confrontare 'Amore e Psiche' con tag @it)
140
- Risposta SPARQL:
141
- PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
142
- SELECT ?autore WHERE {{
143
- ?opera a base:Statua ;
144
- base:nomeOpera "Amore e Psiche"@it ;
145
- base:autoreOpera ?autore .
146
- }}
147
-
148
- Ecco un riassunto dell'ontologia su cui devi lavorare:
149
- {rdf_context}
150
-
151
- RISPONDI ESCLUSIVAMENTE CON IL FORMATO SPECIFICATO.
152
- """
153
 
154
- ####################################
155
- # Funzione per chiamare il modello
156
- ####################################
157
  async def call_model(messages, temperature=0.7, max_tokens=2048):
158
- """
159
- Chiama il modello di linguaggio naturale con i messaggi forniti.
160
- """
161
  logger.info("Chiamata al modello iniziata.")
162
  try:
163
  response = client.chat.completions.create(
@@ -165,120 +119,56 @@ async def call_model(messages, temperature=0.7, max_tokens=2048):
165
  messages=messages,
166
  temperature=temperature,
167
  max_tokens=max_tokens,
168
- top_p=0.7,
169
  stream=False
170
  )
171
  raw_text = response["choices"][0]["message"]["content"]
172
- logger.debug(f"Risposta del modello ricevuta: {raw_text}")
173
- # Rimuoviamo eventuali newline per forzare una singola riga
174
  return raw_text.replace("\n", " ").strip()
175
  except Exception as e:
176
  logger.error(f"Errore durante la chiamata al modello: {e}")
177
  raise HTTPException(status_code=500, detail=str(e))
178
 
179
- ####################################
180
- # Funzione di Interpretazione dei Risultati SPARQL
181
- ####################################
182
- async def interpret_sparql_results(results):
183
- """
184
- Invia i risultati delle query SPARQL al modello per ottenere una risposta naturale.
185
- """
186
- logger.info("Inizio interpretazione dei risultati SPARQL.")
187
- if not results:
188
- logger.info("Nessun risultato trovato per la query SPARQL.")
189
- return "Mi dispiace, non sono riuscita a trovare le informazioni che stavi cercando."
190
-
191
- # Converti i risultati in una stringa leggibile
192
- results_str = "\n".join([
193
- ", ".join([f"{k}: {v}" for k, v in row.asdict().items()])
194
- for row in results
195
- ])
196
- logger.debug(f"Risultati SPARQL:\n{results_str}")
197
-
198
- # Prompt per interpretare i risultati come una guida museale femminile
199
- interpret_prompt = f"""
200
- Mi hai fornito i seguenti risultati di una query SPARQL:
201
- {results_str}
202
-
203
- Per favore, interpreta questi risultati e fornisci una risposta naturale ed enfatica
204
- come farebbe una guida museale femminile (in italiano).
205
- """
206
 
207
- messages = [
208
- {"role": "system", "content": interpret_prompt},
209
- {"role": "user", "content": ""}
210
- ]
211
- logger.info("Invio dei risultati SPARQL al modello per l'interpretazione.")
212
- natural_response = await call_model(messages, temperature=0.7, max_tokens=2048)
213
- logger.info(f"Risposta interpretata ricevuta dal modello: {natural_response}")
214
- return natural_response
215
-
216
- ####################################
217
- # FastAPI
218
- ####################################
219
  app = FastAPI()
220
 
221
  class QueryRequest(BaseModel):
222
  message: str
223
- max_tokens: int = 2048
224
- temperature: float = 0.7
225
 
226
  @app.post("/generate-response/")
227
- async def generate_response(request: QueryRequest):
228
- user_msg = request.message
229
  logger.info(f"Ricevuta richiesta: {user_msg}")
230
 
231
- # 1) Generazione della risposta (SPARQL, INTERPRET o CHAT)
232
- system_msg = create_system_message(rdf_context)
 
233
  messages = [
234
  {"role": "system", "content": system_msg},
235
  {"role": "user", "content": user_msg}
236
  ]
237
- response_text = await call_model(messages, request.temperature, request.max_tokens)
238
- logger.info(f"Risposta generata dal modello: {response_text}")
239
 
240
- # 2) Determinazione se la risposta è una query SPARQL
241
- if response_text.startswith("PREFIX base:"):
242
- sparql_query = response_text
243
- logger.info("La risposta è stata identificata come una query SPARQL.")
 
 
244
 
245
- # Validazione della query SPARQL
246
- if validate_sparql_query(sparql_query, RDF_FILE):
247
- logger.info("La query SPARQL è valida. Inizio esecuzione della query.")
248
- try:
249
- g = Graph()
250
- g.parse(RDF_FILE, format="xml")
251
- results = g.query(sparql_query)
252
- logger.info(f"Query SPARQL eseguita con successo. Numero di risultati: {len(results)}")
253
-
254
- # Interpreta i risultati in una risposta naturale
255
- interpreted_response = await interpret_sparql_results(results)
256
- logger.info(f"Risposta naturale interpretata: {interpreted_response}")
257
- return {"type": "NATURAL", "response": interpreted_response}
258
-
259
- except Exception as e:
260
- logger.error(f"Errore durante l'esecuzione della query SPARQL: {e}")
261
- return {
262
- "type": "ERROR",
263
- "response": "Mi dispiace, c'è stato un errore nell'esecuzione della tua richiesta."
264
- }
265
- else:
266
- logger.warning("La query SPARQL generata non è valida.")
267
- return {
268
- "type": "ERROR",
269
- "response": "La query SPARQL generata non è valida. Per favore, riprova con una domanda diversa."
270
- }
271
-
272
- elif "Non posso generare una query SPARQL" in response_text:
273
- # Risposta di errore dal modello
274
- logger.warning("Il modello ha risposto con un messaggio di errore.")
275
- return {"type": "ERROR", "response": response_text}
276
 
277
- else:
278
- # Presumiamo che sia una risposta naturale o di chat
279
- logger.info("La risposta è stata identificata come una risposta naturale o di chat.")
280
- return {"type": "NATURAL", "response": response_text}
281
 
282
  @app.get("/")
283
  async def root():
284
- return {"message": "Server attivo e pronto a generare risposte!"}
 
1
  import os
2
  import logging
3
+ from typing import Optional
4
  from pydantic import BaseModel
5
  from fastapi import FastAPI, HTTPException
6
  from huggingface_hub import InferenceClient
7
+ import rdflib
8
 
9
  # Configurazione logging
10
  logging.basicConfig(
11
+ level=logging.DEBUG,
12
  format="%(asctime)s - %(levelname)s - %(message)s",
13
  handlers=[
14
+ logging.FileHandler("app.log"),
15
+ logging.StreamHandler()
16
  ]
17
  )
18
  logger = logging.getLogger(__name__)
19
 
20
+ # =========================================
21
+ # PARAMETRI BASE
22
+ # =========================================
23
  API_KEY = os.getenv("HF_API_KEY")
24
  if not API_KEY:
25
  logger.error("HF_API_KEY non impostata nell'ambiente.")
26
  raise EnvironmentError("HF_API_KEY non impostata nell'ambiente.")
27
 
28
+ # Nome del file RDF
 
 
29
  RDF_FILE = "Ontologia.rdf"
30
 
31
+ # Inizializza il client
32
+ client = InferenceClient(api_key=API_KEY)
33
+
34
+ # =========================================
35
+ # Carica e Preprocessa l'Ontologia
36
+ # =========================================
37
+ def load_ontology_as_text(rdf_file: str, max_triples: int = 300) -> str:
38
  """
39
+ Legge l'ontologia dal file RDF e costruisce
40
+ una stringa "knowledge_text" con un numero limitato di triple,
41
+ per non sforare i limiti di contesto.
42
+ max_triples limita quante triple includere.
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 parsing RDF."
53
+
54
+ # Convertiamo un certo numero di triple in stringa
55
+ knowledge_lines = []
56
+ count = 0
57
+ for s, p, o in g:
58
+ # Troncamento se necessario (evita di incollare 10.000 triple)
59
+ if count >= max_triples:
60
+ break
61
+ # Abbreviamo s, p, o se sono lunghi
62
+ s_str = str(s)[:200]
63
+ p_str = str(p)[:200]
64
+ o_str = str(o)[:200]
65
+ line = f"- {s_str} | {p_str} | {o_str}"
66
+ knowledge_lines.append(line)
67
+ count += 1
68
+
69
+ knowledge_text = "\n".join(knowledge_lines)
70
+ return knowledge_text
71
+
72
+ # Carichiamo e preprocessiamo la knowledge
73
+ knowledge_text = load_ontology_as_text(RDF_FILE, max_triples=600) # Aumenta/diminuisci se serve
74
+
75
+ # =========================================
76
+ # Creazione Prompt di Sistema
77
+ # =========================================
78
+ def create_system_message(knowledge_text: str) -> str:
79
  """
80
+ Crea un prompt di sistema con l'ontologia (o una versione ridotta) inline,
81
+ obbligando il modello a cercare le info per la query SPARQL da qui.
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  """
83
+ # Nota: qui rafforziamo che deve SEMPRE consultare questi triple.
84
+ # E deve generare query SPARQL SOLO su questi triple, etc.
85
+ system_message = f"""
86
+ Sei un assistente per un museo. Qui hai l'estratto dell'ontologia RDF in formato triple:
87
+ (EntitaSoggetto, EntitaProprieta, EntitaOggetto).
88
+
89
+ Devi usare ESCLUSIVAMENTE queste informazioni come knowledge base.
90
+
91
+ Ecco la knowledge in forma di triple (max 600):
92
+ {knowledge_text}
93
+
94
+ REGOLE TASSATIVE:
95
+ 1. Se la domanda dell'utente richiede info su entità/opere ecc., DEVI generare una query SPARQL che si basa su questi triple.
96
+ 2. Se la domanda è chat semplice (saluto, ecc.), puoi rispondere brevemente, ma se serve una knowledge, la cerchi nei triple.
97
+ 3. Se i triple non contengono l'info, di' che non è disponibile.
98
+ 4. Il prefisso da usare:
 
 
 
 
 
 
 
 
 
 
 
 
99
  PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#>
 
 
 
100
 
101
+ 5. Se hai generato la query e bisogna interpretare i risultati (es. autoreOpera?), puoi fornire la risposta.
102
+ Ma l'info deve derivare dai triple sopra.
 
 
 
 
 
 
103
 
104
+ Attenzione al fatto che le stringhe (es. <nomeOpera> ) possono avere @it o differenze minuscole/maiuscole.
105
+ Puoi usare FILTER( STR(?x) = "valore" ) se serve.
 
106
 
107
+ Buona fortuna!
108
+ """
109
+ return system_message
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ # =========================================
112
+ # Funzione che chiama il modello
113
+ # =========================================
114
  async def call_model(messages, temperature=0.7, max_tokens=2048):
 
 
 
115
  logger.info("Chiamata al modello iniziata.")
116
  try:
117
  response = client.chat.completions.create(
 
119
  messages=messages,
120
  temperature=temperature,
121
  max_tokens=max_tokens,
122
+ top_p=0.9, # puoi settare come vuoi
123
  stream=False
124
  )
125
  raw_text = response["choices"][0]["message"]["content"]
126
+ logger.debug(f"Risposta del modello: {raw_text}")
127
+ # Togli newline
128
  return raw_text.replace("\n", " ").strip()
129
  except Exception as e:
130
  logger.error(f"Errore durante la chiamata al modello: {e}")
131
  raise HTTPException(status_code=500, detail=str(e))
132
 
133
+ # =========================================
134
+ # FASTAPI
135
+ # =========================================
136
+ from fastapi import FastAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  app = FastAPI()
139
 
140
  class QueryRequest(BaseModel):
141
  message: str
142
+ max_tokens: int = 1024
143
+ temperature: float = 0.5
144
 
145
  @app.post("/generate-response/")
146
+ async def generate_response(req: QueryRequest):
147
+ user_msg = req.message
148
  logger.info(f"Ricevuta richiesta: {user_msg}")
149
 
150
+ # 1) Creiamo un system_msg che contiene TUTTI i triple (preprocessati)
151
+ system_msg = create_system_message(knowledge_text)
152
+
153
  messages = [
154
  {"role": "system", "content": system_msg},
155
  {"role": "user", "content": user_msg}
156
  ]
 
 
157
 
158
+ # 2) Chiamiamo il modello
159
+ response_text = await call_model(
160
+ messages,
161
+ temperature=req.temperature,
162
+ max_tokens=req.max_tokens
163
+ )
164
 
165
+ logger.info(f"Risposta generata dal modello: {response_text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
+ # (Opzionale) Se vuoi, qui potresti controllare se la risposta inizia con "PREFIX base:"
168
+ # e se sì, eseguire su un Graph locale la query, interpretarla, ecc.
169
+ # Ma in questo esempio, ci fermiamo e inviamo la raw response:
170
+ return {"type": "NATURAL", "response": response_text}
171
 
172
  @app.get("/")
173
  async def root():
174
+ return {"message": "Server con ontologia in prompt di sistema!"}