diff --git a/studio/backend/core/inference/tools.py b/studio/backend/core/inference/tools.py index 55bfa095f9..04fdae54cc 100644 --- a/studio/backend/core/inference/tools.py +++ b/studio/backend/core/inference/tools.py @@ -143,8 +143,36 @@ def execute_tool( return f"Unknown tool: {name}" +def _fetch_page_text(url: str, max_chars: int = 4000, timeout: int = 10) -> str: + """Fetch a URL and extract plain text content (best-effort).""" + import urllib.request + import urllib.error + try: + req = urllib.request.Request(url, headers = {"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req, timeout = timeout) as resp: + raw = resp.read(200_000).decode("utf-8", errors = "replace") + # Strip HTML tags (lightweight, no extra deps) + import re + text = re.sub(r"]*>.*?", "", raw, flags = re.DOTALL | re.IGNORECASE) + text = re.sub(r"]*>.*?", "", text, flags = re.DOTALL | re.IGNORECASE) + text = re.sub(r"<[^>]+>", " ", text) + text = re.sub(r"\s+", " ", text).strip() + # Decode HTML entities + import html + text = html.unescape(text) + if len(text) > max_chars: + text = text[:max_chars] + "..." + return text + except Exception: + return "" + + def _web_search(query: str, max_results: int = 5, timeout: int = _EXEC_TIMEOUT) -> str: - """Search the web using DuckDuckGo and return formatted results.""" + """Search the web using DuckDuckGo and return formatted results. + + For the top result, also fetches the actual page content so the + model has real data to work with instead of just snippets. + """ if not query.strip(): return "No query provided." try: @@ -153,13 +181,26 @@ def _web_search(query: str, max_results: int = 5, timeout: int = _EXEC_TIMEOUT) results = DDGS(timeout = timeout).text(query, max_results = max_results) if not results: return "No results found." + + # Fetch full page content for the top result (best-effort, + # capped at 10s so it does not block the agentic loop). + top_page = "" + for r in results[:2]: + href = r.get("href", "") + if href: + top_page = _fetch_page_text(href, max_chars = 6000, timeout = 10) + if len(top_page) > 200: + break parts = [] - for r in results: - parts.append( + for i, r in enumerate(results): + entry = ( f"Title: {r.get('title', '')}\n" f"URL: {r.get('href', '')}\n" f"Snippet: {r.get('body', '')}" ) + if i == 0 and top_page: + entry += f"\n\nPage content:\n{top_page}" + parts.append(entry) return "\n\n---\n\n".join(parts) except Exception as e: return f"Search failed: {e}"