Njap-png commited on
Commit ·
b294605
1
Parent(s): 65322fd
Add self-evolution/self-update loop
Browse files
app.py
CHANGED
|
@@ -449,24 +449,81 @@ def fetch_word_list(limit=500):
|
|
| 449 |
|
| 450 |
|
| 451 |
def fetch_definition(word):
|
|
|
|
|
|
|
| 452 |
try:
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
except Exception:
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 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)
|