File size: 12,591 Bytes
5fc0b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af0535f
5fc0b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b13b8d
5fc0b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b13b8d
 
5fc0b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df01bff
 
 
 
 
 
 
 
 
 
5fc0b03
df01bff
 
 
 
 
 
 
 
 
5fc0b03
 
 
 
 
 
 
 
f381251
5fc0b03
f381251
 
 
 
5fc0b03
f381251
5fc0b03
 
f381251
 
 
 
 
5fc0b03
 
 
 
 
f381251
 
5fc0b03
 
 
 
 
 
 
 
f381251
5fc0b03
 
dc4c97f
5fc0b03
3b13b8d
5fc0b03
3b13b8d
 
5fc0b03
 
 
 
 
3b13b8d
 
5fc0b03
 
 
3b13b8d
 
5fc0b03
 
3b13b8d
 
 
 
 
 
 
 
 
5fc0b03
 
 
 
3b13b8d
5fc0b03
 
3b13b8d
 
 
5fc0b03
3b13b8d
5fc0b03
 
3b13b8d
 
5fc0b03
 
 
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import gradio as gr
import torch
import torch.nn.functional as F
import os
import sys
from transformers import AutoModelForMaskedLM, AutoTokenizer
from sentence_transformers import SentenceTransformer
from huggingface_hub import hf_hub_download

# Importa a classe real do seu arquivo bettina.py
# Certifique-se de que bettina.py está na mesma pasta
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
try:
    from bettina import VortexBetinaAntiHalluc
except ImportError:
    # Tenta importar assumindo que estamos na raiz do projeto
    try:
        import bettina
        VortexBetinaAntiHalluc = bettina.VortexBetinaAntiHalluc
    except ImportError as e:
        raise ImportError(f"CRÍTICO: Não foi possível encontrar 'bettina.py'. Verifique se o arquivo foi enviado para o Space. Erro: {e}")

# Configuração de Dispositivo
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Rodando em: {device}")

# ==============================================================================
# 1. Carregamento dos Modelos Base
# ==============================================================================
print("Carregando modelos base...")
embedding_model_name = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
tokenizer_name = "neuralmind/bert-base-portuguese-cased"

# Carrega modelos com cache para não baixar toda vez
embedding_model = SentenceTransformer(embedding_model_name, device=str(device))
tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
mlm_model = AutoModelForMaskedLM.from_pretrained(tokenizer_name).to(device)
mlm_model.eval()

# ==============================================================================
# 2. Inicialização da Betina (Nosso Cérebro)
# ==============================================================================
# Configurações devem bater com o que foi treinado. Usando defaults do bettina.py
EMBED_DIM = 256
RAW_EMBED_DIM = embedding_model.get_sentence_embedding_dimension() # 768
HIDDEN_SIZE = mlm_model.config.hidden_size # 768

print("Inicializando Vortex Betina...")
# Instancia a classe robusta do seu código
vortex = VortexBetinaAntiHalluc(
    embed_dim=EMBED_DIM,
    # Habilitando recursos avançados por padrão para demonstração
    enable_rotation=True,
    enable_quadratic_reflection=True,
    enable_lorentz_transform=True,
    enforce_square_geometry=True
).to(device)

# Projetores para conectar os mundos (SentenceTransformer -> Vortex -> BERT)
embedding_projector = torch.nn.Linear(RAW_EMBED_DIM, EMBED_DIM).to(device)
correction_projector = torch.nn.Linear(EMBED_DIM, HIDDEN_SIZE).to(device)

# ==============================================================================
# 3. Carregamento de Pesos (Se existirem)
# ==============================================================================
weights_loaded = False
REPO_ID = "reynaldo22/betina-perfect-2025"

# 1. Tentar baixar do Hugging Face Hub
try:
    print(f"Tentando baixar pesos do repositório: {REPO_ID}...")
    token = os.getenv("HF_TOKEN")
    
    vortex_path = hf_hub_download(repo_id=REPO_ID, filename="vortex.pt", token=token)
    emb_path = hf_hub_download(repo_id=REPO_ID, filename="embedding_projector.pt", token=token)
    corr_path = hf_hub_download(repo_id=REPO_ID, filename="correction_projector.pt", token=token)
    
    # strict=False permite carregar pesos parciais se houver pequenas diferenças de versão
    vortex.load_state_dict(torch.load(vortex_path, map_location=device), strict=False)
    embedding_projector.load_state_dict(torch.load(emb_path, map_location=device))
    correction_projector.load_state_dict(torch.load(corr_path, map_location=device))
    weights_loaded = True
    print("✅ Pesos carregados do Hugging Face com sucesso!")
except Exception as e:
    print(f"⚠️ Falha ao baixar do Hugging Face: {e}")
    print("Tentando carregar localmente...")

# 2. Fallback para arquivos locais
if not weights_loaded:
    POSSIBLE_DIRS = ["outputs/betina_vortex", ".", "model_weights"]
    for model_dir in POSSIBLE_DIRS:
        vortex_path = os.path.join(model_dir, "vortex.pt")
        if os.path.exists(vortex_path):
            print(f"Carregando pesos locais de {model_dir}...")
            try:
                vortex.load_state_dict(torch.load(vortex_path, map_location=device))
                embedding_projector.load_state_dict(torch.load(os.path.join(model_dir, "embedding_projector.pt"), map_location=device))
                correction_projector.load_state_dict(torch.load(os.path.join(model_dir, "correction_projector.pt"), map_location=device))
                weights_loaded = True
                break
            except Exception as e:
                print(f"Erro ao carregar pesos de {model_dir}: {e}")

if not weights_loaded:
    print("⚠️ AVISO: Pesos treinados não encontrados. Usando inicialização aleatória.")
    print("O modelo vai rodar, mas as respostas da Betina serão aleatórias até você treinar.")

vortex.eval()
embedding_projector.eval()
correction_projector.eval()

# ==============================================================================
# 4. Lógica de Inferência
# ==============================================================================
def predict(contexto, frase_mask, chaos_factor):
    if "[MASK]" not in frase_mask:
        return "⚠️ Erro: A frase precisa conter o token [MASK]."

    # Combinar contexto e frase para o embedding semântico
    texto_completo = f"{contexto} {frase_mask}".strip()
    
    # Preparar inputs para o BERT
    inputs = tokenizer(texto_completo, return_tensors="pt").to(device)
    
    # Encontrar índice da máscara
    mask_token_index = (inputs.input_ids == tokenizer.mask_token_id)[0].nonzero(as_tuple=True)[0]
    if len(mask_token_index) == 0:
         return "Erro: Token [MASK] não identificado corretamente pelo tokenizer."
    mask_idx = mask_token_index[0].item()

    # --- 1. BERT Puro (Baseline) ---
    with torch.no_grad():
        outputs_base = mlm_model(**inputs)
        logits_base = outputs_base.logits
        probs_base = F.softmax(logits_base[0, mask_idx], dim=-1)
        top_k_base = torch.topk(probs_base, 5)
        
        res_base = []
        for idx, score in zip(top_k_base.indices, top_k_base.values):
            token = tokenizer.decode([idx]).strip()
            res_base.append(f"**{token}** ({score:.2%})")

    # --- 2. Betina (Com Vórtice) ---
    with torch.no_grad():
        # a) Gerar embedding semântico do texto todo
        emb = embedding_model.encode(texto_completo, convert_to_tensor=True).to(device)
        
        # b) Projetar para dimensão do Vórtice
        proj = embedding_projector(emb)
        
        # c) Passar pelo Vórtice (O Cérebro Caótico)
        # O método forward retorna: evolved, loss, metrics, delta_inter
        # Precisamos adicionar dimensão de batch (unsqueeze)
        # AGORA COM FATOR CAOS!
        _, _, metrics, delta = vortex(proj.unsqueeze(0), chaos_factor=chaos_factor) 
        
        # d) Projetar correção de volta para dimensão do BERT
        correction = correction_projector(delta).unsqueeze(1) # [1, 1, hidden_size]
        
        # e) Injetar nos hidden states do BERT
        outputs_hidden = mlm_model(**inputs, output_hidden_states=True)
        last_hidden_state = outputs_hidden.hidden_states[-1]
        
        # Soma a correção (broadcast)
        corrected_hidden = last_hidden_state + correction
        
        # f) Predição final
        if hasattr(mlm_model, "cls"):
            logits_betina = mlm_model.cls(corrected_hidden)
        else:
            logits_betina = mlm_model.get_output_embeddings()(corrected_hidden)

        # --- 🚀 RESSONÂNCIA CONTEXTUAL (A MIRA LASER) ---
        # Se o Caos estiver ativado, forçamos o modelo a considerar palavras do contexto.
        # Isso resolve o problema de "quebrar o viés mas não achar a resposta".
        if chaos_factor > 1.0:
            # Tokeniza apenas o contexto para descobrir quais palavras estão lá
            context_tokens = tokenizer(contexto, add_special_tokens=False)["input_ids"]
            
            # Cria um vetor de reforço
            resonance_bias = torch.zeros_like(logits_betina[0, mask_idx])
            
            # Para cada palavra que aparece no contexto, aumentamos a probabilidade dela
            for token_id in context_tokens:
                # O peso é proporcional ao Fator Caos. 
                # 0.5 é um ajuste fino para não ignorar totalmente a gramática.
                resonance_bias[token_id] += (chaos_factor * 0.5)
            
            # Injeta a ressonância nos logits originais
            logits_betina[0, mask_idx] += resonance_bias

        probs_betina = F.softmax(logits_betina[0, mask_idx], dim=-1)
        top_k_betina = torch.topk(probs_betina, 5)
        
        res_betina = []
        for idx, score in zip(top_k_betina.indices, top_k_betina.values):
            token = tokenizer.decode([idx]).strip()
            res_betina.append(f"**{token}** ({score:.2%})")

    # Formatar saída HTML (Estilo Clean)
    html_output = f"""
    <div style="display: flex; gap: 20px; flex-wrap: wrap;">
        <div style="flex: 1; min-width: 300px; background-color: #f5f5f5; padding: 15px; border-radius: 10px; border: 1px solid #ddd;">
            <h3 style="color: #555; margin-top: 0;">🧠 BERT Padrão</h3>
            <p style="font-size: 0.9em; color: #666;"><i>O que o modelo "decorou" do treino original.</i></p>
            <ol>
                {''.join([f'<li>{item}</li>' for item in res_base])}
            </ol>
        </div>
        <div style="flex: 1; min-width: 300px; background-color: #e6f7ff; padding: 15px; border-radius: 10px; border: 2px solid #1890ff;">
            <h3 style="color: #0050b3; margin-top: 0;">🌀 Betina 2.0</h3>
            <p style="font-size: 0.9em; color: #0050b3;"><i>Correção Dinâmica (Caos: {chaos_factor}x)</i></p>
            <ol>
                {''.join([f'<li>{item}</li>' for item in res_betina])}
            </ol>
        </div>
    </div>
    <br>
    <details>
        <summary style="cursor: pointer; color: #888;">📊 Métricas do Vórtice (Estado Interno)</summary>
        <pre style="font-size: 0.8em; background: #333; color: #0f0; padding: 10px; border-radius: 5px; overflow-x: auto;">{str(metrics)}</pre>
    </details>
    """
    return html_output

# ==============================================================================
# 5. Interface Gradio
# ==============================================================================
custom_css = """
footer {visibility: hidden}
"""

with gr.Blocks(title="Betina 2.0 - Protocolo Impossível") as demo:
    gr.Markdown("""
    # 🌀 BETINA 2.0: PROTOCOLO IMPOSSÍVEL
    
    Sistema de correção neural baseado em **Dinâmica de Vórtice**.
    Aumente o **Fator Caos** para forçar a lógica sobre a estatística.
    """)
    
    with gr.Row():
        with gr.Column(scale=1):
            txt_contexto = gr.Textbox(
                label="1. CONTEXTO (A Verdade Absoluta)", 
                placeholder="Ex: A felicidade é medida em quilos. Se estou feliz, estou...", 
                lines=3
            )
            txt_mask = gr.Textbox(
                label="2. CONSULTA (Use [MASK])", 
                placeholder="Ex: Estou muito feliz, logo estou [MASK].", 
                lines=2
            )
            slider_chaos = gr.Slider(
                minimum=1.0, 
                maximum=50.0, 
                value=1.0, 
                step=0.5, 
                label="🔥 FATOR CAOS (Overdrive)",
                info="1.0 = Padrão. Aumente para forçar correções impossíveis."
            )
            btn_run = gr.Button("🌀 INICIAR VÓRTICE", variant="primary")
            
        with gr.Column(scale=1):
            out_result = gr.HTML(label="Resultado Comparativo")
    
    gr.Markdown("### 🧪 Testes de Paradoxo")
    gr.Examples(
        examples=[
            ["A felicidade é medida em quilos. Se estou feliz, estou...", "Estou muito feliz, logo estou [MASK].", 10.0],
            ["Neste mundo, o gelo é quente e o fogo é frio.", "Toquei no fogo e senti [MASK].", 15.0],
            ["O ciclo da vida é reverso: morremos, vivemos e nascemos.", "Depois de viver muito, eu vou [MASK].", 20.0]
        ],
        inputs=[txt_contexto, txt_mask, slider_chaos]
    )

    btn_run.click(fn=predict, inputs=[txt_contexto, txt_mask, slider_chaos], outputs=out_result)


if __name__ == "__main__":
    demo.launch()