Download adam/web_search.py from SyntheticMDProductions/AI_Development_Automation_Manager: direct link, hf CLI and curl.
- Browser
- Download file 9.85 kB
-
https://huggingface.co/SyntheticMDProductions/AI_Development_Automation_Manager/resolve/main/adam/web_search.py
- Command line
-
hf download hf://SyntheticMDProductions/AI_Development_Automation_Manager/adam/web_search.py
-
curl -L -o web_search.py https://huggingface.co/SyntheticMDProductions/AI_Development_Automation_Manager/resolve/main/adam/web_search.py
9.85 kB
| from __future__ import annotations | |
| """Small, keyless web-search adapter for conversational context. | |
| Search results are deliberately treated as untrusted reference material. This | |
| module never opens result pages, downloads files, or performs actions other | |
| than requesting the public search-results page. | |
| """ | |
| from dataclasses import dataclass | |
| from html import unescape | |
| from html.parser import HTMLParser | |
| import ipaddress | |
| import re | |
| import socket | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| import xml.etree.ElementTree as ET | |
| class SearchResult: | |
| title: str | |
| url: str | |
| snippet: str = "" | |
| class WebSearchError(RuntimeError): | |
| pass | |
| class WebReadError(RuntimeError): | |
| pass | |
| class WebSearchClient: | |
| """Fetch a short list of public DuckDuckGo HTML search results.""" | |
| SEARCH_URL = "https://www.bing.com/search?format=rss&q={query}" | |
| def search(self, query: str, *, limit: int = 5, timeout: float = 8.0) -> list[SearchResult]: | |
| query = " ".join(query.split()) | |
| if not query: | |
| return [] | |
| url = self.SEARCH_URL.format(query=urllib.parse.quote_plus(query)) | |
| request = urllib.request.Request( | |
| url, | |
| headers={"User-Agent": "ADAM-WebSearch/1.0 (+local assistant)"}, | |
| ) | |
| try: | |
| with urllib.request.urlopen(request, timeout=timeout) as response: | |
| page = response.read().decode("utf-8", errors="replace") | |
| except (OSError, urllib.error.URLError, TimeoutError) as exc: | |
| raise WebSearchError(f"Web search is unavailable: {exc}") from exc | |
| results: list[SearchResult] = self._parse_bing_rss(page, limit) | |
| if results: | |
| return results | |
| return self._parse_duckduckgo_html(page, limit) | |
| def _parse_bing_rss(page: str, limit: int) -> list[SearchResult]: | |
| try: | |
| root = ET.fromstring(page) | |
| except ET.ParseError: | |
| return [] | |
| results: list[SearchResult] = [] | |
| for item in root.findall("./channel/item"): | |
| title = " ".join((item.findtext("title") or "").split()) | |
| result_url = (item.findtext("link") or "").strip() | |
| snippet = " ".join((item.findtext("description") or "").split()) | |
| if title and result_url.startswith(("https://", "http://")): | |
| results.append(SearchResult(title, result_url, snippet)) | |
| if len(results) >= max(1, min(limit, 10)): | |
| break | |
| return results | |
| def _parse_duckduckgo_html(self, page: str, limit: int) -> list[SearchResult]: | |
| results: list[SearchResult] = [] | |
| pattern = re.compile( | |
| r'<a[^>]+class="result__a"[^>]+href="(?P<url>[^"]+)"[^>]*>(?P<title>.*?)</a>' | |
| r'.{0,2500}?(?:<a[^>]+class="result__snippet"[^>]*>|<div[^>]+class="result__snippet"[^>]*>)(?P<snippet>.*?)</(?:a|div)>', | |
| re.IGNORECASE | re.DOTALL, | |
| ) | |
| for match in pattern.finditer(page): | |
| title = self._clean_html(match.group("title")) | |
| result_url = unescape(match.group("url")) | |
| parsed = urllib.parse.urlparse(result_url) | |
| if parsed.path.startswith("/l/"): | |
| target = urllib.parse.parse_qs(parsed.query).get("uddg", [""])[0] | |
| result_url = urllib.parse.unquote(target) | |
| if title and result_url.startswith(("https://", "http://")): | |
| results.append(SearchResult(title, result_url, self._clean_html(match.group("snippet")))) | |
| if len(results) >= max(1, min(limit, 10)): | |
| break | |
| return results | |
| def _clean_html(value: str) -> str: | |
| return " ".join(unescape(re.sub(r"<[^>]+>", " ", value)).split()) | |
| class WebPage: | |
| url: str | |
| title: str | |
| text: str | |
| class _ReadableTextParser(HTMLParser): | |
| SKIP_TAGS = {"script", "style", "noscript", "svg", "nav", "footer", "header"} | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self.title = "" | |
| self._in_title = False | |
| self._skip_depth = 0 | |
| self.parts: list[str] = [] | |
| def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: | |
| if tag in self.SKIP_TAGS: | |
| self._skip_depth += 1 | |
| if tag == "title": | |
| self._in_title = True | |
| def handle_endtag(self, tag: str) -> None: | |
| if tag in self.SKIP_TAGS and self._skip_depth: | |
| self._skip_depth -= 1 | |
| if tag == "title": | |
| self._in_title = False | |
| def handle_data(self, data: str) -> None: | |
| if self._skip_depth: | |
| return | |
| if self._in_title: | |
| self.title += data | |
| self.parts.append(data) | |
| class _NoRedirect(urllib.request.HTTPRedirectHandler): | |
| def redirect_request(self, request, fp, code, msg, headers, newurl): # type: ignore[no-untyped-def] | |
| return None | |
| class WebPageReader: | |
| """Fetch small readable extracts from explicitly requested public pages.""" | |
| MAX_BYTES = 1_500_000 | |
| MAX_REDIRECTS = 3 | |
| def read(self, url: str, *, timeout: float = 10.0, max_characters: int = 6_000) -> WebPage: | |
| current_url = url | |
| opener = urllib.request.build_opener(_NoRedirect()) | |
| for _ in range(self.MAX_REDIRECTS + 1): | |
| self._validate_public_url(current_url) | |
| request = urllib.request.Request( | |
| current_url, | |
| headers={"User-Agent": "ADAM-WebReader/1.0 (+local assistant)"}, | |
| ) | |
| try: | |
| response = opener.open(request, timeout=timeout) | |
| except urllib.error.HTTPError as exc: | |
| if exc.code in {301, 302, 303, 307, 308} and exc.headers.get("Location"): | |
| current_url = urllib.parse.urljoin(current_url, exc.headers["Location"]) | |
| continue | |
| raise WebReadError(f"Could not read page (HTTP {exc.code}).") from exc | |
| except (OSError, urllib.error.URLError, TimeoutError) as exc: | |
| raise WebReadError(f"Could not read page: {exc}") from exc | |
| with response: | |
| content_type = response.headers.get_content_type() | |
| if content_type not in {"text/html", "text/plain"}: | |
| raise WebReadError(f"This page is {content_type}, not readable web text.") | |
| raw = response.read(self.MAX_BYTES + 1) | |
| if len(raw) > self.MAX_BYTES: | |
| raise WebReadError("This page is too large to read safely.") | |
| body = raw.decode(response.headers.get_content_charset() or "utf-8", errors="replace") | |
| if content_type == "text/plain": | |
| text = " ".join(body.split()) | |
| return WebPage(current_url, current_url, text[:max_characters]) | |
| parser = _ReadableTextParser() | |
| parser.feed(body) | |
| text = " ".join(parser.parts).strip() | |
| title = " ".join(parser.title.split()) or current_url | |
| return WebPage(current_url, title, text[:max_characters]) | |
| raise WebReadError("Page redirected too many times.") | |
| def _validate_public_url(url: str) -> None: | |
| parsed = urllib.parse.urlparse(url) | |
| if parsed.scheme not in {"http", "https"} or not parsed.hostname: | |
| raise WebReadError("Only public http(s) links can be read.") | |
| try: | |
| addresses = socket.getaddrinfo(parsed.hostname, None, type=socket.SOCK_STREAM) | |
| except socket.gaierror as exc: | |
| raise WebReadError("Could not resolve that website.") from exc | |
| for item in addresses: | |
| address = ipaddress.ip_address(item[4][0]) | |
| if not address.is_global: | |
| raise WebReadError("Private, local, and reserved network addresses are blocked.") | |
| def should_search(request: str) -> bool: | |
| """Whether a chat request explicitly asks ADAM for fresh web information.""" | |
| return bool(re.search( | |
| r"\b(?:search(?:\s+the)?\s+(?:web|internet|online)|web\s+search|" | |
| r"look\s+up|google|browse\s+(?:the\s+)?(?:web|internet)|" | |
| r"latest|current|today'?s|recent\s+news)\b", | |
| request, | |
| re.IGNORECASE, | |
| )) | |
| def search_query_from_request(request: str) -> str: | |
| """Remove a conversational search command before sending the actual topic.""" | |
| query = re.sub( | |
| r"^\s*(?:please\s+)?(?:search(?:\s+the)?\s+(?:web|internet|online)|" | |
| r"web\s+search|look\s+up|google|browse\s+(?:the\s+)?(?:web|internet))" | |
| r"\s+(?:for\s+)?", | |
| "", | |
| request, | |
| flags=re.IGNORECASE, | |
| ) | |
| return query.strip(" \t\r\n?!.") or request | |
| def should_read_links(request: str) -> bool: | |
| return bool(re.search( | |
| r"\b(?:read|open|research|study|summari[sz]e|go\s+further|learn\s+more|" | |
| r"look\s+into)\b.*\b(?:link|links|result|results|page|pages|web|website|site)\b" | |
| r"|\b(?:read|open)\s+https?://", | |
| request, | |
| re.IGNORECASE, | |
| )) | |
| def urls_in_request(request: str) -> list[str]: | |
| return re.findall(r"https?://[^\s<>\]\[\)\}\"']+", request) | |
| def format_search_context(results: list[SearchResult]) -> str: | |
| if not results: | |
| return "No usable web results were returned. Be transparent about that." | |
| lines = [] | |
| for index, result in enumerate(results, 1): | |
| snippet = f" — {result.snippet}" if result.snippet else "" | |
| lines.append(f"[{index}] {result.title}{snippet}\nURL: {result.url}") | |
| return "\n".join(lines) | |
| def format_page_context(pages: list[WebPage]) -> str: | |
| if not pages: | |
| return "No linked pages could be read." | |
| return "\n\n".join( | |
| f"PAGE: {page.title}\nURL: {page.url}\nEXTRACT: {page.text}" | |
| for page in pages | |
| ) | |