vedaco commited on
Commit
5e1076e
·
verified ·
1 Parent(s): e926670

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -35
app.py CHANGED
@@ -1,9 +1,10 @@
1
  """
2
  Veda Programming Assistant (Gradio 6.x)
3
  - Hidden teacher fallback (OpenRouter) when student fails
 
4
  - Auto-training in background using teacher responses
5
  - Math solver for simple arithmetic
6
- - Compatible with Gradio "messages format" + multimodal inputs
7
  """
8
 
9
  import os
@@ -39,13 +40,18 @@ current_conv_id = -1
39
  # Teacher usage stats (not shown in chat)
40
  teacher_used_count = 0
41
  teacher_failed_count = 0
 
 
 
 
 
42
 
43
  # Auto-training control
44
  AUTO_TRAIN_ENABLED = True
45
  AUTO_TRAIN_MIN_TEACHER_SAMPLES = 10 # retrain after this many new teacher samples
46
  AUTO_TRAIN_CHECK_EVERY_SEC = 120 # check every 2 minutes
47
- AUTO_TRAIN_EPOCHS = 5 # keep small for Spaces CPU
48
- AUTO_TRAIN_COOLDOWN_SEC = 60 * 20 # at least 20 minutes between trainings
49
 
50
  _is_training = False
51
  _last_train_time = 0
@@ -147,7 +153,6 @@ def try_math_answer(user_text: str):
147
  s = s.replace("=", "").replace("?", "").strip()
148
  s = s.replace("^", "**") # allow ^
149
 
150
- # only allow numeric math chars
151
  if not re.fullmatch(r"[0-9\.\s\+\-\*\/\(\)%]+", s):
152
  return None
153
 
@@ -168,20 +173,33 @@ def is_code_request(user_text: str) -> bool:
168
  triggers = [
169
  "write", "implement", "code", "function", "algorithm",
170
  "bubble sort", "binary search", "merge sort", "quick sort", "quicksort",
171
- "linked list", "stack", "queue", "class ", "def "
 
172
  ]
173
  return any(k in t for k in triggers)
174
 
175
  def looks_like_python_code(text: str) -> bool:
 
 
 
 
176
  if not text:
177
  return False
178
  t = text.strip()
 
179
  if "```" in t:
180
  return True
181
- if "def " in t or "class " in t:
 
 
 
 
182
  return True
183
- if "\n " in t:
 
 
184
  return True
 
185
  return False
186
 
187
  def is_gibberish(text: str) -> bool:
@@ -189,12 +207,11 @@ def is_gibberish(text: str) -> bool:
189
  return True
190
  t = text.strip()
191
 
192
- # repeated greeting
193
- if t.lower().count("hello how are you") >= 2:
194
  return True
195
 
196
- # too short
197
- if len(t) < 25:
198
  return True
199
 
200
  # lots of symbols vs letters
@@ -210,31 +227,41 @@ def is_gibberish(text: str) -> bool:
210
  if uniq_ratio < 0.35:
211
  return True
212
 
213
- # known “junk patterns
214
  junk_patterns = [
215
- r"\[\s*\"?\s*\]", # empty brackets patterns
216
  r"return\s+if\s+is",
217
  r"=\s*=\s*=",
218
  r"def\s+def",
219
  r"class\s+class",
220
  r"return\s+return",
 
221
  ]
222
  for p in junk_patterns:
223
  if re.search(p, t):
224
  return True
225
 
 
 
 
 
 
 
226
  return False
227
 
228
  def should_use_teacher(user_text: str, student_text: str) -> bool:
229
- # teacher must be available
230
  if not teacher.is_available():
231
  return False
232
 
233
- # always use teacher for code requests unless student produced real code
 
 
 
 
 
 
234
  if is_code_request(user_text) and not looks_like_python_code(student_text):
235
  return True
236
 
237
- # use teacher if student output is gibberish
238
  if is_gibberish(student_text):
239
  return True
240
 
@@ -295,7 +322,6 @@ def clean_response(text: str) -> str:
295
  for token in ["<PAD>", "<UNK>", "<START>", "<END>", "<USER>", "<ASSISTANT>"]:
296
  text = text.replace(token, "")
297
 
298
- # reduce empty lines
299
  lines = text.split("\n")
300
  cleaned = []
301
  empty = 0
@@ -314,10 +340,10 @@ def clean_response(text: str) -> str:
314
  # STUDENT + TEACHER RESPONSE
315
  # -----------------------------
316
  def get_student_response(user_text: str, temperature: float, max_tokens: int) -> str:
 
317
  if model is None or tokenizer is None:
318
  return ""
319
 
320
- # build context from internal conversation_history
321
  context = ""
322
  for m in conversation_history[-3:]:
323
  context += f"<USER> {m['user']}\n<ASSISTANT> {m['assistant']}\n"
@@ -344,11 +370,11 @@ def get_student_response(user_text: str, temperature: float, max_tokens: int) ->
344
  if "<USER>" in out:
345
  out = out.split("<USER>")[0].strip()
346
 
 
347
  return clean_response(out)
348
 
349
 
350
  def get_teacher_response(user_text: str) -> str:
351
- # Build teacher history from our internal conversation_history
352
  teacher_hist = []
353
  for m in conversation_history[-4:]:
354
  teacher_hist.append({"role": "user", "content": m["user"]})
@@ -367,7 +393,7 @@ def generate_response(user_input, temperature=0.7, max_tokens=200) -> str:
367
  if not user_text:
368
  return "Please type a message."
369
 
370
- # math solver first
371
  math_ans = try_math_answer(user_text)
372
  if math_ans is not None:
373
  conversation_history.append({"user": user_text, "assistant": math_ans})
@@ -375,15 +401,15 @@ def generate_response(user_input, temperature=0.7, max_tokens=200) -> str:
375
  return math_ans
376
 
377
  # student attempt
378
- student = get_student_response(user_text, temperature, max_tokens)
379
 
380
- # teacher fallback
381
  if should_use_teacher(user_text, student):
382
  teacher_resp = get_teacher_response(user_text)
 
383
  if teacher_resp.strip():
384
  teacher_used_count += 1
385
 
386
- # save distillation sample
387
  try:
388
  db.save_distillation_data(
389
  user_input=user_text,
@@ -428,7 +454,6 @@ def auto_train_loop():
428
  if _train_lock.locked():
429
  continue
430
 
431
- # check distillation samples
432
  try:
433
  unused = db.get_unused_distillation_data(limit=1000)
434
  except Exception as e:
@@ -438,10 +463,9 @@ def auto_train_loop():
438
  if len(unused) < AUTO_TRAIN_MIN_TEACHER_SAMPLES:
439
  continue
440
 
441
- # train in this background thread
442
  with _train_lock:
443
  _is_training = True
444
- print(f"[AutoTrain] Starting training on {len(unused)} teacher samples...")
445
 
446
  try:
447
  distill_text = ""
@@ -450,7 +474,7 @@ def auto_train_loop():
450
  ids.append(row["id"])
451
  distill_text += f"<USER> {row['user_input']}\n<ASSISTANT> {row['teacher_response']}\n\n"
452
 
453
- # include good user-rated conversations too
454
  extra = ""
455
  try:
456
  good = db.get_good_conversations(limit=200)
@@ -469,7 +493,6 @@ def auto_train_loop():
469
  model = trainer.model
470
  tokenizer = trainer.tokenizer
471
 
472
- # mark distillation used
473
  try:
474
  db.mark_distillation_used(ids)
475
  except Exception as e:
@@ -535,13 +558,13 @@ def clear_chat():
535
  def get_stats_md():
536
  stats = db.get_stats()
537
  teacher_ok = teacher.is_available()
538
-
539
  return f"""
540
  ## Statistics
541
 
542
  **Teacher available:** `{teacher_ok}`
543
  **Teacher used (this runtime):** `{teacher_used_count}`
544
  **Teacher failed (this runtime):** `{teacher_failed_count}`
 
545
  **Auto-training enabled:** `{AUTO_TRAIN_ENABLED}`
546
  **Currently training:** `{_is_training}`
547
 
@@ -579,8 +602,6 @@ with gr.Blocks(title="Veda Programming Assistant") as demo:
579
  # Veda Programming Assistant
580
 
581
  Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
582
-
583
- (Teacher is hidden. Auto-learning is automatic.)
584
  """
585
  )
586
 
@@ -591,7 +612,7 @@ Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
591
  with gr.Row():
592
  msg = gr.Textbox(
593
  label="Message",
594
- placeholder="Example: Write bubble sort",
595
  lines=2,
596
  scale=4,
597
  )
@@ -618,8 +639,8 @@ Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
618
  gr.Examples(
619
  examples=[
620
  ["Write bubble sort in python"],
621
- ["Write binary search"],
622
- ["Explain recursion"],
623
  ["2+2=?"],
624
  ["(10+5)/3"],
625
  ["2^5"],
@@ -631,7 +652,6 @@ Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
631
  stats_md = gr.Markdown()
632
  refresh = gr.Button("Refresh")
633
  refresh.click(get_stats_md, outputs=stats_md)
634
- # Show stats immediately
635
  demo.load(get_stats_md, outputs=stats_md)
636
 
637
  if __name__ == "__main__":
 
1
  """
2
  Veda Programming Assistant (Gradio 6.x)
3
  - Hidden teacher fallback (OpenRouter) when student fails
4
+ - IMPORTANT FIX: Always use teacher for CODE requests (bubble sort etc.)
5
  - Auto-training in background using teacher responses
6
  - Math solver for simple arithmetic
7
+ - Compatible with Gradio messages format + multimodal inputs
8
  """
9
 
10
  import os
 
40
  # Teacher usage stats (not shown in chat)
41
  teacher_used_count = 0
42
  teacher_failed_count = 0
43
+ student_used_count = 0
44
+
45
+ # ---- IMPORTANT BEHAVIOR SWITCH ----
46
+ # Forces teacher for code requests so user sees correct code now.
47
+ FORCE_TEACHER_FOR_CODE_REQUESTS = True
48
 
49
  # Auto-training control
50
  AUTO_TRAIN_ENABLED = True
51
  AUTO_TRAIN_MIN_TEACHER_SAMPLES = 10 # retrain after this many new teacher samples
52
  AUTO_TRAIN_CHECK_EVERY_SEC = 120 # check every 2 minutes
53
+ AUTO_TRAIN_EPOCHS = 3 # keep small for Spaces CPU
54
+ AUTO_TRAIN_COOLDOWN_SEC = 60 * 20 # 20 minutes between trainings
55
 
56
  _is_training = False
57
  _last_train_time = 0
 
153
  s = s.replace("=", "").replace("?", "").strip()
154
  s = s.replace("^", "**") # allow ^
155
 
 
156
  if not re.fullmatch(r"[0-9\.\s\+\-\*\/\(\)%]+", s):
157
  return None
158
 
 
173
  triggers = [
174
  "write", "implement", "code", "function", "algorithm",
175
  "bubble sort", "binary search", "merge sort", "quick sort", "quicksort",
176
+ "linked list", "stack", "queue", "class ", "def ",
177
+ "sort "
178
  ]
179
  return any(k in t for k in triggers)
180
 
181
  def looks_like_python_code(text: str) -> bool:
182
+ """
183
+ Stronger code detector.
184
+ Only returns True if we see real python structure.
185
+ """
186
  if not text:
187
  return False
188
  t = text.strip()
189
+
190
  if "```" in t:
191
  return True
192
+
193
+ # must contain python keywords + structure
194
+ if "def " in t and ":" in t:
195
+ return True
196
+ if "class " in t and ":" in t:
197
  return True
198
+
199
+ # allow indented blocks only if also includes python keywords
200
+ if "\n " in t and ("for " in t or "while " in t or "if " in t or "return " in t):
201
  return True
202
+
203
  return False
204
 
205
  def is_gibberish(text: str) -> bool:
 
207
  return True
208
  t = text.strip()
209
 
210
+ if len(t) < 25:
 
211
  return True
212
 
213
+ # repeated greeting
214
+ if t.lower().count("hello how are you") >= 1:
215
  return True
216
 
217
  # lots of symbols vs letters
 
227
  if uniq_ratio < 0.35:
228
  return True
229
 
230
+ # junk patterns
231
  junk_patterns = [
 
232
  r"return\s+if\s+is",
233
  r"=\s*=\s*=",
234
  r"def\s+def",
235
  r"class\s+class",
236
  r"return\s+return",
237
+ r"\[\s*\"?\s*\]",
238
  ]
239
  for p in junk_patterns:
240
  if re.search(p, t):
241
  return True
242
 
243
+ # “p y t h o n” style (too many single-letter tokens)
244
+ single_letter_words = re.findall(r"\b[a-zA-Z]\b", t)
245
+ word_count = len(re.findall(r"\b[a-zA-Z_]+\b", t))
246
+ if word_count > 0 and (len(single_letter_words) / word_count) > 0.4:
247
+ return True
248
+
249
  return False
250
 
251
  def should_use_teacher(user_text: str, student_text: str) -> bool:
 
252
  if not teacher.is_available():
253
  return False
254
 
255
+ # IMPORTANT: Force teacher for code requests (until student becomes good)
256
+ if FORCE_TEACHER_FOR_CODE_REQUESTS and is_code_request(user_text):
257
+ # If student actually produced code, you could skip teacher,
258
+ # but early stage student is bad, so use teacher always.
259
+ return True
260
+
261
+ # fallback to teacher if gibberish or not code when code asked
262
  if is_code_request(user_text) and not looks_like_python_code(student_text):
263
  return True
264
 
 
265
  if is_gibberish(student_text):
266
  return True
267
 
 
322
  for token in ["<PAD>", "<UNK>", "<START>", "<END>", "<USER>", "<ASSISTANT>"]:
323
  text = text.replace(token, "")
324
 
 
325
  lines = text.split("\n")
326
  cleaned = []
327
  empty = 0
 
340
  # STUDENT + TEACHER RESPONSE
341
  # -----------------------------
342
  def get_student_response(user_text: str, temperature: float, max_tokens: int) -> str:
343
+ global student_used_count
344
  if model is None or tokenizer is None:
345
  return ""
346
 
 
347
  context = ""
348
  for m in conversation_history[-3:]:
349
  context += f"<USER> {m['user']}\n<ASSISTANT> {m['assistant']}\n"
 
370
  if "<USER>" in out:
371
  out = out.split("<USER>")[0].strip()
372
 
373
+ student_used_count += 1
374
  return clean_response(out)
375
 
376
 
377
  def get_teacher_response(user_text: str) -> str:
 
378
  teacher_hist = []
379
  for m in conversation_history[-4:]:
380
  teacher_hist.append({"role": "user", "content": m["user"]})
 
393
  if not user_text:
394
  return "Please type a message."
395
 
396
+ # math first
397
  math_ans = try_math_answer(user_text)
398
  if math_ans is not None:
399
  conversation_history.append({"user": user_text, "assistant": math_ans})
 
401
  return math_ans
402
 
403
  # student attempt
404
+ student = get_student_response(user_text, float(temperature), int(max_tokens))
405
 
 
406
  if should_use_teacher(user_text, student):
407
  teacher_resp = get_teacher_response(user_text)
408
+
409
  if teacher_resp.strip():
410
  teacher_used_count += 1
411
 
412
+ # Save distillation sample
413
  try:
414
  db.save_distillation_data(
415
  user_input=user_text,
 
454
  if _train_lock.locked():
455
  continue
456
 
 
457
  try:
458
  unused = db.get_unused_distillation_data(limit=1000)
459
  except Exception as e:
 
463
  if len(unused) < AUTO_TRAIN_MIN_TEACHER_SAMPLES:
464
  continue
465
 
 
466
  with _train_lock:
467
  _is_training = True
468
+ print(f"[AutoTrain] Training on {len(unused)} teacher samples...")
469
 
470
  try:
471
  distill_text = ""
 
474
  ids.append(row["id"])
475
  distill_text += f"<USER> {row['user_input']}\n<ASSISTANT> {row['teacher_response']}\n\n"
476
 
477
+ # include user-positive feedback too
478
  extra = ""
479
  try:
480
  good = db.get_good_conversations(limit=200)
 
493
  model = trainer.model
494
  tokenizer = trainer.tokenizer
495
 
 
496
  try:
497
  db.mark_distillation_used(ids)
498
  except Exception as e:
 
558
  def get_stats_md():
559
  stats = db.get_stats()
560
  teacher_ok = teacher.is_available()
 
561
  return f"""
562
  ## Statistics
563
 
564
  **Teacher available:** `{teacher_ok}`
565
  **Teacher used (this runtime):** `{teacher_used_count}`
566
  **Teacher failed (this runtime):** `{teacher_failed_count}`
567
+ **Student calls (this runtime):** `{student_used_count}`
568
  **Auto-training enabled:** `{AUTO_TRAIN_ENABLED}`
569
  **Currently training:** `{_is_training}`
570
 
 
602
  # Veda Programming Assistant
603
 
604
  Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
 
 
605
  """
606
  )
607
 
 
612
  with gr.Row():
613
  msg = gr.Textbox(
614
  label="Message",
615
+ placeholder="Example: Write bubble sort in python",
616
  lines=2,
617
  scale=4,
618
  )
 
639
  gr.Examples(
640
  examples=[
641
  ["Write bubble sort in python"],
642
+ ["Write binary search in python"],
643
+ ["Explain recursion with example"],
644
  ["2+2=?"],
645
  ["(10+5)/3"],
646
  ["2^5"],
 
652
  stats_md = gr.Markdown()
653
  refresh = gr.Button("Refresh")
654
  refresh.click(get_stats_md, outputs=stats_md)
 
655
  demo.load(get_stats_md, outputs=stats_md)
656
 
657
  if __name__ == "__main__":