fabrizziomcl commited on
Commit
89447c9
·
verified ·
1 Parent(s): be2d065

Professional minimalist UI (English, no emojis); SFT model; tighter decoding + anti-hallucination prompt

Browse files
Files changed (2) hide show
  1. README.md +24 -24
  2. app.py +63 -76
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: EduCrate — Socratic Tutor
3
- emoji: 🦉
4
- colorFrom: blue
5
- colorTo: indigo
6
  sdk: gradio
7
  app_file: app.py
8
  pinned: false
@@ -24,42 +24,42 @@ models:
24
  - fabrizziomcl/nanoballena-qwen3-socratic
25
  ---
26
 
27
- # 🦉 EduCrate — A Socratic Tutor for Peruvian Public-School Students
28
 
29
- **EduCrate never gives the final answer.** It guides students with one question at a
30
- time, detects their mistake, and offers progressive hints (the *maieutic* method) so they
31
- discover the answer themselves. Spanish-language tutoring focused on **mathematical
32
- reasoning** and **reading comprehension**, small enough to **run on CPU**.
33
 
34
  ## Links
35
- - 🎬 **Demo video:** _TODO: paste YouTube link here_
36
- - 📣 **Social post:** _TODO: paste post link here_
37
- - 🤖 **Model:** https://huggingface.co/fabrizziomcl/nanoballena-qwen3-socratic
38
 
39
  ## The problem
40
- Peru's public secondary schools face a learning crisis. In **PISA 2022 (OECD)**, only
41
- **34%** of Peruvian 15-year-olds reached basic proficiency in **math** (66% below) and
42
- **50%** in **reading**. Peru's national assessment (**ECE / MINEDU, grade 8, 2022**) found
43
- only **~12.7%** *Satisfactory* in math, with public (state) schools far behind private
44
- ones. Most chatbots just hand over the answer — which does not build reasoning.
45
 
46
  ## The model
47
- - Base: **Qwen/Qwen3-0.6B** (596M), fine-tuned with **SFT + GRPO** on **~4,900 Spanish
48
- Socratic dialogues** generated for this project. Runs on **CPU** → no GPU, no paid API.
49
- - Models: `fabrizziomcl/nanoballena-qwen3-socratic` (SFT+GRPO) · `…-qwen3-sft` (SFT).
50
 
51
  ## Measured behavior (held-out mGSM-es)
52
  | Model | Answer-withholding | Asks a question | Avg words |
53
  |---|---|---|---|
54
  | Qwen3-0.6B base | 84% | 100% | 66.5 |
55
- | **EduCrate (fine-tuned)** | **100%** | 100% | ~11 |
56
 
57
  Evidence of real behavioral change on unseen problems — not memorization.
58
 
59
  ## How to use
60
- Click an **example card**, or: (optional) paste a reading passage → choose what you need
61
- (*understand my mistake* / *need a fact* / *just chat*) → ask your question. The tutor
62
- replies **in Spanish** with a guiding question, never the answer.
63
 
64
- > Built for the **Build Small Hackathon** — track *Backyard AI*, **Tiny Titan** badge (≤4B).
65
  > Made with generative AI; validate any pedagogical use with a teacher.
 
1
  ---
2
  title: EduCrate — Socratic Tutor
3
+ emoji: 📘
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: gradio
7
  app_file: app.py
8
  pinned: false
 
24
  - fabrizziomcl/nanoballena-qwen3-socratic
25
  ---
26
 
27
+ # EduCrate — A Socratic Tutor for Peruvian Public-School Students
28
 
29
+ EduCrate never gives the final answer. It guides students with one question at a time,
30
+ detects their mistake, and offers progressive hints (the maieutic method) so they
31
+ discover the answer themselves. Spanish-language tutoring focused on mathematical
32
+ reasoning and reading comprehension, small enough to run on CPU.
33
 
34
  ## Links
35
+ - Demo video: _TODO: paste link_
36
+ - Social post: _TODO: paste link_
37
+ - Model: https://huggingface.co/fabrizziomcl/nanoballena-qwen3-sft
38
 
39
  ## The problem
40
+ Peru's public secondary schools face a learning crisis. In PISA 2022 (OECD), only 34%
41
+ of Peruvian 15-year-olds reached basic proficiency in math (66% below) and 50% in
42
+ reading. Peru's national assessment (ECE / MINEDU, grade 8, 2022) found only about 12.7%
43
+ Satisfactory in math, with public (state) schools far behind private ones. Most chatbots
44
+ just hand over the answer — which does not build reasoning.
45
 
46
  ## The model
47
+ - Base: Qwen/Qwen3-0.6B (596M), fine-tuned with SFT (a GRPO variant is also published)
48
+ on ~4,900 Spanish Socratic dialogues generated for this project. Runs on CPU.
49
+ - Models: `fabrizziomcl/nanoballena-qwen3-sft` · `fabrizziomcl/nanoballena-qwen3-socratic`
50
 
51
  ## Measured behavior (held-out mGSM-es)
52
  | Model | Answer-withholding | Asks a question | Avg words |
53
  |---|---|---|---|
54
  | Qwen3-0.6B base | 84% | 100% | 66.5 |
55
+ | EduCrate (fine-tuned) | 100% | 100% | ~11 |
56
 
57
  Evidence of real behavioral change on unseen problems — not memorization.
58
 
59
  ## How to use
60
+ Click an example, or: (optional) paste a reading passage, choose what you need, and ask
61
+ your question. The tutor replies in Spanish with a guiding question, never the answer.
62
+ It is a 0.6B model, so guidance is sometimes imperfect.
63
 
64
+ > Built for the Build Small Hackathon — track Backyard AI, Tiny Titan badge (≤4B).
65
  > Made with generative AI; validate any pedagogical use with a teacher.
app.py CHANGED
@@ -1,57 +1,53 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- EduCrate — Socratic Tutor (Gradio app)
4
  A Spanish-language Socratic tutor for Peruvian public secondary-school students.
5
 
6
- UI in English (international judges); the tutoring itself happens in Spanish.
7
- Model: Qwen3-0.6B fine-tuned (SFT + GRPO). Runs on CPU.
8
- Gradio 6 (Chatbot uses the messages format).
9
  """
10
  import os
11
  import gradio as gr
12
  import torch
13
 
14
- MODEL_ID = os.environ.get("MODEL_ID", "fabrizziomcl/nanoballena-qwen3-socratic")
15
  MAX_TURNS = int(os.environ.get("MAX_TURNS", "8"))
16
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
17
  DTYPE = torch.bfloat16 if DEVICE == "cuda" else torch.float32
18
 
19
- # Anchors the Socratic behavior (mirrors the training system prompt).
20
  SYSTEM_PROMPT = (
21
  "Eres EduCrate, un tutor socrático en español para estudiantes de secundaria "
22
  "de colegios públicos del Perú (comprensión lectora y razonamiento matemático). "
23
  "REGLA ABSOLUTA: NUNCA des la respuesta final ni el resultado directamente. "
24
- "Guía con UNA pregunta a la vez, detecta el error del estudiante y da pistas "
25
  "progresivas (primero suave, luego más concreta) hasta que él mismo descubra la "
26
- "respuesta. Sé breve, amable y claro."
 
 
27
  )
28
 
29
- # maieutic-inspired modes: reference (give a fact) vs reasoning (counter-question).
30
  MODES = {
31
- "💬 Just chat": "",
32
- "🤔 Understand my mistake": (
33
  "\n\nEl estudiante quiere entender su razonamiento: NO le des datos ni la "
34
  "respuesta; respóndele con una contrapregunta que lo haga revisar su propio paso."
35
  ),
36
- "💡 I need a fact/formula": (
37
- "\n\nEl estudiante pide un DATO o fórmula puntual: puedes dárselo de forma breve, "
38
  "pero NUNCA lo apliques hasta la respuesta final por él; devuélvele la pregunta."
39
  ),
40
  }
41
 
42
- # Example cards (Spanish prompts = real student use). Each: (title, desc, reading, message).
43
  EXAMPLES = [
44
- ("➗ Linear equation",
45
- "Solve 3x + 6 = 15 — guided, no answer",
46
  "", "Ayúdame a resolver 3x + 6 = 15, pero no me des la respuesta."),
47
- ("🍕 Adding fractions",
48
- "Understand 1/2 + 1/3 step by step",
49
  "", "No entiendo cómo sumar 1/2 + 1/3. ¿Me ayudas a pensarlo?"),
50
- ("% Percentages",
51
- "What is 20% of 50? — guide me",
52
  "", "¿Cómo calculo el 20% de 50? No me lo resuelvas, guíame paso a paso."),
53
- ("📖 Reading comprehension",
54
- "Main idea of a short passage",
55
  "El reciclaje ayuda a reducir la basura en las ciudades. Si separamos los "
56
  "plásticos y el papel, menos residuos llegan a los ríos.",
57
  "¿Cuál es la idea principal de este texto? Guíame, no me des la respuesta."),
@@ -88,7 +84,7 @@ def _generate(messages, max_new_tokens):
88
  with torch.no_grad():
89
  out = model.generate(
90
  **inputs, max_new_tokens=max_new_tokens, do_sample=True,
91
- temperature=0.7, top_p=0.9, repetition_penalty=1.1,
92
  pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id)
93
  text = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
94
  for junk in ("<think>", "</think>"):
@@ -103,7 +99,7 @@ def respond(user_msg, history, reading_text, mode, hint_mode):
103
  if model is None:
104
  return history + [
105
  {"role": "user", "content": user_msg},
106
- {"role": "assistant", "content": f"⚠️ Model failed to load ({_load_error})."},
107
  ], ""
108
  system = SYSTEM_PROMPT + MODES.get(mode, "")
109
  if reading_text and reading_text.strip():
@@ -114,7 +110,7 @@ def respond(user_msg, history, reading_text, mode, hint_mode):
114
  messages = [{"role": "system", "content": system}]
115
  messages += history[-2 * MAX_TURNS:]
116
  messages.append({"role": "user", "content": user_msg})
117
- reply = _generate(messages, max_new_tokens=180 if hint_mode else 256)
118
  return history + [
119
  {"role": "user", "content": user_msg},
120
  {"role": "assistant", "content": reply},
@@ -122,88 +118,79 @@ def respond(user_msg, history, reading_text, mode, hint_mode):
122
 
123
 
124
  CSS = """
125
- .educrate-hero {text-align:center; padding: 6px 0 2px 0;}
126
- .ex-card {border:1px solid #e5e7eb; border-radius:12px; padding:6px 10px;}
 
 
127
  footer {visibility:hidden}
128
  """
129
 
130
- with gr.Blocks(title="EduCrate — Socratic Tutor", theme=gr.themes.Soft(), css=CSS) as demo:
 
 
131
  gr.Markdown(
132
- """
133
- <div class="educrate-hero">
134
-
135
- # 🦉 EduCrate — A Socratic Tutor that **never gives the answer**
136
- **For Peruvian public secondary-school students** · Math reasoning & reading comprehension
137
- · Runs **on CPU** (0.6B) · `Build Small` · *Backyard AI* · **Tiny Titan**
138
-
139
- </div>
140
- """
141
- )
142
  if _load_error:
143
- gr.Markdown(f"> ⚠️ *Model `{MODEL_ID}` failed to load: {_load_error}*")
144
 
145
- gr.Markdown("### ✨ Try an example — click a card to start a guided dialogue")
146
  ex_buttons = []
147
  with gr.Row():
148
- for title, desc, reading, message in EXAMPLES:
149
- with gr.Column(scale=1, min_width=150):
150
- b = gr.Button(f"{title}\n{desc}", elem_classes="ex-card", size="md")
151
- ex_buttons.append((b, reading, message))
152
 
153
- with gr.Row():
154
  with gr.Column(scale=1):
155
  reading_text = gr.Textbox(
156
- label="📖 Reading passage (optional) · pega una lectura",
157
- placeholder="Paste a short text to practice reading comprehension...",
158
- lines=7,
159
- )
160
- mode = gr.Radio(choices=list(MODES.keys()), value="💬 Just chat",
161
- label="What do you need? (cambia el estilo del tutor)")
162
- hint_mode = gr.Checkbox(label="💡 Give me a single short hint")
163
  with gr.Column(scale=2):
164
- chatbot = gr.Chatbot(label="Chat with EduCrate (responde en español)", height=440)
165
  user_input = gr.Textbox(
166
- label="💬 Your question · tu pregunta", lines=2,
167
- placeholder="Escribe aquí y presiona Enviar / Type here and press Send...",
168
- )
169
  with gr.Row():
170
- submit_btn = gr.Button("Send / Enviar", variant="primary")
171
- clear_btn = gr.Button("🗑️ New topic")
172
 
173
  inp = [user_input, chatbot, reading_text, mode, hint_mode]
174
  submit_btn.click(respond, inp, [chatbot, user_input])
175
  user_input.submit(respond, inp, [chatbot, user_input])
176
  clear_btn.click(lambda: ([], "", ""), outputs=[chatbot, user_input, reading_text])
177
-
178
- # Example cards: fill inputs (clearing chat), then auto-run one Socratic turn.
179
  for b, reading, message in ex_buttons:
180
  b.click(lambda r=reading, m=message: (r, m, []),
181
  outputs=[reading_text, user_input, chatbot]).then(
182
  respond, inp, [chatbot, user_input])
183
 
184
- with gr.Accordion("ℹ️ About this project — why it matters", open=False):
185
  gr.Markdown(
186
  """
187
  **The problem.** Peru's public secondary schools face a deep learning crisis. In
188
- **PISA 2022 (OECD)**, only **34%** of Peruvian 15-year-olds reached basic proficiency in
189
- **mathematics** (66% below) and **50%** in **reading**. Peru's national assessment
190
- (**ECE / MINEDU, grade 8, 2022**) found only **~12.7%** *Satisfactory* in math — with
191
- public (state) schools far behind private ones and a large socioeconomic gap.
192
 
193
- **Why Socratic.** Most chatbots hand over the answer, which doesn't build reasoning.
194
- **EduCrate never gives the final answer** — it asks one guiding question at a time, detects
195
- the student's mistake, and offers progressive hints (the *maieutic* method) so the student
196
- discovers the answer themselves.
197
 
198
- **Why small.** A **Qwen3-0.6B** model fine-tuned (SFT + GRPO) on ~4,900 Spanish Socratic
199
- dialogues. It **runs on CPU**, so it works on the low-resource laptops common in public
200
- schools — no GPU, no paid API.
201
 
202
- **Measured behavior (held-out mGSM-es).** The fine-tune raised the *answer-withholding rate*
203
- from **84% (base) → 100%**, turning verbose solutions into concise guiding questions.
204
 
205
- *Built for the Build Small Hackathon (Backyard AI track). Made with generative AI; validate
206
- any pedagogical use with a teacher.*
207
  """
208
  )
209
 
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ EduCrate - Socratic Tutor (Gradio app)
4
  A Spanish-language Socratic tutor for Peruvian public secondary-school students.
5
 
6
+ UI in English (international judges); the tutoring happens in Spanish.
7
+ Model: Qwen3-0.6B fine-tuned (SFT). Runs on CPU. Gradio 6 (messages format).
 
8
  """
9
  import os
10
  import gradio as gr
11
  import torch
12
 
13
+ MODEL_ID = os.environ.get("MODEL_ID", "fabrizziomcl/nanoballena-qwen3-sft")
14
  MAX_TURNS = int(os.environ.get("MAX_TURNS", "8"))
15
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
16
  DTYPE = torch.bfloat16 if DEVICE == "cuda" else torch.float32
17
 
18
+ # Anchors the Socratic behavior (mirrors the training system prompt) + anti-hallucination.
19
  SYSTEM_PROMPT = (
20
  "Eres EduCrate, un tutor socrático en español para estudiantes de secundaria "
21
  "de colegios públicos del Perú (comprensión lectora y razonamiento matemático). "
22
  "REGLA ABSOLUTA: NUNCA des la respuesta final ni el resultado directamente. "
23
+ "Guía con UNA sola pregunta a la vez, detecta el error del estudiante y da pistas "
24
  "progresivas (primero suave, luego más concreta) hasta que él mismo descubra la "
25
+ "respuesta. En matemáticas usa solo el método correcto del problema; no inventes "
26
+ "reglas que no apliquen. No menciones 'el texto' a menos que el estudiante haya "
27
+ "compartido una lectura. Sé breve, claro y amable."
28
  )
29
 
 
30
  MODES = {
31
+ "Just chat": "",
32
+ "Understand my mistake": (
33
  "\n\nEl estudiante quiere entender su razonamiento: NO le des datos ni la "
34
  "respuesta; respóndele con una contrapregunta que lo haga revisar su propio paso."
35
  ),
36
+ "I need a fact or formula": (
37
+ "\n\nEl estudiante pide un dato o fórmula puntual: puedes darlo de forma breve, "
38
  "pero NUNCA lo apliques hasta la respuesta final por él; devuélvele la pregunta."
39
  ),
40
  }
41
 
42
+ # Example cards: (button label in English, reading passage, Spanish student message).
43
  EXAMPLES = [
44
+ ("Linear equation",
 
45
  "", "Ayúdame a resolver 3x + 6 = 15, pero no me des la respuesta."),
46
+ ("Adding fractions",
 
47
  "", "No entiendo cómo sumar 1/2 + 1/3. ¿Me ayudas a pensarlo?"),
48
+ ("Percentages",
 
49
  "", "¿Cómo calculo el 20% de 50? No me lo resuelvas, guíame paso a paso."),
50
+ ("Reading comprehension",
 
51
  "El reciclaje ayuda a reducir la basura en las ciudades. Si separamos los "
52
  "plásticos y el papel, menos residuos llegan a los ríos.",
53
  "¿Cuál es la idea principal de este texto? Guíame, no me des la respuesta."),
 
84
  with torch.no_grad():
85
  out = model.generate(
86
  **inputs, max_new_tokens=max_new_tokens, do_sample=True,
87
+ temperature=0.45, top_p=0.9, repetition_penalty=1.15,
88
  pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id)
89
  text = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
90
  for junk in ("<think>", "</think>"):
 
99
  if model is None:
100
  return history + [
101
  {"role": "user", "content": user_msg},
102
+ {"role": "assistant", "content": f"Model failed to load ({_load_error})."},
103
  ], ""
104
  system = SYSTEM_PROMPT + MODES.get(mode, "")
105
  if reading_text and reading_text.strip():
 
110
  messages = [{"role": "system", "content": system}]
111
  messages += history[-2 * MAX_TURNS:]
112
  messages.append({"role": "user", "content": user_msg})
113
+ reply = _generate(messages, max_new_tokens=160 if hint_mode else 220)
114
  return history + [
115
  {"role": "user", "content": user_msg},
116
  {"role": "assistant", "content": reply},
 
118
 
119
 
120
  CSS = """
121
+ .gradio-container {max-width: 1024px !important; margin: 0 auto;}
122
+ #title {text-align:center; margin-bottom: 0;}
123
+ #subtitle {text-align:center; color:#6b7280; margin-top:2px; font-size:0.95rem;}
124
+ .example-btn button {font-weight:500;}
125
  footer {visibility:hidden}
126
  """
127
 
128
+ with gr.Blocks(title="EduCrate - Socratic Tutor", theme=gr.themes.Soft(
129
+ primary_hue="slate", neutral_hue="slate"), css=CSS) as demo:
130
+ gr.Markdown("# EduCrate", elem_id="title")
131
  gr.Markdown(
132
+ "A Socratic tutor that never gives the answer — it guides with questions. "
133
+ "Spanish-language, for Peruvian public-school students. Math reasoning and "
134
+ "reading comprehension. Runs on CPU.", elem_id="subtitle")
 
 
 
 
 
 
 
135
  if _load_error:
136
+ gr.Markdown(f"> Model `{MODEL_ID}` failed to load: {_load_error}")
137
 
138
+ gr.Markdown("**Try an example** (the tutor replies in Spanish):")
139
  ex_buttons = []
140
  with gr.Row():
141
+ for label, reading, message in EXAMPLES:
142
+ b = gr.Button(label, size="sm", elem_classes="example-btn", scale=1)
143
+ ex_buttons.append((b, reading, message))
 
144
 
145
+ with gr.Row(equal_height=False):
146
  with gr.Column(scale=1):
147
  reading_text = gr.Textbox(
148
+ label="Reading passage (optional)",
149
+ placeholder="Paste a short text to practice reading comprehension.",
150
+ lines=6)
151
+ mode = gr.Radio(choices=list(MODES.keys()), value="Just chat",
152
+ label="What do you need?")
153
+ hint_mode = gr.Checkbox(label="Give me a single short hint")
 
154
  with gr.Column(scale=2):
155
+ chatbot = gr.Chatbot(label="Conversation", height=460)
156
  user_input = gr.Textbox(
157
+ label="Your question", lines=2,
158
+ placeholder="Type your question in Spanish and press Send.")
 
159
  with gr.Row():
160
+ submit_btn = gr.Button("Send", variant="primary")
161
+ clear_btn = gr.Button("New topic", variant="secondary")
162
 
163
  inp = [user_input, chatbot, reading_text, mode, hint_mode]
164
  submit_btn.click(respond, inp, [chatbot, user_input])
165
  user_input.submit(respond, inp, [chatbot, user_input])
166
  clear_btn.click(lambda: ([], "", ""), outputs=[chatbot, user_input, reading_text])
 
 
167
  for b, reading, message in ex_buttons:
168
  b.click(lambda r=reading, m=message: (r, m, []),
169
  outputs=[reading_text, user_input, chatbot]).then(
170
  respond, inp, [chatbot, user_input])
171
 
172
+ with gr.Accordion("About this project", open=False):
173
  gr.Markdown(
174
  """
175
  **The problem.** Peru's public secondary schools face a deep learning crisis. In
176
+ **PISA 2022 (OECD)**, only **34%** of Peruvian 15-year-olds reached basic proficiency
177
+ in mathematics (66% below) and **50%** in reading. Peru's national assessment
178
+ (**ECE / MINEDU, grade 8, 2022**) found only about **12.7%** *Satisfactory* in math,
179
+ with public (state) schools far behind private ones.
180
 
181
+ **The approach.** Most chatbots hand over the answer, which does not build reasoning.
182
+ EduCrate never gives the final answer — it asks one guiding question at a time, detects
183
+ the student's mistake, and offers progressive hints (the maieutic method).
 
184
 
185
+ **The model.** Qwen3-0.6B fine-tuned (SFT, with a GRPO variant) on ~4,900 Spanish
186
+ Socratic dialogues. It runs on CPU, so it works on low-resource laptops common in
187
+ public schools — no GPU and no paid API required.
188
 
189
+ **Measured behavior (held-out mGSM-es).** The fine-tune raised the answer-withholding
190
+ rate from 84% (base) to 100%, turning verbose solutions into concise guiding questions.
191
 
192
+ *It is a 0.6B model: guidance is sometimes imperfect. Built for the Build Small
193
+ Hackathon (Backyard AI). Made with generative AI; validate pedagogical use with a teacher.*
194
  """
195
  )
196