File size: 9,195 Bytes
0a3bb3c
8a1005e
8605e52
8ed884f
8605e52
 
 
 
 
 
 
8ed884f
8605e52
2e237ff
7b87981
2e237ff
5f2f621
 
8ed884f
7b87981
 
8ed884f
f5cc935
2e237ff
 
8ed884f
11ca166
 
 
 
 
2e237ff
8ed884f
2e237ff
 
be1633f
2e237ff
fdf9785
2e237ff
 
6b22bd7
7b87981
5f2f621
4d70ba5
 
8605e52
699eb45
 
e38fac9
699eb45
 
 
4d70ba5
7b87981
 
 
 
 
8ed884f
4d70ba5
7b87981
 
 
 
4d70ba5
7b87981
8605e52
 
fdf9785
 
 
 
 
 
4cca62c
fdf9785
 
 
 
 
 
 
4cca62c
4d70ba5
 
d15818b
5f2f621
4d70ba5
5f2f621
3659a78
2e237ff
8ed884f
8605e52
8ed884f
2e237ff
11ca166
8ed884f
11ca166
7b87981
 
 
2e237ff
11ca166
 
 
8ed884f
11ca166
8ed884f
 
11ca166
2e237ff
11ca166
 
 
 
 
 
 
 
 
 
 
 
 
2e237ff
8ed884f
11ca166
 
 
8ed884f
11ca166
 
 
 
8ed884f
11ca166
 
 
7b87981
 
11ca166
8ed884f
11ca166
 
fdf9785
11ca166
 
8ed884f
11ca166
 
 
 
 
 
 
 
 
 
 
8ed884f
11ca166
 
2e237ff
8ed884f
 
 
1ec793a
8ed884f
440734a
 
6ca42dc
440734a
 
 
 
 
6ca42dc
8ed884f
440734a
6ca42dc
 
 
440734a
 
 
1ec793a
 
 
8ed884f
6ca42dc
 
 
 
 
 
1ec793a
82bdb96
8ed884f
6ca42dc
 
8ed884f
5793493
 
8ed884f
5793493
 
 
4d70ba5
5793493
 
2e237ff
 
 
82bdb96
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
212
213
214
import os
import sys

# --- TRUCCO PER BUG MUSL SU GRADIO ---
musl_path = "/home/user/app/libc.musl-x86_64.so.1"
if not os.path.exists(musl_path):
    print("Configurazione libreria musl in corso...")
    os.system(f"ln -s /usr/lib/x86_64-linux-musl/libc.so {musl_path}")
    env = os.environ.copy()
    env["LD_LIBRARY_PATH"] = "/home/user/app:" + env.get("LD_LIBRARY_PATH", "")
    os.execve(sys.executable, [sys.executable] + sys.argv, env)
# -------------------------------------

import gradio as gr
from huggingface_hub import hf_hub_download, InferenceClient
from llama_cpp import Llama
import wikipedia

# 1. Imposta Wikipedia Ufficialmente in Sardo (LSC/Logudorese/Campidanese)
wikipedia.set_lang("it")
hf_client = InferenceClient(token=os.environ.get("HF_TOKEN"))

REPO_ID = "FPll/Llama-LIMBA-2.0-GGUF"
FILENAME = "Meta-Llama-3.1-8B.Q4_K_M.gguf"

# Scarica il file GGUF nella cache della Space
model_path = hf_hub_download(
    repo_id=REPO_ID, 
    filename=FILENAME, 
    token=os.environ.get("HF_TOKEN")
)

# Inizializza Llama (su CPU Free Tier)
llm = Llama(
    model_path=model_path,
    n_ctx=4096,      
    n_threads=2,
    verbose=True
)

def cerca_su_wikipedia(query):
    """Estrazione entità via API remota e ricerca (Sardo -> Italiano)"""
    try:
        print(f"\n--- RAG START --- Query utente: '{query}'")
        prompt_estrazione = f"Estrai solo l'argomento principale da cercare su Wikipedia da questa domanda. Rispondi SOLO con l'argomento, senza punteggiatura. Domanda: '{query}'"
        
        risposta = hf_client.chat_completion(
            messages=[{"role": "user", "content": prompt_estrazione}], 
            model="Qwen/Qwen2.5-7B-Instruct", 
            max_tokens=15
        )
        query_pulita = risposta.choices[0].message.content.strip()
        print(f"--- RAG ESTRAZIONE --- Zephyr ha estratto: '{query_pulita}'")
        
        if not query_pulita:
            return ""

        wikipedia.set_lang("sc")
        risultati = wikipedia.search(query_pulita, results=1)
        print(f"--- RAG SEARCH SC --- Risultati in sardo: {risultati}")
        
        if not risultati:
            wikipedia.set_lang("it")
            risultati = wikipedia.search(query_pulita, results=1)
            print(f"--- RAG SEARCH IT --- Risultati in italiano: {risultati}")
            
        if risultati:
            titolo = risultati[0]
            try:
                # Scarica la pagina completa
                pagina = wikipedia.page(titolo)
                
                # Partiamo da ZERO (per salvare le biografie/definizioni) 
                # e arriviamo fino a 6000 caratteri (per catturare le liste più in basso)
                testo_utile = pagina.content[:6000]
                
                print(f"--- RAG SUCCESSO UNIVERSALE --- Testo estratto da: {titolo}")
                return testo_utile
                
            except wikipedia.exceptions.DisambiguationError as e:
                # Se è una pagina ambigua, prende la prima opzione
                pagina = wikipedia.page(e.options[0])
                return pagina.content[:6000]
            
        print("--- RAG FALLITO --- Nessun risultato trovato.")
        return ""
    except Exception as e:
        print(f"--- RAG ERRORE --- {str(e)}")
        return ""

def respond(message, history):
    # 1. IL SALVAVITA: Emette un segnale vuoto per impedire a Gradio di andare in crash (StopAsyncIteration)
    partial_message = ""
    yield partial_message 
    
    try:
        # Estrazione sicura del testo nel caso Gradio invii un dizionario
        msg_text = message.get("text", "") if isinstance(message, dict) else str(message)
        # Richiama Wikipedia
        wiki_context = cerca_su_wikipedia(msg_text)
        extra_ctx = f"\n\nCONTESTO WIKIPEDIA (usalo per rispondere e traduci in sardo): {wiki_context}" if wiki_context else ""
        
        system_instruction = (
            "Sei Limba 2.0, creato da Francesco Palladino. Sei un modello di linguaggio che parla sardo (LSC) e italiano.\n"
            "REGOLE DA RISPETTARE:\n"
            "1. Se l'utente chiede una traduzione in sardo o in italiano, esegui la traduzione senza aggiungere altro.\n"
            "2. Se l'utente chiede una formula o se nella risposta scrivi formule, usa il formato LaTeX racchiuso tra doppi simboli di dollaro ($$) su una riga separata, esempio: $$E=mc^2$$.\n"
            "3. Se l'utente ti saluta (es. 'Ciao', 'Buongiorno'), rispondi esclusivamente con: 'Salude! So Limba 2.0, comente ti potzo agiudare?' e fermati.\n"
            "4. Se l'utente fa una richiesta su un argomento specifico, rispondi alla richiesta in modo completo e accurato.\n"
        )

        few_shot = (
            "<|start_header_id|>user<|end_header_id|>\n\nDimmi tre colori in sardo.<|eot_id|>"
            "<|start_header_id|>assistant<|end_header_id|>\n\nEcco sos colores:\n\n"
            "- Ruju\n"
            "- Birde\n"
            "- Blu<|eot_id|>"
            "<|start_header_id|>user<|end_header_id|>\n\nConfronta oro e argento.<|eot_id|>"
            "<|start_header_id|>assistant<|end_header_id|>\n\nEcco sa tabella:\n\n"
            "| Metallu | Simbolu | n. Atòmicu |\n"
            "| :--- | :--- | :--- |\n"
            "| Oro | Au | 79 |\n"
            "| Prata | Ag | 47 |<|eot_id|>"
        )

        # 2. GESTIONE UNIVERSALE HISTORY (Funziona con tutte le versioni di Gradio)
        history_text = ""
        for item in history:
            if isinstance(item, dict):
                # Formato nuovo di Gradio
                role = item.get("role", "user")
                content = item.get("content", "")
                history_text += f"<|start_header_id|>{role}<|end_header_id|>\n\n{content}<|eot_id|>"
            else:
                # Formato classico a tuple
                user_msg, bot_msg = item
                history_text += f"<|start_header_id|>user<|end_header_id|>\n\n{user_msg}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n{bot_msg}<|eot_id|>"

       # Assemblaggio del Prompt
        prompt = f"<|start_header_id|>system<|end_header_id|>\n\n{system_instruction}{extra_ctx}<|eot_id|>{few_shot}{history_text}<|start_header_id|>user<|end_header_id|>\n\n{msg_text}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n"

        # 3. GENERAZIONE CON PARAMETRI OTTIMIZZATI PER GLI ELENCHI
        response = llm(
            prompt,
            max_tokens=768,
            stop=["<|eot_id|>", "<|start_header_id|>", "<|end_header_id|>"],
            stream=True,
            temperature=0.1,
            top_p=0.9,
            repeat_penalty=1.15  
        )
        
        for chunk in response:
            if "choices" in chunk and len(chunk["choices"]) > 0:
                text = chunk["choices"][0].get("text", "")
                if text:
                    partial_message += text
                    yield partial_message
                    
    # Scudo Anti-Crash: se c'è un errore te lo stampa in chat invece di far crollare la pagina
    except Exception as e:
        yield f"🚨 Errore Python interno: {str(e)}"

# 4. INTERFACCIA VISIVA E STILE

# CSS "Mano Pesante" per eliminare ogni traccia di bordo e sfondo dall'avatar della chat
custom_css = """
/* Rimuove lo sfondo bianco, il bordo, l'ombra e il margine dal contenitore esterno */
.message-avatar, .avatar-container, .avatar-image {
    background: transparent !important;
    background-color: transparent !important;
    border: none !important;
    box-shadow: none !important;
    padding: 0 !important;
    width: 60px !important;
    height: 60px !important;
}
/* Forza l'immagine a riempire tutto lo spazio senza limitazioni */
.message-avatar img, .avatar-container img, .avatar-image img {
    width: 100% !important;
    height: 100% !important;
    object-fit: contain !important;
    border: none !important;
    background: transparent !important;
    padding: 0 !important;
}
"""

# Header HTML personalizzato con il link corretto (FPll)
header_html = """
<div style="display: flex; align-items: center; margin-bottom: 10px;">
    <img src="https://huggingface.co/spaces/FPll/Limba_2.0/resolve/main/logo.png" style="width: 55px; height: 55px; border-radius: 50%; margin-right: 15px;">
    <h1 style="margin: 0; font-size: 24px;">Limba 2.0 - S'assistente IA tuo chi faeddat sardu</h1>
</div>
"""

with gr.Blocks() as demo:
    # Inseriamo il titolo grafico in cima
    gr.HTML(header_html)
    
    # Rimuoviamo il parametro 'title' da qui perché ora usiamo l'intestazione sopra
    gr.ChatInterface(
        fn=respond,
        description="Ajò! So Limba 2.0, s'assistente tuo pro s'informatica, sa matematica e s'iscentzia. Faeddo sardu e italianu.",
        examples=["Iscrie unu script in Python pro lèghere unu file CSV.", "Ispiegami su teorema de Pitagora.", "Traduci in sardo: L'algoritmo di machine learning sta analizzando i dati."],
        cache_examples=False,
        chatbot=gr.Chatbot(
            avatar_images=[None, "https://huggingface.co/spaces/FPll/Limba_2.0/resolve/main/logo.png"], 
            height=500
        )
    )

if __name__ == "__main__":
    demo.launch(css=custom_css, ssr_mode=False)