Tonic commited on
Commit
2d87eb3
·
verified ·
1 Parent(s): 97ce9ed

Upload trackio_api_client.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. trackio_api_client.py +329 -0
trackio_api_client.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Trackio API Client for Hugging Face Spaces
4
+ Uses gradio_client for proper API communication with automatic Space URL resolution
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import time
10
+ import logging
11
+ from typing import Dict, Any, Optional
12
+ from datetime import datetime
13
+ import os
14
+
15
+ # Setup logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ try:
20
+ from gradio_client import Client
21
+ GRADIO_CLIENT_AVAILABLE = True
22
+ except ImportError:
23
+ GRADIO_CLIENT_AVAILABLE = False
24
+ logger.warning("gradio_client not available. Install with: pip install gradio_client")
25
+
26
+ try:
27
+ from huggingface_hub import HfApi
28
+ HF_HUB_AVAILABLE = True
29
+ except ImportError:
30
+ HF_HUB_AVAILABLE = False
31
+ logger.warning("huggingface_hub not available. Install with: pip install huggingface-hub")
32
+
33
+ class TrackioAPIClient:
34
+ """API client for Trackio Space using gradio_client with automatic Space URL resolution"""
35
+
36
+ def __init__(self, space_id: str, hf_token: Optional[str] = None):
37
+ self.space_id = space_id
38
+ self.hf_token = hf_token
39
+ self.client = None
40
+
41
+ # Auto-resolve Space URL
42
+ self.space_url = self._resolve_space_url()
43
+
44
+ # Initialize gradio client
45
+ if GRADIO_CLIENT_AVAILABLE and self.space_url:
46
+ try:
47
+ try:
48
+ self.client = Client(self.space_url, hf_token=self.hf_token) # type: ignore
49
+ except TypeError:
50
+ self.client = Client(self.space_url)
51
+ logger.info(f"✅ Connected to Trackio Space: {self.space_id}")
52
+ except Exception as e:
53
+ logger.error(f"❌ Failed to connect to Trackio Space: {e}")
54
+ self.client = None
55
+ else:
56
+ logger.error("❌ gradio_client not available. Install with: pip install gradio_client")
57
+
58
+ def _resolve_space_url(self) -> Optional[str]:
59
+ """Resolve Space URL using Hugging Face Hub API"""
60
+ try:
61
+ # Clean the space_id - remove any URL prefixes
62
+ clean_space_id = self.space_id
63
+ if clean_space_id.startswith('http'):
64
+ # Extract space ID from URL
65
+ if '/spaces/' in clean_space_id:
66
+ clean_space_id = clean_space_id.split('/spaces/')[-1]
67
+ else:
68
+ # Try to extract from URL format
69
+ clean_space_id = clean_space_id.replace('https://', '').replace('http://', '')
70
+ if '.hf.space' in clean_space_id:
71
+ clean_space_id = clean_space_id.replace('.hf.space', '').replace('-', '/')
72
+
73
+ logger.info(f"🔧 Resolving Space URL for ID: {clean_space_id}")
74
+
75
+ if not HF_HUB_AVAILABLE:
76
+ logger.warning("⚠️ Hugging Face Hub not available, using default URL format")
77
+ # Fallback to default URL format
78
+ space_name = clean_space_id.replace('/', '-')
79
+ return f"https://{space_name}.hf.space"
80
+
81
+ # Use Hugging Face Hub API to get Space info
82
+ api = HfApi(token=self.hf_token)
83
+
84
+ # Get Space info
85
+ space_info = api.space_info(clean_space_id)
86
+ if space_info and hasattr(space_info, 'host'):
87
+ # Use the host directly from space_info
88
+ space_url = space_info.host
89
+ logger.info(f"✅ Resolved Space URL: {space_url}")
90
+ return space_url
91
+ else:
92
+ # Fallback to default URL format
93
+ space_name = clean_space_id.replace('/', '-')
94
+ space_url = f"https://{space_name}.hf.space"
95
+ logger.info(f"✅ Using fallback Space URL: {space_url}")
96
+ return space_url
97
+
98
+ except Exception as e:
99
+ logger.warning(f"⚠️ Failed to resolve Space URL: {e}")
100
+ # Fallback to default URL format
101
+ space_name = self.space_id.replace('/', '-')
102
+ space_url = f"https://{space_name}.hf.space"
103
+ logger.info(f"✅ Using fallback Space URL: {space_url}")
104
+ return space_url
105
+
106
+ def _make_api_call(self, api_name: str, *args) -> Dict[str, Any]:
107
+ """Make an API call to the Trackio Space using gradio_client"""
108
+ if not self.client:
109
+ return {"error": "Client not available"}
110
+
111
+ try:
112
+ logger.debug(f"Making API call to {api_name} with args: {args}")
113
+
114
+ # Use gradio_client to make the prediction
115
+ result = self.client.predict(*args, api_name=api_name)
116
+
117
+ logger.debug(f"API call result: {result}")
118
+ return {"success": True, "data": result}
119
+
120
+ except Exception as e:
121
+ logger.error(f"API call failed for {api_name}: {e}")
122
+ return {"error": f"API call failed: {str(e)}"}
123
+
124
+ def create_experiment(self, name: str, description: str = "") -> Dict[str, Any]:
125
+ """Create a new experiment"""
126
+ logger.info(f"Creating experiment: {name}")
127
+
128
+ result = self._make_api_call("/create_experiment_interface", name, description)
129
+
130
+ if "success" in result:
131
+ try:
132
+ payload = result.get("data")
133
+ if isinstance(payload, (list, tuple)):
134
+ result["data"] = payload[0]
135
+ except Exception:
136
+ pass
137
+ logger.info(f"Experiment created successfully: {result['data']}")
138
+ return result
139
+ else:
140
+ logger.error(f"Failed to create experiment: {result}")
141
+ return result
142
+
143
+ def log_metrics(self, experiment_id: str, metrics: Dict[str, Any], step: Optional[int] = None) -> Dict[str, Any]:
144
+ """Log metrics for an experiment"""
145
+ metrics_json = json.dumps(metrics)
146
+ step_str = str(step) if step is not None else ""
147
+
148
+ logger.info(f"Logging metrics for experiment {experiment_id} at step {step}")
149
+
150
+ result = self._make_api_call("/log_metrics_interface", experiment_id, metrics_json, step_str)
151
+
152
+ if "success" in result:
153
+ logger.info(f"Metrics logged successfully: {result['data']}")
154
+ return result
155
+ else:
156
+ logger.error(f"Failed to log metrics: {result}")
157
+ return result
158
+
159
+ def log_parameters(self, experiment_id: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
160
+ """Log parameters for an experiment"""
161
+ parameters_json = json.dumps(parameters)
162
+
163
+ logger.info(f"Logging parameters for experiment {experiment_id}")
164
+
165
+ result = self._make_api_call("/log_parameters_interface", experiment_id, parameters_json)
166
+
167
+ if "success" in result:
168
+ logger.info(f"Parameters logged successfully: {result['data']}")
169
+ return result
170
+ else:
171
+ logger.error(f"Failed to log parameters: {result}")
172
+ return result
173
+
174
+ def get_experiment_details(self, experiment_id: str) -> Dict[str, Any]:
175
+ """Get experiment details"""
176
+ logger.info(f"Getting details for experiment {experiment_id}")
177
+
178
+ result = self._make_api_call("/get_experiment_details", experiment_id)
179
+
180
+ if "success" in result:
181
+ logger.info(f"Experiment details retrieved: {result['data']}")
182
+ return result
183
+ else:
184
+ logger.error(f"Failed to get experiment details: {result}")
185
+ return result
186
+
187
+ def list_experiments(self) -> Dict[str, Any]:
188
+ """List all experiments"""
189
+ logger.info("Listing experiments")
190
+
191
+ result = self._make_api_call("/list_experiments_interface")
192
+
193
+ if "success" in result:
194
+ logger.info(f"Experiments listed successfully: {result['data']}")
195
+ return result
196
+ else:
197
+ logger.error(f"Failed to list experiments: {result}")
198
+ return result
199
+
200
+ def update_experiment_status(self, experiment_id: str, status: str) -> Dict[str, Any]:
201
+ """Update experiment status"""
202
+ logger.info(f"Updating experiment {experiment_id} status to {status}")
203
+
204
+ result = self._make_api_call("/update_experiment_status_interface", experiment_id, status)
205
+
206
+ if "success" in result:
207
+ logger.info(f"Experiment status updated successfully: {result['data']}")
208
+ return result
209
+ else:
210
+ logger.error(f"Failed to update experiment status: {result}")
211
+ return result
212
+
213
+ def simulate_training_data(self, experiment_id: str) -> Dict[str, Any]:
214
+ """Simulate training data for testing"""
215
+ logger.info(f"Simulating training data for experiment {experiment_id}")
216
+
217
+ result = self._make_api_call("/simulate_training_data", experiment_id)
218
+
219
+ if "success" in result:
220
+ logger.info(f"Training data simulated successfully: {result['data']}")
221
+ return result
222
+ else:
223
+ logger.error(f"Failed to simulate training data: {result}")
224
+ return result
225
+
226
+ def get_training_metrics(self, experiment_id: str) -> Dict[str, Any]:
227
+ """Get training metrics for an experiment"""
228
+ logger.info(f"Getting training metrics for experiment {experiment_id}")
229
+
230
+ result = self._make_api_call("/get_experiment_details", experiment_id)
231
+
232
+ if "success" in result:
233
+ logger.info(f"Training metrics retrieved: {result['data']}")
234
+ return result
235
+ else:
236
+ logger.error(f"Failed to get training metrics: {result}")
237
+ return result
238
+
239
+ def create_metrics_plot(self, experiment_id: str, metric_name: str = "loss") -> Dict[str, Any]:
240
+ """Create a metrics plot for an experiment"""
241
+ logger.info(f"Creating metrics plot for experiment {experiment_id}, metric: {metric_name}")
242
+
243
+ result = self._make_api_call("/create_metrics_plot", experiment_id, metric_name)
244
+
245
+ if "success" in result:
246
+ logger.info(f"Metrics plot created successfully")
247
+ return result
248
+ else:
249
+ logger.error(f"Failed to create metrics plot: {result}")
250
+ return result
251
+
252
+ def create_experiment_comparison(self, experiment_ids: str) -> Dict[str, Any]:
253
+ """Compare multiple experiments"""
254
+ logger.info(f"Creating experiment comparison for: {experiment_ids}")
255
+
256
+ result = self._make_api_call("/create_experiment_comparison", experiment_ids)
257
+
258
+ if "success" in result:
259
+ logger.info(f"Experiment comparison created successfully")
260
+ return result
261
+ else:
262
+ logger.error(f"Failed to create experiment comparison: {result}")
263
+ return result
264
+
265
+ def test_connection(self) -> Dict[str, Any]:
266
+ """Test connection to the Trackio Space"""
267
+ logger.info("Testing connection to Trackio Space")
268
+
269
+ try:
270
+ # Try to list experiments as a connection test
271
+ result = self.list_experiments()
272
+ if "success" in result:
273
+ return {"success": True, "message": "Connection successful"}
274
+ else:
275
+ return {"error": "Connection failed", "details": result}
276
+ except Exception as e:
277
+ return {"error": f"Connection test failed: {str(e)}"}
278
+
279
+ def get_space_info(self) -> Dict[str, Any]:
280
+ """Get information about the Space"""
281
+ try:
282
+ if not HF_HUB_AVAILABLE:
283
+ return {"error": "Hugging Face Hub not available"}
284
+
285
+ api = HfApi(token=self.hf_token)
286
+ space_info = api.space_info(self.space_id)
287
+
288
+ return {
289
+ "success": True,
290
+ "data": {
291
+ "space_id": self.space_id,
292
+ "space_url": self.space_url,
293
+ "space_info": {
294
+ "title": getattr(space_info, 'title', 'Unknown'),
295
+ "host": getattr(space_info, 'host', 'Unknown'),
296
+ "stage": getattr(space_info, 'stage', 'Unknown'),
297
+ "visibility": getattr(space_info, 'visibility', 'Unknown')
298
+ }
299
+ }
300
+ }
301
+ except Exception as e:
302
+ return {"error": f"Failed to get Space info: {str(e)}"}
303
+
304
+ # Factory function to create client with dynamic configuration
305
+ def create_trackio_client(space_id: Optional[str] = None, hf_token: Optional[str] = None) -> TrackioAPIClient:
306
+ """Create a TrackioAPIClient with dynamic configuration"""
307
+
308
+ # Get space_id from environment if not provided
309
+ if not space_id:
310
+ space_id = os.environ.get('TRACKIO_URL')
311
+ if not space_id:
312
+ # Try to construct from username and space name
313
+ username = os.environ.get('HF_USERNAME')
314
+ space_name = os.environ.get('TRACKIO_SPACE_NAME')
315
+ if username and space_name:
316
+ space_id = f"https://huggingface.co/spaces/{username}/{space_name}"
317
+ else:
318
+ logger.warning("⚠️ No space_id provided and could not determine from environment")
319
+ return None
320
+
321
+ # Get HF token from environment if not provided
322
+ if not hf_token:
323
+ hf_token = os.environ.get('HF_TOKEN')
324
+
325
+ if not space_id:
326
+ logger.error("❌ No space_id available for TrackioAPIClient")
327
+ return None
328
+
329
+ return TrackioAPIClient(space_id, hf_token)