opengradient 0.4.14__py3-none-any.whl → 0.5.0a1__py3-none-any.whl

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.
opengradient/cli.py CHANGED
@@ -18,6 +18,7 @@ from .defaults import (
18
18
  DEFAULT_OG_FAUCET_URL,
19
19
  DEFAULT_RPC_URL,
20
20
  DEFAULT_API_URL,
21
+ DEFAULT_LLM_SERVER_URL,
21
22
  )
22
23
  from .types import InferenceMode, LlmInferenceMode, LLM, TEE_LLM
23
24
 
@@ -119,17 +120,21 @@ def cli(ctx):
119
120
 
120
121
  Visit https://docs.opengradient.ai/developers/python_sdk/ for more documentation.
121
122
  """
122
- # Load existing config
123
123
  ctx.obj = load_og_config()
124
124
 
125
125
  no_client_commands = ["config", "create-account", "version"]
126
126
 
127
- # Only create client if this is not a config management command
128
127
  if ctx.invoked_subcommand in no_client_commands:
129
128
  return
130
129
 
131
130
  if all(key in ctx.obj for key in ["private_key"]):
132
131
  try:
132
+ # Extract API keys from config
133
+ llm_server_url = ctx.obj.get("llm_server_url", DEFAULT_LLM_SERVER_URL)
134
+ openai_api_key = ctx.obj.get("openai_api_key")
135
+ anthropic_api_key = ctx.obj.get("anthropic_api_key")
136
+ google_api_key = ctx.obj.get("google_api_key")
137
+
133
138
  ctx.obj["client"] = Client(
134
139
  private_key=ctx.obj["private_key"],
135
140
  rpc_url=DEFAULT_RPC_URL,
@@ -137,6 +142,10 @@ def cli(ctx):
137
142
  contract_address=DEFAULT_INFERENCE_CONTRACT_ADDRESS,
138
143
  email=ctx.obj.get("email"),
139
144
  password=ctx.obj.get("password"),
145
+ llm_server_url=llm_server_url,
146
+ openai_api_key=openai_api_key,
147
+ anthropic_api_key=anthropic_api_key,
148
+ google_api_key=google_api_key,
140
149
  )
141
150
  except Exception as e:
142
151
  click.echo(f"Failed to create OpenGradient client: {str(e)}")
@@ -197,6 +206,50 @@ def clear(ctx):
197
206
  click.echo("Config clear cancelled.")
198
207
 
199
208
 
209
+ @config.command()
210
+ @click.option("--provider", type=click.Choice(["openai", "anthropic", "google"]), required=True)
211
+ @click.option("--key", required=True, help="API key for the provider")
212
+ @click.pass_context
213
+ def set_api_key(ctx, provider: str, key: str):
214
+ """
215
+ Set API key for external LLM providers.
216
+
217
+ Example usage:
218
+
219
+ \b
220
+ opengradient config set-api-key --provider openai --key ..
221
+ opengradient config set-api-key --provider anthropic --key ...
222
+ opengradient config set-api-key --provider google --key ...
223
+ """
224
+ config_key = f"{provider}_api_key"
225
+ ctx.obj[config_key] = key
226
+ save_og_config(ctx)
227
+
228
+ click.secho(f"✅ API key for {provider} has been set", fg="green")
229
+ click.echo("You can now use models from this provider in completion and chat commands.")
230
+
231
+
232
+ @config.command()
233
+ @click.option("--provider", type=click.Choice(["openai", "anthropic", "google"]), required=True)
234
+ @click.pass_context
235
+ def remove_api_key(ctx, provider: str):
236
+ """
237
+ Remove API key for an external LLM provider.
238
+
239
+ Example usage:
240
+
241
+ \b
242
+ opengradient config remove-api-key --provider openai
243
+ """
244
+ config_key = f"{provider}_api_key"
245
+ if config_key in ctx.obj:
246
+ del ctx.obj[config_key]
247
+ save_og_config(ctx)
248
+ click.secho(f"✅ API key for {provider} has been removed", fg="green")
249
+ else:
250
+ click.echo(f"No API key found for {provider}")
251
+
252
+
200
253
  @cli.command()
201
254
  @click.option("--repo", "-r", "--name", "repo_name", required=True, help="Name of the new model repository")
202
255
  @click.option("--description", "-d", required=True, help="Description of the model")
@@ -354,33 +407,55 @@ def infer(ctx, model_cid: str, inference_mode: str, input_data, input_file: Path
354
407
  "--model",
355
408
  "-m",
356
409
  "model_cid",
357
- type=click.Choice([e.value for e in LLM]),
358
410
  required=True,
359
- help="CID of the LLM model to run inference on",
411
+ help="Model identifier (local model from LLM enum or external model like 'gpt-4o', 'gemini-2.5-flash-lite', etc.)",
360
412
  )
361
413
  @click.option(
362
- "--mode", "inference_mode", type=click.Choice(LlmInferenceModes.keys()), default="VANILLA", help="Inference mode (default: VANILLA)"
414
+ "--mode",
415
+ "inference_mode",
416
+ type=click.Choice(LlmInferenceModes.keys()),
417
+ default="VANILLA",
418
+ help="Inference mode (only applies to local models, default: VANILLA)"
363
419
  )
364
420
  @click.option("--prompt", "-p", required=True, help="Input prompt for the LLM completion")
365
421
  @click.option("--max-tokens", type=int, default=100, help="Maximum number of tokens for LLM completion output")
366
422
  @click.option("--stop-sequence", multiple=True, help="Stop sequences for LLM")
367
423
  @click.option("--temperature", type=float, default=0.0, help="Temperature for LLM inference (0.0 to 1.0)")
424
+ @click.option("--local", is_flag=True, help="Force use of local model even if not in LLM enum")
368
425
  @click.pass_context
369
- def completion(ctx, model_cid: str, inference_mode: str, prompt: str, max_tokens: int, stop_sequence: List[str], temperature: float):
426
+ def completion(ctx, model_cid: str, inference_mode: str, prompt: str, max_tokens: int, stop_sequence: List[str], temperature: float, local: bool):
370
427
  """
371
- Run completion inference on an LLM model.
428
+ Run completion inference on an LLM model (local or external).
372
429
 
373
- This command runs a completion inference on the specified LLM model using the provided prompt and parameters.
430
+ This command supports both local OpenGradient models and external providers
431
+ (OpenAI, Anthropic, Google, etc.). For external models, make sure to set
432
+ the appropriate API key using 'opengradient config set-api-key'.
374
433
 
375
434
  Example usage:
376
435
 
377
436
  \b
378
- opengradient completion --model meta-llama/Meta-Llama-3-8B-Instruct --prompt "Hello, how are you?" --max-tokens 50 --temperature 0.7
379
- opengradient completion -m meta-llama/Meta-Llama-3-8B-Instruct -p "Translate to French: Hello world" --stop-sequence "." --stop-sequence "\\n"
437
+ # Local model
438
+ opengradient completion --model meta-llama/Meta-Llama-3-8B-Instruct --prompt "Hello, how are you?" --max-tokens 50
439
+
440
+ # External OpenAI model
441
+ opengradient completion --model gpt-4o --prompt "Translate to French: Hello world" --max-tokens 50
442
+
443
+ # External Anthropic model
444
+ opengradient completion --model claude-haiku-4-5-20251001--prompt "Write a haiku about coding" --max-tokens 100
445
+
446
+ # External Google model
447
+ opengradient completion --model gemini-2.5-flash-lite --prompt "Explain quantum computing" --max-tokens 200
380
448
  """
381
449
  client: Client = ctx.obj["client"]
450
+
382
451
  try:
383
- click.echo(f'Running LLM completion inference for model "{model_cid}"\n')
452
+ is_local = local or model_cid in [llm.value for llm in LLM]
453
+
454
+ if is_local:
455
+ click.echo(f'Running LLM completion inference for local model "{model_cid}"\n')
456
+ else:
457
+ click.echo(f'Running LLM completion inference for external model "{model_cid}"\n')
458
+
384
459
  completion_output = client.llm_completion(
385
460
  model_cid=model_cid,
386
461
  inference_mode=LlmInferenceModes[inference_mode],
@@ -388,23 +463,31 @@ def completion(ctx, model_cid: str, inference_mode: str, prompt: str, max_tokens
388
463
  max_tokens=max_tokens,
389
464
  stop_sequence=list(stop_sequence),
390
465
  temperature=temperature,
466
+ local_model=local,
391
467
  )
392
468
 
393
- print_llm_completion_result(model_cid, completion_output.transaction_hash, completion_output.completion_output)
469
+ print_llm_completion_result(model_cid, completion_output.transaction_hash, completion_output.completion_output, is_local)
470
+
394
471
  except Exception as e:
395
472
  click.echo(f"Error running LLM completion: {str(e)}")
396
473
 
397
474
 
398
- def print_llm_completion_result(model_cid, tx_hash, llm_output):
475
+ def print_llm_completion_result(model_cid, tx_hash, llm_output, is_local=True):
399
476
  click.secho("✅ LLM completion Successful", fg="green", bold=True)
400
477
  click.echo("──────────────────────────────────────")
401
- click.echo("Model CID: ", nl=False)
478
+ click.echo("Model: ", nl=False)
402
479
  click.secho(model_cid, fg="cyan", bold=True)
403
- click.echo("Transaction hash: ", nl=False)
404
- click.secho(tx_hash, fg="cyan", bold=True)
405
- block_explorer_link = f"{DEFAULT_BLOCKCHAIN_EXPLORER}0x{tx_hash}"
406
- click.echo("Block explorer link: ", nl=False)
407
- click.secho(block_explorer_link, fg="blue", underline=True)
480
+
481
+ if is_local and tx_hash != "external":
482
+ click.echo("Transaction hash: ", nl=False)
483
+ click.secho(tx_hash, fg="cyan", bold=True)
484
+ block_explorer_link = f"{DEFAULT_BLOCKCHAIN_EXPLORER}0x{tx_hash}"
485
+ click.echo("Block explorer link: ", nl=False)
486
+ click.secho(block_explorer_link, fg="blue", underline=True)
487
+ else:
488
+ click.echo("Source: ", nl=False)
489
+ click.secho("External Provider", fg="cyan", bold=True)
490
+
408
491
  click.echo("──────────────────────────────────────")
409
492
  click.secho("LLM Output:", fg="yellow", bold=True)
410
493
  click.echo()
@@ -417,12 +500,15 @@ def print_llm_completion_result(model_cid, tx_hash, llm_output):
417
500
  "--model",
418
501
  "-m",
419
502
  "model_cid",
420
- type=click.Choice([e.value for e in LLM]),
421
503
  required=True,
422
- help="CID of the LLM model to run inference on",
504
+ help="Model identifier (local model from LLM enum or external model like 'gpt-4o', 'gemini-2.5-flash-lite', etc.)",
423
505
  )
424
506
  @click.option(
425
- "--mode", "inference_mode", type=click.Choice(LlmInferenceModes.keys()), default="VANILLA", help="Inference mode (default: VANILLA)"
507
+ "--mode",
508
+ "inference_mode",
509
+ type=click.Choice(LlmInferenceModes.keys()),
510
+ default="VANILLA",
511
+ help="Inference mode (only applies to local models, default: VANILLA)"
426
512
  )
427
513
  @click.option("--messages", type=str, required=False, help="Input messages for the chat inference in JSON format")
428
514
  @click.option(
@@ -436,9 +522,13 @@ def print_llm_completion_result(model_cid, tx_hash, llm_output):
436
522
  @click.option("--temperature", type=float, default=0.0, help="Temperature for LLM inference (0.0 to 1.0)")
437
523
  @click.option("--tools", type=str, default=None, help="Tool configurations in JSON format")
438
524
  @click.option(
439
- "--tools-file", type=click.Path(exists=True, path_type=Path), required=False, help="Path to JSON file containing tool configurations"
525
+ "--tools-file",
526
+ type=click.Path(exists=True, path_type=Path),
527
+ required=False,
528
+ help="Path to JSON file containing tool configurations"
440
529
  )
441
530
  @click.option("--tool-choice", type=str, default="", help="Specific tool choice for the LLM")
531
+ @click.option("--local", is_flag=True, help="Force use of local model even if not in LLM enum")
442
532
  @click.pass_context
443
533
  def chat(
444
534
  ctx,
@@ -452,23 +542,37 @@ def chat(
452
542
  tools: Optional[str],
453
543
  tools_file: Optional[Path],
454
544
  tool_choice: Optional[str],
545
+ local: bool,
455
546
  ):
456
547
  """
457
- Run chat inference on an LLM model.
548
+ Run chat inference on an LLM model (local or external).
458
549
 
459
- This command runs a chat inference on the specified LLM model using the provided messages and parameters.
460
-
461
- Tool call formatting is based on OpenAI documentation tool calls (see here: https://platform.openai.com/docs/guides/function-calling).
550
+ This command supports both local OpenGradient models and external providers.
551
+ Tool calling is supported for compatible models.
462
552
 
463
553
  Example usage:
464
554
 
465
555
  \b
466
- opengradient chat --model meta-llama/Meta-Llama-3-8B-Instruct --messages '[{"role":"user","content":"hello"}]' --max-tokens 50 --temperature 0.7
467
- opengradient chat --model mistralai/Mistral-7B-Instruct-v0.3 --messages-file messages.json --tools-file tools.json --max-tokens 200 --stop-sequence "." --stop-sequence "\\n"
556
+ # Local model
557
+ opengradient chat --model meta-llama/Meta-Llama-3-8B-Instruct --messages '[{"role":"user","content":"hello"}]' --max-tokens 50
558
+
559
+ # External OpenAI model with tools
560
+ opengradient chat --model gpt-4o --messages-file messages.json --tools-file tools.json --max-tokens 200
561
+
562
+ # External Anthropic model
563
+ opengradient chat --model claude-haiku-4-5-20251001 --messages '[{"role":"user","content":"Write a poem"}]' --max-tokens 100
468
564
  """
469
565
  client: Client = ctx.obj["client"]
566
+
470
567
  try:
471
- click.echo(f'Running LLM chat inference for model "{model_cid}"\n')
568
+ is_local = local or model_cid in [llm.value for llm in LLM]
569
+
570
+ if is_local:
571
+ click.echo(f'Running LLM chat inference for local model "{model_cid}"\n')
572
+ else:
573
+ click.echo(f'Running LLM chat inference for external model "{model_cid}"\n')
574
+
575
+ # Parse messages
472
576
  if not messages and not messages_file:
473
577
  click.echo("Must specify either messages or messages-file")
474
578
  ctx.exit(1)
@@ -488,10 +592,10 @@ def chat(
488
592
  with messages_file.open("r") as file:
489
593
  messages = json.load(file)
490
594
 
491
- # Parse tools if provided
595
+ # Parse tools
492
596
  if (tools and tools != "[]") and tools_file:
493
597
  click.echo("Cannot have both tools and tools-file")
494
- click.exit(1)
598
+ ctx.exit(1)
495
599
  return
496
600
 
497
601
  parsed_tools = []
@@ -532,23 +636,37 @@ def chat(
532
636
  temperature=temperature,
533
637
  tools=parsed_tools,
534
638
  tool_choice=tool_choice,
639
+ local_model=local,
535
640
  )
536
641
 
537
- print_llm_chat_result(model_cid, completion_output.transaction_hash, completion_output.finish_reason, completion_output.chat_output)
642
+ print_llm_chat_result(
643
+ model_cid,
644
+ completion_output.transaction_hash,
645
+ completion_output.finish_reason,
646
+ completion_output.chat_output,
647
+ is_local
648
+ )
649
+
538
650
  except Exception as e:
539
651
  click.echo(f"Error running LLM chat inference: {str(e)}")
540
652
 
541
653
 
542
- def print_llm_chat_result(model_cid, tx_hash, finish_reason, chat_output):
654
+ def print_llm_chat_result(model_cid, tx_hash, finish_reason, chat_output, is_local=True):
543
655
  click.secho("✅ LLM Chat Successful", fg="green", bold=True)
544
656
  click.echo("──────────────────────────────────────")
545
- click.echo("Model CID: ", nl=False)
657
+ click.echo("Model: ", nl=False)
546
658
  click.secho(model_cid, fg="cyan", bold=True)
547
- click.echo("Transaction hash: ", nl=False)
548
- click.secho(tx_hash, fg="cyan", bold=True)
549
- block_explorer_link = f"{DEFAULT_BLOCKCHAIN_EXPLORER}0x{tx_hash}"
550
- click.echo("Block explorer link: ", nl=False)
551
- click.secho(block_explorer_link, fg="blue", underline=True)
659
+
660
+ if is_local and tx_hash != "external":
661
+ click.echo("Transaction hash: ", nl=False)
662
+ click.secho(tx_hash, fg="cyan", bold=True)
663
+ block_explorer_link = f"{DEFAULT_BLOCKCHAIN_EXPLORER}0x{tx_hash}"
664
+ click.echo("Block explorer link: ", nl=False)
665
+ click.secho(block_explorer_link, fg="blue", underline=True)
666
+ else:
667
+ click.echo("Source: ", nl=False)
668
+ click.secho("External Provider", fg="cyan", bold=True)
669
+
552
670
  click.echo("──────────────────────────────────────")
553
671
  click.secho("Finish Reason: ", fg="yellow", bold=True)
554
672
  click.echo()
@@ -557,7 +675,6 @@ def print_llm_chat_result(model_cid, tx_hash, finish_reason, chat_output):
557
675
  click.secho("Chat Output:", fg="yellow", bold=True)
558
676
  click.echo()
559
677
  for key, value in chat_output.items():
560
- # If the value doesn't give any information, don't print it
561
678
  if value != None and value != "" and value != "[]" and value != []:
562
679
  click.echo(f"{key}: {value}")
563
680
  click.echo()
opengradient/client.py CHANGED
@@ -30,7 +30,7 @@ from .types import (
30
30
  ModelRepository,
31
31
  FileUploadResult,
32
32
  )
33
- from .defaults import DEFAULT_IMAGE_GEN_HOST, DEFAULT_IMAGE_GEN_PORT, DEFAULT_SCHEDULER_ADDRESS
33
+ from .defaults import DEFAULT_IMAGE_GEN_HOST, DEFAULT_IMAGE_GEN_PORT, DEFAULT_SCHEDULER_ADDRESS, DEFAULT_LLM_SERVER_URL
34
34
  from .utils import convert_array_to_model_output, convert_to_model_input, convert_to_model_output
35
35
 
36
36
  _FIREBASE_CONFIG = {
@@ -62,7 +62,21 @@ class Client:
62
62
  _api_url: str
63
63
  _inference_abi: Dict
64
64
  _precompile_abi: Dict
65
- def __init__(self, private_key: str, rpc_url: str, api_url: str, contract_address: str, email: Optional[str], password: Optional[str]):
65
+ _llm_server_url: str
66
+ _external_api_keys: Dict[str, str]
67
+ def __init__(
68
+ self,
69
+ private_key: str,
70
+ rpc_url: str,
71
+ api_url: str,
72
+ contract_address: str,
73
+ email: Optional[str] = None,
74
+ password: Optional[str] = None,
75
+ llm_server_url: Optional[str] = DEFAULT_LLM_SERVER_URL,
76
+ openai_api_key: Optional[str] = None,
77
+ anthropic_api_key: Optional[str] = None,
78
+ google_api_key: Optional[str] = None,
79
+ ):
66
80
  """
67
81
  Initialize the Client with private key, RPC URL, and contract address.
68
82
 
@@ -91,6 +105,70 @@ class Client:
91
105
  else:
92
106
  self._hub_user = None
93
107
 
108
+ self._llm_server_url = llm_server_url
109
+
110
+ self._external_api_keys = {}
111
+ if openai_api_key or os.getenv("OPENAI_API_KEY"):
112
+ self._external_api_keys["openai"] = openai_api_key or os.getenv("OPENAI_API_KEY")
113
+ if anthropic_api_key or os.getenv("ANTHROPIC_API_KEY"):
114
+ self._external_api_keys["anthropic"] = anthropic_api_key or os.getenv("ANTHROPIC_API_KEY")
115
+ if google_api_key or os.getenv("GOOGLE_API_KEY"):
116
+ self._external_api_keys["google"] = google_api_key or os.getenv("GOOGLE_API_KEY")
117
+
118
+ def set_api_key(self, provider: str, api_key: str):
119
+ """
120
+ Set or update API key for an external provider.
121
+
122
+ Args:
123
+ provider: Provider name (e.g., 'openai', 'anthropic', 'google')
124
+ api_key: The API key for the provider
125
+ """
126
+ self._external_api_keys[provider] = api_key
127
+
128
+ def _is_local_model(self, model_cid: str) -> bool:
129
+ """
130
+ Check if a model is hosted locally on OpenGradient.
131
+
132
+ Args:
133
+ model_cid: Model identifier
134
+
135
+ Returns:
136
+ True if model is local, False if it should use external provider
137
+ """
138
+ # Check if it's in our local LLM enum
139
+ try:
140
+ return model_cid in [llm.value for llm in LLM]
141
+ except:
142
+ return False
143
+
144
+ def _get_provider_from_model(self, model: str) -> str:
145
+ """Infer provider from model name."""
146
+ model_lower = model.lower()
147
+
148
+ if "gpt" in model_lower or model.startswith("openai/"):
149
+ return "openai"
150
+ elif "claude" in model_lower or model.startswith("anthropic/"):
151
+ return "anthropic"
152
+ elif "gemini" in model_lower or "palm" in model_lower or model.startswith("google/"):
153
+ return "google"
154
+ elif "command" in model_lower or model.startswith("cohere/"):
155
+ return "cohere"
156
+ else:
157
+ return "openai"
158
+
159
+ def _get_api_key_for_model(self, model: str) -> Optional[str]:
160
+ """
161
+ Get the appropriate API key for a model.
162
+
163
+ Args:
164
+ model: Model identifier
165
+
166
+ Returns:
167
+ API key string or None
168
+ """
169
+ provider = self._get_provider_from_model(model)
170
+ return self._external_api_keys.get(provider)
171
+
94
172
  def _login_to_hub(self, email, password):
95
173
  try:
96
174
  firebase_app = firebase.initialize_app(_FIREBASE_CONFIG)
@@ -328,36 +406,48 @@ class Client:
328
406
 
329
407
  def llm_completion(
330
408
  self,
331
- model_cid: LLM,
332
- inference_mode: LlmInferenceMode,
409
+ model_cid: str, # Changed from LLM to str to accept any model
333
410
  prompt: str,
334
411
  max_tokens: int = 100,
335
412
  stop_sequence: Optional[List[str]] = None,
336
413
  temperature: float = 0.0,
414
+ inference_mode: LlmInferenceMode = LlmInferenceMode.VANILLA,
337
415
  max_retries: Optional[int] = None,
416
+ local_model: Optional[bool] = False,
338
417
  ) -> TextGenerationOutput:
339
418
  """
340
419
  Perform inference on an LLM model using completions.
341
420
 
342
421
  Args:
343
- model_cid (LLM): The unique content identifier for the model.
344
- inference_mode (InferenceMode): The inference mode.
422
+ model_cid (str): The unique content identifier for the model.
423
+ inference_mode (LlmInferenceMode): The inference mode (only used for local models).
345
424
  prompt (str): The input prompt for the LLM.
346
425
  max_tokens (int): Maximum number of tokens for LLM output. Default is 100.
347
426
  stop_sequence (List[str], optional): List of stop sequences for LLM. Default is None.
348
427
  temperature (float): Temperature for LLM inference, between 0 and 1. Default is 0.0.
428
+ max_retries (int, optional): Maximum number of retry attempts for blockchain transactions.
429
+ local_model (bool, optional): Force use of local model even if not in LLM enum.
349
430
 
350
431
  Returns:
351
432
  TextGenerationOutput: Generated text results including:
352
- - Transaction hash
433
+ - Transaction hash (or "external" for external providers)
353
434
  - String of completion output
354
435
 
355
436
  Raises:
356
437
  OpenGradientError: If the inference fails.
357
438
  """
358
-
439
+ # Check if this is a local model or external
440
+ if not local_model and not self._is_local_model(model_cid):
441
+ return self._external_llm_completion(
442
+ model=model_cid,
443
+ prompt=prompt,
444
+ max_tokens=max_tokens,
445
+ stop_sequence=stop_sequence,
446
+ temperature=temperature,
447
+ )
448
+
449
+ # Original local model logic
359
450
  def execute_transaction():
360
- # Check inference mode and supported model
361
451
  if inference_mode != LlmInferenceMode.VANILLA and inference_mode != LlmInferenceMode.TEE:
362
452
  raise OpenGradientError("Invalid inference mode %s: Inference mode must be VANILLA or TEE" % inference_mode)
363
453
 
@@ -366,14 +456,13 @@ class Client:
366
456
 
367
457
  contract = self._blockchain.eth.contract(address=self._inference_hub_contract_address, abi=self._inference_abi)
368
458
 
369
- # Prepare LLM input
370
459
  llm_request = {
371
460
  "mode": inference_mode.value,
372
461
  "modelCID": model_cid,
373
462
  "prompt": prompt,
374
463
  "max_tokens": max_tokens,
375
464
  "stop_sequence": stop_sequence or [],
376
- "temperature": int(temperature * 100), # Scale to 0-100 range
465
+ "temperature": int(temperature * 100),
377
466
  }
378
467
  logging.debug(f"Prepared LLM request: {llm_request}")
379
468
 
@@ -390,80 +479,117 @@ class Client:
390
479
 
391
480
  return run_with_retry(execute_transaction, max_retries)
392
481
 
482
+ def _external_llm_completion(
483
+ self,
484
+ model: str,
485
+ prompt: str,
486
+ max_tokens: int = 100,
487
+ stop_sequence: Optional[List[str]] = None,
488
+ temperature: float = 0.0,
489
+ ) -> TextGenerationOutput:
490
+ """
491
+ Route completion request to external LLM server.
492
+
493
+ Args:
494
+ model: Model identifier
495
+ prompt: Input prompt
496
+ max_tokens: Maximum tokens to generate
497
+ stop_sequence: Stop sequences
498
+ temperature: Sampling temperature
499
+
500
+ Returns:
501
+ TextGenerationOutput with completion
502
+
503
+ Raises:
504
+ OpenGradientError: If request fails
505
+ """
506
+ url = f"{self._llm_server_url}/v1/completions"
507
+
508
+ headers = {"Content-Type": "application/json"}
509
+ api_key = self._get_api_key_for_model(model)
510
+ if api_key:
511
+ headers["Authorization"] = f"Bearer {api_key}"
512
+
513
+ payload = {
514
+ "model": model,
515
+ "prompt": prompt,
516
+ "max_tokens": max_tokens,
517
+ "temperature": temperature,
518
+ }
519
+
520
+ if stop_sequence:
521
+ payload["stop"] = stop_sequence
522
+
523
+ try:
524
+ response = requests.post(url, json=payload, headers=headers, timeout=60)
525
+ response.raise_for_status()
526
+
527
+ result = response.json()
528
+
529
+ return TextGenerationOutput(
530
+ transaction_hash="external", # No blockchain transaction for external
531
+ completion_output=result["completion"]
532
+ )
533
+
534
+ except requests.RequestException as e:
535
+ error_msg = f"External LLM completion failed: {str(e)}"
536
+ if hasattr(e, 'response') and e.response is not None:
537
+ try:
538
+ error_detail = e.response.json()
539
+ error_msg += f" - {error_detail}"
540
+ except:
541
+ error_msg += f" - {e.response.text}"
542
+ logging.error(error_msg)
543
+ raise OpenGradientError(error_msg)
544
+
393
545
  def llm_chat(
394
546
  self,
395
- model_cid: LLM,
396
- inference_mode: LlmInferenceMode,
547
+ model_cid: str, # Changed from LLM to str
397
548
  messages: List[Dict],
549
+ inference_mode: LlmInferenceMode = LlmInferenceMode.VANILLA,
398
550
  max_tokens: int = 100,
399
551
  stop_sequence: Optional[List[str]] = None,
400
552
  temperature: float = 0.0,
401
553
  tools: Optional[List[Dict]] = [],
402
554
  tool_choice: Optional[str] = None,
403
555
  max_retries: Optional[int] = None,
556
+ local_model: Optional[bool] = False,
404
557
  ) -> TextGenerationOutput:
405
558
  """
406
559
  Perform inference on an LLM model using chat.
407
560
 
408
561
  Args:
409
- model_cid (LLM): The unique content identifier for the model.
410
- inference_mode (InferenceMode): The inference mode.
411
- messages (dict): The messages that will be passed into the chat.
412
- This should be in OpenAI API format (https://platform.openai.com/docs/api-reference/chat/create)
413
- Example:
414
- [
415
- {
416
- "role": "system",
417
- "content": "You are a helpful assistant."
418
- },
419
- {
420
- "role": "user",
421
- "content": "Hello!"
422
- }
423
- ]
562
+ model_cid (str): The unique content identifier for the model.
563
+ inference_mode (LlmInferenceMode): The inference mode (only used for local models).
564
+ messages (List[Dict]): The messages that will be passed into the chat.
424
565
  max_tokens (int): Maximum number of tokens for LLM output. Default is 100.
425
- stop_sequence (List[str], optional): List of stop sequences for LLM. Default is None.
426
- temperature (float): Temperature for LLM inference, between 0 and 1. Default is 0.0.
427
- tools (List[dict], optional): Set of tools
428
- This should be in OpenAI API format (https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools)
429
- Example:
430
- [
431
- {
432
- "type": "function",
433
- "function": {
434
- "name": "get_current_weather",
435
- "description": "Get the current weather in a given location",
436
- "parameters": {
437
- "type": "object",
438
- "properties": {
439
- "location": {
440
- "type": "string",
441
- "description": "The city and state, e.g. San Francisco, CA"
442
- },
443
- "unit": {
444
- "type": "string",
445
- "enum": ["celsius", "fahrenheit"]
446
- }
447
- },
448
- "required": ["location"]
449
- }
450
- }
451
- }
452
- ]
453
- tool_choice (str, optional): Sets a specific tool to choose. Default value is "auto".
566
+ stop_sequence (List[str], optional): List of stop sequences for LLM.
567
+ temperature (float): Temperature for LLM inference, between 0 and 1.
568
+ tools (List[dict], optional): Set of tools for function calling.
569
+ tool_choice (str, optional): Sets a specific tool to choose.
570
+ max_retries (int, optional): Maximum number of retry attempts.
571
+ local_model (bool, optional): Force use of local model.
454
572
 
455
573
  Returns:
456
- TextGenerationOutput: Generated text results including:
457
- - Transaction hash
458
- - Finish reason (tool_call, stop, etc.)
459
- - Dictionary of chat message output (role, content, tool_call, etc.)
574
+ TextGenerationOutput: Generated text results.
460
575
 
461
576
  Raises:
462
577
  OpenGradientError: If the inference fails.
463
578
  """
464
-
579
+ # Check if this is a local model or external
580
+ if not local_model and not self._is_local_model(model_cid):
581
+ return self._external_llm_chat(
582
+ model=model_cid,
583
+ messages=messages,
584
+ max_tokens=max_tokens,
585
+ stop_sequence=stop_sequence,
586
+ temperature=temperature,
587
+ tools=tools,
588
+ tool_choice=tool_choice,
589
+ )
590
+
591
+ # Original local model logic
465
592
  def execute_transaction():
466
- # Check inference mode and supported model
467
593
  if inference_mode != LlmInferenceMode.VANILLA and inference_mode != LlmInferenceMode.TEE:
468
594
  raise OpenGradientError("Invalid inference mode %s: Inference mode must be VANILLA or TEE" % inference_mode)
469
595
 
@@ -472,7 +598,6 @@ class Client:
472
598
 
473
599
  contract = self._blockchain.eth.contract(address=self._inference_hub_contract_address, abi=self._inference_abi)
474
600
 
475
- # For incoming chat messages, tool_calls can be empty. Add an empty array so that it will fit the ABI.
476
601
  for message in messages:
477
602
  if "tool_calls" not in message:
478
603
  message["tool_calls"] = []
@@ -481,7 +606,6 @@ class Client:
481
606
  if "name" not in message:
482
607
  message["name"] = ""
483
608
 
484
- # Create simplified tool structure for smart contract
485
609
  converted_tools = []
486
610
  if tools is not None:
487
611
  for tool in tools:
@@ -496,14 +620,13 @@ class Client:
496
620
  raise OpenGradientError("Chat LLM failed to convert parameters into JSON: %s", e)
497
621
  converted_tools.append(converted_tool)
498
622
 
499
- # Prepare LLM input
500
623
  llm_request = {
501
624
  "mode": inference_mode.value,
502
625
  "modelCID": model_cid,
503
626
  "messages": messages,
504
627
  "max_tokens": max_tokens,
505
628
  "stop_sequence": stop_sequence or [],
506
- "temperature": int(temperature * 100), # Scale to 0-100 range
629
+ "temperature": int(temperature * 100),
507
630
  "tools": converted_tools or [],
508
631
  "tool_choice": tool_choice if tool_choice else ("" if tools is None else "auto"),
509
632
  }
@@ -529,6 +652,78 @@ class Client:
529
652
 
530
653
  return run_with_retry(execute_transaction, max_retries)
531
654
 
655
+ def _external_llm_chat(
656
+ self,
657
+ model: str,
658
+ messages: List[Dict],
659
+ max_tokens: int = 100,
660
+ stop_sequence: Optional[List[str]] = None,
661
+ temperature: float = 0.0,
662
+ tools: Optional[List[Dict]] = None,
663
+ tool_choice: Optional[str] = None,
664
+ ) -> TextGenerationOutput:
665
+ """
666
+ Route chat request to external LLM server.
667
+
668
+ Args:
669
+ model: Model identifier
670
+ messages: List of chat messages
671
+ max_tokens: Maximum tokens to generate
672
+ stop_sequence: Stop sequences
673
+ temperature: Sampling temperature
674
+ tools: Function calling tools
675
+ tool_choice: Tool selection strategy
676
+
677
+ Returns:
678
+ TextGenerationOutput with chat completion
679
+
680
+ Raises:
681
+ OpenGradientError: If request fails
682
+ """
683
+ url = f"{self._llm_server_url}/v1/chat/completions"
684
+
685
+ headers = {"Content-Type": "application/json"}
686
+ api_key = self._get_api_key_for_model(model)
687
+ if api_key:
688
+ headers["Authorization"] = f"Bearer {api_key}"
689
+
690
+ payload = {
691
+ "model": model,
692
+ "messages": messages,
693
+ "max_tokens": max_tokens,
694
+ "temperature": temperature,
695
+ }
696
+
697
+ if stop_sequence:
698
+ payload["stop"] = stop_sequence
699
+
700
+ if tools:
701
+ payload["tools"] = tools
702
+ payload["tool_choice"] = tool_choice or "auto"
703
+
704
+ try:
705
+ response = requests.post(url, json=payload, headers=headers, timeout=60)
706
+ response.raise_for_status()
707
+
708
+ result = response.json()
709
+
710
+ return TextGenerationOutput(
711
+ transaction_hash="external", # No blockchain transaction for external
712
+ finish_reason=result["finish_reason"],
713
+ chat_output=result["message"]
714
+ )
715
+
716
+ except requests.RequestException as e:
717
+ error_msg = f"External LLM chat failed: {str(e)}"
718
+ if hasattr(e, 'response') and e.response is not None:
719
+ try:
720
+ error_detail = e.response.json()
721
+ error_msg += f" - {error_detail}"
722
+ except:
723
+ error_msg += f" - {e.response.text}"
724
+ logging.error(error_msg)
725
+ raise OpenGradientError(error_msg)
726
+
532
727
  def list_files(self, model_name: str, version: str) -> List[Dict]:
533
728
  """
534
729
  List files for a specific version of a model.
opengradient/defaults.py CHANGED
@@ -7,4 +7,5 @@ DEFAULT_INFERENCE_CONTRACT_ADDRESS = "0x8383C9bD7462F12Eb996DD02F78234C0421A6FaE
7
7
  DEFAULT_SCHEDULER_ADDRESS = "0x7179724De4e7FF9271FA40C0337c7f90C0508eF6"
8
8
  DEFAULT_BLOCKCHAIN_EXPLORER = "https://explorer.opengradient.ai/tx/"
9
9
  DEFAULT_IMAGE_GEN_HOST = "18.217.25.69"
10
- DEFAULT_IMAGE_GEN_PORT = 5125
10
+ DEFAULT_IMAGE_GEN_PORT = 5125
11
+ DEFAULT_LLM_SERVER_URL = "http://35.225.197.84:8000"
@@ -1,34 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opengradient
3
- Version: 0.4.14
3
+ Version: 0.5.0a1
4
4
  Summary: Python SDK for OpenGradient decentralized model management & inference services
5
- Author-email: OpenGradient <oliver@opengradient.ai>
6
- License: MIT License
7
-
8
- Copyright (c) 2024 OpenGradient
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
5
+ Author-email: OpenGradient <kyle@vannalabs.ai>
6
+ License-Expression: MIT
28
7
  Project-URL: Homepage, https://opengradient.ai
29
8
  Classifier: Development Status :: 3 - Alpha
30
9
  Classifier: Intended Audience :: Developers
31
- Classifier: License :: OSI Approved :: MIT License
32
10
  Classifier: Programming Language :: Python :: 3.10
33
11
  Classifier: Programming Language :: Python :: 3.11
34
12
  Classifier: Programming Language :: Python :: 3.12
@@ -1,8 +1,8 @@
1
1
  opengradient/__init__.py,sha256=hFvN9sCEIQGWjT4Qnel6oQjadgAhDXMti72tdrP3sIo,12611
2
2
  opengradient/account.py,sha256=5wrYpws_1lozjOFjLCTHtxgoxK-LmObDAaVy9eDcJY4,1145
3
- opengradient/cli.py,sha256=dSIX6ieOh8ql9GdtE-mn_exSQFywh7Os0rt1ZRoHROk,25488
4
- opengradient/client.py,sha256=YpWsbzK1xHID5yuvkQ_kfDj6Qoncwq7zNZANLqMusNg,48702
5
- opengradient/defaults.py,sha256=IRk9aWFgctSKEQrF4Gm_D07AFy2dHCa3rKsMf0BYAls,552
3
+ opengradient/cli.py,sha256=QzjH_KS6TF8gm_L1otFWA-oHkJ5SSfizFoRn0xR0b70,29162
4
+ opengradient/client.py,sha256=mcI4xC8XnOFQdROexSJojvTlEu6HiWzVJVquOOXcDms,55278
5
+ opengradient/defaults.py,sha256=TuaFLZ-sb9v0YOE9oKmPUCYHuXG7FbUZXxdGUIaaZ4w,605
6
6
  opengradient/exceptions.py,sha256=88tfegboGtlehQcwhxsl6ZzhLJWZWlkf_bkHTiCtXpo,3391
7
7
  opengradient/types.py,sha256=JH8hjpcq4H7gQki74cuJpH4PcyQpwivLiqn9iq0evrI,5715
8
8
  opengradient/utils.py,sha256=ZUq4OBIml2vsC0tRqus4Zwb_e3g4woo00apByrafuVw,8058
@@ -27,9 +27,9 @@ opengradient/workflow_models/constants.py,sha256=viIkb_LGcfVprqQNaA80gBTj6cfYam0
27
27
  opengradient/workflow_models/types.py,sha256=Z22hF6c8Y4D2GlzVEIBODGwsqSjSrQvUcpZ7R-mIJdI,409
28
28
  opengradient/workflow_models/utils.py,sha256=ySfpuiOBqLTlfto6ZxZf2vc7K6RGIja0l4eaVm5AOzY,1503
29
29
  opengradient/workflow_models/workflow_models.py,sha256=d4C_gs39DAfy4cdY9Ee6GMXpPfzwvKFpmxzK1A7LNgU,3900
30
- opengradient-0.4.14.dist-info/licenses/LICENSE,sha256=xEcvQ3AxZOtDkrqkys2Mm6Y9diEnaSeQRKvxi-JGnNA,1069
31
- opengradient-0.4.14.dist-info/METADATA,sha256=yk1_p5Av6i73J-D3cnFzF5WZWSew5Hr38WQ5bFYWLrw,5237
32
- opengradient-0.4.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- opengradient-0.4.14.dist-info/entry_points.txt,sha256=yUKTaJx8RXnybkob0J62wVBiCp_1agVbgw9uzsmaeJc,54
34
- opengradient-0.4.14.dist-info/top_level.txt,sha256=oC1zimVLa2Yi1LQz8c7x-0IQm92milb5ax8gHBHwDqU,13
35
- opengradient-0.4.14.dist-info/RECORD,,
30
+ opengradient-0.5.0a1.dist-info/licenses/LICENSE,sha256=xEcvQ3AxZOtDkrqkys2Mm6Y9diEnaSeQRKvxi-JGnNA,1069
31
+ opengradient-0.5.0a1.dist-info/METADATA,sha256=EnQ7ZzcjIeci9WUgxbtCCmHxR7VyAKfLG44kr3BHTsw,3959
32
+ opengradient-0.5.0a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ opengradient-0.5.0a1.dist-info/entry_points.txt,sha256=yUKTaJx8RXnybkob0J62wVBiCp_1agVbgw9uzsmaeJc,54
34
+ opengradient-0.5.0a1.dist-info/top_level.txt,sha256=oC1zimVLa2Yi1LQz8c7x-0IQm92milb5ax8gHBHwDqU,13
35
+ opengradient-0.5.0a1.dist-info/RECORD,,