#!/usr/bin/env python3 """Loom harness — the search half of Loom Spark 3. The model never searches. It decides a lookup is needed and writes the query: france This script does the rest: searches Wikipedia, finds the ONE sentence most likely to hold the answer, hands it back as a , and lets the model answer from it. python3 harness.py "what's the capital of france" python3 harness.py # interactive python3 harness.py --no-tools "who are you" python3 harness.py --show "who wrote hamlet" # print what was searched and read How it finds the answer, and why each step exists (all measured on live questions): * searches the model's query AND the subject it can see in your question — "whats the capital of france" searched as-is returns "Capital city" and "Das Kapital" * prefers the real article over lists, films, albums and disambiguation pages * reads the article's intro first, and further only when the intro has no answer of the right kind (a height with a unit, a year, a number, a name) * strips brackets and pronunciation guides, so real text looks like training text * hands back ONE sentence. A 340-character window found the answer more often but the model misread it four times in five; one sentence doubled the final score (15% -> 30%) Swap search() for anything you like — the contract is text in, one sentence out. Wikipedia needs no API key. Stdlib only. """ from __future__ import annotations import argparse, json, re, ssl, sys, time, urllib.error, urllib.parse, urllib.request try: # macOS system Python often lacks a CA bundle import certifi SSL_CTX = ssl.create_default_context(cafile=certifi.where()) except Exception: SSL_CTX = ssl.create_default_context() OLLAMA = "http://localhost:11434/api/generate" MODEL = "hf.co/textilelabs/Loom-Tapestry-3" API = "https://en.wikipedia.org/w/api.php?" # Wikipedia returns 403 without a descriptive User-Agent. UA = {"User-Agent": "LoomHarness/3.0 (Textile Labs; https://huggingface.co/textilelabs)"} LOOKUP = re.compile(r"(.*?)", re.S) _cache: dict = {} # ------------------------------------------------------------------- the model def loom(prompt: str, n: int = 64) -> str: body = json.dumps({"model": MODEL, "prompt": prompt, "raw": True, "stream": False, "options": {"temperature": 0, "num_predict": n, "stop": ["<|eot|>", "", ""]}}).encode() req = urllib.request.Request(OLLAMA, data=body, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=120) as r: return json.load(r)["response"].strip() # ------------------------------------------------------------------ wikipedia def _get(params: dict) -> dict: key = json.dumps(params, sort_keys=True) if key in _cache: return _cache[key] for attempt in range(3): try: with urllib.request.urlopen(urllib.request.Request( API + urllib.parse.urlencode(params), headers=UA), context=SSL_CTX, timeout=20) as r: _cache[key] = json.load(r) return _cache[key] except urllib.error.HTTPError as e: if e.code == 429: time.sleep(3 * (attempt + 1)); continue raise raise RuntimeError("Wikipedia rate limit") def search(q: str, n: int = 3) -> list: return [h["title"] for h in _get({"action": "query", "list": "search", "srsearch": q, "format": "json", "srlimit": n})["query"]["search"]] def _extract(title: str, intro: bool) -> str: p = {"action": "query", "prop": "extracts", "explaintext": 1, "titles": title, "format": "json", "redirects": 1} if intro: p["exintro"] = 1 return next(iter(_get(p)["query"]["pages"].values())).get("extract", "") or "" # ------------------------------------------------------------------ the finder SENT = re.compile(r"(?<=[.!?])\s+(?=[A-Z0-9])") PAREN = re.compile(r"\s*\([^()]*\)") HEADING = re.compile(r"^\s*=+[^=]+=+\s*$", re.M) STOP = set(("what whats who whos whom whose when where which why how is are was were be the a an " "of in on to for does did do by from with as at and or that this it its there tell me " "please can you many much").split()) ATTR = set(("capital city height tall high elevation population largest biggest smallest longest " "shortest tallest highest deepest first last symbol chemical language languages spoken " "legs year date end ended sink sank invented inventor discovered discovery developed " "wrote written author painted painter president founded born died age old size area " "distance speed").split()) JUNK = re.compile(r"^(lists? of|outline of|index of|timeline of)\b|\((film|album|song|band|" r"novel|play|tv series|musical|opera|video game|book|composition|poem)\)|" r"\bdisambiguation\b", re.I) def keywords(t: str) -> list: return [w for w in re.findall(r"[^\W_]+", t.lower()) if w not in STOP] def subject(question: str) -> str: kw = keywords(question) return " ".join(k for k in kw if k not in ATTR) or " ".join(kw) def clean(t: str) -> str: prev = None while prev != t: prev, t = t, PAREN.sub("", t) return re.sub(r"\s+", " ", t.replace(" ,", ",")).strip() def _hard(q: str, s: str) -> float: """The answer is of the right KIND: a height with a unit, a year, a number, a name.""" b = 0.0 if re.search(r"\b(how tall|how high|height|elevation)\b", q): b += 2.0 if re.search(r"\d[\d,.]*\s*(m|metres|meters|ft|feet|km)\b", s) else 0 if re.search(r"\b(when|what year|which year|what date)\b", q): b += 2.0 if re.search(r"\b(1\d{3}|20\d{2})\b", s) else 0 if re.search(r"\b(how many|how much|population|number of)\b", q): b += 1.5 if re.search(r"\d", s) else 0 if re.search(r"\bwho\b", q): b += 1.5 if re.search(r"\b[A-Z][a-z]+ [A-Z][a-z]+", s) else 0 if re.search(r"\bsymbol\b", q): b += 2.0 if re.search(r"\bsymbol\b", s, re.I) else 0 if re.search(r"\bcapital\b", q): b += 2.0 if re.search(r"\bcapital\b", s, re.I) else 0 return b def _kind(q: str, s: str) -> float: b, sl = _hard(q, s), s.lower() if re.search(r"\b(how tall|how high|height|elevation)\b", q): b += 1.5 if re.search(r"\b(summit|elevation|height|above sea level|highest|stands)\b", sl) else -0.5 if re.search(r"\b(end|ended|finish|finished)\b", q): b += 1.5 if re.search(r"\b(ended|end of|surrender|surrendered|concluded|finished)\b", sl) else -0.5 if re.search(r"\bpopulation\b", q): b += 2.0 if re.search(r"\d{1,3}(,\d{3})+|\d+(\.\d+)?\s*(million|billion)", s) else -1.0 if re.search(r"\b(invent|invented|inventor|discovered|wrote|painted|composed|founded)\b", q): b += 1.0 if re.search(r"\b[A-Z][a-z]+ (?:[A-Z][a-z]+ )?[A-Z][a-z]+\b", s) else 0.0 return b def find(query: str, question: str) -> tuple: """One sentence most likely to hold the answer, and the article it came from.""" q = question.lower() subj = subject(question) pool = {} for tq in dict.fromkeys(x for x in (query.strip(), subj, " ".join(keywords(question))) if x): for rank, t in enumerate(search(tq, 3)): tl = t.lower() s = (4.0 if tl in (subj, query.strip().lower()) else 2.0 if subj and tl.startswith(subj) else 0.0) s += -4.0 if JUNK.search(t) else 0.0 pool[t] = max(pool.get(t, -1e9), s - 0.3 * rank) qk = list(dict.fromkeys(keywords(question) + keywords(query))) top = sorted(pool.items(), key=lambda x: -x[1])[:3] typed = bool(re.search(r"\b(how tall|how high|height|elevation|when|what year|which year|" r"how many|how much|population|who|symbol|capital)\b", q)) best = (-1e9, "", "") for intro in (True, False): found_kind = False for title, ps in top: raw = _extract(title, intro) if re.search(r"\b(may|can) refer to\b", raw[:400]): continue body = clean(HEADING.sub(" ", raw)) sents = [s.strip() for s in SENT.split(body) if 20 < len(s.strip()) < 600] for i, s in enumerate(sents[: 14 if intro else 90]): sc = ps + sum(1.0 for k in qk if k in s.lower()) + _kind(q, s) + (0.5 if i < 3 else 0.0) if sc > best[0]: best = (sc, s, title) found_kind = _hard(q, s) > 0 if best[1] and (not typed or found_kind): break # the intro held an answer of the right kind return best[1], best[2] # ------------------------------------------------------------------ the loop def ask(message: str, tools: bool = True, show: bool = False) -> str: convo = f"\n\n{message.strip()}\n<|eot|>\n\n" first = loom(convo) m = LOOKUP.search(first) if not m: return first query = m.group(1).strip() try: result, source = find(query, message) except Exception as e: # Never feed an error in as if it were a result — the model will answer from it. return f"[harness] lookup failed for {query!r}: {e}" if not result: return f"[harness] nothing found for {query!r}" if show: print(f" [searched: {query!r}]\n [read from {source}: {result[:150]}]") return loom(convo + first + f"<|eot|>\n\n{result}\n<|eot|>\n\n", n=48) def main() -> int: global MODEL ap = argparse.ArgumentParser(description="Loom Spark 3 harness") ap.add_argument("message", nargs="*") ap.add_argument("--no-tools", action="store_true", help="chat only, no lookups") ap.add_argument("--show", action="store_true", help="print the query and the sentence read") ap.add_argument("--model", default=MODEL) a = ap.parse_args() MODEL = a.model if a.message: print(ask(" ".join(a.message), not a.no_tools, a.show)); return 0 print(f"Loom harness — {MODEL} (tools {'off' if a.no_tools else 'on'}, ctrl-c to quit)\n") while True: try: msg = input("you > ").strip() except (EOFError, KeyboardInterrupt): print(); return 0 if msg: print(f"loom > {ask(msg, not a.no_tools, a.show)}\n") if __name__ == "__main__": sys.exit(main())