Skip to content

Commit bb071e3

Browse files
authored
Merge branch 'main' into fix/opg-token-allowance-modelhub-firebase-refresh
Signed-off-by: kukac <adambalogh@users.noreply.github.com>
2 parents ea3b99c + 7eaf127 commit bb071e3

21 files changed

Lines changed: 1326 additions & 266 deletions

File tree

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ jobs:
4444
- name: Run tests
4545
run: make test
4646

47+
- name: Run LLM integration tests
48+
env:
49+
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
50+
run: make llm_integrationtest
51+
4752
release:
4853
needs: [check, test]
4954
runs-on: ubuntu-latest

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ OpenGradient enables developers to build AI applications with verifiable executi
2424
pip install opengradient
2525
```
2626

27-
**Note**: Windows users should temporarily enable WSL during installation (fix in progress).
27+
**Note**: > **Windows users:** See the [Windows Installation Guide](./WINDOWS_INSTALL.md) for step-by-step setup instructions.
2828

2929
## Network Architecture
3030

WINDOWS_INSTALL.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Windows Installation Guide
2+
3+
The `opengradient` package requires a C compiler
4+
to build its native dependencies. Windows does not
5+
have one by default.
6+
7+
## Step 1 — Enable WSL
8+
9+
Open PowerShell as Administrator and run:
10+
11+
wsl --install
12+
13+
Restart your PC when prompted.
14+
15+
## Step 2 — Install Python and uv inside WSL
16+
17+
Open the Ubuntu app and run:
18+
19+
sudo apt update && sudo apt install -y python3 curl
20+
curl -LsSf https://astral.sh/uv/install.sh | sh
21+
source $HOME/.local/bin/env
22+
23+
## Step 3 — Install SDK
24+
25+
uv add opengradient
26+
27+
## Step 4 — Verify
28+
29+
uv run python3 -c "import opengradient; print('Ready!')"
30+
31+
## Common Errors
32+
33+
- Visual C++ 14.0 required → Use WSL instead
34+
- wsl: command not found → Update Windows 10 to Build 19041+
35+
- WSL stuck → Enable Virtualization in BIOS
36+
- uv: command not found → Run: source $HOME/.local/bin/env

docs/opengradient/client/llm.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,30 @@ below the requested amount.
3131
def __init__(
3232
private_keystr,
3333
rpc_urlstr = 'https://ogevmdevnet.opengradient.ai',
34-
tee_registry_addressstr = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248',
35-
llm_server_url: Optional[str= None
34+
tee_registry_addressstr = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248'
3635
)
3736
```
3837

38+
#### Static methods
39+
40+
---
41+
42+
#### `from_url()`
43+
44+
```python
45+
static def from_url(private_keystr, llm_server_urlstr) ‑> `LLM`
46+
```
47+
**[Dev]** Create an LLM client with a hardcoded TEE endpoint URL.
48+
49+
Intended for development and self-hosted TEE servers. TLS certificate
50+
verification is disabled because these servers typically use self-signed
51+
certificates. For production use, prefer the default constructor which
52+
resolves TEEs from the on-chain registry.
53+
3954
**Arguments**
4055

41-
* **`private_key (str)`**: Ethereum private key for signing x402 payments.
42-
* **`rpc_url (str)`**: RPC URL for the OpenGradient network. Used to fetch the
43-
active TEE endpoint from the on-chain registry when ``llm_server_url``
44-
is not provided.
45-
* **`tee_registry_address (str)`**: Address of the on-chain TEE registry contract.
46-
* **`llm_server_url (str, optional)`**: Bypass the registry and connect directly
47-
to this TEE endpoint URL (e.g. ``"https://1.2.3.4"``). When set,
48-
TLS certificate verification is disabled automatically because
49-
self-hosted TEE servers typically use self-signed certificates.
50-
51-
.. warning::
52-
Using ``llm_server_url`` disables TLS certificate verification,
53-
which removes protection against man-in-the-middle attacks.
54-
Only connect to servers you trust and over secure network paths.
56+
* **`private_key`**: Ethereum private key for signing x402 payments.
57+
* **`llm_server_url`**: The TEE endpoint URL (e.g. ``"https://1.2.3.4"``).
5558

5659
#### Methods
5760

@@ -125,7 +128,7 @@ Union[TextGenerationOutput, AsyncGenerator[StreamChunk, None]]:
125128
```python
126129
async def close(self) ‑> None
127130
```
128-
Close the underlying HTTP client.
131+
Cancel the background refresh loop and close the HTTP client.
129132

130133
---
131134

@@ -200,8 +203,8 @@ a transaction. Otherwise, sends an ERC-20 approve transaction.
200203

201204
**Arguments**
202205

203-
* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``0.05``
204-
for 0.05 OPG). Must be at least 0.05 OPG.
206+
* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``0.1``
207+
for 0.1 OPG). Must be at least 0.1 OPG.
205208

206209
**Returns**
207210

@@ -211,5 +214,5 @@ Permit2ApprovalResult: Contains ``allowance_before``,
211214

212215
**Raises**
213216

214-
* **`ValueError`**: If the OPG amount is less than 0.05.
217+
* **`ValueError`**: If the OPG amount is less than 0.1.
215218
* **`RuntimeError`**: If the approval transaction fails.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
---
2+
outline: [2,4]
3+
---
4+
5+
[opengradient](../index) / [client](./index) / tee_connection
6+
7+
# Package opengradient.client.tee_connection
8+
9+
Manages the lifecycle of a connection to a TEE endpoint.
10+
11+
## Classes
12+
13+
### `ActiveTEE`
14+
15+
Snapshot of the currently connected TEE.
16+
17+
#### Constructor
18+
19+
```python
20+
def __init__(
21+
endpointstr,
22+
http_client`x402HttpxClient`,
23+
tee_id: Optional[str],
24+
payment_address: Optional[str]
25+
)
26+
```
27+
28+
#### Methods
29+
30+
---
31+
32+
#### `metadata()`
33+
34+
```python
35+
def metadata(self) ‑> Dict
36+
```
37+
Return TEE metadata dict for decorating responses.
38+
39+
#### Variables
40+
41+
* static `endpoint` : str
42+
* static `http_client``x402HttpxClient`
43+
* static `payment_address` : Optional[str]
44+
* static `tee_id` : Optional[str]
45+
46+
### `RegistryTEEConnection`
47+
48+
TEE connection resolved from the on-chain registry.
49+
50+
Handles TLS certificate pinning, background health checks, and automatic
51+
failover when the current TEE becomes unavailable.
52+
53+
#### Constructor
54+
55+
```python
56+
def __init__(x402_client`x402Client`, registry: [TEERegistry](./tee_registry))
57+
```
58+
59+
**Arguments**
60+
61+
* **`x402_client`**: Configured x402 payment client for creating HTTP clients.
62+
* **`registry`**: TEERegistry for looking up active TEEs.
63+
64+
#### Methods
65+
66+
---
67+
68+
#### `close()`
69+
70+
```python
71+
async def close(self) ‑> None
72+
```
73+
Cancel the background refresh loop and close the HTTP client.
74+
75+
---
76+
77+
#### `ensure_refresh_loop()`
78+
79+
```python
80+
def ensure_refresh_loop(self) ‑> None
81+
```
82+
Start the background TEE refresh loop if not already running.
83+
84+
Called lazily from async request methods since ``__init__`` is synchronous.
85+
86+
---
87+
88+
#### `get()`
89+
90+
```python
91+
def get(self) ‑> `ActiveTEE`
92+
```
93+
Return a snapshot of the current TEE connection.
94+
95+
---
96+
97+
#### `reconnect()`
98+
99+
```python
100+
async def reconnect(self) ‑> None
101+
```
102+
Connect to a new TEE from the registry and rebuild the HTTP client.
103+
104+
### `StaticTEEConnection`
105+
106+
TEE connection with a hardcoded endpoint URL.
107+
108+
No registry lookup, no background refresh. TLS certificate verification
109+
is disabled because self-hosted TEE servers typically use self-signed certs.
110+
111+
#### Constructor
112+
113+
```python
114+
def __init__(x402_client`x402Client`, endpointstr)
115+
```
116+
117+
**Arguments**
118+
119+
* **`x402_client`**: Configured x402 payment client for creating HTTP clients.
120+
* **`endpoint`**: The TEE endpoint URL to connect to.
121+
122+
#### Methods
123+
124+
---
125+
126+
#### `close()`
127+
128+
```python
129+
async def close(self) ‑> None
130+
```
131+
Close the HTTP client.
132+
133+
---
134+
135+
#### `ensure_refresh_loop()`
136+
137+
```python
138+
def ensure_refresh_loop(self) ‑> None
139+
```
140+
No-op — static connections don't refresh.
141+
142+
---
143+
144+
#### `get()`
145+
146+
```python
147+
def get(self) ‑> `ActiveTEE`
148+
```
149+
Return a snapshot of the current TEE connection.
150+
151+
---
152+
153+
#### `reconnect()`
154+
155+
```python
156+
async def reconnect(self) ‑> None
157+
```
158+
Rebuild the HTTP client (same endpoint).
159+
160+
### `TEEConnectionInterface`
161+
162+
Interface for TEE connection implementations.
163+
164+
#### Constructor
165+
166+
```python
167+
def __init__(*args, **kwargs)
168+
```
169+
170+
#### Methods
171+
172+
---
173+
174+
#### `close()`
175+
176+
```python
177+
async def close(self) ‑> None
178+
```
179+
180+
---
181+
182+
#### `ensure_refresh_loop()`
183+
184+
```python
185+
def ensure_refresh_loop(self) ‑> None
186+
```
187+
188+
---
189+
190+
#### `get()`
191+
192+
```python
193+
def get(self) ‑> `ActiveTEE`
194+
```
195+
196+
---
197+
198+
#### `reconnect()`
199+
200+
```python
201+
async def reconnect(self) ‑> None
202+
```

docs/opengradient/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ opengradient
66

77
# Package opengradient
88

9-
**Version: 0.9.0**
9+
**Version: 0.9.3**
1010

1111
OpenGradient Python SDK for decentralized AI inference with end-to-end verification.
1212

integrationtest/llm/test_llm_chat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
]
2424

2525
# Amount of OPG tokens to fund the test account with
26-
OPG_FUND_AMOUNT = 0.05
26+
OPG_FUND_AMOUNT = 0.1
2727
# Amount of ETH to fund the test account with (for gas)
2828
ETH_FUND_AMOUNT = 0.0001
2929

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "opengradient"
7-
version = "0.9.1"
7+
version = "0.9.3"
88
description = "Python SDK for OpenGradient decentralized model management & inference services"
99
authors = [{name = "OpenGradient", email = "adam@vannalabs.ai"}]
1010
readme = "README.md"
@@ -27,7 +27,7 @@ dependencies = [
2727
"langchain>=0.3.7",
2828
"openai>=1.58.1",
2929
"pydantic>=2.9.2",
30-
"og-x402==0.0.1.dev2"
30+
"og-x402==0.0.1.dev4"
3131
]
3232

3333
[project.optional-dependencies]

src/opengradient/client/_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ def run_with_retry(
4949
"""
5050
effective_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRY
5151

52+
if effective_retries < 1:
53+
raise ValueError(f"max_retries must be at least 1, got {effective_retries}")
54+
5255
for attempt in range(effective_retries):
5356
try:
5457
return txn_function()
@@ -62,3 +65,5 @@ def run_with_retry(
6265
continue
6366

6467
raise
68+
69+
raise RuntimeError(f"run_with_retry exhausted {effective_retries} attempts without returning or raising")

0 commit comments

Comments
 (0)