clarifai 11.8.0__py3-none-any.whl → 11.8.2__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.
- clarifai/__init__.py +1 -1
- clarifai/cli/model.py +43 -14
- clarifai/cli/templates/model_templates.py +1 -1
- clarifai/client/base.py +54 -16
- clarifai/client/dataset.py +18 -6
- clarifai/client/model.py +21 -12
- clarifai/client/model_client.py +2 -0
- clarifai/client/module.py +14 -13
- clarifai/client/nodepool.py +3 -1
- clarifai/client/pipeline.py +23 -23
- clarifai/client/pipeline_step.py +20 -18
- clarifai/client/search.py +35 -11
- clarifai/client/user.py +180 -5
- clarifai/client/workflow.py +18 -17
- clarifai/runners/models/model_builder.py +149 -17
- clarifai/runners/models/model_runner.py +21 -24
- clarifai/runners/server.py +1 -0
- clarifai/runners/utils/code_script.py +12 -1
- clarifai/utils/cli.py +62 -0
- clarifai/utils/constants.py +5 -3
- clarifai/utils/secrets.py +7 -2
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/METADATA +4 -3
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/RECORD +27 -27
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/WHEEL +0 -0
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/entry_points.txt +0 -0
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/licenses/LICENSE +0 -0
- {clarifai-11.8.0.dist-info → clarifai-11.8.2.dist-info}/top_level.txt +0 -0
clarifai/client/search.py
CHANGED
@@ -91,14 +91,21 @@ class Search(Lister, BaseClient):
|
|
91
91
|
)
|
92
92
|
Lister.__init__(self, page_size=1000)
|
93
93
|
|
94
|
-
def _get_annot_proto(self, **kwargs):
|
94
|
+
def _get_annot_proto(self, **kwargs) -> resources_pb2.Annotation:
|
95
95
|
"""Get an Annotation proto message based on keyword arguments.
|
96
96
|
|
97
97
|
Args:
|
98
|
-
**kwargs: Keyword arguments specifying the
|
98
|
+
**kwargs: Keyword arguments specifying the annotation data.
|
99
|
+
Supported keys:
|
100
|
+
- image_bytes (bytes): Raw image bytes
|
101
|
+
- image_url (str): URL to an image
|
102
|
+
- concepts (List[Dict]): List of concept dictionaries
|
103
|
+
- metadata (Dict): Metadata dictionary
|
104
|
+
- geo_longitude (float): Geographic longitude
|
105
|
+
- geo_latitude (float): Geographic latitude
|
99
106
|
|
100
107
|
Returns:
|
101
|
-
resources_pb2.Annotation: An Annotation proto message.
|
108
|
+
resources_pb2.Annotation: An Annotation proto message with the specified data.
|
102
109
|
"""
|
103
110
|
if not kwargs:
|
104
111
|
return resources_pb2.Annotation()
|
@@ -139,14 +146,24 @@ class Search(Lister, BaseClient):
|
|
139
146
|
raise UserError(f"kwargs contain key that is not supported: {key}")
|
140
147
|
return resources_pb2.Annotation(data=self.data_proto)
|
141
148
|
|
142
|
-
def _get_input_proto(self, **kwargs):
|
149
|
+
def _get_input_proto(self, **kwargs) -> resources_pb2.Input:
|
143
150
|
"""Get an Input proto message based on keyword arguments.
|
144
151
|
|
145
152
|
Args:
|
146
|
-
**kwargs: Keyword arguments specifying the
|
153
|
+
**kwargs: Keyword arguments specifying the input data.
|
154
|
+
Supported keys:
|
155
|
+
- input_types (List[str]): List of input types ('image', 'text', 'audio', 'video')
|
156
|
+
- dataset_ids (List[str]): List of dataset IDs to filter by
|
157
|
+
- image_bytes (bytes): Raw image bytes
|
158
|
+
- image_url (str): URL to an image
|
159
|
+
- text_raw (str): Raw text content
|
160
|
+
- concepts (List[Dict]): List of concept dictionaries
|
161
|
+
- metadata (Dict): Metadata dictionary
|
162
|
+
- geo_longitude (float): Geographic longitude
|
163
|
+
- geo_latitude (float): Geographic latitude
|
147
164
|
|
148
165
|
Returns:
|
149
|
-
resources_pb2.Input: An Input proto message.
|
166
|
+
resources_pb2.Input: An Input proto message with the specified data.
|
150
167
|
"""
|
151
168
|
if not kwargs:
|
152
169
|
return resources_pb2.Input()
|
@@ -194,15 +211,22 @@ class Search(Lister, BaseClient):
|
|
194
211
|
def _list_topk_generator(
|
195
212
|
self, endpoint: Callable[..., Any], proto_message: Any, request_data: Dict[str, Any]
|
196
213
|
) -> Generator[Dict[str, Any], None, None]:
|
197
|
-
"""Lists
|
214
|
+
"""Lists top-k results with pagination support.
|
215
|
+
|
216
|
+
This method handles pagination for search results when top_k is specified,
|
217
|
+
automatically calculating the required number of pages and per-page limits.
|
198
218
|
|
199
219
|
Args:
|
200
|
-
endpoint (Callable): The endpoint to call.
|
201
|
-
proto_message (Any): The
|
202
|
-
request_data (
|
220
|
+
endpoint (Callable[..., Any]): The gRPC endpoint method to call for search.
|
221
|
+
proto_message (Any): The protobuf message class for the request.
|
222
|
+
request_data (Dict[str, Any]): The base request data dictionary.
|
203
223
|
|
204
224
|
Yields:
|
205
|
-
|
225
|
+
Dict[str, Any]: Individual search result items from the API response.
|
226
|
+
|
227
|
+
Raises:
|
228
|
+
UserError: If pagination limits are exceeded or top_k is too large.
|
229
|
+
Exception: If the API request fails.
|
206
230
|
"""
|
207
231
|
max_pages = ceil(self.top_k / self.default_page_size)
|
208
232
|
total_hits = 0
|
clarifai/client/user.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from typing import Any, Dict, Generator, List
|
2
|
+
from typing import Any, Dict, Generator, List, Optional
|
3
3
|
|
4
4
|
import yaml
|
5
5
|
from clarifai_grpc.grpc.api import resources_pb2, service_pb2
|
@@ -53,14 +53,17 @@ class User(Lister, BaseClient):
|
|
53
53
|
Lister.__init__(self)
|
54
54
|
|
55
55
|
def list_apps(
|
56
|
-
self,
|
56
|
+
self,
|
57
|
+
filter_by: Dict[str, Any] = {},
|
58
|
+
page_no: Optional[int] = None,
|
59
|
+
per_page: Optional[int] = None,
|
57
60
|
) -> Generator[App, None, None]:
|
58
61
|
"""Lists all the apps for the user.
|
59
62
|
|
60
63
|
Args:
|
61
|
-
filter_by (
|
62
|
-
page_no (int): The page number to list.
|
63
|
-
per_page (int): The number of items per page.
|
64
|
+
filter_by (Dict[str, Any]): A dictionary of filters to be applied to the list of apps.
|
65
|
+
page_no (Optional[int]): The page number to list. If None, lists all pages.
|
66
|
+
per_page (Optional[int]): The number of items per page. If None, uses default.
|
64
67
|
|
65
68
|
Yields:
|
66
69
|
App: App objects for the user.
|
@@ -533,6 +536,178 @@ class User(Lister, BaseClient):
|
|
533
536
|
]
|
534
537
|
return f"Clarifai User Details: \n{', '.join(attribute_strings)}\n"
|
535
538
|
|
539
|
+
def get_secret(self, secret_id: str) -> dict:
|
540
|
+
"""Returns a secret object if exists.
|
541
|
+
|
542
|
+
Args:
|
543
|
+
secret_id (str): The secret ID to interact with
|
544
|
+
|
545
|
+
Returns:
|
546
|
+
Dict: A dictionary containing information about the existing secret ID.
|
547
|
+
|
548
|
+
Example:
|
549
|
+
>>> from clarifai.client.user import User
|
550
|
+
>>> client = User(user_id="user_id")
|
551
|
+
>>> secret_info = client.get_secret(secret_id="secret_id")
|
552
|
+
"""
|
553
|
+
request = service_pb2.GetSecretRequest(user_app_id=self.user_app_id, id=secret_id)
|
554
|
+
response = self._grpc_request(self.STUB.GetSecret, request)
|
555
|
+
if response.status.code != status_code_pb2.SUCCESS:
|
556
|
+
raise Exception(
|
557
|
+
f"""Error getting secret, are you sure this is a valid secret id {secret_id} at the user_id
|
558
|
+
{self.user_app_id.user_id}.
|
559
|
+
Error: {response.status.description}"""
|
560
|
+
)
|
561
|
+
|
562
|
+
dict_response = MessageToDict(response, preserving_proto_field_name=True)
|
563
|
+
kwargs = self.process_response_keys(dict_response["secret"], "secret")
|
564
|
+
|
565
|
+
return dict(auth=self.auth_helper, **kwargs)
|
566
|
+
|
567
|
+
def list_secrets(
|
568
|
+
self, page_no: int = None, per_page: int = None
|
569
|
+
) -> Generator[dict, None, None]:
|
570
|
+
"""List all secrets for the user
|
571
|
+
|
572
|
+
Args:
|
573
|
+
page_no (int): The page number to list.
|
574
|
+
per_page (int): The number of items per page.
|
575
|
+
|
576
|
+
Yields:
|
577
|
+
Dict: Dictionaries containing information about the secrets.
|
578
|
+
|
579
|
+
Example:
|
580
|
+
>>> from clarifai.client.user import User
|
581
|
+
>>> client = User(user_id="user_id")
|
582
|
+
>>> all_secrets = list(client.list_secrets())
|
583
|
+
|
584
|
+
Note:
|
585
|
+
Defaults to 16 per page if page_no is specified and per_page is not specified.
|
586
|
+
If both page_no and per_page are None, then lists all the resources.
|
587
|
+
"""
|
588
|
+
request_data = dict(user_app_id=self.user_app_id)
|
589
|
+
all_secrets_info = self.list_pages_generator(
|
590
|
+
self.STUB.ListSecrets,
|
591
|
+
service_pb2.ListSecretsRequest,
|
592
|
+
request_data,
|
593
|
+
per_page=per_page,
|
594
|
+
page_no=page_no,
|
595
|
+
)
|
596
|
+
for secret_info in all_secrets_info:
|
597
|
+
yield dict(auth=self.auth_helper, **secret_info)
|
598
|
+
|
599
|
+
def create_secrets(self, secrets: List[Dict[str, Any]]) -> List[dict]:
|
600
|
+
"""Creates secrets for the user.
|
601
|
+
|
602
|
+
Args:
|
603
|
+
secrets (List[Dict[str, Any]]): List of secret configurations to create.
|
604
|
+
Each secret dict can contain:
|
605
|
+
- id (str): The name/ID of the secret (required)
|
606
|
+
- value (str): The secret value (required)
|
607
|
+
- description (str): Optional description of the secret
|
608
|
+
- expires_at (str): Optional expiration timestamp
|
609
|
+
|
610
|
+
Returns:
|
611
|
+
List[Dict]: List of dictionaries containing information about the created secrets.
|
612
|
+
|
613
|
+
Example:
|
614
|
+
>>> from clarifai.client.user import User
|
615
|
+
>>> client = User(user_id="user_id")
|
616
|
+
>>> secrets = [{"id": "secret1", "value": "secret_value", "description": "My Secret"}]
|
617
|
+
>>> created_secrets = client.create_secrets(secrets)
|
618
|
+
"""
|
619
|
+
assert isinstance(secrets, list), "secrets param should be a list"
|
620
|
+
|
621
|
+
# Convert dict secrets to protobuf Secret objects
|
622
|
+
secret_objects = []
|
623
|
+
for secret_config in secrets:
|
624
|
+
secret_objects.append(resources_pb2.Secret(**secret_config))
|
625
|
+
|
626
|
+
request = service_pb2.PostSecretsRequest(
|
627
|
+
user_app_id=self.user_app_id, secrets=secret_objects
|
628
|
+
)
|
629
|
+
response = self._grpc_request(self.STUB.PostSecrets, request)
|
630
|
+
if response.status.code != status_code_pb2.SUCCESS:
|
631
|
+
raise Exception(response.status)
|
632
|
+
|
633
|
+
self.logger.info(f"Secrets created successfully:\n{response.status}")
|
634
|
+
|
635
|
+
# Convert response to list of dictionaries
|
636
|
+
dict_response = MessageToDict(response, preserving_proto_field_name=True)
|
637
|
+
created_secrets = []
|
638
|
+
for secret in dict_response.get("secrets", []):
|
639
|
+
kwargs = self.process_response_keys(secret, "secret")
|
640
|
+
created_secrets.append(dict(auth=self.auth_helper, **kwargs))
|
641
|
+
|
642
|
+
return created_secrets
|
643
|
+
|
644
|
+
def patch_secrets(
|
645
|
+
self, secrets: List[Dict[str, Any]], action: str = 'overwrite'
|
646
|
+
) -> List[dict]:
|
647
|
+
"""Patches secrets for the user.
|
648
|
+
|
649
|
+
Args:
|
650
|
+
secrets (List[Dict[str, Any]]): List of secret configurations to patch.
|
651
|
+
Each secret dict should contain:
|
652
|
+
- id (str): The name/ID of the secret to patch (required)
|
653
|
+
- value (str): Optional new secret value
|
654
|
+
- description (str): Optional new description
|
655
|
+
- expires_at (str): Optional new expiration timestamp
|
656
|
+
action (str): The action to perform on the secrets (overwrite/remove).
|
657
|
+
|
658
|
+
Returns:
|
659
|
+
List[Dict]: List of dictionaries containing information about the patched secrets.
|
660
|
+
|
661
|
+
Example:
|
662
|
+
>>> from clarifai.client.user import User
|
663
|
+
>>> client = User(user_id="user_id")
|
664
|
+
>>> secrets = [{"id": "secret1", "description": "Updated Secret Description"}]
|
665
|
+
>>> patched_secrets = client.patch_secrets(secrets, action="overwrite")
|
666
|
+
"""
|
667
|
+
assert isinstance(secrets, list), "secrets param should be a list"
|
668
|
+
|
669
|
+
# Convert dict secrets to protobuf Secret objects
|
670
|
+
secret_objects = []
|
671
|
+
for secret_config in secrets:
|
672
|
+
secret_objects.append(resources_pb2.Secret(**secret_config))
|
673
|
+
|
674
|
+
request = service_pb2.PatchSecretsRequest(
|
675
|
+
user_app_id=self.user_app_id, secret=secret_objects, action=action
|
676
|
+
)
|
677
|
+
response = self._grpc_request(self.STUB.PatchSecrets, request)
|
678
|
+
if response.status.code != status_code_pb2.SUCCESS:
|
679
|
+
raise Exception(response.status)
|
680
|
+
|
681
|
+
self.logger.info(f"Secrets patched successfully:\n{response.status}")
|
682
|
+
|
683
|
+
# Convert response to list of dictionaries
|
684
|
+
dict_response = MessageToDict(response, preserving_proto_field_name=True)
|
685
|
+
patched_secrets = []
|
686
|
+
for secret in dict_response.get("secrets", []):
|
687
|
+
kwargs = self.process_response_keys(secret, "secret")
|
688
|
+
patched_secrets.append(dict(auth=self.auth_helper, **kwargs))
|
689
|
+
|
690
|
+
return patched_secrets
|
691
|
+
|
692
|
+
def delete_secrets(self, secret_ids: List[str]) -> None:
|
693
|
+
"""Deletes a list of secrets for the user.
|
694
|
+
|
695
|
+
Args:
|
696
|
+
secret_ids (List[str]): The secret IDs of the user to delete.
|
697
|
+
|
698
|
+
Example:
|
699
|
+
>>> from clarifai.client.user import User
|
700
|
+
>>> client = User(user_id="user_id")
|
701
|
+
>>> client.delete_secrets(secret_ids=["secret_id1", "secret_id2"])
|
702
|
+
"""
|
703
|
+
assert isinstance(secret_ids, list), "secret_ids param should be a list"
|
704
|
+
|
705
|
+
request = service_pb2.DeleteSecretsRequest(user_app_id=self.user_app_id, ids=secret_ids)
|
706
|
+
response = self._grpc_request(self.STUB.DeleteSecrets, request)
|
707
|
+
if response.status.code != status_code_pb2.SUCCESS:
|
708
|
+
raise Exception(response.status)
|
709
|
+
self.logger.info("\nSecrets Deleted\n%s", response.status)
|
710
|
+
|
536
711
|
def list_models(
|
537
712
|
self,
|
538
713
|
user_id: str = None,
|
clarifai/client/workflow.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
from typing import Dict, Generator, List
|
3
|
+
from typing import Any, Dict, Generator, List, Optional
|
4
4
|
|
5
5
|
from clarifai_grpc.grpc.api import resources_pb2, service_pb2
|
6
6
|
from clarifai_grpc.grpc.api.resources_pb2 import Input
|
@@ -25,29 +25,30 @@ class Workflow(Lister, BaseClient):
|
|
25
25
|
|
26
26
|
def __init__(
|
27
27
|
self,
|
28
|
-
url: str = None,
|
29
|
-
workflow_id: str = None,
|
30
|
-
workflow_version: Dict = {'id': ""},
|
31
|
-
output_config: Dict = {'min_value': 0},
|
28
|
+
url: Optional[str] = None,
|
29
|
+
workflow_id: Optional[str] = None,
|
30
|
+
workflow_version: Dict[str, str] = {'id': ""},
|
31
|
+
output_config: Dict[str, Any] = {'min_value': 0},
|
32
32
|
base_url: str = DEFAULT_BASE,
|
33
|
-
pat: str = None,
|
34
|
-
token: str = None,
|
35
|
-
root_certificates_path: str = None,
|
33
|
+
pat: Optional[str] = None,
|
34
|
+
token: Optional[str] = None,
|
35
|
+
root_certificates_path: Optional[str] = None,
|
36
36
|
**kwargs,
|
37
37
|
):
|
38
38
|
"""Initializes a Workflow object.
|
39
39
|
|
40
40
|
Args:
|
41
|
-
url (str): The URL to initialize the workflow object.
|
42
|
-
workflow_id (str): The Workflow ID to interact with.
|
43
|
-
workflow_version (
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
url (Optional[str]): The URL to initialize the workflow object.
|
42
|
+
workflow_id (Optional[str]): The Workflow ID to interact with.
|
43
|
+
workflow_version (Dict[str, str]): The Workflow Version to interact with.
|
44
|
+
Defaults to {'id': ""} for latest version.
|
45
|
+
output_config (Dict[str, Any]): The output config to interact with.
|
46
|
+
- min_value (float): The minimum value of the prediction confidence to filter.
|
47
|
+
- max_concepts (int): The maximum number of concepts to return.
|
48
|
+
- select_concepts (List[Concept]): The concepts to select.
|
49
|
+
- sample_ms (int): The number of milliseconds to sample.
|
49
50
|
base_url (str): Base API url. Default "https://api.clarifai.com"
|
50
|
-
pat (str): A personal access token for authentication. Can be set as env var CLARIFAI_PAT
|
51
|
+
pat (Optional[str]): A personal access token for authentication. Can be set as env var CLARIFAI_PAT
|
51
52
|
token (str): A session token for authentication. Accepts either a session token or a pat. Can be set as env var CLARIFAI_SESSION_TOKEN
|
52
53
|
root_certificates_path (str): Path to the SSL root certificates file, used to establish secure gRPC connections.
|
53
54
|
**kwargs: Additional keyword arguments to be passed to the Workflow.
|
@@ -102,6 +102,7 @@ class ModelBuilder:
|
|
102
102
|
self.folder = self._validate_folder(folder)
|
103
103
|
self.config = self._load_config(os.path.join(self.folder, 'config.yaml'))
|
104
104
|
self._validate_config()
|
105
|
+
self._validate_config_secrets()
|
105
106
|
self._validate_stream_options()
|
106
107
|
self.model_proto = self._get_model_proto()
|
107
108
|
self.model_id = self.model_proto.id
|
@@ -465,6 +466,115 @@ class ModelBuilder:
|
|
465
466
|
"2) set_output_context"
|
466
467
|
)
|
467
468
|
|
469
|
+
def _validate_config_secrets(self):
|
470
|
+
"""
|
471
|
+
Validate the secrets section in the config file.
|
472
|
+
"""
|
473
|
+
if "secrets" not in self.config:
|
474
|
+
return
|
475
|
+
|
476
|
+
secrets = self.config.get("secrets", [])
|
477
|
+
if not isinstance(secrets, list):
|
478
|
+
raise ValueError("The 'secrets' field must be an array.")
|
479
|
+
|
480
|
+
for i, secret in enumerate(secrets):
|
481
|
+
if not isinstance(secret, dict):
|
482
|
+
raise ValueError(f"Secret at index {i} must be a dictionary.")
|
483
|
+
|
484
|
+
# Validate required fields
|
485
|
+
if "id" not in secret or not secret["id"]:
|
486
|
+
raise ValueError(f"Secret at index {i} must have a non-empty 'id' field.")
|
487
|
+
|
488
|
+
if "type" not in secret or not secret["type"]:
|
489
|
+
secret["type"] = "env"
|
490
|
+
|
491
|
+
if "env_var" not in secret or not secret["env_var"]:
|
492
|
+
raise ValueError(f"Secret at index {i} must have a non-empty 'env_var' field.")
|
493
|
+
# Validate secret type
|
494
|
+
if secret["type"] != "env":
|
495
|
+
raise ValueError(
|
496
|
+
f"Secret at index {i} has invalid type '{secret['type']}'. Must be 'env'."
|
497
|
+
)
|
498
|
+
|
499
|
+
logger.info(f"Validated {len(secrets)} secrets in config file.")
|
500
|
+
|
501
|
+
def _process_secrets(self):
|
502
|
+
"""
|
503
|
+
Process secrets from config file and create/validate them using the User client.
|
504
|
+
Returns the processed secrets array for inclusion in ModelVersion.OutputInfo.Params.
|
505
|
+
"""
|
506
|
+
if "secrets" not in self.config:
|
507
|
+
return []
|
508
|
+
|
509
|
+
secrets = self.config.get("secrets", [])
|
510
|
+
if not secrets:
|
511
|
+
return []
|
512
|
+
|
513
|
+
# Get user client for secret operations
|
514
|
+
user = User(
|
515
|
+
user_id=self.config.get('model').get('user_id'),
|
516
|
+
pat=self.client.pat,
|
517
|
+
token=self.client.token,
|
518
|
+
base_url=self.client.base,
|
519
|
+
)
|
520
|
+
|
521
|
+
processed_secrets = []
|
522
|
+
secrets_to_create = []
|
523
|
+
|
524
|
+
for secret in secrets:
|
525
|
+
secret_id = secret["id"]
|
526
|
+
secret_type = secret.get("type", "env")
|
527
|
+
env_var = secret["env_var"]
|
528
|
+
secret_value = secret.get("value") # Optional for existing secrets
|
529
|
+
|
530
|
+
# Check if secret already exists
|
531
|
+
try:
|
532
|
+
existing_secret = user.get_secret(secret_id)
|
533
|
+
logger.info(f"Secret '{secret_id}' already exists, using existing secret.")
|
534
|
+
|
535
|
+
# Add to processed secrets without the value
|
536
|
+
processed_secret = {
|
537
|
+
"id": secret_id,
|
538
|
+
"type": secret_type,
|
539
|
+
"env_var": env_var,
|
540
|
+
}
|
541
|
+
processed_secrets.append(processed_secret)
|
542
|
+
|
543
|
+
except Exception:
|
544
|
+
# Secret doesn't exist, need to create it
|
545
|
+
if secret_value:
|
546
|
+
logger.info(f"Secret '{secret_id}' does not exist, will create it.")
|
547
|
+
secrets_to_create.append(
|
548
|
+
{
|
549
|
+
"id": secret_id,
|
550
|
+
"value": secret_value,
|
551
|
+
"description": secret.get("description", f"Secret for {env_var}"),
|
552
|
+
}
|
553
|
+
)
|
554
|
+
|
555
|
+
# Add to processed secrets
|
556
|
+
processed_secret = {
|
557
|
+
"id": secret_id,
|
558
|
+
"type": secret_type,
|
559
|
+
"env_var": env_var,
|
560
|
+
}
|
561
|
+
processed_secrets.append(processed_secret)
|
562
|
+
else:
|
563
|
+
raise ValueError(
|
564
|
+
f"Secret '{secret_id}' does not exist and no value provided for creation."
|
565
|
+
)
|
566
|
+
|
567
|
+
# Create new secrets if any
|
568
|
+
if secrets_to_create:
|
569
|
+
try:
|
570
|
+
created_secrets = user.create_secrets(secrets_to_create)
|
571
|
+
logger.info(f"Successfully created {len(created_secrets)} new secrets.")
|
572
|
+
except Exception as e:
|
573
|
+
logger.error(f"Failed to create secrets: {e}")
|
574
|
+
raise
|
575
|
+
|
576
|
+
return processed_secrets
|
577
|
+
|
468
578
|
def _is_clarifai_internal(self):
|
469
579
|
"""
|
470
580
|
Check if the current user is a Clarifai internal user based on email domain.
|
@@ -891,19 +1001,21 @@ class ModelBuilder:
|
|
891
1001
|
)
|
892
1002
|
torch_version = dependencies.get('torch', None)
|
893
1003
|
if 'torch' in dependencies:
|
894
|
-
if python_version != DEFAULT_PYTHON_VERSION:
|
895
|
-
raise Exception(
|
896
|
-
f"torch is not supported with Python version {python_version}, please use Python version {DEFAULT_PYTHON_VERSION} in your config.yaml"
|
897
|
-
)
|
898
1004
|
if not torch_version:
|
899
1005
|
logger.info(
|
900
1006
|
f"Setup: torch version not found in requirements.txt, using the default version {DEFAULT_AMD_TORCH_VERSION}"
|
901
1007
|
)
|
902
1008
|
torch_version = DEFAULT_AMD_TORCH_VERSION
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
1009
|
+
elif torch_version not in [DEFAULT_AMD_TORCH_VERSION]:
|
1010
|
+
# Currently, we have only one vLLM image built with the DEFAULT_AMD_TORCH_VERSION.
|
1011
|
+
# If the user requests a different PyTorch version, that specific version will be
|
1012
|
+
# installed during the requirements.txt installation step
|
1013
|
+
torch_version = DEFAULT_AMD_TORCH_VERSION
|
1014
|
+
else:
|
1015
|
+
logger.info(
|
1016
|
+
f"`torch` not found in requirements.txt, using the default torch=={DEFAULT_AMD_TORCH_VERSION}"
|
1017
|
+
)
|
1018
|
+
torch_version = DEFAULT_AMD_TORCH_VERSION
|
907
1019
|
python_version = DEFAULT_PYTHON_VERSION
|
908
1020
|
gpu_version = DEFAULT_AMD_GPU_VERSION
|
909
1021
|
final_image = AMD_VLLM_BASE_IMAGE.format(
|
@@ -912,21 +1024,17 @@ class ModelBuilder:
|
|
912
1024
|
gpu_version=gpu_version,
|
913
1025
|
)
|
914
1026
|
logger.info("Setup: Using vLLM base image to build the Docker image")
|
915
|
-
elif
|
1027
|
+
elif (
|
1028
|
+
'torch' in dependencies
|
1029
|
+
and (dependencies['torch'] in [None, DEFAULT_AMD_TORCH_VERSION])
|
1030
|
+
and python_version == DEFAULT_PYTHON_VERSION
|
1031
|
+
):
|
916
1032
|
torch_version = dependencies['torch']
|
917
|
-
if python_version != DEFAULT_PYTHON_VERSION:
|
918
|
-
raise Exception(
|
919
|
-
f"torch is not supported with Python version {python_version}, please use Python version {DEFAULT_PYTHON_VERSION} in your config.yaml"
|
920
|
-
)
|
921
1033
|
if not torch_version:
|
922
1034
|
logger.info(
|
923
1035
|
f"torch version not found in requirements.txt, using the default version {DEFAULT_AMD_TORCH_VERSION}"
|
924
1036
|
)
|
925
1037
|
torch_version = DEFAULT_AMD_TORCH_VERSION
|
926
|
-
if torch_version not in [DEFAULT_AMD_TORCH_VERSION]:
|
927
|
-
raise Exception(
|
928
|
-
f"torch version {torch_version} not supported, please use one of the following versions: {DEFAULT_AMD_TORCH_VERSION} in your requirements.txt"
|
929
|
-
)
|
930
1038
|
python_version = DEFAULT_PYTHON_VERSION
|
931
1039
|
gpu_version = DEFAULT_AMD_GPU_VERSION
|
932
1040
|
final_image = AMD_TORCH_BASE_IMAGE.format(
|
@@ -1258,6 +1366,29 @@ class ModelBuilder:
|
|
1258
1366
|
metadata_struct.update({'git_registry': git_info})
|
1259
1367
|
model_version_proto.metadata.CopyFrom(metadata_struct)
|
1260
1368
|
|
1369
|
+
# Process and add secrets to output_info.params
|
1370
|
+
try:
|
1371
|
+
processed_secrets = self._process_secrets()
|
1372
|
+
if processed_secrets:
|
1373
|
+
# Initialize output_info.params if not already present
|
1374
|
+
if not model_version_proto.HasField("output_info"):
|
1375
|
+
model_version_proto.output_info.CopyFrom(resources_pb2.OutputInfo())
|
1376
|
+
|
1377
|
+
# Initialize params if not already present
|
1378
|
+
if not model_version_proto.output_info.HasField("params"):
|
1379
|
+
from google.protobuf.struct_pb2 import Struct
|
1380
|
+
|
1381
|
+
model_version_proto.output_info.params.CopyFrom(Struct())
|
1382
|
+
|
1383
|
+
# Add secrets to params
|
1384
|
+
model_version_proto.output_info.params.update({"secrets": processed_secrets})
|
1385
|
+
logger.info(
|
1386
|
+
f"Added {len(processed_secrets)} secrets to model version output_info.params"
|
1387
|
+
)
|
1388
|
+
except Exception as e:
|
1389
|
+
logger.error(f"Failed to process secrets: {e}")
|
1390
|
+
raise
|
1391
|
+
|
1261
1392
|
model_type_id = self.config.get('model').get('model_type_id')
|
1262
1393
|
if model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE:
|
1263
1394
|
if 'concepts' in self.config:
|
@@ -1382,6 +1513,7 @@ class ModelBuilder:
|
|
1382
1513
|
user_id=self.client.user_app_id.user_id,
|
1383
1514
|
app_id=self.client.user_app_id.app_id,
|
1384
1515
|
model_id=self.model_proto.id,
|
1516
|
+
colorize=True,
|
1385
1517
|
)
|
1386
1518
|
logger.info("""\n
|
1387
1519
|
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import time
|
2
|
-
from typing import Iterator, Optional
|
2
|
+
from typing import Iterator, Optional, Union
|
3
3
|
|
4
4
|
from clarifai_grpc.grpc.api import service_pb2
|
5
5
|
from clarifai_grpc.grpc.api.status import status_code_pb2, status_pb2
|
6
6
|
from clarifai_protocol import BaseRunner
|
7
|
-
from clarifai_protocol.utils.health import HealthProbeRequestHandler
|
7
|
+
from clarifai_protocol.utils.health import HealthProbeRequestHandler, start_health_server_thread
|
8
8
|
|
9
9
|
from clarifai.client.auth.helper import ClarifaiAuthHelper
|
10
10
|
from clarifai.utils.constants import STATUS_FAIL, STATUS_MIXED, STATUS_OK, STATUS_UNKNOWN
|
@@ -15,7 +15,7 @@ from ..utils.url_fetcher import ensure_urls_downloaded
|
|
15
15
|
from .model_class import ModelClass
|
16
16
|
|
17
17
|
|
18
|
-
class ModelRunner(BaseRunner
|
18
|
+
class ModelRunner(BaseRunner):
|
19
19
|
"""
|
20
20
|
This is a subclass of the runner class which will handle only the work items relevant to models.
|
21
21
|
"""
|
@@ -32,6 +32,7 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
|
|
32
32
|
pat: Optional[str] = None,
|
33
33
|
token: Optional[str] = None,
|
34
34
|
num_parallel_polls: int = 4,
|
35
|
+
health_check_port: Union[int, None] = 8080,
|
35
36
|
**kwargs,
|
36
37
|
) -> None:
|
37
38
|
super().__init__(
|
@@ -70,10 +71,27 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
|
|
70
71
|
# If auth helper creation fails, proceed without authentication
|
71
72
|
self._auth_helper = None
|
72
73
|
|
74
|
+
# if the model has a handle_liveness_probe method, call it to determine liveness
|
75
|
+
# otherwise rely on self.is_alive from the protocol
|
76
|
+
if hasattr(self.model, 'handle_liveness_probe'):
|
77
|
+
HealthProbeRequestHandler.handle_liveness_probe = self.model.handle_liveness_probe
|
78
|
+
|
79
|
+
# if the model has a handle_readiness_probe method, call it to determine readiness
|
80
|
+
# otherwise rely on self.is_ready from the protocol
|
81
|
+
if hasattr(self.model, 'handle_readiness_probe'):
|
82
|
+
HealthProbeRequestHandler.handle_readiness_probe = self.model.handle_readiness_probe
|
83
|
+
|
84
|
+
# if the model has a handle_startup_probe method, call it to determine startup
|
85
|
+
# otherwise rely on self.is_startup from the protocol
|
86
|
+
if hasattr(self.model, 'handle_startup_probe'):
|
87
|
+
HealthProbeRequestHandler.handle_startup_probe = self.model.handle_startup_probe
|
88
|
+
|
73
89
|
# After model load successfully set the health probe to ready and startup
|
74
90
|
HealthProbeRequestHandler.is_ready = True
|
75
91
|
HealthProbeRequestHandler.is_startup = True
|
76
92
|
|
93
|
+
start_health_server_thread(port=health_check_port, address='')
|
94
|
+
|
77
95
|
def get_runner_item_output_for_status(
|
78
96
|
self, status: status_pb2.Status
|
79
97
|
) -> service_pb2.RunnerItemOutput:
|
@@ -301,27 +319,6 @@ class ModelRunner(BaseRunner, HealthProbeRequestHandler):
|
|
301
319
|
"""Set the model for this runner."""
|
302
320
|
self.model = model
|
303
321
|
|
304
|
-
def handle_liveness_probe(self):
|
305
|
-
# if the model has a handle_liveness_probe method, call it to determine liveness
|
306
|
-
# otherwise rely on HealthProbeRequestHandler.is_alive from the protocol
|
307
|
-
if hasattr(self.model, 'handle_liveness_probe'):
|
308
|
-
HealthProbeRequestHandler.is_alive = self.model.handle_liveness_probe()
|
309
|
-
return super().handle_liveness_probe()
|
310
|
-
|
311
|
-
def handle_readiness_probe(self):
|
312
|
-
# if the model has a handle_readiness_probe method, call it to determine readiness
|
313
|
-
# otherwise rely on HealthProbeRequestHandler.is_ready from the protocol
|
314
|
-
if hasattr(self.model, 'handle_readiness_probe'):
|
315
|
-
HealthProbeRequestHandler.is_ready = self.model.handle_readiness_probe()
|
316
|
-
return super().handle_readiness_probe()
|
317
|
-
|
318
|
-
def handle_startup_probe(self):
|
319
|
-
# if the model has a handle_startup_probe method, call it to determine startup
|
320
|
-
# otherwise rely on HealthProbeRequestHandler.is_startup from the protocol
|
321
|
-
if hasattr(self.model, 'handle_startup_probe'):
|
322
|
-
HealthProbeRequestHandler.is_startup = self.model.handle_startup_probe()
|
323
|
-
return super().handle_startup_probe()
|
324
|
-
|
325
322
|
|
326
323
|
def pmo_iterator(runner_item_iterator, auth_helper=None):
|
327
324
|
for runner_item in runner_item_iterator:
|
clarifai/runners/server.py
CHANGED