Download eval/tools/run_eval.py from DataEyond/Agentic-Service-Data-Eyond-Catalog: direct link, hf CLI and curl.
- Browser
- Download file 5.22 kB
-
https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/main/eval/tools/run_eval.py
- Command line
-
hf download hf://spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/eval/tools/run_eval.py
-
curl -L -o run_eval.py https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/main/eval/tools/run_eval.py
5.22 kB
| """Tool-output eval runner (PR-2 Layer 2). | |
| Runs every case in `cases.py` — each builds a fixture with a property planted and | |
| asserts the tool reports it — then prints a per-tool table + aggregate summary and | |
| writes a timestamped JSON report under `results/` (never overwritten). | |
| **No LLM, no DB, no network.** The only compute is the tools themselves (which may | |
| fit sklearn/statsmodels — fast, local, deterministic). This is the shared, committed | |
| version of the fixtures that otherwise live gitignored under `tests/`. | |
| Invoke as a module so `src` imports resolve: | |
| PYTHONPATH=. python -m eval.tools.run_eval | |
| PYTHONPATH=. python -m eval.tools.run_eval --tool analyze_cluster | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import asyncio | |
| import json | |
| import time | |
| import traceback | |
| from dataclasses import asdict, dataclass | |
| from datetime import datetime | |
| from pathlib import Path | |
| from eval.tools.cases import CASES, EvalCase | |
| from src.tools.registry import ACTIVE_ANALYTICS_TOOLS | |
| _HERE = Path(__file__).resolve().parent | |
| RESULTS_DIR = _HERE / "results" | |
| class CaseResult: | |
| id: str | |
| tool: str | |
| prop: str | |
| passed: bool | |
| latency_ms: float | |
| error: str | None | |
| async def run_case(case: EvalCase) -> CaseResult: | |
| start = time.perf_counter() | |
| error: str | None = None | |
| try: | |
| await case.fn() | |
| passed = True | |
| except Exception as exc: # noqa: BLE001 — a failed assertion is a failed case, recorded | |
| passed = False | |
| error = f"{type(exc).__name__}: {exc}".strip() | |
| latency_ms = round((time.perf_counter() - start) * 1000, 1) | |
| return CaseResult(case.id, case.tool, case.prop, passed, latency_ms, error) | |
| def _coverage() -> dict[str, list[str]]: | |
| """Which ACTIVE analytics tools have >= 1 case, and which have none (§4 acceptance).""" | |
| covered = {c.tool for c in CASES} | |
| active = {t.name for t in ACTIVE_ANALYTICS_TOOLS} | |
| return { | |
| "covered": sorted(active & covered), | |
| "uncovered": sorted(active - covered), | |
| } | |
| def format_table(results: list[CaseResult]) -> str: | |
| header = f"{'ID':<38} {'TOOL':<22} {'ms':>6} OK" | |
| rule = "-" * len(header) | |
| lines = [rule, header, rule] | |
| for r in results: | |
| ok = "ok" if r.passed else "X" | |
| lines.append(f"{r.id:<38} {r.tool:<22} {r.latency_ms:>6.1f} {ok}") | |
| if not r.passed: | |
| lines.append(f" -> {r.error}") | |
| lines.append(rule) | |
| return "\n".join(lines) | |
| def format_summary(results: list[CaseResult], coverage: dict[str, list[str]]) -> str: | |
| passed = sum(r.passed for r in results) | |
| total = len(results) | |
| lines = ["SUMMARY", f" {passed}/{total} cases passed"] | |
| by_tool: dict[str, list[CaseResult]] = {} | |
| for r in results: | |
| by_tool.setdefault(r.tool, []).append(r) | |
| lines.append("") | |
| lines.append(" By tool") | |
| for tool in sorted(by_tool): | |
| sub = by_tool[tool] | |
| lines.append(f" {tool:<24} {sum(s.passed for s in sub)}/{len(sub)}") | |
| lines.append("") | |
| lines.append( | |
| f" Coverage: {len(coverage['covered'])}/" | |
| f"{len(coverage['covered']) + len(coverage['uncovered'])} ACTIVE analytics tools" | |
| ) | |
| if coverage["uncovered"]: | |
| lines.append(f" UNCOVERED: {', '.join(coverage['uncovered'])}") | |
| failures = [r for r in results if not r.passed] | |
| if failures: | |
| lines.append("") | |
| lines.append(f" FAILURES ({len(failures)})") | |
| for r in failures: | |
| lines.append(f" {r.id:<38} {r.error}") | |
| return "\n".join(lines) | |
| async def main() -> int: | |
| parser = argparse.ArgumentParser(description="Tool-output eval (PR-2 Layer 2)") | |
| parser.add_argument("--tool", default="", help="run only cases for this tool") | |
| parser.add_argument("--no-table", action="store_true", help="skip the detail table") | |
| args = parser.parse_args() | |
| cases = [c for c in CASES if not args.tool or c.tool == args.tool] | |
| started = datetime.now() | |
| print(f"Tool-Output Eval -- {started:%Y-%m-%d %H:%M:%S}") | |
| print(f"cases: {len(cases)} (no LLM, no DB)") | |
| results = [await run_case(c) for c in cases] | |
| coverage = _coverage() | |
| if not args.no_table: | |
| print(format_table(results)) | |
| print(format_summary(results, coverage)) | |
| report = { | |
| "run": { | |
| "timestamp": started.isoformat(timespec="seconds"), | |
| "total": len(results), | |
| "passed": sum(r.passed for r in results), | |
| "runtime_ms": round(sum(r.latency_ms for r in results), 1), | |
| "llm_calls": 0, | |
| }, | |
| "coverage": coverage, | |
| "cases": [asdict(r) for r in results], | |
| } | |
| RESULTS_DIR.mkdir(parents=True, exist_ok=True) | |
| out_path = RESULTS_DIR / f"tools_result_{started:%Y-%m-%d_%H%M%S}.json" | |
| out_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") | |
| print(f"\n-> saved: {out_path.relative_to(_HERE.parent.parent)}") | |
| # Non-zero exit on any failure so CI can gate on it. | |
| return 0 if all(r.passed for r in results) else 1 | |
| if __name__ == "__main__": | |
| try: | |
| raise SystemExit(asyncio.run(main())) | |
| except KeyboardInterrupt: | |
| traceback.print_exc() | |
| raise SystemExit(130) from None | |