Skip to content

Commit c7501cd

Browse files
author
balogh.adam@icloud.com
committed
refresh tee configs periodically
1 parent 6ffa8e2 commit c7501cd

2 files changed

Lines changed: 29 additions & 7 deletions

File tree

src/opengradient/client/llm.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""LLM chat and completion via TEE-verified execution with x402 payments."""
22

3+
import asyncio
34
import json
45
import logging
56
import ssl
7+
import time
68
from dataclasses import dataclass
79
from typing import AsyncGenerator, Awaitable, Callable, Dict, List, Optional, TypeVar, Union
810
import httpx
@@ -32,6 +34,7 @@
3234
_CHAT_ENDPOINT = "/v1/chat/completions"
3335
_COMPLETION_ENDPOINT = "/v1/completions"
3436
_REQUEST_TIMEOUT = 60
37+
_TEE_REFRESH_INTERVAL = 300 # Re-resolve TEE from registry every 5 minutes
3538

3639

3740
@dataclass
@@ -107,6 +110,8 @@ def __init__(
107110
register_upto_evm_client(self._x402_client, signer, networks=[BASE_TESTNET_NETWORK])
108111

109112
self._connect_tee()
113+
self._tee_refreshed_at: float = time.monotonic()
114+
self._refresh_lock = asyncio.Lock()
110115

111116
# ── TEE resolution and connection ───────────────────────────────────────────
112117

@@ -127,12 +132,27 @@ def _connect_tee(self) -> None:
127132

128133
async def _refresh_tee(self) -> None:
129134
"""Re-resolve TEE from the registry and rebuild the HTTP client."""
130-
old_http_client = self._http_client
131-
self._connect_tee()
132-
try:
133-
await old_http_client.aclose()
134-
except Exception:
135-
logger.debug("Failed to close previous HTTP client during TEE refresh.", exc_info=True)
135+
async with self._refresh_lock:
136+
old_http_client = self._http_client
137+
self._connect_tee()
138+
self._tee_refreshed_at = time.monotonic()
139+
try:
140+
await old_http_client.aclose()
141+
except Exception:
142+
logger.debug("Failed to close previous HTTP client during TEE refresh.", exc_info=True)
143+
144+
async def _maybe_refresh_tee(self) -> None:
145+
"""Re-resolve TEE if the current one is older than ``_TEE_REFRESH_INTERVAL``.
146+
147+
Skips the refresh for explicit ``llm_server_url`` overrides since they
148+
bypass the registry entirely.
149+
"""
150+
if self._llm_server_url is not None:
151+
return
152+
if time.monotonic() - self._tee_refreshed_at < _TEE_REFRESH_INTERVAL:
153+
return
154+
logger.debug("TEE endpoint stale (>%ds); refreshing from registry.", _TEE_REFRESH_INTERVAL)
155+
await self._refresh_tee()
136156

137157

138158
@staticmethod
@@ -212,6 +232,7 @@ async def _call_with_tee_retry(
212232
Only retries when the request never reached the server (no HTTP response).
213233
Server-side errors (4xx/5xx) are not retried.
214234
"""
235+
await self._maybe_refresh_tee()
215236
try:
216237
return await call()
217238
except httpx.HTTPStatusError:
@@ -448,6 +469,7 @@ async def _chat_tools_as_stream(self, params: _ChatParams, messages: List[Dict])
448469

449470
async def _chat_stream(self, params: _ChatParams, messages: List[Dict]) -> AsyncGenerator[StreamChunk, None]:
450471
"""Async SSE streaming implementation."""
472+
await self._maybe_refresh_tee()
451473
headers = self._headers(params.x402_settlement_mode)
452474
payload = self._chat_payload(params, messages, stream=True)
453475

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)