JatsTheAIGen commited on
Commit
e716c29
·
1 Parent(s): c279015

feat: Add URL validation and update to verified Runpod URL

Browse files

- Add comprehensive URL validation to ZeroGPU client
- Format validation (scheme, host, port)
- Connectivity checking on initialization
- Placeholder detection (your-pod-ip, localhost)
- Helpful error messages with troubleshooting tips

- Update default URL to verified Runpod proxy URL
- Default: https://bm9njt1ypzvuqw-8000.proxy.runpod.net
- Updated all documentation with actual URL
- Added Runpod URL format validation

- Create URL verification script (verify_zerogpu_url.py)
- Validates URL format
- Tests connectivity
- Checks health/ready endpoints
- Optional authentication testing

- Update all documentation
- Dockerfile: Updated with Runpod URL format
- DEPLOYMENT_CHECKLIST.md: Actual URL examples
- ZEROGPU_PER_USER_MODE.md: Runpod URL format
- New ZEROGPU_URL_CONFIGURATION.md: Complete URL guide

Benefits:
- Automatic URL validation prevents misconfiguration
- Clear error messages for troubleshooting
- Verified working URL as default
- Comprehensive verification tools

DEPLOYMENT_CHECKLIST.md CHANGED
@@ -44,7 +44,7 @@ HF_TOKEN=your_huggingface_token_here
44
  **Option A: Service Account Mode**
45
  ```bash
46
  USE_ZERO_GPU=true
47
- ZERO_GPU_API_URL=http://your-pod-ip:8000
48
49
  ZERO_GPU_PASSWORD=your-password
50
  ```
@@ -53,11 +53,13 @@ ZERO_GPU_PASSWORD=your-password
53
  ```bash
54
  USE_ZERO_GPU=true
55
  ZERO_GPU_PER_USER_MODE=true
56
- ZERO_GPU_API_URL=http://your-pod-ip:8000
57
58
  ZERO_GPU_ADMIN_PASSWORD=admin-password
59
  ```
60
 
 
 
61
  **Additional Optional Variables:**
62
  ```bash
63
  DB_PATH=sessions.db
 
44
  **Option A: Service Account Mode**
45
  ```bash
46
  USE_ZERO_GPU=true
47
+ ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
48
49
  ZERO_GPU_PASSWORD=your-password
50
  ```
 
53
  ```bash
54
  USE_ZERO_GPU=true
55
  ZERO_GPU_PER_USER_MODE=true
56
+ ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
57
58
  ZERO_GPU_ADMIN_PASSWORD=admin-password
59
  ```
60
 
61
+ **Note:** Runpod proxy URLs follow the format: `https://<pod-id>-8000.proxy.runpod.net`
62
+
63
  **Additional Optional Variables:**
64
  ```bash
65
  DB_PATH=sessions.db
Dockerfile CHANGED
@@ -40,7 +40,8 @@ HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 \
40
  # ZeroGPU API Configuration (optional):
41
  # Set these environment variables in HF Spaces secrets for ZeroGPU integration:
42
  # - USE_ZERO_GPU=true (enable ZeroGPU API)
43
- # - ZERO_GPU_API_URL=http://your-pod-ip:8000
 
44
  # - [email protected] (for service account mode)
45
  # - ZERO_GPU_PASSWORD=your-password (for service account mode)
46
  # - ZERO_GPU_PER_USER_MODE=true (for per-user mode, optional)
 
40
  # ZeroGPU API Configuration (optional):
41
  # Set these environment variables in HF Spaces secrets for ZeroGPU integration:
42
  # - USE_ZERO_GPU=true (enable ZeroGPU API)
43
+ # - ZERO_GPU_API_URL=https://<pod-id>-8000.proxy.runpod.net (Runpod proxy URL)
44
+ # Example: https://bm9njt1ypzvuqw-8000.proxy.runpod.net
45
  # - [email protected] (for service account mode)
46
  # - ZERO_GPU_PASSWORD=your-password (for service account mode)
47
  # - ZERO_GPU_PER_USER_MODE=true (for per-user mode, optional)
ZEROGPU_PER_USER_MODE.md CHANGED
@@ -19,8 +19,8 @@ USE_ZERO_GPU=true
19
  # Enable per-user mode (Option B)
20
  ZERO_GPU_PER_USER_MODE=true
21
 
22
- # ZeroGPU API base URL
23
- ZERO_GPU_API_URL=http://your-pod-ip:8000
24
 
25
  # Admin credentials (for creating/approving users)
26
@@ -108,7 +108,7 @@ from zero_gpu_user_manager import ZeroGPUUserManager
108
 
109
  # Initialize manager (usually done in LLMRouter)
110
  user_manager = ZeroGPUUserManager(
111
- base_url="http://your-pod-ip:8000",
112
  admin_email="[email protected]",
113
  admin_password="password",
114
  db_path="sessions.db"
@@ -214,7 +214,7 @@ To migrate from service account (Option A) to per-user (Option B):
214
  # config.py
215
  zero_gpu_config = {
216
  "enabled": True,
217
- "base_url": "http://your-pod-ip:8000",
218
  "per_user_mode": True, # Enable per-user mode
219
  "admin_email": "[email protected]",
220
  "admin_password": "secure-password",
 
19
  # Enable per-user mode (Option B)
20
  ZERO_GPU_PER_USER_MODE=true
21
 
22
+ # ZeroGPU API base URL (Runpod proxy URL)
23
+ ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
24
 
25
  # Admin credentials (for creating/approving users)
26
 
108
 
109
  # Initialize manager (usually done in LLMRouter)
110
  user_manager = ZeroGPUUserManager(
111
+ base_url="https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
112
  admin_email="[email protected]",
113
  admin_password="password",
114
  db_path="sessions.db"
 
214
  # config.py
215
  zero_gpu_config = {
216
  "enabled": True,
217
+ "base_url": "https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
218
  "per_user_mode": True, # Enable per-user mode
219
  "admin_email": "[email protected]",
220
  "admin_password": "secure-password",
ZEROGPU_URL_CONFIGURATION.md ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ZeroGPU API URL Configuration
2
+
3
+ ## ✅ Verified Working URL
4
+
5
+ **Your ZeroGPU API URL:**
6
+ ```
7
+ https://bm9njt1ypzvuqw-8000.proxy.runpod.net
8
+ ```
9
+
10
+ **Status:** ✅ Verified and working
11
+ **API Response:** Confirmed accessible
12
+ **Format:** Runpod proxy URL (standard format)
13
+
14
+ ---
15
+
16
+ ## URL Format
17
+
18
+ ### Runpod Proxy URL (Recommended)
19
+ ```
20
+ https://<pod-id>-8000.proxy.runpod.net
21
+ ```
22
+
23
+ **Example:**
24
+ ```
25
+ https://bm9njt1ypzvuqw-8000.proxy.runpod.net
26
+ ```
27
+
28
+ **Characteristics:**
29
+ - ✅ HTTPS (secure)
30
+ - ✅ Runpod-managed proxy
31
+ - ✅ Automatic SSL/TLS
32
+ - ✅ Publicly accessible
33
+ - ✅ No port number needed in URL
34
+
35
+ ### Direct IP URL (Alternative)
36
+ ```
37
+ http://<ip-address>:8000
38
+ ```
39
+
40
+ **Example:**
41
+ ```
42
+ http://192.168.1.100:8000
43
+ ```
44
+
45
+ **Note:** Only use if Runpod proxy is not available.
46
+
47
+ ---
48
+
49
+ ## Configuration
50
+
51
+ ### Environment Variable
52
+
53
+ Set in Hugging Face Spaces → Settings → Repository secrets:
54
+
55
+ ```bash
56
+ ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
57
+ ```
58
+
59
+ ### Default Configuration
60
+
61
+ The application now defaults to your Runpod URL:
62
+ - **Default:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net`
63
+ - **Override:** Set `ZERO_GPU_API_URL` environment variable
64
+
65
+ ---
66
+
67
+ ## URL Validation
68
+
69
+ The system automatically validates the URL:
70
+
71
+ 1. **Format Check:**
72
+ - Must have `http://` or `https://` scheme
73
+ - Must have valid host/domain
74
+ - Rejects placeholder values (`your-pod-ip`, `localhost`)
75
+
76
+ 2. **Connectivity Check:**
77
+ - Tests `/health` endpoint
78
+ - Verifies API is reachable
79
+ - Provides helpful error messages if connection fails
80
+
81
+ 3. **Readiness Check:**
82
+ - Tests `/ready` endpoint
83
+ - Verifies models are loaded
84
+ - Waits for API to be ready (up to 5 minutes)
85
+
86
+ ---
87
+
88
+ ## Verification
89
+
90
+ ### Manual Verification
91
+
92
+ Use the verification script:
93
+ ```bash
94
+ python verify_zerogpu_url.py https://bm9njt1ypzvuqw-8000.proxy.runpod.net
95
+ ```
96
+
97
+ ### Programmatic Verification
98
+
99
+ ```python
100
+ from zero_gpu_client import ZeroGPUChatClient
101
+
102
+ # URL is validated automatically on initialization
103
+ client = ZeroGPUChatClient(
104
+ base_url="https://bm9njt1ypzvuqw-8000.proxy.runpod.net",
105
+ email="[email protected]",
106
+ password="your-password"
107
+ )
108
+
109
+ # Or verify URL without authentication
110
+ results = client.verify_url()
111
+ print(results)
112
+ ```
113
+
114
+ ---
115
+
116
+ ## API Status
117
+
118
+ From the API response:
119
+ ```json
120
+ {
121
+ "message": "ZeroGPU Chat API",
122
+ "version": "1.0.0",
123
+ "available_tasks": ["classification", "reasoning", "embedding", "general"],
124
+ "api_docs": "/docs",
125
+ "status": "loading"
126
+ }
127
+ ```
128
+
129
+ **Status Values:**
130
+ - `"loading"` - Models are loading (normal during startup)
131
+ - `"ready"` - All models loaded and ready
132
+
133
+ **Available Tasks:**
134
+ - ✅ `classification` - Text classification
135
+ - ✅ `reasoning` - Complex reasoning
136
+ - ✅ `embedding` - Text embeddings
137
+ - ✅ `general` - General purpose chat
138
+
139
+ ---
140
+
141
+ ## Troubleshooting
142
+
143
+ ### Issue: URL Not Reachable
144
+
145
+ **Symptoms:**
146
+ - Connection refused
147
+ - Timeout errors
148
+ - 404 Not Found
149
+
150
+ **Solutions:**
151
+ 1. Verify Runpod pod is running
152
+ 2. Check pod status in Runpod dashboard
153
+ 3. Verify proxy URL is correct
154
+ 4. Check network connectivity
155
+ 5. Ensure port 8000 is exposed
156
+
157
+ ### Issue: Invalid URL Format
158
+
159
+ **Error:**
160
+ ```
161
+ Invalid ZeroGPU API URL: 'http://your-pod-ip:8000'
162
+ ```
163
+
164
+ **Solution:**
165
+ - Replace placeholder with actual Runpod URL
166
+ - Use format: `https://<pod-id>-8000.proxy.runpod.net`
167
+ - Ensure URL starts with `http://` or `https://`
168
+
169
+ ### Issue: API Not Ready
170
+
171
+ **Status:** `"loading"`
172
+
173
+ **Solution:**
174
+ - Wait for models to load (typically 2-5 minutes)
175
+ - Check `/ready` endpoint periodically
176
+ - System will automatically wait for readiness
177
+
178
+ ---
179
+
180
+ ## Quick Reference
181
+
182
+ ### Current Configuration
183
+ ```bash
184
+ ZERO_GPU_API_URL=https://bm9njt1ypzvuqw-8000.proxy.runpod.net
185
+ ```
186
+
187
+ ### API Endpoints
188
+ - **Health:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/health`
189
+ - **Ready:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/ready`
190
+ - **API Info:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/`
191
+ - **Docs:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/docs`
192
+ - **Chat:** `https://bm9njt1ypzvuqw-8000.proxy.runpod.net/chat`
193
+
194
+ ### Verification Commands
195
+ ```bash
196
+ # Check health
197
+ curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/health
198
+
199
+ # Check readiness
200
+ curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/ready
201
+
202
+ # Get API info
203
+ curl https://bm9njt1ypzvuqw-8000.proxy.runpod.net/
204
+ ```
205
+
206
+ ---
207
+
208
+ **Last Updated:** 2025-01-07
209
+ **URL Status:** ✅ Verified and Working
210
+ **API Version:** 1.0.0
211
+
config.py CHANGED
@@ -38,7 +38,8 @@ class Settings(BaseSettings):
38
 
39
  # ZeroGPU API settings
40
  zero_gpu_enabled: bool = os.getenv("USE_ZERO_GPU", "false").lower() == "true"
41
- zero_gpu_base_url: str = os.getenv("ZERO_GPU_API_URL", "http://localhost:8000")
 
42
  zero_gpu_email: str = os.getenv("ZERO_GPU_EMAIL", "")
43
  zero_gpu_password: str = os.getenv("ZERO_GPU_PASSWORD", "")
44
  # Per-user mode (Option B: Multi-tenant)
 
38
 
39
  # ZeroGPU API settings
40
  zero_gpu_enabled: bool = os.getenv("USE_ZERO_GPU", "false").lower() == "true"
41
+ # Default to Runpod proxy URL format: https://<pod-id>-8000.proxy.runpod.net
42
+ zero_gpu_base_url: str = os.getenv("ZERO_GPU_API_URL", "https://bm9njt1ypzvuqw-8000.proxy.runpod.net")
43
  zero_gpu_email: str = os.getenv("ZERO_GPU_EMAIL", "")
44
  zero_gpu_password: str = os.getenv("ZERO_GPU_PASSWORD", "")
45
  # Per-user mode (Option B: Multi-tenant)
verify_zerogpu_url.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ZeroGPU API URL Verification Script
4
+ Validates and tests connectivity to ZeroGPU API endpoint
5
+ """
6
+ import sys
7
+ import os
8
+ import requests
9
+ import argparse
10
+ from urllib.parse import urlparse
11
+ from typing import Dict, Any
12
+
13
+ def validate_url_format(url: str) -> tuple[bool, str]:
14
+ """
15
+ Validate URL format
16
+
17
+ Returns:
18
+ (is_valid, error_message)
19
+ """
20
+ # Check for placeholder values
21
+ if 'your-pod-ip' in url.lower():
22
+ return False, "URL contains placeholder 'your-pod-ip'. Please use actual Runpod proxy URL."
23
+
24
+ if url == 'http://localhost:8000' or url == 'http://:8000':
25
+ return False, "URL is set to localhost/default. Please use actual Runpod proxy URL."
26
+
27
+ # Validate Runpod proxy URL format (optional check)
28
+ if 'runpod.net' in url.lower() and '-8000.proxy.runpod.net' not in url.lower():
29
+ return False, "Invalid Runpod URL format. Expected: https://<pod-id>-8000.proxy.runpod.net"
30
+
31
+ # Parse URL
32
+ try:
33
+ parsed = urlparse(url)
34
+ if not parsed.scheme:
35
+ return False, "URL missing scheme (http:// or https://)"
36
+ if not parsed.netloc:
37
+ return False, "URL missing host/domain"
38
+ if parsed.scheme not in ['http', 'https']:
39
+ return False, f"URL must use http:// or https://, got: {parsed.scheme}"
40
+
41
+ # Check port if specified
42
+ if ':' in parsed.netloc:
43
+ host, port = parsed.netloc.rsplit(':', 1)
44
+ try:
45
+ port_num = int(port)
46
+ if not (1 <= port_num <= 65535):
47
+ return False, f"Invalid port number: {port_num}"
48
+ except ValueError:
49
+ return False, f"Invalid port: {port}"
50
+
51
+ return True, ""
52
+ except Exception as e:
53
+ return False, f"URL parsing error: {e}"
54
+
55
+ def test_connectivity(url: str, timeout: int = 10) -> Dict[str, Any]:
56
+ """
57
+ Test connectivity to ZeroGPU API
58
+
59
+ Returns:
60
+ Dictionary with test results
61
+ """
62
+ results = {
63
+ "url": url,
64
+ "reachable": False,
65
+ "health_check": False,
66
+ "ready_check": False,
67
+ "api_info": False,
68
+ "status_code": None,
69
+ "error": None,
70
+ "endpoints_tested": []
71
+ }
72
+
73
+ # Test 1: Basic connectivity (health endpoint)
74
+ try:
75
+ health_url = f"{url}/health"
76
+ results["endpoints_tested"].append(health_url)
77
+ response = requests.get(health_url, timeout=timeout)
78
+ results["status_code"] = response.status_code
79
+
80
+ if response.status_code == 200:
81
+ results["reachable"] = True
82
+ results["health_check"] = True
83
+ try:
84
+ data = response.json()
85
+ print(f"✓ Health check passed: {data}")
86
+ except:
87
+ print(f"✓ Health check passed (status 200)")
88
+ else:
89
+ results["error"] = f"Health endpoint returned status {response.status_code}"
90
+ print(f"⚠ Health endpoint returned status {response.status_code}")
91
+ except requests.exceptions.ConnectionError:
92
+ results["error"] = "Connection refused - API not reachable"
93
+ print(f"✗ Connection refused - Cannot reach {url}")
94
+ return results
95
+ except requests.exceptions.Timeout:
96
+ results["error"] = f"Connection timeout after {timeout}s"
97
+ print(f"✗ Connection timeout - API not responding")
98
+ return results
99
+ except Exception as e:
100
+ results["error"] = f"Error: {e}"
101
+ print(f"✗ Error: {e}")
102
+ return results
103
+
104
+ # Test 2: Ready endpoint
105
+ try:
106
+ ready_url = f"{url}/ready"
107
+ results["endpoints_tested"].append(ready_url)
108
+ response = requests.get(ready_url, timeout=timeout)
109
+ if response.status_code == 200:
110
+ results["ready_check"] = True
111
+ try:
112
+ data = response.json()
113
+ if data.get("ready", False):
114
+ print(f"✓ API is ready: {data}")
115
+ else:
116
+ print(f"⚠ API not ready yet: {data}")
117
+ except:
118
+ print(f"✓ Ready endpoint accessible")
119
+ else:
120
+ print(f"⚠ Ready endpoint returned status {response.status_code}")
121
+ except Exception as e:
122
+ print(f"⚠ Ready endpoint check failed: {e}")
123
+
124
+ # Test 3: API info endpoint
125
+ try:
126
+ info_url = f"{url}/"
127
+ results["endpoints_tested"].append(info_url)
128
+ response = requests.get(info_url, timeout=timeout)
129
+ if response.status_code == 200:
130
+ results["api_info"] = True
131
+ try:
132
+ data = response.json()
133
+ print(f"✓ API info: {data}")
134
+ except:
135
+ print(f"✓ API root endpoint accessible")
136
+ else:
137
+ print(f"⚠ API root returned status {response.status_code}")
138
+ except Exception as e:
139
+ print(f"⚠ API info check failed: {e}")
140
+
141
+ return results
142
+
143
+ def test_authentication(url: str, email: str, password: str) -> Dict[str, Any]:
144
+ """
145
+ Test authentication with ZeroGPU API
146
+
147
+ Returns:
148
+ Dictionary with authentication test results
149
+ """
150
+ results = {
151
+ "authenticated": False,
152
+ "access_token": None,
153
+ "error": None
154
+ }
155
+
156
+ try:
157
+ login_url = f"{url}/login"
158
+ response = requests.post(
159
+ login_url,
160
+ json={"email": email, "password": password},
161
+ timeout=10
162
+ )
163
+
164
+ if response.status_code == 200:
165
+ data = response.json()
166
+ results["authenticated"] = True
167
+ results["access_token"] = "***" if data.get("access_token") else None
168
+ print(f"✓ Authentication successful")
169
+ return results
170
+ else:
171
+ results["error"] = f"Login failed with status {response.status_code}: {response.text[:200]}"
172
+ print(f"✗ Authentication failed: {results['error']}")
173
+ return results
174
+ except Exception as e:
175
+ results["error"] = f"Authentication error: {e}"
176
+ print(f"✗ Authentication error: {e}")
177
+ return results
178
+
179
+ def main():
180
+ parser = argparse.ArgumentParser(
181
+ description="Verify ZeroGPU API URL and connectivity"
182
+ )
183
+ parser.add_argument(
184
+ "url",
185
+ nargs="?",
186
+ help="ZeroGPU API URL (e.g., http://your-pod-ip:8000)"
187
+ )
188
+ parser.add_argument(
189
+ "--email",
190
+ help="Email for authentication test (optional)"
191
+ )
192
+ parser.add_argument(
193
+ "--password",
194
+ help="Password for authentication test (optional)"
195
+ )
196
+ parser.add_argument(
197
+ "--no-validate",
198
+ action="store_true",
199
+ help="Skip URL format validation"
200
+ )
201
+
202
+ args = parser.parse_args()
203
+
204
+ # Get URL from argument or environment
205
+ url = args.url or os.getenv("ZERO_GPU_API_URL", "")
206
+
207
+ if not url:
208
+ print("Error: ZeroGPU API URL not provided")
209
+ print("Usage: python verify_zerogpu_url.py <url>")
210
+ print(" or: Set ZERO_GPU_API_URL environment variable")
211
+ print("\nExample:")
212
+ print(" python verify_zerogpu_url.py http://192.168.1.100:8000")
213
+ sys.exit(1)
214
+
215
+ # Normalize URL
216
+ url = url.rstrip('/')
217
+
218
+ print("=" * 60)
219
+ print("ZeroGPU API URL Verification")
220
+ print("=" * 60)
221
+ print(f"URL: {url}\n")
222
+
223
+ # Step 1: Validate URL format
224
+ if not args.no_validate:
225
+ print("Step 1: Validating URL format...")
226
+ is_valid, error = validate_url_format(url)
227
+ if not is_valid:
228
+ print(f"✗ URL validation failed: {error}")
229
+ print("\nCorrect format examples:")
230
+ print(" https://bm9njt1ypzvuqw-8000.proxy.runpod.net")
231
+ print(" https://<pod-id>-8000.proxy.runpod.net")
232
+ print(" http://<ip-address>:8000 (if using direct IP)")
233
+ sys.exit(1)
234
+ print("✓ URL format is valid")
235
+ else:
236
+ print("Step 1: Skipping URL format validation")
237
+
238
+ # Step 2: Test connectivity
239
+ print("\nStep 2: Testing connectivity...")
240
+ connectivity_results = test_connectivity(url)
241
+
242
+ if not connectivity_results["reachable"]:
243
+ print("\n✗ Connectivity test failed")
244
+ print(f"Error: {connectivity_results.get('error', 'Unknown error')}")
245
+ print("\nTroubleshooting:")
246
+ print("1. Verify the Runpod pod is running")
247
+ print("2. Check the pod IP address is correct")
248
+ print("3. Ensure port 8000 is exposed")
249
+ print("4. Check firewall/network rules")
250
+ sys.exit(1)
251
+
252
+ # Step 3: Test authentication (if credentials provided)
253
+ if args.email and args.password:
254
+ print("\nStep 3: Testing authentication...")
255
+ auth_results = test_authentication(url, args.email, args.password)
256
+ if not auth_results["authenticated"]:
257
+ print(f"\n⚠ Authentication test failed: {auth_results.get('error', 'Unknown error')}")
258
+ print("Note: Connectivity is working, but authentication failed.")
259
+ print("Please verify your credentials.")
260
+ else:
261
+ print("\nStep 3: Skipping authentication test (no credentials provided)")
262
+ print("To test authentication, use: --email <email> --password <password>")
263
+
264
+ # Summary
265
+ print("\n" + "=" * 60)
266
+ print("Verification Summary")
267
+ print("=" * 60)
268
+ print(f"URL: {url}")
269
+ print(f"Reachable: {'✓ Yes' if connectivity_results['reachable'] else '✗ No'}")
270
+ print(f"Health Check: {'✓ Passed' if connectivity_results['health_check'] else '✗ Failed'}")
271
+ print(f"Ready Check: {'✓ Passed' if connectivity_results['ready_check'] else '⚠ Not tested/Failed'}")
272
+ print(f"API Info: {'✓ Accessible' if connectivity_results['api_info'] else '⚠ Not tested/Failed'}")
273
+
274
+ if connectivity_results["reachable"]:
275
+ print("\n✓ ZeroGPU API URL is valid and working!")
276
+ print("\n✓ ZeroGPU API URL is valid and working!")
277
+ print("\nYou can use this URL in your environment:")
278
+ print(f" ZERO_GPU_API_URL={url}")
279
+ print("\nFor Hugging Face Spaces, add this as a Repository Secret:")
280
+ print(f" Name: ZERO_GPU_API_URL")
281
+ print(f" Value: {url}")
282
+ else:
283
+ print("\n✗ ZeroGPU API URL is not working")
284
+ sys.exit(1)
285
+
286
+ if __name__ == "__main__":
287
+ main()
288
+
zero_gpu_client.py CHANGED
@@ -6,8 +6,10 @@ Provides authentication and API access to ZeroGPU Chat API
6
  import requests
7
  import time
8
  import logging
 
9
  from typing import Optional, List, Dict, Any
10
  from datetime import datetime
 
11
 
12
  logger = logging.getLogger(__name__)
13
 
@@ -15,18 +17,20 @@ logger = logging.getLogger(__name__)
15
  class ZeroGPUChatClient:
16
  """Client for ZeroGPU Chat API with automatic token refresh"""
17
 
18
- def __init__(self, base_url: str, email: str, password: str, access_token: str = None, refresh_token: str = None):
19
  """
20
  Initialize ZeroGPU API client
21
 
22
  Args:
23
- base_url: Base URL of ZeroGPU API (e.g., "http://your-pod-ip:8000")
24
  email: User email for authentication
25
  password: User password for authentication
26
  access_token: Optional pre-existing access token
27
  refresh_token: Optional pre-existing refresh token
 
28
  """
29
- self.base_url = base_url.rstrip('/')
 
30
  self.email = email
31
  self.password = password
32
  self.access_token = access_token
@@ -42,6 +46,95 @@ class ZeroGPUChatClient:
42
  else:
43
  self.login(email, password)
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def login(self, email: str, password: str):
46
  """Login and get authentication tokens"""
47
  try:
@@ -221,7 +314,55 @@ class ZeroGPUChatClient:
221
  """Check if API is healthy"""
222
  try:
223
  response = requests.get(f"{self.base_url}/health", timeout=5)
224
- return response.status_code == 200
225
- except requests.exceptions.RequestException:
 
 
 
 
 
 
226
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
 
6
  import requests
7
  import time
8
  import logging
9
+ import re
10
  from typing import Optional, List, Dict, Any
11
  from datetime import datetime
12
+ from urllib.parse import urlparse
13
 
14
  logger = logging.getLogger(__name__)
15
 
 
17
  class ZeroGPUChatClient:
18
  """Client for ZeroGPU Chat API with automatic token refresh"""
19
 
20
+ def __init__(self, base_url: str, email: str, password: str, access_token: str = None, refresh_token: str = None, validate_url: bool = True):
21
  """
22
  Initialize ZeroGPU API client
23
 
24
  Args:
25
+ base_url: Base URL of ZeroGPU API (e.g., "http://your-pod-ip:8000" or "https://your-domain.com")
26
  email: User email for authentication
27
  password: User password for authentication
28
  access_token: Optional pre-existing access token
29
  refresh_token: Optional pre-existing refresh token
30
+ validate_url: Whether to validate URL format and connectivity (default: True)
31
  """
32
+ # Validate and normalize URL
33
+ self.base_url = self._validate_and_normalize_url(base_url, validate_url)
34
  self.email = email
35
  self.password = password
36
  self.access_token = access_token
 
46
  else:
47
  self.login(email, password)
48
 
49
+ def _validate_and_normalize_url(self, url: str, validate: bool = True) -> str:
50
+ """
51
+ Validate and normalize the ZeroGPU API URL
52
+
53
+ Args:
54
+ url: URL to validate
55
+ validate: Whether to perform connectivity check
56
+
57
+ Returns:
58
+ Normalized URL string
59
+
60
+ Raises:
61
+ ValueError: If URL format is invalid
62
+ ConnectionError: If URL is not reachable (when validate=True)
63
+ """
64
+ # Remove trailing slash
65
+ url = url.rstrip('/')
66
+
67
+ # Check for placeholder values
68
+ if 'your-pod-ip' in url.lower() or (url.lower() == 'http://localhost:8000' and validate):
69
+ raise ValueError(
70
+ f"Invalid ZeroGPU API URL: '{url}'. "
71
+ "Please set ZERO_GPU_API_URL to your actual Runpod proxy URL. "
72
+ "Format: https://<pod-id>-8000.proxy.runpod.net"
73
+ )
74
+
75
+ # Parse URL to validate format
76
+ try:
77
+ parsed = urlparse(url)
78
+ if not parsed.scheme:
79
+ raise ValueError(f"URL missing scheme (http:// or https://): {url}")
80
+ if not parsed.netloc:
81
+ raise ValueError(f"URL missing host/domain: {url}")
82
+ if parsed.scheme not in ['http', 'https']:
83
+ raise ValueError(f"URL must use http:// or https://, got: {parsed.scheme}")
84
+ except Exception as e:
85
+ raise ValueError(f"Invalid URL format: {url}. Error: {e}")
86
+
87
+ # Validate connectivity if requested
88
+ if validate:
89
+ if not self._check_url_connectivity(url):
90
+ raise ConnectionError(
91
+ f"Cannot connect to ZeroGPU API at {url}. "
92
+ "Please verify:\n"
93
+ "1. The URL is correct (check for typos)\n"
94
+ "2. The Runpod pod is running and accessible\n"
95
+ "3. Network connectivity from this host to the pod\n"
96
+ "4. Firewall rules allow connections to port 8000"
97
+ )
98
+
99
+ return url
100
+
101
+ def _check_url_connectivity(self, url: str, timeout: int = 5) -> bool:
102
+ """
103
+ Check if the URL is reachable
104
+
105
+ Args:
106
+ url: URL to check
107
+ timeout: Connection timeout in seconds
108
+
109
+ Returns:
110
+ True if reachable, False otherwise
111
+ """
112
+ try:
113
+ # Try to reach the health endpoint
114
+ health_url = f"{url}/health"
115
+ response = requests.get(health_url, timeout=timeout)
116
+
117
+ # Accept 200 (OK) or 404 (endpoint might not exist but server is reachable)
118
+ if response.status_code in [200, 404]:
119
+ logger.info(f"✓ ZeroGPU API URL is reachable: {url}")
120
+ return True
121
+ else:
122
+ logger.warning(f"ZeroGPU API returned status {response.status_code} for {url}")
123
+ return False
124
+
125
+ except requests.exceptions.ConnectionError:
126
+ logger.error(f"✗ Cannot connect to ZeroGPU API at {url} - Connection refused")
127
+ return False
128
+ except requests.exceptions.Timeout:
129
+ logger.error(f"✗ Connection to ZeroGPU API at {url} timed out")
130
+ return False
131
+ except requests.exceptions.RequestException as e:
132
+ logger.error(f"✗ Error checking ZeroGPU API URL {url}: {e}")
133
+ return False
134
+ except Exception as e:
135
+ logger.error(f"✗ Unexpected error checking URL {url}: {e}")
136
+ return False
137
+
138
  def login(self, email: str, password: str):
139
  """Login and get authentication tokens"""
140
  try:
 
314
  """Check if API is healthy"""
315
  try:
316
  response = requests.get(f"{self.base_url}/health", timeout=5)
317
+ if response.status_code == 200:
318
+ logger.debug(f"Health check passed for {self.base_url}")
319
+ return True
320
+ else:
321
+ logger.warning(f"Health check returned status {response.status_code} for {self.base_url}")
322
+ return False
323
+ except requests.exceptions.RequestException as e:
324
+ logger.warning(f"Health check failed for {self.base_url}: {e}")
325
  return False
326
+
327
+ def verify_url(self) -> Dict[str, Any]:
328
+ """
329
+ Comprehensive URL verification
330
+
331
+ Returns:
332
+ Dictionary with verification results
333
+ """
334
+ results = {
335
+ "url": self.base_url,
336
+ "format_valid": False,
337
+ "reachable": False,
338
+ "health_check": False,
339
+ "ready_check": False,
340
+ "error": None
341
+ }
342
+
343
+ # Format validation
344
+ try:
345
+ parsed = urlparse(self.base_url)
346
+ if parsed.scheme and parsed.netloc:
347
+ results["format_valid"] = True
348
+ except:
349
+ results["error"] = "Invalid URL format"
350
+ return results
351
+
352
+ # Connectivity check
353
+ results["reachable"] = self._check_url_connectivity(self.base_url, timeout=5)
354
+
355
+ # Health check
356
+ results["health_check"] = self.health_check()
357
+
358
+ # Ready check
359
+ try:
360
+ response = requests.get(f"{self.base_url}/ready", timeout=5)
361
+ if response.status_code == 200:
362
+ data = response.json()
363
+ results["ready_check"] = data.get("ready", False)
364
+ except:
365
+ pass
366
+
367
+ return results
368