tetra-rp 0.17.1__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.

Potentially problematic release.


This version of tetra-rp might be problematic. Click here for more details.

Files changed (66) hide show
  1. tetra_rp/__init__.py +43 -0
  2. tetra_rp/cli/__init__.py +0 -0
  3. tetra_rp/cli/commands/__init__.py +1 -0
  4. tetra_rp/cli/commands/build.py +534 -0
  5. tetra_rp/cli/commands/deploy.py +370 -0
  6. tetra_rp/cli/commands/init.py +119 -0
  7. tetra_rp/cli/commands/resource.py +191 -0
  8. tetra_rp/cli/commands/run.py +100 -0
  9. tetra_rp/cli/main.py +85 -0
  10. tetra_rp/cli/utils/__init__.py +1 -0
  11. tetra_rp/cli/utils/conda.py +127 -0
  12. tetra_rp/cli/utils/deployment.py +172 -0
  13. tetra_rp/cli/utils/ignore.py +139 -0
  14. tetra_rp/cli/utils/skeleton.py +184 -0
  15. tetra_rp/cli/utils/skeleton_template/.env.example +3 -0
  16. tetra_rp/cli/utils/skeleton_template/.flashignore +40 -0
  17. tetra_rp/cli/utils/skeleton_template/.gitignore +44 -0
  18. tetra_rp/cli/utils/skeleton_template/README.md +256 -0
  19. tetra_rp/cli/utils/skeleton_template/main.py +43 -0
  20. tetra_rp/cli/utils/skeleton_template/requirements.txt +1 -0
  21. tetra_rp/cli/utils/skeleton_template/workers/__init__.py +0 -0
  22. tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py +20 -0
  23. tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py +38 -0
  24. tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py +20 -0
  25. tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py +62 -0
  26. tetra_rp/client.py +128 -0
  27. tetra_rp/config.py +29 -0
  28. tetra_rp/core/__init__.py +0 -0
  29. tetra_rp/core/api/__init__.py +6 -0
  30. tetra_rp/core/api/runpod.py +319 -0
  31. tetra_rp/core/exceptions.py +50 -0
  32. tetra_rp/core/resources/__init__.py +37 -0
  33. tetra_rp/core/resources/base.py +47 -0
  34. tetra_rp/core/resources/cloud.py +4 -0
  35. tetra_rp/core/resources/constants.py +4 -0
  36. tetra_rp/core/resources/cpu.py +146 -0
  37. tetra_rp/core/resources/environment.py +41 -0
  38. tetra_rp/core/resources/gpu.py +68 -0
  39. tetra_rp/core/resources/live_serverless.py +62 -0
  40. tetra_rp/core/resources/network_volume.py +148 -0
  41. tetra_rp/core/resources/resource_manager.py +145 -0
  42. tetra_rp/core/resources/serverless.py +463 -0
  43. tetra_rp/core/resources/serverless_cpu.py +162 -0
  44. tetra_rp/core/resources/template.py +94 -0
  45. tetra_rp/core/resources/utils.py +50 -0
  46. tetra_rp/core/utils/__init__.py +0 -0
  47. tetra_rp/core/utils/backoff.py +43 -0
  48. tetra_rp/core/utils/constants.py +10 -0
  49. tetra_rp/core/utils/file_lock.py +260 -0
  50. tetra_rp/core/utils/json.py +33 -0
  51. tetra_rp/core/utils/lru_cache.py +75 -0
  52. tetra_rp/core/utils/singleton.py +21 -0
  53. tetra_rp/core/validation.py +44 -0
  54. tetra_rp/execute_class.py +319 -0
  55. tetra_rp/logger.py +34 -0
  56. tetra_rp/protos/__init__.py +0 -0
  57. tetra_rp/protos/remote_execution.py +148 -0
  58. tetra_rp/stubs/__init__.py +5 -0
  59. tetra_rp/stubs/live_serverless.py +155 -0
  60. tetra_rp/stubs/registry.py +117 -0
  61. tetra_rp/stubs/serverless.py +30 -0
  62. tetra_rp-0.17.1.dist-info/METADATA +976 -0
  63. tetra_rp-0.17.1.dist-info/RECORD +66 -0
  64. tetra_rp-0.17.1.dist-info/WHEEL +5 -0
  65. tetra_rp-0.17.1.dist-info/entry_points.txt +2 -0
  66. tetra_rp-0.17.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,184 @@
1
+ """Project skeleton creation utilities."""
2
+
3
+ from fnmatch import fnmatch
4
+ from pathlib import Path
5
+ from typing import List
6
+
7
+ # Patterns to ignore during skeleton operations
8
+ IGNORE_PATTERNS = {
9
+ "__pycache__",
10
+ "*.pyc",
11
+ "*.pyo",
12
+ "*.pyd",
13
+ ".DS_Store",
14
+ "Thumbs.db",
15
+ ".git",
16
+ ".pytest_cache",
17
+ "*.egg-info",
18
+ }
19
+
20
+
21
+ def _should_ignore(path: Path) -> bool:
22
+ """
23
+ Check if path matches any ignore pattern.
24
+
25
+ Args:
26
+ path: Path to check
27
+
28
+ Returns:
29
+ True if path should be ignored, False otherwise
30
+ """
31
+ for pattern in IGNORE_PATTERNS:
32
+ # Check if any path component matches the pattern
33
+ if fnmatch(path.name, pattern):
34
+ return True
35
+ if any(fnmatch(part, pattern) for part in path.parts):
36
+ return True
37
+ return False
38
+
39
+
40
+ def detect_file_conflicts(project_dir: Path) -> List[Path]:
41
+ """
42
+ Detect files that would be overwritten when creating project skeleton.
43
+
44
+ Args:
45
+ project_dir: Project directory path to check
46
+
47
+ Returns:
48
+ List of file paths that already exist and would be overwritten
49
+ """
50
+ conflicts: List[Path] = []
51
+
52
+ # Get template directory path
53
+ template_dir = Path(__file__).parent / "skeleton_template"
54
+
55
+ if not template_dir.exists():
56
+ return conflicts
57
+
58
+ def check_conflicts_recursive(src_dir: Path) -> None:
59
+ """Recursively check for file conflicts."""
60
+ for item in src_dir.iterdir():
61
+ if _should_ignore(item):
62
+ continue
63
+
64
+ relative_path = item.relative_to(template_dir)
65
+ target_file = project_dir / relative_path
66
+
67
+ if item.is_dir():
68
+ check_conflicts_recursive(item)
69
+ elif item.is_file():
70
+ if target_file.exists():
71
+ conflicts.append(relative_path)
72
+
73
+ # Start recursive conflict check
74
+ check_conflicts_recursive(template_dir)
75
+
76
+ return conflicts
77
+
78
+
79
+ def _copy_template_file(
80
+ item: Path,
81
+ template_dir: Path,
82
+ project_dir: Path,
83
+ force: bool,
84
+ created_files: List[str],
85
+ ) -> None:
86
+ """
87
+ Copy a single template file with substitutions.
88
+
89
+ Args:
90
+ item: Source file path
91
+ template_dir: Template directory base path
92
+ project_dir: Target project directory
93
+ force: Overwrite existing files
94
+ created_files: List to append created file paths to
95
+ """
96
+ relative_path = item.relative_to(template_dir)
97
+ target_path = project_dir / relative_path
98
+
99
+ # Skip existing files unless force is True
100
+ if target_path.exists() and not force:
101
+ return
102
+
103
+ # Create parent directories if needed
104
+ target_path.parent.mkdir(parents=True, exist_ok=True)
105
+
106
+ # Read content and handle template substitutions
107
+ try:
108
+ content = item.read_text()
109
+
110
+ # Replace {{project_name}} placeholder
111
+ if "{{project_name}}" in content:
112
+ content = content.replace("{{project_name}}", project_dir.name)
113
+
114
+ # Write file
115
+ target_path.write_text(content)
116
+ created_files.append(str(relative_path))
117
+ except UnicodeDecodeError:
118
+ # Handle binary files (just copy bytes)
119
+ target_path.write_bytes(item.read_bytes())
120
+ created_files.append(str(relative_path))
121
+
122
+
123
+ def _copy_directory_recursive(
124
+ src_dir: Path,
125
+ template_dir: Path,
126
+ project_dir: Path,
127
+ force: bool,
128
+ created_files: List[str],
129
+ ) -> None:
130
+ """
131
+ Recursively copy directory contents, including hidden files.
132
+
133
+ Args:
134
+ src_dir: Source directory to copy from
135
+ template_dir: Template directory base path
136
+ project_dir: Target project directory
137
+ force: Overwrite existing files
138
+ created_files: List to append created file paths to
139
+ """
140
+ for item in src_dir.iterdir():
141
+ # Skip ignored items
142
+ if _should_ignore(item):
143
+ continue
144
+
145
+ if item.is_dir():
146
+ # Create target directory and recurse
147
+ relative_path = item.relative_to(template_dir)
148
+ target_path = project_dir / relative_path
149
+ target_path.mkdir(parents=True, exist_ok=True)
150
+ _copy_directory_recursive(
151
+ item, template_dir, project_dir, force, created_files
152
+ )
153
+ elif item.is_file():
154
+ _copy_template_file(item, template_dir, project_dir, force, created_files)
155
+
156
+
157
+ def create_project_skeleton(project_dir: Path, force: bool = False) -> List[str]:
158
+ """
159
+ Create Flash project skeleton from template directory.
160
+
161
+ Args:
162
+ project_dir: Project directory path
163
+ force: Overwrite existing files
164
+
165
+ Returns:
166
+ List of created file paths
167
+ """
168
+ created_files: List[str] = []
169
+
170
+ # Get template directory path
171
+ template_dir = Path(__file__).parent / "skeleton_template"
172
+
173
+ if not template_dir.exists():
174
+ raise FileNotFoundError(f"Template directory not found: {template_dir}")
175
+
176
+ # Create project directory
177
+ project_dir.mkdir(parents=True, exist_ok=True)
178
+
179
+ # Start recursive copy
180
+ _copy_directory_recursive(
181
+ template_dir, template_dir, project_dir, force, created_files
182
+ )
183
+
184
+ return created_files
@@ -0,0 +1,3 @@
1
+ # RUNPOD_API_KEY=your_api_key_here
2
+ # PORT=80
3
+ # LOG_LEVEL=INFO
@@ -0,0 +1,40 @@
1
+ # Flash Build Ignore Patterns
2
+
3
+ # Python cache
4
+ __pycache__/
5
+ *.pyc
6
+
7
+ # Virtual environments
8
+ venv/
9
+ .venv/
10
+ env/
11
+
12
+ # IDE
13
+ .vscode/
14
+ .idea/
15
+
16
+ # Environment files
17
+ .env
18
+ .env.local
19
+
20
+ # Git
21
+ .git/
22
+ .gitignore
23
+
24
+ # Build artifacts
25
+ dist/
26
+ build/
27
+ *.egg-info/
28
+
29
+ # Flash resources
30
+ .tetra_resources.pkl
31
+
32
+ # Tests
33
+ tests/
34
+ test_*.py
35
+ *_test.py
36
+
37
+ # Documentation
38
+ docs/
39
+ *.md
40
+ !README.md
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ env/
8
+ venv/
9
+ .venv/
10
+ ENV/
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # IDEs
28
+ .vscode/
29
+ .idea/
30
+ *.swp
31
+ *.swo
32
+ *~
33
+
34
+ # Environment
35
+ .env
36
+ .env.local
37
+
38
+ # Flash
39
+ .tetra_resources.pkl
40
+ dist/
41
+
42
+ # OS
43
+ .DS_Store
44
+ Thumbs.db
@@ -0,0 +1,256 @@
1
+ # {{project_name}}
2
+
3
+ Flash application demonstrating distributed GPU and CPU computing on Runpod's serverless infrastructure.
4
+
5
+ ## About This Template
6
+
7
+ This project was generated using `flash init`. The `{{project_name}}` placeholder is automatically replaced with your actual project name during initialization.
8
+
9
+ ## Quick Start
10
+
11
+ ### 1. Install Dependencies
12
+
13
+ ```bash
14
+ pip install -r requirements.txt
15
+ ```
16
+
17
+ ### 2. Configure Environment
18
+
19
+ Create `.env` file:
20
+
21
+ ```bash
22
+ RUNPOD_API_KEY=your_api_key_here
23
+ ```
24
+
25
+ Get your API key from [Runpod Settings](https://www.runpod.io/console/user/settings).
26
+
27
+ ### 3. Run Locally
28
+
29
+ ```bash
30
+ flash run
31
+ ```
32
+
33
+ Server starts at **http://localhost:8000**
34
+
35
+ ### 4. Test the API
36
+
37
+ ```bash
38
+ # Health check
39
+ curl http://localhost:8000/ping
40
+
41
+ # GPU worker
42
+ curl -X POST http://localhost:8000/gpu/hello \
43
+ -H "Content-Type: application/json" \
44
+ -d '{"message": "Hello GPU!"}'
45
+
46
+ # CPU worker
47
+ curl -X POST http://localhost:8000/cpu/hello \
48
+ -H "Content-Type: application/json" \
49
+ -d '{"message": "Hello CPU!"}'
50
+ ```
51
+
52
+ Visit **http://localhost:8000/docs** for interactive API documentation.
53
+
54
+ ## What This Demonstrates
55
+
56
+ ### GPU Worker (`workers/gpu/`)
57
+ Simple GPU-based serverless function:
58
+ - Remote execution with `@remote` decorator
59
+ - GPU resource configuration
60
+ - Automatic scaling (0-3 workers)
61
+ - No external dependencies required
62
+
63
+ ```python
64
+ @remote(
65
+ resource_config=LiveServerless(
66
+ name="gpu_worker",
67
+ gpus=[GpuGroup.ADA_24], # RTX 4090
68
+ workersMin=0,
69
+ workersMax=3,
70
+ )
71
+ )
72
+ async def gpu_hello(input_data: dict) -> dict:
73
+ # Your GPU code here
74
+ return {"status": "success", "message": "Hello from GPU!"}
75
+ ```
76
+
77
+ ### CPU Worker (`workers/cpu/`)
78
+ Simple CPU-based serverless function:
79
+ - CPU-only execution (no GPU overhead)
80
+ - CpuLiveServerless configuration
81
+ - Efficient for API endpoints
82
+ - Automatic scaling (0-5 workers)
83
+
84
+ ```python
85
+ @remote(
86
+ resource_config=CpuLiveServerless(
87
+ name="cpu_worker",
88
+ instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM
89
+ workersMin=0,
90
+ workersMax=5,
91
+ )
92
+ )
93
+ async def cpu_hello(input_data: dict) -> dict:
94
+ # Your CPU code here
95
+ return {"status": "success", "message": "Hello from CPU!"}
96
+ ```
97
+
98
+ ## Project Structure
99
+
100
+ ```
101
+ {{project_name}}/
102
+ ├── main.py # FastAPI application
103
+ ├── workers/
104
+ │ ├── gpu/ # GPU worker
105
+ │ │ ├── __init__.py # FastAPI router
106
+ │ │ └── endpoint.py # @remote decorated function
107
+ │ └── cpu/ # CPU worker
108
+ │ ├── __init__.py # FastAPI router
109
+ │ └── endpoint.py # @remote decorated function
110
+ ├── .env # Environment variables
111
+ ├── requirements.txt # Dependencies
112
+ └── README.md # This file
113
+ ```
114
+
115
+ ## Key Concepts
116
+
117
+ ### Remote Execution
118
+ The `@remote` decorator transparently executes functions on serverless infrastructure:
119
+ - Code runs locally during development
120
+ - Automatically deploys to Runpod when configured
121
+ - Handles serialization, dependencies, and resource management
122
+
123
+ ### Resource Scaling
124
+ Both workers scale to zero when idle to minimize costs:
125
+ - **idleTimeout**: Minutes before scaling down (default: 5)
126
+ - **workersMin**: 0 = completely scales to zero
127
+ - **workersMax**: Maximum concurrent workers
128
+
129
+ ### GPU Types
130
+ Available GPU options for `LiveServerless`:
131
+ - `GpuGroup.ADA_24` - RTX 4090 (24GB)
132
+ - `GpuGroup.ADA_48_PRO` - RTX 6000 Ada, L40 (48GB)
133
+ - `GpuGroup.AMPERE_80` - A100 (80GB)
134
+ - `GpuGroup.ANY` - Any available GPU
135
+
136
+ ### CPU Types
137
+ Available CPU options for `CpuLiveServerless`:
138
+ - `CpuInstanceType.CPU3G_2_8` - 2 vCPU, 8GB RAM (General Purpose)
139
+ - `CpuInstanceType.CPU3C_4_8` - 4 vCPU, 8GB RAM (Compute Optimized)
140
+ - `CpuInstanceType.CPU5G_4_16` - 4 vCPU, 16GB RAM (Latest Gen)
141
+ - `CpuInstanceType.ANY` - Any available GPU
142
+
143
+ ## Development Workflow
144
+
145
+ ### Test Workers Locally
146
+ ```bash
147
+ # Test GPU worker
148
+ python -m workers.gpu.endpoint
149
+
150
+ # Test CPU worker
151
+ python -m workers.cpu.endpoint
152
+ ```
153
+
154
+ ### Run the Application
155
+ ```bash
156
+ flash run
157
+ ```
158
+
159
+ ### Deploy to Production
160
+ ```bash
161
+ # Discover and configure handlers
162
+ flash build
163
+
164
+ # Create deployment environment
165
+ flash deploy new production
166
+
167
+ # Deploy to Runpod
168
+ flash deploy send production
169
+ ```
170
+
171
+ ## Adding New Workers
172
+
173
+ ### Add a GPU Worker
174
+
175
+ 1. Create `workers/my_worker/endpoint.py`:
176
+ ```python
177
+ from tetra_rp import remote, LiveServerless
178
+
179
+ config = LiveServerless(name="my_worker")
180
+
181
+ @remote(resource_config=config, dependencies=["torch"])
182
+ async def my_function(data: dict) -> dict:
183
+ import torch
184
+ # Your code here
185
+ return {"result": "success"}
186
+ ```
187
+
188
+ 2. Create `workers/my_worker/__init__.py`:
189
+ ```python
190
+ from fastapi import APIRouter
191
+ from .endpoint import my_function
192
+
193
+ router = APIRouter()
194
+
195
+ @router.post("/process")
196
+ async def handler(data: dict):
197
+ return await my_function(data)
198
+ ```
199
+
200
+ 3. Add to `main.py`:
201
+ ```python
202
+ from workers.my_worker import router as my_router
203
+ app.include_router(my_router, prefix="/my_worker")
204
+ ```
205
+
206
+ ### Add a CPU Worker
207
+
208
+ Same pattern but use `CpuLiveServerless`:
209
+ ```python
210
+ from tetra_rp import remote, CpuLiveServerless, CpuInstanceType
211
+
212
+ config = CpuLiveServerless(
213
+ name="my_cpu_worker",
214
+ instanceIds=[CpuInstanceType.CPU3G_2_8]
215
+ )
216
+
217
+ @remote(resource_config=config, dependencies=["requests"])
218
+ async def fetch_data(url: str) -> dict:
219
+ import requests
220
+ return requests.get(url).json()
221
+ ```
222
+
223
+ ## Adding Dependencies
224
+
225
+ Specify dependencies in the `@remote` decorator:
226
+ ```python
227
+ @remote(
228
+ resource_config=config,
229
+ dependencies=["torch>=2.0.0", "transformers"], # Python packages
230
+ system_dependencies=["ffmpeg"] # System packages
231
+ )
232
+ async def my_function(data: dict) -> dict:
233
+ # Dependencies are automatically installed
234
+ import torch
235
+ import transformers
236
+ ```
237
+
238
+ ## Environment Variables
239
+
240
+ ```bash
241
+ # Required
242
+ RUNPOD_API_KEY=your_api_key
243
+
244
+ # Optional
245
+ PORT=8000
246
+ LOG_LEVEL=INFO
247
+ ```
248
+
249
+ ## Next Steps
250
+
251
+ - Add your ML models or processing logic
252
+ - Configure GPU/CPU resources based on your needs
253
+ - Add authentication to your endpoints
254
+ - Implement error handling and retries
255
+ - Add monitoring and logging
256
+ - Deploy to production with `flash deploy`
@@ -0,0 +1,43 @@
1
+ import os
2
+ import logging
3
+ from fastapi import FastAPI
4
+
5
+ from workers.gpu import gpu_router
6
+ from workers.cpu import cpu_router
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ app = FastAPI(
13
+ title="Flash Application",
14
+ description="Distributed GPU and CPU computing with Runpod Flash",
15
+ version="0.1.0",
16
+ )
17
+
18
+ # Include routers
19
+ app.include_router(gpu_router, prefix="/gpu", tags=["GPU Workers"])
20
+ app.include_router(cpu_router, prefix="/cpu", tags=["CPU Workers"])
21
+
22
+
23
+ @app.get("/")
24
+ def home():
25
+ return {
26
+ "message": "Flash Application",
27
+ "docs": "/docs",
28
+ "endpoints": {"gpu_hello": "/gpu/hello", "cpu_hello": "/cpu/hello"},
29
+ }
30
+
31
+
32
+ @app.get("/ping")
33
+ def ping():
34
+ return {"status": "healthy"}
35
+
36
+
37
+ if __name__ == "__main__":
38
+ import uvicorn
39
+
40
+ port = int(os.getenv("PORT", 8888))
41
+ logger.info(f"Starting Flash server on port {port}")
42
+
43
+ uvicorn.run(app, host="0.0.0.0", port=port)
@@ -0,0 +1 @@
1
+ tetra_rp
@@ -0,0 +1,20 @@
1
+ from fastapi import APIRouter
2
+ from pydantic import BaseModel
3
+
4
+ from .endpoint import cpu_hello
5
+
6
+
7
+ cpu_router = APIRouter()
8
+
9
+
10
+ class MessageRequest(BaseModel):
11
+ """Request model for CPU worker."""
12
+
13
+ message: str = "Hello from CPU!"
14
+
15
+
16
+ @cpu_router.post("/hello")
17
+ async def hello(request: MessageRequest):
18
+ """Simple CPU worker endpoint."""
19
+ result = await cpu_hello({"message": request.message})
20
+ return result
@@ -0,0 +1,38 @@
1
+ from tetra_rp import remote, CpuLiveServerless, CpuInstanceType
2
+
3
+
4
+ cpu_config = CpuLiveServerless(
5
+ name="cpu_worker",
6
+ instanceIds=[CpuInstanceType.ANY],
7
+ workersMin=0,
8
+ workersMax=5,
9
+ idleTimeout=5,
10
+ )
11
+
12
+
13
+ @remote(resource_config=cpu_config)
14
+ async def cpu_hello(input_data: dict) -> dict:
15
+ """Simple CPU worker example."""
16
+ import platform
17
+ from datetime import datetime
18
+
19
+ message = input_data.get("message", "Hello from CPU worker!")
20
+
21
+ return {
22
+ "status": "success",
23
+ "message": message,
24
+ "worker_type": "CPU",
25
+ "timestamp": datetime.now().isoformat(),
26
+ "platform": platform.system(),
27
+ "python_version": platform.python_version(),
28
+ }
29
+
30
+
31
+ # Test locally with: python -m workers.cpu.endpoint
32
+ if __name__ == "__main__":
33
+ import asyncio
34
+
35
+ test_payload = {"message": "Testing CPU worker"}
36
+ print(f"Testing CPU worker with payload: {test_payload}")
37
+ result = asyncio.run(cpu_hello(test_payload))
38
+ print(f"Result: {result}")
@@ -0,0 +1,20 @@
1
+ from fastapi import APIRouter
2
+ from pydantic import BaseModel
3
+
4
+ from .endpoint import gpu_hello
5
+
6
+
7
+ gpu_router = APIRouter()
8
+
9
+
10
+ class MessageRequest(BaseModel):
11
+ """Request model for GPU worker."""
12
+
13
+ message: str = "Hello from GPU!"
14
+
15
+
16
+ @gpu_router.post("/hello")
17
+ async def hello(request: MessageRequest):
18
+ """Simple GPU worker endpoint."""
19
+ result = await gpu_hello({"message": request.message})
20
+ return result