import re, ast, json, operator, requests UA={"User-Agent":"zero-loop-distill/1.0 (research notebook)"} API="https://en.wikipedia.org/w/api.php" _cache={} def search(query, n=3): query=str(query).strip()[:200] if not query: raise ValueError("empty query") if query in _cache: return _cache[query] r=requests.get(API, params={"action":"query","list":"search","srsearch":query,"srlimit":n,"format":"json"}, headers=UA, timeout=20) r.raise_for_status() titles=[h["title"] for h in r.json()["query"]["search"]] if not titles: raise ValueError("no results") r2=requests.get(API, params={"action":"query","prop":"extracts","exintro":1,"explaintext":1,"exchars":400,"exlimit":n,"titles":"|".join(titles),"redirects":1,"format":"json"}, headers=UA, timeout=20) r2.raise_for_status() ex={p["title"]:" ".join(p.get("extract","").split()) for p in r2.json()["query"]["pages"].values()} if not any(ex.values()): raise ValueError("empty extracts") out="\n".join(f"{i+1}. {t}: {ex.get(t,'')}" for i,t in enumerate(titles))[:1300] _cache[query]=out return out _ops={ast.Add:operator.add,ast.Sub:operator.sub,ast.Mult:operator.mul,ast.Div:operator.truediv,ast.FloorDiv:operator.floordiv,ast.Mod:operator.mod,ast.Pow:operator.pow,ast.USub:operator.neg,ast.UAdd:operator.pos} def _ev(n): if isinstance(n,ast.Constant) and isinstance(n.value,(int,float)) and not isinstance(n.value,bool): return n.value if isinstance(n,ast.BinOp) and type(n.op) in _ops: a,b=_ev(n.left),_ev(n.right) if isinstance(n.op,ast.Pow) and abs(b)>100: raise ValueError("exponent too large") return _ops[type(n.op)](a,b) if isinstance(n,ast.UnaryOp) and type(n.op) in _ops: return _ops[type(n.op)](_ev(n.operand)) raise ValueError("unsupported expression") def calculator(expression): e=str(expression).replace("^","**").replace("\u00d7","*").replace("\u00f7","/").replace(",","").strip() if not e or len(e)>200: raise ValueError("bad expression") v=_ev(ast.parse(e,mode="eval").body) if isinstance(v,float): v=int(v) if (v==int(v) and abs(v)<1e15) else round(v,10) return str(v) def run_tool(name, args): if name=="search": return search(args["query"]) if name=="calculator": return calculator(args["expression"]) raise KeyError(name) CALL_RE=re.compile(r"\s*(\{.*?\})\s*", re.S) def parse_call(text): m=CALL_RE.search(text) if not m: return None try: c=json.loads(m.group(1)) except Exception: return None if not isinstance(c,dict) or c.get("name") not in ("search","calculator") or not isinstance(c.get("arguments"),dict): return None k="query" if c["name"]=="search" else "expression" if not isinstance(c["arguments"].get(k),str) or not c["arguments"][k].strip(): return None return {"name":c["name"],"arguments":{k:c["arguments"][k].strip()}}