"""Offline Hugging Face Space for Trace2MCP. The UI is intentionally self-contained: it never calls a model or a remote API. It can use the Trace2MCP package when installed and falls back to a transparent graph preview while the package is being developed. """ from __future__ import annotations import html import json import os import sys from dataclasses import asdict, is_dataclass from datetime import datetime from pathlib import Path from typing import Any # Keep the public demo genuinely offline, including framework analytics and # version checks. The compiler itself never needs a model, token, or network. os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False") os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1") import gradio as gr ROOT = Path(__file__).resolve().parent SAMPLE_PATH = ROOT / "examples" / "sample_trace.json" STYLE_PATH = ROOT / "assets" / "style.css" SRC_PATH = ROOT / "src" if SRC_PATH.exists() and str(SRC_PATH) not in sys.path: sys.path.insert(0, str(SRC_PATH)) def _load_engine() -> tuple[Any | None, Any | None, str]: """Load the public API without making the Space depend on one import layout.""" try: from trace2mcp.compiler import compile_trace from trace2mcp.parsers import load_trace return load_trace, compile_trace, "package" except Exception as first_error: # pragma: no cover - depends on install state try: from trace2mcp import compile_trace, load_trace return load_trace, compile_trace, "package" except Exception: return None, None, f"preview ({type(first_error).__name__})" LOAD_TRACE, COMPILE_TRACE, ENGINE_MODE = _load_engine() def _parse_with_package(raw: dict[str, Any]) -> Any: """Parse an in-memory Trace v1 using either supported package surface.""" if LOAD_TRACE is None: return raw try: return LOAD_TRACE(raw) except TypeError: # The alpha loader accepts filesystem paths; the model is the canonical # in-memory equivalent used by the interactive editor. from trace2mcp.models import Trace return Trace.from_dict(raw) FALLBACK_SAMPLE: dict[str, Any] = { "version": "1", "name": "company_brief", "inputs": {"company": "Acme Labs"}, "events": [ { "id": "resolve", "tool": "resolve_company", "duration_ms": 520, "depends_on": [], "side_effect": "read", "arguments": {"company": "Acme Labs"}, }, { "id": "news", "tool": "search_news", "duration_ms": 900, "depends_on": ["resolve"], "side_effect": "read", "arguments": {"company_id": "acme-labs"}, }, { "id": "profile", "tool": "get_company_profile", "duration_ms": 730, "depends_on": ["resolve"], "side_effect": "read", "arguments": {"company_id": "acme-labs"}, }, { "id": "financials", "tool": "get_financial_snapshot", "duration_ms": 840, "depends_on": ["resolve"], "side_effect": "read", "arguments": {"company_id": "acme-labs"}, }, { "id": "rank", "tool": "rank_evidence", "duration_ms": 310, "depends_on": ["news", "profile", "financials"], "side_effect": "read", "arguments": { "news": "{{ news.items }}", "profile": "{{ profile }}", "financials": "{{ financials }}", }, }, { "id": "render", "tool": "render_brief", "duration_ms": 180, "depends_on": ["rank"], "side_effect": "read", "arguments": {"evidence": ["Verified company profile", "Recent public roadmap"]}, }, ], } def _sample_text() -> str: if SAMPLE_PATH.exists(): return SAMPLE_PATH.read_text(encoding="utf-8") return json.dumps(FALLBACK_SAMPLE, indent=2, ensure_ascii=False) def _trace_mapping(value: Any) -> dict[str, Any]: if isinstance(value, dict): return value for method_name in ("model_dump", "to_dict", "dict"): method = getattr(value, method_name, None) if callable(method): result = method() if isinstance(result, dict): return result if is_dataclass(value): return asdict(value) if hasattr(value, "__dict__"): return dict(vars(value)) raise TypeError("The trace loader did not return a mapping-like object.") def _serializable(value: Any) -> Any: if value is None or isinstance(value, (str, int, float, bool)): return value if isinstance(value, dict): return {str(key): _serializable(item) for key, item in value.items()} if isinstance(value, (list, tuple, set)): return [_serializable(item) for item in value] for method_name in ("model_dump", "to_dict", "dict"): method = getattr(value, method_name, None) if callable(method): return _serializable(method()) if is_dataclass(value): return _serializable(asdict(value)) if hasattr(value, "__dict__"): return _serializable(vars(value)) return repr(value) def _calls_from(trace: dict[str, Any]) -> list[dict[str, Any]]: raw_calls = trace.get("calls") or trace.get("steps") or trace.get("events") if not isinstance(raw_calls, list) or not raw_calls: raise ValueError("Expected a non-empty 'calls', 'steps', or 'events' array.") calls: list[dict[str, Any]] = [] for index, raw in enumerate(raw_calls): if not isinstance(raw, dict): raise ValueError(f"Step {index + 1} must be a JSON object.") call_id = str(raw.get("id") or raw.get("call_id") or f"step_{index + 1}") tool = str(raw.get("tool") or raw.get("name") or raw.get("tool_name") or "tool") duration = raw.get("duration_ms", raw.get("latency_ms")) started_at = raw.get("started_at") ended_at = raw.get("ended_at") if started_at and ended_at: try: started = datetime.fromisoformat(str(started_at).replace("Z", "+00:00")) ended = datetime.fromisoformat(str(ended_at).replace("Z", "+00:00")) duration = (ended - started).total_seconds() * 1000 except ValueError as error: raise ValueError(f"'{call_id}' has invalid ISO timestamps.") from error if duration is None: duration = 0 try: duration_ms = max(0.0, float(duration)) except (TypeError, ValueError) as error: raise ValueError(f"'{call_id}' has an invalid duration_ms value.") from error dependencies = raw.get("depends_on") or raw.get("dependencies") or [] if isinstance(dependencies, str): dependencies = [dependencies] if not isinstance(dependencies, list): raise ValueError(f"'{call_id}' depends_on must be an array.") if "read_only" in raw: read_only = bool(raw["read_only"]) else: read_only = raw.get("side_effect", "read") == "read" calls.append( { **raw, "id": call_id, "tool": tool, "duration_ms": duration_ms, "depends_on": [str(item) for item in dependencies], "read_only": read_only, } ) ids = [call["id"] for call in calls] if len(ids) != len(set(ids)): raise ValueError("Every step must have a unique id.") known = set(ids) for call in calls: missing = set(call["depends_on"]) - known if missing: raise ValueError( f"'{call['id']}' depends on unknown step(s): {', '.join(sorted(missing))}." ) return calls def _schedule(calls: list[dict[str, Any]]) -> tuple[list[list[dict[str, Any]]], float]: """Topologically group steps and calculate the graph's weighted critical path.""" by_id = {call["id"]: call for call in calls} remaining = set(by_id) completed: set[str] = set() stages: list[list[dict[str, Any]]] = [] finish_time: dict[str, float] = {} while remaining: ready_ids = [ call["id"] for call in calls if call["id"] in remaining and set(call["depends_on"]) <= completed ] if not ready_ids: raise ValueError("Dependency cycle detected; the workflow is not a DAG.") stage = [by_id[call_id] for call_id in ready_ids] stages.append(stage) for call in stage: start = max((finish_time[item] for item in call["depends_on"]), default=0.0) finish_time[call["id"]] = start + call["duration_ms"] completed.update(ready_ids) remaining.difference_update(ready_ids) critical_path_ms = max(finish_time.values(), default=0.0) return stages, critical_path_ms def _metric(label: str, value: str, note: str) -> str: return ( '
duration_ms; graph latency is the weighted critical path allowed by
depends_on. Tool execution is simulated in this Space. These values
demonstrate scheduling mechanics and do not prove production gains.
Turn a successful tool-call trace into a deterministic, reusable workflow.