Njap-png commited on
Commit
b294605
·
1 Parent(s): 65322fd

Add self-evolution/self-update loop

Browse files
Files changed (1) hide show
  1. app.py +150 -15
app.py CHANGED
@@ -449,24 +449,81 @@ def fetch_word_list(limit=500):
449
 
450
 
451
  def fetch_definition(word):
 
 
452
  try:
453
- url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
454
- with urllib.request.urlopen(url, timeout=20) as r:
455
- data = json.loads(r.read().decode())
456
- meanings = data[0].get("meanings", [])
457
- for m in meanings:
458
- for d in m.get("definitions", []):
459
- if d.get("definition"):
460
- ex = d.get("example")
461
- txt = f"{word}: {d['definition']}"
462
- if ex:
463
- txt += f" Example: {ex}"
464
- return txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  except Exception:
466
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  return None
468
 
469
 
 
470
  def learn_dictionary():
471
  global MODEL
472
  if not TRAIN_LOCK.acquire(blocking=False):
@@ -474,7 +531,7 @@ def learn_dictionary():
474
  training_state["running"] = True
475
  training_state["last"] = "fetching dictionary..."
476
  try:
477
- words = fetch_word_list(400)
478
  if not words:
479
  raise RuntimeError("could not fetch word list")
480
  lines = []
@@ -658,6 +715,71 @@ def learn_web(topic):
658
  TRAIN_LOCK.release()
659
 
660
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  with gr.Blocks(title="Agron Neural Interface") as demo:
662
  gr.HTML(PARTICLE_HTML, js_on_load=PARTICLE_JS)
663
  gr.Markdown("<h1 class='agron-title'>AGRØN NEURAL INTERFACE</h1>", elem_classes="agron-card")
@@ -677,6 +799,10 @@ with gr.Blocks(title="Agron Neural Interface") as demo:
677
  dict_btn = gr.Button("Learn dictionary words")
678
  web_topic = gr.Textbox(label="Web topic / search query", placeholder="e.g. transformer architecture", lines=1)
679
  web_btn = gr.Button("Search & learn web")
 
 
 
 
680
  gr.Markdown("### Network metrics")
681
  metrics = gr.Textbox(value="nodes: 130 · mesh: active · LFS: ok", label="Diagnostics", interactive=False)
682
 
@@ -696,7 +822,7 @@ with gr.Blocks(title="Agron Neural Interface") as demo:
696
  return "training started in background..."
697
 
698
  def refresh_status():
699
- return f"interactions: {len(interactions)} | running={training_state['running']} | {training_state['last']}"
700
 
701
  send.click(respond, [msg, chatbot], [chatbot, msg])
702
  msg.submit(respond, [msg, chatbot], [chatbot, msg])
@@ -718,6 +844,15 @@ with gr.Blocks(title="Agron Neural Interface") as demo:
718
  learn_btn.click(do_learn, outputs=status)
719
  dict_btn.click(do_dict, outputs=status)
720
  web_btn.click(do_web, outputs=status)
 
 
 
 
 
 
 
 
 
721
  demo.load(refresh_status, outputs=status)
722
 
723
  # periodic status refresh (not true polling inside gradio, use change hook via demo load only)
 
449
 
450
 
451
  def fetch_definition(word):
452
+ UA = "AgronBot/1.0 (teddy.njagi.w@gmail.com)"
453
+ # Try Wiktionary extract
454
  try:
455
+ r = requests.get("https://en.wiktionary.org/w/api.php", params={
456
+ "action": "query", "prop": "extracts", "explaintext": "1",
457
+ "titles": word, "format": "json"
458
+ }, headers={"User-Agent": UA}, timeout=20)
459
+ if r.status_code == 200:
460
+ page = list(r.json().get("query", {}).get("pages", {}).values())
461
+ if page:
462
+ extract = page[0].get("extract", "")
463
+ if extract:
464
+ meaning = _parse_wiktionary(extract)
465
+ if meaning:
466
+ return f"{word}: {meaning}"
467
+ except Exception:
468
+ pass
469
+ # Try Wikipedia summary
470
+ try:
471
+ r = requests.get("https://en.wikipedia.org/api/rest_v1/page/summary/" + word, headers={"User-Agent": UA}, timeout=20)
472
+ if r.status_code == 200:
473
+ ex = r.json().get("extract", "")
474
+ if ex and len(ex) > 20:
475
+ return f"{word}: {ex.split('.')[0]}."
476
+ except Exception:
477
+ pass
478
+ # Fallback: ask local voice model
479
+ try:
480
+ if VOICE_MODEL is not None and VOICE_TOK is not None:
481
+ msgs = [{"role": "system", "content": "You are a dictionary. Reply with a single concise definition for the given word, no extra text."},
482
+ {"role": "user", "content": f"Define: {word}"}]
483
+ prompt = VOICE_TOK.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
484
+ inputs = VOICE_TOK(prompt, return_tensors="pt").to(VOICE_MODEL.device)
485
+ with torch.no_grad():
486
+ out = VOICE_MODEL.generate(**inputs, max_new_tokens=40, do_sample=False)
487
+ txt = VOICE_TOK.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True).strip()
488
+ if txt and len(txt) > 8:
489
+ return f"{word}: {txt.split(chr(10))[0]}"
490
  except Exception:
491
+ pass
492
+ return None
493
+
494
+
495
+ def _parse_wiktionary(extract):
496
+ lines = [ln.strip() for ln in extract.splitlines()]
497
+ # find section headers for parts of speech
498
+ for i, ln in enumerate(lines):
499
+ if ln.startswith("===") and ln.endswith("==="):
500
+ title = ln.strip("=").strip().lower()
501
+ if title in {"noun", "verb", "adjective", "adverb", "pronoun", "preposition"}:
502
+ # take next non-empty, non-heading line
503
+ for j in range(i+1, min(i+12, len(lines))):
504
+ cand = lines[j]
505
+ if not cand or cand.startswith("===") or cand.startswith("===="):
506
+ continue
507
+ cand2 = cand.lstrip("#*:- ").strip()
508
+ if len(cand2) <= 8:
509
+ continue
510
+ lowc = cand2.lower()
511
+ if "plural" in lowc or lowc.startswith(word.lower()):
512
+ continue
513
+ if lowc.startswith("synonym") or lowc.startswith("see thesaurus"):
514
+ continue
515
+ return cand2
516
+ # fallback: first long line not a header
517
+ for ln in lines:
518
+ if not ln or ln.startswith("===") or ln.startswith("=="):
519
+ continue
520
+ cand = ln.lstrip("#*:- ").strip()
521
+ if len(cand) > 12:
522
+ return cand
523
  return None
524
 
525
 
526
+
527
  def learn_dictionary():
528
  global MODEL
529
  if not TRAIN_LOCK.acquire(blocking=False):
 
531
  training_state["running"] = True
532
  training_state["last"] = "fetching dictionary..."
533
  try:
534
+ words = fetch_word_list(150)
535
  if not words:
536
  raise RuntimeError("could not fetch word list")
537
  lines = []
 
715
  TRAIN_LOCK.release()
716
 
717
 
718
+ EVOLVE_STATE = {"running": False, "cycles": 0, "last_topic": "none"}
719
+
720
+
721
+ def self_update():
722
+ try:
723
+ subprocess.run(["git", "-C", ROOT, "fetch", "origin"], check=True, timeout=60)
724
+ local = subprocess.run(["git", "-C", ROOT, "rev-parse", "HEAD"], capture_output=True, text=True, check=True).stdout.strip()
725
+ remote = subprocess.run(["git", "-C", ROOT, "rev-parse", "origin/main"], capture_output=True, text=True, check=True).stdout.strip()
726
+ if local != remote:
727
+ subprocess.run(["git", "-C", ROOT, "pull", "--ff-only", "origin", "main"], check=True, timeout=300)
728
+ return "updated from GitHub; restart app to apply new code"
729
+ return "already up to date"
730
+ except Exception as e:
731
+ return f"update error: {str(e)[:120]}"
732
+
733
+
734
+ EVOLVE_TOPICS = [
735
+ "artificial intelligence self improvement",
736
+ "plant design software PDMS",
737
+ "machine learning transformers",
738
+ "python programming best practices",
739
+ "data structures algorithms",
740
+ "neural network training techniques",
741
+ "encyclopedia general knowledge",
742
+ ]
743
+
744
+
745
+ def _self_evolve_worker():
746
+ EVOLVE_STATE["running"] = True
747
+ i = 0
748
+ try:
749
+ while EVOLVE_STATE["running"]:
750
+ # evolve from conversations if enough
751
+ if len(interactions) >= 4:
752
+ learn_from_interactions()
753
+ # learn from web on rotating topics
754
+ if EVOLVE_STATE["running"]:
755
+ topic = EVOLVE_TOPICS[i % len(EVOLVE_TOPICS)]
756
+ i += 1
757
+ EVOLVE_STATE["last_topic"] = topic
758
+ learn_web(topic)
759
+ EVOLVE_STATE["cycles"] += 1
760
+ # downtime before next cycle
761
+ for _ in range(300):
762
+ if not EVOLVE_STATE["running"]:
763
+ break
764
+ time.sleep(1)
765
+ except Exception as e:
766
+ training_state["last"] = f"evolve error: {str(e)[:120]}"
767
+ finally:
768
+ EVOLVE_STATE["running"] = False
769
+
770
+
771
+ def start_self_evolution():
772
+ if EVOLVE_STATE["running"]:
773
+ return "self-evolution already running"
774
+ threading.Thread(target=_self_evolve_worker, daemon=True).start()
775
+ return "self-evolution started"
776
+
777
+
778
+ def stop_self_evolution():
779
+ EVOLVE_STATE["running"] = False
780
+ return "stopping self-evolution..."
781
+
782
+
783
  with gr.Blocks(title="Agron Neural Interface") as demo:
784
  gr.HTML(PARTICLE_HTML, js_on_load=PARTICLE_JS)
785
  gr.Markdown("<h1 class='agron-title'>AGRØN NEURAL INTERFACE</h1>", elem_classes="agron-card")
 
799
  dict_btn = gr.Button("Learn dictionary words")
800
  web_topic = gr.Textbox(label="Web topic / search query", placeholder="e.g. transformer architecture", lines=1)
801
  web_btn = gr.Button("Search & learn web")
802
+ with gr.Row():
803
+ evolve_btn = gr.Button("Start self-evolution")
804
+ stop_evolve_btn = gr.Button("Stop")
805
+ update_btn = gr.Button("Self-update")
806
  gr.Markdown("### Network metrics")
807
  metrics = gr.Textbox(value="nodes: 130 · mesh: active · LFS: ok", label="Diagnostics", interactive=False)
808
 
 
822
  return "training started in background..."
823
 
824
  def refresh_status():
825
+ return f"interactions: {len(interactions)} | running={training_state['running']} | cycles={EVOLVE_STATE['cycles']} | {training_state['last']}"
826
 
827
  send.click(respond, [msg, chatbot], [chatbot, msg])
828
  msg.submit(respond, [msg, chatbot], [chatbot, msg])
 
844
  learn_btn.click(do_learn, outputs=status)
845
  dict_btn.click(do_dict, outputs=status)
846
  web_btn.click(do_web, outputs=status)
847
+ def do_evolve():
848
+ return start_self_evolution()
849
+ def do_stop_evolve():
850
+ return stop_self_evolution()
851
+ def do_update():
852
+ return self_update()
853
+ evolve_btn.click(do_evolve, outputs=status)
854
+ stop_evolve_btn.click(do_stop_evolve, outputs=status)
855
+ update_btn.click(do_update, outputs=status)
856
  demo.load(refresh_status, outputs=status)
857
 
858
  # periodic status refresh (not true polling inside gradio, use change hook via demo load only)