diff --git a/README.md b/README.md index f61a8c8..585980e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Before using the SDK, you will need: 1. **Private Key**: An Ethereum-compatible wallet private key funded with **Base Sepolia OPG tokens** for x402 LLM payments 2. **Test Tokens**: Obtain free test tokens from the [OpenGradient Faucet](https://faucet.opengradient.ai) for testnet LLM inference -3. **Alpha Private Key** (Optional): A separate private key funded with **OpenGradient testnet gas tokens** for Alpha Testnet on-chain inference. If not provided, the primary `private_key` is used for both chains. +3. **Alpha Testnet Key** (Optional): A private key funded with **OpenGradient testnet gas tokens** for Alpha Testnet on-chain inference (can be the same or a different key) 4. **Model Hub Account** (Optional): Required only for model uploads. Register at [hub.opengradient.ai/signup](https://hub.opengradient.ai/signup) ### Configuration @@ -66,29 +66,30 @@ The following Firebase configuration variables are **optional** and only needed **Note**: If you're only using the SDK for LLM inference, you don't need to configure any environment variables. -### Client Initialization +### Initialization + +The SDK provides separate clients for each service. Create only the ones you need: + ```python import os import opengradient as og -client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), # Base Sepolia OPG tokens for LLM payments - alpha_private_key=os.environ.get("OG_ALPHA_PRIVATE_KEY"), # Optional: OpenGradient testnet tokens for on-chain inference - email=None, # Optional: required only for model uploads - password=None, -) -``` +# LLM inference — settles via x402 on Base Sepolia using OPG tokens +llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) -The client operates across two chains: -- **LLM inference** (`client.llm`) settles via x402 on **Base Sepolia** using OPG tokens (funded by `private_key`) -- **Alpha Testnet** (`client.alpha`) runs on the **OpenGradient network** using testnet gas tokens (funded by `alpha_private_key`, or `private_key` when not provided) +# Alpha Testnet — on-chain inference on the OpenGradient network using testnet gas tokens +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) + +# Model Hub — requires email/password, only needed for model uploads +hub = og.ModelHub(email="you@example.com", password="...") +``` ### OPG Token Approval Before making LLM requests, your wallet must approve OPG token spending via the [Permit2](https://github.com/Uniswap/permit2) protocol. Call this once (it's idempotent — no transaction is sent if the allowance already covers the requested amount): ```python -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) ``` See [Payment Settlement](#payment-settlement) for details on settlement modes. @@ -99,7 +100,7 @@ See [Payment Settlement](#payment-settlement) for details on settlement modes. OpenGradient provides secure, verifiable inference through Trusted Execution Environments. All supported models include cryptographic attestation verified by the OpenGradient network. LLM methods are async: ```python -completion = await client.llm.chat( +completion = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Hello!"}], ) @@ -111,7 +112,7 @@ print(f"Transaction hash: {completion.transaction_hash}") For real-time generation, enable streaming: ```python -stream = await client.llm.chat( +stream = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Explain quantum computing"}], max_tokens=500, @@ -190,7 +191,7 @@ The Alpha Testnet provides access to experimental capabilities including custom Browse models on the [Model Hub](https://hub.opengradient.ai/) or deploy your own: ```python -result = client.alpha.infer( +result = alpha.infer( model_cid="your-model-cid", model_input={"input": [1.0, 2.0, 3.0]}, inference_mode=og.InferenceMode.VANILLA, @@ -204,12 +205,7 @@ Deploy on-chain AI workflows with optional scheduling: ```python import opengradient as og -client = og.Client( - private_key="your-private-key", # Base Sepolia OPG tokens - alpha_private_key="your-alpha-private-key", # OpenGradient testnet tokens - email="your-email", - password="your-password", -) +alpha = og.Alpha(private_key="your-private-key") # Define input query for historical price data input_query = og.HistoricalInputQuery( @@ -222,7 +218,7 @@ input_query = og.HistoricalInputQuery( ) # Deploy workflow with optional scheduling -contract_address = client.alpha.new_workflow( +contract_address = alpha.new_workflow( model_cid="your-model-cid", input_query=input_query, input_tensor_name="input", @@ -237,14 +233,14 @@ print(f"Workflow deployed at: {contract_address}") ### Workflow Execution and Monitoring ```python # Manually trigger workflow execution -result = client.alpha.run_workflow(contract_address) +result = alpha.run_workflow(contract_address) print(f"Inference output: {result}") # Read the latest result -latest = client.alpha.read_workflow_result(contract_address) +latest = alpha.read_workflow_result(contract_address) # Retrieve historical results -history = client.alpha.read_workflow_history( +history = alpha.read_workflow_history( contract_address, num_results=5 ) @@ -299,7 +295,7 @@ OpenGradient supports multiple settlement modes through the x402 payment protoco Specify settlement mode in your requests: ```python -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Hello"}], x402_settlement_mode=og.x402SettlementMode.BATCH_HASHED, diff --git a/docs/CLAUDE_SDK_USERS.md b/docs/CLAUDE_SDK_USERS.md index c3d19cb..f65a8e7 100644 --- a/docs/CLAUDE_SDK_USERS.md +++ b/docs/CLAUDE_SDK_USERS.md @@ -18,13 +18,11 @@ pip install opengradient import opengradient as og import os -# Initialize client -client = og.Client( - private_key=os.environ["OG_PRIVATE_KEY"], # Required: Ethereum private key -) +# Create an LLM client +llm = og.LLM(private_key=os.environ["OG_PRIVATE_KEY"]) # LLM Chat (TEE-verified with x402 payments, async) -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[{"role": "user", "content": "Hello!"}], max_tokens=100, @@ -34,24 +32,25 @@ print(result.chat_output["content"]) ## Core API Reference -### Client Initialization +### Initialization + +Each service has its own client class: ```python -# Option 1: Create client instance (recommended) -client = og.Client( - private_key="0x...", # Required: Ethereum private key - email=None, # Optional: Model Hub auth - password=None, # Optional: Model Hub auth -) +# LLM inference (Base Sepolia OPG tokens for x402 payments) +llm = og.LLM(private_key="0x...") + +# On-chain model inference (OpenGradient testnet gas tokens) +alpha = og.Alpha(private_key="0x...") -# Option 2: Global initialization -og.init(private_key="0x...", email="...", password="...") +# Model Hub (email/password auth, only needed for model uploads) +hub = og.ModelHub(email="...", password="...") ``` ### LLM Chat ```python -result = await client.llm.chat( +result = await llm.chat( model: TEE_LLM, # og.TEE_LLM enum value messages: List[Dict], # [{"role": "user", "content": "..."}] max_tokens: int = 100, @@ -72,7 +71,7 @@ result = await client.llm.chat( ### LLM Completion ```python -result = await client.llm.completion( +result = await llm.completion( model: TEE_LLM, prompt: str, max_tokens: int = 100, @@ -88,7 +87,7 @@ result = await client.llm.completion( ### ONNX Model Inference ```python -result = client.alpha.infer( +result = alpha.infer( model_cid: str, # IPFS CID of model inference_mode: og.InferenceMode, # VANILLA, TEE, or ZKML model_input: Dict[str, Any], # Input tensors @@ -139,7 +138,7 @@ og.TEE_LLM.GROK_4_1_FAST_NON_REASONING All models are accessed through the OpenGradient TEE infrastructure with x402 payments: ```python -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Hello"}], ) @@ -165,7 +164,7 @@ tools = [{ } }] -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "What's the weather in NYC?"}], tools=tools, @@ -180,7 +179,7 @@ if result.chat_output.get("tool_calls"): ### Streaming ```python -stream = await client.llm.chat( +stream = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Tell me a story"}], stream=True, @@ -200,7 +199,7 @@ from langgraph.prebuilt import create_react_agent # Create LangChain-compatible LLM llm = og.agents.langchain_adapter( private_key=os.environ["OG_PRIVATE_KEY"], - model_cid=og.LLM.CLAUDE_SONNET_4_6, + model_cid=og.TEE_LLM.CLAUDE_SONNET_4_6, max_tokens=300, ) @@ -228,7 +227,7 @@ input_query = og.HistoricalInputQuery( scheduler = og.SchedulerParams(frequency=60, duration_hours=2) # Deploy -contract = client.alpha.new_workflow( +contract = alpha.new_workflow( model_cid="your-model-cid", input_query=input_query, input_tensor_name="price_data", @@ -236,11 +235,11 @@ contract = client.alpha.new_workflow( ) # Manually trigger execution -result = client.alpha.run_workflow(contract) +result = alpha.run_workflow(contract) # Read results -latest = client.alpha.read_workflow_result(contract) -history = client.alpha.read_workflow_history(contract, num_results=5) +latest = alpha.read_workflow_result(contract) +history = alpha.read_workflow_history(contract, num_results=5) ``` ### AlphaSense Tool Creation @@ -287,7 +286,7 @@ LLM methods raise `RuntimeError` on failure and `ValueError` for invalid argumen ```python try: - result = await client.llm.chat(...) + result = await llm.chat(...) except RuntimeError as e: print(f"Inference failed: {e}") except ValueError as e: diff --git a/docs/opengradient/client/alpha.md b/docs/opengradient/client/alpha.md index 0b35ef4..d39d863 100644 --- a/docs/opengradient/client/alpha.md +++ b/docs/opengradient/client/alpha.md @@ -24,10 +24,10 @@ including on-chain ONNX model inference, workflow deployment, and execution. ```python def __init__( - blockchain: `Web3`, - wallet_account: `LocalAccount`, - inference_hub_contract_address: str, - api_url: str + private_key: str, + rpc_url: str = 'https://ogevmdevnet.opengradient.ai', + inference_contract_address: str = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', + api_url: str = 'https://sdk-devnet.opengradient.ai' ) ``` diff --git a/docs/opengradient/client/client.md b/docs/opengradient/client/client.md deleted file mode 100644 index d35ed94..0000000 --- a/docs/opengradient/client/client.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -outline: [2,3] ---- - -[opengradient](../index) / [client](./index) / client - -# Package opengradient.client.client - -Main Client class that unifies all OpenGradient service namespaces. - -## Classes - -### `Client` - -Main OpenGradient SDK client. - -Provides unified access to all OpenGradient services including LLM inference, -on-chain model inference, and the Model Hub. - -The client operates across two chains: - -- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia** - using OPG tokens (funded by ``private_key``). -- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network** - using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key`` - when not provided). - -#### Constructor - -```python -def __init__( - private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - twins_api_key: Optional[str] = None, - rpc_url: str = 'https://ogevmdevnet.opengradient.ai', - api_url: str = 'https://sdk-devnet.opengradient.ai', - inference_contract_address: str = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', - llm_server_url: Optional[str] = None, - tee_registry_address: str = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248' -) -``` - -**Arguments** - -* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. -* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. -* **`email`**: Email for Model Hub authentication. Must be provided together - with ``password``. -* **`password`**: Password for Model Hub authentication. Must be provided - together with ``email``. -* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional. -* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet. -* **`api_url`**: API URL for the OpenGradient API. -* **`inference_contract_address`**: Inference contract address on the - OpenGradient Alpha Testnet. -* **`llm_server_url`**: Override the LLM server URL instead of using the - registry-discovered endpoint. When set, the TLS certificate is - validated against the system CA bundle rather than the registry. -* **`tee_registry_address`**: Address of the TEERegistry contract used to - discover active LLM proxy endpoints and their verified TLS certs. - -#### Methods - ---- - -#### `close()` - -```python -async def close(self) ‑> None -``` -Close underlying SDK resources. - -#### Variables - -* [**`alpha`**](./alpha): Alpha Testnet features including on-chain inference, workflow management, and ML model execution. -* [**`llm`**](./llm): LLM chat and completion via TEE-verified execution. -* [**`model_hub`**](./model_hub): Model Hub for creating, versioning, and uploading ML models. -* [**`twins`**](./twins): Digital twins chat via OpenGradient verifiable inference. ``None`` when no ``twins_api_key`` is provided. \ No newline at end of file diff --git a/docs/opengradient/client/exceptions.md b/docs/opengradient/client/exceptions.md deleted file mode 100644 index 0850ddf..0000000 --- a/docs/opengradient/client/exceptions.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -outline: [2,3] ---- - -[opengradient](../index) / [client](./index) / exceptions - -# Package opengradient.client.exceptions - -Exception types for OpenGradient SDK errors. - -## Classes - -### `AuthenticationError` - -Raised when there's an authentication error - -#### Constructor - -```python -def __init__(message='Authentication failed', **kwargs) -``` - -### `FileNotFoundError` - -Raised when a file is not found - -#### Constructor - -```python -def __init__(file_path) -``` - -### `InferenceError` - -Raised when there's an error during inference - -#### Constructor - -```python -def __init__(message, model_cid=None, **kwargs) -``` - -### `InsufficientCreditsError` - -Raised when the user has insufficient credits for the operation - -#### Constructor - -```python -def __init__( - message='Insufficient credits', - required_credits=None, - available_credits=None, - **kwargs -) -``` - -### `InvalidInputError` - -Raised when invalid input is provided - -#### Constructor - -```python -def __init__(message, invalid_fields=None, **kwargs) -``` - -### `NetworkError` - -Raised when a network error occurs - -#### Constructor - -```python -def __init__(message, status_code=None, response=None) -``` - -### `OpenGradientError` - -Base exception for OpenGradient SDK - -#### Constructor - -```python -def __init__(message, status_code=None, response=None) -``` - -#### Subclasses - -* `AuthenticationError` -* `FileNotFoundError` -* `InferenceError` -* `InsufficientCreditsError` -* `InvalidInputError` -* `NetworkError` -* `RateLimitError` -* `ResultRetrievalError` -* `ServerError` -* `TimeoutError` -* `UnsupportedModelError` -* `UploadError` - -### `RateLimitError` - -Raised when API rate limit is exceeded - -#### Constructor - -```python -def __init__(message='Rate limit exceeded', retry_after=None, **kwargs) -``` - -### `ResultRetrievalError` - -Raised when there's an error retrieving results - -#### Constructor - -```python -def __init__(message, inference_cid=None, **kwargs) -``` - -### `ServerError` - -Raised when a server error occurs - -#### Constructor - -```python -def __init__(message, status_code=None, response=None) -``` - -### `TimeoutError` - -Raised when a request times out - -#### Constructor - -```python -def __init__(message='Request timed out', timeout=None, **kwargs) -``` - -### `UnsupportedModelError` - -Raised when an unsupported model type is used - -#### Constructor - -```python -def __init__(model_type) -``` - -### `UploadError` - -Raised when there's an error during file upload - -#### Constructor - -```python -def __init__(message, file_path=None, **kwargs) -``` \ No newline at end of file diff --git a/docs/opengradient/client/index.md b/docs/opengradient/client/index.md index 029ac20..0e546ff 100644 --- a/docs/opengradient/client/index.md +++ b/docs/opengradient/client/index.md @@ -6,141 +6,39 @@ outline: [2,3] # Package opengradient.client -OpenGradient Client -- the central entry point to all SDK services. +OpenGradient Client -- service modules for the SDK. -## Overview - -The [Client](./client) class provides unified access to four service namespaces: +## Modules - **[llm](./llm)** -- LLM chat and text completion with TEE-verified execution and x402 payment settlement (Base Sepolia OPG tokens) - **[model_hub](./model_hub)** -- Model repository management: create, version, and upload ML models - **[alpha](./alpha)** -- Alpha Testnet features: on-chain ONNX model inference (VANILLA, TEE, ZKML modes), workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens) - **[twins](./twins)** -- Digital twins chat via OpenGradient verifiable inference -## Private Keys - -The SDK operates across two chains: - -- **`private_key`** -- used for LLM inference (``client.llm``). Pays via x402 on **Base Sepolia** with OPG tokens. -- **`alpha_private_key`** *(optional)* -- used for Alpha Testnet features (``client.alpha``). Pays gas on the **OpenGradient network** with testnet tokens. Falls back to ``private_key`` when omitted. - ## Usage ```python import opengradient as og -# Single key for both chains (backward compatible) -client = og.init(private_key="0x...") - -# Separate keys: Base Sepolia OPG for LLM, OpenGradient testnet gas for Alpha -client = og.init(private_key="0xLLM_KEY...", alpha_private_key="0xALPHA_KEY...") - -# One-time approval (idempotent — skips if allowance is already sufficient) -client.llm.ensure_opg_approval(opg_amount=5) - -# LLM chat (TEE-verified, streamed) -stream = await client.llm.chat( - model=og.TEE_LLM.CLAUDE_HAIKU_4_5, - messages=[{"role": "user", "content": "Hello!"}], - max_tokens=200, - stream=True, -) -async for chunk in stream: - if chunk.choices[0].delta.content: - print(chunk.choices[0].delta.content, end="") +# LLM inference (Base Sepolia OPG tokens) +llm = og.LLM(private_key="0x...") +llm.ensure_opg_approval(opg_amount=5) +result = await llm.chat(model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...]) -# On-chain model inference -result = client.alpha.infer( - model_cid="your_model_cid", - inference_mode=og.InferenceMode.VANILLA, - model_input={"input": [1.0, 2.0, 3.0]}, -) +# On-chain model inference (OpenGradient testnet gas tokens) +alpha = og.Alpha(private_key="0x...") +result = alpha.infer(model_cid, og.InferenceMode.VANILLA, model_input) # Model Hub (requires email auth) -client = og.init(private_key="0x...", email="you@example.com", password="...") -repo = client.model_hub.create_model("my-model", "A price prediction model") +hub = og.ModelHub(email="you@example.com", password="...") +repo = hub.create_model("my-model", "A price prediction model") ``` ## Submodules * [alpha](./alpha): Alpha Testnet features for OpenGradient SDK. -* [client](./client): Main Client class that unifies all OpenGradient service namespaces. * [llm](./llm): LLM chat and completion via TEE-verified execution with x402 payments. * [model_hub](./model_hub): Model Hub for creating, versioning, and uploading ML models. * [opg_token](./opg_token): OPG token Permit2 approval utilities for x402 payments. * [tee_registry](./tee_registry): TEE Registry client for fetching verified TEE endpoints and TLS certificates. -* [twins](./twins): Digital twins chat via OpenGradient verifiable inference. - -## Classes - -### `Client` - -Main OpenGradient SDK client. - -Provides unified access to all OpenGradient services including LLM inference, -on-chain model inference, and the Model Hub. - -The client operates across two chains: - -- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia** - using OPG tokens (funded by ``private_key``). -- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network** - using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key`` - when not provided). - -#### Constructor - -```python -def __init__( - private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - twins_api_key: Optional[str] = None, - rpc_url: str = 'https://ogevmdevnet.opengradient.ai', - api_url: str = 'https://sdk-devnet.opengradient.ai', - inference_contract_address: str = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', - llm_server_url: Optional[str] = None, - tee_registry_address: str = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248' -) -``` - -**Arguments** - -* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. -* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. -* **`email`**: Email for Model Hub authentication. Must be provided together - with ``password``. -* **`password`**: Password for Model Hub authentication. Must be provided - together with ``email``. -* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional. -* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet. -* **`api_url`**: API URL for the OpenGradient API. -* **`inference_contract_address`**: Inference contract address on the - OpenGradient Alpha Testnet. -* **`llm_server_url`**: Override the LLM server URL instead of using the - registry-discovered endpoint. When set, the TLS certificate is - validated against the system CA bundle rather than the registry. -* **`tee_registry_address`**: Address of the TEERegistry contract used to - discover active LLM proxy endpoints and their verified TLS certs. - -#### Methods - ---- - -#### `close()` - -```python -async def close(self) ‑> None -``` -Close underlying SDK resources. - -#### Variables - -* [**`alpha`**](./alpha): Alpha Testnet features including on-chain inference, workflow management, and ML model execution. -* [**`llm`**](./llm): LLM chat and completion via TEE-verified execution. -* [**`model_hub`**](./model_hub): Model Hub for creating, versioning, and uploading ML models. -* [**`twins`**](./twins): Digital twins chat via OpenGradient verifiable inference. ``None`` when no ``twins_api_key`` is provided. \ No newline at end of file +* [twins](./twins): Digital twins chat via OpenGradient verifiable inference. \ No newline at end of file diff --git a/docs/opengradient/client/llm.md b/docs/opengradient/client/llm.md index cd6d716..91fc655 100644 --- a/docs/opengradient/client/llm.md +++ b/docs/opengradient/client/llm.md @@ -29,9 +29,9 @@ below the requested amount. ```python def __init__( - wallet_account: `LocalAccount`, - rpc_url: Optional[str] = None, - tee_registry_address: Optional[str] = None, + private_key: str, + rpc_url: str = 'https://ogevmdevnet.opengradient.ai', + tee_registry_address: str = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248', llm_server_url: Optional[str] = None ) ``` diff --git a/docs/opengradient/client/x402_auth.md b/docs/opengradient/client/x402_auth.md deleted file mode 100644 index 76b49b3..0000000 --- a/docs/opengradient/client/x402_auth.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -outline: [2,3] ---- - -[opengradient](../index) / [client](./index) / x402_auth - -# Package opengradient.client.x402_auth - -X402 Authentication handler for httpx streaming requests. - -This module provides an httpx Auth class that handles x402 payment protocol -authentication for streaming responses. - -## Classes - -### `X402Auth` - -httpx Auth handler for x402 payment protocol. - -This class implements the httpx Auth interface to handle 402 Payment Required -responses by automatically creating and attaching payment headers. - -#### Constructor - -```python -def __init__(account: Any, max_value: Optional[int] = None, payment_requirements_selector: Optional[Callable[[list[`PaymentRequirements`], Optional[str], Optional[str], Optional[int]], `PaymentRequirements`]] = None, network_filter: Optional[str] = None) -``` - -**Arguments** - -* **`account`**: eth_account LocalAccount instance for signing payments -* **`max_value`**: Optional maximum allowed payment amount in base units -* **`network_filter`**: Optional network filter for selecting payment requirements -* **`scheme_filter`**: Optional scheme filter for selecting payment requirements - -#### Methods - ---- - -#### `async_auth_flow()` - -```python -async def async_auth_flow(self, request: `Request`) ‑> AsyncGenerator[`Request`, `Response`] -``` -Handle authentication flow for x402 payment protocol. - -**Arguments** - -* **`request`**: httpx Request object to be authenticated - -#### Variables - -* static `requires_response_body` \ No newline at end of file diff --git a/docs/opengradient/index.md b/docs/opengradient/index.md index e9e6874..5428a2d 100644 --- a/docs/opengradient/index.md +++ b/docs/opengradient/index.md @@ -26,13 +26,13 @@ All LLM inference runs inside Trusted Execution Environments (TEEs) and settles import asyncio import opengradient as og -client = og.init(private_key="0x...") +llm = og.LLM(private_key="0x...") # One-time approval (idempotent — skips if allowance is already sufficient) -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) # Chat with an LLM (TEE-verified) -response = asyncio.run(client.llm.chat( +response = asyncio.run(llm.chat( model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[{"role": "user", "content": "Hello!"}], max_tokens=200, @@ -41,7 +41,7 @@ print(response.chat_output) # Stream a response async def stream_example(): - stream = await client.llm.chat( + stream = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Explain TEE in one paragraph."}], max_tokens=300, @@ -54,7 +54,8 @@ async def stream_example(): asyncio.run(stream_example()) # Run on-chain ONNX model inference -result = client.alpha.infer( +alpha = og.Alpha(private_key="0x...") +result = alpha.infer( model_cid="your_model_cid", inference_mode=og.InferenceMode.VANILLA, model_input={"input": [1.0, 2.0, 3.0]}, @@ -64,19 +65,12 @@ print(result.model_output) ## Private Keys -The SDK operates across two chains. You can use a single key for both, or provide separate keys: +The SDK operates across two chains. Use separate keys for each: -- **``private_key``** -- pays for LLM inference via x402 on **Base Sepolia** (requires OPG tokens) -- **``alpha_private_key``** *(optional)* -- pays gas for Alpha Testnet on-chain inference on the **OpenGradient network** (requires testnet gas tokens). Falls back to ``private_key`` when omitted. +- **LLM** (``og.LLM``) -- pays for inference via x402 on **Base Sepolia** (requires OPG tokens) +- **Alpha** (``og.Alpha``) -- pays gas for on-chain inference on the **OpenGradient network** (requires testnet gas tokens) -```python -# Separate keys for each chain -client = og.init(private_key="0xBASE_KEY...", alpha_private_key="0xALPHA_KEY...") -``` - -## Client Namespaces - -The [Client](./client/index) object exposes four namespaces: +## Modules - **[llm](./client/llm)** -- Verifiable LLM chat and completion via TEE-verified execution with x402 payments (Base Sepolia OPG tokens) - **[alpha](./client/alpha)** -- On-chain ONNX model inference, workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens) @@ -86,14 +80,9 @@ The [Client](./client/index) object exposes four namespaces: ## Model Hub (requires email auth) ```python -client = og.init( - private_key="0x...", - email="you@example.com", - password="...", -) - -repo = client.model_hub.create_model("my-model", "A price prediction model") -client.model_hub.upload("model.onnx", repo.name, repo.initialVersion) +hub = og.ModelHub(email="you@example.com", password="...") +repo = hub.create_model("my-model", "A price prediction model") +hub.upload("model.onnx", repo.name, repo.initialVersion) ``` ## Framework Integrations @@ -104,118 +93,177 @@ The SDK includes adapters for popular AI frameworks -- see the `agents` submodul * [**agents**](./agents/index): OpenGradient Agent Framework Adapters * [**alphasense**](./alphasense/index): OpenGradient AlphaSense Tools -* [**client**](./client/index): OpenGradient Client -- the central entry point to all SDK services. +* [**client**](./client/index): OpenGradient Client -- service modules for the SDK. * [**types**](./types): OpenGradient Specific Types * [**workflow_models**](./workflow_models/index): OpenGradient Hardcoded Models -## Functions +## Classes + +### `Alpha` ---- +Alpha Testnet features namespace. -### `init()` +This class provides access to features that are only available on the Alpha Testnet, +including on-chain ONNX model inference, workflow deployment, and execution. + +#### Constructor ```python -def init( +def __init__( private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - **kwargs -) ‑> `Client` + rpc_url: str = 'https://ogevmdevnet.opengradient.ai', + inference_contract_address: str = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', + api_url: str = 'https://sdk-devnet.opengradient.ai' +) ``` -Initialize the global OpenGradient client. -This is the recommended way to get started. It creates a `Client` instance -and stores it as the global client for convenience. +#### Methods + +--- + +#### `infer()` + +```python +def infer( + self, + model_cid: str, + inference_mode: `InferenceMode`, + model_input: Dict[str, Union[str, int, float, List, `ndarray`]], + max_retries: Optional[int] = None +) ‑> `InferenceResult` +``` +Perform inference on a model. **Arguments** -* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. -* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. -* **`email`**: Email for Model Hub authentication. Optional. -* **`password`**: Password for Model Hub authentication. Optional. - **kwargs: Additional arguments forwarded to `Client`. +* **`model_cid (str)`**: The unique content identifier for the model from IPFS. +* **`inference_mode (InferenceMode)`**: The inference mode. +* **`model_input (Dict[str, Union[str, int, float, List, np.ndarray]])`**: The input data for the model. +* **`max_retries (int, optional)`**: Maximum number of retry attempts. Defaults to 5. **Returns** -The newly created `Client` instance. +InferenceResult (InferenceResult): A dataclass object containing the transaction hash and model output. + transaction_hash (str): Blockchain hash for the transaction + model_output (Dict[str, np.ndarray]): Output of the ONNX model -## Classes +**Raises** -### `Client` +* **`RuntimeError`**: If the inference fails. -Main OpenGradient SDK client. +--- -Provides unified access to all OpenGradient services including LLM inference, -on-chain model inference, and the Model Hub. +#### `new_workflow()` -The client operates across two chains: +```python +def new_workflow( + self, + model_cid: str, + input_query: `HistoricalInputQuery`, + input_tensor_name: str, + scheduler_params: Optional[`SchedulerParams`] = None +) ‑> str +``` +Deploy a new workflow contract with the specified parameters. -- **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia** - using OPG tokens (funded by ``private_key``). -- **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network** - using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key`` - when not provided). +This function deploys a new workflow contract on OpenGradient that connects +an AI model with its required input data. When executed, the workflow will fetch +the specified model, evaluate the input query to get data, and perform inference. -#### Constructor +The workflow can be set to execute manually or automatically via a scheduler. + +**Arguments** + +* **`model_cid (str)`**: CID of the model to be executed from the Model Hub +* **`input_query (HistoricalInputQuery)`**: Input definition for the model inference, + will be evaluated at runtime for each inference +* **`input_tensor_name (str)`**: Name of the input tensor expected by the model +* **`scheduler_params (Optional[SchedulerParams])`**: Scheduler configuration for automated execution: + - frequency: Execution frequency in seconds + - duration_hours: How long the schedule should live for + +**Returns** + +str: Deployed contract address. If scheduler_params was provided, the workflow + will be automatically executed according to the specified schedule. + +**Raises** + +* **`Exception`**: If transaction fails or gas estimation fails + +--- + +#### `read_workflow_history()` ```python -def __init__( - private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - twins_api_key: Optional[str] = None, - rpc_url: str = 'https://ogevmdevnet.opengradient.ai', - api_url: str = 'https://sdk-devnet.opengradient.ai', - inference_contract_address: str = '0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE', - llm_server_url: Optional[str] = None, - tee_registry_address: str = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248' -) +def read_workflow_history( + self, + contract_address: str, + num_results: int +) ‑> List[`ModelOutput`] ``` +Gets historical inference results from a workflow contract. + +Retrieves the specified number of most recent inference results from the contract's +storage, with the most recent result first. **Arguments** -* **`private_key`**: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. -* **`alpha_private_key`**: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. -* **`email`**: Email for Model Hub authentication. Must be provided together - with ``password``. -* **`password`**: Password for Model Hub authentication. Must be provided - together with ``email``. -* **`twins_api_key`**: API key for digital twins chat (twin.fun). Optional. -* **`rpc_url`**: RPC URL for the OpenGradient Alpha Testnet. -* **`api_url`**: API URL for the OpenGradient API. -* **`inference_contract_address`**: Inference contract address on the - OpenGradient Alpha Testnet. -* **`llm_server_url`**: Override the LLM server URL instead of using the - registry-discovered endpoint. When set, the TLS certificate is - validated against the system CA bundle rather than the registry. -* **`tee_registry_address`**: Address of the TEERegistry contract used to - discover active LLM proxy endpoints and their verified TLS certs. +* **`contract_address (str)`**: Address of the deployed workflow contract +* **`num_results (int)`**: Number of historical results to retrieve -#### Methods +**Returns** + +List[ModelOutput]: List of historical inference results --- -#### `close()` +#### `read_workflow_result()` ```python -async def close(self) ‑> None +def read_workflow_result(self, contract_address: str) ‑> `ModelOutput` ``` -Close underlying SDK resources. +Reads the latest inference result from a deployed workflow contract. + +**Arguments** + +* **`contract_address (str)`**: Address of the deployed workflow contract + +**Returns** + +ModelOutput: The inference result from the contract + +**Raises** + +* **`ContractLogicError`**: If the transaction fails +* **`Web3Error`**: If there are issues with the web3 connection or contract interaction + +--- + +#### `run_workflow()` + +```python +def run_workflow(self, contract_address: str) ‑> `ModelOutput` +``` +Triggers the run() function on a deployed workflow contract and returns the result. + +**Arguments** + +* **`contract_address (str)`**: Address of the deployed workflow contract + +**Returns** + +ModelOutput: The inference result from the contract + +**Raises** + +* **`ContractLogicError`**: If the transaction fails +* **`Web3Error`**: If there are issues with the web3 connection or contract interaction #### Variables -* [**`alpha`**](./client/alpha): Alpha Testnet features including on-chain inference, workflow management, and ML model execution. -* [**`llm`**](./client/llm): LLM chat and completion via TEE-verified execution. -* [**`model_hub`**](./client/model_hub): Model Hub for creating, versioning, and uploading ML models. -* [**`twins`**](./client/twins): Digital twins chat via OpenGradient verifiable inference. ``None`` when no ``twins_api_key`` is provided. +* `inference_abi` : dict +* `precompile_abi` : dict ### `InferenceMode` @@ -227,6 +275,320 @@ Enum for the different inference modes available for inference (VANILLA, ZKML, T * static `VANILLA` * static `ZKML` +### `LLM` + +LLM inference namespace. + +Provides access to large language model completions and chat via TEE +(Trusted Execution Environment) with x402 payment protocol support. +Supports both streaming and non-streaming responses. + +All request methods (``chat``, ``completion``) are async. + +Before making LLM requests, ensure your wallet has approved sufficient +OPG tokens for Permit2 spending by calling ``ensure_opg_approval``. +This only sends an on-chain transaction when the current allowance is +below the requested amount. + +#### Constructor + +```python +def __init__( + private_key: str, + rpc_url: str = 'https://ogevmdevnet.opengradient.ai', + tee_registry_address: str = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248', + llm_server_url: Optional[str] = None +) +``` + +#### Methods + +--- + +#### `chat()` + +```python +async def chat( + self, + model: `TEE_LLM`, + messages: List[Dict], + max_tokens: int = 100, + stop_sequence: Optional[List[str]] = None, + temperature: float = 0.0, + tools: Optional[List[Dict]] = None, + tool_choice: Optional[str] = None, + x402_settlement_mode: `x402SettlementMode` = x402SettlementMode.BATCH_HASHED, + stream: bool = False +) ‑> Union[`TextGenerationOutput`, AsyncGenerator[`StreamChunk`, None]] +``` +Perform inference on an LLM model using chat via TEE. + +**Arguments** + +* **`model (TEE_LLM)`**: The model to use (e.g., TEE_LLM.CLAUDE_HAIKU_4_5). +* **`messages (List[Dict])`**: The messages that will be passed into the chat. +* **`max_tokens (int)`**: Maximum number of tokens for LLM output. Default is 100. +* **`stop_sequence (List[str], optional)`**: List of stop sequences for LLM. +* **`temperature (float)`**: Temperature for LLM inference, between 0 and 1. +* **`tools (List[dict], optional)`**: Set of tools for function calling. +* **`tool_choice (str, optional)`**: Sets a specific tool to choose. +* **`x402_settlement_mode (x402SettlementMode, optional)`**: Settlement mode for x402 payments. + - PRIVATE: Payment only, no input/output data on-chain (most privacy-preserving). + - BATCH_HASHED: Aggregates inferences into a Merkle tree with input/output hashes and signatures (default, most cost-efficient). + - INDIVIDUAL_FULL: Records input, output, timestamp, and verification on-chain (maximum auditability). + Defaults to BATCH_HASHED. +* **`stream (bool, optional)`**: Whether to stream the response. Default is False. + +**Returns** + +Union[TextGenerationOutput, AsyncGenerator[StreamChunk, None]]: + - If stream=False: TextGenerationOutput with chat_output, transaction_hash, finish_reason, and payment_hash + - If stream=True: Async generator yielding StreamChunk objects + +**`TextGenerationOutput` fields:** + +* **`transaction_hash`**: Blockchain transaction hash. Set to + ``"external"`` for TEE-routed providers. +* **`finish_reason`**: Reason the model stopped generating + (e.g. ``"stop"``, ``"tool_call"``, ``"error"``). + Only populated for chat requests. +* **`chat_output`**: Dictionary with the assistant message returned by + a chat request. Contains ``role``, ``content``, and + optionally ``tool_calls``. +* **`completion_output`**: Raw text returned by a completion request. +* **`payment_hash`**: Payment hash for the x402 transaction. +* **`tee_signature`**: RSA-PSS signature over the response produced + by the TEE enclave. +* **`tee_timestamp`**: ISO-8601 timestamp from the TEE at signing + time. + +**Raises** + +* **`RuntimeError`**: If the inference fails. + +--- + +#### `close()` + +```python +async def close(self) ‑> None +``` +Close the underlying HTTP client. + +--- + +#### `completion()` + +```python +async def completion( + self, + model: `TEE_LLM`, + prompt: str, + max_tokens: int = 100, + stop_sequence: Optional[List[str]] = None, + temperature: float = 0.0, + x402_settlement_mode: `x402SettlementMode` = x402SettlementMode.BATCH_HASHED +) ‑> `TextGenerationOutput` +``` +Perform inference on an LLM model using completions via TEE. + +**Arguments** + +* **`model (TEE_LLM)`**: The model to use (e.g., TEE_LLM.CLAUDE_HAIKU_4_5). +* **`prompt (str)`**: The input prompt for the LLM. +* **`max_tokens (int)`**: Maximum number of tokens for LLM output. Default is 100. +* **`stop_sequence (List[str], optional)`**: List of stop sequences for LLM. Default is None. +* **`temperature (float)`**: Temperature for LLM inference, between 0 and 1. Default is 0.0. +* **`x402_settlement_mode (x402SettlementMode, optional)`**: Settlement mode for x402 payments. + - PRIVATE: Payment only, no input/output data on-chain (most privacy-preserving). + - BATCH_HASHED: Aggregates inferences into a Merkle tree with input/output hashes and signatures (default, most cost-efficient). + - INDIVIDUAL_FULL: Records input, output, timestamp, and verification on-chain (maximum auditability). + Defaults to BATCH_HASHED. + +**Returns** + +TextGenerationOutput: Generated text results including: + - Transaction hash ("external" for TEE providers) + - String of completion output + - Payment hash for x402 transactions + +**`TextGenerationOutput` fields:** + +* **`transaction_hash`**: Blockchain transaction hash. Set to + ``"external"`` for TEE-routed providers. +* **`finish_reason`**: Reason the model stopped generating + (e.g. ``"stop"``, ``"tool_call"``, ``"error"``). + Only populated for chat requests. +* **`chat_output`**: Dictionary with the assistant message returned by + a chat request. Contains ``role``, ``content``, and + optionally ``tool_calls``. +* **`completion_output`**: Raw text returned by a completion request. +* **`payment_hash`**: Payment hash for the x402 transaction. +* **`tee_signature`**: RSA-PSS signature over the response produced + by the TEE enclave. +* **`tee_timestamp`**: ISO-8601 timestamp from the TEE at signing + time. + +**Raises** + +* **`RuntimeError`**: If the inference fails. + +--- + +#### `ensure_opg_approval()` + +```python +def ensure_opg_approval(self, opg_amount: float) ‑> `Permit2ApprovalResult` +``` +Ensure the Permit2 allowance for OPG is at least ``opg_amount``. + +Checks the current Permit2 allowance for the wallet. If the allowance +is already >= the requested amount, returns immediately without sending +a transaction. Otherwise, sends an ERC-20 approve transaction. + +**Arguments** + +* **`opg_amount`**: Minimum number of OPG tokens required (e.g. ``0.05`` + for 0.05 OPG). Must be at least 0.05 OPG. + +**Returns** + +Permit2ApprovalResult: Contains ``allowance_before``, + ``allowance_after``, and ``tx_hash`` (None when no approval + was needed). + +**`Permit2ApprovalResult` fields:** + +* **`allowance_before`**: The Permit2 allowance before the method ran. +* **`allowance_after`**: The Permit2 allowance after the method ran. +* **`tx_hash`**: Transaction hash of the approval, or None if no transaction was needed. + +**Raises** + +* **`ValueError`**: If the OPG amount is less than 0.05. +* **`RuntimeError`**: If the approval transaction fails. + +### `ModelHub` + +Model Hub namespace. + +Provides access to the OpenGradient Model Hub for creating, versioning, +and uploading ML models. Requires email/password authentication. + +#### Constructor + +```python +def __init__(email: Optional[str] = None, password: Optional[str] = None) +``` + +#### Methods + +--- + +#### `create_model()` + +```python +def create_model( + self, + model_name: str, + model_desc: str, + version: str = '1.00' +) ‑> `ModelRepository` +``` +Create a new model with the given model_name and model_desc, and a specified version. + +**Arguments** + +* **`model_name (str)`**: The name of the model. +* **`model_desc (str)`**: The description of the model. +* **`version (str)`**: The version identifier (default is "1.00"). + +**Returns** + +dict: The server response containing model details. + +**Raises** + +* **`CreateModelError`**: If the model creation fails. + +--- + +#### `create_version()` + +```python +def create_version( + self, + model_name: str, + notes: str = '', + is_major: bool = False +) ‑> dict +``` +Create a new version for the specified model. + +**Arguments** + +* **`model_name (str)`**: The unique identifier for the model. +* **`notes (str, optional)`**: Notes for the new version. +* **`is_major (bool, optional)`**: Whether this is a major version update. Defaults to False. + +**Returns** + +dict: The server response containing version details. + +**Raises** + +* **`Exception`**: If the version creation fails. + +--- + +#### `list_files()` + +```python +def list_files(self, model_name: str, version: str) ‑> List[Dict] +``` +List files for a specific version of a model. + +**Arguments** + +* **`model_name (str)`**: The unique identifier for the model. +* **`version (str)`**: The version identifier for the model. + +**Returns** + +List[Dict]: A list of dictionaries containing file information. + +**Raises** + +* **`RuntimeError`**: If the file listing fails. + +--- + +#### `upload()` + +```python +def upload( + self, + model_path: str, + model_name: str, + version: str +) ‑> `FileUploadResult` +``` +Upload a model file to the server. + +**Arguments** + +* **`model_path (str)`**: The path to the model file. +* **`model_name (str)`**: The unique identifier for the model. +* **`version (str)`**: The version identifier for the model. + +**Returns** + +dict: The processed result. + +**Raises** + +* **`RuntimeError`**: If the upload fails. + ### `TEE_LLM` Enum for LLM models available for TEE (Trusted Execution Environment) execution. @@ -338,6 +700,70 @@ The final chunk will have ``is_final=True`` and may include def __init__(_iterator: Union[Iterator[str], AsyncIterator[str]]) ``` +### `Twins` + +Digital twins chat namespace. + +Provides access to digital twin conversations backed by OpenGradient +verifiable inference. Browse available twins at https://twin.fun. + +#### Constructor + +```python +def __init__(api_key: str) +``` + +#### Methods + +--- + +#### `chat()` + +```python +def chat( + self, + twin_id: str, + model: `TEE_LLM`, + messages: List[Dict], + temperature: Optional[float] = None, + max_tokens: Optional[int] = None +) ‑> `TextGenerationOutput` +``` +Chat with a digital twin. + +**Arguments** + +* **`twin_id`**: The unique identifier of the digital twin. +* **`model`**: The model to use for inference (e.g., TEE_LLM.GROK_4_1_FAST_NON_REASONING). +* **`messages`**: The conversation messages to send. +* **`temperature`**: Sampling temperature. Optional. +* **`max_tokens`**: Maximum number of tokens for the response. Optional. + +**Returns** + +TextGenerationOutput: Generated text results including chat_output and finish_reason. + +**`TextGenerationOutput` fields:** + +* **`transaction_hash`**: Blockchain transaction hash. Set to + ``"external"`` for TEE-routed providers. +* **`finish_reason`**: Reason the model stopped generating + (e.g. ``"stop"``, ``"tool_call"``, ``"error"``). + Only populated for chat requests. +* **`chat_output`**: Dictionary with the assistant message returned by + a chat request. Contains ``role``, ``content``, and + optionally ``tool_calls``. +* **`completion_output`**: Raw text returned by a completion request. +* **`payment_hash`**: Payment hash for the x402 transaction. +* **`tee_signature`**: RSA-PSS signature over the response produced + by the TEE enclave. +* **`tee_timestamp`**: ISO-8601 timestamp from the TEE at signing + time. + +**Raises** + +* **`RuntimeError`**: If the request fails. + ### `x402SettlementMode` Settlement modes for x402 payment protocol transactions. diff --git a/examples/README.md b/examples/README.md index e58e88c..cdd321f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,8 +8,7 @@ Before running any examples, ensure you have: 1. **Installed the SDK**: `pip install opengradient` 2. **Set up your credentials**: Configure your OpenGradient account using environment variables: - - `OG_PRIVATE_KEY`: Private key funded with **Base Sepolia OPG tokens** for x402 LLM payments (can be obtained from our [faucet](https://faucet.opengradient.ai)). - - `OG_ALPHA_PRIVATE_KEY`: (Optional) Private key funded with **OpenGradient testnet gas tokens** for Alpha Testnet on-chain inference. Falls back to `OG_PRIVATE_KEY` when not set. + - `OG_PRIVATE_KEY`: Private key funded with **Base Sepolia OPG tokens** for x402 LLM payments (can be obtained from our [faucet](https://faucet.opengradient.ai)). Also used for Alpha Testnet on-chain inference (requires **OpenGradient testnet gas tokens**). - `OG_MODEL_HUB_EMAIL`: (Optional) Your Model Hub email for model uploads - `OG_MODEL_HUB_PASSWORD`: (Optional) Your Model Hub password for model uploads @@ -133,17 +132,22 @@ python examples/twins_chat.py ## Common Patterns -### Initializing the Client +### Initialization -All examples use a similar pattern to initialize the OpenGradient client: +Each sub-client is created independently with the credentials it needs: ```python import os import opengradient as og -og_client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), # Base Sepolia OPG tokens for LLM payments - alpha_private_key=os.environ.get("OG_ALPHA_PRIVATE_KEY"), # Optional: OpenGradient testnet tokens for on-chain inference +# LLM inference (Base Sepolia OPG tokens for x402 payments) +llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) + +# On-chain model inference (OpenGradient testnet gas tokens) +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) + +# Model Hub (email/password auth) +hub = og.ModelHub( email=os.environ.get("OG_MODEL_HUB_EMAIL"), password=os.environ.get("OG_MODEL_HUB_PASSWORD"), ) @@ -154,7 +158,9 @@ og_client = og.Client( Basic inference pattern: ```python -result = og_client.alpha.infer( +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) + +result = alpha.infer( model_cid="your-model-cid", model_input={"input_key": "input_value"}, inference_mode=og.InferenceMode.VANILLA @@ -168,7 +174,9 @@ print(f"Tx hash: {result.transaction_hash}") LLM chat methods are async: ```python -completion = await og_client.llm.chat( +llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) + +completion = await llm.chat( model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[{"role": "user", "content": "Your message"}], ) diff --git a/examples/alpha/README.md b/examples/alpha/README.md index b8c76d1..ccbfc8a 100644 --- a/examples/alpha/README.md +++ b/examples/alpha/README.md @@ -7,8 +7,7 @@ These examples demonstrate features that are currently only available on the **O 1. **Alpha Testnet Access**: You must be connected to the OpenGradient Alpha Testnet 2. **SDK Installation**: `pip install opengradient` 3. **Credentials**: Set up your environment variables: - - `OG_ALPHA_PRIVATE_KEY`: Private key funded with **OpenGradient testnet gas tokens** for on-chain inference. If not set, `OG_PRIVATE_KEY` is used instead. - - `OG_PRIVATE_KEY`: Private key funded with **Base Sepolia OPG tokens** (used as fallback for Alpha if `OG_ALPHA_PRIVATE_KEY` is not set) + - `OG_PRIVATE_KEY`: Private key funded with **OpenGradient testnet gas tokens** for on-chain inference ## Examples diff --git a/examples/alpha/create_workflow.py b/examples/alpha/create_workflow.py index ed6bda0..669d7a7 100644 --- a/examples/alpha/create_workflow.py +++ b/examples/alpha/create_workflow.py @@ -2,7 +2,7 @@ import opengradient as og -og_client = og.Client(private_key=os.environ.get("OG_PRIVATE_KEY")) +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) # Define model input input_query = og.HistoricalInputQuery( @@ -21,7 +21,7 @@ model_cid = "hJD2Ja3akZFt1A2LT-D_1oxOCz_OtuGYw4V9eE1m39M" # Deploy schedule -contract_address = og_client.alpha.new_workflow( +contract_address = alpha.new_workflow( model_cid=model_cid, input_query=input_query, # Input name in ONNX model diff --git a/examples/alpha/run_embeddings_model.py b/examples/alpha/run_embeddings_model.py index 812db91..11832d6 100644 --- a/examples/alpha/run_embeddings_model.py +++ b/examples/alpha/run_embeddings_model.py @@ -2,7 +2,7 @@ import opengradient as og -og_client = og.Client(private_key=os.environ.get("OG_PRIVATE_KEY")) +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) queries = [ "how much protein should a female eat", @@ -14,7 +14,7 @@ "Since you're reading this, you are probably someone from a judo background or someone who is just wondering how judo techniques can be applied under wrestling rules. So without further ado, let's get to the question. Are Judo throws allowed in wrestling? Yes, judo throws are allowed in freestyle and folkstyle wrestling. You only need to be careful to follow the slam rules when executing judo throws. In wrestling, a slam is lifting and returning an opponent to the mat with unnecessary force.", ] -model_embeddings = og_client.alpha.infer( +model_embeddings = alpha.infer( model_cid="intfloat/multilingual-e5-large-instruct", model_input={"queries": queries, "instruction": instruction, "passages": passages}, inference_mode=og.InferenceMode.VANILLA, diff --git a/examples/alpha/run_inference.py b/examples/alpha/run_inference.py index 4a38cc6..d23ed03 100644 --- a/examples/alpha/run_inference.py +++ b/examples/alpha/run_inference.py @@ -2,9 +2,9 @@ import opengradient as og -og_client = og.Client(private_key=os.environ.get("OG_PRIVATE_KEY")) +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) -inference_result = og_client.alpha.infer( +inference_result = alpha.infer( model_cid="hJD2Ja3akZFt1A2LT-D_1oxOCz_OtuGYw4V9eE1m39M", model_input={ "open_high_low_close": [ diff --git a/examples/alpha/use_workflow.py b/examples/alpha/use_workflow.py index c7e2e18..a86ffda 100644 --- a/examples/alpha/use_workflow.py +++ b/examples/alpha/use_workflow.py @@ -2,16 +2,16 @@ import opengradient as og -og_client = og.Client(private_key=os.environ.get("OG_PRIVATE_KEY")) +alpha = og.Alpha(private_key=os.environ.get("OG_PRIVATE_KEY")) -model_output = og_client.alpha.read_workflow_result( +model_output = alpha.read_workflow_result( # This is the workflow contract address that you previously deployed contract_address="0x58Dd93E1aE6B6f21b479b3B2913B055eFD2E74Ee" ) print(f"Latest model prediction: {model_output.numbers}") -model_output_history = og_client.alpha.read_workflow_history( +model_output_history = alpha.read_workflow_history( # This is the workflow contract address that you previously deployed contract_address="0x58Dd93E1aE6B6f21b479b3B2913B055eFD2E74Ee", num_results=5, diff --git a/examples/create_model_repo.py b/examples/create_model_repo.py index 7437c01..9d315f6 100644 --- a/examples/create_model_repo.py +++ b/examples/create_model_repo.py @@ -2,12 +2,9 @@ import opengradient as og -og_client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), +hub = og.ModelHub( email=os.environ.get("OG_MODEL_HUB_EMAIL"), password=os.environ.get("OG_MODEL_HUB_PASSWORD"), ) -og_client.model_hub.create_model( - model_name="example-model", model_desc="An example machine learning model for demonstration purposes", version="1.0.0" -) +hub.create_model(model_name="example-model", model_desc="An example machine learning model for demonstration purposes", version="1.0.0") diff --git a/examples/langchain_react_agent.py b/examples/langchain_react_agent.py index 6dc0a53..ebd7c03 100644 --- a/examples/langchain_react_agent.py +++ b/examples/langchain_react_agent.py @@ -15,13 +15,15 @@ import opengradient as og +private_key = os.environ.get("OG_PRIVATE_KEY") + # One-time Permit2 approval for OPG spending (idempotent) -client = og.init(private_key=os.environ.get("OG_PRIVATE_KEY")) -client.llm.ensure_opg_approval(opg_amount=5) +llm_client = og.LLM(private_key=private_key) +llm_client.ensure_opg_approval(opg_amount=5) # Create the OpenGradient LangChain adapter llm = og.agents.langchain_adapter( - private_key=os.environ.get("OG_PRIVATE_KEY"), + private_key=private_key, model_cid=og.TEE_LLM.GPT_4_1_2025_04_14, max_tokens=300, x402_settlement_mode=og.x402SettlementMode.INDIVIDUAL_FULL, diff --git a/examples/llm_chat.py b/examples/llm_chat.py index 7b3c3f7..01ff652 100644 --- a/examples/llm_chat.py +++ b/examples/llm_chat.py @@ -9,16 +9,14 @@ async def main(): - client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), - ) - client.llm.ensure_opg_approval(opg_amount=2) + llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) + llm.ensure_opg_approval(opg_amount=2) messages = [ {"role": "user", "content": "What is the capital of France?"}, ] - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GEMINI_2_5_FLASH, messages=messages, max_tokens=300, diff --git a/examples/llm_chat_streaming.py b/examples/llm_chat_streaming.py index 5ab686d..8e34d55 100644 --- a/examples/llm_chat_streaming.py +++ b/examples/llm_chat_streaming.py @@ -5,10 +5,8 @@ async def main(): - client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), - ) - client.llm.ensure_opg_approval(opg_amount=1) + llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) + llm.ensure_opg_approval(opg_amount=1) messages = [ {"role": "user", "content": "What is Python?"}, @@ -16,7 +14,7 @@ async def main(): {"role": "user", "content": "What makes it good for beginners?"}, ] - stream = await client.llm.chat( + stream = await llm.chat( model=og.TEE_LLM.GPT_4_1_2025_04_14, messages=messages, x402_settlement_mode=og.x402SettlementMode.INDIVIDUAL_FULL, diff --git a/examples/llm_tool_calling.py b/examples/llm_tool_calling.py index bd1d600..a5a4990 100644 --- a/examples/llm_tool_calling.py +++ b/examples/llm_tool_calling.py @@ -13,10 +13,7 @@ async def main(): - # Initialize client - client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), - ) + llm = og.LLM(private_key=os.environ.get("OG_PRIVATE_KEY")) # Define a simple tool tools = [ @@ -54,7 +51,7 @@ async def main(): ] # One-time Permit2 approval for OPG spending (idempotent) - client.llm.ensure_opg_approval(opg_amount=5) + llm.ensure_opg_approval(opg_amount=5) print("Testing Gemini tool calls...") print(f"Model: {og.TEE_LLM.GEMINI_2_5_FLASH_LITE}") @@ -62,7 +59,7 @@ async def main(): print(f"Tools: {tools}") print("-" * 50) - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GEMINI_2_5_FLASH_LITE, messages=messages, tools=tools, diff --git a/examples/twins_chat.py b/examples/twins_chat.py index 315a0d1..786c9c4 100644 --- a/examples/twins_chat.py +++ b/examples/twins_chat.py @@ -5,17 +5,14 @@ import opengradient as og -client = og.init( - private_key=os.environ.get("OG_PRIVATE_KEY"), - twins_api_key=os.environ.get("TWINS_API_KEY"), -) +twins = og.Twins(api_key=os.environ.get("TWINS_API_KEY")) # Chat with Elon Musk print("--------------------------------") print("Chat with Elon Musk") print("--------------------------------") -elon = client.twins.chat( +elon = twins.chat( twin_id="0x1abd463fd6244be4a1dc0f69e0b70cd5", model=og.TEE_LLM.GROK_4_1_FAST_NON_REASONING, messages=[{"role": "user", "content": "What do you think about AI?"}], @@ -28,7 +25,7 @@ print("Chat with Donald Trump") print("--------------------------------") -trump = client.twins.chat( +trump = twins.chat( twin_id="0x66ae99aae4324ed580b2787ac5e811f6", model=og.TEE_LLM.GROK_4_1_FAST_NON_REASONING, messages=[{"role": "user", "content": "What's your plan for America?"}], diff --git a/examples/upload_model_to_hub.py b/examples/upload_model_to_hub.py index a1f2822..631f42c 100644 --- a/examples/upload_model_to_hub.py +++ b/examples/upload_model_to_hub.py @@ -2,13 +2,12 @@ import opengradient as og -og_client = og.Client( - private_key=os.environ.get("OG_PRIVATE_KEY"), +hub = og.ModelHub( email=os.environ.get("OG_MODEL_HUB_EMAIL"), password=os.environ.get("OG_MODEL_HUB_PASSWORD"), ) -model_repo = og_client.model_hub.create_model(model_name="Demo_Custom_Model_Adam", model_desc="My custom model for demoing Model Hub") -upload_result = og_client.model_hub.upload(model_name=model_repo.name, version=model_repo.initialVersion, model_path="./path/to/model.onnx") +model_repo = hub.create_model(model_name="Demo_Custom_Model_Adam", model_desc="My custom model for demoing Model Hub") +upload_result = hub.upload(model_name=model_repo.name, version=model_repo.initialVersion, model_path="./path/to/model.onnx") print(f"Uploaded model, use following CID to access: {upload_result.modelCid}") diff --git a/integrationtest/agent/test_agent.py b/integrationtest/agent/test_agent.py index bd9db99..8652911 100644 --- a/integrationtest/agent/test_agent.py +++ b/integrationtest/agent/test_agent.py @@ -7,9 +7,9 @@ from pydantic import BaseModel, Field import opengradient as og -from opengradient import LLM, InferenceResult +from opengradient import TEE_LLM, InferenceResult +from opengradient.agents import OpenGradientChatModel from opengradient.alphasense import ToolType, create_read_workflow_tool, create_run_model_tool -from opengradient.llm import OpenGradientChatModel class TestLLM(unittest.TestCase): @@ -19,8 +19,8 @@ def setUp(self): if not private_key: raise ValueError("PRIVATE_KEY environment variable is not set") - self.client = og.Client(private_key=private_key) - self.llm = OpenGradientChatModel(private_key=private_key, model_cid=LLM.CLAUDE_SONNET_4_6) + self.alpha = og.Alpha(private_key=private_key) + self.llm = OpenGradientChatModel(private_key=private_key, model_cid=TEE_LLM.CLAUDE_SONNET_4_6) def test_simple_completion(self): message = self.llm.invoke("say 'hello'. literally") @@ -39,7 +39,7 @@ def get_balance(): def test_read_workflow(self): # Read current workflow result - workflow_result = self.client.alpha.read_workflow_result(contract_address="0x6e0641925b845A1ca8aA9a890C4DEF388E9197e0") + workflow_result = self.alpha.read_workflow_result(contract_address="0x6e0641925b845A1ca8aA9a890C4DEF388E9197e0") expected_result = str(workflow_result.numbers["Y"][0]) btc_workflow_tool = create_read_workflow_tool( @@ -47,7 +47,7 @@ def test_read_workflow(self): workflow_contract_address="0x6e0641925b845A1ca8aA9a890C4DEF388E9197e0", tool_name="ETH_Price_Forecast", tool_description="Reads latest forecast for ETH price", - alpha=self.client.alpha, + alpha=self.alpha, output_formatter=lambda x: x, ) @@ -87,12 +87,12 @@ def output_formatter(inference_result: InferenceResult): tool_name="One_hour_volatility_ETH_USDT", model_input_provider=model_input_provider, model_output_formatter=output_formatter, - inference=self.client.alpha, + inference=self.alpha, tool_description="This tool measures the live 1 hour volatility for the trading pair ETH/USDT.", inference_mode=og.InferenceMode.VANILLA, ) - expected_result = self.client.alpha.infer( + expected_result = self.alpha.infer( inference_mode=og.InferenceMode.VANILLA, model_cid="QmRhcpDXfYCKsimTmJYrAVM4Bbvck59Zb2onj3MHv9Kw5N", model_input=model_input ) formatted_expected_result = format(float(expected_result.model_output["Y"].item()), ".3%") @@ -193,14 +193,14 @@ def output_formatter(inference_result: InferenceResult): tool_name="Return_volatility_tool", model_input_provider=model_input_provider, model_output_formatter=output_formatter, - inference=self.client.alpha, + inference=self.alpha, tool_input_schema=InputSchema, tool_description="This tool takes a token and measures the return volatility (standard deviation of returns).", inference_mode=og.InferenceMode.VANILLA, ) # Test option ETH - expected_result_eth = self.client.alpha.infer( + expected_result_eth = self.alpha.infer( inference_mode=og.InferenceMode.VANILLA, model_cid="QmZdSfHWGJyzBiB2K98egzu3MypPcv4R1ASypUxwZ1MFUG", model_input=eth_model_input ) formatted_expected_result_eth = format(float(expected_result_eth.model_output["std"].item()), ".3%") @@ -215,7 +215,7 @@ def output_formatter(inference_result: InferenceResult): self.assertIn(formatted_expected_result_eth, list(events)[-1]["messages"][-1].content) # Test option BTC - expected_result_btc = self.client.alpha.infer( + expected_result_btc = self.alpha.infer( inference_mode=og.InferenceMode.VANILLA, model_cid="QmZdSfHWGJyzBiB2K98egzu3MypPcv4R1ASypUxwZ1MFUG", model_input=btc_model_input ) formatted_expected_result_btc = format(float(expected_result_btc.model_output["std"].item()), ".3%") diff --git a/integrationtest/workflow_models/test_workflow_models.py b/integrationtest/workflow_models/test_workflow_models.py index d20009f..1e4b1fd 100644 --- a/integrationtest/workflow_models/test_workflow_models.py +++ b/integrationtest/workflow_models/test_workflow_models.py @@ -37,7 +37,7 @@ def setUp(self): if not private_key: raise ValueError("PRIVATE_KEY environment variable is not set") - self.client = og.Client(private_key=private_key) + self.alpha = og.Alpha(private_key=private_key) def test_models(self): model_functions = { @@ -55,9 +55,9 @@ def test_models(self): } for function, model_info in model_functions.items(): - workflow_result = function(self.client.alpha) + workflow_result = function(self.alpha) expected_result = format( - float(self.client.alpha.read_workflow_result(model_info.address).numbers[model_info.output_name].item()), ".10%" + float(self.alpha.read_workflow_result(model_info.address).numbers[model_info.output_name].item()), ".10%" ) print(function) print("Workflow result: ", workflow_result) diff --git a/src/opengradient/__init__.py b/src/opengradient/__init__.py index 4758ccb..bcbe2a6 100644 --- a/src/opengradient/__init__.py +++ b/src/opengradient/__init__.py @@ -17,13 +17,13 @@ import asyncio import opengradient as og -client = og.init(private_key="0x...") +llm = og.LLM(private_key="0x...") # One-time approval (idempotent — skips if allowance is already sufficient) -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) # Chat with an LLM (TEE-verified) -response = asyncio.run(client.llm.chat( +response = asyncio.run(llm.chat( model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[{"role": "user", "content": "Hello!"}], max_tokens=200, @@ -32,7 +32,7 @@ # Stream a response async def stream_example(): - stream = await client.llm.chat( + stream = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Explain TEE in one paragraph."}], max_tokens=300, @@ -45,7 +45,8 @@ async def stream_example(): asyncio.run(stream_example()) # Run on-chain ONNX model inference -result = client.alpha.infer( +alpha = og.Alpha(private_key="0x...") +result = alpha.infer( model_cid="your_model_cid", inference_mode=og.InferenceMode.VANILLA, model_input={"input": [1.0, 2.0, 3.0]}, @@ -55,19 +56,12 @@ async def stream_example(): ## Private Keys -The SDK operates across two chains. You can use a single key for both, or provide separate keys: +The SDK operates across two chains. Use separate keys for each: -- **``private_key``** -- pays for LLM inference via x402 on **Base Sepolia** (requires OPG tokens) -- **``alpha_private_key``** *(optional)* -- pays gas for Alpha Testnet on-chain inference on the **OpenGradient network** (requires testnet gas tokens). Falls back to ``private_key`` when omitted. +- **LLM** (``og.LLM``) -- pays for inference via x402 on **Base Sepolia** (requires OPG tokens) +- **Alpha** (``og.Alpha``) -- pays gas for on-chain inference on the **OpenGradient network** (requires testnet gas tokens) -```python -# Separate keys for each chain -client = og.init(private_key="0xBASE_KEY...", alpha_private_key="0xALPHA_KEY...") -``` - -## Client Namespaces - -The `opengradient.client.Client` object exposes four namespaces: +## Modules - **`opengradient.client.llm`** -- Verifiable LLM chat and completion via TEE-verified execution with x402 payments (Base Sepolia OPG tokens) - **`opengradient.client.alpha`** -- On-chain ONNX model inference, workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens) @@ -77,14 +71,9 @@ async def stream_example(): ## Model Hub (requires email auth) ```python -client = og.init( - private_key="0x...", - email="you@example.com", - password="...", -) - -repo = client.model_hub.create_model("my-model", "A price prediction model") -client.model_hub.upload("model.onnx", repo.name, repo.initialVersion) +hub = og.ModelHub(email="you@example.com", password="...") +repo = hub.create_model("my-model", "A price prediction model") +hub.upload("model.onnx", repo.name, repo.initialVersion) ``` ## Framework Integrations @@ -92,10 +81,8 @@ async def stream_example(): The SDK includes adapters for popular AI frameworks -- see the `agents` submodule for LangChain and OpenAI integration. """ -from typing import Optional - from . import agents, alphasense -from .client import Client +from .client import LLM, Alpha, ModelHub, Twins from .types import ( TEE_LLM, CandleOrder, @@ -112,56 +99,11 @@ async def stream_example(): x402SettlementMode, ) -global_client: Optional[Client] = None -"""Global client instance. Set by calling `init()`.""" - - -def init( - private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - **kwargs, -) -> Client: - """Initialize the global OpenGradient client. - - This is the recommended way to get started. It creates a `Client` instance - and stores it as the global client for convenience. - - Args: - private_key: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. - alpha_private_key: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. - email: Email for Model Hub authentication. Optional. - password: Password for Model Hub authentication. Optional. - **kwargs: Additional arguments forwarded to `Client`. - - Returns: - The newly created `Client` instance. - - Usage: - import opengradient as og - client = og.init(private_key="0x...") - client.llm.ensure_opg_approval(opg_amount=5) - response = await client.llm.chat(model=og.TEE_LLM.GPT_5, messages=[...]) - """ - global global_client - global_client = Client( - private_key=private_key, - alpha_private_key=alpha_private_key, - email=email, - password=password, - **kwargs, - ) - return global_client - - __all__ = [ - "Client", - "global_client", - "init", + "LLM", + "Alpha", + "ModelHub", + "Twins", "TEE_LLM", "InferenceMode", "HistoricalInputQuery", @@ -188,5 +130,4 @@ def init( "CandleType": False, "HistoricalInputQuery": False, "SchedulerParams": False, - "global_client": False, } diff --git a/src/opengradient/agents/og_langchain.py b/src/opengradient/agents/og_langchain.py index c0ba490..4f238a5 100644 --- a/src/opengradient/agents/og_langchain.py +++ b/src/opengradient/agents/og_langchain.py @@ -22,7 +22,7 @@ from langchain_core.tools import BaseTool from pydantic import PrivateAttr -from ..client import Client +from ..client.llm import LLM from ..types import TEE_LLM, x402SettlementMode __all__ = ["OpenGradientChatModel"] @@ -74,7 +74,7 @@ class OpenGradientChatModel(BaseChatModel): max_tokens: int = 300 x402_settlement_mode: Optional[str] = x402SettlementMode.BATCH_HASHED - _client: Client = PrivateAttr() + _llm: LLM = PrivateAttr() _tools: List[Dict] = PrivateAttr(default_factory=list) def __init__( @@ -91,7 +91,7 @@ def __init__( x402_settlement_mode=x402_settlement_mode, **kwargs, ) - self._client = Client(private_key=private_key) + self._llm = LLM(private_key=private_key) @property def _llm_type(self) -> str: @@ -167,7 +167,7 @@ def _generate( raise ValueError(f"Unexpected message type: {message}") chat_output = asyncio.run( - self._client.llm.chat( + self._llm.chat( model=self.model_cid, messages=sdk_messages, stop_sequence=stop, diff --git a/src/opengradient/alphasense/read_workflow_tool.py b/src/opengradient/alphasense/read_workflow_tool.py index d440aba..2199c37 100644 --- a/src/opengradient/alphasense/read_workflow_tool.py +++ b/src/opengradient/alphasense/read_workflow_tool.py @@ -59,14 +59,7 @@ def create_read_workflow_tool( """ if alpha is None: - import opengradient as og - - if og.global_client is None: - raise ValueError( - "No alpha instance provided and no global client initialized. " - "Either pass alpha=client.alpha or call opengradient.init() first." - ) - alpha = og.global_client.alpha + raise ValueError("No alpha instance provided. Pass alpha=og.Alpha(private_key=...).") # define runnable def read_workflow(): diff --git a/src/opengradient/alphasense/run_model_tool.py b/src/opengradient/alphasense/run_model_tool.py index 6153598..ffbf369 100644 --- a/src/opengradient/alphasense/run_model_tool.py +++ b/src/opengradient/alphasense/run_model_tool.py @@ -112,14 +112,7 @@ def create_run_model_tool( """ if inference is None: - import opengradient as og - - if og.global_client is None: - raise ValueError( - "No inference instance provided and no global client initialized. " - "Either pass inference=client.alpha or call opengradient.init() first." - ) - inference = og.global_client.alpha + raise ValueError("No inference instance provided. Pass inference=og.Alpha(private_key=...).") def model_executor(**llm_input): # Pass LLM input arguments (formatted based on tool_input_schema) as parameters into model_input_provider diff --git a/src/opengradient/cli.py b/src/opengradient/cli.py index a7a1c4f..6dbccef 100644 --- a/src/opengradient/cli.py +++ b/src/opengradient/cli.py @@ -12,17 +12,15 @@ import click from .account import EthAccount, generate_eth_account -from .client import Client -from .defaults import ( - DEFAULT_API_URL, - DEFAULT_BLOCKCHAIN_EXPLORER, - DEFAULT_HUB_SIGNUP_URL, - DEFAULT_INFERENCE_CONTRACT_ADDRESS, - DEFAULT_OG_FAUCET_URL, - DEFAULT_RPC_URL, -) +from .client.alpha import Alpha +from .client.llm import LLM +from .client.model_hub import ModelHub from .types import InferenceMode, x402SettlementMode +DEFAULT_BLOCKCHAIN_EXPLORER = "https://explorer.opengradient.ai/tx/" +DEFAULT_OG_FAUCET_URL = "https://faucet.opengradient.ai/?address=" +DEFAULT_HUB_SIGNUP_URL = "https://hub.opengradient.ai/signup" + OG_CONFIG_FILE = Path.home() / ".opengradient_config.json" @@ -140,12 +138,12 @@ def cli(ctx): if all(key in ctx.obj for key in ["private_key"]): try: - ctx.obj["client"] = Client( - private_key=ctx.obj["private_key"], - alpha_private_key=ctx.obj.get("alpha_private_key"), - rpc_url=DEFAULT_RPC_URL, - api_url=DEFAULT_API_URL, - contract_address=DEFAULT_INFERENCE_CONTRACT_ADDRESS, + private_key = ctx.obj["private_key"] + alpha_private_key = ctx.obj.get("alpha_private_key") or private_key + + ctx.obj["llm"] = LLM(private_key=private_key) + ctx.obj["alpha"] = Alpha(private_key=alpha_private_key) + ctx.obj["model_hub"] = ModelHub( email=ctx.obj.get("email"), password=ctx.obj.get("password"), ) @@ -225,10 +223,10 @@ def create_model_repo(obj, repo_name: str, description: str): opengradient create-model-repo --name "my_new_model" --description "A new model for XYZ task" opengradient create-model-repo -n "my_new_model" -d "A new model for XYZ task" """ - client: Client = obj["client"] + model_hub: ModelHub = obj["model_hub"] try: - result = client.create_model(repo_name, description) + result = model_hub.create_model(repo_name, description) click.echo(f"Model repository created successfully: {result}") except Exception as e: click.echo(f"Error creating model: {str(e)}") @@ -251,10 +249,10 @@ def create_version(obj, repo_name: str, notes: str, major: bool): opengradient create-version --repo my_model_repo --notes "Added new feature X" --major opengradient create-version -r my_model_repo -n "Bug fixes" """ - client: Client = obj["client"] + model_hub: ModelHub = obj["model_hub"] try: - result = client.create_version(repo_name, notes, major) + result = model_hub.create_version(repo_name, notes, major) click.echo(f"New version created successfully: {result}") except Exception as e: click.echo(f"Error creating version: {str(e)}") @@ -279,10 +277,10 @@ def upload_file(obj, file_path: Path, repo_name: str, version: str): opengradient upload-file path/to/model.onnx --repo my_model_repo --version 0.01 opengradient upload-file path/to/model.onnx -r my_model_repo -v 0.01 """ - client: Client = obj["client"] + model_hub: ModelHub = obj["model_hub"] try: - result = client.upload(file_path, repo_name, version) + result = model_hub.upload(file_path, repo_name, version) click.echo(f"File uploaded successfully: {result}") except Exception as e: click.echo(f"Error uploading model: {str(e)}") @@ -314,7 +312,7 @@ def infer(ctx, model_cid: str, inference_mode: str, input_data, input_file: Path opengradient infer --model Qm... --mode VANILLA --input '{"key": "value"}' opengradient infer -m Qm... -i ZKML -f input_data.json """ - client: Client = ctx.obj["client"] + alpha: Alpha = ctx.obj["alpha"] try: if not input_data and not input_file: @@ -335,7 +333,7 @@ def infer(ctx, model_cid: str, inference_mode: str, input_data, input_file: Path model_input = json.load(file) click.echo(f'Running {inference_mode} inference for model "{model_cid}"') - inference_result = client.alpha.infer(model_cid=model_cid, inference_mode=InferenceModes[inference_mode], model_input=model_input) + inference_result = alpha.infer(model_cid=model_cid, inference_mode=InferenceModes[inference_mode], model_input=model_input) click.echo() # Add a newline for better spacing click.secho("✅ Transaction successful", fg="green", bold=True) @@ -400,13 +398,13 @@ def completion( opengradient completion --model anthropic/claude-haiku-4-5 --prompt "Hello, how are you?" --max-tokens 50 opengradient completion --model openai/gpt-5 --prompt "Write a haiku" --max-tokens 100 """ - client: Client = ctx.obj["client"] + llm: LLM = ctx.obj["llm"] try: click.echo(f'Running TEE LLM completion for model "{model_cid}"\n') completion_output = asyncio.run( - client.llm.completion( + llm.completion( model=model_cid, prompt=prompt, max_tokens=max_tokens, @@ -526,7 +524,7 @@ def chat( # With streaming opengradient chat --model anthropic/claude-haiku-4-5 --messages '[{"role":"user","content":"How are clouds formed?"}]' --max-tokens 250 --stream """ - client: Client = ctx.obj["client"] + llm: LLM = ctx.obj["llm"] try: click.echo(f'Running TEE LLM chat for model "{model_cid}"\n') @@ -587,7 +585,7 @@ def chat( parsed_tools = None result = asyncio.run( - client.llm.chat( + llm.chat( model=model_cid, messages=messages, max_tokens=max_tokens, @@ -781,7 +779,7 @@ def create_account_impl() -> EthAccount: @click.option("--repo", "-r", "repo_name", required=True, help="Name of the model repository") @click.option("--version", "-v", required=True, help='Version of the model (e.g., "0.01")') @click.pass_obj -def list_files(client: Client, repo_name: str, version: str): +def list_files(obj, repo_name: str, version: str): """ List files for a specific version of a model repository. @@ -793,8 +791,9 @@ def list_files(client: Client, repo_name: str, version: str): opengradient list-files --repo my_model_repo --version 0.01 opengradient list-files -r my_model_repo -v 0.01 """ + model_hub: ModelHub = obj["model_hub"] try: - files = client.list_files(repo_name, version) + files = model_hub.list_files(repo_name, version) if files: click.echo(f"Files for {repo_name} version {version}:") for file in files: @@ -820,10 +819,9 @@ def generate_image(ctx, model: str, prompt: str, output_path: Path, width: int, opengradient generate-image --model stabilityai/stable-diffusion-xl-base-1.0 --prompt "A beautiful sunset over mountains" --output-path sunset.png """ - client: Client = ctx.obj["client"] try: click.echo(f'Generating image with model "{model}"') - image_data = client.generate_image(model_cid=model, prompt=prompt, width=width, height=height) + raise NotImplementedError("Image generation is not yet supported.") # Save the image with open(output_path, "wb") as f: diff --git a/src/opengradient/client/__init__.py b/src/opengradient/client/__init__.py index 5135740..e2e0668 100644 --- a/src/opengradient/client/__init__.py +++ b/src/opengradient/client/__init__.py @@ -1,62 +1,43 @@ """ -OpenGradient Client -- the central entry point to all SDK services. +OpenGradient Client -- service modules for the SDK. -## Overview - -The `opengradient.client.client.Client` class provides unified access to four service namespaces: +## Modules - **`opengradient.client.llm`** -- LLM chat and text completion with TEE-verified execution and x402 payment settlement (Base Sepolia OPG tokens) - **`opengradient.client.model_hub`** -- Model repository management: create, version, and upload ML models - **`opengradient.client.alpha`** -- Alpha Testnet features: on-chain ONNX model inference (VANILLA, TEE, ZKML modes), workflow deployment, and scheduled ML model execution (OpenGradient testnet gas tokens) - **`opengradient.client.twins`** -- Digital twins chat via OpenGradient verifiable inference -## Private Keys - -The SDK operates across two chains: - -- **`private_key`** -- used for LLM inference (``client.llm``). Pays via x402 on **Base Sepolia** with OPG tokens. -- **`alpha_private_key`** *(optional)* -- used for Alpha Testnet features (``client.alpha``). Pays gas on the **OpenGradient network** with testnet tokens. Falls back to ``private_key`` when omitted. - ## Usage ```python import opengradient as og -# Single key for both chains (backward compatible) -client = og.init(private_key="0x...") - -# Separate keys: Base Sepolia OPG for LLM, OpenGradient testnet gas for Alpha -client = og.init(private_key="0xLLM_KEY...", alpha_private_key="0xALPHA_KEY...") - -# One-time approval (idempotent — skips if allowance is already sufficient) -client.llm.ensure_opg_approval(opg_amount=5) - -# LLM chat (TEE-verified, streamed) -stream = await client.llm.chat( - model=og.TEE_LLM.CLAUDE_HAIKU_4_5, - messages=[{"role": "user", "content": "Hello!"}], - max_tokens=200, - stream=True, -) -async for chunk in stream: - if chunk.choices[0].delta.content: - print(chunk.choices[0].delta.content, end="") - -# On-chain model inference -result = client.alpha.infer( - model_cid="your_model_cid", - inference_mode=og.InferenceMode.VANILLA, - model_input={"input": [1.0, 2.0, 3.0]}, -) +# LLM inference (Base Sepolia OPG tokens) +llm = og.LLM(private_key="0x...") +llm.ensure_opg_approval(opg_amount=5) +result = await llm.chat(model=og.TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...]) + +# On-chain model inference (OpenGradient testnet gas tokens) +alpha = og.Alpha(private_key="0x...") +result = alpha.infer(model_cid, og.InferenceMode.VANILLA, model_input) # Model Hub (requires email auth) -client = og.init(private_key="0x...", email="you@example.com", password="...") -repo = client.model_hub.create_model("my-model", "A price prediction model") +hub = og.ModelHub(email="you@example.com", password="...") +repo = hub.create_model("my-model", "A price prediction model") ``` """ -from .client import Client +from .alpha import Alpha +from .llm import LLM +from .model_hub import ModelHub +from .twins import Twins -__all__ = ["Client"] +__all__ = ["LLM", "Alpha", "ModelHub", "Twins"] -__pdoc__ = {} +__pdoc__ = { + "Alpha": False, + "LLM": False, + "ModelHub": False, + "Twins": False, +} diff --git a/src/opengradient/client/alpha.py b/src/opengradient/client/alpha.py index 5a489cb..1a31e33 100644 --- a/src/opengradient/client/alpha.py +++ b/src/opengradient/client/alpha.py @@ -17,11 +17,15 @@ from web3.exceptions import ContractLogicError from web3.logs import DISCARD -from ..defaults import DEFAULT_SCHEDULER_ADDRESS from ..types import HistoricalInputQuery, InferenceMode, InferenceResult, ModelOutput, SchedulerParams from ._conversions import convert_array_to_model_output, convert_to_model_input, convert_to_model_output from ._utils import get_abi, get_bin, run_with_retry +DEFAULT_RPC_URL = "https://ogevmdevnet.opengradient.ai" +DEFAULT_API_URL = "https://sdk-devnet.opengradient.ai" +DEFAULT_INFERENCE_CONTRACT_ADDRESS = "0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE" +DEFAULT_SCHEDULER_ADDRESS = "0x7179724De4e7FF9271FA40C0337c7f90C0508eF6" + # How much time we wait for txn to be included in chain INFERENCE_TX_TIMEOUT = 120 REGULAR_TX_TIMEOUT = 30 @@ -37,21 +41,21 @@ class Alpha: including on-chain ONNX model inference, workflow deployment, and execution. Usage: - client = og.Client(...) - result = client.alpha.infer(model_cid, InferenceMode.VANILLA, model_input) - result = client.alpha.new_workflow(model_cid, input_query, input_tensor_name) + alpha = og.Alpha(private_key="0x...") + result = alpha.infer(model_cid, InferenceMode.VANILLA, model_input) + result = alpha.new_workflow(model_cid, input_query, input_tensor_name) """ def __init__( self, - blockchain: Web3, - wallet_account: LocalAccount, - inference_hub_contract_address: str, - api_url: str, + private_key: str, + rpc_url: str = DEFAULT_RPC_URL, + inference_contract_address: str = DEFAULT_INFERENCE_CONTRACT_ADDRESS, + api_url: str = DEFAULT_API_URL, ): - self._blockchain = blockchain - self._wallet_account = wallet_account - self._inference_hub_contract_address = inference_hub_contract_address + self._blockchain = Web3(Web3.HTTPProvider(rpc_url)) + self._wallet_account: LocalAccount = self._blockchain.eth.account.from_key(private_key) + self._inference_hub_contract_address = inference_contract_address self._api_url = api_url self._inference_abi = None self._precompile_abi = None diff --git a/src/opengradient/client/client.py b/src/opengradient/client/client.py deleted file mode 100644 index 514a9bb..0000000 --- a/src/opengradient/client/client.py +++ /dev/null @@ -1,137 +0,0 @@ -"""Main Client class that unifies all OpenGradient service namespaces.""" - -from typing import Optional - -from web3 import Web3 - -from ..defaults import ( - DEFAULT_API_URL, - DEFAULT_INFERENCE_CONTRACT_ADDRESS, - DEFAULT_RPC_URL, - DEFAULT_TEE_REGISTRY_ADDRESS, -) -from .alpha import Alpha -from .llm import LLM -from .model_hub import ModelHub -from .twins import Twins - - -class Client: - """ - Main OpenGradient SDK client. - - Provides unified access to all OpenGradient services including LLM inference, - on-chain model inference, and the Model Hub. - - The client operates across two chains: - - - **LLM inference** (``client.llm``) settles via x402 on **Base Sepolia** - using OPG tokens (funded by ``private_key``). - - **Alpha Testnet** (``client.alpha``) runs on the **OpenGradient network** - using testnet gas tokens (funded by ``alpha_private_key``, or ``private_key`` - when not provided). - - Usage: - client = og.Client(private_key="0x...") - client = og.Client(private_key="0xBASE_KEY", alpha_private_key="0xALPHA_KEY") - client.llm.ensure_opg_approval(opg_amount=5) # one-time Permit2 approval - result = await client.llm.chat(model=TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...]) - result = client.alpha.infer(model_cid, InferenceMode.VANILLA, input_data) - """ - - model_hub: ModelHub - """Model Hub for creating, versioning, and uploading ML models.""" - - llm: LLM - """LLM chat and completion via TEE-verified execution.""" - - alpha: Alpha - """Alpha Testnet features including on-chain inference, workflow management, and ML model execution.""" - - twins: Optional[Twins] - """Digital twins chat via OpenGradient verifiable inference. ``None`` when no ``twins_api_key`` is provided.""" - - def __init__( - self, - private_key: str, - alpha_private_key: Optional[str] = None, - email: Optional[str] = None, - password: Optional[str] = None, - twins_api_key: Optional[str] = None, - rpc_url: str = DEFAULT_RPC_URL, - api_url: str = DEFAULT_API_URL, - inference_contract_address: str = DEFAULT_INFERENCE_CONTRACT_ADDRESS, - llm_server_url: Optional[str] = None, - tee_registry_address: str = DEFAULT_TEE_REGISTRY_ADDRESS, - ): - """ - Initialize the OpenGradient client. - - The SDK uses two different chains. LLM inference (``client.llm``) settles - via x402 on **Base Sepolia** using OPG tokens, while Alpha Testnet features - (``client.alpha``) run on the **OpenGradient network** using testnet gas tokens. - You can supply a separate ``alpha_private_key`` so each chain uses its own - funded wallet. When omitted, ``private_key`` is used for both. - - By default the LLM server endpoint and its TLS certificate are fetched from - the on-chain TEE Registry, which stores certificates that were verified during - enclave attestation. You can override the endpoint by passing - ``llm_server_url`` explicitly (the system CA bundle is used for that URL). - - Args: - private_key: Private key whose wallet holds **Base Sepolia OPG tokens** - for x402 LLM payments. - alpha_private_key: Private key whose wallet holds **OpenGradient testnet - gas tokens** for on-chain inference. Optional -- falls back to - ``private_key`` for backward compatibility. - email: Email for Model Hub authentication. Must be provided together - with ``password``. - password: Password for Model Hub authentication. Must be provided - together with ``email``. - twins_api_key: API key for digital twins chat (twin.fun). Optional. - rpc_url: RPC URL for the OpenGradient Alpha Testnet. - api_url: API URL for the OpenGradient API. - inference_contract_address: Inference contract address on the - OpenGradient Alpha Testnet. - llm_server_url: Override the LLM server URL instead of using the - registry-discovered endpoint. When set, the TLS certificate is - validated against the system CA bundle rather than the registry. - tee_registry_address: Address of the TEERegistry contract used to - discover active LLM proxy endpoints and their verified TLS certs. - """ - if (email is None) != (password is None): - raise ValueError("Both 'email' and 'password' must be provided together for Model Hub authentication.") - - w3 = Web3(Web3.HTTPProvider(rpc_url)) - account = w3.eth.account.from_key(private_key) - - # Use a separate account for Alpha Testnet when provided - alpha_account = w3.eth.account.from_key(alpha_private_key) if alpha_private_key is not None else account - - self.model_hub = ModelHub(email=email, password=password) - - self.llm = LLM( - wallet_account=account, - llm_server_url=llm_server_url, - rpc_url=rpc_url, - tee_registry_address=tee_registry_address, - ) - - self.alpha = Alpha( - blockchain=w3, - wallet_account=alpha_account, - inference_hub_contract_address=inference_contract_address, - api_url=api_url, - ) - - self.twins = Twins(api_key=twins_api_key) if twins_api_key is not None else None - - async def close(self) -> None: - """Close underlying SDK resources.""" - await self.llm.close() - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc, tb): - await self.close() diff --git a/src/opengradient/client/llm.py b/src/opengradient/client/llm.py index 0ab2f17..a345caa 100644 --- a/src/opengradient/client/llm.py +++ b/src/opengradient/client/llm.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import AsyncGenerator, Dict, List, Optional, Union +from eth_account import Account from eth_account.account import LocalAccount from x402v2 import x402Client as x402Clientv2 from x402v2.http.clients import x402HttpxClient as x402HttpxClientv2 @@ -19,6 +20,9 @@ logger = logging.getLogger(__name__) +DEFAULT_RPC_URL = "https://ogevmdevnet.opengradient.ai" +DEFAULT_TEE_REGISTRY_ADDRESS = "0x4e72238852f3c918f4E4e57AeC9280dDB0c80248" + X402_PROCESSING_HASH_HEADER = "x-processing-hash" X402_PLACEHOLDER_API_KEY = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" BASE_TESTNET_NETWORK = "eip155:84532" @@ -57,23 +61,23 @@ class LLM: below the requested amount. Usage: - client = og.Client(...) + llm = og.LLM(private_key="0x...") # One-time approval (idempotent — skips if allowance is already sufficient) - client.llm.ensure_opg_approval(opg_amount=5) + llm.ensure_opg_approval(opg_amount=5) - result = await client.llm.chat(model=TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...]) - result = await client.llm.completion(model=TEE_LLM.CLAUDE_HAIKU_4_5, prompt="Hello") + result = await llm.chat(model=TEE_LLM.CLAUDE_HAIKU_4_5, messages=[...]) + result = await llm.completion(model=TEE_LLM.CLAUDE_HAIKU_4_5, prompt="Hello") """ def __init__( self, - wallet_account: LocalAccount, - rpc_url: Optional[str] = None, - tee_registry_address: Optional[str] = None, + private_key: str, + rpc_url: str = DEFAULT_RPC_URL, + tee_registry_address: str = DEFAULT_TEE_REGISTRY_ADDRESS, llm_server_url: Optional[str] = None, ): - self._wallet_account = wallet_account + self._wallet_account: LocalAccount = Account.from_key(private_key) endpoint, tls_cert_der, tee_id, tee_payment_address = self._resolve_tee( llm_server_url, diff --git a/src/opengradient/client/model_hub.py b/src/opengradient/client/model_hub.py index c64e563..35f0b72 100644 --- a/src/opengradient/client/model_hub.py +++ b/src/opengradient/client/model_hub.py @@ -28,9 +28,9 @@ class ModelHub: and uploading ML models. Requires email/password authentication. Usage: - client = og.Client(private_key="0x...", email="user@example.com", password="...") - repo = client.model_hub.create_model("my-model", "A description") - client.model_hub.upload("model.onnx", repo.name, repo.version) + hub = og.ModelHub(email="user@example.com", password="...") + repo = hub.create_model("my-model", "A description") + hub.upload("model.onnx", repo.name, repo.version) """ def __init__(self, email: Optional[str] = None, password: Optional[str] = None): diff --git a/src/opengradient/client/twins.py b/src/opengradient/client/twins.py index ebe35f6..55adeaf 100644 --- a/src/opengradient/client/twins.py +++ b/src/opengradient/client/twins.py @@ -17,8 +17,8 @@ class Twins: verifiable inference. Browse available twins at https://twin.fun. Usage: - client = og.init(private_key="0x...", twins_api_key="your-api-key") - response = client.twins.chat( + twins = og.Twins(api_key="your-api-key") + response = twins.chat( twin_id="0x1abd463fd6244be4a1dc0f69e0b70cd5", model=og.TEE_LLM.GROK_4_1_FAST_NON_REASONING, messages=[{"role": "user", "content": "What do you think about AI?"}], diff --git a/src/opengradient/defaults.py b/src/opengradient/defaults.py deleted file mode 100644 index ba2bfc5..0000000 --- a/src/opengradient/defaults.py +++ /dev/null @@ -1,11 +0,0 @@ -# Default variables -DEFAULT_RPC_URL = "https://ogevmdevnet.opengradient.ai" -DEFAULT_API_URL = "https://sdk-devnet.opengradient.ai" -DEFAULT_OG_FAUCET_URL = "https://faucet.opengradient.ai/?address=" -DEFAULT_HUB_SIGNUP_URL = "https://hub.opengradient.ai/signup" -DEFAULT_INFERENCE_CONTRACT_ADDRESS = "0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE" -DEFAULT_SCHEDULER_ADDRESS = "0x7179724De4e7FF9271FA40C0337c7f90C0508eF6" -DEFAULT_BLOCKCHAIN_EXPLORER = "https://explorer.opengradient.ai/tx/" -# TEE Registry contract on the OG EVM chain — used to discover LLM proxy endpoints -# and fetch their registry-verified TLS certificates instead of blindly trusting TOFU. -DEFAULT_TEE_REGISTRY_ADDRESS = "0x4e72238852f3c918f4E4e57AeC9280dDB0c80248" diff --git a/stresstest/infer.py b/stresstest/infer.py index 5986205..6a72cef 100644 --- a/stresstest/infer.py +++ b/stresstest/infer.py @@ -11,10 +11,10 @@ def main(private_key: str): - client = og.Client(private_key=private_key) + alpha = og.Alpha(private_key=private_key) def run_inference(input_data: dict): - client.alpha.infer(MODEL, og.InferenceMode.VANILLA, input_data) + alpha.infer(MODEL, og.InferenceMode.VANILLA, input_data) latencies, failures = stress_test_wrapper(run_inference, num_requests=NUM_REQUESTS) diff --git a/stresstest/llm.py b/stresstest/llm.py index 610d3bf..7c2811d 100644 --- a/stresstest/llm.py +++ b/stresstest/llm.py @@ -11,10 +11,10 @@ def main(private_key: str): - client = og.Client(private_key=private_key) + llm = og.LLM(private_key=private_key) def run_prompt(prompt: str): - client.llm.completion(MODEL, prompt, max_tokens=50) + llm.completion(MODEL, prompt, max_tokens=50) latencies, failures = stress_test_wrapper(run_prompt, num_requests=NUM_REQUESTS, is_llm=True) diff --git a/tests/client_test.py b/tests/client_test.py index 7da14eb..28df2bf 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -7,22 +7,35 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "..")) -from src.opengradient.client import Client +from src.opengradient.client.llm import LLM +from src.opengradient.client.model_hub import ModelHub from src.opengradient.types import ( StreamChunk, x402SettlementMode, ) +FAKE_PRIVATE_KEY = "0x" + "a" * 64 + # --- Fixtures --- +@pytest.fixture +def mock_tee_registry(): + """Mock the TEE registry so LLM.__init__ doesn't need a live registry.""" + with patch("src.opengradient.client.llm.TEERegistry") as mock_tee_registry: + mock_tee = MagicMock() + mock_tee.endpoint = "https://test.tee.server" + mock_tee.tls_cert_der = None + mock_tee.tee_id = "test-tee-id" + mock_tee.payment_address = "0xTestPaymentAddress" + mock_tee_registry.return_value.get_llm_tee.return_value = mock_tee + yield mock_tee_registry + + @pytest.fixture def mock_web3(): - """Create a mock Web3 instance.""" - with ( - patch("src.opengradient.client.client.Web3") as mock, - patch("src.opengradient.client.llm.TEERegistry") as mock_tee_registry, - ): + """Create a mock Web3 instance for Alpha.""" + with patch("src.opengradient.client.alpha.Web3") as mock: mock_instance = MagicMock() mock.return_value = mock_instance mock.HTTPProvider.return_value = MagicMock() @@ -32,14 +45,6 @@ def mock_web3(): mock_instance.eth.gas_price = 1000000000 mock_instance.eth.contract.return_value = MagicMock() - # Return a fake active TEE endpoint so Client.__init__ doesn't need a live registry - mock_tee = MagicMock() - mock_tee.endpoint = "https://test.tee.server" - mock_tee.tls_cert_der = None - mock_tee.tee_id = "test-tee-id" - mock_tee.payment_address = "0xTestPaymentAddress" - mock_tee_registry.return_value.get_llm_tee.return_value = mock_tee - yield mock_instance @@ -60,66 +65,27 @@ def mock_file_open(path, *args, **kwargs): yield -# --- Client Initialization Tests --- - +# --- LLM Initialization Tests --- -class TestClientInitialization: - def test_client_initialization_without_auth(self, mock_web3, mock_abi_files): - """Test basic client initialization without authentication.""" - client = Client( - private_key="0x" + "a" * 64, - rpc_url="https://test.rpc.url", - api_url="https://test.api.url", - inference_contract_address="0x" + "b" * 40, - ) - assert client.model_hub._hub_user is None +class TestLLMInitialization: + def test_llm_initialization(self, mock_tee_registry): + """Test basic LLM initialization.""" + llm = LLM(private_key=FAKE_PRIVATE_KEY) + assert llm._tee_endpoint == "https://test.tee.server" - def test_client_initialization_with_auth(self, mock_web3, mock_abi_files): - """Test client initialization with email/password authentication.""" - with ( - patch("src.opengradient.client.model_hub._FIREBASE_CONFIG", {"apiKey": "fake"}), - patch("src.opengradient.client.model_hub.firebase") as mock_firebase, - ): - mock_auth = MagicMock() - mock_auth.sign_in_with_email_and_password.return_value = { - "idToken": "test_token", - "email": "test@test.com", - } - mock_firebase.initialize_app.return_value.auth.return_value = mock_auth - - client = Client( - private_key="0x" + "a" * 64, - rpc_url="https://test.rpc.url", - api_url="https://test.api.url", - inference_contract_address="0x" + "b" * 40, - email="test@test.com", - password="test_password", - ) - - assert client.model_hub._hub_user is not None - assert client.model_hub._hub_user["idToken"] == "test_token" - - def test_client_initialization_custom_llm_urls(self, mock_web3, mock_abi_files): - """Test client initialization with custom LLM server URL.""" + def test_llm_initialization_custom_url(self, mock_tee_registry): + """Test LLM initialization with custom server URL.""" custom_llm_url = "https://custom.llm.server" - - client = Client( - private_key="0x" + "a" * 64, - rpc_url="https://test.rpc.url", - api_url="https://test.api.url", - inference_contract_address="0x" + "b" * 40, - llm_server_url=custom_llm_url, - ) - - assert client.llm._tee_endpoint == custom_llm_url + llm = LLM(private_key=FAKE_PRIVATE_KEY, llm_server_url=custom_llm_url) + assert llm._tee_endpoint == custom_llm_url -# --- Authentication Tests --- +# --- ModelHub Authentication Tests --- class TestAuthentication: - def test_login_to_hub_success(self, mock_web3, mock_abi_files): + def test_login_to_hub_success(self): """Test successful login to hub.""" with ( patch("src.opengradient.client.model_hub._FIREBASE_CONFIG", {"apiKey": "fake"}), @@ -132,19 +98,12 @@ def test_login_to_hub_success(self, mock_web3, mock_abi_files): } mock_firebase.initialize_app.return_value.auth.return_value = mock_auth - client = Client( - private_key="0x" + "a" * 64, - rpc_url="https://test.rpc.url", - api_url="https://test.api.url", - inference_contract_address="0x" + "b" * 40, - email="user@test.com", - password="password123", - ) + hub = ModelHub(email="user@test.com", password="password123") mock_auth.sign_in_with_email_and_password.assert_called_once_with("user@test.com", "password123") - assert client.model_hub._hub_user["idToken"] == "success_token" + assert hub._hub_user["idToken"] == "success_token" - def test_login_to_hub_failure(self, mock_web3, mock_abi_files): + def test_login_to_hub_failure(self): """Test login failure raises exception.""" with ( patch("src.opengradient.client.model_hub._FIREBASE_CONFIG", {"apiKey": "fake"}), @@ -155,14 +114,7 @@ def test_login_to_hub_failure(self, mock_web3, mock_abi_files): mock_firebase.initialize_app.return_value.auth.return_value = mock_auth with pytest.raises(Exception, match="Invalid credentials"): - Client( - private_key="0x" + "a" * 64, - rpc_url="https://test.rpc.url", - api_url="https://test.api.url", - inference_contract_address="0x" + "b" * 40, - email="user@test.com", - password="wrong_password", - ) + ModelHub(email="user@test.com", password="wrong_password") # --- StreamChunk Tests --- diff --git a/tests/langchain_adapter_test.py b/tests/langchain_adapter_test.py index 3e1ec03..e651ab4 100644 --- a/tests/langchain_adapter_test.py +++ b/tests/langchain_adapter_test.py @@ -15,18 +15,18 @@ @pytest.fixture -def mock_client(): - """Create a mock Client instance.""" - with patch("src.opengradient.agents.og_langchain.Client") as MockClient: +def mock_llm_client(): + """Create a mock LLM instance.""" + with patch("src.opengradient.agents.og_langchain.LLM") as MockLLM: mock_instance = MagicMock() - mock_instance.llm.chat = AsyncMock() - MockClient.return_value = mock_instance + mock_instance.chat = AsyncMock() + MockLLM.return_value = mock_instance yield mock_instance @pytest.fixture -def model(mock_client): - """Create an OpenGradientChatModel with a mocked client.""" +def model(mock_llm_client): + """Create an OpenGradientChatModel with a mocked LLM client.""" return OpenGradientChatModel(private_key="0x" + "a" * 64, model_cid=TEE_LLM.GPT_5) @@ -38,12 +38,12 @@ def test_initialization(self, model): assert model.x402_settlement_mode == x402SettlementMode.BATCH_HASHED assert model._llm_type == "opengradient" - def test_initialization_custom_max_tokens(self, mock_client): + def test_initialization_custom_max_tokens(self, mock_llm_client): """Test model initializes with custom max_tokens.""" model = OpenGradientChatModel(private_key="0x" + "a" * 64, model_cid=TEE_LLM.CLAUDE_HAIKU_4_5, max_tokens=1000) assert model.max_tokens == 1000 - def test_initialization_custom_settlement_mode(self, mock_client): + def test_initialization_custom_settlement_mode(self, mock_llm_client): """Test model initializes with custom settlement mode.""" model = OpenGradientChatModel( private_key="0x" + "a" * 64, @@ -58,9 +58,9 @@ def test_identifying_params(self, model): class TestGenerate: - def test_text_response(self, model, mock_client): + def test_text_response(self, model, mock_llm_client): """Test _generate with a simple text response.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output={"role": "assistant", "content": "Hello there!"}, @@ -72,9 +72,9 @@ def test_text_response(self, model, mock_client): assert result.generations[0].message.content == "Hello there!" assert result.generations[0].generation_info == {"finish_reason": "stop"} - def test_tool_call_response_flat_format(self, model, mock_client): + def test_tool_call_response_flat_format(self, model, mock_llm_client): """Test _generate with tool calls in flat format {name, arguments}.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="tool_call", chat_output={ @@ -99,9 +99,9 @@ def test_tool_call_response_flat_format(self, model, mock_client): assert ai_msg.tool_calls[0]["name"] == "get_balance" assert ai_msg.tool_calls[0]["args"] == {"account": "main"} - def test_tool_call_response_nested_format(self, model, mock_client): + def test_tool_call_response_nested_format(self, model, mock_llm_client): """Test _generate with tool calls in OpenAI nested format {function: {name, arguments}}.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="tool_call", chat_output={ @@ -129,9 +129,9 @@ def test_tool_call_response_nested_format(self, model, mock_client): assert ai_msg.tool_calls[0]["name"] == "get_balance" assert ai_msg.tool_calls[0]["args"] == {"account": "savings"} - def test_content_as_list_of_blocks(self, model, mock_client): + def test_content_as_list_of_blocks(self, model, mock_llm_client): """Test _generate when API returns content as a list of content blocks.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output={ @@ -144,9 +144,9 @@ def test_content_as_list_of_blocks(self, model, mock_client): assert result.generations[0].message.content == "Hello there!" - def test_empty_chat_output(self, model, mock_client): + def test_empty_chat_output(self, model, mock_llm_client): """Test _generate handles None chat_output gracefully.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output=None, @@ -158,9 +158,9 @@ def test_empty_chat_output(self, model, mock_client): class TestMessageConversion: - def test_converts_all_message_types(self, model, mock_client): + def test_converts_all_message_types(self, model, mock_llm_client): """Test that all LangChain message types are correctly converted to SDK format.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output={"role": "assistant", "content": "ok"}, @@ -179,7 +179,7 @@ def test_converts_all_message_types(self, model, mock_client): model._generate(messages) - sdk_messages = mock_client.llm.chat.call_args.kwargs["messages"] + sdk_messages = mock_llm_client.chat.call_args.kwargs["messages"] assert sdk_messages[0] == {"role": "system", "content": "You are helpful."} assert sdk_messages[1] == {"role": "user", "content": "Hi"} @@ -195,14 +195,14 @@ def test_converts_all_message_types(self, model, mock_client): # ToolMessage assert sdk_messages[4] == {"role": "tool", "content": "result", "tool_call_id": "call_1"} - def test_unsupported_message_type_raises(self, model, mock_client): + def test_unsupported_message_type_raises(self, model, mock_llm_client): """Test that unsupported message types raise ValueError.""" with pytest.raises(ValueError, match="Unexpected message type"): model._generate([MagicMock(spec=[])]) - def test_passes_correct_params_to_client(self, model, mock_client): + def test_passes_correct_params_to_client(self, model, mock_llm_client): """Test that _generate passes model params correctly to the SDK client.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output={"role": "assistant", "content": "ok"}, @@ -210,7 +210,7 @@ def test_passes_correct_params_to_client(self, model, mock_client): model._generate([HumanMessage(content="Hi")], stop=["END"]) - mock_client.llm.chat.assert_called_once_with( + mock_llm_client.chat.assert_called_once_with( model=TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Hi"}], stop_sequence=["END"], @@ -253,9 +253,9 @@ def test_bind_dict_tool(self, model): assert model._tools == [tool_dict] - def test_tools_used_in_generate(self, model, mock_client): + def test_tools_used_in_generate(self, model, mock_llm_client): """Test that bound tools are passed to the client chat call.""" - mock_client.llm.chat.return_value = TextGenerationOutput( + mock_llm_client.chat.return_value = TextGenerationOutput( transaction_hash="external", finish_reason="stop", chat_output={"role": "assistant", "content": "ok"}, @@ -265,7 +265,7 @@ def test_tools_used_in_generate(self, model, mock_client): model.bind_tools([tool_dict]) model._generate([HumanMessage(content="Hi")]) - assert mock_client.llm.chat.call_args.kwargs["tools"] == [tool_dict] + assert mock_llm_client.chat.call_args.kwargs["tools"] == [tool_dict] class TestExtractContent: diff --git a/tests/llm_test.py b/tests/llm_test.py index 2bef98a..3f068f3 100644 --- a/tests/llm_test.py +++ b/tests/llm_test.py @@ -113,13 +113,14 @@ def fake_http(): yield http +FAKE_PRIVATE_KEY = "0x" + "a" * 64 + + def _make_llm( endpoint: str = "https://test.tee.server", ) -> LLM: """Build an LLM with an explicit server URL (skips registry lookup).""" - wallet = MagicMock() - wallet.address = "0x" + "ab" * 20 - llm = LLM(wallet_account=wallet, llm_server_url=endpoint) + llm = LLM(private_key=FAKE_PRIVATE_KEY, llm_server_url=endpoint) # llm_server_url path sets tee_id/payment_address to None; set them for assertions. llm._tee_id = "test-tee-id" llm._tee_payment_address = "0xTestPayment" diff --git a/tutorials/01-verifiable-ai-agent.md b/tutorials/01-verifiable-ai-agent.md index a323e60..1ae369e 100644 --- a/tutorials/01-verifiable-ai-agent.md +++ b/tutorials/01-verifiable-ai-agent.md @@ -33,7 +33,7 @@ export OG_PRIVATE_KEY="0x..." > so your wallet can pay for inference transactions. All x402 LLM payments currently > settle on Base Sepolia. -## Step 1: Initialize the Client and LangChain Adapter +## Step 1: Initialize and Create the LangChain Adapter Before making any LLM calls, you need to approve OPG token spending for the x402 payment protocol. The `ensure_opg_approval` method checks your wallet's current @@ -47,13 +47,12 @@ import opengradient as og private_key = os.environ["OG_PRIVATE_KEY"] # Approve OPG spending for x402 payments (idempotent -- skips if already approved). -client = og.Client(private_key=private_key) -client.llm.ensure_opg_approval(opg_amount=5) +llm_client = og.LLM(private_key=private_key) +llm_client.ensure_opg_approval(opg_amount=5) # Create the LangChain chat model backed by OpenGradient TEE. -# Note: the adapter creates its own internal Client, separate from any client -# created via og.init(). This is why private_key is passed here explicitly. -# The approval above applies to the wallet, so it covers the adapter's client too. +# The adapter creates its own internal LLM client. The approval above applies +# to the wallet, so it covers the adapter's client too. llm = og.agents.langchain_adapter( private_key=private_key, model_cid=og.TEE_LLM.GPT_4_1_2025_04_14, @@ -137,11 +136,10 @@ def format_model_output(inference_result: og.InferenceResult) -> str: ) ``` -Now create the tool. You need an initialized client to pass `client.alpha` as the -inference backend: +Now create the tool. You need an `Alpha` instance for the on-chain inference backend: ```python -client = og.init(private_key=os.environ["OG_PRIVATE_KEY"]) +alpha = og.Alpha(private_key=os.environ["OG_PRIVATE_KEY"]) volatility_tool = create_run_model_tool( tool_type=ToolType.LANGCHAIN, @@ -154,7 +152,7 @@ volatility_tool = create_run_model_tool( ), model_input_provider=provide_model_input, model_output_formatter=format_model_output, - inference=client.alpha, + inference=alpha, tool_input_schema=VolatilityInput, inference_mode=og.InferenceMode.VANILLA, ) @@ -286,14 +284,15 @@ SAMPLE_PRICES = { Token.BTC: [67100.0, 67250.0, 67180.0, 67320.0, 67150.0, 67400.0, 67280.0, 67350.0], } -# ── Client ──────────────────────────────────────────────────────────────── -# og.init() creates the global client (used by AlphaSense tools). -# langchain_adapter() creates its own internal client for LLM calls. +# ── Clients ─────────────────────────────────────────────────────────────── private_key = os.environ["OG_PRIVATE_KEY"] -client = og.init(private_key=private_key) # Approve OPG spending for x402 payments (idempotent -- skips if already approved). -client.llm.ensure_opg_approval(opg_amount=5) +llm_client = og.LLM(private_key=private_key) +llm_client.ensure_opg_approval(opg_amount=5) + +# Alpha client for on-chain model inference. +alpha = og.Alpha(private_key=private_key) llm = og.agents.langchain_adapter( private_key=private_key, @@ -326,7 +325,7 @@ volatility_tool = create_run_model_tool( tool_description="Measures return volatility for a crypto token using an on-chain model.", model_input_provider=provide_model_input, model_output_formatter=format_model_output, - inference=client.alpha, + inference=alpha, tool_input_schema=VolatilityInput, inference_mode=og.InferenceMode.VANILLA, ) diff --git a/tutorials/02-streaming-multi-provider.md b/tutorials/02-streaming-multi-provider.md index 1df3759..57ee4ce 100644 --- a/tutorials/02-streaming-multi-provider.md +++ b/tutorials/02-streaming-multi-provider.md @@ -11,7 +11,7 @@ compliance, billing audits, or dispute resolution. You choose how much data goes on-chain: just a hash (privacy), a batch digest (cost savings), or full metadata (complete transparency). -This tutorial walks through the `client.llm.chat()` API, covering non-streaming and +This tutorial walks through the `llm.chat()` API, covering non-streaming and streaming responses, multi-provider switching, settlement modes, and function calling -- all in one place. @@ -50,13 +50,13 @@ if not private_key: print("Error: set the OG_PRIVATE_KEY environment variable.") sys.exit(1) -client = og.init(private_key=private_key) +llm = og.LLM(private_key=private_key) # Approve OPG spending for x402 payments (one-time, idempotent). -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) async def main(): - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "What is the x402 payment protocol?"}], max_tokens=200, @@ -85,28 +85,28 @@ identical. # All LLM methods are async -- use await in an async function # OpenAI -result_openai = await client.llm.chat( +result_openai = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "Hello from OpenAI!"}], max_tokens=100, ) # Anthropic -result_anthropic = await client.llm.chat( +result_anthropic = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Hello from Anthropic!"}], max_tokens=100, ) # Google -result_google = await client.llm.chat( +result_google = await llm.chat( model=og.TEE_LLM.GEMINI_2_5_FLASH, messages=[{"role": "user", "content": "Hello from Google!"}], max_tokens=100, ) # xAI -result_xai = await client.llm.chat( +result_xai = await llm.chat( model=og.TEE_LLM.GROK_4, messages=[{"role": "user", "content": "Hello from xAI!"}], max_tokens=100, @@ -124,7 +124,7 @@ are generated. The return value changes from a `TextGenerationOutput` to an asyn generator that yields `StreamChunk` objects. ```python -stream = await client.llm.chat( +stream = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[ {"role": "system", "content": "You are a concise technical writer."}, @@ -175,7 +175,7 @@ privacy/cost/transparency trade-off: ```python # Privacy-first: only hashes stored on-chain -result_private = await client.llm.chat( +result_private = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Sensitive query here."}], max_tokens=100, @@ -184,7 +184,7 @@ result_private = await client.llm.chat( print(f"Payment hash (SETTLE): {result_private.payment_hash}") # Cost-efficient: batched settlement (this is the default) -result_batch = await client.llm.chat( +result_batch = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Regular query."}], max_tokens=100, @@ -193,7 +193,7 @@ result_batch = await client.llm.chat( print(f"Payment hash (BATCH_HASHED): {result_batch.payment_hash}") # Full transparency: everything on-chain -result_transparent = await client.llm.chat( +result_transparent = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Auditable query."}], max_tokens=100, @@ -208,7 +208,7 @@ audit trail -- they are the on-chain receipts for each inference call. ## Step 5: Function Calling -You can pass tools to `client.llm.chat()` in the standard OpenAI function-calling +You can pass tools to `llm.chat()` in the standard OpenAI function-calling format. This works with any model that supports tool use. ```python @@ -232,7 +232,7 @@ tools = [ } ] -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.GEMINI_2_5_FLASH, messages=[{"role": "user", "content": "What's the current price of ETH?"}], max_tokens=200, @@ -252,7 +252,7 @@ else: ``` When the model returns tool calls, execute the requested functions locally, -then send the results back in a follow-up `client.llm.chat()` call with a `"tool"` +then send the results back in a follow-up `llm.chat()` call with a `"tool"` role message. See **Tutorial 3** for a complete multi-turn tool-calling loop. ## Complete Code @@ -271,10 +271,10 @@ if not private_key: print("Error: set the OG_PRIVATE_KEY environment variable.") sys.exit(1) -client = og.init(private_key=private_key) +llm = og.LLM(private_key=private_key) # Approve OPG spending for x402 payments (idempotent -- skips if already approved). -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) PROMPT = "Explain what a Trusted Execution Environment is in two sentences." @@ -290,7 +290,7 @@ async def main(): for name, model in models: try: - result = await client.llm.chat( + result = await llm.chat( model=model, messages=[{"role": "user", "content": PROMPT}], max_tokens=200, @@ -303,7 +303,7 @@ async def main(): # ── Streaming ───────────────────────────────────────────────────────── print("--- Streaming from GPT-5 ---") - stream = await client.llm.chat( + stream = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[{"role": "user", "content": "What is x402? Keep it under 50 words."}], max_tokens=100, @@ -322,7 +322,7 @@ async def main(): ("INDIVIDUAL_FULL", og.x402SettlementMode.INDIVIDUAL_FULL), ]: try: - r = await client.llm.chat( + r = await llm.chat( model=og.TEE_LLM.CLAUDE_SONNET_4_6, messages=[{"role": "user", "content": "Say hello."}], max_tokens=50, @@ -348,7 +348,7 @@ async def main(): }, }] - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GEMINI_2_5_FLASH, messages=[{"role": "user", "content": "What is the price of ETH?"}], max_tokens=200, diff --git a/tutorials/03-verified-tool-calling.md b/tutorials/03-verified-tool-calling.md index d56bf33..86ea119 100644 --- a/tutorials/03-verified-tool-calling.md +++ b/tutorials/03-verified-tool-calling.md @@ -52,10 +52,10 @@ if not private_key: print("Error: set the OG_PRIVATE_KEY environment variable.") sys.exit(1) -client = og.init(private_key=private_key) +llm = og.LLM(private_key=private_key) # Approve OPG spending for x402 payments (one-time, idempotent). -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) ``` ## Step 2: Define Local Tool Implementations @@ -162,12 +162,12 @@ TOOLS = [ ] ``` -## Step 4: Pass Tools to `client.llm.chat` +## Step 4: Pass Tools to `llm.chat` -Pass the `tools` list and `tool_choice` parameter to any `client.llm.chat()` call. +Pass the `tools` list and `tool_choice` parameter to any `llm.chat()` call. ```python -result = await client.llm.chat( +result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=[ {"role": "system", "content": "You are a crypto portfolio assistant."}, @@ -198,7 +198,7 @@ The core pattern for a tool-calling agent is a loop: 5. Repeat until the model responds with a regular text message ```python -async def run_agent(client: og.Client, user_query: str) -> str: +async def run_agent(user_query: str) -> str: """Run a multi-turn tool-calling agent loop.""" messages = [ { @@ -218,7 +218,7 @@ async def run_agent(client: og.Client, user_query: str) -> str: print(f"\n [Round {i + 1}] Calling LLM...") try: - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=messages, max_tokens=600, @@ -286,7 +286,7 @@ async def main(): print("\n" + "=" * 70) print(f"USER: {query}") print("=" * 70) - answer = await run_agent(client, query) + answer = await run_agent(query) print(f"\nASSISTANT: {answer}") if __name__ == "__main__": @@ -315,10 +315,10 @@ if not private_key: print("Error: set the OG_PRIVATE_KEY environment variable.") sys.exit(1) -client = og.init(private_key=private_key) +llm = og.LLM(private_key=private_key) # Approve OPG spending for x402 payments (idempotent -- skips if already approved). -client.llm.ensure_opg_approval(opg_amount=5) +llm.ensure_opg_approval(opg_amount=5) # ── Mock data ───────────────────────────────────────────────────────────── PORTFOLIO = {"ETH": {"amount": 5.0, "avg_cost": 1950.00}, @@ -379,7 +379,7 @@ async def run_agent(user_query: str) -> str: ] for i in range(5): try: - result = await client.llm.chat( + result = await llm.chat( model=og.TEE_LLM.GPT_5, messages=messages, max_tokens=600, temperature=0.0, tools=TOOLS, tool_choice="auto", x402_settlement_mode=og.x402SettlementMode.BATCH_HASHED,