Spaces:
Sleeping
Sleeping
Download search.py from BonusLockSMith/web-research-agent: direct link, hf CLI and curl.
- Browser
- Download file 4.59 kB
-
https://huggingface.co/spaces/BonusLockSMith/web-research-agent/resolve/main/search.py
- Command line
-
hf download hf://spaces/BonusLockSMith/web-research-agent/search.py
-
curl -L -o search.py https://huggingface.co/spaces/BonusLockSMith/web-research-agent/resolve/main/search.py
4.59 kB
| #!/usr/bin/env python | |
| """Keyless multi-engine web search for the Web Research Agent (#13). | |
| No API key required β so this runs as a free, private local tool as easily as a public demo. | |
| Order: a local SearXNG (if GRITAI_SEARXNG / SEARXNG_URL is set β truest private) β Google News RSS | |
| (keyless, reliable, no rate limit) β DuckDuckGo HTML β Bing HTML. Returns structured results so the | |
| agent can fetch each URL and build numbered citations. | |
| Adapted from swarm/orchestrator/tools.py (the studio's proven keyless search) into structured output. | |
| """ | |
| import os | |
| import urllib.parse | |
| import requests | |
| import feedparser | |
| from bs4 import BeautifulSoup | |
| _UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " | |
| "(KHTML, like Gecko) Chrome/124.0 Safari/537.36") | |
| TIMEOUT = 15 | |
| def _searxng(query, n): | |
| base = os.environ.get("GRITAI_SEARXNG") or os.environ.get("SEARXNG_URL") | |
| if not base: | |
| return [] | |
| d = requests.get(f"{base}/search", params={"q": query, "format": "json"}, | |
| headers={"User-Agent": _UA}, timeout=TIMEOUT).json() | |
| return [{"title": r.get("title", ""), "url": r.get("url", ""), | |
| "snippet": (r.get("content", "") or "")[:300], "source": "searxng"} | |
| for r in d.get("results", [])[:n] if r.get("url")] | |
| def _gnews(query, n): | |
| """Google News RSS β keyless, structured, fresh, no rate limit.""" | |
| d = feedparser.parse( | |
| f"https://news.google.com/rss/search?q={urllib.parse.quote(query)}&hl=en-US&gl=US&ceid=US:en") | |
| out = [] | |
| for e in d.entries[:n]: | |
| src = e.get("source", {}).get("title", "") if isinstance(e.get("source"), dict) else "" | |
| out.append({"title": e.get("title", ""), "url": e.get("link", ""), | |
| "snippet": f"{src} Β· {e.get('published', '')[:16]}".strip(" Β·"), "source": "google-news"}) | |
| return [r for r in out if r["url"]] | |
| def _ddg(query, n): | |
| r = requests.post("https://html.duckduckgo.com/html/", data={"q": query}, | |
| headers={"User-Agent": _UA}, timeout=TIMEOUT) | |
| soup = BeautifulSoup(r.text, "html.parser") | |
| out = [] | |
| for res in soup.select(".result")[:n]: | |
| a = res.select_one(".result__a") | |
| snip = res.select_one(".result__snippet") | |
| if a and a.get("href"): | |
| out.append({"title": a.get_text(strip=True), "url": a.get("href"), | |
| "snippet": (snip.get_text(strip=True) if snip else "")[:300], "source": "duckduckgo"}) | |
| return out | |
| def _bing(query, n): | |
| r = requests.get("https://www.bing.com/search", params={"q": query}, | |
| headers={"User-Agent": _UA}, timeout=TIMEOUT) | |
| soup = BeautifulSoup(r.text, "html.parser") | |
| out = [] | |
| for li in soup.select("li.b_algo")[:n]: | |
| a = li.select_one("h2 a") | |
| p = li.select_one(".b_caption p") or li.select_one("p") | |
| if a and a.get("href"): | |
| out.append({"title": a.get_text(strip=True), "url": a.get("href"), | |
| "snippet": (p.get_text(strip=True) if p else "")[:300], "source": "bing"}) | |
| return out | |
| def _dedup(results): | |
| seen, out = set(), [] | |
| for r in results: | |
| key = (r["url"] or "").split("#")[0].rstrip("/") | |
| if key and key not in seen: | |
| seen.add(key) | |
| out.append(r) | |
| return out | |
| def search(query: str, n: int = 6) -> list: | |
| """Return up to n structured results [{title,url,snippet,source}] with DIRECT, fetchable URLs. | |
| Order favors engines whose links can actually be read: SearXNG (local, direct) β DuckDuckGo | |
| (direct) β Bing (resolvable click-redirect). Google News is excluded here (encoded links that | |
| don't resolve to full text) β use search_news() for freshness/discovery.""" | |
| n = int(n) | |
| for engine in (_searxng, _ddg, _bing): | |
| try: | |
| hits = engine(query, n) | |
| if hits: | |
| return _dedup(hits)[:n] | |
| except Exception: | |
| continue | |
| return [] | |
| def search_news(query: str, n: int = 6) -> list: | |
| """Fresh headlines via Google News RSS β keyless, no rate limit. Links are encoded redirects | |
| (good for discovery/snippets; not reliably full-text readable), so keep this separate from the | |
| readable web search above.""" | |
| try: | |
| return _dedup(_gnews(query, int(n)))[:int(n)] | |
| except Exception: | |
| return [] | |
| if __name__ == "__main__": | |
| import sys, json | |
| q = " ".join(sys.argv[1:]) or "what is retrieval augmented generation" | |
| res = search(q, 6) | |
| print(f"query: {q}\n{len(res)} results:") | |
| print(json.dumps(res, indent=2)[:2000]) | |