File size: 8,835 Bytes
034a1e6
fe3bdda
034a1e6
 
ee1f7e6
fe3bdda
4c52594
fe3bdda
4c52594
 
1575481
fe3bdda
034a1e6
 
 
fe3bdda
21119e3
034a1e6
fe3bdda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70bec3c
fe3bdda
 
 
 
70bec3c
034a1e6
a1f8fd1
fe3bdda
034a1e6
8c772f5
4c52594
034a1e6
 
fe3bdda
 
 
70bec3c
c191446
fe3bdda
 
 
 
 
c191446
034a1e6
70bec3c
3031ece
034a1e6
e623027
c191446
 
 
 
 
 
 
 
 
 
 
 
 
 
70bec3c
c191446
fe3bdda
baa2201
fe3bdda
70bec3c
fe3bdda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70bec3c
034a1e6
baa2201
034a1e6
3b0ec68
 
 
 
ee1f7e6
034a1e6
70bec3c
fe3bdda
70bec3c
034a1e6
70bec3c
 
 
fe3bdda
 
 
70bec3c
 
 
 
 
 
aa07e9d
034a1e6
 
70bec3c
 
 
fe3bdda
 
70bec3c
fe3bdda
70bec3c
fe3bdda
70bec3c
 
fe3bdda
 
 
 
 
 
 
 
 
70bec3c
 
 
fe3bdda
 
 
 
 
 
 
70bec3c
fe3bdda
70bec3c
 
fe3bdda
 
 
 
 
 
 
 
 
 
 
70bec3c
 
 
 
 
 
 
 
1575481
034a1e6
fe3bdda
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
import os
import logging
from rdflib import Graph
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException
from huggingface_hub import InferenceClient

# Configurazione logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# Configurazione API Hugging Face
API_KEY = os.getenv("HF_API_KEY")
client = InferenceClient(api_key=API_KEY)

# File RDF
RDF_FILE = "Ontologia.rdf"

####################################
# Caricamento RDF (riassunto)
####################################
def load_rdf_summary():
    """
    Carica un riassunto dell'ontologia dal file RDF (se necessario).
    Qui puoi usare parse e scansionare classi e proprietà reali.
    """
    if not os.path.exists(RDF_FILE):
        return "Nessun file RDF trovato."
    try:
        g = Graph()
        g.parse(RDF_FILE, format="xml")
        
        # Esempio di estrazione semplificata di classi e proprietà
        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])
        return f"Classi:\n{class_summary}\n\nProprietà:\n{prop_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("RDF Summary: %s", rdf_context)

####################################
# Validazione SPARQL
####################################
def validate_sparql_query(query: str, rdf_file_path: str) -> bool:
    """
    Esegue il parsing e l'esecuzione di test della query su RDF,
    per verificare che sia sintatticamente e semanticamente corretta.
    """
    g = Graph()
    try:
        g.parse(rdf_file_path, format="xml")
        g.query(query)  # Se c'è errore di sintassi o referenza, solleva eccezione
        return True
    except Exception as e:
        logger.error(f"Errore durante la validazione della query SPARQL: {e}")
        return False

####################################
# Prompt di Sistema molto stringente
####################################
def create_system_message(rdf_context: str) -> str:
    """
    Prompt di sistema estremo:
    - impone l'uso di un SOLO prefisso
    - vieta righe multiple
    - vieta di inventare prefissi
    - obbliga a iniziare con `PREFIX base: ... SELECT` o `ASK`
    """
    return f"""
Sei un assistente esperto nella generazione di query SPARQL basate su un'ontologia RDF.
Ecco un riassunto dell'ontologia su cui devi lavorare:
{rdf_context}

DI SEGUITO LE REGOLE TASSATIVE:
1. 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/>
2. La query deve stare in UNA SOLA RIGA, senza andare a capo.
3. La query deve INIZIARE con:
   PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> SELECT
   oppure
   PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> ASK
4. Se devi indicare una classe, usa: ?qualcosa a base:NomeClasse .
5. Se devi indicare una proprietà, usa: ?s base:NomeProprieta ?o .
6. NON generare alcun altro prefisso.
7. NON utilizzare URI lunghe senza < > e NON inventare prefissi o risorse inesistenti.
8. Se non puoi rispondere con una query SPARQL valida secondo questi criteri, scrivi:
   "Non posso generare una query SPARQL per questa richiesta."

Esempio di query corretta (fittizia) in una sola riga:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/> SELECT ?stanza WHERE {{ ?stanza a base:Stanza . }} LIMIT 10

RISPONDI ESCLUSIVAMENTE CON LA QUERY O IL MESSAGGIO DI IMPOSSIBILITA'.
"""

####################################
# Prompt di "correzione"
####################################
def create_correction_message(rdf_context: str, errore: str) -> str:
    """
    Questo prompt serve per la seconda iterazione se la query non è valida.
    Invita a correggere la query e a rispettare le regole.
    """
    return f"""
La query che hai fornito è risultata NON valida per il seguente motivo:
{errore}

RICORDA LE REGOLE TASSATIVE, in particolare l'uso ESATTO del prefisso:
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/>
Riscrivi la query in UNA SOLA RIGA, rispettando la sintassi SPARQL e usando solo classi e proprietà presenti nell'ontologia.
Se non riesci, dì: "Non posso generare una query SPARQL per questa richiesta."
"""

####################################
# Funzione per chiamare il modello
####################################
async def call_model(messages, temperature=0.7, max_tokens=2048):
    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"]
        # Rimuoviamo eventuali newline per forzare la singola riga
        return raw_text.replace("\n", " ").strip()
    except Exception as e:
        logger.error(f"Errore nel modello: {e}")
        raise HTTPException(status_code=500, detail=str(e))

####################################
# FastAPI
####################################
app = FastAPI()

class QueryRequest(BaseModel):
    message: str
    max_tokens: int = 2048
    temperature: float = 0.7

@app.post("/generate-query/")
async def generate_query(request: QueryRequest):
    # 1) Prima iterazione
    system_msg = create_system_message(rdf_context)
    user_msg = request.message
    
    messages_first = [
        {"role": "system", "content": system_msg},
        {"role": "user", "content": user_msg}
    ]
    response1 = await call_model(messages_first, request.temperature, request.max_tokens)
    logger.info(f"[Prima iterazione] Risposta generata dal modello: {response1}")

    # Controllo se comincia con il prefisso esatto
    mandated_prefix = "PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/2024/11/untitled-ontology-39/>"
    if not (response1.startswith(mandated_prefix + " SELECT") or response1.startswith(mandated_prefix + " ASK")):
        return {
            "query": None,
            "explanation": "Il modello non ha usato il prefisso obbligatorio o non ha usato SELECT/ASK."
        }

    # Verifichiamo se la query è valida
    if validate_sparql_query(response1, RDF_FILE):
        return {"query": response1, "explanation": "Query valida alla prima iterazione."}
    else:
        # 2) Seconda iterazione (correzione)
        correction_msg = create_correction_message(rdf_context, "Query non valida alla prima iterazione.")
        # Comunichiamo al modello la query precedente come contesto e chiediamo la correzione
        messages_second = [
            {"role": "system", "content": system_msg},          # Prompt di sistema invariato
            {"role": "assistant", "content": response1},        # La risposta 'errata'
            {"role": "system", "content": correction_msg}       # Istruzione di correzione
        ]
        response2 = await call_model(messages_second, request.temperature, request.max_tokens)
        logger.info(f"[Seconda iterazione] Risposta generata dal modello: {response2}")

        # Ricontrollo se comincia con il prefisso esatto
        if not (response2.startswith(mandated_prefix + " SELECT") and not response2.startswith(mandated_prefix + " ASK")):
            # O se manca la SELECT e l'ASK
            # Con un piccolo fix: potresti voler controllare sia SELECT che ASK qui
            if not (response2.startswith(mandated_prefix + " SELECT") or response2.startswith(mandated_prefix + " ASK")):
                return {
                    "query": None,
                    "explanation": "Anche la seconda iterazione non ha usato il prefisso e SELECT/ASK corretti."
                }

        # Validazione della seconda risposta
        if validate_sparql_query(response2, RDF_FILE):
            return {"query": response2, "explanation": "Query valida alla seconda iterazione (corretta)."}
        else:
            return {
                "query": None,
                "explanation": "Anche la seconda iterazione ha prodotto una query non valida. Interrompo."
            }

@app.get("/")
async def root():
    return {"message": "Server attivo e pronto a generare query SPARQL!"}