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.
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()
@@ -0,0 +1,6 @@
1
+ """Client components for communicating with framework adapters."""
2
+
3
+ from .adapter_client import AdapterClient
4
+ from .discovery import AdapterDiscovery
5
+
6
+ __all__ = ["AdapterClient", "AdapterDiscovery"]