From 3b9d0a5416ad87376afc56000dcd10af0adfe565 Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:01:49 +0700 Subject: [PATCH 1/6] refactor: improve asyncio loop and exception handling --- src/opengradient/client.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/opengradient/client.py b/src/opengradient/client.py index 7d0482a..b216968 100644 --- a/src/opengradient/client.py +++ b/src/opengradient/client.py @@ -185,17 +185,11 @@ def set_api_key(self, provider: str, api_key: str): def _is_local_model(self, model_cid: str) -> bool: """ Check if a model is hosted locally on OpenGradient. - - Args: - model_cid: Model identifier - - Returns: - True if model is local, False if it should use external provider """ # Check if it's in our local LLM enum try: return model_cid in [llm.value for llm in LLM] - except: + except (AttributeError, TypeError, ValueError): return False def _get_provider_from_model(self, model: str) -> str: @@ -672,8 +666,18 @@ async def make_request(): raise OpenGradientError(error_msg) try: - # Run the async function in a sync context - return asyncio.run(make_request()) + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + if loop.is_running(): + logging.debug("Existing event loop detected. Applying nest_asyncio.") + import nest_asyncio + nest_asyncio.apply() + + return loop.run_until_complete(make_request()) except Exception as e: error_msg = f"External LLM completion failed: {str(e)}" if hasattr(e, "response") and e.response is not None: @@ -1559,4 +1563,4 @@ def run_with_retry(txn_function: Callable, max_retries=DEFAULT_MAX_RETRY, retry_ time.sleep(retry_delay) continue - raise \ No newline at end of file + raise From 96ed81c38e6203c862869093a466bf11a746228b Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:05:50 +0700 Subject: [PATCH 2/6] build: add nest-asyncio dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b915455..175b201 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "openai>=1.58.1", "pydantic>=2.9.2", "og-test-x402==0.0.9", + "nest-asyncio>=1.5.0", ] [project.scripts] From 23f13827ee0aa4fc9e26ade3ce57a2e2af29e22b Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Fri, 6 Feb 2026 08:35:37 +0700 Subject: [PATCH 3/6] fix: implement scheme_filter and address Copilot feedback in X402Auth - Added missing scheme_filter parameter to X402Auth.__init__ - Updated async_auth_flow to use scheme_filter in payment requirements selection - Fixed type hinting for payment_requirements_selector to resolve Copilot warnings --- src/opengradient/x402_auth.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/opengradient/x402_auth.py b/src/opengradient/x402_auth.py index 9f54c14..5069d4f 100644 --- a/src/opengradient/x402_auth.py +++ b/src/opengradient/x402_auth.py @@ -43,6 +43,7 @@ def __init__( ] ] = None, network_filter: typing.Optional[str] = None, + scheme_filter: typing.Optional[str] = None, ): """ Initialize X402Auth with an Ethereum account for signing payments. @@ -50,6 +51,7 @@ def __init__( Args: account: eth_account LocalAccount instance for signing payments max_value: Optional maximum allowed payment amount in base units + payment_requirements_selector: Optional callable to select payment requirements network_filter: Optional network filter for selecting payment requirements scheme_filter: Optional scheme filter for selecting payment requirements """ @@ -59,6 +61,7 @@ def __init__( payment_requirements_selector=payment_requirements_selector, # type: ignore ) self.network_filter = network_filter + self.scheme_filter = scheme_filter async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator[httpx.Request, httpx.Response]: """ @@ -79,12 +82,17 @@ async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator payment_response = x402PaymentRequiredResponse(**data) + # Теперь используем и network_filter, и scheme_filter, как просил Copilot selected_requirements = self.x402_client.select_payment_requirements( payment_response.accepts, self.network_filter, + self.scheme_filter, ) - payment_header = self.x402_client.create_payment_header(selected_requirements, payment_response.x402_version) + payment_header = self.x402_client.create_payment_header( + selected_requirements, + payment_response.x402_version + ) request.headers["X-Payment"] = payment_header request.headers["Access-Control-Expose-Headers"] = "X-Payment-Response" From ad804843241df90e3c840f5b643518a5525ac368 Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Sat, 7 Feb 2026 02:06:32 +0000 Subject: [PATCH 4/6] fix: resolve complex merge conflicts in x402_auth --- src/opengradient/x402_auth.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/opengradient/x402_auth.py b/src/opengradient/x402_auth.py index 5069d4f..7e58704 100644 --- a/src/opengradient/x402_auth.py +++ b/src/opengradient/x402_auth.py @@ -19,10 +19,6 @@ class X402Auth(httpx.Auth): This class implements the httpx Auth interface to handle 402 Payment Required responses by automatically creating and attaching payment headers. - - Example: - async with httpx.AsyncClient(auth=X402Auth(account=wallet_account)) as client: - response = await client.get("https://api.example.com/paid-resource") """ requires_response_body = True @@ -45,16 +41,6 @@ def __init__( network_filter: typing.Optional[str] = None, scheme_filter: typing.Optional[str] = None, ): - """ - Initialize X402Auth with an Ethereum account for signing payments. - - Args: - account: eth_account LocalAccount instance for signing payments - max_value: Optional maximum allowed payment amount in base units - payment_requirements_selector: Optional callable to select payment requirements - network_filter: Optional network filter for selecting payment requirements - scheme_filter: Optional scheme filter for selecting payment requirements - """ self.x402_client = x402Client( account, max_value=max_value, @@ -64,15 +50,6 @@ def __init__( self.scheme_filter = scheme_filter async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator[httpx.Request, httpx.Response]: - """ - Handle authentication flow for x402 payment protocol. - - Args: - request: httpx Request object to be authenticated - - Yields: - httpx Request object with authentication headers attached - """ response = yield request if response.status_code == 402: @@ -82,7 +59,6 @@ async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator payment_response = x402PaymentRequiredResponse(**data) - # Теперь используем и network_filter, и scheme_filter, как просил Copilot selected_requirements = self.x402_client.select_payment_requirements( payment_response.accepts, self.network_filter, @@ -100,4 +76,4 @@ async def async_auth_flow(self, request: httpx.Request) -> typing.AsyncGenerator except Exception as e: logging.error(f"X402Auth: Error handling payment: {e}") - return + return \ No newline at end of file From 66f3a96be13ba1c19406617476884377ef821da3 Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Sat, 7 Feb 2026 03:51:42 +0000 Subject: [PATCH 5/6] fix: add asyncio loop management to client init --- src/opengradient/client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/opengradient/client.py b/src/opengradient/client.py index 02dbbe3..7963f69 100644 --- a/src/opengradient/client.py +++ b/src/opengradient/client.py @@ -107,6 +107,13 @@ def __init__( email (str, optional): Email for authentication. Defaults to "test@test.com". password (str, optional): Password for authentication. Defaults to "Test-123". """ + import asyncio + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + self._inference_hub_contract_address = contract_address self._blockchain = Web3(Web3.HTTPProvider(rpc_url)) self._api_url = api_url From 14cd1e284a6ccc1b9f467e95531b73eecb51e7a5 Mon Sep 17 00:00:00 2001 From: lofingv <97442878+lofingv@users.noreply.github.com> Date: Sat, 7 Feb 2026 04:00:08 +0000 Subject: [PATCH 6/6] fix: add event loop management and sync X402Auth filters --- src/opengradient/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/opengradient/client.py b/src/opengradient/client.py index 7963f69..bf4469e 100644 --- a/src/opengradient/client.py +++ b/src/opengradient/client.py @@ -113,7 +113,7 @@ def __init__( except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - + self._inference_hub_contract_address = contract_address self._blockchain = Web3(Web3.HTTPProvider(rpc_url)) self._api_url = api_url @@ -794,7 +794,11 @@ async def _tee_llm_chat_stream_async( limits=LIMITS, http2=False, follow_redirects=False, - auth=X402Auth(account=self._wallet_account, network_filter=DEFAULT_NETWORK_FILTER), # type: ignore + auth=X402Auth( + account=self._wallet_account, + network_filter=DEFAULT_NETWORK_FILTER, + scheme_filter="ethereum-sepolia" + ), # type: ignore verify=True, ) as client: headers = {