singularity-grid 0.1.0__tar.gz

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.
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install build tools
23
+ run: pip install build
24
+
25
+ - name: Build package
26
+ run: python -m build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ *.egg-info/
5
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Singularity Layer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: singularity-grid
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the SGL Network confidential compute grid
5
+ Project-URL: Homepage, https://singularitylayer.xyz
6
+ Project-URL: Repository, https://github.com/SingularityLayer/sgl-network-sdk
7
+ Project-URL: Documentation, https://docs.singularitylayer.xyz/sdk
8
+ Author-email: Singularity Layer <dev@singularitylayer.xyz>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: httpx>=0.24
24
+ Requires-Dist: pydantic>=2.0
25
+ Provides-Extra: openai
26
+ Requires-Dist: openai>=1.0; extra == 'openai'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # singularity-grid
30
+
31
+ Python SDK for the SGL Network confidential compute grid. Submit inference jobs to TEE-verified nodes, check grid capacity, and use the OpenAI-compatible chat completions endpoint -- all from a single package.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install singularity-grid
37
+ ```
38
+
39
+ To use the OpenAI compatibility helper:
40
+
41
+ ```bash
42
+ pip install singularity-grid[openai]
43
+ ```
44
+
45
+ ## Quick start
46
+
47
+ ### 1. OpenAI-compatible usage (simplest)
48
+
49
+ The SGL Network orchestrator exposes an OpenAI-compatible `/v1/chat/completions` endpoint. You can use the standard `openai` package with no wrapper:
50
+
51
+ ```python
52
+ from openai import OpenAI
53
+
54
+ client = OpenAI(
55
+ base_url="https://sgl-network-orchestrator.ivaavimusicproductions.workers.dev/v1",
56
+ api_key="scg_your_api_key",
57
+ )
58
+
59
+ response = client.chat.completions.create(
60
+ model="gemma-4-26b",
61
+ messages=[{"role": "user", "content": "Hello"}],
62
+ )
63
+ print(response.choices[0].message.content)
64
+ ```
65
+
66
+ ### 2. Helper function
67
+
68
+ If you prefer not to copy the URL, use the built-in helper:
69
+
70
+ ```python
71
+ from singularity_grid import create_openai_client
72
+
73
+ client = create_openai_client(api_key="scg_your_api_key")
74
+
75
+ response = client.chat.completions.create(
76
+ model="gemma-4-26b",
77
+ messages=[{"role": "user", "content": "Hello"}],
78
+ )
79
+ print(response.choices[0].message.content)
80
+ ```
81
+
82
+ ### 3. GridClient (full grid features)
83
+
84
+ For grid-specific features like job submission, TEE attestation, capacity checks, and pricing:
85
+
86
+ ```python
87
+ from singularity_grid import GridClient
88
+
89
+ # Public endpoints -- no auth required
90
+ grid = GridClient()
91
+ capacity = grid.capacity()
92
+ models = grid.models()
93
+ pricing = grid.pricing()
94
+
95
+ print(f"Active nodes: {capacity.active_nodes}/{capacity.total_nodes}")
96
+ for m in models:
97
+ print(f" {m.id} -- {m.sgl_node_count} nodes")
98
+
99
+ # Authenticated endpoints
100
+ grid = GridClient(api_key="scg_your_api_key")
101
+
102
+ job = grid.submit_job(
103
+ model="gemma-4-26b",
104
+ input_payload={
105
+ "messages": [{"role": "user", "content": "Analyze this data"}]
106
+ },
107
+ submitter_wallet="0xYourWallet",
108
+ submitter_chain="base",
109
+ )
110
+
111
+ print(f"Job {job.job_id}: {job.status}")
112
+
113
+ # Retrieve result and attestation
114
+ result = grid.get_job(job.job_id)
115
+ attestation = grid.get_attestation(job.job_id)
116
+ print(f"TEE verified: {attestation.verified}")
117
+ ```
118
+
119
+ ## API reference
120
+
121
+ ### GridClient
122
+
123
+ | Method | Auth | Description |
124
+ |---|---|---|
125
+ | `capacity()` | No | Grid-wide capacity summary |
126
+ | `models()` | No | Available models with pricing and TEE info |
127
+ | `pricing()` | No | Pricing table for all models |
128
+ | `submit_job(model, input_payload, ...)` | Yes | Submit a compute job |
129
+ | `get_job(job_id)` | Yes | Get job status and result |
130
+ | `get_attestation(job_id)` | Yes | Get TEE attestation proof |
131
+
132
+ ### Exceptions
133
+
134
+ | Exception | When |
135
+ |---|---|
136
+ | `SGLError` | Base class for all SDK errors |
137
+ | `SGLAPIError` | Non-2xx response from the API |
138
+ | `SGLAuthError` | 401 or 403 response |
139
+ | `SGLNotFoundError` | 404 response |
140
+ | `SGLConnectionError` | Orchestrator unreachable or timeout |
141
+
142
+ ### Configuration
143
+
144
+ | Parameter | Default | Description |
145
+ |---|---|---|
146
+ | `api_key` | `None` | Bearer token for authenticated endpoints |
147
+ | `base_url` | orchestrator URL | Override the orchestrator URL |
148
+ | `timeout` | `60.0` | Request timeout in seconds |
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,124 @@
1
+ # singularity-grid
2
+
3
+ Python SDK for the SGL Network confidential compute grid. Submit inference jobs to TEE-verified nodes, check grid capacity, and use the OpenAI-compatible chat completions endpoint -- all from a single package.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install singularity-grid
9
+ ```
10
+
11
+ To use the OpenAI compatibility helper:
12
+
13
+ ```bash
14
+ pip install singularity-grid[openai]
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ### 1. OpenAI-compatible usage (simplest)
20
+
21
+ The SGL Network orchestrator exposes an OpenAI-compatible `/v1/chat/completions` endpoint. You can use the standard `openai` package with no wrapper:
22
+
23
+ ```python
24
+ from openai import OpenAI
25
+
26
+ client = OpenAI(
27
+ base_url="https://sgl-network-orchestrator.ivaavimusicproductions.workers.dev/v1",
28
+ api_key="scg_your_api_key",
29
+ )
30
+
31
+ response = client.chat.completions.create(
32
+ model="gemma-4-26b",
33
+ messages=[{"role": "user", "content": "Hello"}],
34
+ )
35
+ print(response.choices[0].message.content)
36
+ ```
37
+
38
+ ### 2. Helper function
39
+
40
+ If you prefer not to copy the URL, use the built-in helper:
41
+
42
+ ```python
43
+ from singularity_grid import create_openai_client
44
+
45
+ client = create_openai_client(api_key="scg_your_api_key")
46
+
47
+ response = client.chat.completions.create(
48
+ model="gemma-4-26b",
49
+ messages=[{"role": "user", "content": "Hello"}],
50
+ )
51
+ print(response.choices[0].message.content)
52
+ ```
53
+
54
+ ### 3. GridClient (full grid features)
55
+
56
+ For grid-specific features like job submission, TEE attestation, capacity checks, and pricing:
57
+
58
+ ```python
59
+ from singularity_grid import GridClient
60
+
61
+ # Public endpoints -- no auth required
62
+ grid = GridClient()
63
+ capacity = grid.capacity()
64
+ models = grid.models()
65
+ pricing = grid.pricing()
66
+
67
+ print(f"Active nodes: {capacity.active_nodes}/{capacity.total_nodes}")
68
+ for m in models:
69
+ print(f" {m.id} -- {m.sgl_node_count} nodes")
70
+
71
+ # Authenticated endpoints
72
+ grid = GridClient(api_key="scg_your_api_key")
73
+
74
+ job = grid.submit_job(
75
+ model="gemma-4-26b",
76
+ input_payload={
77
+ "messages": [{"role": "user", "content": "Analyze this data"}]
78
+ },
79
+ submitter_wallet="0xYourWallet",
80
+ submitter_chain="base",
81
+ )
82
+
83
+ print(f"Job {job.job_id}: {job.status}")
84
+
85
+ # Retrieve result and attestation
86
+ result = grid.get_job(job.job_id)
87
+ attestation = grid.get_attestation(job.job_id)
88
+ print(f"TEE verified: {attestation.verified}")
89
+ ```
90
+
91
+ ## API reference
92
+
93
+ ### GridClient
94
+
95
+ | Method | Auth | Description |
96
+ |---|---|---|
97
+ | `capacity()` | No | Grid-wide capacity summary |
98
+ | `models()` | No | Available models with pricing and TEE info |
99
+ | `pricing()` | No | Pricing table for all models |
100
+ | `submit_job(model, input_payload, ...)` | Yes | Submit a compute job |
101
+ | `get_job(job_id)` | Yes | Get job status and result |
102
+ | `get_attestation(job_id)` | Yes | Get TEE attestation proof |
103
+
104
+ ### Exceptions
105
+
106
+ | Exception | When |
107
+ |---|---|
108
+ | `SGLError` | Base class for all SDK errors |
109
+ | `SGLAPIError` | Non-2xx response from the API |
110
+ | `SGLAuthError` | 401 or 403 response |
111
+ | `SGLNotFoundError` | 404 response |
112
+ | `SGLConnectionError` | Orchestrator unreachable or timeout |
113
+
114
+ ### Configuration
115
+
116
+ | Parameter | Default | Description |
117
+ |---|---|---|
118
+ | `api_key` | `None` | Bearer token for authenticated endpoints |
119
+ | `base_url` | orchestrator URL | Override the orchestrator URL |
120
+ | `timeout` | `60.0` | Request timeout in seconds |
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,28 @@
1
+ """Check grid capacity, available models, and pricing (no auth required)."""
2
+
3
+ from singularity_grid import GridClient
4
+
5
+ grid = GridClient()
6
+
7
+ # Grid capacity
8
+ capacity = grid.capacity()
9
+ print(f"Nodes: {capacity.active_nodes}/{capacity.total_nodes} active")
10
+ for tee in capacity.by_tee_type:
11
+ print(f" {tee.tee_type}: {tee.active_nodes}/{tee.total_nodes}")
12
+
13
+ # Available models
14
+ print("\nModels:")
15
+ models = grid.models()
16
+ for m in models:
17
+ tees = ", ".join(m.sgl_tee_types) if m.sgl_tee_types else "none"
18
+ print(f" {m.id} -- {m.sgl_node_count} nodes, TEE: {tees}")
19
+
20
+ # Pricing
21
+ print("\nPricing:")
22
+ pricing = grid.pricing()
23
+ for p in pricing:
24
+ print(
25
+ f" {p.model}: "
26
+ f"${p.price_per_1k_input_tokens_usd}/1k input, "
27
+ f"${p.price_per_1k_output_tokens_usd}/1k output"
28
+ )
@@ -0,0 +1,39 @@
1
+ """Use the SGL Network with the standard OpenAI Python SDK.
2
+
3
+ The SGL Network orchestrator exposes an OpenAI-compatible /v1/chat/completions
4
+ endpoint, so you can use the openai package directly with no code changes
5
+ beyond swapping the base URL and API key.
6
+ """
7
+
8
+ # --- Option 1: raw OpenAI SDK (no singularity-grid import needed) ----------
9
+
10
+ from openai import OpenAI
11
+
12
+ client = OpenAI(
13
+ base_url="https://sgl-network-orchestrator.ivaavimusicproductions.workers.dev/v1",
14
+ api_key="scg_your_api_key_here",
15
+ )
16
+
17
+ response = client.chat.completions.create(
18
+ model="gemma-4-26b",
19
+ messages=[{"role": "user", "content": "Hello from the SGL Network!"}],
20
+ )
21
+ print(response.choices[0].message.content)
22
+
23
+
24
+ # --- Option 2: use the helper from singularity-grid -----------------------
25
+
26
+ from singularity_grid import create_openai_client
27
+
28
+ client2 = create_openai_client(api_key="scg_your_api_key_here")
29
+
30
+ response2 = client2.chat.completions.create(
31
+ model="gemma-4-26b",
32
+ messages=[{"role": "user", "content": "What TEE type processed this?"}],
33
+ )
34
+ print(response2.choices[0].message.content)
35
+
36
+ # List available models
37
+ models = client2.models.list()
38
+ for m in models.data:
39
+ print(f" {m.id} (owned by {m.owned_by})")
@@ -0,0 +1,31 @@
1
+ """Quickstart: submit a job to the SGL Network and retrieve its result."""
2
+
3
+ from singularity_grid import GridClient
4
+
5
+ # Authenticated client for job submission
6
+ grid = GridClient(api_key="scg_your_api_key_here")
7
+
8
+ # Submit an inference job
9
+ job = grid.submit_job(
10
+ model="gemma-4-26b",
11
+ input_payload={
12
+ "messages": [{"role": "user", "content": "Explain zero-knowledge proofs in one paragraph."}]
13
+ },
14
+ submitter_wallet="0xYourWalletAddress",
15
+ submitter_chain="base",
16
+ )
17
+
18
+ print(f"Job submitted: {job.job_id} (status: {job.status})")
19
+ print(f"Estimated cost: ${job.estimated_cost_usd}")
20
+
21
+ # Poll for result (in production, use a callback or webhook)
22
+ result = grid.get_job(job.job_id)
23
+ print(f"Job status: {result.status}")
24
+
25
+ if result.status == "completed":
26
+ print(f"Result: {result.result}")
27
+
28
+ # Verify TEE attestation
29
+ attestation = grid.get_attestation(job.job_id)
30
+ print(f"TEE type: {attestation.tee_type}")
31
+ print(f"Verified: {attestation.verified}")
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "singularity-grid"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the SGL Network confidential compute grid"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Singularity Layer", email = "dev@singularitylayer.xyz" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
27
+ ]
28
+ dependencies = [
29
+ "httpx>=0.24",
30
+ "pydantic>=2.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ openai = ["openai>=1.0"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://singularitylayer.xyz"
38
+ Repository = "https://github.com/SingularityLayer/sgl-network-sdk"
39
+ Documentation = "https://docs.singularitylayer.xyz/sdk"
@@ -0,0 +1,50 @@
1
+ """singularity-grid -- Python SDK for the SGL Network compute grid."""
2
+
3
+ from .client import (
4
+ DEFAULT_BASE_URL,
5
+ GridClient,
6
+ SGLAPIError,
7
+ SGLAuthError,
8
+ SGLConnectionError,
9
+ SGLError,
10
+ SGLNotFoundError,
11
+ )
12
+ from .models import (
13
+ AttestationProof,
14
+ CapacityResponse,
15
+ JobResponse,
16
+ JobResult,
17
+ ModelInfo,
18
+ ModelPricing,
19
+ ModelsResponse,
20
+ PricingInfo,
21
+ PricingResponse,
22
+ TeeCapacity,
23
+ )
24
+ from .openai_compat import create_openai_client
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ __all__ = [
29
+ # Client
30
+ "GridClient",
31
+ "create_openai_client",
32
+ "DEFAULT_BASE_URL",
33
+ # Exceptions
34
+ "SGLError",
35
+ "SGLAPIError",
36
+ "SGLAuthError",
37
+ "SGLConnectionError",
38
+ "SGLNotFoundError",
39
+ # Models
40
+ "AttestationProof",
41
+ "CapacityResponse",
42
+ "JobResponse",
43
+ "JobResult",
44
+ "ModelInfo",
45
+ "ModelPricing",
46
+ "ModelsResponse",
47
+ "PricingInfo",
48
+ "PricingResponse",
49
+ "TeeCapacity",
50
+ ]
@@ -0,0 +1,213 @@
1
+ """GridClient — main entry point for the SGL Network compute grid."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import httpx
8
+
9
+ from .models import (
10
+ AttestationProof,
11
+ CapacityResponse,
12
+ JobResponse,
13
+ JobResult,
14
+ ModelInfo,
15
+ ModelsResponse,
16
+ PricingInfo,
17
+ PricingResponse,
18
+ )
19
+
20
+ DEFAULT_BASE_URL = (
21
+ "https://sgl-network-orchestrator.ivaavimusicproductions.workers.dev"
22
+ )
23
+
24
+ DEFAULT_TIMEOUT = 60.0
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Exceptions
29
+ # ---------------------------------------------------------------------------
30
+
31
+ class SGLError(Exception):
32
+ """Base exception for all SGL Network SDK errors."""
33
+
34
+
35
+ class SGLAPIError(SGLError):
36
+ """Raised when the API returns a non-2xx status code."""
37
+
38
+ def __init__(
39
+ self,
40
+ status_code: int,
41
+ message: str,
42
+ body: Optional[Dict[str, Any]] = None,
43
+ ) -> None:
44
+ self.status_code = status_code
45
+ self.body = body
46
+ super().__init__(f"HTTP {status_code}: {message}")
47
+
48
+
49
+ class SGLAuthError(SGLAPIError):
50
+ """Raised on 401/403 responses."""
51
+
52
+
53
+ class SGLNotFoundError(SGLAPIError):
54
+ """Raised on 404 responses."""
55
+
56
+
57
+ class SGLConnectionError(SGLError):
58
+ """Raised when the orchestrator is unreachable."""
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Client
63
+ # ---------------------------------------------------------------------------
64
+
65
+ class GridClient:
66
+ """Client for the SGL Network orchestrator API.
67
+
68
+ Parameters
69
+ ----------
70
+ api_key:
71
+ Bearer token for authenticated endpoints (e.g. ``scg_...``).
72
+ Not required for public endpoints like ``capacity()``.
73
+ base_url:
74
+ Override the default orchestrator URL.
75
+ timeout:
76
+ Request timeout in seconds (default 60).
77
+ """
78
+
79
+ def __init__(
80
+ self,
81
+ api_key: Optional[str] = None,
82
+ base_url: str = DEFAULT_BASE_URL,
83
+ timeout: float = DEFAULT_TIMEOUT,
84
+ ) -> None:
85
+ self._api_key = api_key
86
+ self._base_url = base_url.rstrip("/")
87
+ headers: Dict[str, str] = {"Accept": "application/json"}
88
+ if api_key:
89
+ headers["Authorization"] = f"Bearer {api_key}"
90
+ self._client = httpx.Client(
91
+ base_url=self._base_url,
92
+ headers=headers,
93
+ timeout=timeout,
94
+ )
95
+
96
+ # -- helpers ------------------------------------------------------------
97
+
98
+ def _request(
99
+ self,
100
+ method: str,
101
+ path: str,
102
+ *,
103
+ json: Optional[Dict[str, Any]] = None,
104
+ params: Optional[Dict[str, Any]] = None,
105
+ ) -> Dict[str, Any]:
106
+ """Make an HTTP request and return the parsed JSON body."""
107
+ try:
108
+ response = self._client.request(
109
+ method, path, json=json, params=params
110
+ )
111
+ except httpx.ConnectError as exc:
112
+ raise SGLConnectionError(
113
+ f"Could not connect to {self._base_url}: {exc}"
114
+ ) from exc
115
+ except httpx.TimeoutException as exc:
116
+ raise SGLConnectionError(
117
+ f"Request to {self._base_url}{path} timed out: {exc}"
118
+ ) from exc
119
+
120
+ if response.status_code >= 400:
121
+ body: Optional[Dict[str, Any]] = None
122
+ message = response.text
123
+ try:
124
+ body = response.json()
125
+ message = body.get("error", {}).get("message", message) if isinstance(body.get("error"), dict) else body.get("error", message)
126
+ except Exception:
127
+ pass
128
+
129
+ if response.status_code in (401, 403):
130
+ raise SGLAuthError(response.status_code, str(message), body)
131
+ if response.status_code == 404:
132
+ raise SGLNotFoundError(response.status_code, str(message), body)
133
+ raise SGLAPIError(response.status_code, str(message), body)
134
+
135
+ if response.status_code == 204:
136
+ return {}
137
+ return response.json() # type: ignore[no-any-return]
138
+
139
+ # -- public endpoints (no auth required) --------------------------------
140
+
141
+ def capacity(self) -> CapacityResponse:
142
+ """Return grid-wide capacity summary."""
143
+ data = self._request("GET", "/grid/capacity")
144
+ return CapacityResponse.model_validate(data)
145
+
146
+ def models(self) -> List[ModelInfo]:
147
+ """Return available models with pricing and node info."""
148
+ data = self._request("GET", "/grid/models")
149
+ wrapped = ModelsResponse.model_validate(data)
150
+ return wrapped.models
151
+
152
+ def pricing(self) -> List[PricingInfo]:
153
+ """Return the pricing table for all models."""
154
+ data = self._request("GET", "/grid/pricing")
155
+ wrapped = PricingResponse.model_validate(data)
156
+ return wrapped.pricing
157
+
158
+ # -- authenticated endpoints --------------------------------------------
159
+
160
+ def submit_job(
161
+ self,
162
+ model: str,
163
+ input_payload: Dict[str, Any],
164
+ *,
165
+ submitter_wallet: Optional[str] = None,
166
+ submitter_chain: Optional[str] = None,
167
+ ) -> JobResponse:
168
+ """Submit a compute job to the grid.
169
+
170
+ Parameters
171
+ ----------
172
+ model:
173
+ Model identifier (e.g. ``"gemma-4-26b"``).
174
+ input_payload:
175
+ The request body for the model (e.g. chat messages).
176
+ submitter_wallet:
177
+ On-chain wallet address of the submitter.
178
+ submitter_chain:
179
+ Chain identifier (e.g. ``"base"``, ``"ethereum"``).
180
+ """
181
+ body: Dict[str, Any] = {
182
+ "model": model,
183
+ "input": input_payload,
184
+ }
185
+ if submitter_wallet is not None:
186
+ body["submitter_wallet"] = submitter_wallet
187
+ if submitter_chain is not None:
188
+ body["submitter_chain"] = submitter_chain
189
+
190
+ data = self._request("POST", "/grid/jobs", json=body)
191
+ return JobResponse.model_validate(data)
192
+
193
+ def get_job(self, job_id: str) -> JobResult:
194
+ """Get the status and result of a previously submitted job."""
195
+ data = self._request("GET", f"/grid/jobs/{job_id}")
196
+ return JobResult.model_validate(data)
197
+
198
+ def get_attestation(self, job_id: str) -> AttestationProof:
199
+ """Retrieve the TEE attestation proof for a completed job."""
200
+ data = self._request("GET", f"/grid/jobs/{job_id}/attestation")
201
+ return AttestationProof.model_validate(data)
202
+
203
+ # -- lifecycle ----------------------------------------------------------
204
+
205
+ def close(self) -> None:
206
+ """Close the underlying HTTP client."""
207
+ self._client.close()
208
+
209
+ def __enter__(self) -> "GridClient":
210
+ return self
211
+
212
+ def __exit__(self, *args: Any) -> None:
213
+ self.close()
@@ -0,0 +1,110 @@
1
+ """Pydantic models for SGL Network API responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Capacity
12
+ # ---------------------------------------------------------------------------
13
+
14
+ class TeeCapacity(BaseModel):
15
+ """Capacity breakdown for a single TEE type."""
16
+ tee_type: str
17
+ total_nodes: int = 0
18
+ active_nodes: int = 0
19
+ available_nodes: int = 0
20
+
21
+
22
+ class CapacityResponse(BaseModel):
23
+ """Grid-wide capacity summary returned by GET /grid/capacity."""
24
+ total_nodes: int = 0
25
+ active_nodes: int = 0
26
+ available_nodes: int = 0
27
+ by_tee_type: List[TeeCapacity] = Field(default_factory=list)
28
+ updated_at: Optional[str] = None
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Models
33
+ # ---------------------------------------------------------------------------
34
+
35
+ class ModelPricing(BaseModel):
36
+ """Pricing for a specific model."""
37
+ price_per_1k_input_tokens_usd: float = 0.0
38
+ price_per_1k_output_tokens_usd: float = 0.0
39
+
40
+
41
+ class ModelInfo(BaseModel):
42
+ """Model descriptor returned by GET /grid/models."""
43
+ id: str
44
+ owned_by: str = ""
45
+ sgl_node_count: int = 0
46
+ sgl_tee_types: List[str] = Field(default_factory=list)
47
+ sgl_pricing: Optional[ModelPricing] = None
48
+
49
+
50
+ class ModelsResponse(BaseModel):
51
+ """Wrapper for the models list."""
52
+ models: List[ModelInfo] = Field(default_factory=list)
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Pricing
57
+ # ---------------------------------------------------------------------------
58
+
59
+ class PricingInfo(BaseModel):
60
+ """Pricing entry for a single model."""
61
+ model: str
62
+ price_per_1k_input_tokens_usd: float = 0.0
63
+ price_per_1k_output_tokens_usd: float = 0.0
64
+
65
+
66
+ class PricingResponse(BaseModel):
67
+ """Full pricing table returned by GET /grid/pricing."""
68
+ pricing: List[PricingInfo] = Field(default_factory=list)
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Jobs
73
+ # ---------------------------------------------------------------------------
74
+
75
+ class JobResponse(BaseModel):
76
+ """Response from POST /grid/jobs (job submission)."""
77
+ job_id: str
78
+ status: str = "pending"
79
+ model: str = ""
80
+ node_id: Optional[str] = None
81
+ tee_type: Optional[str] = None
82
+ estimated_cost_usd: Optional[float] = None
83
+ created_at: Optional[str] = None
84
+
85
+
86
+ class AttestationProof(BaseModel):
87
+ """TEE attestation proof for a completed job."""
88
+ node_id: str = ""
89
+ tee_type: str = ""
90
+ job_id: str = ""
91
+ attestation_signature: str = ""
92
+ attestation_report: Optional[str] = None
93
+ verified: bool = False
94
+ verified_at: Optional[str] = None
95
+
96
+
97
+ class JobResult(BaseModel):
98
+ """Full job result returned by GET /grid/jobs/:id."""
99
+ id: str
100
+ status: str = "pending"
101
+ model: str = ""
102
+ node_id: Optional[str] = None
103
+ tee_type: Optional[str] = None
104
+ result: Optional[Dict[str, Any]] = None
105
+ encrypted_result: Optional[str] = None
106
+ attestation_proof: Optional[AttestationProof] = None
107
+ cost_usd: Optional[float] = None
108
+ created_at: Optional[str] = None
109
+ completed_at: Optional[str] = None
110
+ error: Optional[str] = None
@@ -0,0 +1,51 @@
1
+ """Helper to configure the OpenAI SDK for use with the SGL Network."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ from .client import DEFAULT_BASE_URL
8
+
9
+
10
+ def create_openai_client(
11
+ api_key: str = "sgl-anonymous",
12
+ base_url: Optional[str] = None,
13
+ **kwargs,
14
+ ):
15
+ """Return an ``openai.OpenAI`` instance pointed at the SGL Network.
16
+
17
+ Parameters
18
+ ----------
19
+ api_key:
20
+ API key for the orchestrator (e.g. ``"scg_..."``).
21
+ Defaults to ``"sgl-anonymous"`` for unauthenticated access.
22
+ base_url:
23
+ Override the default orchestrator URL. The ``/v1`` suffix is
24
+ appended automatically if not already present.
25
+ **kwargs:
26
+ Additional keyword arguments forwarded to ``openai.OpenAI()``.
27
+
28
+ Returns
29
+ -------
30
+ openai.OpenAI
31
+ A configured OpenAI client.
32
+
33
+ Raises
34
+ ------
35
+ ImportError
36
+ If the ``openai`` package is not installed. Install it with
37
+ ``pip install singularity-grid[openai]``.
38
+ """
39
+ try:
40
+ from openai import OpenAI # type: ignore[import-untyped]
41
+ except ImportError:
42
+ raise ImportError(
43
+ "The openai package is required for create_openai_client(). "
44
+ "Install it with: pip install singularity-grid[openai]"
45
+ ) from None
46
+
47
+ url = base_url or DEFAULT_BASE_URL
48
+ if not url.endswith("/v1"):
49
+ url = url.rstrip("/") + "/v1"
50
+
51
+ return OpenAI(api_key=api_key, base_url=url, **kwargs)
File without changes