benchmark-router-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchmark_router_cli-0.1.0/.gitignore +10 -0
- benchmark_router_cli-0.1.0/PKG-INFO +9 -0
- benchmark_router_cli-0.1.0/brouter_cli/__init__.py +0 -0
- benchmark_router_cli-0.1.0/brouter_cli/client.py +95 -0
- benchmark_router_cli-0.1.0/brouter_cli/config.py +50 -0
- benchmark_router_cli-0.1.0/brouter_cli/main.py +523 -0
- benchmark_router_cli-0.1.0/pyproject.toml +24 -0
- benchmark_router_cli-0.1.0/tests/test_cli.py +239 -0
- benchmark_router_cli-0.1.0/uv.lock +402 -0
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from .config import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApiError(Exception):
|
|
9
|
+
def __init__(self, status: int, message: str):
|
|
10
|
+
super().__init__(f"{status}: {message}")
|
|
11
|
+
self.status = status
|
|
12
|
+
self.message = message
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Client:
|
|
16
|
+
def __init__(self, settings: Settings, transport: httpx.BaseTransport | None = None):
|
|
17
|
+
if not settings.api_key:
|
|
18
|
+
raise ApiError(401, "no API key; run `brouter login` or set BROUTER_API_KEY")
|
|
19
|
+
self.api_url = settings.api_url
|
|
20
|
+
self._http = httpx.Client(
|
|
21
|
+
base_url=f"{settings.api_url}/api/v1",
|
|
22
|
+
headers={"Authorization": f"Bearer {settings.api_key}"},
|
|
23
|
+
timeout=60,
|
|
24
|
+
transport=transport,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
28
|
+
resp = self._http.request(method, path, **kwargs)
|
|
29
|
+
if resp.status_code >= 400:
|
|
30
|
+
try:
|
|
31
|
+
body = resp.json()
|
|
32
|
+
message = body.get("error") or body.get("message") or resp.text
|
|
33
|
+
except ValueError:
|
|
34
|
+
message = resp.text
|
|
35
|
+
raise ApiError(resp.status_code, str(message))
|
|
36
|
+
return resp.json()
|
|
37
|
+
|
|
38
|
+
def benchmarks(self) -> list[dict]:
|
|
39
|
+
return self._request("GET", "/benchmarks")["benchmarks"]
|
|
40
|
+
|
|
41
|
+
def models(self) -> list[dict]:
|
|
42
|
+
return self._request("GET", "/models")["models"]
|
|
43
|
+
|
|
44
|
+
def estimate(self, body: dict) -> dict:
|
|
45
|
+
return self._request("POST", "/estimate", json=body)
|
|
46
|
+
|
|
47
|
+
def create_run(self, body: dict) -> dict:
|
|
48
|
+
return self._request("POST", "/runs", json=body)["run"]
|
|
49
|
+
|
|
50
|
+
def runs(self) -> list[dict]:
|
|
51
|
+
return self._request("GET", "/runs")["runs"]
|
|
52
|
+
|
|
53
|
+
def run(self, run_id: str) -> dict:
|
|
54
|
+
return self._request("GET", f"/runs/{run_id}")["run"]
|
|
55
|
+
|
|
56
|
+
def download(self, run_id: str) -> list[dict]:
|
|
57
|
+
return self._request("GET", f"/runs/{run_id}/download")["files"]
|
|
58
|
+
|
|
59
|
+
def balance(self) -> dict:
|
|
60
|
+
return self._request("GET", "/balance")
|
|
61
|
+
|
|
62
|
+
def task(self, run_id: str) -> dict:
|
|
63
|
+
return self._request("GET", f"/runs/{run_id}/task")
|
|
64
|
+
|
|
65
|
+
def comparisons(self) -> list[dict]:
|
|
66
|
+
return self._request("GET", "/comparisons")["comparisons"]
|
|
67
|
+
|
|
68
|
+
def estimate_comparison(self, harness_id: str, model_ids: list[str]) -> dict:
|
|
69
|
+
return self._request("POST", "/comparisons", json={"harness_id": harness_id, "model_ids": model_ids, "dry_run": True})
|
|
70
|
+
|
|
71
|
+
def create_comparison(self, harness_id: str, model_ids: list[str]) -> dict:
|
|
72
|
+
# The server submits one run per benchmark x entrant, so this can take minutes.
|
|
73
|
+
body = {"harness_id": harness_id, "model_ids": model_ids}
|
|
74
|
+
return self._request("POST", "/comparisons", json=body, timeout=300)["comparison"]
|
|
75
|
+
|
|
76
|
+
def update_comparison(self, comparison_id: str, body: dict) -> dict:
|
|
77
|
+
return self._request("PATCH", f"/comparisons/{comparison_id}", json=body)["comparison"]
|
|
78
|
+
|
|
79
|
+
def harness_requests(self) -> list[dict]:
|
|
80
|
+
return self._request("GET", "/harness-requests")["requests"]
|
|
81
|
+
|
|
82
|
+
def create_harness_request(self, body: dict) -> dict:
|
|
83
|
+
return self._request("POST", "/harness-requests", json=body)["request"]
|
|
84
|
+
|
|
85
|
+
def benchmark_requests(self) -> list[dict]:
|
|
86
|
+
return self._request("GET", "/benchmark-requests")["requests"]
|
|
87
|
+
|
|
88
|
+
def create_benchmark_request(self, body: dict) -> dict:
|
|
89
|
+
return self._request("POST", "/benchmark-requests", json=body)["request"]
|
|
90
|
+
|
|
91
|
+
def checkout(self, amount: float) -> str:
|
|
92
|
+
return self._request("POST", "/credits/checkout", json={"credits": amount})["url"]
|
|
93
|
+
|
|
94
|
+
def fetch(self, url: str) -> httpx.Response:
|
|
95
|
+
return httpx.get(url, timeout=600, follow_redirects=True)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
DEFAULT_API_URL = "https://benchmarkrouter.newmeasure.ai"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _config_dir() -> Path:
|
|
10
|
+
return Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def config_path() -> Path:
|
|
14
|
+
return _config_dir() / "brouter" / "config.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _env(name: str) -> str | None:
|
|
18
|
+
# EVALHUB_* is the pre-rename spelling, still honoured.
|
|
19
|
+
return os.environ.get(f"BROUTER_{name}") or os.environ.get(f"EVALHUB_{name}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Settings:
|
|
24
|
+
api_url: str
|
|
25
|
+
api_key: str | None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_settings() -> Settings:
|
|
29
|
+
stored: dict = {}
|
|
30
|
+
path = config_path()
|
|
31
|
+
legacy = _config_dir() / "evalh" / "config.json"
|
|
32
|
+
if not path.exists() and legacy.exists():
|
|
33
|
+
path = legacy
|
|
34
|
+
if path.exists():
|
|
35
|
+
stored = json.loads(path.read_text())
|
|
36
|
+
return Settings(
|
|
37
|
+
api_url=(_env("API_URL") or stored.get("api_url") or DEFAULT_API_URL).rstrip("/"),
|
|
38
|
+
api_key=_env("API_KEY") or stored.get("api_key"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def save_settings(api_key: str, api_url: str | None) -> Path:
|
|
43
|
+
path = config_path()
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
data = {"api_key": api_key}
|
|
46
|
+
if api_url:
|
|
47
|
+
data["api_url"] = api_url.rstrip("/")
|
|
48
|
+
path.write_text(json.dumps(data, indent=2))
|
|
49
|
+
path.chmod(0o600)
|
|
50
|
+
return path
|