roneymatusp commited on
Commit
a7110dc
·
verified ·
1 Parent(s): cbb1a9c

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +43 -4
  2. app.py +138 -68
  3. requirements.txt +5 -6
README.md CHANGED
@@ -1,14 +1,53 @@
1
  ---
2
- title: Paulean British Optimizer
3
- emoji: "🎓"
4
  colorFrom: indigo
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: 4.41.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Paulean British Optimizer é um assistente educacional que ajusta instruções de forma concisa e em inglês britânico.
13
 
14
- Este Space utiliza o modelo Mistral‑7B com afinamento LoRA e funciona tanto no plano grátis (ZeroGPU) quanto com GPU fixa.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Paulean AI — British Prompt Optimiser
3
+ emoji: 🏫
4
  colorFrom: indigo
5
  colorTo: red
6
  sdk: gradio
7
  sdk_version: 4.41.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Paulean AI British Prompt Optimiser (LoRA Mistral‑7B)
14
 
15
+ **O que ele faz:**
16
+ Transforma uma **ideia em português** do professor (ex.: “faça uma aula de matemática sobre equações…”) em **um único prompt otimizado** no formato:
17
+
18
+ - Persona
19
+ - Contexto
20
+ - Tarefa
21
+ - Formato
22
+ - Critérios
23
+ - Idioma de saída (sempre “English (United Kingdom)”)
24
+
25
+ **O que ele NÃO faz:**
26
+ - Não responde conteúdos nem aulas.
27
+ - Não tira dúvidas.
28
+ - Apenas **otimiza o prompt**.
29
+
30
+ **Política:** entradas inadequadas ou ofensivas retornam **`fora da política de otimização de prompts`**.
31
+
32
+ ## Variáveis do Space (Settings → Variables and secrets)
33
+
34
+ **Variables (Public):**
35
+ - `BASE_ID = mistralai/Mistral-7B-v0.1`
36
+ - `ADAPTER_ID = roneymatusp/british-optimizer-mistral-final`
37
+
38
+ **Secrets (Private):**
39
+ - `HF_TOKEN = <seu token HF, se necessário>`
40
+
41
+ ## Hardware
42
+ - Funciona em **ZeroGPU** (primeira resposta pode demorar no warmup).
43
+ - Para uso com professores, habilitar temporariamente **Nvidia T4 small** e depois voltar a ZeroGPU.
44
+
45
+ ## Embed (SharePoint)
46
+ ```html
47
+ <iframe
48
+ src="https://roneymatusp-paulean-british-optimizer.hf.space"
49
+ width="100%" height="700" style="border:0;"
50
+ allow="clipboard-read; clipboard-write; microphone"
51
+ loading="lazy"
52
+ ></iframe>
53
+ ```
app.py CHANGED
@@ -1,4 +1,4 @@
1
- import os
2
  import gradio as gr
3
  import torch
4
  import spaces
@@ -7,7 +7,9 @@ from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
7
  from peft import PeftModel
8
  from huggingface_hub import login
9
 
10
- # --------- Config via Variables/Secrets ---------
 
 
11
  BASE_ID = os.getenv("BASE_ID", "mistralai/Mistral-7B-v0.1")
12
  ADAPTER_ID = os.getenv("ADAPTER_ID", "roneymatusp/british-optimizer-mistral-final")
13
  HF_TOKEN = os.getenv("HF_TOKEN")
@@ -16,21 +18,20 @@ if HF_TOKEN:
16
  try:
17
  login(HF_TOKEN)
18
  except Exception:
 
19
  pass
20
 
21
- # --------- Lazy globals (carrega só quando necessário) ---------
22
- _tokenizer = None
 
 
23
  _model = None
24
 
25
- def _load_model():
26
- """
27
- Carrega base + LoRA em 4-bit (quando houver GPU) e fica em cache.
28
- Em ZeroGPU, este carregamento acontece DENTRO da função anotada com @spaces.GPU.
29
- Em GPU fixa, também funciona e permanece em VRAM.
30
- """
31
- global _tokenizer, _model
32
- if _model is not None and _tokenizer is not None:
33
- return _tokenizer, _model
34
 
35
  bnb = BitsAndBytesConfig(
36
  load_in_4bit=True,
@@ -39,81 +40,150 @@ def _load_model():
39
  bnb_4bit_compute_dtype=torch.bfloat16,
40
  )
41
 
42
- _tokenizer = AutoTokenizer.from_pretrained(BASE_ID, use_fast=True)
43
  base = AutoModelForCausalLM.from_pretrained(
44
  BASE_ID,
45
  torch_dtype=torch.bfloat16,
46
  device_map="auto",
47
  quantization_config=bnb,
48
  )
49
-
50
  _model = PeftModel.from_pretrained(base, ADAPTER_ID)
51
  _model.eval()
52
- return _tokenizer, _model
53
-
54
- SYSTEM_PROMPT = (
55
- "You are a British educator. Be concise, courteous, and academically precise. "
56
- "Prefer UK spelling and classroom vocabulary used in British schools."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  )
58
 
59
- def _build_prompt(history_pairs, user_message):
60
- # history_pairs: list of (user, assistant)
61
- lines = [SYSTEM_PROMPT, ""]
62
- for u, a in history_pairs:
63
- if u:
64
- lines.append(f"User: {u}")
65
- if a:
66
- lines.append(f"Assistant: {a}")
67
- lines.append(f"User: {user_message}")
68
- lines.append("Assistant:")
69
- return "\n".join(lines)
70
-
71
- # --------- Função de resposta (GPU on-demand / ZeroGPU) ---------
72
- @spaces.GPU(duration=120) # ignorado quando o hardware não é ZeroGPU
73
- def respond(message, history):
74
- """
75
- ChatInterface chama com:
76
- - message: str
77
- - history: list[tuple[str, str]]
78
- Retorno: str com a resposta do assistente.
79
- """
80
- tok, model = _load_model()
81
 
82
- prompt = _build_prompt(history, message)
83
- inputs = tok(prompt, return_tensors="pt").to(model.device)
 
 
 
 
 
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  with torch.no_grad():
86
  out = model.generate(
87
  **inputs,
88
- max_new_tokens=256,
89
  do_sample=True,
90
- temperature=0.7,
91
  top_p=0.95,
92
  pad_token_id=tok.eos_token_id,
93
  )
 
94
 
95
- text = tok.decode(out[0], skip_special_tokens=True)
96
-
97
- # Extrai apenas o trecho após "Assistant:"
98
- if "Assistant:" in text:
99
- text = text.split("Assistant:", 1)[1].strip()
100
-
101
- return text
102
-
103
- # --------- Gradio UI ---------
104
- demo = gr.ChatInterface(
105
- fn=respond,
106
- type="messages", # formato moderno compatível
107
- title="Paulean AI British Prompt Optimiser",
108
- description=(
109
- "Demo escolar (Mistral‑7B + LoRA). Evite dados sensíveis. "
110
- "Em ZeroGPU a primeira resposta pode demorar para carregar os pesos."
111
- ),
112
- submit_btn="Enviar",
113
- retry_btn="Refazer",
114
- undo_btn="Voltar",
115
- clear_btn="Limpar",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  )
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  if __name__ == "__main__":
119
- demo.launch()
 
1
+ import os, re
2
  import gradio as gr
3
  import torch
4
  import spaces
 
7
  from peft import PeftModel
8
  from huggingface_hub import login
9
 
10
+ # =========================
11
+ # Variáveis do ambiente
12
+ # =========================
13
  BASE_ID = os.getenv("BASE_ID", "mistralai/Mistral-7B-v0.1")
14
  ADAPTER_ID = os.getenv("ADAPTER_ID", "roneymatusp/british-optimizer-mistral-final")
15
  HF_TOKEN = os.getenv("HF_TOKEN")
 
18
  try:
19
  login(HF_TOKEN)
20
  except Exception:
21
+ # Se o token não for necessário (modelo não-gated), segue silencioso.
22
  pass
23
 
24
+ # =========================
25
+ # Cache de modelo
26
+ # =========================
27
+ _tok = None
28
  _model = None
29
 
30
+ def load_model():
31
+ """Carrega Mistral-7B em 4-bit e aplica o LoRA; mantém em cache."""
32
+ global _tok, _model
33
+ if _tok is not None and _model is not None:
34
+ return _tok, _model
 
 
 
 
35
 
36
  bnb = BitsAndBytesConfig(
37
  load_in_4bit=True,
 
40
  bnb_4bit_compute_dtype=torch.bfloat16,
41
  )
42
 
43
+ _tok = AutoTokenizer.from_pretrained(BASE_ID, use_fast=True)
44
  base = AutoModelForCausalLM.from_pretrained(
45
  BASE_ID,
46
  torch_dtype=torch.bfloat16,
47
  device_map="auto",
48
  quantization_config=bnb,
49
  )
 
50
  _model = PeftModel.from_pretrained(base, ADAPTER_ID)
51
  _model.eval()
52
+ return _tok, _model
53
+
54
+ # =========================
55
+ # Política de bloqueio
56
+ # (lista simples; ajuste conforme a escola)
57
+ # =========================
58
+ BANNED = {
59
+ # palavrões/insultos em PT (exemplos)
60
+ "merda","porra","caralho","buceta","puta","puto",
61
+ "viad","bixa","bicha","otario","otário","otaria","otária",
62
+ "idiota","imbecil","burro","burra",
63
+ # acrescente termos específicos da política da escola
64
+ }
65
+
66
+ def violates_policy(text: str) -> bool:
67
+ if not text or len(text.strip()) < 6:
68
+ return True
69
+ t = text.lower()
70
+ return any(b in t for b in BANNED)
71
+
72
+ # =========================
73
+ # Instruções do Otimizador
74
+ # =========================
75
+ SYSTEM = (
76
+ "You are a PROMPT OPTIMISER for teachers in the UK. "
77
+ "You NEVER answer the user's task or give examples/solutions. "
78
+ "You ONLY return ONE structured prompt that another assistant will answer later. "
79
+ "Use UK spelling and an academic yet concise tone."
80
  )
81
 
82
+ OPT_TEMPLATE = """Rewrite the user's idea (Portuguese) into exactly ONE optimised prompt for a teaching assistant.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ Constraints:
85
+ - Headings MUST be in Portuguese EXACTLY as below.
86
+ - Content MUST be in UK English (en-GB).
87
+ - Do NOT include explanations, solutions, examples, or chit-chat.
88
+ - If the idea is vague (e.g., just 'equations'), keep it curriculum-appropriate and generic.
89
+ - The assistant may ask up to 3 clarifying questions only if critical gaps remain.
90
+
91
+ Return ONLY the block below:
92
 
93
+ Persona: British educator and prompt engineer supporting teachers in UK schools.
94
+ Contexto: <brief UK classroom context derived from the user's idea; keep generic if unspecified>
95
+ Tarefa: <what the assistant should produce or plan, aligned to the user's intent>
96
+ Formato: <bulleted or numbered; resources if any; expected length; approximate timings if relevant>
97
+ Critérios: <clarity; UK spelling; curriculum alignment; accessibility (SEN/EAL); inclusivity; retrieval practice>
98
+ Idioma de saída: English (United Kingdom)
99
+
100
+ User idea (pt-BR):
101
+ {user_pt}
102
+ """
103
+
104
+ def _generate(prompt: str, max_new_tokens=280, temperature=0.25) -> str:
105
+ tok, model = load_model()
106
+ inputs = tok(prompt, return_tensors="pt").to(model.device)
107
  with torch.no_grad():
108
  out = model.generate(
109
  **inputs,
110
+ max_new_tokens=max_new_tokens,
111
  do_sample=True,
112
+ temperature=temperature,
113
  top_p=0.95,
114
  pad_token_id=tok.eos_token_id,
115
  )
116
+ return tok.decode(out[0], skip_special_tokens=True)
117
 
118
+ def keep_only_block(text: str) -> str:
119
+ """
120
+ Mantém apenas o bloco a partir de 'Persona:' até antes de qualquer
121
+ tokenização extra (User:, Assistant:, ###, ``` etc.). Garante que
122
+ sai o prompt, nada de respostas.
123
+ """
124
+ m = re.search(r"Persona\s*:", text, flags=re.IGNORECASE)
125
+ if not m:
126
+ # fallback mínimo sempre no formato correto
127
+ return (
128
+ "Persona: British educator and prompt engineer supporting teachers in UK schools.\n"
129
+ "Contexto: UK classroom context (generic).\n"
130
+ "Tarefa: Produce a concise lesson plan outline aligned to the user's intent.\n"
131
+ "Formato: Numbered steps; brief timings; resources if any.\n"
132
+ "Critérios: Clarity; UK spelling; curriculum alignment; inclusivity (SEN/EAL).\n"
133
+ "Idioma de saída: English (United Kingdom)"
134
+ )
135
+ clean = text[m.start():].strip()
136
+ clean = re.split(r"\n\s*(Assistant:|User:|###|```)", clean)[0].strip()
137
+ # Evita vazamentos ao final (repetições ou rodapés).
138
+ return clean
139
+
140
+ # =========================
141
+ # Função pública do Space
142
+ # (decorada para ZeroGPU/GPU)
143
+ # =========================
144
+ @spaces.GPU(duration=120)
145
+ def optimise_free_text(user_input: str) -> str:
146
+ if violates_policy(user_input):
147
+ return "fora da política de otimização de prompts"
148
+
149
+ instruction = f"{SYSTEM}\n\n" + OPT_TEMPLATE.format(user_pt=user_input.strip())
150
+ raw = _generate(instruction, max_new_tokens=320, temperature=0.22)
151
+ return keep_only_block(raw)
152
+
153
+ # =========================
154
+ # UI — simples, sem chat
155
+ # =========================
156
+ THEME = gr.themes.Base(
157
+ primary_hue="indigo",
158
+ secondary_hue="red",
159
  )
160
 
161
+ with gr.Blocks(title="Paulean AI — Otimizador de Prompts (British)", theme=THEME) as demo:
162
+ gr.Markdown(
163
+ "## Paulean AI — Otimizador de Prompts (British)\n"
164
+ "Digite sua ideia **em português** (ex.: *faça uma aula de matemática sobre equações para o IB*). "
165
+ "O sistema **não responde aulas** nem dúvidas — ele **apenas** devolve um **prompt otimizado** "
166
+ "no formato padronizado (**Persona, Contexto, Tarefa, Formato, Critérios, Idioma**).\n\n"
167
+ "**Entradas inadequadas** retornam: `fora da política de otimização de prompts`."
168
+ )
169
+ with gr.Row():
170
+ with gr.Column(scale=1):
171
+ inp = gr.Textbox(
172
+ label="Sua ideia (pt-BR)",
173
+ placeholder="Ex.: Faça uma aula de matemática sobre equações do 2º grau (40-50 min), com exemplos e exercícios...",
174
+ lines=8
175
+ )
176
+ with gr.Row():
177
+ btn = gr.Button("Gerar prompt", variant="primary")
178
+ clr = gr.Button("Limpar")
179
+ with gr.Column(scale=1):
180
+ out = gr.Textbox(
181
+ label="Prompt otimizado (copiar e usar)",
182
+ lines=18,
183
+ show_copy_button=True
184
+ )
185
+ btn.click(optimise_free_text, inputs=inp, outputs=out)
186
+ clr.click(lambda: ("", ""), inputs=None, outputs=[inp, out])
187
+
188
  if __name__ == "__main__":
189
+ demo.launch()
requirements.txt CHANGED
@@ -1,9 +1,8 @@
1
  transformers==4.45.1
2
- accelerate>=0.33.0
3
- peft>=0.12.0
4
- bitsandbytes>=0.43.1
5
- torch>=2.3.0
6
  gradio>=4.41.0
7
  spaces>=0.29.0
8
- sentencepiece
9
- huggingface_hub>=0.24.6
 
1
  transformers==4.45.1
2
+ accelerate>=0.30.0
3
+ bitsandbytes
4
+ peft>=0.11.0
5
+ torch
6
  gradio>=4.41.0
7
  spaces>=0.29.0
8
+ huggingface_hub>=0.24.0