|
import os |
|
import logging |
|
from typing import Optional |
|
from pydantic import BaseModel |
|
from fastapi import FastAPI, HTTPException |
|
from huggingface_hub import InferenceClient |
|
import rdflib |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.DEBUG, |
|
format="%(asctime)s - %(levelname)s - %(message)s", |
|
handlers=[ |
|
logging.FileHandler("app.log"), |
|
logging.StreamHandler() |
|
] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
API_KEY = os.getenv("HF_API_KEY") |
|
if not API_KEY: |
|
logger.error("HF_API_KEY non impostata nell'ambiente.") |
|
raise EnvironmentError("HF_API_KEY non impostata nell'ambiente.") |
|
|
|
|
|
RDF_FILE = "Ontologia.rdf" |
|
|
|
|
|
client = InferenceClient(api_key=API_KEY) |
|
|
|
|
|
|
|
|
|
def load_ontology_as_text(rdf_file: str, max_triples: int = 300) -> str: |
|
""" |
|
Legge l'ontologia dal file RDF e costruisce |
|
una stringa "knowledge_text" con un numero limitato di triple, |
|
per non sforare i limiti di contesto. |
|
max_triples limita quante triple includere. |
|
""" |
|
if not os.path.exists(rdf_file): |
|
return "Nessun file RDF trovato." |
|
|
|
g = rdflib.Graph() |
|
try: |
|
g.parse(rdf_file, format="xml") |
|
except Exception as e: |
|
logger.error(f"Errore parsing RDF: {e}") |
|
return "Errore parsing RDF." |
|
|
|
|
|
knowledge_lines = [] |
|
count = 0 |
|
for s, p, o in g: |
|
|
|
if count >= max_triples: |
|
break |
|
|
|
s_str = str(s)[:200] |
|
p_str = str(p)[:200] |
|
o_str = str(o)[:200] |
|
line = f"- {s_str} | {p_str} | {o_str}" |
|
knowledge_lines.append(line) |
|
count += 1 |
|
|
|
knowledge_text = "\n".join(knowledge_lines) |
|
return knowledge_text |
|
|
|
|
|
knowledge_text = load_ontology_as_text(RDF_FILE, max_triples=600) |
|
|
|
|
|
|
|
|
|
def create_system_message(knowledge_text: str) -> str: |
|
""" |
|
Crea un prompt di sistema con l'ontologia (o una versione ridotta) inline, |
|
obbligando il modello a cercare le info per la query SPARQL da qui. |
|
""" |
|
|
|
|
|
system_message = f""" |
|
Sei un assistente per un museo. Qui hai l'estratto dell'ontologia RDF in formato triple: |
|
(EntitaSoggetto, EntitaProprieta, EntitaOggetto). |
|
|
|
Devi usare ESCLUSIVAMENTE queste informazioni come knowledge base. |
|
|
|
Ecco la knowledge in forma di triple (max 600): |
|
{knowledge_text} |
|
|
|
REGOLE TASSATIVE: |
|
1. Se la domanda dell'utente richiede info su entità/opere ecc., DEVI generare una query SPARQL che si basa su questi triple. |
|
2. Se la domanda è chat semplice (saluto, ecc.), puoi rispondere brevemente, ma se serve una knowledge, la cerchi nei triple. |
|
3. Se i triple non contengono l'info, di' che non è disponibile. |
|
4. Il prefisso da usare: |
|
PREFIX base: <http://www.semanticweb.org/lucreziamosca/ontologies/progettoMuseo#> |
|
|
|
5. Se hai generato la query e bisogna interpretare i risultati (es. autoreOpera?), puoi fornire la risposta. |
|
Ma l'info deve derivare dai triple sopra. |
|
|
|
Attenzione al fatto che le stringhe (es. <nomeOpera> ) possono avere @it o differenze minuscole/maiuscole. |
|
Puoi usare FILTER( STR(?x) = "valore" ) se serve. |
|
|
|
Buona fortuna! |
|
""" |
|
return system_message |
|
|
|
|
|
|
|
|
|
async def call_model(messages, temperature=0.7, max_tokens=2048): |
|
logger.info("Chiamata al modello iniziata.") |
|
try: |
|
response = client.chat.completions.create( |
|
model="Qwen/Qwen2.5-72B-Instruct", |
|
messages=messages, |
|
temperature=temperature, |
|
max_tokens=max_tokens, |
|
top_p=0.9, |
|
stream=False |
|
) |
|
raw_text = response["choices"][0]["message"]["content"] |
|
logger.debug(f"Risposta del modello: {raw_text}") |
|
|
|
return raw_text.replace("\n", " ").strip() |
|
except Exception as e: |
|
logger.error(f"Errore durante la chiamata al modello: {e}") |
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI |
|
|
|
app = FastAPI() |
|
|
|
class QueryRequest(BaseModel): |
|
message: str |
|
max_tokens: int = 1024 |
|
temperature: float = 0.5 |
|
|
|
@app.post("/generate-response/") |
|
async def generate_response(req: QueryRequest): |
|
user_msg = req.message |
|
logger.info(f"Ricevuta richiesta: {user_msg}") |
|
|
|
|
|
system_msg = create_system_message(knowledge_text) |
|
|
|
messages = [ |
|
{"role": "system", "content": system_msg}, |
|
{"role": "user", "content": user_msg} |
|
] |
|
|
|
|
|
response_text = await call_model( |
|
messages, |
|
temperature=req.temperature, |
|
max_tokens=req.max_tokens |
|
) |
|
|
|
logger.info(f"Risposta generata dal modello: {response_text}") |
|
|
|
|
|
|
|
|
|
return {"type": "NATURAL", "response": response_text} |
|
|
|
@app.get("/") |
|
async def root(): |
|
return {"message": "Server con ontologia in prompt di sistema!"} |
|
|