Njap-png commited on
Commit
ac8f558
路
1 Parent(s): eea05c0

Add web search crawling + web learning

Browse files
Files changed (1) hide show
  1. app.py +113 -0
app.py CHANGED
@@ -5,6 +5,8 @@ import random
5
  import threading
6
  import subprocess
7
  import urllib.request
 
 
8
  import torch
9
  import gradio as gr
10
  from datasets import Dataset
@@ -410,6 +412,105 @@ def learn_dictionary():
410
  TRAIN_LOCK.release()
411
 
412
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  with gr.Blocks(title="Agron Neural Interface") as demo:
414
  gr.HTML(PARTICLE_HTML, js_on_load=PARTICLE_JS)
415
  gr.Markdown("<h1 class='agron-title'>AGR脴N NEURAL INTERFACE</h1>", elem_classes="agron-card")
@@ -427,6 +528,8 @@ with gr.Blocks(title="Agron Neural Interface") as demo:
427
  status = gr.Textbox(value=f"interactions: {len(interactions)} | {training_state['last']}", label="Learning", interactive=False, elem_classes="status-box")
428
  learn_btn = gr.Button("Train now on memory")
429
  dict_btn = gr.Button("Learn dictionary words")
 
 
430
  gr.Markdown("### Network metrics")
431
  metrics = gr.Textbox(value="nodes: 130 路 mesh: active 路 LFS: ok", label="Diagnostics", interactive=False)
432
 
@@ -456,8 +559,18 @@ with gr.Blocks(title="Agron Neural Interface") as demo:
456
  return "training already running"
457
  threading.Thread(target=learn_dictionary, daemon=True).start()
458
  return "dictionary learning started in background..."
 
 
 
 
 
 
 
 
 
459
  learn_btn.click(do_learn, outputs=status)
460
  dict_btn.click(do_dict, outputs=status)
 
461
  demo.load(refresh_status, outputs=status)
462
 
463
  # periodic status refresh (not true polling inside gradio, use change hook via demo load only)
 
5
  import threading
6
  import subprocess
7
  import urllib.request
8
+ import requests
9
+ from bs4 import BeautifulSoup
10
  import torch
11
  import gradio as gr
12
  from datasets import Dataset
 
412
  TRAIN_LOCK.release()
413
 
414
 
415
+ def duckduckgo_search(query, max_results=5):
416
+ try:
417
+ r = requests.get("https://html.duckduckgo.com/html/", params={"q": query}, headers={"User-Agent": "Mozilla/5.0"}, timeout=20)
418
+ soup = BeautifulSoup(r.text, "html.parser")
419
+ out = []
420
+ for res in soup.select(".result"):
421
+ a = res.select_one("a.result__a")
422
+ if not a:
423
+ continue
424
+ link = a.get("href", "")
425
+ title = a.get_text(strip=True)
426
+ sn = res.select_one(".result__snippet")
427
+ snippet = sn.get_text(strip=True) if sn else ""
428
+ if link:
429
+ out.append({"title": title, "url": link, "snippet": snippet})
430
+ if len(out) >= max_results:
431
+ break
432
+ return out
433
+ except Exception as e:
434
+ training_state["last"] = f"search error: {str(e)[:100]}"
435
+ return []
436
+
437
+
438
+ def crawl_page(url):
439
+ try:
440
+ r = requests.get(url, timeout=20, headers={"User-Agent": "Mozilla/5.0"}, allow_redirects=True)
441
+ if r.status_code != 200:
442
+ return ""
443
+ soup = BeautifulSoup(r.text, "html.parser")
444
+ for tag in soup(["script", "style", "nav", "footer", "header", "noscript", "form"]):
445
+ tag.decompose()
446
+ text = " ".join(soup.get_text(separator=" ", strip=True).split())
447
+ return text[:2500]
448
+ except Exception:
449
+ return ""
450
+
451
+
452
+ def learn_web(topic):
453
+ global MODEL
454
+ if not TRAIN_LOCK.acquire(blocking=False):
455
+ return
456
+ training_state["running"] = True
457
+ training_state["last"] = f"searching web: {topic}"
458
+ try:
459
+ results = duckduckgo_search(topic, 5)
460
+ if not results:
461
+ raise RuntimeError("no search results")
462
+ lines = []
463
+ for i, res in enumerate(results):
464
+ training_state["last"] = f"crawling {i+1}/{len(results)}: {res['title'][:40]}"
465
+ page = crawl_page(res["url"])
466
+ lines.append(f"Topic: {topic}\nSource: {res['title']} ({res['url']})\nSnippet: {res['snippet']}\nContent: {page}")
467
+ time.sleep(1)
468
+ corpus = "\n\n".join(lines)
469
+ if not corpus.strip():
470
+ raise RuntimeError("empty web corpus")
471
+ toks = TOKENIZER(corpus)["input_ids"]
472
+ block = 64
473
+ chunks = [toks[i:i+block] for i in range(0, max(1, len(toks) - block + 1), block)]
474
+ chunks = [c for c in chunks if len(c) == block]
475
+ if not chunks:
476
+ raise RuntimeError("not enough web text")
477
+ ds = Dataset.from_dict({"input_ids": chunks, "labels": [c[:] for c in chunks]})
478
+ out_dir = os.path.join("/content", "agron-web")
479
+ os.makedirs(out_dir, exist_ok=True)
480
+ args = TrainingArguments(
481
+ output_dir=out_dir,
482
+ per_device_train_batch_size=2,
483
+ max_steps=60,
484
+ learning_rate=1e-4,
485
+ optim="adamw_torch",
486
+ fp16=True if torch.cuda.is_available() else False,
487
+ logging_steps=20,
488
+ save_strategy="no",
489
+ report_to=[],
490
+ remove_unused_columns=True,
491
+ )
492
+ collator = DataCollatorForLanguageModeling(tokenizer=TOKENIZER, mlm=False)
493
+ trainer = Trainer(model=MODEL, args=args, train_dataset=ds, data_collator=collator, processing_class=TOKENIZER)
494
+ training_state["last"] = f"training on web content for '{topic}'..."
495
+ trainer.train()
496
+ save_file({k: v.detach().cpu().contiguous() for k, v in MODEL.state_dict().items()}, os.path.join(out_dir, "model.safetensors"))
497
+ import shutil
498
+ shutil.copyfile(os.path.join(out_dir, "model.safetensors"), os.path.join(ROOT, "model.safetensors"))
499
+ training_state["last"] = "pushing web-learned model..."
500
+ subprocess.run(["git", "-C", ROOT, "add", "model.safetensors"], check=True)
501
+ subprocess.run(["git", "-C", ROOT, "add", "memory/interactions.jsonl"], check=False)
502
+ status = subprocess.run(["git", "-C", ROOT, "status", "--porcelain"], capture_output=True, text=True, check=True).stdout.strip()
503
+ if status:
504
+ subprocess.run(["git", "-C", ROOT, "commit", "-m", f"Agron web learning: {topic}"], check=True)
505
+ subprocess.run(["git", "-C", ROOT, "push", "origin", "main"], check=True)
506
+ training_state["last"] = f"web learned & pushed: {topic}"
507
+ except Exception as e:
508
+ training_state["last"] = f"web error: {str(e)[:150]}"
509
+ finally:
510
+ training_state["running"] = False
511
+ TRAIN_LOCK.release()
512
+
513
+
514
  with gr.Blocks(title="Agron Neural Interface") as demo:
515
  gr.HTML(PARTICLE_HTML, js_on_load=PARTICLE_JS)
516
  gr.Markdown("<h1 class='agron-title'>AGR脴N NEURAL INTERFACE</h1>", elem_classes="agron-card")
 
528
  status = gr.Textbox(value=f"interactions: {len(interactions)} | {training_state['last']}", label="Learning", interactive=False, elem_classes="status-box")
529
  learn_btn = gr.Button("Train now on memory")
530
  dict_btn = gr.Button("Learn dictionary words")
531
+ web_topic = gr.Textbox(label="Web topic / search query", placeholder="e.g. transformer architecture", lines=1)
532
+ web_btn = gr.Button("Search & learn web")
533
  gr.Markdown("### Network metrics")
534
  metrics = gr.Textbox(value="nodes: 130 路 mesh: active 路 LFS: ok", label="Diagnostics", interactive=False)
535
 
 
559
  return "training already running"
560
  threading.Thread(target=learn_dictionary, daemon=True).start()
561
  return "dictionary learning started in background..."
562
+
563
+ def do_web():
564
+ if training_state["running"]:
565
+ return "training already running"
566
+ q = web_topic.value.strip()
567
+ if not q:
568
+ return "enter a topic/query first"
569
+ threading.Thread(target=learn_web, args=(q,), daemon=True).start()
570
+ return f"web learning started: {q}"
571
  learn_btn.click(do_learn, outputs=status)
572
  dict_btn.click(do_dict, outputs=status)
573
+ web_btn.click(do_web, outputs=status)
574
  demo.load(refresh_status, outputs=status)
575
 
576
  # periodic status refresh (not true polling inside gradio, use change hook via demo load only)