juiceb0xc0de commited on
Commit
ec942b9
·
1 Parent(s): 4f4e0d4

Bake grown graph into the Space: 63 nodes (35 curated + 28 discovered)

Browse files
Files changed (3) hide show
  1. forge/forge.db +0 -0
  2. forge/grow.py +82 -0
  3. forge/scraper.py +30 -0
forge/forge.db ADDED
Binary file (57.3 kB). View file
 
forge/grow.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Node discovery — grow the graph's component ("tool") count from scraped pages.
2
+
3
+ The edge extractor only links tools already in the graph; this pass finds NEW
4
+ tools named in scraped content and adds them as nodes. Gated: a candidate must
5
+ appear in >= min_sources distinct sources before it's added, so we don't fill
6
+ the graph with one-off junk. New nodes are tagged 'discovered' (review-pending).
7
+
8
+ Replays saved campaign content — no new Bright Data credits.
9
+
10
+ Run: python -m forge.grow
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import glob
15
+ import json
16
+ from collections import Counter
17
+
18
+ from . import collect, db, scraper
19
+
20
+ VALID_TYPES = {"optimizer", "scheduler", "technique", "quantization",
21
+ "architecture", "inference", "framework"}
22
+
23
+
24
+ def run(min_sources=2):
25
+ conn = db.connect()
26
+ ai = scraper.build_alias_index(conn)
27
+ known = {scraper._norm(a) for a, _ in ai}
28
+ before = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
29
+
30
+ files = sorted(glob.glob(str(collect.RAW_DIR / "forge_campaign_*.json")))
31
+ files += sorted(glob.glob(str(collect.RAW_DIR / "forge_collect_*.json")))
32
+ cand, pages = {}, 0
33
+ for f in files:
34
+ bundle = json.load(open(f))
35
+ for plist in bundle.get("passes", {}).values():
36
+ if not isinstance(plist, list):
37
+ continue
38
+ for item in plist:
39
+ content = item.get("content") or ""
40
+ url = item.get("link") or item.get("url") or ""
41
+ if len(content) < 200:
42
+ continue
43
+ pages += 1
44
+ for comp in scraper.extract_components(content):
45
+ norm = scraper._norm(comp.get("name", ""))
46
+ ty = comp.get("type", "")
47
+ if not norm or norm in known or ty not in VALID_TYPES:
48
+ continue
49
+ c = cand.setdefault(norm, {"display": comp["name"].strip(), "types": [], "sources": set()})
50
+ c["types"].append(ty)
51
+ if url:
52
+ c["sources"].add(url)
53
+
54
+ added = []
55
+ for norm, info in cand.items():
56
+ if len(info["sources"]) >= min_sources:
57
+ ty = Counter(info["types"]).most_common(1)[0][0]
58
+ try:
59
+ db.add_node(conn, type=ty, name=info["display"], canonical=norm,
60
+ aliases=[], description="Discovered via Bright Data scraping (review-pending).",
61
+ tags=["discovered"])
62
+ known.add(norm)
63
+ added.append((info["display"], ty, len(info["sources"])))
64
+ except Exception: # noqa: BLE001 - duplicate canonical race
65
+ pass
66
+ conn.commit()
67
+ after = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
68
+ return conn, dict(pages=pages, candidates=len(cand), added=added, before=before, after=after)
69
+
70
+
71
+ def main():
72
+ conn, r = run()
73
+ print(f"pages scanned : {r['pages']}")
74
+ print(f"new candidates : {r['candidates']}")
75
+ print(f"nodes: {r['before']} -> {r['after']} (+{r['after'] - r['before']})")
76
+ print("new tools added (>=2 sources):")
77
+ for name, ty, n in sorted(r["added"], key=lambda x: -x[2]):
78
+ print(f" + {name} [{ty}] ({n} sources)")
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
forge/scraper.py CHANGED
@@ -226,6 +226,36 @@ def _llm_up() -> bool:
226
  return False
227
 
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  def _extract_local(markdown: str, alias_index) -> list[dict]:
230
  """Extract via a local llama.cpp OpenAI-compatible server. No API key."""
231
  vocab = sorted({c for _, c in alias_index})
 
226
  return False
227
 
228
 
229
+ _NODE_PROMPT = """List the ML TRAINING components/tools/methods NAMED in this text.
230
+ Categories: optimizer, scheduler, technique, quantization, architecture, inference, framework.
231
+ Return JSON: {"components":[{"name":"<specific named tool>","type":"<category>"}]}.
232
+ Only specific named tools/methods (e.g. GaLore, DoRA, FlashAttention, Sophia, Adafactor,
233
+ Megatron, Axolotl, ReLoRA, Shampoo). NO generic words, NO prose. If none: {"components":[]}."""
234
+
235
+
236
+ def extract_components(markdown: str) -> list[dict]:
237
+ """Local-LLM node discovery — names + categories of training tools mentioned."""
238
+ if not _llm_up():
239
+ return []
240
+ payload = {
241
+ "messages": [{"role": "system", "content": _NODE_PROMPT},
242
+ {"role": "user", "content": _clean_markdown(markdown)[:12000]}],
243
+ "temperature": 0, "max_tokens": 1200, "response_format": {"type": "json_object"},
244
+ }
245
+ req = urllib.request.Request(
246
+ LLM_URL.rstrip("/") + "/v1/chat/completions", data=json.dumps(payload).encode(),
247
+ headers={"Content-Type": "application/json", "Authorization": "Bearer no-key"})
248
+ try:
249
+ with urllib.request.urlopen(req, timeout=120) as r:
250
+ resp = json.loads(r.read())
251
+ text = resp["choices"][0]["message"]["content"]
252
+ m = re.search(r"\{[\s\S]*\}", text)
253
+ obj = json.loads(m.group(0)) if m else {}
254
+ return [c for c in obj.get("components", []) if c.get("name") and c.get("type")]
255
+ except Exception: # noqa: BLE001
256
+ return []
257
+
258
+
259
  def _extract_local(markdown: str, alias_index) -> list[dict]:
260
  """Extract via a local llama.cpp OpenAI-compatible server. No API key."""
261
  vocab = sorted({c for _, c in alias_index})