File size: 4,607 Bytes
c17a53d
a6c1b66
 
26c4fd9
a6c1b66
26c4fd9
a6c1b66
c17a53d
a6c1b66
 
 
 
 
899531f
8c14e3d
a6c1b66
 
 
 
 
8c14e3d
26c4fd9
a6c1b66
 
 
 
 
 
 
 
 
 
 
 
26c4fd9
 
a6c1b66
26c4fd9
a6c1b66
8c14e3d
a6c1b66
 
7baa46e
a6c1b66
7baa46e
a6c1b66
8c14e3d
a6c1b66
26c4fd9
a6c1b66
 
 
 
26c4fd9
a6c1b66
 
26c4fd9
a6c1b66
3018f62
a6c1b66
 
 
 
 
 
3018f62
a6c1b66
 
 
 
 
26c4fd9
a6c1b66
 
 
 
 
 
26c4fd9
 
a6c1b66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26c4fd9
a6c1b66
 
bcce881
a6c1b66
 
 
 
 
 
 
 
 
 
26c4fd9
a6c1b66
 
 
 
 
 
 
26c4fd9
a6c1b66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import gradio as gr
import fitz  # PyMuPDF
import torch
import numpy as np
import os
from transformers import AutoTokenizer, AutoModelForQuestionAnswering, pipeline
from sentence_transformers import SentenceTransformer

# Configurazione ambiente
os.environ["GRADIO_SERVER_NAME"] = "0.0.0.0"
os.environ["GRADIO_SERVER_PORT"] = "7860"

# Costanti configurabili
PDF_PATH = "reg_2024.pdf"
EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
QA_MODEL = "mrm8488/bert-italian-finedtuned-squadv1-it-alfa"
CHUNK_SIZE = 800
OVERLAP = 150
TOP_K_CHUNKS = 3
MIN_SCORE = 0.1

def load_models():
    """Carica i modelli con gestione ottimizzata della memoria"""
    try:
        # Prova a caricare con GPU/quantizzazione
        model = AutoModelForQuestionAnswering.from_pretrained(
            QA_MODEL,
            device_map="auto",
            load_in_4bit=True if torch.cuda.is_available() else False,
            torch_dtype=torch.float16
        )
    except ImportError:
        # Fallback per CPU
        model = AutoModelForQuestionAnswering.from_pretrained(QA_MODEL)
    
    tokenizer = AutoTokenizer.from_pretrained(QA_MODEL)
    embedder = SentenceTransformer(EMBEDDING_MODEL)
    
    return model, tokenizer, embedder

def process_pdf():
    """Elabora il PDF e crea gli embeddings"""
    text = ""
    with fitz.open(PDF_PATH) as doc:
        for page in doc:
            text += page.get_text().replace("\n", " ") + " "
    
    # Suddivisione in chunk ottimizzata
    words = text.split()
    chunks = [
        ' '.join(words[i:i + CHUNK_SIZE]) 
        for i in range(0, len(words), CHUNK_SIZE - OVERLAP)
    ]
    
    # Calcola embeddings
    embeddings = model_embed.encode(chunks, convert_to_tensor=True)
    
    return chunks, embeddings

def semantic_search(query, chunks, embeddings):
    """Ricerca semantica dei chunk più rilevanti"""
    query_embed = model_embed.encode(query, convert_to_tensor=True)
    scores = torch.nn.functional.cosine_similarity(query_embed, embeddings)
    top_indices = torch.topk(scores, k=TOP_K_CHUNKS).indices.cpu().numpy()
    return [chunks[i] for i in top_indices]

def answer_question(question):
    """Pipeline completa per la risposta"""
    try:
        # Ricerca contestuale
        relevant_chunks = semantic_search(question, doc_chunks, doc_embeddings)
        
        # Inizializza pipeline QA
        qa_pipe = pipeline(
            "question-answering",
            model=model_qa,
            tokenizer=tokenizer,
            device=0 if torch.cuda.is_available() else -1
        )
        
        best_answer = {"answer": "Nessuna risposta trovata", "score": 0}
        
        # Analizza ogni chunk rilevante
        for chunk in relevant_chunks:
            try:
                result = qa_pipe(
                    question=question,
                    context=chunk,
                    max_answer_len=100,
                    handle_impossible_answer=True
                )
                
                if result["score"] > best_answer["score"]:
                    best_answer = result
            except Exception:
                continue
        
        if best_answer["score"] > MIN_SCORE:
            return best_answer["answer"]
        return "Nessuna risposta sufficientemente certa trovata nel documento"
    
    except Exception as e:
        return f"Errore durante l'elaborazione: {str(e)}"

# Caricamento iniziale risorse
print("Caricamento modelli...")
model_qa, tokenizer, model_embed = load_models()
print("Elaborazione documento...")
doc_chunks, doc_embeddings = process_pdf()

# Interfaccia Gradio
with gr.Blocks(title="AI Esperto Regolamento Calcio") as demo:
    gr.Markdown("# ⚽ Assistente Virtuale Regolamento FIFA")
    gr.Markdown("Poni domande sul regolamento ufficiale del calcio")
    
    with gr.Row():
        question = gr.Textbox(
            label="La tua domanda",
            placeholder="Es: Quando si assegna un calcio di rigore?",
            max_lines=2
        )
        answer = gr.Textbox(label="Risposta ufficiale", interactive=False)
    
    examples = gr.Examples(
        examples=[
            ["Quanti cambi sono permessi a partita?"],
            ["Cosa costituisce un fallo da cartellino rosso diretto?"],
            ["Quali sono le dimensioni minime del campo?"]
        ],
        inputs=[question],
        outputs=[answer],
        fn=answer_question,
        cache_examples=True
    )
    
    question.submit(fn=answer_question, inputs=[question], outputs=[answer])

if __name__ == "__main__":
    demo.launch(show_error=True, share=True)