AItool commited on
Commit
7bf40ca
·
verified ·
1 Parent(s): 3c4a09c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -179
app.py CHANGED
@@ -7,7 +7,27 @@ from datetime import datetime, timedelta
7
  from transformers import AutoTokenizer, AutoModelForCausalLM
8
 
9
  # ----------------------------
10
- # Config and defaults
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # ----------------------------
12
  MODEL_OPTIONS = {
13
  "Phi-3.5 Mini Instruct (4B)": "microsoft/Phi-3.5-mini-instruct",
@@ -16,74 +36,22 @@ MODEL_OPTIONS = {
16
  "Phi-3 Mini 128K Instruct (4B)": "microsoft/Phi-3-mini-128k-instruct"
17
  }
18
 
 
 
 
19
  EXAMPLES = [
20
- # Comprehension
21
- "Listen to this short passage and tell me the main idea in your own words.",
22
-
23
- # Explanation
24
- "I’ll teach you something new: Solar panels turn sunlight into electricity. Can you explain that back to me simply?",
25
-
26
- # Vocabulary / Translation
27
- "Here’s a new phrase: 'The sea is calm today.' Try saying it in Basque, then repeat it in English.",
28
-
29
- # Style play
30
  "Let’s practice style: noir detective. Write one short sentence about Gros in that style.",
31
-
32
- # Literary reflection
33
  "Here’s a Shakespeare line: 'All the world’s a stage.' What do you think it means?",
34
-
35
- # Emotional reading
36
- "Read this Dickens passage and tell me how it feels — happy, sad, or something else?",
37
-
38
- # Poetry + translation
39
  "Translate this short poem line into another language, then tell me what mood it carries.",
40
-
41
- # Summarization + reflection
42
  "Summarize this text in two sentences, then say if it sounds optimistic or pessimistic.",
43
-
44
- # New: opinion practice
45
  "Read a short story and tell me what part you liked the most.",
46
-
47
- # New: correction loop
48
  "I’ll give you a sentence with a mistake: 'He go to school yesterday.' Can you fix it?"
49
  ]
50
 
51
- DEFAULT_PROFILE = {
52
- "name": "Learner",
53
- "style": ["concise", "reflective", "Basque context where relevant"],
54
- "goals": ["conversation-first learning", "daily language blocks", "CPU-only"]
55
- }
56
-
57
- DEFAULT_BLOCKS = [
58
- {
59
- "type": "style",
60
- "rule": "Ask clarifying questions when uncertain."
61
- },
62
- {
63
- "type": "vocab",
64
- "rule": "Use sensory detail + local place anchoring when writing creatively."
65
- },
66
- {
67
- "type": "conversation",
68
- "rule": "Keep answers short and specific; avoid repeating conclusions."
69
- },
70
- {
71
- "type": "conversation",
72
- "rule": "Offer warm, encouraging replies with a touch of humor or playfulness to lighten the mood."
73
- },
74
- {
75
- "type": "conversation",
76
- "rule": "Use light satire, gentle irony, and clever humor when appropriate to keep dialogue playful and engaging."
77
- },
78
- {
79
- "type": "conversation",
80
- "rule": "Clever swearing is allowed when it adds humor or emphasis, but keep it light, playful, and never offensive."
81
- }
82
- ]
83
-
84
-
85
- BLOCKS_FILE = "blocks.json"
86
-
87
  # ----------------------------
88
  # Persistence helpers
89
  # ----------------------------
@@ -100,18 +68,33 @@ def save_blocks(data):
100
  with open(BLOCKS_FILE, "w", encoding="utf-8") as f:
101
  json.dump(data, f, ensure_ascii=False, indent=2)
102
 
103
- def add_block(data, rule_text, block_type="conversation"):
104
- if not rule_text.strip():
105
- return data
106
- entry = {
107
- "type": block_type,
108
- "rule": rule_text.strip(),
109
- "validated": True,
110
- "review_schedule": schedule_reviews()
111
- }
112
- data["language_blocks"].append(entry)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  save_blocks(data)
114
- return data
115
 
116
  def schedule_reviews():
117
  today = datetime.utcnow().date()
@@ -124,7 +107,7 @@ def schedule_reviews():
124
  # ----------------------------
125
  # Model loading (CPU-only)
126
  # ----------------------------
127
- _loaded = {} # cache
128
 
129
  def load_model(model_id):
130
  if model_id in _loaded:
@@ -133,7 +116,7 @@ def load_model(model_id):
133
  model = AutoModelForCausalLM.from_pretrained(
134
  model_id,
135
  trust_remote_code=True,
136
- torch_dtype=torch.float32 # CPU friendly
137
  )
138
  model.eval()
139
  _loaded[model_id] = (tokenizer, model)
@@ -143,36 +126,25 @@ def load_model(model_id):
143
  # Prompt construction
144
  # ----------------------------
145
  def format_blocks(blocks):
146
- return "\n".join([f"- [{b.get('type','rule')}] {b.get('rule','')}" for b in blocks])
147
 
148
  SYSTEM_TEMPLATE = """You are a conversation-first learning chatbot.
149
  Follow the user's style and goals, reinforce today's blocks, and confirm corrections.
150
- User style: {style}
151
- Goals: {goals}
152
  Active language blocks:
153
  {blocks}
154
- Guidelines:
155
- - Keep responses concise and specific.
156
- - Ask for clarification when needed.
157
- - Extract new patterns only when validated by the user.
158
  """
159
 
160
  def build_messages(user_text, profile, blocks):
161
- system = SYSTEM_TEMPLATE.format(
162
- style=", ".join(profile.get("style", [])),
163
- goals=", ".join(profile.get("goals", [])),
164
- blocks=format_blocks(blocks)
165
- )
166
  return [
167
  {"role": "system", "content": system},
168
  {"role": "user", "content": user_text}
169
  ]
170
 
171
  # ----------------------------
172
- # Generate (with token/latency)
173
  # ----------------------------
174
  def chat(user_text, model_label, blocks_json):
175
- # parse blocks from textarea (JSON or fallback lines)
176
  data = load_blocks()
177
  blocks = parse_blocks_editor(blocks_json, data.get("language_blocks", []))
178
 
@@ -194,29 +166,22 @@ def chat(user_text, model_label, blocks_json):
194
  **inputs,
195
  max_new_tokens=200,
196
  do_sample=False,
197
- use_cache=False # Avoid DynamicCache mismatch issues on some setups
198
  )
199
  latency = time.time() - start
200
 
201
- # slice out the generated continuation
202
  gen_text = tokenizer.decode(
203
  outputs[0][inputs["input_ids"].shape[-1]:],
204
  skip_special_tokens=True
205
  ).strip()
206
 
207
- # token counts
208
  input_tokens = int(inputs["input_ids"].shape[-1])
209
  output_tokens = int(outputs[0].shape[-1] - inputs["input_ids"].shape[-1])
210
-
211
  metrics = f"Input tokens: {input_tokens} | Output tokens: {output_tokens} | Latency: {latency:.2f}s"
 
212
  return gen_text, metrics
213
 
214
  def parse_blocks_editor(text, fallback):
215
- """
216
- Accept either:
217
- - JSON array of blocks
218
- - Plain text lines ("type: rule")
219
- """
220
  if not text or not text.strip():
221
  return fallback
222
  text = text.strip()
@@ -226,7 +191,6 @@ def parse_blocks_editor(text, fallback):
226
  return parsed
227
  except Exception:
228
  pass
229
- # Fallback: each non-empty line becomes a block
230
  blocks = []
231
  for line in text.splitlines():
232
  line = line.strip()
@@ -240,37 +204,11 @@ def parse_blocks_editor(text, fallback):
240
  return blocks or fallback
241
 
242
  # ----------------------------
243
- # Reflection: extract new rule
244
  # ----------------------------
245
- REFLECT_TEMPLATE = """From the user's last message and your reply, extract ONE reusable conversation rule.
246
- Return only the rule, no preface, max 20 words.
247
- Example rules:
248
- - Ask clarifying questions when uncertain.
249
- - Use sensory detail with local anchors in creative writing.
250
- - Summarize then assess tone (optimistic/pessimistic).
251
- User said:
252
- {user}
253
- Assistant replied:
254
- {assistant}
255
- Now output one new rule:"""
256
-
257
- def reflect_and_save(user_text, assistant_text, blocks_editor_value):
258
- data = load_blocks()
259
- # Propose a rule via a simple heuristic (no extra model call, keeps it lean)
260
- # If you prefer model-based reflection, you can run a generation with REFLECT_TEMPLATE.
261
- proposal = heuristic_rule(user_text, assistant_text)
262
- data = add_block(data, proposal, block_type="conversation")
263
-
264
- # Return updated blocks as pretty JSON to show in the editor
265
- pretty = json.dumps(data["language_blocks"], ensure_ascii=False, indent=2)
266
- return pretty, f"Saved rule: {proposal}"
267
-
268
  def heuristic_rule(user_text, assistant_text):
269
- # Very simple heuristic: if assistant asked a question, reinforce clarification;
270
- # otherwise, reinforce concise responses.
271
  if "?" in assistant_text:
272
  return "Ask clarifying questions when uncertain."
273
- # If user asked for style or translation, capture that
274
  low = user_text.lower()
275
  if "translate" in low:
276
  return "Confirm translation intent and target tone before translating."
@@ -278,6 +216,13 @@ def heuristic_rule(user_text, assistant_text):
278
  return "Confirm style constraints before writing and keep it concise."
279
  return "Keep answers short, specific, and avoid repeating conclusions."
280
 
 
 
 
 
 
 
 
281
  # ----------------------------
282
  # Gradio UI
283
  # ----------------------------
@@ -287,58 +232,4 @@ def launch():
287
 
288
  with gr.Blocks(title="Conversation Learning Lab (CPU)") as demo:
289
  gr.Markdown("# 🗣️ Conversation Learning Lab (CPU-friendly)")
290
- gr.Markdown("Focus on daily dialogue. Reinforce validated language blocks. Transparent tokens and latency.")
291
-
292
- with gr.Row():
293
- model_dd = gr.Dropdown(
294
- label="Choose a model",
295
- choices=list(MODEL_OPTIONS.keys()),
296
- value="Phi-3.5 Mini Instruct (4B)"
297
- )
298
-
299
- with gr.Row():
300
- user_in = gr.Textbox(
301
- label="Your message",
302
- placeholder="Start a conversation or choose an example below...",
303
- lines=3
304
- )
305
-
306
- gr.Markdown("### 🧪 Try an example prompt:")
307
- gr.Examples(
308
- examples=EXAMPLES,
309
- inputs=user_in
310
- )
311
-
312
- with gr.Row():
313
- blocks_editor = gr.Textbox(
314
- label="Today's blocks (JSON array or 'type: rule' lines)",
315
- value=default_blocks_text,
316
- lines=10
317
- )
318
-
319
- with gr.Row():
320
- generate_btn = gr.Button("Generate (CPU)")
321
- reflect_btn = gr.Button("Reflect & Save Rule")
322
-
323
- with gr.Row():
324
- output = gr.Textbox(label="Assistant", lines=8)
325
- with gr.Row():
326
- metrics = gr.Markdown("")
327
-
328
- # Wire up events
329
- generate_btn.click(
330
- fn=chat,
331
- inputs=[user_in, model_dd, blocks_editor],
332
- outputs=[output, metrics]
333
- )
334
-
335
- reflect_btn.click(
336
- fn=reflect_and_save,
337
- inputs=[user_in, output, blocks_editor],
338
- outputs=[blocks_editor, metrics]
339
- )
340
-
341
- demo.launch()
342
-
343
- if __name__ == "__main__":
344
- launch()
 
7
  from transformers import AutoTokenizer, AutoModelForCausalLM
8
 
9
  # ----------------------------
10
+ # Default profile and blocks
11
+ # ----------------------------
12
+ DEFAULT_PROFILE = {
13
+ "name": "Learner",
14
+ "style": ["concise", "reflective", "Basque context where relevant"],
15
+ "goals": ["conversation-first learning", "daily language blocks", "CPU-only"]
16
+ }
17
+
18
+ DEFAULT_BLOCKS = [
19
+ {"type": "style", "rule": "Ask clarifying questions when uncertain."},
20
+ {"type": "vocab", "rule": "Use sensory detail + local place anchoring when writing creatively."},
21
+ {"type": "conversation", "rule": "Keep answers short and specific; avoid repeating conclusions."},
22
+ {"type": "conversation", "rule": "Offer warm, encouraging replies with a touch of humor or playfulness to lighten the mood."},
23
+ {"type": "conversation", "rule": "Use light satire, gentle irony, and clever humor when appropriate to keep dialogue playful and engaging."},
24
+ {"type": "conversation", "rule": "Clever swearing is allowed when it adds humor or emphasis, but keep it light, playful, and never offensive."}
25
+ ]
26
+
27
+ BLOCKS_FILE = "blocks.json"
28
+
29
+ # ----------------------------
30
+ # Model options
31
  # ----------------------------
32
  MODEL_OPTIONS = {
33
  "Phi-3.5 Mini Instruct (4B)": "microsoft/Phi-3.5-mini-instruct",
 
36
  "Phi-3 Mini 128K Instruct (4B)": "microsoft/Phi-3-mini-128k-instruct"
37
  }
38
 
39
+ # ----------------------------
40
+ # Example prompts
41
+ # ----------------------------
42
  EXAMPLES = [
43
+ "Read this short passage and tell me the main idea in your own words.",
44
+ "I’ll teach you a concept. Repeat it back to me in simple words: Solar panels turn sunlight into electricity.",
45
+ "Here’s a new phrase: 'The sea is calm today.' Try saying it in Basque.",
 
 
 
 
 
 
 
46
  "Let’s practice style: noir detective. Write one short sentence about Gros in that style.",
 
 
47
  "Here’s a Shakespeare line: 'All the world’s a stage.' What do you think it means?",
48
+ "Read a Dickens passage and tell me how it feels — happy, sad, or something else?",
 
 
 
 
49
  "Translate this short poem line into another language, then tell me what mood it carries.",
 
 
50
  "Summarize this text in two sentences, then say if it sounds optimistic or pessimistic.",
 
 
51
  "Read a short story and tell me what part you liked the most.",
 
 
52
  "I’ll give you a sentence with a mistake: 'He go to school yesterday.' Can you fix it?"
53
  ]
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # ----------------------------
56
  # Persistence helpers
57
  # ----------------------------
 
68
  with open(BLOCKS_FILE, "w", encoding="utf-8") as f:
69
  json.dump(data, f, ensure_ascii=False, indent=2)
70
 
71
+ def normalize_rule_text(text: str) -> str:
72
+ return " ".join(text.strip().split())
73
+
74
+ def is_duplicate_rule(rules_list, new_rule_text, new_type="conversation"):
75
+ key = (new_type.lower(), normalize_rule_text(new_rule_text).lower())
76
+ for r in rules_list:
77
+ if (r.get("type", "").lower(), normalize_rule_text(r.get("rule", "")).lower()) == key:
78
+ return True
79
+ return False
80
+
81
+ def add_block(data, rule_text, block_type="conversation", add_review=False):
82
+ rule_text = normalize_rule_text(rule_text)
83
+ if not rule_text:
84
+ return data, "Rule is empty. Nothing added."
85
+
86
+ rules = data.get("language_blocks", [])
87
+ if is_duplicate_rule(rules, rule_text, block_type):
88
+ return data, "Duplicate rule detected. Skipped."
89
+
90
+ entry = {"type": block_type, "rule": rule_text}
91
+ if add_review:
92
+ entry["review_schedule"] = schedule_reviews()
93
+
94
+ rules.append(entry)
95
+ data["language_blocks"] = rules
96
  save_blocks(data)
97
+ return data, f"Added rule: {rule_text}"
98
 
99
  def schedule_reviews():
100
  today = datetime.utcnow().date()
 
107
  # ----------------------------
108
  # Model loading (CPU-only)
109
  # ----------------------------
110
+ _loaded = {}
111
 
112
  def load_model(model_id):
113
  if model_id in _loaded:
 
116
  model = AutoModelForCausalLM.from_pretrained(
117
  model_id,
118
  trust_remote_code=True,
119
+ torch_dtype=torch.float32
120
  )
121
  model.eval()
122
  _loaded[model_id] = (tokenizer, model)
 
126
  # Prompt construction
127
  # ----------------------------
128
  def format_blocks(blocks):
129
+ return "\n".join([f"- [{b['type']}] {b['rule']}" for b in blocks])
130
 
131
  SYSTEM_TEMPLATE = """You are a conversation-first learning chatbot.
132
  Follow the user's style and goals, reinforce today's blocks, and confirm corrections.
 
 
133
  Active language blocks:
134
  {blocks}
 
 
 
 
135
  """
136
 
137
  def build_messages(user_text, profile, blocks):
138
+ system = SYSTEM_TEMPLATE.format(blocks=format_blocks(blocks))
 
 
 
 
139
  return [
140
  {"role": "system", "content": system},
141
  {"role": "user", "content": user_text}
142
  ]
143
 
144
  # ----------------------------
145
+ # Generate
146
  # ----------------------------
147
  def chat(user_text, model_label, blocks_json):
 
148
  data = load_blocks()
149
  blocks = parse_blocks_editor(blocks_json, data.get("language_blocks", []))
150
 
 
166
  **inputs,
167
  max_new_tokens=200,
168
  do_sample=False,
169
+ use_cache=False
170
  )
171
  latency = time.time() - start
172
 
 
173
  gen_text = tokenizer.decode(
174
  outputs[0][inputs["input_ids"].shape[-1]:],
175
  skip_special_tokens=True
176
  ).strip()
177
 
 
178
  input_tokens = int(inputs["input_ids"].shape[-1])
179
  output_tokens = int(outputs[0].shape[-1] - inputs["input_ids"].shape[-1])
 
180
  metrics = f"Input tokens: {input_tokens} | Output tokens: {output_tokens} | Latency: {latency:.2f}s"
181
+
182
  return gen_text, metrics
183
 
184
  def parse_blocks_editor(text, fallback):
 
 
 
 
 
185
  if not text or not text.strip():
186
  return fallback
187
  text = text.strip()
 
191
  return parsed
192
  except Exception:
193
  pass
 
194
  blocks = []
195
  for line in text.splitlines():
196
  line = line.strip()
 
204
  return blocks or fallback
205
 
206
  # ----------------------------
207
+ # Reflection
208
  # ----------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  def heuristic_rule(user_text, assistant_text):
 
 
210
  if "?" in assistant_text:
211
  return "Ask clarifying questions when uncertain."
 
212
  low = user_text.lower()
213
  if "translate" in low:
214
  return "Confirm translation intent and target tone before translating."
 
216
  return "Confirm style constraints before writing and keep it concise."
217
  return "Keep answers short, specific, and avoid repeating conclusions."
218
 
219
+ def reflect_and_save(user_text, assistant_text, blocks_editor_value):
220
+ data = load_blocks()
221
+ proposal = heuristic_rule(user_text, assistant_text)
222
+ data, msg = add_block(data, proposal, block_type="conversation", add_review=False)
223
+ pretty = json.dumps(data["language_blocks"], ensure_ascii=False, indent=2)
224
+ return pretty, msg
225
+
226
  # ----------------------------
227
  # Gradio UI
228
  # ----------------------------
 
232
 
233
  with gr.Blocks(title="Conversation Learning Lab (CPU)") as demo:
234
  gr.Markdown("# 🗣️ Conversation Learning Lab (CPU-friendly)")
235
+ gr.Markdown("Focus on daily dialogue. Reinforce validated language