-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/runnable example #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c617ab8
9f5763b
0f0c61a
594aa77
7b72e50
a514262
40776d3
d0d8eb4
62a9399
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,10 +2,14 @@ | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| from typing import List, Optional, Dict, Any | ||||||||||||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||
| import os, re, logging | ||||||||||||||||||||||||||||||||||||||||||||
| from .utils import get_pipeline | ||||||||||||||||||||||||||||||||||||||||||||
| from utils.utils import _best_runnable_link | ||||||||||||||||||||||||||||||||||||||||||||
| from utils.previews import _build_preview_for_vlm | ||||||||||||||||||||||||||||||||||||||||||||
| from gradio_client import Client, handle_file | ||||||||||||||||||||||||||||||||||||||||||||
| import tempfile | ||||||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # -------- Gradio run_example tool ------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||
| class RunExampleInput(BaseModel): | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -21,47 +25,83 @@ class RunExampleOutput(BaseModel): | |||||||||||||||||||||||||||||||||||||||||||
| endpoint_url: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||||||||
| api_name: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||||||||
| notes: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||||||||
| # Back-compat: 'result_image' kept as alias for preview | ||||||||||||||||||||||||||||||||||||||||||||
| result_image: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||||||||
| result_preview: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||||||||
| result_origin: Optional[str] = None # original returned file (downloaded if URL) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| log = logging.getLogger("agent.run_example") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| _HF_SPACE_RE = re.compile(r"^https?://huggingface\.co/spaces/([^/]+)/([^/]+)/?$") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _normalize_space_identifier(url_or_name: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||
| """Accepts full HF Spaces URL or 'owner/space' or a direct app URL; returns a Client-acceptable src. | ||||||||||||||||||||||||||||||||||||||||||||
| Prefer 'owner/space' for HF Spaces page URLs. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| s = (url_or_name or "").strip() | ||||||||||||||||||||||||||||||||||||||||||||
| m = _HF_SPACE_RE.match(s) | ||||||||||||||||||||||||||||||||||||||||||||
| if m: | ||||||||||||||||||||||||||||||||||||||||||||
| owner, space = m.group(1), m.group(2) | ||||||||||||||||||||||||||||||||||||||||||||
| return f"{owner}/{space}" | ||||||||||||||||||||||||||||||||||||||||||||
| return s | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _choose_endpoint(endpoints: List[Dict[str, Any]], have_image: bool) -> Optional[Dict[str, Any]]: | ||||||||||||||||||||||||||||||||||||||||||||
| """Pick a sensible endpoint: prefer one that accepts an image if we have one; else the first text-only.""" | ||||||||||||||||||||||||||||||||||||||||||||
| def has_image(f: Dict[str, Any]) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||
| for i in f.get("inputs", []): | ||||||||||||||||||||||||||||||||||||||||||||
| t = str(i.get("type") or i.get("component") or "").lower() | ||||||||||||||||||||||||||||||||||||||||||||
| if "image" in t: | ||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if have_image: | ||||||||||||||||||||||||||||||||||||||||||||
| for f in endpoints: | ||||||||||||||||||||||||||||||||||||||||||||
| if has_image(f): | ||||||||||||||||||||||||||||||||||||||||||||
| return f | ||||||||||||||||||||||||||||||||||||||||||||
| # fallback: any endpoint | ||||||||||||||||||||||||||||||||||||||||||||
| return endpoints[0] if endpoints else None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _build_payload(fn: Dict[str, Any], image_path: Optional[str], extra_text: Optional[str]) -> List[Any]: | ||||||||||||||||||||||||||||||||||||||||||||
| inputs = fn.get("inputs", []) | ||||||||||||||||||||||||||||||||||||||||||||
| payload: List[Any] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| for spec in inputs: | ||||||||||||||||||||||||||||||||||||||||||||
| t = str(spec.get("type") or spec.get("component") or "").lower() | ||||||||||||||||||||||||||||||||||||||||||||
| # Gradio client supports passing file paths for image inputs | ||||||||||||||||||||||||||||||||||||||||||||
| if "image" in t and image_path: | ||||||||||||||||||||||||||||||||||||||||||||
| payload.append(handle_file(image_path) if handle_file else image_path) | ||||||||||||||||||||||||||||||||||||||||||||
| elif "textbox" in t or "text" in t or "textarea" in t: | ||||||||||||||||||||||||||||||||||||||||||||
| payload.append(extra_text or "") | ||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||
| # default empty for other inputs (checkbox, number, etc.) | ||||||||||||||||||||||||||||||||||||||||||||
| payload.append("") | ||||||||||||||||||||||||||||||||||||||||||||
| return payload | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _download_to_temp(url: str) -> Optional[str]: | ||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| r = requests.get(url, timeout=20) | ||||||||||||||||||||||||||||||||||||||||||||
| if r.status_code != 200 or not r.content: | ||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||
| # try to preserve extension from URL | ||||||||||||||||||||||||||||||||||||||||||||
| from urllib.parse import urlparse | ||||||||||||||||||||||||||||||||||||||||||||
| parsed = urlparse(url) | ||||||||||||||||||||||||||||||||||||||||||||
| ext = os.path.splitext(parsed.path)[1] | ||||||||||||||||||||||||||||||||||||||||||||
| if not ext: | ||||||||||||||||||||||||||||||||||||||||||||
| # guess based on content-type | ||||||||||||||||||||||||||||||||||||||||||||
| ct = r.headers.get("content-type", "").lower() | ||||||||||||||||||||||||||||||||||||||||||||
| if "tiff" in ct or "tif" in ct: | ||||||||||||||||||||||||||||||||||||||||||||
| ext = ".tif" | ||||||||||||||||||||||||||||||||||||||||||||
| elif "png" in ct: | ||||||||||||||||||||||||||||||||||||||||||||
| ext = ".png" | ||||||||||||||||||||||||||||||||||||||||||||
| elif "jpeg" in ct or "jpg" in ct: | ||||||||||||||||||||||||||||||||||||||||||||
| ext = ".jpg" | ||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||
| ext = ".bin" | ||||||||||||||||||||||||||||||||||||||||||||
| with tempfile.NamedTemporaryFile(delete=False, prefix="demo_result_", suffix=ext) as fd: | ||||||||||||||||||||||||||||||||||||||||||||
| fd.write(r.content) | ||||||||||||||||||||||||||||||||||||||||||||
| fd.flush() | ||||||||||||||||||||||||||||||||||||||||||||
| return fd.name | ||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _materialize_result(obj: Any) -> Optional[str]: | ||||||||||||||||||||||||||||||||||||||||||||
| """Try to materialize an image result to a local file and return the path. | ||||||||||||||||||||||||||||||||||||||||||||
| Accepts a filepath or URL from common Gradio outputs. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| # Direct file path | ||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| s = str(obj) | ||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||
| if not s: | ||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||
| # If it's an existing local file | ||||||||||||||||||||||||||||||||||||||||||||
| p = Path(s) | ||||||||||||||||||||||||||||||||||||||||||||
| if p.exists() and p.is_file(): | ||||||||||||||||||||||||||||||||||||||||||||
| return str(p) | ||||||||||||||||||||||||||||||||||||||||||||
| # If it's a URL, try to download | ||||||||||||||||||||||||||||||||||||||||||||
| if s.lower().startswith("http://") or s.lower().startswith("https://"): | ||||||||||||||||||||||||||||||||||||||||||||
| return _download_to_temp(s) | ||||||||||||||||||||||||||||||||||||||||||||
| # Unknown shape | ||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def tool_run_example(inp: RunExampleInput) -> RunExampleOutput: | ||||||||||||||||||||||||||||||||||||||||||||
| """Run a remote Gradio demo for a catalog tool on an optional user image using gradio_client. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Behavior: | ||||||||||||||||||||||||||||||||||||||||||||
| - Determine Space URL: prefer explicit endpoint_url, else catalog runnable link. | ||||||||||||||||||||||||||||||||||||||||||||
| - Discover API endpoints via view_api and choose one matching image/no-image needs. | ||||||||||||||||||||||||||||||||||||||||||||
| - Used agreed endpoint /segment for now | ||||||||||||||||||||||||||||||||||||||||||||
| - Build payload by mapping image path to image inputs and extra_text into text fields. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| pipe = get_pipeline() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -74,25 +114,62 @@ def tool_run_example(inp: RunExampleInput) -> RunExampleOutput: | |||||||||||||||||||||||||||||||||||||||||||
| return RunExampleOutput(tool_name=inp.tool_name, ran=False, notes="No runnable example URL found") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| client = Client(url) | ||||||||||||||||||||||||||||||||||||||||||||
| apis = client.view_api(return_format="dict") or {} | ||||||||||||||||||||||||||||||||||||||||||||
| endpoints = apis.get("endpoints") or apis.get("named_endpoints") or [] | ||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(endpoints, list): | ||||||||||||||||||||||||||||||||||||||||||||
| # some versions return dict of name->spec | ||||||||||||||||||||||||||||||||||||||||||||
| endpoints = list(endpoints.values()) | ||||||||||||||||||||||||||||||||||||||||||||
| fn = _choose_endpoint(endpoints, have_image=bool(inp.image_path)) | ||||||||||||||||||||||||||||||||||||||||||||
| if not fn: | ||||||||||||||||||||||||||||||||||||||||||||
| return RunExampleOutput(tool_name=inp.tool_name, ran=False, notes="No endpoints discovered", endpoint_url=url) | ||||||||||||||||||||||||||||||||||||||||||||
| api_name = fn.get("api_name") or fn.get("path") or fn.get("route") | ||||||||||||||||||||||||||||||||||||||||||||
| if not api_name: | ||||||||||||||||||||||||||||||||||||||||||||
| # common default | ||||||||||||||||||||||||||||||||||||||||||||
| api_name = "/predict" | ||||||||||||||||||||||||||||||||||||||||||||
| payload = _build_payload(fn, inp.image_path, inp.extra_text) | ||||||||||||||||||||||||||||||||||||||||||||
| src = _normalize_space_identifier(url) | ||||||||||||||||||||||||||||||||||||||||||||
| hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN") | ||||||||||||||||||||||||||||||||||||||||||||
| log.info("Gradio run_example: src=%s (from=%s), tool=%s", src, url, inp.tool_name) | ||||||||||||||||||||||||||||||||||||||||||||
| client = Client(src, hf_token=hf_token) if hf_token else Client(src) | ||||||||||||||||||||||||||||||||||||||||||||
| api_name = "/segment" # agreed endpoint | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| api_name = "/segment" # agreed endpoint | |
| # Discover API endpoints via view_api and choose one matching image/no-image needs | |
| api_info = client.view_api() | |
| api_name = None | |
| def _select_api_endpoint(api_info, has_image): | |
| # Prefer endpoints with image input if image is provided, else text-only | |
| for endpoint in api_info: | |
| inputs = endpoint.get("inputs", []) | |
| # Check for image input | |
| if has_image and any(i.get("type", "").lower() in ("image", "file", "filepath") for i in inputs): | |
| return endpoint.get("name") | |
| # If no image, prefer text-only endpoint | |
| if not has_image and all(i.get("type", "").lower() in ("text", "str", "string") for i in inputs): | |
| return endpoint.get("name") | |
| # Fallback: return first endpoint | |
| if api_info: | |
| return api_info[0].get("name") | |
| return None | |
| api_name = _select_api_endpoint(api_info, bool(inp.image_path)) | |
| if not api_name: | |
| return RunExampleOutput(tool_name=inp.tool_name, ran=False, notes="No suitable API endpoint found", endpoint_url=url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid date in version entry. The date "2025-10-22" is in the future relative to the current date. This should likely be "2024-10-22" or another valid past date.