opengradient 0.2.8__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {opengradient-0.2.8/src/opengradient.egg-info → opengradient-0.3.0}/PKG-INFO +27 -16
  2. {opengradient-0.2.8 → opengradient-0.3.0}/README.md +25 -14
  3. {opengradient-0.2.8 → opengradient-0.3.0}/pyproject.toml +2 -2
  4. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/__init__.py +22 -9
  5. opengradient-0.3.0/src/opengradient/abi/llm.abi +1 -0
  6. opengradient-0.3.0/src/opengradient/account.py +37 -0
  7. opengradient-0.3.0/src/opengradient/cli.py +390 -0
  8. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/client.py +51 -6
  9. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/defaults.py +2 -2
  10. {opengradient-0.2.8 → opengradient-0.3.0/src/opengradient.egg-info}/PKG-INFO +27 -16
  11. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient.egg-info/SOURCES.txt +2 -3
  12. opengradient-0.2.8/src/opengradient/cli.py +0 -195
  13. opengradient-0.2.8/tests/test_api.py +0 -110
  14. opengradient-0.2.8/tests/test_exceptions.py +0 -25
  15. opengradient-0.2.8/tests/test_integration.py +0 -20
  16. {opengradient-0.2.8 → opengradient-0.3.0}/LICENSE +0 -0
  17. {opengradient-0.2.8 → opengradient-0.3.0}/setup.cfg +0 -0
  18. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/abi/inference.abi +0 -0
  19. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/exceptions.py +0 -0
  20. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/types.py +0 -0
  21. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient/utils.py +0 -0
  22. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient.egg-info/dependency_links.txt +0 -0
  23. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient.egg-info/entry_points.txt +0 -0
  24. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient.egg-info/requires.txt +0 -0
  25. {opengradient-0.2.8 → opengradient-0.3.0}/src/opengradient.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opengradient
3
- Version: 0.2.8
4
- Summary: A Python SDK for OpenGradient inference services
3
+ Version: 0.3.0
4
+ Summary: Python SDK for OpenGradient decentralized model management & inference services
5
5
  Author-email: OpenGradient <oliver@opengradient.ai>
6
6
  License: MIT License
7
7
 
@@ -135,9 +135,9 @@ Requires-Dist: websockets==13.1
135
135
  Requires-Dist: xattr==1.1.0
136
136
  Requires-Dist: yarl==1.13.1
137
137
 
138
- # OpenGradient Python SDK
138
+ # OpenGradient SDK
139
139
 
140
- Python SDK for OpenGradient inference services.
140
+ Python SDK for OpenGradient decentralized model management & inference services.
141
141
 
142
142
  ## Installation
143
143
  ```python
@@ -147,41 +147,47 @@ pip install opengradient
147
147
  ## Quick Start
148
148
  ```python
149
149
  import opengradient as og
150
- og.init(private_key="x", rpc_url="y", contract_address="z")
150
+ og.init(email="<email>", password="<password>")
151
151
  ```
152
152
 
153
- ### Sign in with Email
153
+ ### Create a Model
154
154
  ```python
155
- og.login(email="you@opengradient.ai", password="xyz")
155
+ og.create_model(model_name="<model_name>", model_desc="<model_description>")
156
156
  ```
157
157
 
158
- ### Create a Model
158
+ ### Create a Model (with file upload)
159
159
  ```python
160
- og.create_model(model_name="test-network-model", model_desc="testing upload to sdk")
160
+ og.create_model(model_name="<model_name>", model_desc="<model_description>", model_path="<model_path>")
161
161
  ```
162
162
 
163
163
  ### Create a Version of a Model
164
164
  ```python
165
- og.create_version(model_name="test-network-model", notes="test notes")
165
+ og.create_version(model_name="<model_name>", notes="<model_notes>")
166
166
  ```
167
167
 
168
168
  ### Upload Files to a Model
169
169
  ```python
170
- og.upload(model_path="local_path_to_your_model.onnx", model_name="test-network-model", version="0.01")
170
+ og.upload(model_path="<model_path>", model_name="<model_name>", version="<version>")
171
171
  ```
172
172
 
173
- ### Run Inference
173
+ ### List Files of a Model Version
174
174
  ```python
175
- inference_mode = og.InferenceMode.VANILLA
176
- inference_cid = og.infer(model_cid, model_inputs, inference_mode)
175
+ og.list_files(model_name="<model_name>", version="<version>")
177
176
  ```
178
177
 
178
+ ### Run Inference
179
179
  ```python
180
- og.infer(model_id, inference_mode, model_input)
180
+ inference_mode = og.InferenceMode.VANILLA
181
+ og.infer(model_cid, model_inputs, inference_mode)
181
182
  ```
182
183
 
183
184
  ## Using the CLI
184
185
 
186
+ ```bash
187
+ export OPENGRADIENT_EMAIL="<email>"
188
+ export OPENGRADIENT_PASSWORD="<password>"
189
+ ```
190
+
185
191
  #### Creating a Model
186
192
  ```bash
187
193
  opengradient create_model "<model_name>" "<description>"
@@ -195,7 +201,12 @@ opengradient create_model "<model_name>" "<description>"
195
201
 
196
202
  #### Upload a File
197
203
  ```bash
198
- opengradient upload "path/to/model.onnx" "<model_name>" "<version>"
204
+ opengradient upload "<model_path>" "<model_name>" "<version>"
205
+ ```
206
+
207
+ #### List Files of a Model Version
208
+ ```bash
209
+ opengradient list_files "<model_name>" "<version>"
199
210
  ```
200
211
 
201
212
  #### CLI infer using string
@@ -1,6 +1,6 @@
1
- # OpenGradient Python SDK
1
+ # OpenGradient SDK
2
2
 
3
- Python SDK for OpenGradient inference services.
3
+ Python SDK for OpenGradient decentralized model management & inference services.
4
4
 
5
5
  ## Installation
6
6
  ```python
@@ -10,41 +10,47 @@ pip install opengradient
10
10
  ## Quick Start
11
11
  ```python
12
12
  import opengradient as og
13
- og.init(private_key="x", rpc_url="y", contract_address="z")
13
+ og.init(email="<email>", password="<password>")
14
14
  ```
15
15
 
16
- ### Sign in with Email
16
+ ### Create a Model
17
17
  ```python
18
- og.login(email="you@opengradient.ai", password="xyz")
18
+ og.create_model(model_name="<model_name>", model_desc="<model_description>")
19
19
  ```
20
20
 
21
- ### Create a Model
21
+ ### Create a Model (with file upload)
22
22
  ```python
23
- og.create_model(model_name="test-network-model", model_desc="testing upload to sdk")
23
+ og.create_model(model_name="<model_name>", model_desc="<model_description>", model_path="<model_path>")
24
24
  ```
25
25
 
26
26
  ### Create a Version of a Model
27
27
  ```python
28
- og.create_version(model_name="test-network-model", notes="test notes")
28
+ og.create_version(model_name="<model_name>", notes="<model_notes>")
29
29
  ```
30
30
 
31
31
  ### Upload Files to a Model
32
32
  ```python
33
- og.upload(model_path="local_path_to_your_model.onnx", model_name="test-network-model", version="0.01")
33
+ og.upload(model_path="<model_path>", model_name="<model_name>", version="<version>")
34
34
  ```
35
35
 
36
- ### Run Inference
36
+ ### List Files of a Model Version
37
37
  ```python
38
- inference_mode = og.InferenceMode.VANILLA
39
- inference_cid = og.infer(model_cid, model_inputs, inference_mode)
38
+ og.list_files(model_name="<model_name>", version="<version>")
40
39
  ```
41
40
 
41
+ ### Run Inference
42
42
  ```python
43
- og.infer(model_id, inference_mode, model_input)
43
+ inference_mode = og.InferenceMode.VANILLA
44
+ og.infer(model_cid, model_inputs, inference_mode)
44
45
  ```
45
46
 
46
47
  ## Using the CLI
47
48
 
49
+ ```bash
50
+ export OPENGRADIENT_EMAIL="<email>"
51
+ export OPENGRADIENT_PASSWORD="<password>"
52
+ ```
53
+
48
54
  #### Creating a Model
49
55
  ```bash
50
56
  opengradient create_model "<model_name>" "<description>"
@@ -58,7 +64,12 @@ opengradient create_model "<model_name>" "<description>"
58
64
 
59
65
  #### Upload a File
60
66
  ```bash
61
- opengradient upload "path/to/model.onnx" "<model_name>" "<version>"
67
+ opengradient upload "<model_path>" "<model_name>" "<version>"
68
+ ```
69
+
70
+ #### List Files of a Model Version
71
+ ```bash
72
+ opengradient list_files "<model_name>" "<version>"
62
73
  ```
63
74
 
64
75
  #### CLI infer using string
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opengradient"
7
- version = "0.2.8"
8
- description = "A Python SDK for OpenGradient inference services"
7
+ version = "0.3.0"
8
+ description = "Python SDK for OpenGradient decentralized model management & inference services"
9
9
  authors = [{name = "OpenGradient", email = "oliver@opengradient.ai"}]
10
10
  license = {file = "LICENSE"}
11
11
  readme = "README.md"
@@ -1,16 +1,16 @@
1
1
  from .client import Client
2
2
  from .defaults import *
3
3
  from .types import InferenceMode
4
-
5
- __version__ = "0.2.8"
4
+ from typing import List, Dict
5
+ __version__ = "0.3.0"
6
6
 
7
7
  _client = None
8
8
 
9
- def init(private_key=DEFAULT_PRIVATE_KEY,
9
+ def init(email: str,
10
+ password: str,
11
+ private_key=DEFAULT_PRIVATE_KEY,
10
12
  rpc_url=DEFAULT_RPC_URL,
11
- contract_address=DEFAULT_INFERENCE_CONTRACT_ADDRESS,
12
- email=DEFAULT_HUB_EMAIL,
13
- password=DEFAULT_HUB_PASSWORD):
13
+ contract_address=DEFAULT_INFERENCE_CONTRACT_ADDRESS):
14
14
  global _client
15
15
  _client = Client(private_key=private_key, rpc_url=rpc_url, contract_address=contract_address, email=email, password=password)
16
16
 
@@ -19,10 +19,18 @@ def upload(model_path, model_name, version):
19
19
  raise RuntimeError("OpenGradient client not initialized. Call og.init() first.")
20
20
  return _client.upload(model_path, model_name, version)
21
21
 
22
- def create_model(model_name, model_desc):
22
+ def create_model(model_name: str, model_desc: str, model_path: str = None):
23
23
  if _client is None:
24
24
  raise RuntimeError("OpenGradient client not initialized. Call og.init() first.")
25
- return _client.create_model(model_name, model_desc)
25
+
26
+ result = _client.create_model(model_name, model_desc)
27
+
28
+ if model_path:
29
+ version = "0.01"
30
+ upload_result = _client.upload(model_path, model_name, version)
31
+ result["upload"] = upload_result
32
+
33
+ return result
26
34
 
27
35
  def create_version(model_name, notes=None, is_major=False):
28
36
  if _client is None:
@@ -37,4 +45,9 @@ def infer(model_cid, inference_mode, model_input):
37
45
  def login(email: str, password: str):
38
46
  if _client is None:
39
47
  raise RuntimeError("OpenGradient client not initialized. Call og.init() first.")
40
- return _client.login(email, password)
48
+ return _client.login(email, password)
49
+
50
+ def list_files(model_name: str, version: str) -> List[Dict]:
51
+ if _client is None:
52
+ raise RuntimeError("OpenGradient client not initialized. Call og.init() first.")
53
+ return _client.list_files(model_name, version)
@@ -0,0 +1 @@
1
+ [{"anonymous":false,"inputs":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"int128","name":"value","type":"int128"},{"internalType":"int128","name":"decimals","type":"int128"}],"internalType":"struct TensorLib.Number[]","name":"values","type":"tuple[]"},{"internalType":"uint32[]","name":"shape","type":"uint32[]"}],"internalType":"struct TensorLib.MultiDimensionalNumberTensor[]","name":"numbers","type":"tuple[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string[]","name":"values","type":"string[]"}],"internalType":"struct TensorLib.StringTensor[]","name":"strings","type":"tuple[]"},{"internalType":"bool","name":"is_simulation_result","type":"bool"}],"indexed":false,"internalType":"struct ModelOutput","name":"output","type":"tuple"}],"name":"InferenceResult","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"string","name":"answer","type":"string"}],"indexed":false,"internalType":"struct LlmResponse","name":"response","type":"tuple"}],"name":"LLMResult","type":"event"},{"inputs":[{"internalType":"string","name":"modelId","type":"string"},{"internalType":"enum ModelInferenceMode","name":"inferenceMode","type":"uint8"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"int128","name":"value","type":"int128"},{"internalType":"int128","name":"decimals","type":"int128"}],"internalType":"struct TensorLib.Number[]","name":"values","type":"tuple[]"},{"internalType":"uint32[]","name":"shape","type":"uint32[]"}],"internalType":"struct TensorLib.MultiDimensionalNumberTensor[]","name":"numbers","type":"tuple[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string[]","name":"values","type":"string[]"}],"internalType":"struct TensorLib.StringTensor[]","name":"strings","type":"tuple[]"}],"internalType":"struct ModelInput","name":"modelInput","type":"tuple"}],"name":"run","outputs":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"int128","name":"value","type":"int128"},{"internalType":"int128","name":"decimals","type":"int128"}],"internalType":"struct TensorLib.Number[]","name":"values","type":"tuple[]"},{"internalType":"uint32[]","name":"shape","type":"uint32[]"}],"internalType":"struct TensorLib.MultiDimensionalNumberTensor[]","name":"numbers","type":"tuple[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string[]","name":"values","type":"string[]"}],"internalType":"struct TensorLib.StringTensor[]","name":"strings","type":"tuple[]"},{"internalType":"bool","name":"is_simulation_result","type":"bool"}],"internalType":"struct ModelOutput","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum LlmInferenceMode","name":"mode","type":"uint8"},{"internalType":"string","name":"modelCID","type":"string"},{"internalType":"string","name":"prompt","type":"string"},{"internalType":"uint32","name":"max_tokens","type":"uint32"},{"internalType":"string[]","name":"stop_sequence","type":"string[]"},{"internalType":"uint32","name":"temperature","type":"uint32"}],"internalType":"struct LlmInferenceRequest","name":"request","type":"tuple"}],"name":"runLLM","outputs":[{"components":[{"internalType":"string","name":"answer","type":"string"}],"internalType":"struct LlmResponse","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"}]
@@ -0,0 +1,37 @@
1
+ from eth_account import Account
2
+ import secrets
3
+ from collections import namedtuple
4
+ import os
5
+ import hashlib
6
+
7
+ EthAccount = namedtuple('EthAccount', ['address', 'private_key'])
8
+
9
+
10
+ def generate_eth_account() -> EthAccount:
11
+ user_seed = _get_user_random_seed()
12
+ private_key = _generate_secure_private_key(user_seed)
13
+
14
+ # derive account
15
+ account = Account.from_key(private_key)
16
+
17
+ # get the public key (address)
18
+ public_key = account.address
19
+
20
+ return EthAccount(address=public_key, private_key=private_key)
21
+
22
+ def _get_user_random_seed():
23
+ print("Please type a random string of characters (the longer and more random, the better):")
24
+ print("> ", end="") # Add a '>' prompt on a new line
25
+ return input().encode()
26
+
27
+ def _generate_secure_private_key(user_input):
28
+ # Combine multiple sources of entropy
29
+ system_random = secrets.token_bytes(32)
30
+ os_urandom = os.urandom(32)
31
+ timestamp = str(secrets.randbits(256)).encode()
32
+
33
+ # Add user input to the entropy sources
34
+ combined = system_random + os_urandom + timestamp + user_input
35
+
36
+ # Hash the combined entropy
37
+ return hashlib.sha256(combined).hexdigest()
@@ -0,0 +1,390 @@
1
+ import click
2
+ import opengradient
3
+ import json
4
+ import ast
5
+ from pathlib import Path
6
+ import logging
7
+ from pprint import pformat
8
+ import webbrowser
9
+ import sys
10
+
11
+ from .client import Client
12
+ from .defaults import *
13
+ from .types import InferenceMode
14
+ from .account import EthAccount, generate_eth_account
15
+
16
+ OG_CONFIG_FILE = Path.home() / '.opengradient_config.json'
17
+
18
+
19
+ def load_og_config():
20
+ if OG_CONFIG_FILE.exists():
21
+ with OG_CONFIG_FILE.open('r') as f:
22
+ return json.load(f)
23
+ return {}
24
+
25
+ def save_og_config(ctx):
26
+ with OG_CONFIG_FILE.open('w') as f:
27
+ json.dump(ctx.obj, f)
28
+
29
+ # Convert string to dictionary click parameter typing
30
+ class DictParamType(click.ParamType):
31
+ name = "dictionary"
32
+
33
+ def convert(self, value, param, ctx):
34
+ if isinstance(value, dict):
35
+ return value
36
+ try:
37
+ # First, try to parse as JSON
38
+ return json.loads(value)
39
+ except json.JSONDecodeError:
40
+ # If JSON parsing fails, try to evaluate as a Python literal
41
+ try:
42
+ # ast.literal_eval is safer than eval as it only parses Python literals
43
+ result = ast.literal_eval(value)
44
+ if not isinstance(result, dict):
45
+ self.fail(f"'{value}' is not a valid dictionary", param, ctx)
46
+ return result
47
+ except (ValueError, SyntaxError):
48
+ self.fail(f"'{value}' is not a valid dictionary", param, ctx)
49
+
50
+ Dict = DictParamType()
51
+
52
+ # Support inference modes
53
+ InferenceModes = {
54
+ "VANILLA": InferenceMode.VANILLA,
55
+ "ZKML": InferenceMode.ZKML,
56
+ "TEE": InferenceMode.TEE,
57
+ }
58
+
59
+
60
+ def initialize_config(ctx):
61
+ """Interactively initialize OpenGradient config"""
62
+ if ctx.obj: # Check if config data already exists
63
+ click.echo("A config already exists. Please run 'opengradient config clear' first if you want to reinitialize.")
64
+ click.echo("You can view your current config with 'opengradient config show'.")
65
+
66
+ click.echo("Initializing OpenGradient config...")
67
+ click.secho(f"Config will be stored in: {OG_CONFIG_FILE}", fg='cyan')
68
+
69
+ # Check if user has an existing account
70
+ has_account = click.confirm("Do you already have an OpenGradient account?", default=True)
71
+
72
+ if not has_account:
73
+ eth_account = create_account_impl()
74
+ if eth_account is None:
75
+ click.echo("Account creation cancelled. Config initialization aborted.")
76
+ return
77
+ ctx.obj['private_key'] = eth_account.private_key
78
+ else:
79
+ ctx.obj['private_key'] = click.prompt("Enter your OpenGradient private key", type=str)
80
+
81
+ # Make email and password optional
82
+ email = click.prompt("Enter your OpenGradient Hub email address (optional, press Enter to skip)",
83
+ type=str, default='', show_default=False)
84
+ ctx.obj['email'] = email if email else None
85
+ password = click.prompt("Enter your OpenGradient Hub password (optional, press Enter to skip)",
86
+ type=str, hide_input=True, default='', show_default=False)
87
+ ctx.obj['password'] = password if password else None
88
+
89
+ ctx.obj['rpc_url'] = DEFAULT_RPC_URL
90
+ ctx.obj['contract_address'] = DEFAULT_INFERENCE_CONTRACT_ADDRESS
91
+
92
+ save_og_config(ctx)
93
+ click.echo("Config has been saved.")
94
+ click.secho("You can run 'opengradient config show' to see configs.", fg='green')
95
+
96
+
97
+ @click.group()
98
+ @click.pass_context
99
+ def cli(ctx):
100
+ """CLI for OpenGradient SDK. Visit https://docs.opengradient.ai/developers/python_sdk/ for more documentation."""
101
+ # Load existing config
102
+ ctx.obj = load_og_config()
103
+
104
+ no_client_commands = ['config', 'create-account', 'version']
105
+
106
+ # Only create client if this is not a config management command
107
+ if ctx.invoked_subcommand in no_client_commands:
108
+ return
109
+
110
+ if all(key in ctx.obj for key in ['private_key', 'rpc_url', 'contract_address']):
111
+ try:
112
+ ctx.obj['client'] = Client(private_key=ctx.obj['private_key'],
113
+ rpc_url=ctx.obj['rpc_url'],
114
+ contract_address=ctx.obj['contract_address'],
115
+ email=ctx.obj.get('email'),
116
+ password=ctx.obj.get('password'))
117
+ except Exception as e:
118
+ click.echo(f"Failed to create OpenGradient client: {str(e)}")
119
+ ctx.exit(1)
120
+ else:
121
+ click.echo("Insufficient information to create client. Some commands may not be available.")
122
+ click.echo("Please run 'opengradient config clear' and/or 'opengradient config init' and to reinitialize your configs.")
123
+ ctx.exit(1)
124
+
125
+
126
+ @cli.group()
127
+ def config():
128
+ """Manage your OpenGradient configuration (credentials etc)"""
129
+ pass
130
+
131
+
132
+ @config.command()
133
+ @click.pass_context
134
+ def init(ctx):
135
+ """Initialize or reinitialize the OpenGradient config"""
136
+ initialize_config(ctx)
137
+
138
+
139
+ @config.command()
140
+ @click.pass_context
141
+ def show(ctx):
142
+ """Display current config information"""
143
+ click.secho(f"Config file location: {OG_CONFIG_FILE}", fg='cyan')
144
+
145
+ if not ctx.obj:
146
+ click.echo("Config is empty. Run 'opengradient config init' to initialize it.")
147
+ return
148
+
149
+ click.echo("Current config:")
150
+ for key, value in ctx.obj.items():
151
+ if key != 'client': # Don't display the client object
152
+ if key == 'password' and value is not None:
153
+ click.echo(f"{key}: {'*' * len(value)}") # Mask the password
154
+ elif value is None:
155
+ click.echo(f"{key}: Not set")
156
+ else:
157
+ click.echo(f"{key}: {value}")
158
+
159
+
160
+ @config.command()
161
+ @click.pass_context
162
+ def clear(ctx):
163
+ """Clear all saved configs"""
164
+ if not ctx.obj:
165
+ click.echo("No configs to clear.")
166
+ return
167
+
168
+ if click.confirm("Are you sure you want to clear all configs? This action cannot be undone.", abort=True):
169
+ ctx.obj.clear()
170
+ save_og_config(ctx)
171
+ click.echo("Configs cleared.")
172
+ else:
173
+ click.echo("Config clear cancelled.")
174
+
175
+
176
+ @cli.command()
177
+ @click.option('--repo', '-r', '--name', 'repo_name', required=True, help='Name of the new model repository')
178
+ @click.option('--description', '-d', required=True, help='Description of the model')
179
+ @click.pass_obj
180
+ def create_model_repo(obj, repo_name: str, description: str):
181
+ """
182
+ Create a new model repository.
183
+
184
+ This command creates a new model repository with the specified name and description.
185
+ The repository name should be unique within your account.
186
+
187
+ Example usage:
188
+
189
+ \b
190
+ opengradient create-model-repo --name "my_new_model" --description "A new model for XYZ task"
191
+ opengradient create-model-repo -n "my_new_model" -d "A new model for XYZ task"
192
+ """
193
+ client: Client = obj['client']
194
+
195
+ try:
196
+ result = client.create_model(repo_name, description)
197
+ click.echo(f"Model repository created successfully: {result}")
198
+ except Exception as e:
199
+ click.echo(f"Error creating model: {str(e)}")
200
+
201
+
202
+ @cli.command()
203
+ @click.option('--repo', '-r', 'repo_name', required=True, help='Name of the existing model repository')
204
+ @click.option('--notes', '-n', help='Version notes (optional)')
205
+ @click.option('--major', '-m', is_flag=True, default=False, help='Flag to indicate a major version update')
206
+ @click.pass_obj
207
+ def create_version(obj, repo_name: str, notes: str, major: bool):
208
+ """Create a new version in an existing model repository.
209
+
210
+ This command creates a new version for the specified model repository.
211
+ You can optionally provide version notes and indicate if it's a major version update.
212
+
213
+ Example usage:
214
+
215
+ \b
216
+ opengradient create-version --repo my_model_repo --notes "Added new feature X" --major
217
+ opengradient create-version -r my_model_repo -n "Bug fixes"
218
+ """
219
+ client: Client = obj['client']
220
+
221
+ try:
222
+ result = client.create_version(repo_name, notes, major)
223
+ click.echo(f"New version created successfully: {result}")
224
+ except Exception as e:
225
+ click.echo(f"Error creating version: {str(e)}")
226
+
227
+
228
+ @cli.command()
229
+ @click.argument('file_path', type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path),
230
+ metavar='FILE_PATH')
231
+ @click.option('--repo', '-r', 'repo_name', required=True, help='Name of the model repository')
232
+ @click.option('--version', '-v', required=True, help='Version of the model (e.g., "0.01")')
233
+ @click.pass_obj
234
+ def upload_file(obj, file_path: Path, repo_name: str, version: str):
235
+ """
236
+ Upload a file to an existing model repository and version.
237
+
238
+ FILE_PATH: Path to the file you want to upload (e.g., model.onnx)
239
+
240
+ Example usage:
241
+
242
+ \b
243
+ opengradient upload-file path/to/model.onnx --repo my_model_repo --version 0.01
244
+ opengradient upload-file path/to/model.onnx -r my_model_repo -v 0.01
245
+ """
246
+ client: Client = obj['client']
247
+
248
+ try:
249
+ result = client.upload(file_path, repo_name, version)
250
+ click.echo(f"File uploaded successfully: {result}")
251
+ except Exception as e:
252
+ click.echo(f"Error uploading model: {str(e)}")
253
+
254
+
255
+ @cli.command()
256
+ @click.option('--model', '-m', 'model_cid', required=True, help='CID of the model to run inference on')
257
+ @click.option('--mode', 'inference_mode', type=click.Choice(InferenceModes.keys()), default="VANILLA",
258
+ help='Inference mode (default: VANILLA)')
259
+ @click.option('--input', '-d', 'input_data', type=Dict, help='Input data for inference as a JSON string')
260
+ @click.option('--input-file', '-f',
261
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path),
262
+ help="JSON file containing input data for inference")
263
+ @click.pass_context
264
+ def infer(ctx, model_cid: str, inference_mode: str, input_data, input_file: Path):
265
+ """
266
+ Run inference on a model.
267
+
268
+ This command runs inference on the specified model using the provided input data.
269
+ You must provide either --input or --input-file, but not both.
270
+
271
+ Example usage:
272
+
273
+ \b
274
+ opengradient infer --model Qm... --mode VANILLA --input '{"key": "value"}'
275
+ opengradient infer -m Qm... -i ZKML -f input_data.json
276
+ """
277
+ client: Client = ctx.obj['client']
278
+
279
+ try:
280
+ if not input_data and not input_file:
281
+ click.echo("Must specify either input_data or input_file")
282
+ ctx.exit(1)
283
+ return
284
+
285
+ if input_data and input_file:
286
+ click.echo("Cannot have both input_data and input_file")
287
+ ctx.exit(1)
288
+ return
289
+
290
+ if input_data:
291
+ model_input = input_data
292
+
293
+ if input_file:
294
+ with input_file.open('r') as file:
295
+ model_input = json.load(file)
296
+
297
+ # Parse input data from string to dict
298
+ click.echo(f"Running {inference_mode} inference for model \"{model_cid}\"\n")
299
+ tx_hash, model_output = client.infer(model_cid=model_cid, inference_mode=InferenceModes[inference_mode], model_input=model_input)
300
+
301
+ click.secho("Success!", fg="green")
302
+ click.echo(f"Transaction hash: {tx_hash}")
303
+ click.echo(f"Inference result:\n{pformat(model_output, indent=2, width=120)}")
304
+ except json.JSONDecodeError as e:
305
+ click.echo(f"Error decoding JSON: {e}", err=True)
306
+ click.echo(f"Error occurred on line {e.lineno}, column {e.colno}", err=True)
307
+ except Exception as e:
308
+ click.echo(f"Error running inference: {str(e)}")
309
+
310
+
311
+ @cli.command()
312
+ def create_account():
313
+ """Create a new test account for OpenGradient inference and model management"""
314
+ create_account_impl()
315
+
316
+
317
+ def create_account_impl() -> EthAccount:
318
+ click.echo("\n" + "=" * 50)
319
+ click.echo("OpenGradient Account Creation Wizard".center(50))
320
+ click.echo("=" * 50 + "\n")
321
+
322
+ click.echo("\n" + "-" * 50)
323
+ click.echo("Step 1: Create Account on OpenGradient Hub")
324
+ click.echo("-" * 50)
325
+
326
+ click.echo(f"Please create an account on the OpenGradient Hub")
327
+ webbrowser.open(DEFAULT_HUB_SIGNUP_URL, new=2)
328
+ click.confirm("Have you successfully created your account on the OpenGradient Hub?", abort=True)
329
+
330
+ click.echo("\n" + "-" * 50)
331
+ click.echo("Step 2: Generate Ethereum Account")
332
+ click.echo("-" * 50)
333
+ eth_account = generate_eth_account()
334
+ click.echo(f"Generated OpenGradient chain account with address: {eth_account.address}")
335
+
336
+ click.echo("\n" + "-" * 50)
337
+ click.echo("Step 3: Fund Your Account")
338
+ click.echo("-" * 50)
339
+ click.echo(f"Please fund your account clicking 'Request' on the Faucet website")
340
+ webbrowser.open(DEFAULT_OG_FAUCET_URL + eth_account.address, new=2)
341
+ click.confirm("Have you successfully funded your account using the Faucet?", abort=True)
342
+
343
+ click.echo("\n" + "=" * 50)
344
+ click.echo("Account Creation Complete!".center(50))
345
+ click.echo("=" * 50)
346
+ click.echo("\nYour OpenGradient account has been successfully created and funded.")
347
+ click.secho(f"Address: {eth_account.address}", fg='green')
348
+ click.secho(f"Private Key: {eth_account.private_key}", fg='green')
349
+ click.secho("\nPlease save this information for your records.\n", fg='cyan')
350
+
351
+ return eth_account
352
+
353
+
354
+ @cli.command()
355
+ def version():
356
+ """Return version of OpenGradient CLI"""
357
+ click.echo(f"OpenGradient CLI version: {opengradient.__version__}")
358
+
359
+
360
+ @cli.command()
361
+ @click.option('--repo', '-r', 'repo_name', required=True, help='Name of the model repository')
362
+ @click.option('--version', '-v', required=True, help='Version of the model (e.g., "0.01")')
363
+ @click.pass_obj
364
+ def list_files(client: Client, repo_name: str, version: str):
365
+ """
366
+ List files for a specific version of a model repository.
367
+
368
+ This command lists all files associated with the specified model repository and version.
369
+
370
+ Example usage:
371
+
372
+ \b
373
+ opengradient list-files --repo my_model_repo --version 0.01
374
+ opengradient list-files -r my_model_repo -v 0.01
375
+ """
376
+ try:
377
+ files = client.list_files(repo_name, version)
378
+ if files:
379
+ click.echo(f"Files for {repo_name} version {version}:")
380
+ for file in files:
381
+ click.echo(f" - {file['name']} (Size: {file['size']} bytes)")
382
+ else:
383
+ click.echo(f"No files found for {repo_name} version {version}")
384
+ except Exception as e:
385
+ click.echo(f"Error listing files: {str(e)}")
386
+
387
+
388
+ if __name__ == '__main__':
389
+ logging.getLogger().setLevel(logging.WARN)
390
+ cli()
@@ -11,8 +11,6 @@ from typing import Dict, Tuple, Union, List
11
11
  from web3.exceptions import ContractLogicError
12
12
  import firebase
13
13
 
14
- logging.basicConfig(level=logging.INFO)
15
-
16
14
  class Client:
17
15
  FIREBASE_CONFIG = {
18
16
  "apiKey": "AIzaSyDUVckVtfl-hiteBzPopy1pDD8Uvfncs7w",
@@ -23,7 +21,7 @@ class Client:
23
21
  "databaseURL": ""
24
22
  }
25
23
 
26
- def __init__(self, private_key: str, rpc_url: str, contract_address: str, email: str = "test@test.com", password: str = "Test-123"):
24
+ def __init__(self, private_key: str, rpc_url: str, contract_address: str, email: str, password: str):
27
25
  """
28
26
  Initialize the Client with private key, RPC URL, and contract address.
29
27
 
@@ -43,6 +41,8 @@ class Client:
43
41
  self.firebase_app = firebase.initialize_app(self.FIREBASE_CONFIG)
44
42
  self.auth = self.firebase_app.auth()
45
43
  self.user = None
44
+ self.email = email
45
+ self.password = password
46
46
 
47
47
  logging.debug("Initialized client with parameters:\n"
48
48
  "private key: %s\n"
@@ -55,7 +55,8 @@ class Client:
55
55
  inference_abi = json.load(abi_file)
56
56
  self.abi = inference_abi
57
57
 
58
- self.login(email, password)
58
+ if email is not None:
59
+ self.login(email, password)
59
60
 
60
61
  def _initialize_web3(self):
61
62
  """
@@ -279,7 +280,6 @@ class Client:
279
280
  logging.error(f"Request exception during upload: {str(e)}")
280
281
  if hasattr(e, 'response') and e.response is not None:
281
282
  logging.error(f"Response status code: {e.response.status_code}")
282
- logging.error(f"Response headers: {e.response.headers}")
283
283
  logging.error(f"Response content: {e.response.text[:1000]}...") # Log first 1000 characters
284
284
  raise OpenGradientError(f"Upload failed due to request exception: {str(e)}",
285
285
  status_code=e.response.status_code if hasattr(e, 'response') else None)
@@ -408,4 +408,49 @@ class Client:
408
408
  return self.user
409
409
  except Exception as e:
410
410
  logging.error(f"Authentication failed: {str(e)}")
411
- raise
411
+ raise
412
+
413
+ def list_files(self, model_name: str, version: str) -> List[Dict]:
414
+ """
415
+ List files for a specific version of a model.
416
+
417
+ Args:
418
+ model_name (str): The unique identifier for the model.
419
+ version (str): The version identifier for the model.
420
+
421
+ Returns:
422
+ List[Dict]: A list of dictionaries containing file information.
423
+
424
+ Raises:
425
+ OpenGradientError: If the file listing fails.
426
+ """
427
+ if not self.user:
428
+ raise ValueError("User not authenticated")
429
+
430
+ url = f"https://api.opengradient.ai/api/v0/models/{model_name}/versions/{version}/files"
431
+ headers = {
432
+ 'Authorization': f'Bearer {self.user["idToken"]}'
433
+ }
434
+
435
+ logging.debug(f"List Files URL: {url}")
436
+ logging.debug(f"Headers: {headers}")
437
+
438
+ try:
439
+ response = requests.get(url, headers=headers)
440
+ response.raise_for_status()
441
+
442
+ json_response = response.json()
443
+ logging.info(f"File listing successful. Number of files: {len(json_response)}")
444
+
445
+ return json_response
446
+
447
+ except requests.RequestException as e:
448
+ logging.error(f"File listing failed: {str(e)}")
449
+ if hasattr(e, 'response') and e.response is not None:
450
+ logging.error(f"Response status code: {e.response.status_code}")
451
+ logging.error(f"Response content: {e.response.text[:1000]}...") # Log first 1000 characters
452
+ raise OpenGradientError(f"File listing failed: {str(e)}",
453
+ status_code=e.response.status_code if hasattr(e, 'response') else None)
454
+ except Exception as e:
455
+ logging.error(f"Unexpected error during file listing: {str(e)}", exc_info=True)
456
+ raise OpenGradientError(f"Unexpected error during file listing: {str(e)}")
@@ -2,6 +2,6 @@
2
2
  # Default variables
3
3
  DEFAULT_PRIVATE_KEY="cd09980ef6e280afc3900d2d6801f9e9c5d858a5deaeeab74a65643f5ff1a4c1"
4
4
  DEFAULT_RPC_URL="http://18.218.115.248:8545"
5
+ DEFAULT_OG_FAUCET_URL="http://18.218.115.248:8080/?address="
6
+ DEFAULT_HUB_SIGNUP_URL="https://hub.opengradient.ai/signup"
5
7
  DEFAULT_INFERENCE_CONTRACT_ADDRESS="0x75D0266DAb643417e9FFD828A1A31C1E039a966c"
6
- DEFAULT_HUB_EMAIL="test@test.com"
7
- DEFAULT_HUB_PASSWORD="Test-123"
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opengradient
3
- Version: 0.2.8
4
- Summary: A Python SDK for OpenGradient inference services
3
+ Version: 0.3.0
4
+ Summary: Python SDK for OpenGradient decentralized model management & inference services
5
5
  Author-email: OpenGradient <oliver@opengradient.ai>
6
6
  License: MIT License
7
7
 
@@ -135,9 +135,9 @@ Requires-Dist: websockets==13.1
135
135
  Requires-Dist: xattr==1.1.0
136
136
  Requires-Dist: yarl==1.13.1
137
137
 
138
- # OpenGradient Python SDK
138
+ # OpenGradient SDK
139
139
 
140
- Python SDK for OpenGradient inference services.
140
+ Python SDK for OpenGradient decentralized model management & inference services.
141
141
 
142
142
  ## Installation
143
143
  ```python
@@ -147,41 +147,47 @@ pip install opengradient
147
147
  ## Quick Start
148
148
  ```python
149
149
  import opengradient as og
150
- og.init(private_key="x", rpc_url="y", contract_address="z")
150
+ og.init(email="<email>", password="<password>")
151
151
  ```
152
152
 
153
- ### Sign in with Email
153
+ ### Create a Model
154
154
  ```python
155
- og.login(email="you@opengradient.ai", password="xyz")
155
+ og.create_model(model_name="<model_name>", model_desc="<model_description>")
156
156
  ```
157
157
 
158
- ### Create a Model
158
+ ### Create a Model (with file upload)
159
159
  ```python
160
- og.create_model(model_name="test-network-model", model_desc="testing upload to sdk")
160
+ og.create_model(model_name="<model_name>", model_desc="<model_description>", model_path="<model_path>")
161
161
  ```
162
162
 
163
163
  ### Create a Version of a Model
164
164
  ```python
165
- og.create_version(model_name="test-network-model", notes="test notes")
165
+ og.create_version(model_name="<model_name>", notes="<model_notes>")
166
166
  ```
167
167
 
168
168
  ### Upload Files to a Model
169
169
  ```python
170
- og.upload(model_path="local_path_to_your_model.onnx", model_name="test-network-model", version="0.01")
170
+ og.upload(model_path="<model_path>", model_name="<model_name>", version="<version>")
171
171
  ```
172
172
 
173
- ### Run Inference
173
+ ### List Files of a Model Version
174
174
  ```python
175
- inference_mode = og.InferenceMode.VANILLA
176
- inference_cid = og.infer(model_cid, model_inputs, inference_mode)
175
+ og.list_files(model_name="<model_name>", version="<version>")
177
176
  ```
178
177
 
178
+ ### Run Inference
179
179
  ```python
180
- og.infer(model_id, inference_mode, model_input)
180
+ inference_mode = og.InferenceMode.VANILLA
181
+ og.infer(model_cid, model_inputs, inference_mode)
181
182
  ```
182
183
 
183
184
  ## Using the CLI
184
185
 
186
+ ```bash
187
+ export OPENGRADIENT_EMAIL="<email>"
188
+ export OPENGRADIENT_PASSWORD="<password>"
189
+ ```
190
+
185
191
  #### Creating a Model
186
192
  ```bash
187
193
  opengradient create_model "<model_name>" "<description>"
@@ -195,7 +201,12 @@ opengradient create_model "<model_name>" "<description>"
195
201
 
196
202
  #### Upload a File
197
203
  ```bash
198
- opengradient upload "path/to/model.onnx" "<model_name>" "<version>"
204
+ opengradient upload "<model_path>" "<model_name>" "<version>"
205
+ ```
206
+
207
+ #### List Files of a Model Version
208
+ ```bash
209
+ opengradient list_files "<model_name>" "<version>"
199
210
  ```
200
211
 
201
212
  #### CLI infer using string
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/opengradient/__init__.py
5
+ src/opengradient/account.py
5
6
  src/opengradient/cli.py
6
7
  src/opengradient/client.py
7
8
  src/opengradient/defaults.py
@@ -15,6 +16,4 @@ src/opengradient.egg-info/entry_points.txt
15
16
  src/opengradient.egg-info/requires.txt
16
17
  src/opengradient.egg-info/top_level.txt
17
18
  src/opengradient/abi/inference.abi
18
- tests/test_api.py
19
- tests/test_exceptions.py
20
- tests/test_integration.py
19
+ src/opengradient/abi/llm.abi
@@ -1,195 +0,0 @@
1
- import click
2
- import os
3
- import opengradient
4
- import json
5
- import ast
6
- from pathlib import Path
7
- from .client import Client
8
- from .defaults import *
9
- from .types import InferenceMode, ModelInput
10
-
11
- # Environment variable names
12
- PRIVATE_KEY_ENV = 'OPENGRADIENT_PRIVATE_KEY'
13
- RPC_URL_ENV = 'OPENGRADIENT_RPC_URL'
14
- CONTRACT_ADDRESS_ENV = 'OPENGRADIENT_CONTRACT_ADDRESS'
15
- EMAIL_ENV = 'OPENGRADIENT_EMAIL'
16
- PASSWORD_ENV = 'OPENGRADIENT_PASSWORD'
17
-
18
- # Convert string to dictionary click parameter typing
19
- class DictParamType(click.ParamType):
20
- name = "dictionary"
21
-
22
- def convert(self, value, param, ctx):
23
- if isinstance(value, dict):
24
- return value
25
- try:
26
- # First, try to parse as JSON
27
- return json.loads(value)
28
- except json.JSONDecodeError:
29
- # If JSON parsing fails, try to evaluate as a Python literal
30
- try:
31
- # ast.literal_eval is safer than eval as it only parses Python literals
32
- result = ast.literal_eval(value)
33
- if not isinstance(result, dict):
34
- self.fail(f"'{value}' is not a valid dictionary", param, ctx)
35
- return result
36
- except (ValueError, SyntaxError):
37
- self.fail(f"'{value}' is not a valid dictionary", param, ctx)
38
-
39
- Dict = DictParamType()
40
-
41
- # Support inference modes
42
- InferenceModes = {
43
- "VANILLA": InferenceMode.VANILLA,
44
- "ZKML": InferenceMode.ZKML,
45
- "TEE": InferenceMode.TEE,
46
- }
47
-
48
- # TODO (Kyle): Once we're farther into development, we should remove the defaults for these options
49
- @click.group()
50
- @click.option('--private_key',
51
- envvar=PRIVATE_KEY_ENV,
52
- help='Your OpenGradient private key',
53
- default=DEFAULT_PRIVATE_KEY)
54
- @click.option('--rpc_url',
55
- envvar=RPC_URL_ENV,
56
- help='OpenGradient RPC URL address',
57
- default=DEFAULT_RPC_URL)
58
- @click.option('--contract_address',
59
- envvar=CONTRACT_ADDRESS_ENV,
60
- help='OpenGradient inference contract address',
61
- default=DEFAULT_INFERENCE_CONTRACT_ADDRESS)
62
- @click.option('--email',
63
- envvar=EMAIL_ENV,
64
- help='Your OpenGradient Hub email address -- not required for inference',
65
- default=DEFAULT_HUB_EMAIL)
66
- @click.option('--password',
67
- envvar=PASSWORD_ENV,
68
- help='Your OpenGradient Hub password -- not required for inference',
69
- default=DEFAULT_HUB_PASSWORD)
70
- @click.pass_context
71
- def cli(ctx, private_key, rpc_url, contract_address, email, password):
72
- """CLI for OpenGradient SDK"""
73
- if not private_key:
74
- click.echo("Please provide a private key via flag or setting environment variable OPENGRADIENT_PRIVATE_KEY")
75
- if not rpc_url:
76
- click.echo("Please provide a RPC URL via flag or setting environment variable OPENGRADIENT_RPC_URL")
77
- if not contract_address:
78
- click.echo("Please provide a contract address via flag or setting environment variable OPENGRADIENT_CONTRACT_ADDRESS")
79
- if not private_key or not rpc_url or not contract_address:
80
- ctx.exit(1)
81
- return
82
-
83
- try:
84
- ctx.obj = Client(private_key=private_key,
85
- rpc_url=rpc_url,
86
- contract_address=contract_address,
87
- email=email,
88
- password=password)
89
- except Exception as e:
90
- click.echo(f"Failed to create OpenGradient client: {str(e)}")
91
-
92
- @cli.command()
93
- @click.pass_context
94
- def client_settings(ctx):
95
- """Display OpenGradient client settings"""
96
- client = ctx.obj
97
- if not client:
98
- click.echo("Client not initialized")
99
- ctx.exit(1)
100
-
101
- click.echo("Settings for OpenGradient client:")
102
- click.echo(f"\tPrivate key ({PRIVATE_KEY_ENV}): {client.private_key}")
103
- click.echo(f"\tRPC URL ({RPC_URL_ENV}): {client.rpc_url}")
104
- click.echo(f"\tContract address ({CONTRACT_ADDRESS_ENV}): {client.contract_address}")
105
- if client.user:
106
- click.echo(f"\tEmail ({EMAIL_ENV}): {client.user["email"]}")
107
- else:
108
- click.echo(f"\tEmail: not set")
109
-
110
- @cli.command()
111
- @click.argument('model_path', type=Path)
112
- @click.argument('model_id', type=str)
113
- @click.argument('version_id', type=str)
114
- @click.pass_obj
115
- def upload(client, model_path, model_id, version_id):
116
- """Upload a model"""
117
- try:
118
- result = client.upload(model_path, model_id, version_id)
119
- click.echo(f"Model uploaded successfully: {result}")
120
- except Exception as e:
121
- click.echo(f"Error uploading model: {str(e)}")
122
-
123
- @cli.command()
124
- @click.argument('model_name', type=str)
125
- @click.argument('model_desc', type=str)
126
- @click.pass_obj
127
- def create_model(client, model_name, model_desc):
128
- """Create a new model"""
129
- try:
130
- result = client.create_model(model_name, model_desc)
131
- click.echo(f"Model created successfully: {result}")
132
- except Exception as e:
133
- click.echo(f"Error creating model: {str(e)}")
134
-
135
- @cli.command()
136
- @click.argument('model_id', type=str)
137
- @click.option('--notes', type=str, default=None, help='Version notes')
138
- @click.option('--is-major', default=False, is_flag=True, help='Is this a major version')
139
- @click.pass_obj
140
- def create_version(client, model_id, notes, is_major):
141
- """Create a new version of a model"""
142
- try:
143
- result = client.create_version(model_id, notes, is_major)
144
- click.echo(f"Version created successfully: {result}")
145
- except Exception as e:
146
- click.echo(f"Error creating version: {str(e)}")
147
-
148
- @cli.command()
149
- @click.argument('model_cid', type=str)
150
- @click.argument('inference_mode', type=click.Choice(InferenceModes.keys()), default="VANILLA")
151
- @click.argument('input_data', type=Dict, required=False)
152
- @click.option('--input_file',
153
- type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path),
154
- help="Optional file input for model inference -- must be JSON")
155
- @click.pass_context
156
- def infer(ctx, model_cid, inference_mode, input_data, input_file):
157
- """Run inference on a model"""
158
- client = ctx.obj
159
- try:
160
- if not input_data and not input_file:
161
- click.echo("Must specify either input_data or input_file")
162
- ctx.exit(1)
163
- return
164
-
165
- if input_data and input_file:
166
- click.echo("Cannot have both input_data and input_file")
167
- ctx.exit(1)
168
- return
169
-
170
- if input_data:
171
- model_input = input_data
172
-
173
- if input_file:
174
- with input_file.open('r') as file:
175
- model_input = json.load(file)
176
-
177
- # Parse input data from string to dict
178
- click.echo(f"Running {inference_mode} inference for {model_cid}...")
179
- tx_hash, model_output = client.infer(model_cid=model_cid, inference_mode=InferenceModes[inference_mode], model_input=model_input)
180
- click.secho("Success!", fg="green")
181
- click.echo(f"\nTransaction Hash: \n{tx_hash}")
182
- click.echo(f"\nInference result: \n{model_output}")
183
- except json.JSONDecodeError as e:
184
- click.echo(f"Error decoding JSON: {e}", err=True)
185
- click.echo(f"Error occurred on line {e.lineno}, column {e.colno}", err=True)
186
- except Exception as e:
187
- click.echo(f"Error running inference: {str(e)}")
188
-
189
-
190
- @cli.command()
191
- def version():
192
- click.echo(f"OpenGradient CLI version: {opengradient.__version__}")
193
-
194
- if __name__ == '__main__':
195
- cli()
@@ -1,110 +0,0 @@
1
- import sys
2
- import os
3
-
4
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
5
-
6
- from src.client import Client
7
- from src.exceptions import FileNotFoundError, UploadError, InferenceError, ResultRetrievalError, AuthenticationError, RateLimitError, InvalidInputError
8
-
9
- import pytest
10
- from unittest.mock import patch, Mock, mock_open
11
- import requests
12
- import time
13
-
14
- @pytest.fixture
15
- def client():
16
- return Client(api_key="test_api_key")
17
-
18
- def test_upload_success(client):
19
- with patch('src.client.requests.request') as mock_request, \
20
- patch('src.client.os.path.exists', return_value=True), \
21
- patch('builtins.open', mock_open(read_data=b'fake_file_content')):
22
- mock_response = Mock()
23
- mock_response.json.return_value = {"model_cid": "test_cid"}
24
- mock_response.raise_for_status.return_value = None
25
- mock_request.return_value = mock_response
26
-
27
- result = client.upload("test_model.onnx")
28
- assert result == {"model_cid": "test_cid"}
29
-
30
- def test_upload_file_not_found(client):
31
- with pytest.raises(FileNotFoundError):
32
- client.upload("non_existent_file.onnx")
33
-
34
- def test_upload_authentication_error(client):
35
- with patch('src.client.requests.request') as mock_request, \
36
- patch('src.client.os.path.exists', return_value=True), \
37
- patch('builtins.open', mock_open(read_data=b'fake_file_content')):
38
- mock_response = Mock()
39
- mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(response=Mock(status_code=401))
40
- mock_request.return_value = mock_response
41
-
42
- with pytest.raises(UploadError):
43
- client.upload("test_model.onnx")
44
-
45
- # def test_infer_success(client):
46
- # with patch('src.client.requests.request') as mock_request:
47
- # mock_response = Mock()
48
- # mock_response.json.return_value = {"inference_cid": "test_inference_cid"}
49
- # mock_response.raise_for_status.return_value = None
50
- # mock_request.return_value = mock_response
51
-
52
- # result = client.infer("test_model_cid", {"input": [1, 2, 3]})
53
- # assert result == {"inference_cid": "test_inference_cid"}
54
-
55
- # def test_get_results_not_ready(client):
56
- # with patch('src.client.requests.request') as mock_request:
57
- # mock_response = Mock()
58
- # mock_response.json.return_value = {"status": "processing"}
59
- # mock_response.raise_for_status.return_value = None
60
- # mock_request.return_value = mock_response
61
-
62
- # result, proof = client.get_results("test_inference_cid")
63
- # assert result is None
64
- # assert proof is None
65
-
66
- # def test_get_results_success(client):
67
- # with patch('src.client.requests.request') as mock_request:
68
- # mock_response = Mock()
69
- # mock_response.json.return_value = {"status": "completed", "data": "test_result", "proof": "test_proof"}
70
- # mock_response.raise_for_status.return_value = None
71
- # mock_request.return_value = mock_response
72
-
73
- # result, proof = client.get_results("test_inference_cid")
74
- # assert result == "test_result"
75
- # assert proof == "test_proof"
76
-
77
- # def test_get_results_failed(client):
78
- # with patch('src.client.requests.request') as mock_request:
79
- # mock_response = Mock()
80
- # mock_response.json.return_value = {"status": "failed", "error": "Test error"}
81
- # mock_response.raise_for_status.return_value = None
82
- # mock_request.return_value = mock_response
83
-
84
- # with pytest.raises(InferenceError, match="Inference failed: Test error"):
85
- # client.get_results("test_inference_cid")
86
-
87
- def test_infer_success(client):
88
- with patch('src.client.requests.request') as mock_request:
89
- mock_response = Mock()
90
- mock_response.json.return_value = {"inference_cid": "test_inference_cid"}
91
- mock_response.raise_for_status.return_value = None
92
- mock_request.return_value = mock_response
93
-
94
- result = client.infer("test_model_cid", {"input": [1, 2, 3]})
95
- assert result == {"inference_cid": "test_inference_cid"}
96
-
97
- mock_request.assert_called_once_with(
98
- "POST",
99
- "http://localhost:5002/infer",
100
- json={
101
- "model_cid": "test_model_cid",
102
- "model_inputs": {"input": [1, 2, 3]},
103
- "contract_address": "0x1234567890123456789012345678901234567890"
104
- },
105
- headers={"Authorization": "Bearer test_api_key"}
106
- )
107
-
108
- def test_infer_invalid_input(client):
109
- with pytest.raises(InvalidInputError):
110
- client.infer("test_model_cid", {"invalid_input": "value"})
@@ -1,25 +0,0 @@
1
- import pytest
2
- from src.exceptions import (
3
- OpenGradientError, FileNotFoundError, UploadError, InferenceError,
4
- ResultRetrievalError, AuthenticationError, RateLimitError, InvalidInputError,
5
- ServerError, TimeoutError, NetworkError, UnsupportedModelError, InsufficientCreditsError
6
- )
7
-
8
- def test_open_gradient_error():
9
- error = OpenGradientError("Test error", status_code=400)
10
- assert str(error) == "Test error (Status code: 400)"
11
-
12
- def test_file_not_found_error():
13
- error = FileNotFoundError("test_file.onnx")
14
- assert str(error) == "File not found: test_file.onnx"
15
-
16
- def test_upload_error():
17
- error = UploadError("Upload failed", file_path="test_file.onnx")
18
- assert str(error) == "Upload failed (Status code: None)"
19
- assert error.file_path == "test_file.onnx"
20
-
21
- def test_insufficient_credits_error():
22
- error = InsufficientCreditsError(required_credits=100, available_credits=50)
23
- assert str(error) == "Insufficient credits (Status code: None) (Required: 100, Available: 50)"
24
-
25
- # Add more tests for other exception classes
@@ -1,20 +0,0 @@
1
- import pytest
2
- from src.client import Client
3
- import os
4
-
5
- @pytest.mark.integration
6
- def test_upload_integration():
7
- client = Client(api_key="test_api_key", base_url="http://localhost:5002")
8
-
9
- # Create a temporary test file
10
- test_file_path = "test_model.onnx"
11
- with open(test_file_path, "wb") as f:
12
- f.write(b"fake model content")
13
-
14
- try:
15
- result = client.upload(test_file_path)
16
- assert "model_cid" in result
17
- print(f"Upload successful. Model CID: {result['model_cid']}")
18
- finally:
19
- # Clean up the test file
20
- os.remove(test_file_path)
File without changes
File without changes