Update app.py
Browse files
app.py
CHANGED
|
@@ -1,180 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
-
import json
|
| 4 |
-
from pathlib import Path
|
| 5 |
from datetime import datetime
|
| 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 |
-
return out["choices"][0]["text"].strip()
|
| 54 |
-
|
| 55 |
|
| 56 |
def divergence(a, b):
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
return 1.0
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
if not union:
|
| 64 |
-
return 1.0
|
| 65 |
-
return round(1 - len(inter)/len(union), 3)
|
| 66 |
-
|
| 67 |
|
| 68 |
-
#
|
| 69 |
-
#
|
| 70 |
-
#
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
-
timestamp = datetime.utcnow().isoformat()
|
| 77 |
-
base = SAVE_DIR / f"dataset_{timestamp}"
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
# Markdown
|
| 83 |
-
md = f"""
|
| 84 |
-
# Registro A-M
|
| 85 |
-
Gerado: {timestamp}
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
-
|
| 97 |
-
{
|
| 98 |
-
"""
|
| 99 |
-
|
| 100 |
-
md_path.write_text(md, encoding="utf-8")
|
| 101 |
-
|
| 102 |
-
# JSON
|
| 103 |
-
data = {
|
| 104 |
"timestamp": timestamp,
|
| 105 |
"question": question,
|
| 106 |
-
"
|
| 107 |
-
"
|
| 108 |
-
"
|
|
|
|
| 109 |
}
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
|
| 117 |
-
#
|
| 118 |
-
# ==========================
|
| 119 |
-
|
| 120 |
-
def run(question):
|
| 121 |
-
if not question.strip():
|
| 122 |
-
return ["", "", "", ""]
|
| 123 |
-
|
| 124 |
-
prompt = f"Pergunta: {question}\nResponda de forma direta:"
|
| 125 |
-
|
| 126 |
-
respA = generate(prompt, TEMP_A)
|
| 127 |
-
respM = generate(prompt, TEMP_M)
|
| 128 |
-
div_score = divergence(respA, respM)
|
| 129 |
-
|
| 130 |
-
return respA, respM, str(div_score), ""
|
| 131 |
-
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
return "Nada para salvar."
|
| 136 |
|
| 137 |
-
|
| 138 |
-
return f"Salvo!\nMD: {md_path}\nJSON: {json_path}"
|
| 139 |
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
|
|
|
| 142 |
|
| 143 |
-
|
| 144 |
-
# 🔵 A-M Divergence Generator
|
| 145 |
-
Gera **duas respostas** usando o mesmo modelo Gemma:
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
- Mostra divergência → Se ≈0 = iguais, se ≈1 = totalmente diferentes
|
| 150 |
-
- Permite salvar dataset `.md` + `.json` automaticamente
|
| 151 |
-
""")
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
with gr.Row():
|
| 159 |
-
respA = gr.Textbox(label="Resposta A (determinística)")
|
| 160 |
-
respM = gr.Textbox(label="Resposta M (exploratória)")
|
| 161 |
|
| 162 |
-
|
|
|
|
| 163 |
|
| 164 |
-
|
| 165 |
|
| 166 |
-
save = gr.Button("Salvar como Dataset (.md + .json)")
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
outputs=[respA, respM, div, status]
|
| 172 |
-
)
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------
|
| 2 |
+
# app.py — Twin-Branch A–M Reasoning Demo (Gemma 3n)
|
| 3 |
+
# Carlos Rodrigues — 2025
|
| 4 |
+
# ---------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
import gradio as gr
|
| 8 |
+
from huggingface_hub import snapshot_download
|
| 9 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 10 |
import os
|
|
|
|
|
|
|
| 11 |
from datetime import datetime
|
| 12 |
+
import re
|
| 13 |
+
|
| 14 |
+
# ---------------------------------------------------------
|
| 15 |
+
# CONFIGURAÇÃO DO MODELO (troque aqui se quiser outro)
|
| 16 |
+
# ---------------------------------------------------------
|
| 17 |
+
|
| 18 |
+
MODEL_REPO = "google/gemma-3n-E2B-it-litert-lm"
|
| 19 |
+
|
| 20 |
+
LOCAL_MODEL_DIR = snapshot_download(
|
| 21 |
+
repo_id=MODEL_REPO,
|
| 22 |
+
allow_patterns=["*.json", "*.bin", "*.model", "*.safetensors", "*.txt"],
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# ---------------------------------------------------------
|
| 26 |
+
# CARREGAR TOKENIZER & MODELO
|
| 27 |
+
# ---------------------------------------------------------
|
| 28 |
+
|
| 29 |
+
tokenizer = AutoTokenizer.from_pretrained(LOCAL_MODEL_DIR)
|
| 30 |
+
|
| 31 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 32 |
+
LOCAL_MODEL_DIR,
|
| 33 |
+
torch_dtype=torch.float32,
|
| 34 |
+
device_map="auto" # usa GPU se tiver
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# ---------------------------------------------------------
|
| 38 |
+
# FUNÇÕES AUXILIARES
|
| 39 |
+
# ---------------------------------------------------------
|
| 40 |
+
|
| 41 |
+
def clean_text(t):
|
| 42 |
+
t = t.replace("<s>", "").replace("</s>", "")
|
| 43 |
+
t = re.sub(r"\s+", " ", t)
|
| 44 |
+
return t.strip()
|
| 45 |
+
|
| 46 |
+
def generate_answer(prompt, temperature=0.0, top_p=1.0, max_new_tokens=180):
|
| 47 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 48 |
+
with torch.no_grad():
|
| 49 |
+
output = model.generate(
|
| 50 |
+
**inputs,
|
| 51 |
+
max_new_tokens=max_new_tokens,
|
| 52 |
+
temperature=temperature,
|
| 53 |
+
top_p=top_p,
|
| 54 |
+
do_sample=temperature > 0
|
| 55 |
+
)
|
| 56 |
+
decoded = tokenizer.decode(output[0])
|
| 57 |
+
return clean_text(decoded.replace(prompt, ""))
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
def divergence(a, b):
|
| 60 |
+
"""
|
| 61 |
+
Divergência simples: token overlap (0=igual, 1=muito diferente)
|
| 62 |
+
"""
|
| 63 |
+
sa, sb = a.split(), b.split()
|
| 64 |
+
if len(sa) == 0 or len(sb) == 0:
|
| 65 |
return 1.0
|
| 66 |
+
common = len(set(sa) & set(sb))
|
| 67 |
+
union = len(set(sa) | set(sb))
|
| 68 |
+
sim = common / union
|
| 69 |
+
return round(1 - sim, 3)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
# ---------------------------------------------------------
|
| 72 |
+
# PIPELINE A–M
|
| 73 |
+
# ---------------------------------------------------------
|
| 74 |
|
| 75 |
+
def twin_branch_pipeline(question):
|
| 76 |
+
timestamp = datetime.utcnow().isoformat() + "Z"
|
| 77 |
|
| 78 |
+
prompt = f"Pergunta: {question}\nResponda claramente em até 120 palavras:\n"
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
# A — determinístico
|
| 81 |
+
answer_A = generate_answer(prompt, temperature=0.0, top_p=1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
# M — exploratório (descobre divergências)
|
| 84 |
+
answer_M = generate_answer(prompt, temperature=0.9, top_p=0.95)
|
| 85 |
|
| 86 |
+
# Divergência
|
| 87 |
+
div_score = divergence(answer_A, answer_M)
|
| 88 |
|
| 89 |
+
# Decisão simples
|
| 90 |
+
if div_score < 0.15:
|
| 91 |
+
decision = "Ambas respostas convergem — usar Resposta A."
|
| 92 |
+
elif div_score < 0.40:
|
| 93 |
+
decision = "Diferença moderada — sugerir resposta A, mas avisar ambiguidade."
|
| 94 |
+
elif div_score < 0.75:
|
| 95 |
+
decision = "As respostas divergem — investigar as duas interpretações."
|
| 96 |
+
else:
|
| 97 |
+
decision = "Alta divergência — perguntar ao operador para sanear intenção."
|
| 98 |
|
| 99 |
+
# Retorno estruturado
|
| 100 |
+
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
"timestamp": timestamp,
|
| 102 |
"question": question,
|
| 103 |
+
"A (determinística)": answer_A,
|
| 104 |
+
"M (exploratória)": answer_M,
|
| 105 |
+
"Divergência (0=igual,1=muito diferente)": div_score,
|
| 106 |
+
"Decisão da Máquina": decision
|
| 107 |
}
|
| 108 |
|
| 109 |
+
# ---------------------------------------------------------
|
| 110 |
+
# INTERFACE GRADIO
|
| 111 |
+
# ---------------------------------------------------------
|
| 112 |
|
| 113 |
+
def run_interface(question):
|
| 114 |
+
result = twin_branch_pipeline(question)
|
| 115 |
|
| 116 |
+
md = f"""
|
| 117 |
+
# 🔎 Twin-Reasoning A–M (Gemma-3n E2B)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
**Pergunta:**
|
| 120 |
+
{result['question']}
|
|
|
|
| 121 |
|
| 122 |
+
---
|
|
|
|
| 123 |
|
| 124 |
+
## 🟦 Resposta A (determinística)
|
| 125 |
+
{result['A (determinística)']}
|
| 126 |
|
| 127 |
+
## 🟧 Resposta M (exploratória)
|
| 128 |
+
{result['M (exploratória)']}
|
| 129 |
|
| 130 |
+
---
|
|
|
|
|
|
|
| 131 |
|
| 132 |
+
### 📊 Divergência
|
| 133 |
+
**{result['Divergência (0=igual,1=muito diferente)']}**
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
### 🧭 Decisão Interna
|
| 136 |
+
**{result['Decisão da Máquina']}**
|
| 137 |
|
| 138 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
*Gerado em:* `{result['timestamp']}`
|
| 141 |
+
"""
|
| 142 |
|
| 143 |
+
return md
|
| 144 |
|
|
|
|
| 145 |
|
| 146 |
+
# ---------------------------------------------------------
|
| 147 |
+
# LANÇAR A APP
|
| 148 |
+
# ---------------------------------------------------------
|
|
|
|
|
|
|
| 149 |
|
| 150 |
+
demo = gr.Interface(
|
| 151 |
+
fn=run_interface,
|
| 152 |
+
inputs=gr.Textbox(label="Digite uma pergunta:", placeholder="Ex: É legítimo filmar alguém em via pública?"),
|
| 153 |
+
outputs=gr.Markdown(),
|
| 154 |
+
title="Twin-Reasoner A–M — Gemma 3n",
|
| 155 |
+
description="Simulador de Arquitetura A–M (Geração + Verificação) baseado em pesos congelados.",
|
| 156 |
+
)
|
| 157 |
|
| 158 |
+
if __name__ == "__main__":
|
| 159 |
+
demo.launch()
|