vedaco commited on
Commit
304f12d
·
verified ·
1 Parent(s): c4cd8de

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -637
app.py CHANGED
@@ -1,23 +1,8 @@
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
11
  import json
12
- import time
13
- import threading
14
- import re
15
- import ast
16
- import operator as op
17
-
18
- import gradio as gr
19
- import tensorflow as tf
20
-
21
  from model import VedaProgrammingLLM
22
  from tokenizer import VedaTokenizer
23
  from database import db
@@ -25,634 +10,87 @@ from train import VedaTrainer
25
  from teacher import teacher
26
  from config import MODEL_DIR
27
 
28
-
29
- # -----------------------------
30
- # GLOBALS
31
- # -----------------------------
32
  model = None
33
  tokenizer = None
 
34
 
35
- # For building student prompt context
36
- conversation_history = [] # list of dicts: {"user": "...", "assistant": "..."}
37
-
38
- current_conv_id = -1
39
-
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
58
- _train_lock = threading.Lock()
59
-
60
-
61
- # -----------------------------
62
- # GRADIO INPUT HELPERS
63
- # -----------------------------
64
- def extract_text(message):
65
- """
66
- Convert Gradio multimodal/messages -> plain string.
67
- Handles:
68
- - str
69
- - dict {"text": "..."} or {"content": ...}
70
- - list [{"type":"text","text":"..."}]
71
- """
72
- if message is None:
73
- return ""
74
- if isinstance(message, str):
75
- return message
76
-
77
- if isinstance(message, dict):
78
- if "text" in message:
79
- return str(message.get("text", ""))
80
- if "content" in message:
81
- return extract_text(message["content"])
82
- return ""
83
-
84
- if isinstance(message, list):
85
- out = []
86
- for part in message:
87
- if isinstance(part, dict) and part.get("type") == "text":
88
- out.append(str(part.get("text", "")))
89
- elif isinstance(part, str):
90
- out.append(part)
91
- return "".join(out).strip()
92
-
93
- return str(message)
94
-
95
-
96
- def ensure_messages_history(history):
97
- """
98
- Ensure history is messages-format list:
99
- [{"role":"user","content":"..."}, {"role":"assistant","content":"..."}]
100
- Convert tuple format if needed.
101
- """
102
- if history is None:
103
- return []
104
-
105
- # already messages format
106
- if len(history) > 0 and isinstance(history[0], dict) and "role" in history[0] and "content" in history[0]:
107
- fixed = []
108
- for m in history:
109
- fixed.append({"role": m["role"], "content": extract_text(m["content"])})
110
- return fixed
111
-
112
- # tuple format -> messages format
113
- fixed = []
114
- for pair in history:
115
- if isinstance(pair, (list, tuple)) and len(pair) == 2:
116
- fixed.append({"role": "user", "content": extract_text(pair[0])})
117
- fixed.append({"role": "assistant", "content": extract_text(pair[1])})
118
- return fixed
119
-
120
-
121
- # -----------------------------
122
- # SAFE MATH SOLVER
123
- # -----------------------------
124
- _ALLOWED_OPS = {
125
- ast.Add: op.add,
126
- ast.Sub: op.sub,
127
- ast.Mult: op.mul,
128
- ast.Div: op.truediv,
129
- ast.Mod: op.mod,
130
- ast.Pow: op.pow,
131
- ast.USub: op.neg,
132
- ast.UAdd: op.pos,
133
- }
134
-
135
- def safe_eval_math(expr: str):
136
- node = ast.parse(expr, mode="eval").body
137
-
138
- def _eval(n):
139
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)):
140
- return n.value
141
- if isinstance(n, ast.BinOp) and type(n.op) in _ALLOWED_OPS:
142
- return _ALLOWED_OPS[type(n.op)](_eval(n.left), _eval(n.right))
143
- if isinstance(n, ast.UnaryOp) and type(n.op) in _ALLOWED_OPS:
144
- return _ALLOWED_OPS[type(n.op)](_eval(n.operand))
145
- raise ValueError("Unsupported expression")
146
-
147
- return _eval(node)
148
-
149
- def try_math_answer(user_text: str):
150
- if not user_text:
151
- return None
152
- s = user_text.strip()
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
-
159
- try:
160
- val = safe_eval_math(s)
161
- if isinstance(val, float) and val.is_integer():
162
- val = int(val)
163
- return str(val)
164
- except Exception:
165
- return None
166
-
167
-
168
- # -----------------------------
169
- # QUALITY CHECK + TEACHER TRIGGER
170
- # -----------------------------
171
- def is_code_request(user_text: str) -> bool:
172
- t = user_text.lower()
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:
206
- if not text:
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
218
- letters = sum(c.isalpha() for c in t)
219
- special = sum(c in "[]{}()=<>|\\" for c in t)
220
- if letters > 0 and (special / max(letters, 1)) > 0.35:
221
- return True
222
-
223
- # low unique word ratio
224
- words = re.findall(r"[a-zA-Z_]+", t.lower())
225
- if len(words) >= 20:
226
- uniq_ratio = len(set(words)) / len(words)
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
-
268
- return False
269
-
270
-
271
- # -----------------------------
272
- # MODEL LOAD
273
- # -----------------------------
274
- def initialize():
275
  global model, tokenizer
276
-
277
- print("Initializing Veda Programming Assistant...")
278
-
279
- config_path = os.path.join(MODEL_DIR, "config.json")
280
- weights_path = os.path.join(MODEL_DIR, "weights.h5")
281
- tok_path = os.path.join(MODEL_DIR, "tokenizer.json")
282
-
283
- if os.path.exists(config_path) and os.path.exists(weights_path) and os.path.exists(tok_path):
284
- print("Loading existing model...")
285
-
286
- with open(config_path, "r") as f:
287
- config = json.load(f)
288
-
289
  tokenizer = VedaTokenizer()
290
- tokenizer.load(tok_path)
291
-
292
- model = VedaProgrammingLLM(
293
- vocab_size=config["vocab_size"],
294
- max_length=config["max_length"],
295
- d_model=config["d_model"],
296
- num_heads=config["num_heads"],
297
- num_layers=config["num_layers"],
298
- ff_dim=config["ff_dim"],
299
- )
300
-
301
- dummy = tf.zeros((1, config["max_length"]), dtype=tf.int32)
302
- model(dummy)
303
- model.load_weights(weights_path)
304
-
305
- print("Model loaded.")
306
- else:
307
- print("No saved model found. Training initial model...")
308
- trainer = VedaTrainer()
309
- trainer.train(epochs=10)
310
- model = trainer.model
311
- tokenizer = trainer.tokenizer
312
- print("Initial model trained.")
313
-
314
-
315
- def clean_response(text: str) -> str:
316
- if not text:
317
- return ""
318
-
319
- text = text.replace("<CODE>", "\n```python\n")
320
- text = text.replace("<ENDCODE>", "\n```\n")
321
-
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
328
- for line in lines:
329
- if line.strip() == "":
330
- empty += 1
331
- if empty <= 2:
332
- cleaned.append(line)
333
- else:
334
- empty = 0
335
- cleaned.append(line)
336
- return "\n".join(cleaned).strip()
337
-
338
-
339
- # -----------------------------
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"
350
-
351
- prompt = context + f"<USER> {user_text}\n<ASSISTANT>"
352
- tokens = tokenizer.encode(prompt)
353
-
354
- if len(tokens) > model.max_length - max_tokens:
355
- tokens = tokens[-(model.max_length - max_tokens):]
356
-
357
- generated = model.generate(
358
- tokens,
359
- max_new_tokens=max_tokens,
360
- temperature=temperature,
361
- top_k=50,
362
- top_p=0.9,
363
- repetition_penalty=1.2,
364
- )
365
-
366
- out = tokenizer.decode(generated)
367
-
368
- if "<ASSISTANT>" in out:
369
- out = out.split("<ASSISTANT>")[-1].strip()
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"]})
381
- teacher_hist.append({"role": "assistant", "content": m["assistant"]})
382
-
383
- return teacher.ask(user_message=user_text, conversation_history=teacher_hist) or ""
384
-
385
-
386
- # -----------------------------
387
- # MAIN GENERATION (HIDDEN TEACHER)
388
- # -----------------------------
389
- def generate_response(user_input, temperature=0.7, max_tokens=200) -> str:
390
- global current_conv_id, teacher_used_count, teacher_failed_count
391
-
392
- user_text = extract_text(user_input).strip()
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})
400
- current_conv_id = db.save_conversation(user_text, 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,
416
- teacher_response=teacher_resp,
417
- student_response=student,
418
- quality_score=1.0,
419
- )
420
- except Exception as e:
421
- print("Could not save distillation sample:", e)
422
-
423
- final = teacher_resp
424
- else:
425
- teacher_failed_count += 1
426
- final = student if student else "Please try again."
427
  else:
428
- final = student
429
-
430
- final = clean_response(final)
431
- if not final:
432
- final = "Please try asking in a different way."
433
-
434
- conversation_history.append({"user": user_text, "assistant": final})
435
- current_conv_id = db.save_conversation(user_text, final)
436
- return final
437
-
438
-
439
- # -----------------------------
440
- # AUTO TRAINING
441
- # -----------------------------
442
- def auto_train_loop():
443
- global _is_training, _last_train_time, model, tokenizer
444
 
 
 
445
  while True:
446
- time.sleep(AUTO_TRAIN_CHECK_EVERY_SEC)
447
-
448
- if not AUTO_TRAIN_ENABLED:
449
- continue
450
-
451
- if time.time() - _last_train_time < AUTO_TRAIN_COOLDOWN_SEC:
452
- continue
453
-
454
- if _train_lock.locked():
455
- continue
456
-
457
- try:
458
- unused = db.get_unused_distillation_data(limit=1000)
459
- except Exception as e:
460
- print("[AutoTrain] Could not read distillation data:", e)
461
- continue
462
-
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 = ""
472
- ids = []
473
- for row in unused:
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)
481
- for conv in good:
482
- extra += f"<USER> {conv['user_input']}\n<ASSISTANT> {conv['assistant_response']}\n\n"
483
- except Exception:
484
- pass
485
-
486
- trainer = VedaTrainer()
487
- hist = trainer.train(
488
- epochs=AUTO_TRAIN_EPOCHS,
489
- extra_data=extra,
490
- distillation_data=distill_text,
491
- )
492
-
493
- model = trainer.model
494
- tokenizer = trainer.tokenizer
495
-
496
- try:
497
- db.mark_distillation_used(ids)
498
- except Exception as e:
499
- print("[AutoTrain] Could not mark distillation used:", e)
500
-
501
- loss = float(hist.history["loss"][-1])
502
- try:
503
- db.save_training_history(
504
- training_type="auto",
505
- samples_used=len(unused),
506
- epochs=AUTO_TRAIN_EPOCHS,
507
- final_loss=loss,
508
- )
509
- except Exception:
510
- pass
511
-
512
- _last_train_time = time.time()
513
- print(f"[AutoTrain] Done. loss={loss:.4f}")
514
-
515
- except Exception as e:
516
- print("[AutoTrain] Training failed:", e)
517
-
518
- _is_training = False
519
-
520
-
521
- # -----------------------------
522
- # GRADIO HANDLERS
523
- # -----------------------------
524
- def respond(message, history, temperature, max_tokens):
525
- history = ensure_messages_history(history)
526
- user_text = extract_text(message).strip()
527
- if not user_text:
528
- return "", history
529
-
530
- bot_text = generate_response(user_text, temperature=float(temperature), max_tokens=int(max_tokens))
531
-
532
- history.append({"role": "user", "content": user_text})
533
- history.append({"role": "assistant", "content": bot_text})
534
-
535
  return "", history
536
 
 
 
537
 
538
- def feedback_good():
539
- if current_conv_id > 0:
540
- db.update_feedback(current_conv_id, 1)
541
- return "Thanks!"
542
- return "No message to rate yet."
543
-
544
-
545
- def feedback_bad():
546
- if current_conv_id > 0:
547
- db.update_feedback(current_conv_id, -1)
548
- return "Thanks!"
549
- return "No message to rate yet."
550
-
551
-
552
- def clear_chat():
553
- global conversation_history
554
- conversation_history = []
555
- return [], ""
556
-
557
-
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
-
571
- ### Conversations
572
- - Total: **{stats.get('total', 0)}**
573
- - Positive: **{stats.get('positive', 0)}**
574
- - Negative: **{stats.get('negative', 0)}**
575
-
576
- ### Distillation (teacher lessons)
577
- - Total saved: **{stats.get('distillation_total', 0)}**
578
- - Pending for training: **{stats.get('distillation_unused', 0)}**
579
- """
580
-
581
-
582
- # -----------------------------
583
- # STARTUP
584
- # -----------------------------
585
- print("=== Booting Veda Assistant ===")
586
- initialize()
587
-
588
- print("Teacher available:", teacher.is_available())
589
- if AUTO_TRAIN_ENABLED:
590
- t = threading.Thread(target=auto_train_loop, daemon=True)
591
- t.start()
592
- print("Auto-training thread started.")
593
- print("=== Ready ===")
594
-
595
-
596
- # -----------------------------
597
  # UI
598
- # -----------------------------
599
- with gr.Blocks(title="Veda Programming Assistant") as demo:
600
- gr.Markdown(
601
- """
602
- # Veda Programming Assistant
603
-
604
- Ask programming questions, request code, or do math like `2+2=?` or `(10+5)/3`.
605
- """
606
- )
607
-
608
- with gr.Tabs():
609
- with gr.TabItem("Chat"):
610
- chatbot = gr.Chatbot(label="Conversation", height=420, value=[])
611
-
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
- )
619
- send = gr.Button("Send", variant="primary", scale=1)
620
-
621
- with gr.Row():
622
- temperature = gr.Slider(0.1, 1.5, 0.7, step=0.1, label="Temperature")
623
- max_tokens = gr.Slider(50, 400, 200, step=50, label="Max tokens")
624
-
625
- with gr.Row():
626
- good = gr.Button("Helpful", variant="secondary")
627
- bad = gr.Button("Not helpful", variant="secondary")
628
- clear = gr.Button("Clear", variant="secondary")
629
-
630
- status = gr.Textbox(label="", show_label=False, lines=1)
631
-
632
- send.click(respond, inputs=[msg, chatbot, temperature, max_tokens], outputs=[msg, chatbot])
633
- msg.submit(respond, inputs=[msg, chatbot, temperature, max_tokens], outputs=[msg, chatbot])
634
-
635
- good.click(feedback_good, outputs=status)
636
- bad.click(feedback_bad, outputs=status)
637
- clear.click(clear_chat, outputs=[chatbot, status])
638
-
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"],
647
- ],
648
- inputs=msg,
649
- )
650
-
651
- with gr.TabItem("Statistics"):
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__":
658
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ import gradio as gr
2
+ import threading
3
+ import time
 
 
 
 
 
 
4
  import os
5
  import json
 
 
 
 
 
 
 
 
 
6
  from model import VedaProgrammingLLM
7
  from tokenizer import VedaTokenizer
8
  from database import db
 
10
  from teacher import teacher
11
  from config import MODEL_DIR
12
 
 
 
 
 
13
  model = None
14
  tokenizer = None
15
+ current_id = -1
16
 
17
+ # Initialize
18
+ def init():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  global model, tokenizer
20
+ if os.path.exists(os.path.join(MODEL_DIR, "weights.h5")):
21
+ with open(os.path.join(MODEL_DIR, "config.json")) as f: conf = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
22
  tokenizer = VedaTokenizer()
23
+ tokenizer.load(os.path.join(MODEL_DIR, "tokenizer.json"))
24
+ model = VedaProgrammingLLM(**conf)
25
+ model(tf.zeros((1, conf['max_length'])))
26
+ model.load_weights(os.path.join(MODEL_DIR, "weights.h5"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  else:
28
+ print("Training initial model...")
29
+ VedaTrainer().train(epochs=15)
30
+ init()
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ # Auto-train loop
33
+ def auto_train():
34
  while True:
35
+ time.sleep(300) # Check every 5 mins
36
+ data = db.get_unused_distillation()
37
+ if len(data) >= 5:
38
+ print("Auto-training on teacher data...")
39
+ text = "\n".join([f"<USER> {r[1]}\n<ASSISTANT> {r[2]}" for r in data])
40
+ VedaTrainer().train(epochs=5, extra_data=text)
41
+ db.mark_used([r[0] for r in data])
42
+ # Reload
43
+ init()
44
+
45
+ threading.Thread(target=auto_train, daemon=True).start()
46
+
47
+ def is_good(text):
48
+ if not text or len(text) < 10: return False
49
+ if "arr[" in text and "return" not in text: return False # Gibberish check
50
+ return True
51
+
52
+ def respond(msg, history):
53
+ global current_id
54
+ if not msg.strip(): return "", history
55
+
56
+ # 1. Try student
57
+ prompt = f"<USER> {msg}\n<ASSISTANT>"
58
+ toks = tokenizer.encode(prompt)
59
+ out = model.generate(toks, max_new_tokens=200)
60
+ resp = tokenizer.decode(out).split("<ASSISTANT>")[-1].split("<USER>")[0].strip()
61
+
62
+ # Clean code tags
63
+ if "<CODE>" in resp:
64
+ resp = resp.replace("<CODE>", "```python\n").replace("</CODE>", "\n```")
65
+ elif "```" in resp and not ("def " in resp or "print" in resp):
66
+ # If model hallucinated code blocks around text
67
+ resp = resp.replace("```", "")
68
+
69
+ # 2. Check quality & fallback
70
+ if not is_good(resp) and teacher.is_available():
71
+ teacher_resp = teacher.ask(msg)
72
+ if teacher_resp:
73
+ resp = teacher_resp
74
+ db.save_distillation(msg, teacher_resp) # Save for learning
75
+
76
+ current_id = db.save_conversation(msg, resp)
77
+ history.append({"role": "user", "content": msg})
78
+ history.append({"role": "assistant", "content": resp})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  return "", history
80
 
81
+ def feedback(vote):
82
+ if current_id > 0: db.update_feedback(current_id, 1 if vote=="good" else -1)
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  # UI
85
+ init()
86
+ with gr.Blocks(title="Veda") as demo:
87
+ gr.Markdown("# 🕉️ Veda Assistant")
88
+ chat = gr.Chatbot(type="messages", height=400)
89
+ msg = gr.Textbox(label="Message")
90
+ with gr.Row():
91
+ gr.Button("👍").click(lambda: feedback("good"))
92
+ gr.Button("👎").click(lambda: feedback("bad"))
93
+
94
+ msg.submit(respond, [msg, chat], [msg, chat])
95
+
96
+ demo.launch(server_name="0.0.0.0", server_port=7860)