eval-hub-sdk 0.1.0a0__py3-none-any.whl
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.
- eval_hub_sdk-0.1.0a0.dist-info/METADATA +711 -0
- eval_hub_sdk-0.1.0a0.dist-info/RECORD +27 -0
- eval_hub_sdk-0.1.0a0.dist-info/WHEEL +5 -0
- eval_hub_sdk-0.1.0a0.dist-info/entry_points.txt +2 -0
- eval_hub_sdk-0.1.0a0.dist-info/licenses/LICENSE +201 -0
- eval_hub_sdk-0.1.0a0.dist-info/top_level.txt +1 -0
- evalhub/__init__.py +84 -0
- evalhub/adapter/__init__.py +28 -0
- evalhub/adapter/api/__init__.py +6 -0
- evalhub/adapter/api/endpoints.py +342 -0
- evalhub/adapter/api/router.py +135 -0
- evalhub/adapter/cli.py +331 -0
- evalhub/adapter/client/__init__.py +6 -0
- evalhub/adapter/client/adapter_client.py +418 -0
- evalhub/adapter/client/discovery.py +275 -0
- evalhub/adapter/models/__init__.py +9 -0
- evalhub/adapter/models/framework.py +404 -0
- evalhub/adapter/oci/__init__.py +5 -0
- evalhub/adapter/oci/persister.py +76 -0
- evalhub/adapter/server/__init__.py +5 -0
- evalhub/adapter/server/app.py +157 -0
- evalhub/cli.py +331 -0
- evalhub/models/__init__.py +32 -0
- evalhub/models/api.py +388 -0
- evalhub/py.typed +0 -0
- evalhub/utils/__init__.py +5 -0
- evalhub/utils/logging.py +41 -0
evalhub/adapter/cli.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Command-line interface for EvalHub SDK."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# typing imports removed - using PEP 604 union syntax
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from .client import AdapterClient, AdapterDiscovery
|
|
15
|
+
from .models import AdapterConfig
|
|
16
|
+
from .server import run_adapter_server
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
name="evalhub-adapter",
|
|
20
|
+
help="EvalHub SDK command-line interface for framework adapters",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
err_console = Console(file=sys.stderr)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.command()
|
|
29
|
+
def run(
|
|
30
|
+
adapter_module: str = typer.Argument(
|
|
31
|
+
...,
|
|
32
|
+
help="Python module path to your adapter class (e.g., 'my_adapter:MyAdapter')",
|
|
33
|
+
),
|
|
34
|
+
config_file: Path | None = typer.Option(
|
|
35
|
+
None,
|
|
36
|
+
"--config",
|
|
37
|
+
"-c",
|
|
38
|
+
help="Path to configuration file (YAML or JSON)",
|
|
39
|
+
exists=True,
|
|
40
|
+
),
|
|
41
|
+
host: str = typer.Option("0.0.0.0", help="Host to bind to"),
|
|
42
|
+
port: int = typer.Option(8000, help="Port to bind to"),
|
|
43
|
+
workers: int = typer.Option(1, help="Number of worker processes"),
|
|
44
|
+
reload: bool = typer.Option(False, help="Enable auto-reload for development"),
|
|
45
|
+
log_level: str = typer.Option("INFO", help="Log level"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Run a framework adapter server.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
evalhub-adapter run my_framework.adapter:MyFrameworkAdapter --port 8080
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
# Parse adapter module and class
|
|
54
|
+
if ":" not in adapter_module:
|
|
55
|
+
err_console.print(
|
|
56
|
+
"[red]Error:[/red] Adapter module must be in format 'module:class'"
|
|
57
|
+
)
|
|
58
|
+
raise typer.Exit(1)
|
|
59
|
+
|
|
60
|
+
module_path, class_name = adapter_module.split(":", 1)
|
|
61
|
+
|
|
62
|
+
# Import the adapter class
|
|
63
|
+
try:
|
|
64
|
+
module = importlib.import_module(module_path)
|
|
65
|
+
adapter_class = getattr(module, class_name)
|
|
66
|
+
except ImportError as e:
|
|
67
|
+
err_console.print(f"[red]Error:[/red] Failed to import {module_path}: {e}")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
except AttributeError:
|
|
70
|
+
err_console.print(
|
|
71
|
+
f"[red]Error:[/red] Class {class_name} not found in {module_path}"
|
|
72
|
+
)
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
# Load configuration
|
|
76
|
+
config = AdapterConfig(
|
|
77
|
+
framework_id="custom_framework", # Default, should be overridden
|
|
78
|
+
adapter_name="Custom Framework Adapter",
|
|
79
|
+
version="1.0.0",
|
|
80
|
+
host=host,
|
|
81
|
+
port=port,
|
|
82
|
+
workers=workers,
|
|
83
|
+
max_concurrent_jobs=5,
|
|
84
|
+
job_timeout_seconds=3600,
|
|
85
|
+
memory_limit_gb=None,
|
|
86
|
+
log_level=log_level.upper(),
|
|
87
|
+
enable_metrics=True,
|
|
88
|
+
health_check_interval=30,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if config_file:
|
|
92
|
+
config = load_config(config_file, config)
|
|
93
|
+
|
|
94
|
+
# Display startup information
|
|
95
|
+
console.print(
|
|
96
|
+
Panel.fit(
|
|
97
|
+
f"[bold blue]EvalHub Framework Adapter[/bold blue]\n\n"
|
|
98
|
+
f"[bold]Framework:[/bold] {config.framework_id}\n"
|
|
99
|
+
f"[bold]Adapter:[/bold] {config.adapter_name}\n"
|
|
100
|
+
f"[bold]Version:[/bold] {config.version}\n"
|
|
101
|
+
f"[bold]Server:[/bold] http://{host}:{port}\n"
|
|
102
|
+
f"[bold]Workers:[/bold] {workers}\n"
|
|
103
|
+
f"[bold]Reload:[/bold] {reload}",
|
|
104
|
+
title="Starting Server",
|
|
105
|
+
border_style="blue",
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
console.print(f"📚 API Documentation: http://{host}:{port}/docs")
|
|
110
|
+
console.print(f"🏥 Health Check: http://{host}:{port}/api/v1/health")
|
|
111
|
+
console.print(f"ℹ️ Framework Info: http://{host}:{port}/api/v1/info")
|
|
112
|
+
console.print()
|
|
113
|
+
|
|
114
|
+
# Run the server
|
|
115
|
+
run_adapter_server(
|
|
116
|
+
adapter_class,
|
|
117
|
+
config,
|
|
118
|
+
reload=reload,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
except KeyboardInterrupt:
|
|
122
|
+
console.print("\n[yellow]Server stopped by user[/yellow]")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
err_console.print(f"[red]Error:[/red] {e}")
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command()
|
|
129
|
+
def info(
|
|
130
|
+
url: str = typer.Argument(..., help="Adapter URL (e.g., http://localhost:8080)")
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Get information about a framework adapter."""
|
|
133
|
+
|
|
134
|
+
async def get_info() -> None:
|
|
135
|
+
try:
|
|
136
|
+
async with AdapterClient(url) as client:
|
|
137
|
+
# Get framework info
|
|
138
|
+
framework_info = await client.get_framework_info()
|
|
139
|
+
|
|
140
|
+
# Display framework information
|
|
141
|
+
console.print(
|
|
142
|
+
Panel.fit(
|
|
143
|
+
f"[bold blue]{framework_info.name}[/bold blue]\n\n"
|
|
144
|
+
f"[bold]Framework ID:[/bold] {framework_info.framework_id}\n"
|
|
145
|
+
f"[bold]Version:[/bold] {framework_info.version}\n"
|
|
146
|
+
f"[bold]Description:[/bold] {framework_info.description or 'N/A'}\n"
|
|
147
|
+
f"[bold]Benchmarks:[/bold] {len(framework_info.supported_benchmarks)}\n"
|
|
148
|
+
f"[bold]Model Types:[/bold] {', '.join(framework_info.supported_model_types) or 'N/A'}",
|
|
149
|
+
title="Framework Information",
|
|
150
|
+
border_style="blue",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Display benchmarks
|
|
155
|
+
if framework_info.supported_benchmarks:
|
|
156
|
+
table = Table(title="Available Benchmarks")
|
|
157
|
+
table.add_column("ID", style="cyan")
|
|
158
|
+
table.add_column("Name")
|
|
159
|
+
table.add_column("Category", style="green")
|
|
160
|
+
table.add_column("Metrics", style="yellow")
|
|
161
|
+
|
|
162
|
+
for benchmark in framework_info.supported_benchmarks[
|
|
163
|
+
:10
|
|
164
|
+
]: # Show first 10
|
|
165
|
+
table.add_row(
|
|
166
|
+
benchmark.benchmark_id,
|
|
167
|
+
benchmark.name or "N/A",
|
|
168
|
+
benchmark.category or "N/A",
|
|
169
|
+
", ".join(benchmark.metrics)
|
|
170
|
+
if benchmark.metrics
|
|
171
|
+
else "N/A",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
console.print(table)
|
|
175
|
+
|
|
176
|
+
if len(framework_info.supported_benchmarks) > 10:
|
|
177
|
+
console.print(
|
|
178
|
+
f"... and {len(framework_info.supported_benchmarks) - 10} more benchmarks"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
err_console.print(f"[red]Error:[/red] Failed to get adapter info: {e}")
|
|
183
|
+
raise typer.Exit(1)
|
|
184
|
+
|
|
185
|
+
asyncio.run(get_info())
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.command()
|
|
189
|
+
def health(
|
|
190
|
+
url: str = typer.Argument(..., help="Adapter URL (e.g., http://localhost:8080)")
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Check the health of a framework adapter."""
|
|
193
|
+
|
|
194
|
+
async def check_health() -> None:
|
|
195
|
+
try:
|
|
196
|
+
async with AdapterClient(url) as client:
|
|
197
|
+
health_response = await client.health_check()
|
|
198
|
+
|
|
199
|
+
# Determine status color
|
|
200
|
+
status_color = {
|
|
201
|
+
"healthy": "green",
|
|
202
|
+
"unhealthy": "red",
|
|
203
|
+
"degraded": "yellow",
|
|
204
|
+
}.get(health_response.status, "white")
|
|
205
|
+
|
|
206
|
+
# Display health information
|
|
207
|
+
console.print(
|
|
208
|
+
Panel.fit(
|
|
209
|
+
f"[bold {status_color}]{health_response.status.upper()}[/bold {status_color}]\n\n"
|
|
210
|
+
f"[bold]Framework:[/bold] {health_response.framework_id}\n"
|
|
211
|
+
f"[bold]Version:[/bold] {health_response.version}\n"
|
|
212
|
+
f"[bold]Uptime:[/bold] {health_response.uptime_seconds or 0:.1f}s",
|
|
213
|
+
title="Health Status",
|
|
214
|
+
border_style=status_color,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Display dependencies
|
|
219
|
+
if health_response.dependencies:
|
|
220
|
+
table = Table(title="Dependencies")
|
|
221
|
+
table.add_column("Component", style="cyan")
|
|
222
|
+
table.add_column("Status")
|
|
223
|
+
table.add_column("Details")
|
|
224
|
+
|
|
225
|
+
for name, info in health_response.dependencies.items():
|
|
226
|
+
status = info.get("status", "unknown")
|
|
227
|
+
status_style = "green" if status == "healthy" else "red"
|
|
228
|
+
|
|
229
|
+
details = []
|
|
230
|
+
for key, value in info.items():
|
|
231
|
+
if key != "status":
|
|
232
|
+
details.append(f"{key}: {value}")
|
|
233
|
+
|
|
234
|
+
table.add_row(
|
|
235
|
+
name,
|
|
236
|
+
f"[{status_style}]{status}[/{status_style}]",
|
|
237
|
+
", ".join(details) if details else "N/A",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
console.print(table)
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
err_console.print(f"[red]Error:[/red] Failed to check health: {e}")
|
|
244
|
+
raise typer.Exit(1)
|
|
245
|
+
|
|
246
|
+
asyncio.run(check_health())
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@app.command()
|
|
250
|
+
def discover(
|
|
251
|
+
urls: list[str] = typer.Argument(
|
|
252
|
+
...,
|
|
253
|
+
help="Adapter URLs to discover (e.g., http://localhost:8080 http://localhost:8081)",
|
|
254
|
+
),
|
|
255
|
+
) -> None:
|
|
256
|
+
"""Discover and display information about multiple adapters."""
|
|
257
|
+
|
|
258
|
+
async def discover_adapters() -> None:
|
|
259
|
+
discovery = AdapterDiscovery()
|
|
260
|
+
|
|
261
|
+
console.print("[blue]Discovering adapters...[/blue]")
|
|
262
|
+
|
|
263
|
+
# Discover all adapters
|
|
264
|
+
adapters = []
|
|
265
|
+
for url in urls:
|
|
266
|
+
console.print(f" Checking {url}...")
|
|
267
|
+
adapter = await discovery.discover_adapter(url)
|
|
268
|
+
if adapter:
|
|
269
|
+
adapters.append(adapter)
|
|
270
|
+
|
|
271
|
+
if not adapters:
|
|
272
|
+
console.print("[red]No adapters discovered[/red]")
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
# Display results
|
|
276
|
+
table = Table(title="Discovered Adapters")
|
|
277
|
+
table.add_column("URL", style="cyan")
|
|
278
|
+
table.add_column("Framework ID")
|
|
279
|
+
table.add_column("Name")
|
|
280
|
+
table.add_column("Version")
|
|
281
|
+
table.add_column("Status")
|
|
282
|
+
|
|
283
|
+
for adapter in adapters:
|
|
284
|
+
status_style = {
|
|
285
|
+
"healthy": "green",
|
|
286
|
+
"unhealthy": "red",
|
|
287
|
+
"degraded": "yellow",
|
|
288
|
+
"unreachable": "red",
|
|
289
|
+
}.get(adapter.status, "white")
|
|
290
|
+
|
|
291
|
+
table.add_row(
|
|
292
|
+
adapter.url,
|
|
293
|
+
adapter.framework_id,
|
|
294
|
+
adapter.name,
|
|
295
|
+
adapter.version,
|
|
296
|
+
f"[{status_style}]{adapter.status}[/{status_style}]",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
console.print(table)
|
|
300
|
+
|
|
301
|
+
asyncio.run(discover_adapters())
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def load_config(config_file: Path, base_config: AdapterConfig) -> AdapterConfig:
|
|
305
|
+
"""Load configuration from file."""
|
|
306
|
+
import json
|
|
307
|
+
|
|
308
|
+
import yaml
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
with open(config_file) as f:
|
|
312
|
+
if config_file.suffix.lower() in [".yaml", ".yml"]:
|
|
313
|
+
config_data = yaml.safe_load(f)
|
|
314
|
+
else:
|
|
315
|
+
config_data = json.load(f)
|
|
316
|
+
|
|
317
|
+
# Update base config with file data
|
|
318
|
+
return base_config.model_copy(update=config_data)
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
err_console.print(f"[red]Error:[/red] Failed to load config file: {e}")
|
|
322
|
+
raise typer.Exit(1)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def main() -> None:
|
|
326
|
+
"""Main entry point for the CLI."""
|
|
327
|
+
app()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if __name__ == "__main__":
|
|
331
|
+
main()
|