import json import os from pathlib import Path from huggingface_hub import InferenceClient from rdflib import Graph from pydantic import BaseModel from fastapi import FastAPI, HTTPException import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) API_KEY = os.getenv("HF_API_KEY") client = InferenceClient(api_key=API_KEY) RDF_FILE = "Ontologia.rdf" # Funzione di validazione def validate_sparql_query(query: str, rdf_file_path: str) -> bool: g = Graph() try: g.parse(rdf_file_path, format="xml") g.query(query) # Se c'è un 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 "stringente" def create_system_message(rdf_context: str) -> str: """ Prompt di sistema estremamente rigido, che forza il modello a usare solo il prefisso definito e a generare la query in un'unica riga. """ 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: 2. La query deve stare in UNA SOLA RIGA, senza andare a capo. 3. La query deve INIZIARE con: PREFIX base: SELECT oppure PREFIX base: 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: SELECT ?stanza WHERE { ?stanza a base:Stanza . } LIMIT 10 RISPONDI ESCLUSIVAMENTE CON LA QUERY O IL MESSAGGIO DI IMPOSSIBILITA'. NON SCRIVERE NESSUN COMMENTO AGGIUNTIVO. """ # Funzione per chiamare il modello su Hugging Face 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"] 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)) # Caricamento di un riassunto dell'ontologia (semplificato per brevità) def load_rdf_summary() -> str: # Qui un caricamento minimo o un testo statico return "Classi e proprietà dell'ontologia: base:Stanza, base:Contiene, base:Opera, ecc." app = FastAPI() rdf_context = load_rdf_summary() 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 = [ {"role": "system", "content": system_msg}, {"role": "user", "content": user_msg}, ] response1 = await call_model(messages, request.temperature, request.max_tokens) logger.info(f"[Prima iterazione] Risposta generata dal modello: {response1}") # 2) Validazione if not (response1.startswith("PREFIX") and ("SELECT" in response1 or "ASK" in response1)): # Fallimento immediato return {"query": None, "explanation": "La query non rispetta le regole base (PREFIX + SELECT/ASK)."} if validate_sparql_query(response1, RDF_FILE): # Query valida! Restituisco la prima return {"query": response1, "explanation": "Query valida alla prima iterazione."} else: # 3) Seconda iterazione “correttiva” correction_msg = create_correction_message(rdf_context, "Errore di validazione (prima iterazione).") messages2 = [ {"role": "system", "content": system_msg}, # Sistema invariato {"role": "assistant", "content": response1}, # Metti la risposta errata in contesto {"role": "system", "content": correction_msg}, # Istruzione di correzione ] response2 = await call_model(messages2, request.temperature, request.max_tokens) logger.info(f"[Seconda iterazione] Risposta generata dal modello: {response2}") if not (response2.startswith("PREFIX") and ("SELECT" in response2 or "ASK" in response2)): return {"query": None, "explanation": "Anche la seconda query non rispetta le regole (PREFIX + SELECT/ASK)."} 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 per generare query SPARQL"}