| """
|
| Documentation search tools for exploring HuggingFace and Gradio documentation.
|
| """
|
|
|
| import asyncio
|
| import json
|
| import os
|
| from typing import Any
|
|
|
| import httpx
|
| from bs4 import BeautifulSoup
|
| from whoosh.analysis import StemmingAnalyzer
|
| from whoosh.fields import ID, TEXT, Schema
|
| from whoosh.filedb.filestore import RamStorage
|
| from whoosh.qparser import MultifieldParser, OrGroup
|
|
|
|
|
|
|
|
|
|
|
| DEFAULT_MAX_RESULTS = 20
|
| MAX_RESULTS_CAP = 50
|
|
|
| GRADIO_LLMS_TXT_URL = "https://gradio.app/llms.txt"
|
| GRADIO_SEARCH_URL = "https://playground-worker.pages.dev/api/prompt"
|
|
|
| COMPOSITE_ENDPOINTS: dict[str, list[str]] = {
|
| "optimum": [
|
| "optimum",
|
| "optimum-habana",
|
| "optimum-neuron",
|
| "optimum-intel",
|
| "optimum-executorch",
|
| "optimum-tpu",
|
| ],
|
| "courses": [
|
| "llm-course",
|
| "robotics-course",
|
| "mcp-course",
|
| "smol-course",
|
| "agents-course",
|
| "deep-rl-course",
|
| "computer-vision-course",
|
| "audio-course",
|
| "ml-games-course",
|
| "diffusion-course",
|
| "ml-for-3d-course",
|
| "cookbook",
|
| ],
|
| }
|
|
|
|
|
|
|
|
|
|
|
| _docs_cache: dict[str, list[dict[str, str]]] = {}
|
| _index_cache: dict[str, tuple[Any, MultifieldParser]] = {}
|
| _cache_lock = asyncio.Lock()
|
| _openapi_cache: dict[str, Any] | None = None
|
| _openapi_index_cache: tuple[Any, MultifieldParser, list[dict[str, Any]]] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| async def _fetch_gradio_docs(query: str | None = None) -> str:
|
| """
|
| Fetch Gradio documentation.
|
| Without query: Get full documentation from llms.txt
|
| With query: Run embedding search on guides/demos for relevant content
|
| """
|
| async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
| if not query:
|
| resp = await client.get(GRADIO_LLMS_TXT_URL)
|
| resp.raise_for_status()
|
| return resp.text
|
|
|
| resp = await client.post(
|
| GRADIO_SEARCH_URL,
|
| headers={
|
| "Content-Type": "application/json",
|
| "Origin": "https://gradio-docs-mcp.up.railway.app",
|
| },
|
| json={
|
| "prompt_to_embed": query,
|
| "SYSTEM_PROMPT": "$INSERT_GUIDES_DOCS_DEMOS",
|
| "FALLBACK_PROMPT": "No results found",
|
| },
|
| )
|
| resp.raise_for_status()
|
| return resp.json().get("SYS_PROMPT", "No results found")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| async def _fetch_endpoint_docs(hf_token: str, endpoint: str) -> list[dict[str, str]]:
|
| """Fetch all docs for an endpoint by parsing sidebar and fetching each page."""
|
| url = f"https://huggingface.co/docs/{endpoint}"
|
| headers = {"Authorization": f"Bearer {hf_token}"}
|
|
|
| async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
| resp = await client.get(url, headers=headers)
|
| resp.raise_for_status()
|
|
|
| soup = BeautifulSoup(resp.text, "html.parser")
|
| sidebar = soup.find("nav", class_=lambda x: x and "flex-auto" in x)
|
| if not sidebar:
|
| raise ValueError(f"Could not find navigation sidebar for '{endpoint}'")
|
|
|
| nav_items = []
|
| for link in sidebar.find_all("a", href=True):
|
| href = link["href"]
|
| page_url = f"https://huggingface.co{href}" if href.startswith("/") else href
|
| nav_items.append({"title": link.get_text(strip=True), "url": page_url})
|
|
|
| if not nav_items:
|
| raise ValueError(f"No navigation links found for '{endpoint}'")
|
|
|
| async def fetch_page(item: dict[str, str]) -> dict[str, str]:
|
| md_url = f"{item['url']}.md"
|
| try:
|
| r = await client.get(md_url, headers=headers)
|
| r.raise_for_status()
|
| content = r.text.strip()
|
| glimpse = content[:200] + "..." if len(content) > 200 else content
|
| except Exception as e:
|
| content, glimpse = "", f"[Could not fetch: {str(e)[:50]}]"
|
| return {
|
| "title": item["title"],
|
| "url": item["url"],
|
| "md_url": md_url,
|
| "glimpse": glimpse,
|
| "content": content,
|
| "section": endpoint,
|
| }
|
|
|
| return list(await asyncio.gather(*[fetch_page(item) for item in nav_items]))
|
|
|
|
|
| async def _get_docs(hf_token: str, endpoint: str) -> list[dict[str, str]]:
|
| """Get docs for endpoint with caching. Expands composite endpoints."""
|
| async with _cache_lock:
|
| if endpoint in _docs_cache:
|
| return _docs_cache[endpoint]
|
|
|
| sub_endpoints = COMPOSITE_ENDPOINTS.get(endpoint, [endpoint])
|
| all_docs: list[dict[str, str]] = []
|
|
|
| for sub in sub_endpoints:
|
| async with _cache_lock:
|
| if sub in _docs_cache:
|
| all_docs.extend(_docs_cache[sub])
|
| continue
|
|
|
| docs = await _fetch_endpoint_docs(hf_token, sub)
|
| async with _cache_lock:
|
| _docs_cache[sub] = docs
|
| all_docs.extend(docs)
|
|
|
| async with _cache_lock:
|
| _docs_cache[endpoint] = all_docs
|
| return all_docs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| async def _build_search_index(
|
| endpoint: str, docs: list[dict[str, str]]
|
| ) -> tuple[Any, MultifieldParser]:
|
| """Build or retrieve cached Whoosh search index."""
|
| async with _cache_lock:
|
| if endpoint in _index_cache:
|
| return _index_cache[endpoint]
|
|
|
| analyzer = StemmingAnalyzer()
|
| schema = Schema(
|
| title=TEXT(stored=True, analyzer=analyzer),
|
| url=ID(stored=True, unique=True),
|
| md_url=ID(stored=True),
|
| section=ID(stored=True),
|
| glimpse=TEXT(stored=True, analyzer=analyzer),
|
| content=TEXT(stored=False, analyzer=analyzer),
|
| )
|
| storage = RamStorage()
|
| index = storage.create_index(schema)
|
| writer = index.writer()
|
| for doc in docs:
|
| writer.add_document(
|
| title=doc.get("title", ""),
|
| url=doc.get("url", ""),
|
| md_url=doc.get("md_url", ""),
|
| section=doc.get("section", endpoint),
|
| glimpse=doc.get("glimpse", ""),
|
| content=doc.get("content", ""),
|
| )
|
| writer.commit()
|
|
|
| parser = MultifieldParser(
|
| ["title", "content"],
|
| schema=schema,
|
| fieldboosts={"title": 2.0, "content": 1.0},
|
| group=OrGroup,
|
| )
|
|
|
| async with _cache_lock:
|
| _index_cache[endpoint] = (index, parser)
|
| return index, parser
|
|
|
|
|
| async def _search_docs(
|
| endpoint: str, docs: list[dict[str, str]], query: str, limit: int
|
| ) -> tuple[list[dict[str, Any]], str | None]:
|
| """Search docs using Whoosh. Returns (results, fallback_message)."""
|
| index, parser = await _build_search_index(endpoint, docs)
|
|
|
| try:
|
| query_obj = parser.parse(query)
|
| except Exception:
|
| return [], "Query contained unsupported syntax; showing default ordering."
|
|
|
| with index.searcher() as searcher:
|
| results = searcher.search(query_obj, limit=limit)
|
| matches = [
|
| {
|
| "title": hit["title"],
|
| "url": hit["url"],
|
| "md_url": hit.get("md_url", ""),
|
| "section": hit.get("section", endpoint),
|
| "glimpse": hit["glimpse"],
|
| "score": round(hit.score, 2),
|
| }
|
| for hit in results
|
| ]
|
|
|
| if not matches:
|
| return [], "No strong matches found; showing default ordering."
|
| return matches, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _format_results(
|
| endpoint: str,
|
| items: list[dict[str, Any]],
|
| total: int,
|
| query: str | None = None,
|
| note: str | None = None,
|
| ) -> str:
|
| """Format search results as readable text."""
|
| base_url = f"https://huggingface.co/docs/{endpoint}"
|
| out = f"Documentation structure for: {base_url}\n\n"
|
|
|
| if query:
|
| out += f"Query: '{query}' β showing {len(items)} result(s) out of {total} pages"
|
| if note:
|
| out += f" ({note})"
|
| out += "\n\n"
|
| else:
|
| out += f"Found {len(items)} page(s) (total available: {total}).\n"
|
| if note:
|
| out += f"({note})\n"
|
| out += "\n"
|
|
|
| for i, item in enumerate(items, 1):
|
| out += f"{i}. **{item['title']}**\n"
|
| out += f" URL: {item['url']}\n"
|
| out += f" Section: {item.get('section', endpoint)}\n"
|
| if query and "score" in item:
|
| out += f" Relevance score: {item['score']:.2f}\n"
|
| out += f" Glimpse: {item['glimpse']}\n\n"
|
|
|
| return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| async def explore_hf_docs_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
|
| """Explore documentation structure with optional search query."""
|
| endpoint = arguments.get("endpoint", "").lstrip("/")
|
| query = arguments.get("query")
|
| max_results = arguments.get("max_results")
|
|
|
| if not endpoint:
|
| return "Error: No endpoint provided", False
|
|
|
|
|
| if endpoint.lower() == "gradio":
|
| try:
|
| clean_query = (
|
| query.strip() if isinstance(query, str) and query.strip() else None
|
| )
|
| content = await _fetch_gradio_docs(clean_query)
|
| header = "# Gradio Documentation\n\n"
|
| if clean_query:
|
| header += f"Query: '{clean_query}'\n\n"
|
| header += "Source: https://gradio.app/docs\n\n---\n\n"
|
| return header + content, True
|
| except httpx.HTTPStatusError as e:
|
| return f"HTTP error fetching Gradio docs: {e.response.status_code}", False
|
| except httpx.RequestError as e:
|
| return f"Request error fetching Gradio docs: {str(e)}", False
|
| except Exception as e:
|
| return f"Error fetching Gradio docs: {str(e)}", False
|
|
|
|
|
| hf_token = os.environ.get("HF_TOKEN")
|
| if not hf_token:
|
| return "Error: HF_TOKEN environment variable not set", False
|
|
|
| try:
|
| max_results_int = int(max_results) if max_results is not None else None
|
| except (TypeError, ValueError):
|
| return "Error: max_results must be an integer", False
|
|
|
| if max_results_int is not None and max_results_int <= 0:
|
| return "Error: max_results must be greater than zero", False
|
|
|
| try:
|
| docs = await _get_docs(hf_token, endpoint)
|
| total = len(docs)
|
|
|
|
|
| if max_results_int is None:
|
| limit = DEFAULT_MAX_RESULTS
|
| limit_note = f"Showing top {DEFAULT_MAX_RESULTS} results (set max_results to adjust)."
|
| elif max_results_int > MAX_RESULTS_CAP:
|
| limit = MAX_RESULTS_CAP
|
| limit_note = f"Requested {max_results_int} but showing top {MAX_RESULTS_CAP} (maximum)."
|
| else:
|
| limit = max_results_int
|
| limit_note = None
|
|
|
|
|
| clean_query = (
|
| query.strip() if isinstance(query, str) and query.strip() else None
|
| )
|
| fallback_msg = None
|
|
|
| if clean_query:
|
| results, fallback_msg = await _search_docs(
|
| endpoint, docs, clean_query, limit
|
| )
|
| if not results:
|
| results = docs[:limit]
|
| else:
|
| results = docs[:limit]
|
|
|
|
|
| notes = []
|
| if fallback_msg:
|
| notes.append(fallback_msg)
|
| if limit_note:
|
| notes.append(limit_note)
|
| note = "; ".join(notes) if notes else None
|
|
|
| return _format_results(endpoint, results, total, clean_query, note), True
|
|
|
| except httpx.HTTPStatusError as e:
|
| return f"HTTP error: {e.response.status_code} - {e.response.text[:200]}", False
|
| except httpx.RequestError as e:
|
| return f"Request error: {str(e)}", False
|
| except ValueError as e:
|
| return f"Error: {str(e)}", False
|
| except Exception as e:
|
| return f"Unexpected error: {str(e)}", False
|
|
|
|
|
| async def hf_docs_fetch_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
|
| """Fetch full markdown content of a documentation page."""
|
| url = arguments.get("url", "")
|
| if not url:
|
| return "Error: No URL provided", False
|
|
|
| hf_token = os.environ.get("HF_TOKEN")
|
| if not hf_token:
|
| return "Error: HF_TOKEN environment variable not set", False
|
|
|
| if not url.endswith(".md"):
|
| url = f"{url}.md"
|
|
|
| try:
|
| async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
| resp = await client.get(
|
| url, headers={"Authorization": f"Bearer {hf_token}"}
|
| )
|
| resp.raise_for_status()
|
| return f"Documentation from: {url}\n\n{resp.text}", True
|
| except httpx.HTTPStatusError as e:
|
| return (
|
| f"HTTP error fetching {url}: {e.response.status_code} - {e.response.text[:200]}",
|
| False,
|
| )
|
| except httpx.RequestError as e:
|
| return f"Request error fetching {url}: {str(e)}", False
|
| except Exception as e:
|
| return f"Error fetching documentation: {str(e)}", False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| async def _fetch_openapi_spec() -> dict[str, Any]:
|
| """Fetch and cache HuggingFace OpenAPI specification."""
|
| global _openapi_cache
|
| if _openapi_cache is not None:
|
| return _openapi_cache
|
|
|
| async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
| resp = await client.get("https://huggingface.co/.well-known/openapi.json")
|
| resp.raise_for_status()
|
|
|
| _openapi_cache = resp.json()
|
| return _openapi_cache
|
|
|
|
|
| def _extract_all_tags(spec: dict[str, Any]) -> list[str]:
|
| """Extract all unique tags from OpenAPI spec."""
|
| tags = set()
|
| for tag_obj in spec.get("tags", []):
|
| if "name" in tag_obj:
|
| tags.add(tag_obj["name"])
|
| for path_item in spec.get("paths", {}).values():
|
| for method, op in path_item.items():
|
| if method in ["get", "post", "put", "delete", "patch", "head", "options"]:
|
| for tag in op.get("tags", []):
|
| tags.add(tag)
|
| return sorted(tags)
|
|
|
|
|
| def _extract_all_endpoints(spec: dict[str, Any]) -> list[dict[str, Any]]:
|
| """Extract all endpoints from OpenAPI spec."""
|
| servers = spec.get("servers", [])
|
| base_url = (
|
| servers[0].get("url", "https://huggingface.co")
|
| if servers
|
| else "https://huggingface.co"
|
| )
|
|
|
| endpoints = []
|
| for path, path_item in spec.get("paths", {}).items():
|
| for method, op in path_item.items():
|
| if method not in ["get", "post", "put", "delete", "patch", "head", "options"]:
|
| continue
|
| endpoints.append({
|
| "path": path,
|
| "method": method.upper(),
|
| "operationId": op.get("operationId", ""),
|
| "summary": op.get("summary", ""),
|
| "description": op.get("description", ""),
|
| "tags": " ".join(op.get("tags", [])),
|
| "parameters": op.get("parameters", []),
|
| "request_body": op.get("requestBody", {}),
|
| "responses": op.get("responses", {}),
|
| "base_url": base_url,
|
| })
|
| return endpoints
|
|
|
|
|
| async def _build_openapi_index() -> tuple[Any, MultifieldParser, list[dict[str, Any]]]:
|
| """Build or retrieve cached Whoosh index for OpenAPI endpoints."""
|
| global _openapi_index_cache
|
| async with _cache_lock:
|
| if _openapi_index_cache is not None:
|
| return _openapi_index_cache
|
|
|
| spec = await _fetch_openapi_spec()
|
| endpoints = _extract_all_endpoints(spec)
|
|
|
| analyzer = StemmingAnalyzer()
|
| schema = Schema(
|
| path=ID(stored=True, unique=True),
|
| method=ID(stored=True),
|
| operationId=TEXT(stored=True, analyzer=analyzer),
|
| summary=TEXT(stored=True, analyzer=analyzer),
|
| description=TEXT(stored=True, analyzer=analyzer),
|
| tags=TEXT(stored=True, analyzer=analyzer),
|
| param_names=TEXT(stored=False, analyzer=analyzer),
|
| )
|
| storage = RamStorage()
|
| index = storage.create_index(schema)
|
| writer = index.writer()
|
|
|
| for ep in endpoints:
|
| param_names = " ".join(p.get("name", "") for p in ep.get("parameters", []))
|
| writer.add_document(
|
| path=ep["path"],
|
| method=ep["method"],
|
| operationId=ep.get("operationId", ""),
|
| summary=ep.get("summary", ""),
|
| description=ep.get("description", ""),
|
| tags=ep.get("tags", ""),
|
| param_names=param_names,
|
| )
|
| writer.commit()
|
|
|
| parser = MultifieldParser(
|
| ["summary", "description", "operationId", "tags", "param_names"],
|
| schema=schema,
|
| fieldboosts={"summary": 3.0, "operationId": 2.0, "description": 1.0, "tags": 1.5},
|
| group=OrGroup,
|
| )
|
|
|
| async with _cache_lock:
|
| _openapi_index_cache = (index, parser, endpoints)
|
| return index, parser, endpoints
|
|
|
|
|
| async def _search_openapi(
|
| query: str, tag: str | None, limit: int = 20
|
| ) -> tuple[list[dict[str, Any]], str | None]:
|
| """Search OpenAPI endpoints using Whoosh. Returns (results, fallback_message)."""
|
| index, parser, endpoints = await _build_openapi_index()
|
|
|
| try:
|
| query_obj = parser.parse(query)
|
| except Exception:
|
| return [], "Query contained unsupported syntax."
|
|
|
| with index.searcher() as searcher:
|
| results = searcher.search(query_obj, limit=limit * 2)
|
| matches = []
|
| for hit in results:
|
|
|
| ep = next((e for e in endpoints if e["path"] == hit["path"] and e["method"] == hit["method"]), None)
|
| if ep is None:
|
| continue
|
|
|
| if tag and tag not in ep.get("tags", ""):
|
| continue
|
| matches.append({**ep, "score": round(hit.score, 2)})
|
| if len(matches) >= limit:
|
| break
|
|
|
| return matches, None if matches else "No matches found for query."
|
|
|
|
|
| def _generate_curl_example(endpoint: dict[str, Any]) -> str:
|
| """Generate curl command example for an endpoint."""
|
| method = endpoint["method"]
|
| path = endpoint["path"]
|
| base_url = endpoint["base_url"]
|
|
|
|
|
| full_path = path
|
| for param in endpoint.get("parameters", []):
|
| if param.get("in") == "path" and param.get("required"):
|
| name = param["name"]
|
| example = param.get(
|
| "example", param.get("schema", {}).get("example", f"<{name}>")
|
| )
|
| full_path = full_path.replace(f"{{{name}}}", str(example))
|
|
|
| curl = f"curl -X {method} \\\n '{base_url}{full_path}'"
|
|
|
|
|
| query_params = [p for p in endpoint.get("parameters", []) if p.get("in") == "query"]
|
| if query_params and query_params[0].get("required"):
|
| param = query_params[0]
|
| example = param.get("example", param.get("schema", {}).get("example", "value"))
|
| curl += f"?{param['name']}={example}"
|
|
|
| curl += " \\\n -H 'Authorization: Bearer $HF_TOKEN'"
|
|
|
|
|
| if method in ["POST", "PUT", "PATCH"] and endpoint.get("request_body"):
|
| content = endpoint["request_body"].get("content", {})
|
| if "application/json" in content:
|
| curl += " \\\n -H 'Content-Type: application/json'"
|
| schema = content["application/json"].get("schema", {})
|
| example = schema.get("example", "{}")
|
| if isinstance(example, dict):
|
| example = json.dumps(example, indent=2)
|
| curl += f" \\\n -d '{example}'"
|
|
|
| return curl
|
|
|
|
|
| def _format_parameters(parameters: list[dict[str, Any]]) -> str:
|
| """Format parameter information from OpenAPI spec."""
|
| if not parameters:
|
| return ""
|
|
|
| path_params = [p for p in parameters if p.get("in") == "path"]
|
| query_params = [p for p in parameters if p.get("in") == "query"]
|
| header_params = [p for p in parameters if p.get("in") == "header"]
|
|
|
| output = []
|
|
|
| for label, params in [
|
| ("Path Parameters", path_params),
|
| ("Query Parameters", query_params),
|
| ("Header Parameters", header_params),
|
| ]:
|
| if not params:
|
| continue
|
| if output:
|
| output.append("")
|
| output.append(f"**{label}:**")
|
| for p in params:
|
| name = p.get("name", "")
|
| required = " (required)" if p.get("required") else " (optional)"
|
| desc = p.get("description", "")
|
| ptype = p.get("schema", {}).get("type", "string")
|
| example = p.get("example") or p.get("schema", {}).get("example", "")
|
|
|
| output.append(f"- `{name}` ({ptype}){required}: {desc}")
|
| if example:
|
| output.append(f" Example: `{example}`")
|
|
|
| return "\n".join(output)
|
|
|
|
|
| def _format_response_info(responses: dict[str, Any]) -> str:
|
| """Format response information from OpenAPI spec."""
|
| if not responses:
|
| return "No response information available"
|
|
|
| output = []
|
| for status, resp_obj in list(responses.items())[:3]:
|
| desc = resp_obj.get("description", "")
|
| output.append(f"- **{status}**: {desc}")
|
| content = resp_obj.get("content", {})
|
| if "application/json" in content:
|
| schema = content["application/json"].get("schema", {})
|
| if "type" in schema:
|
| output.append(f" Returns: {schema.get('type', 'object')}")
|
|
|
| return "\n".join(output)
|
|
|
|
|
| def _format_openapi_results(
|
| results: list[dict[str, Any]],
|
| tag: str | None = None,
|
| query: str | None = None,
|
| note: str | None = None,
|
| ) -> str:
|
| """Format OpenAPI search results with curl examples."""
|
| if not results:
|
| if query and tag:
|
| return f"No API endpoints found matching '{query}' in tag '{tag}'"
|
| elif query:
|
| return f"No API endpoints found matching '{query}'"
|
| elif tag:
|
| return f"No API endpoints found with tag '{tag}'"
|
| return "No API endpoints found"
|
|
|
|
|
| if query and tag:
|
| out = f"# API Endpoints matching '{query}' (tag: `{tag}`)\n\n"
|
| elif query:
|
| out = f"# API Endpoints matching '{query}'\n\n"
|
| elif tag:
|
| out = f"# API Endpoints for tag: `{tag}`\n\n"
|
| else:
|
| out = "# API Endpoints\n\n"
|
|
|
| out += f"Found {len(results)} endpoint(s)"
|
| if note:
|
| out += f" ({note})"
|
| out += "\n\n---\n\n"
|
|
|
| for i, ep in enumerate(results, 1):
|
| out += f"## {i}. {ep['method']} {ep['path']}\n\n"
|
|
|
| if query and "score" in ep:
|
| out += f"**Relevance:** {ep['score']:.2f}\n\n"
|
|
|
| if ep.get("summary"):
|
| out += f"**Summary:** {ep['summary']}\n\n"
|
|
|
| if ep.get("description"):
|
| desc = ep["description"][:300]
|
| if len(ep["description"]) > 300:
|
| desc += "..."
|
| out += f"**Description:** {desc}\n\n"
|
|
|
| if ep.get("tags"):
|
| out += f"**Tags:** {ep['tags']}\n\n"
|
|
|
| params_info = _format_parameters(ep.get("parameters", []))
|
| if params_info:
|
| out += params_info + "\n\n"
|
|
|
| out += "**Usage:**\n```bash\n"
|
| out += _generate_curl_example(ep)
|
| out += "\n```\n\n"
|
|
|
| out += "**Returns:**\n"
|
| out += _format_response_info(ep["responses"])
|
| out += "\n\n---\n\n"
|
|
|
| return out
|
|
|
|
|
| async def search_openapi_handler(arguments: dict[str, Any]) -> tuple[str, bool]:
|
| """Search HuggingFace OpenAPI specification by query and/or tag."""
|
| tag = arguments.get("tag", "").strip() or None
|
| query = arguments.get("query", "").strip() or None
|
|
|
| if not tag and not query:
|
| return "Error: Provide either 'query' (keyword search) or 'tag' (category filter), or both.", False
|
|
|
| try:
|
| note = None
|
|
|
|
|
| if query:
|
| results, search_note = await _search_openapi(query, tag, limit=20)
|
|
|
|
|
| if results:
|
| return _format_openapi_results(results, tag=tag, query=query, note=search_note), True
|
|
|
|
|
| if tag:
|
| note = f"No matches for '{query}'; showing all endpoints in tag '{tag}'"
|
| else:
|
|
|
| return _format_openapi_results([], query=query), True
|
|
|
|
|
| if tag:
|
| _, _, endpoints = await _build_openapi_index()
|
| results = [ep for ep in endpoints if tag in ep.get("tags", "")]
|
| return _format_openapi_results(results, tag=tag, query=None, note=note), True
|
|
|
| return "Error: No results found", False
|
|
|
| except httpx.HTTPStatusError as e:
|
| return f"HTTP error fetching OpenAPI spec: {e.response.status_code}", False
|
| except httpx.RequestError as e:
|
| return f"Request error: {str(e)}", False
|
| except Exception as e:
|
| return f"Error searching OpenAPI spec: {str(e)}", False
|
|
|
|
|
| async def _get_api_search_tool_spec() -> dict[str, Any]:
|
| """Generate OpenAPI tool spec with tags populated at runtime."""
|
| spec = await _fetch_openapi_spec()
|
| tags = _extract_all_tags(spec)
|
|
|
| return {
|
| "name": "find_hf_api",
|
| "description": (
|
| "Find HuggingFace Hub REST API endpoints to make HTTP requests. Returns curl examples with authentication. "
|
| "β οΈ USE THIS TOOL when you need to call the HF Hub API directly - for operations like: "
|
| "uploading/downloading files, managing repos, listing models/datasets, getting user info, "
|
| "managing webhooks, collections, discussions, or any Hub interaction not covered by other tools. "
|
| "**Use cases:** (1) 'Stream Space logs' β query='space logs', "
|
| "(2) 'Get Space metrics/Zero-GPU usage' β query='space metrics', "
|
| "(3) 'List organization members' β query='organization members', "
|
| "(4) 'Generate repo access token' β query='jwt token', "
|
| "(5) 'Check repo security scan' β query='security scan'. "
|
| "**Search modes:** Use 'query' for keyword search, 'tag' to browse a category, or both. "
|
| "If query finds no results, falls back to showing all endpoints in the tag. "
|
| "**Output:** Full endpoint details with method, path, parameters, curl command, and response schema."
|
| ),
|
| "parameters": {
|
| "type": "object",
|
| "properties": {
|
| "query": {
|
| "type": "string",
|
| "description": (
|
| "Keyword search across endpoint summaries, descriptions, and operation IDs. "
|
| "Examples: 'upload file', 'create repository', 'list user models', 'delete branch', "
|
| "'webhook', 'collection', 'discussion comments'. Supports stemming (upload/uploading both work)."
|
| ),
|
| },
|
| "tag": {
|
| "type": "string",
|
| "enum": tags,
|
| "description": (
|
| "Filter by API category. Use alone to browse all endpoints in a category, "
|
| "or combine with 'query' to search within a category."
|
| ),
|
| },
|
| },
|
| "required": [],
|
| },
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| DOC_ENDPOINTS = [
|
| "hub",
|
| "transformers",
|
| "diffusers",
|
| "datasets",
|
| "gradio",
|
| "trackio",
|
| "smolagents",
|
| "huggingface_hub",
|
| "huggingface.js",
|
| "transformers.js",
|
| "inference-providers",
|
| "inference-endpoints",
|
| "peft",
|
| "accelerate",
|
| "optimum",
|
| "tokenizers",
|
| "courses",
|
| "evaluate",
|
| "tasks",
|
| "dataset-viewer",
|
| "trl",
|
| "simulate",
|
| "sagemaker",
|
| "timm",
|
| "safetensors",
|
| "tgi",
|
| "setfit",
|
| "lerobot",
|
| "autotrain",
|
| "tei",
|
| "bitsandbytes",
|
| "sentence_transformers",
|
| "chat-ui",
|
| "leaderboards",
|
| "lighteval",
|
| "argilla",
|
| "distilabel",
|
| "microsoft-azure",
|
| "kernels",
|
| "google-cloud",
|
| ]
|
|
|
| EXPLORE_HF_DOCS_TOOL_SPEC = {
|
| "name": "explore_hf_docs",
|
| "description": (
|
| "Explore Hugging Face documentation structure and discover available pages with 200-character previews. "
|
| "β οΈ MANDATORY: ALWAYS use this BEFORE implementing any ML task (training, fine-tuning, data processing, inference). "
|
| "Your training data may be outdated - current documentation is the source of truth. "
|
| "**Use when:** (1) Starting any implementation task, (2) User asks 'how to' questions, "
|
| "(3) Before writing training/processing code, (4) Researching library capabilities, "
|
| "(5) Verifying API syntax and parameters. "
|
| "**Pattern:** explore (discover structure) β fetch_hf_docs (get details) β implement with researched approach. "
|
| "Returns: Sidebar navigation with titles, URLs, and glimpses of all pages in the selected documentation. "
|
| "**Then:** Use fetch_hf_docs with specific URLs from results to get full content. "
|
| "**Critical for reliability:** Never implement based on internal knowledge without checking current docs first - APIs change frequently."
|
| " By default returns the top 20 results; set max_results (max 50) to adjust."
|
| ),
|
| "parameters": {
|
| "type": "object",
|
| "properties": {
|
| "endpoint": {
|
| "type": "string",
|
| "enum": DOC_ENDPOINTS,
|
| "description": (
|
| "The documentation endpoint to explore. Each endpoint corresponds to a major section of the Hugging Face documentation:\n\n"
|
| "β’ courses β All Hugging Face courses (LLM, robotics, MCP, smol (llm training), agents, deep RL, computer vision, games, diffusion, 3D, audio) and the cookbook recipes. Probably the best place for examples.\n"
|
| "β’ hub β Find answers to questions about models/datasets/spaces, auth, versioning, metadata.\n"
|
| "β’ transformers β Core model library: architectures, configs, tokenizers, training & inference APIs.\n"
|
| "β’ diffusers β Diffusion pipelines, schedulers, fine-tuning, training, and deployment patterns.\n"
|
| "β’ datasets β Dataset loading, streaming, processing, Arrow format, Hub integration.\n"
|
| "β’ gradio β UI components and demos for ML models. Uses Gradio's native API: without query returns full docs (llms.txt), with query uses embedding search for precise results.\n"
|
| "β’ trackio β Experiment tracking, metrics logging, and run comparison.\n"
|
| "β’ smolagents β Lightweight agent abstractions and tool-using patterns.\n"
|
| "β’ huggingface_hub β Python client for Hub operations (auth, upload/download, repo management).\n"
|
| "β’ huggingface.js β JS/TS client for Hub APIs in browser and Node.\n"
|
| "β’ transformers.js β Run Transformer models in browser/Node via WebGPU/WASM.\n"
|
| "β’ inference-providers β Unified interface for third-party inference backends.\n"
|
| "β’ inference-endpoints β Managed, scalable model deployments on HF infrastructure.\n"
|
| "β’ peft β Parameter-efficient fine-tuning methods (LoRA, adapters, etc.).\n"
|
| "β’ accelerate β Hardware-agnostic, distributed and mixed-precision training orchestration.\n"
|
| "β’ optimum β Hardware-aware optimization and model export tooling, including Habana, Neuron, Intel, ExecuTorch, and TPU variants.\n"
|
| "β’ tokenizers β Fast tokenizer internals, training, and low-level APIs.\n"
|
| "β’ evaluate β Metrics, evaluation workflows, and training-loop integration.\n"
|
| "β’ tasks β Canonical task definitions and model categorization.\n"
|
| "β’ dataset-viewer β Dataset preview, streaming views, and viewer internals.\n"
|
| "β’ trl β RLHF, DPO, PPO, and SFT utilities for LLMs.\n"
|
| "β’ simulate β Experimental simulation tools and workflows.\n"
|
| "β’ sagemaker β Deploying Hugging Face models on AWS SageMaker.\n"
|
| "β’ timm β Image model zoo and utilities via HF integrations.\n"
|
| "β’ safetensors β Safe, fast tensor serialization format.\n"
|
| "β’ tgi β High-throughput text generation server for LLMs.\n"
|
| "β’ setfit β Few-shot text classification via sentence embeddings.\n"
|
| "β’ lerobot β Robotics datasets, policies, and learning workflows.\n"
|
| "β’ autotrain β No/low-code model training on Hugging Face.\n"
|
| "β’ tei β Optimized inference server for embedding workloads.\n"
|
| "β’ bitsandbytes β Quantization and memory-efficient optimizers.\n"
|
| "β’ sentence_transformers β Embedding models, training recipes, similarity/search workflows.\n"
|
| "β’ chat-ui β Reference chat interfaces for LLM deployment.\n"
|
| "β’ leaderboards β Evaluation leaderboards and submission mechanics.\n"
|
| "β’ lighteval β Lightweight, reproducible LLM evaluation framework.\n"
|
| "β’ argilla β Data annotation, feedback, and human-in-the-loop workflows.\n"
|
| "β’ distilabel β Synthetic data generation and distillation pipelines.\n"
|
| "β’ microsoft-azure β Azure deployment and integration guides.\n"
|
| "β’ kernels β Lightweight execution environments and notebook-style workflows.\n"
|
| "β’ google-cloud β GCP deployment and serving workflows.\n"
|
| ),
|
| },
|
| "query": {
|
| "type": "string",
|
| "description": (
|
| "Optional keyword query to rank and filter documentation pages. "
|
| "For Gradio, use concise queries like 'how to use the image component' or 'audio component demo'."
|
| ),
|
| },
|
| "max_results": {
|
| "type": "integer",
|
| "description": "Max results (default 20, max 50). Ignored for Gradio.",
|
| "minimum": 1,
|
| "maximum": 50,
|
| },
|
| },
|
| "required": ["endpoint"],
|
| },
|
| }
|
|
|
| HF_DOCS_FETCH_TOOL_SPEC = {
|
| "name": "fetch_hf_docs",
|
| "description": (
|
| "Fetch full markdown content of a specific HF documentation page. "
|
| "β οΈ CRITICAL: Use this after explore_hf_docs to get detailed implementation guidance. "
|
| "**Use when:** (1) Found relevant page in explore_hf_docs results, (2) Need complete API documentation, "
|
| "(3) Need training method details (SFT/DPO/GRPO), (4) Need configuration examples, "
|
| "(5) Need parameter descriptions and usage patterns. "
|
| "**Pattern:** explore_hf_docs (find relevant page) β fetch_hf_docs (get full content) β implement using documented approach. "
|
| "Provide full URL from explore_hf_docs results (e.g., 'https://huggingface.co/docs/trl/sft_trainer'). "
|
| "Returns: Complete markdown documentation with examples, parameters, and usage patterns. "
|
| "**For training tasks:** ALWAYS fetch trainer docs (SFTConfig, DPOConfig, etc.) before creating training scripts. "
|
| "**Critical for reliability:** This ensures you use current APIs and best practices."
|
| ),
|
| "parameters": {
|
| "type": "object",
|
| "properties": {
|
| "url": {
|
| "type": "string",
|
| "description": (
|
| "The full URL to the documentation page. "
|
| "Example: 'https://huggingface.co/docs/trl/dpo_trainer' "
|
| "The .md extension will be added automatically if not present."
|
| ),
|
| },
|
| },
|
| "required": ["url"],
|
| },
|
| }
|
|
|